diff --git a/.editorconfig b/.editorconfig index 3cd29dd7a348..4ee6cd02539a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,4 @@ -# http://editorconfig.org +# https://editorconfig.org/ root = true @@ -39,3 +39,6 @@ indent_style = tab # Batch files use tabs for indentation [*.bat] indent_style = tab + +[docs/**.txt] +max_line_length = 79 diff --git a/AUTHORS b/AUTHORS index dbc414f5a378..20cc0c75c7ca 100644 --- a/AUTHORS +++ b/AUTHORS @@ -8,28 +8,40 @@ answer newbie questions, and generally made Django that much better: Aaron Cannon Aaron Swartz Aaron T. Myers + Abeer Upadhyay + Abhijeet Viswa + Abhinav Patil + Abhishek Gautam + Adam Allred + Adam Bogdał + Adam Donaghy Adam Johnson - Adam Malinowski + Adam Malinowski Adam Vandenberg Adiyat Mubarak + Adnan Umer Adrian Holovaty Adrien Lemaire Afonso Fernández Nogueira AgarFu Ahmad Alhashemi Ahmad Al-Ibrahim + Ahmed Eltawela ajs + Akash Agrawal Akis Kesoglou Aksel Ethem Akshesh Doshi alang@bright-green.com - Alasdair Nicol + Alasdair Nicol Albert Wang Alcides Fonseca Aleksandra Sendecka Aleksi Häkli Alexander Dutton Alexander Myodov + Alexandr Tatarinov + Alex Becker Alex Couper Alex Dedul Alex Gaynor @@ -38,7 +50,7 @@ answer newbie questions, and generally made Django that much better: Alex Robbins Alexey Boriskin Aljosa Mohorovic - Amit Chakradeo + Amit Chakradeo Amit Ramon Amit Upadhyay A. Murat Eren @@ -51,7 +63,7 @@ answer newbie questions, and generally made Django that much better: Andreas Mock Andreas Pelme Andrés Torres Marroquín - Andrew Brehaut + Andrew Brehaut Andrew Clark Andrew Durdin Andrew Godwin @@ -78,6 +90,7 @@ answer newbie questions, and generally made Django that much better: Artem Gnilov Arthur Arthur Koziel + Arthur Rio Arvis Bickovskis Aryeh Leib Taurog A S Alam @@ -92,6 +105,7 @@ answer newbie questions, and generally made Django that much better: Baptiste Mispelon Barry Pederson Bartolome Sanchez Salado + Bartosz Grabski Bashar Al-Abdulhadi Bastian Kleineidam Batiste Bieler @@ -110,13 +124,16 @@ answer newbie questions, and generally made Django that much better: berto Bill Fenner Bjørn Stabell + Bo Marchman + Bogdan Mateescu Bojan Mihelac Bouke Haarsma Božidar Benko Brad Melin - Brandon Chinn + Brandon Chinn Brant Harris Brendan Hayward + Brendan Quinn Brenton Simpson Brett Cannon Brett Hoerner @@ -125,7 +142,7 @@ answer newbie questions, and generally made Django that much better: Brian Harring Brian Ray Brian Rosner - Bruce Kroeze + Bruce Kroeze Bruno Alla Bruno Renié brut.alll@gmail.com @@ -134,13 +151,18 @@ answer newbie questions, and generally made Django that much better: bthomas btoll@bestweb.net C8E + Caio Ariede Calvin Spealman + Cameron Curry Cameron Knight (ckknight) Can Burak Çilingir Carl Meyer + Carles Pina i Estany Carlos Eduardo de Paula Carlos Matías de la Torre + Carlton Gibson cedric@terramater.net + Chad Whitman ChaosKCW Charlie Leifer charly.wilhelm@gmail.com @@ -154,13 +176,14 @@ answer newbie questions, and generally made Django that much better: Chris Jones Chris Lamb Chris Streeter + Christian Barcenas Christian Metts Christian Oudard Christian Tanzer Christophe Pettus Christopher Adams Christopher Babiak - Christopher Lenz + Christopher Lenz Christoph Mędrela Chris Wagner Chris Wesseling @@ -185,27 +208,29 @@ answer newbie questions, and generally made Django that much better: dAniel hAhler Daniel Jilg Daniel Lindsley - Daniel Poelzleithner + Daniel Poelzleithner Daniel Pyrathon Daniel Roseman Daniel Wiesmann Danilo Bargen Dan Johnson + Dan Palmer Dan Poirier Dan Stephenson Dan Watson dave@thebarproject.com - David Ascher + David Ascher David Avsajanishvili David Blewett David Brenneman David Cramer David Danier David Eklund + David Foster David Gouldin david@kazserve.org David Krauth - David Larlet + David Larlet David Reynolds David Sanders David Schein @@ -232,11 +257,13 @@ answer newbie questions, and generally made Django that much better: dusk@woofle.net Ed Morley eibaan@gmail.com + elky Emmanuelle Delescolle Emil Stenström enlight Enrico Eric Boersma + Eric Brandwein Eric Floehr Eric Florenzano Eric Holscher @@ -248,6 +275,7 @@ answer newbie questions, and generally made Django that much better: Esdras Beleza Espen Grindhaug Eugene Lazutkin + Evan Grim Fabrice Aneche favo@exoweb.net fdr @@ -255,9 +283,12 @@ answer newbie questions, and generally made Django that much better: Filip Noetzel Filip Wasilewski Finn Gruwier Larsen + Flávio Juvenal da Silva Junior flavio.curella@gmail.com Florian Apolloner + Florian Moussous Francisco Albarran Cristobal + François Freitag Frank Tegtmeyer Frank Wierzbicki Frank Wiles @@ -290,7 +321,9 @@ answer newbie questions, and generally made Django that much better: Gonzalo Saavedra Gopal Narayanan Graham Carlyle + Grant Jenks Greg Chapple + Gregor Allensworth Gregor Müllegger Grigory Fateyev Grzegorz Ślusarek @@ -298,11 +331,15 @@ answer newbie questions, and generally made Django that much better: Guillaume Pannatier Gustavo Picon hambaloney + Hannes Ljungberg Hannes Struß + Hasan Ramezani Hawkeye Helen Sherwood-Taylor Henrique Romano Henry Dang + Hidde Bultsma + Himanshu Chauhan hipertracker@gmail.com Hiroki Kiyohara Honza Král @@ -317,16 +354,19 @@ answer newbie questions, and generally made Django that much better: Ian Lee Ibon Idan Gazit + Idan Melamed Ifedapo Olarewaju Igor Kolar Illia Volochii Ilya Semenov + Ingo Klöcker I.S. van Oostveen ivan.chelubeev@gmail.com Ivan Sagalaev (Maniac) Jaap Roes Jack Moffitt Jacob Burch + Jacob Green Jacob Kaplan-Moss Jakub Paczkowski Jakub Wilk @@ -343,13 +383,14 @@ answer newbie questions, and generally made Django that much better: Jan Rademaker Jarek Głowacki Jarek Zgoda - Jason Davies (Esaj) + Jason Davies (Esaj) Jason Huggins Jason McBrayer jason.sidabras@gmail.com Jason Yan Javier Mansilla Jay Parlar + Jay Welborn Jay Wineinger J. Clifford Dyer jcrasta@gmail.com @@ -359,21 +400,25 @@ answer newbie questions, and generally made Django that much better: Jeff Hui Jeffrey Gelens Jeff Triplett + Jeffrey Yancey Jens Diemer Jens Page Jensen Cochran Jeong-Min Lee Jérémie Blaser + Jeremy Bowman Jeremy Carbaugh Jeremy Dunck Jeremy Lainé Jesse Young + Jezeniel Zapanta jhenry Jim Dalton Jimmy Song Jiri Barton Joachim Jablon Joao Oliveira + Joao Pedro Silva Joe Heck Joel Bohman Joel Heenan @@ -383,6 +428,7 @@ answer newbie questions, and generally made Django that much better: Johann Queuniet john@calixto.net John D'Agostino + John D'Ambrosio John Huddleston John Moses John Paulett @@ -403,6 +449,7 @@ answer newbie questions, and generally made Django that much better: Josef Rousek Joseph Kocherhans Josh Smeaton + Joshua Cannon Joshua Ginsberg Jozko Skrablin J. Pablo Fernandez @@ -414,6 +461,7 @@ answer newbie questions, and generally made Django that much better: Julia Matsieva Julian Bez Julien Phalip + Junyoung Choi junzhang.jn@gmail.com Jure Cuhalev Justin Bronn @@ -426,10 +474,12 @@ answer newbie questions, and generally made Django that much better: Karderio Karen Tracey Karol Sikora + Katherine “Kati” Michel Katie Miller Keith Bussell Kenneth Love Kent Hauser + Kevin Grinberg Kevin Kubasik Kevin McConnell Kieran Holland @@ -445,7 +495,7 @@ answer newbie questions, and generally made Django that much better: Lakin Wecker Lars Yencken Lau Bech Lauritzen - Laurent Luce + Laurent Luce Laurent Rahuel lcordier@point45.com Leah Culver @@ -456,14 +506,18 @@ answer newbie questions, and generally made Django that much better: Leo Shklovskii Leo Soto lerouxb@gmail.com + Lex Berezhny Liang Feng limodou + Lincoln Smith Loek van Gent Loïc Bistuer Lowe Thiderman Luan Pablo + Lucas Connors Luciano Ramalho Ludvig Ericson + Luis C. Berrocal Łukasz Langa Łukasz Rekucki Luke Granger-Brown @@ -489,17 +543,20 @@ answer newbie questions, and generally made Django that much better: Mario Gonzalez Mariusz Felisiak Mark Biggers + Mark Gensler mark@junklight.com Mark Lavin Mark Sandstrom Markus Amalthea Magnuson Markus Holtermann Marten Kenbeek + Marti Raudsepp martin.glueck@gmail.com Martin Green Martin Kosír - Martin Mahner + Martin Mahner Martin Maney + Martin von Gagern Mart Sõmermaa Marty Alchin masonsimon+django@gmail.com @@ -511,7 +568,7 @@ answer newbie questions, and generally made Django that much better: Matt Croydon Matt Deacalion Stevens Matt Dennenbaum - Matthew Flanagan + Matthew Flanagan Matthew Schinckel Matthew Somerville Matthew Tretter @@ -519,16 +576,18 @@ answer newbie questions, and generally made Django that much better: Matthias Kestenholz Matthias Pronk Matt Hoskins - Matt McClanahan + Matt McClanahan Matt Riggott Matt Robenolt Mattia Larentis + Mattia Procopio Mattias Loverot mattycakes@gmail.com Max Burstein Max Derkachev Maxime Lorant Maxime Turcotte + Maximilian Merz Maximillian Dornseif mccutchen@gmail.com Meir Kriheli @@ -538,7 +597,9 @@ answer newbie questions, and generally made Django that much better: michael.mcewan@gmail.com Michael Placentra II Michael Radziej + Michael Sanders Michael Schwarz + Michael Sinov Michael Thornhill Michal Chruszcz michal@plovarna.cz @@ -547,13 +608,14 @@ answer newbie questions, and generally made Django that much better: Mihai Preda Mikaël Barbero Mike Axiak - Mike Grouchy + Mike Grouchy Mike Malone Mike Richardson Mike Wiacek Mikhail Korobov Mikko Hellsing Mikołaj Siedlarek + milkomeda Milton Waddams mitakummaa@gmail.com mmarshall @@ -563,34 +625,39 @@ answer newbie questions, and generally made Django that much better: Morten Bagai msaelices msundstr + Mushtaq Ali Mykola Zamkovoi Nagy Károly Nasimul Haque + Nasir Hussain Natalia Bidart Nate Bragg Neal Norwitz Nebojša Dorđević - Ned Batchelder + Ned Batchelder Nena Kojadin + Niall Dalton Niall Kelly Nick Efford Nick Lane Nick Pope Nick Presta Nick Sandford + Nick Sarbicki Niclas Olofsson Nicola Larosa Nicolas Lara Nicolas Noé Niran Babalola Nis Jørgensen - Nowell Strite + Nowell Strite Nuno Mariz oggie rob oggy Oliver Beattie Oliver Rutherfurd Olivier Sels + Olivier Tabone Orestis Markou Orne Brocaar Oscar Ramirez @@ -600,16 +667,20 @@ answer newbie questions, and generally made Django that much better: Panos Laganakos Pascal Hartig Pascal Varet + Patrik Sletmo Paul Bissex Paul Collier Paul Collins + Paul Donohue Paul Lanier Paul McLanahan Paul McMillan Paulo Poiati Paulo Scardine Paul Smith + Pavel Kulikov pavithran s + Pavlo Kapyshin permonik@mesias.brnonet.cz Petar Marić Pete Crosier @@ -632,17 +703,24 @@ answer newbie questions, and generally made Django that much better: pradeep.gowda@gmail.com Preston Holmes Preston Timmons + Priyansh Saxena + Przemysław Buczkowski + Przemysław Suliga + Rachel Tobin Rachel Willmer - Radek Švarz + Radek Švarz + Raffaele Salmaso Rajesh Dhawan Ramez Ashraf Ramin Farajpour Cami Ramiro Morales + Ramon Saraiva Ram Rachum Randy Barlow Raphaël Barrois Raphael Michel Raúl Cumplido + Rebecca Smith Remco Wendt Renaud Parent Renbi Yu @@ -661,10 +739,11 @@ answer newbie questions, and generally made Django that much better: Roberto Aguilar Robert Rock Howard Robert Wittams - Rob Hudson + Rob Hudson Robin Munn + Rodrigo Pinheiro Marques de Araújo Romain Garrigues - Ronny Haryanto + Ronny Haryanto Ross Poulton Rozza Rudolph Froger @@ -677,18 +756,23 @@ answer newbie questions, and generally made Django that much better: ryankanno Ryan Kelly Ryan Niemeyer + Ryan Rubin Ryno Mathee Sam Newman Sander Dijkhuis + Sanket Saurav + Sanyam Khurana Sarthak Mehrish schwank@gmail.com Scot Hacker Scott Barr + Scott Fitsimones Scott Pashley scott@staplefish.com Sean Brant Sebastian Hillig - Sebastian Spiegel + Sebastian Spiegel + Segyo Myung Selwin Ong Sengtha Chay Senko Rašić @@ -698,7 +782,7 @@ answer newbie questions, and generally made Django that much better: Sergey Kolosov Seth Hill Shai Berger - Shannon -jj Behrens + Shannon -jj Behrens Shawn Milochik Silvan Spross Simeon Visser @@ -709,27 +793,35 @@ answer newbie questions, and generally made Django that much better: Simon Meers Simon Williams Simon Willison + Sjoerd Job Postmus Slawek Mikula sloonz smurf@smurf.noris.de sopel + Srinivas Reddy Thatiparthy Stanislas Guerra Stanislaus Madueke + Stanislav Karpov starrynight + Stefan R. Filipek Stefane Fermgier + Stefano Rivera Stéphane Raimbault Stephan Jaekel Stephen Burrows Steven L. Smith (fvox13) - Stuart Langridge + Steven Noorbergen (Xaroth) + Stuart Langridge + Subhav Gautam Sujay S Kumar - Sune Kirkeby + Sune Kirkeby Sung-Jin Hong SuperJared Susan Tan Sutrisno Efendi Swaroop C H Szilveszter Farkas + Taavi Teska Tai Lee Takashi Matsuo Tareque Hossain @@ -738,6 +830,7 @@ answer newbie questions, and generally made Django that much better: thebjorn Thejaswi Puthraya Thijs van Dien + Thom Wiggers Thomas Chaumeny Thomas Güttler Thomas Kerpe @@ -746,21 +839,24 @@ answer newbie questions, and generally made Django that much better: Thomas Stromberg Thomas Tanner tibimicu@gmx.net + Tim Allen Tim Graham Tim Heap Tim Saylor Tobias Kunze - Tobias McNulty + Tobias McNulty tobias@neuyork.de Todd O'Bryan + Tom Christie + Tom Forbes + Tom Insam + Tom Tobin Tomáš Ehrlich Tomáš Kopeček - Tom Christie Tome Cvitan Tomek Paczkowski - Tom Insam + Tomer Chachamu Tommy Beadle - Tom Tobin Tore Lundqvist torne-django@wolfpuppy.org.uk Travis Cline @@ -781,7 +877,9 @@ answer newbie questions, and generally made Django that much better: Vasil Vangelovski Victor Andrée viestards.lists@gmail.com - Ville Säävuori + Viktor Danyliuk + Ville Säävuori + Vinay Karanam Vinay Sajip Vincent Foley Vitaly Babiy @@ -796,12 +894,13 @@ answer newbie questions, and generally made Django that much better: Wiktor Kołodziej Wiley Kestner Wiliam Alves de Souza + Will Ayd William Schwartz Will Hardy Wilson Miner Wim Glenn wojtek - Xia Kai + Xia Kai Yann Fouillat Yann Malet Yasushi Masuda @@ -809,7 +908,9 @@ answer newbie questions, and generally made Django that much better: ymasuda@ethercube.com Yoong Kang Lim Yusuke Miyazaki + Zac Hatfield-Dodds Zachary Voase + Zach Liu Zach Thompson Zain Memon Zak Johnson @@ -828,7 +929,6 @@ A big THANK YOU goes to: Ian Bicking for convincing Adrian to ditch code generation. - Mark Pilgrim for "Dive Into Python" (http://www.diveintopython.net, - http://www.diveintopython3.net). + Mark Pilgrim for "Dive Into Python" (https://www.diveinto.org/python3/). Guido van Rossum for creating Python. diff --git a/INSTALL b/INSTALL index 92be6da6506d..25ff0ec52503 100644 --- a/INSTALL +++ b/INSTALL @@ -1,17 +1,8 @@ Thanks for downloading Django. -To install it, make sure you have Python 2.7 or greater installed. Then run +To install it, make sure you have Python 3.5 or greater installed. Then run this command from the command prompt: - python setup.py install - -If you're upgrading from a previous version, you need to remove it first. - -AS AN ALTERNATIVE, you can just copy the entire "django" directory to Python's -site-packages directory, which is located wherever your Python installation -lives. Some places you might check are: - - /usr/lib/python2.7/site-packages (Unix, Python 2.7) - C:\\PYTHON\site-packages (Windows) + python -m pip install . For more detailed instructions, see docs/intro/install.txt. diff --git a/LICENSE.python b/LICENSE.python index 84a3337c2e52..d51773342935 100644 --- a/LICENSE.python +++ b/LICENSE.python @@ -1,3 +1,14 @@ +Django is licensed under the three-clause BSD license; see the file +LICENSE for details. + +Django includes code from the Python standard library, which is licensed under +the Python license, a permissive open source license. The copyright and license +is included below for compliance with Python's terms. + +---------------------------------------------------------------------- + +Copyright (c) 2001-present Python Software Foundation; All Rights Reserved + A. HISTORY OF THE SOFTWARE ========================== @@ -74,9 +85,9 @@ analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -2011, 2012, 2013, 2014, 2015, 2016 Python Software Foundation; All Rights -Reserved" are retained in Python alone or in any derivative version prepared by -Licensee. +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Python Software Foundation; All +Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and wants to make diff --git a/README.rst b/README.rst index 3afba227fbe8..2a33fa2e640a 100644 --- a/README.rst +++ b/README.rst @@ -25,8 +25,9 @@ ticket here: https://code.djangoproject.com/newticket To get more help: -* Join the ``#django`` channel on irc.freenode.net. Lots of helpful people hang out - there. Read the archives at http://django-irc-logs.com/. +* Join the ``#django`` channel on irc.freenode.net. Lots of helpful people hang + out there. See https://en.wikipedia.org/wiki/Wikipedia:IRC/Tutorial if you're + new to IRC. * Join the django-users mailing list, or read the archives, at https://groups.google.com/group/django-users. diff --git a/django/__init__.py b/django/__init__.py index 1584f27d4b39..ea8d2c6ee5e9 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,8 +1,6 @@ -from __future__ import unicode_literals - from django.utils.version import get_version -VERSION = (1, 11, 0, 'alpha', 0) +VERSION = (2, 2, 15, 'alpha', 0) __version__ = get_version(VERSION) @@ -16,12 +14,11 @@ def setup(set_prefix=True): from django.apps import apps from django.conf import settings from django.urls import set_script_prefix - from django.utils.encoding import force_text from django.utils.log import configure_logging configure_logging(settings.LOGGING_CONFIG, settings.LOGGING) if set_prefix: set_script_prefix( - '/' if settings.FORCE_SCRIPT_NAME is None else force_text(settings.FORCE_SCRIPT_NAME) + '/' if settings.FORCE_SCRIPT_NAME is None else settings.FORCE_SCRIPT_NAME ) apps.populate(settings.INSTALLED_APPS) diff --git a/django/apps/config.py b/django/apps/config.py index edb1146afc6d..f5c971fc9c2e 100644 --- a/django/apps/config.py +++ b/django/apps/config.py @@ -2,23 +2,20 @@ from importlib import import_module from django.core.exceptions import ImproperlyConfigured -from django.utils._os import upath from django.utils.module_loading import module_has_submodule MODELS_MODULE_NAME = 'models' -class AppConfig(object): - """ - Class representing a Django application and its configuration. - """ +class AppConfig: + """Class representing a Django application and its configuration.""" def __init__(self, app_name, app_module): - # Full Python path to the application eg. 'django.contrib.admin'. + # Full Python path to the application e.g. 'django.contrib.admin'. self.name = app_name - # Root module for the application eg. . + # Root module for the application e.g. . self.module = app_module # Reference to the Apps registry that holds this AppConfig. Set by the @@ -28,27 +25,26 @@ def __init__(self, app_name, app_module): # The following attributes could be defined at the class level in a # subclass, hence the test-and-set pattern. - # Last component of the Python path to the application eg. 'admin'. + # Last component of the Python path to the application e.g. 'admin'. # This value must be unique across a Django project. if not hasattr(self, 'label'): self.label = app_name.rpartition(".")[2] - # Human-readable name for the application eg. "Admin". + # Human-readable name for the application e.g. "Admin". if not hasattr(self, 'verbose_name'): self.verbose_name = self.label.title() - # Filesystem path to the application directory eg. - # u'/usr/lib/python2.7/dist-packages/django/contrib/admin'. Unicode on - # Python 2 and a str on Python 3. + # Filesystem path to the application directory e.g. + # '/path/to/django/contrib/admin'. if not hasattr(self, 'path'): self.path = self._path_from_module(app_module) - # Module containing models eg. . Set by import_models(). + # Module containing models e.g. . Set by import_models(). # None if the application doesn't have a models module. self.models_module = None - # Mapping of lower case model names to model classes. Initially set to + # Mapping of lowercase model names to model classes. Initially set to # None to prevent accidental access before import_models() runs. self.models = None @@ -59,8 +55,8 @@ def _path_from_module(self, module): """Attempt to determine app's filesystem path from its module.""" # See #21874 for extended discussion of the behavior of this method in # various cases. - # Convert paths to list because Python 3's _NamespacePath does not - # support indexing. + # Convert paths to list because Python's _NamespacePath doesn't support + # indexing. paths = list(getattr(module, '__path__', [])) if len(paths) != 1: filename = getattr(module, '__file__', None) @@ -80,7 +76,7 @@ def _path_from_module(self, module): "The app module %r has no filesystem location, " "you must configure this app with an AppConfig subclass " "with a 'path' class attribute." % (module,)) - return upath(paths[0]) + return paths[0] @classmethod def create(cls, entry): @@ -122,8 +118,21 @@ def create(cls, entry): cls = getattr(mod, cls_name) except AttributeError: if module is None: - # If importing as an app module failed, that error probably - # contains the most informative traceback. Trigger it again. + # If importing as an app module failed, check if the module + # contains any valid AppConfigs and show them as choices. + # Otherwise, that error probably contains the most informative + # traceback, so trigger it again. + candidates = sorted( + repr(name) for name, candidate in mod.__dict__.items() + if isinstance(candidate, type) and + issubclass(candidate, AppConfig) and + candidate is not AppConfig + ) + if candidates: + raise ImproperlyConfigured( + "'%s' does not contain a class '%s'. Choices are: %s." + % (mod_path, cls_name, ', '.join(candidates)) + ) import_module(entry) else: raise @@ -157,9 +166,9 @@ def create(cls, entry): def get_model(self, model_name, require_ready=True): """ - Returns the model with the given case-insensitive model_name. + Return the model with the given case-insensitive model_name. - Raises LookupError if no model exists with this name. + Raise LookupError if no model exists with this name. """ if require_ready: self.apps.check_models_ready() @@ -173,7 +182,7 @@ def get_model(self, model_name, require_ready=True): def get_models(self, include_auto_created=False, include_swapped=False): """ - Returns an iterable of models. + Return an iterable of models. By default, the following models aren't included: diff --git a/django/apps/registry.py b/django/apps/registry.py index 453e4d42f623..234a830fb963 100644 --- a/django/apps/registry.py +++ b/django/apps/registry.py @@ -1,3 +1,4 @@ +import functools import sys import threading import warnings @@ -5,16 +6,15 @@ from functools import partial from django.core.exceptions import AppRegistryNotReady, ImproperlyConfigured -from django.utils import lru_cache from .config import AppConfig -class Apps(object): +class Apps: """ A registry that stores the configuration of installed applications. - It also keeps track of models eg. to provide reverse-relations. + It also keeps track of models, e.g. to provide reverse relations. """ def __init__(self, installed_apps=()): @@ -42,9 +42,12 @@ def __init__(self, installed_apps=()): # Whether the registry is populated. self.apps_ready = self.models_ready = self.ready = False + # For the autoreloader. + self.ready_event = threading.Event() # Lock for thread-safe population. - self._lock = threading.Lock() + self._lock = threading.RLock() + self.loading = False # Maps ("app_label", "modelname") tuples to lists of functions to be # called when the corresponding model is ready. Used by this class's @@ -57,11 +60,11 @@ def __init__(self, installed_apps=()): def populate(self, installed_apps=None): """ - Loads application configurations and models. + Load application configurations and models. - This method imports each application module and then each model module. + Import each application module and then each model module. - It is thread safe and idempotent, but not reentrant. + It is thread-safe and idempotent, but not reentrant. """ if self.ready: return @@ -72,10 +75,13 @@ def populate(self, installed_apps=None): if self.ready: return - # app_config should be pristine, otherwise the code below won't - # guarantee that the order matches the order in INSTALLED_APPS. - if self.app_configs: + # An RLock prevents other threads from entering this section. The + # compare and set operation below is atomic. + if self.loading: + # Prevent reentrant calls to avoid running AppConfig.ready() + # methods twice. raise RuntimeError("populate() isn't reentrant") + self.loading = True # Phase 1: initialize app configs and import app modules. for entry in installed_apps: @@ -116,33 +122,33 @@ def populate(self, installed_apps=None): app_config.ready() self.ready = True + self.ready_event.set() def check_apps_ready(self): - """ - Raises an exception if all apps haven't been imported yet. - """ + """Raise an exception if all apps haven't been imported yet.""" if not self.apps_ready: + from django.conf import settings + # If "not ready" is due to unconfigured settings, accessing + # INSTALLED_APPS raises a more helpful ImproperlyConfigured + # exception. + settings.INSTALLED_APPS raise AppRegistryNotReady("Apps aren't loaded yet.") def check_models_ready(self): - """ - Raises an exception if all models haven't been imported yet. - """ + """Raise an exception if all models haven't been imported yet.""" if not self.models_ready: raise AppRegistryNotReady("Models aren't loaded yet.") def get_app_configs(self): - """ - Imports applications and returns an iterable of app configs. - """ + """Import applications and return an iterable of app configs.""" self.check_apps_ready() return self.app_configs.values() def get_app_config(self, app_label): """ - Imports applications and returns an app config for the given label. + Import applications and returns an app config for the given label. - Raises LookupError if no application exists with this label. + Raise LookupError if no application exists with this label. """ self.check_apps_ready() try: @@ -156,10 +162,10 @@ def get_app_config(self, app_label): raise LookupError(message) # This method is performance-critical at least for Django's test suite. - @lru_cache.lru_cache(maxsize=None) + @functools.lru_cache(maxsize=None) def get_models(self, include_auto_created=False, include_swapped=False): """ - Returns a list of all installed models. + Return a list of all installed models. By default, the following models aren't included: @@ -173,20 +179,19 @@ def get_models(self, include_auto_created=False, include_swapped=False): result = [] for app_config in self.app_configs.values(): - result.extend(list(app_config.get_models(include_auto_created, include_swapped))) + result.extend(app_config.get_models(include_auto_created, include_swapped)) return result def get_model(self, app_label, model_name=None, require_ready=True): """ - Returns the model matching the given app_label and model_name. + Return the model matching the given app_label and model_name. - As a shortcut, this function also accepts a single argument in the - form .. + As a shortcut, app_label may be in the form .. model_name is case-insensitive. - Raises LookupError if no application exists with this label, or no - model exists with this name in the application. Raises ValueError if + Raise LookupError if no application exists with this label, or no + model exists with this name in the application. Raise ValueError if called with a single argument that doesn't contain exactly one dot. """ if require_ready: @@ -228,9 +233,9 @@ def register_model(self, app_label, model): def is_installed(self, app_name): """ - Checks whether an application with this name exists in the registry. + Check whether an application with this name exists in the registry. - app_name is the full name of the app eg. 'django.contrib.admin'. + app_name is the full name of the app e.g. 'django.contrib.admin'. """ self.check_apps_ready() return any(ac.name == app_name for ac in self.app_configs.values()) @@ -241,8 +246,8 @@ def get_containing_app_config(self, object_name): object_name is the dotted Python path to the object. - Returns the app config for the inner application in case of nesting. - Returns None if the object isn't in any registered app config. + Return the app config for the inner application in case of nesting. + Return None if the object isn't in any registered app config. """ self.check_apps_ready() candidates = [] @@ -268,7 +273,7 @@ def get_registered_model(self, app_label, model_name): "Model '%s.%s' not registered." % (app_label, model_name)) return model - @lru_cache.lru_cache(maxsize=None) + @functools.lru_cache(maxsize=None) def get_swappable_settings_name(self, to_string): """ For a given model string (e.g. "auth.User"), return the name of the @@ -292,7 +297,7 @@ def get_swappable_settings_name(self, to_string): def set_available_apps(self, available): """ - Restricts the set of installed apps used by get_app_config[s]. + Restrict the set of installed apps used by get_app_config[s]. available must be an iterable of application names. @@ -300,10 +305,10 @@ def set_available_apps(self, available): Primarily used for performance optimization in TransactionTestCase. - This method is safe is the sense that it doesn't trigger any imports. + This method is safe in the sense that it doesn't trigger any imports. """ available = set(available) - installed = set(app_config.name for app_config in self.get_app_configs()) + installed = {app_config.name for app_config in self.get_app_configs()} if not available.issubset(installed): raise ValueError( "Available apps isn't a subset of installed apps, extra apps: %s" @@ -318,15 +323,13 @@ def set_available_apps(self, available): self.clear_cache() def unset_available_apps(self): - """ - Cancels a previous call to set_available_apps(). - """ + """Cancel a previous call to set_available_apps().""" self.app_configs = self.stored_app_configs.pop() self.clear_cache() def set_installed_apps(self, installed): """ - Enables a different set of installed apps for get_app_config[s]. + Enable a different set of installed apps for get_app_config[s]. installed must be an iterable in the same format as INSTALLED_APPS. @@ -338,28 +341,26 @@ def set_installed_apps(self, installed): This method may trigger new imports, which may add new models to the registry of all imported models. They will stay in the registry even after unset_installed_apps(). Since it isn't possible to replay - imports safely (eg. that could lead to registering listeners twice), + imports safely (e.g. that could lead to registering listeners twice), models are registered when they're imported and never removed. """ if not self.ready: raise AppRegistryNotReady("App registry isn't ready yet.") self.stored_app_configs.append(self.app_configs) self.app_configs = OrderedDict() - self.apps_ready = self.models_ready = self.ready = False + self.apps_ready = self.models_ready = self.loading = self.ready = False self.clear_cache() self.populate(installed) def unset_installed_apps(self): - """ - Cancels a previous call to set_installed_apps(). - """ + """Cancel a previous call to set_installed_apps().""" self.app_configs = self.stored_app_configs.pop() self.apps_ready = self.models_ready = self.ready = True self.clear_cache() def clear_cache(self): """ - Clears all internal caches, for methods that alter the app registry. + Clear all internal caches, for methods that alter the app registry. This is mostly used in tests. """ @@ -391,7 +392,7 @@ def lazy_model_operation(self, function, *model_keys): # to lazy_model_operation() along with the remaining model args and # repeat until all models are loaded and all arguments are applied. else: - next_model, more_models = model_keys[0], model_keys[1:] + next_model, *more_models = model_keys # This will be executed after the class corresponding to next_model # has been imported and registered. The `func` attribute provides @@ -415,7 +416,7 @@ def apply_next_model(model): def do_pending_operations(self, model): """ Take a newly-prepared model and pass it to each function waiting for - it. This is called at the very end of `Apps.register_model()`. + it. This is called at the very end of Apps.register_model(). """ key = model._meta.app_label, model._meta.model_name for function in self._pending_operations.pop(key, []): diff --git a/django/conf/__init__.py b/django/conf/__init__.py index f99236a7784e..cf91ce83d4d4 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -1,21 +1,46 @@ """ Settings and configuration for Django. -Values will be read from the module specified by the DJANGO_SETTINGS_MODULE environment -variable, and then from django.conf.global_settings; see the global settings file for -a list of all possible variables. +Read values from the module specified by the DJANGO_SETTINGS_MODULE environment +variable, and then from django.conf.global_settings; see the global_settings.py +for a list of all possible variables. """ import importlib import os import time +import traceback +import warnings +from pathlib import Path +import django from django.conf import global_settings from django.core.exceptions import ImproperlyConfigured +from django.utils.deprecation import ( + RemovedInDjango30Warning, RemovedInDjango31Warning, +) from django.utils.functional import LazyObject, empty ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE" +DEFAULT_CONTENT_TYPE_DEPRECATED_MSG = 'The DEFAULT_CONTENT_TYPE setting is deprecated.' +FILE_CHARSET_DEPRECATED_MSG = ( + 'The FILE_CHARSET setting is deprecated. Starting with Django 3.1, all ' + 'files read from disk must be UTF-8 encoded.' +) + + +class SettingsReference(str): + """ + String subclass which references a current settings value. It's treated as + the value in memory but serializes to a settings.NAME attribute reference. + """ + def __new__(self, value, setting_name): + return str.__new__(self, value) + + def __init__(self, value, setting_name): + self.setting_name = setting_name + class LazySettings(LazyObject): """ @@ -26,8 +51,8 @@ class LazySettings(LazyObject): def _setup(self, name=None): """ Load the settings module pointed to by the environment variable. This - is used the first time we need any settings at all, if the user has not - previously configured the settings manually. + is used the first time settings are needed, if the user hasn't + configured settings manually. """ settings_module = os.environ.get(ENVIRONMENT_VARIABLE) if not settings_module: @@ -49,9 +74,7 @@ def __repr__(self): } def __getattr__(self, name): - """ - Return the value of a setting and cache it in self.__dict__. - """ + """Return the value of a setting and cache it in self.__dict__.""" if self._wrapped is empty: self._setup(name) val = getattr(self._wrapped, name) @@ -67,13 +90,11 @@ def __setattr__(self, name, value): self.__dict__.clear() else: self.__dict__.pop(name, None) - super(LazySettings, self).__setattr__(name, value) + super().__setattr__(name, value) def __delattr__(self, name): - """ - Delete a setting and clear it from cache if needed. - """ - super(LazySettings, self).__delattr__(name) + """Delete a setting and clear it from cache if needed.""" + super().__delattr__(name) self.__dict__.pop(name, None) def configure(self, default_settings=global_settings, **options): @@ -91,13 +112,39 @@ def configure(self, default_settings=global_settings, **options): @property def configured(self): - """ - Returns True if the settings have already been configured. - """ + """Return True if the settings have already been configured.""" return self._wrapped is not empty + @property + def DEFAULT_CONTENT_TYPE(self): + stack = traceback.extract_stack() + # Show a warning if the setting is used outside of Django. + # Stack index: -1 this line, -2 the caller. + filename, _line_number, _function_name, _text = stack[-2] + if not filename.startswith(os.path.dirname(django.__file__)): + warnings.warn( + DEFAULT_CONTENT_TYPE_DEPRECATED_MSG, + RemovedInDjango30Warning, + stacklevel=2, + ) + return self.__getattr__('DEFAULT_CONTENT_TYPE') -class Settings(object): + @property + def FILE_CHARSET(self): + stack = traceback.extract_stack() + # Show a warning if the setting is used outside of Django. + # Stack index: -1 this line, -2 the caller. + filename, _line_number, _function_name, _text = stack[-2] + if not filename.startswith(os.path.dirname(django.__file__)): + warnings.warn( + FILE_CHARSET_DEPRECATED_MSG, + RemovedInDjango31Warning, + stacklevel=2, + ) + return self.__getattr__('FILE_CHARSET') + + +class Settings: def __init__(self, settings_module): # update this dict from global settings (but only for ALL_CAPS settings) for setting in dir(global_settings): @@ -128,12 +175,17 @@ def __init__(self, settings_module): if not self.SECRET_KEY: raise ImproperlyConfigured("The SECRET_KEY setting must not be empty.") + if self.is_overridden('DEFAULT_CONTENT_TYPE'): + warnings.warn(DEFAULT_CONTENT_TYPE_DEPRECATED_MSG, RemovedInDjango30Warning) + if self.is_overridden('FILE_CHARSET'): + warnings.warn(FILE_CHARSET_DEPRECATED_MSG, RemovedInDjango31Warning) + if hasattr(time, 'tzset') and self.TIME_ZONE: # When we can, attempt to validate the timezone. If we can't find # this file, no check happens and it's harmless. - zoneinfo_root = '/usr/share/zoneinfo' - if (os.path.exists(zoneinfo_root) and not - os.path.exists(os.path.join(zoneinfo_root, *(self.TIME_ZONE.split('/'))))): + zoneinfo_root = Path('/usr/share/zoneinfo') + zone_info_file = zoneinfo_root.joinpath(*self.TIME_ZONE.split('/')) + if zoneinfo_root.exists() and not zone_info_file.exists(): raise ValueError("Incorrect timezone setting: %s" % self.TIME_ZONE) # Move the time zone info into os.environ. See ticket #2315 for why # we don't do this unconditionally (breaks Windows). @@ -150,10 +202,8 @@ def __repr__(self): } -class UserSettingsHolder(object): - """ - Holder for user configured settings. - """ +class UserSettingsHolder: + """Holder for user configured settings.""" # SETTINGS_MODULE doesn't make much sense in the manually configured # (standalone) case. SETTINGS_MODULE = None @@ -173,16 +223,20 @@ def __getattr__(self, name): def __setattr__(self, name, value): self._deleted.discard(name) - super(UserSettingsHolder, self).__setattr__(name, value) + if name == 'DEFAULT_CONTENT_TYPE': + warnings.warn(DEFAULT_CONTENT_TYPE_DEPRECATED_MSG, RemovedInDjango30Warning) + elif name == 'FILE_CHARSET': + warnings.warn(FILE_CHARSET_DEPRECATED_MSG, RemovedInDjango31Warning) + super().__setattr__(name, value) def __delattr__(self, name): self._deleted.add(name) if hasattr(self, name): - super(UserSettingsHolder, self).__delattr__(name) + super().__delattr__(name) def __dir__(self): return sorted( - s for s in list(self.__dict__) + dir(self.default_settings) + s for s in [*self.__dict__, *dir(self.default_settings)] if s not in self._deleted ) @@ -190,7 +244,7 @@ def is_overridden(self, setting): deleted = (setting in self._deleted) set_locally = (setting in self.__dict__) set_on_default = getattr(self.default_settings, 'is_overridden', lambda s: False)(setting) - return (deleted or set_locally or set_on_default) + return deleted or set_locally or set_on_default def __repr__(self): return '<%(cls)s>' % { diff --git a/django/conf/app_template/admin.py-tpl b/django/conf/app_template/admin.py-tpl index b2ff964e3ba1..8c38f3f3dad5 100644 --- a/django/conf/app_template/admin.py-tpl +++ b/django/conf/app_template/admin.py-tpl @@ -1,3 +1,3 @@ -{{ unicode_literals }}from django.contrib import admin +from django.contrib import admin # Register your models here. diff --git a/django/conf/app_template/apps.py-tpl b/django/conf/app_template/apps.py-tpl index 8d1a017751c8..9b2ce5289c52 100644 --- a/django/conf/app_template/apps.py-tpl +++ b/django/conf/app_template/apps.py-tpl @@ -1,4 +1,4 @@ -{{ unicode_literals }}from django.apps import AppConfig +from django.apps import AppConfig class {{ camel_case_app_name }}Config(AppConfig): diff --git a/django/conf/app_template/models.py-tpl b/django/conf/app_template/models.py-tpl index 7a54b3e371a6..71a836239075 100644 --- a/django/conf/app_template/models.py-tpl +++ b/django/conf/app_template/models.py-tpl @@ -1,3 +1,3 @@ -{{ unicode_literals }}from django.db import models +from django.db import models # Create your models here. diff --git a/django/conf/app_template/tests.py-tpl b/django/conf/app_template/tests.py-tpl index fa96c654d5df..7ce503c2dd97 100644 --- a/django/conf/app_template/tests.py-tpl +++ b/django/conf/app_template/tests.py-tpl @@ -1,3 +1,3 @@ -{{ unicode_literals }}from django.test import TestCase +from django.test import TestCase # Create your tests here. diff --git a/django/conf/app_template/views.py-tpl b/django/conf/app_template/views.py-tpl index 61821e705e82..91ea44a218fb 100644 --- a/django/conf/app_template/views.py-tpl +++ b/django/conf/app_template/views.py-tpl @@ -1,3 +1,3 @@ -{{ unicode_literals }}from django.shortcuts import render +from django.shortcuts import render # Create your views here. diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index f732682b1cfe..f3abfada25f2 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -1,9 +1,7 @@ -# -*- coding: utf-8 -*- """ Default Django settings. Override these with settings in the module pointed to by the DJANGO_SETTINGS_MODULE environment variable. """ -from __future__ import unicode_literals # This is defined here as a do-nothing function because we can't import @@ -23,11 +21,6 @@ def gettext_noop(s): # on a live site. DEBUG_PROPAGATE_EXCEPTIONS = False -# Whether to use the "ETag" header. This saves bandwidth but slows down performance. -# Deprecated (RemovedInDjango21Warning) in favor of ConditionalGetMiddleware -# which sets the ETag regardless of this setting. -USE_ETAGS = False - # People who get code error notifications. # In the format [('Full Name', 'email@example.com'), ('Full Name', 'anotheremail@example.com')] ADMINS = [] @@ -96,6 +89,7 @@ def gettext_noop(s): ('hr', gettext_noop('Croatian')), ('hsb', gettext_noop('Upper Sorbian')), ('hu', gettext_noop('Hungarian')), + ('hy', gettext_noop('Armenian')), ('ia', gettext_noop('Interlingua')), ('id', gettext_noop('Indonesian')), ('io', gettext_noop('Ido')), @@ -103,6 +97,7 @@ def gettext_noop(s): ('it', gettext_noop('Italian')), ('ja', gettext_noop('Japanese')), ('ka', gettext_noop('Georgian')), + ('kab', gettext_noop('Kabyle')), ('kk', gettext_noop('Kazakh')), ('km', gettext_noop('Khmer')), ('kn', gettext_noop('Kannada')), @@ -244,7 +239,7 @@ def gettext_noop(s): # re.compile(r'^NaverBot.*'), # re.compile(r'^EmailSiphon.*'), # re.compile(r'^SiteSucker.*'), -# re.compile(r'^sohu-search') +# re.compile(r'^sohu-search'), # ] DISALLOWED_USER_AGENTS = [] @@ -255,9 +250,9 @@ def gettext_noop(s): # import re # IGNORABLE_404_URLS = [ # re.compile(r'^/apple-touch-icon.*\.png$'), -# re.compile(r'^/favicon.ico$), -# re.compile(r'^/robots.txt$), -# re.compile(r'^/phpmyadmin/), +# re.compile(r'^/favicon.ico$'), +# re.compile(r'^/robots.txt$'), +# re.compile(r'^/phpmyadmin/'), # re.compile(r'\.(cgi|php|pl)$'), # ] IGNORABLE_404_URLS = [] @@ -310,12 +305,12 @@ def gettext_noop(s): FILE_UPLOAD_TEMP_DIR = None # The numeric mode to set newly-uploaded files to. The value should be a mode -# you'd pass directly to os.chmod; see https://docs.python.org/3/library/os.html#files-and-directories. +# you'd pass directly to os.chmod; see https://docs.python.org/library/os.html#files-and-directories. FILE_UPLOAD_PERMISSIONS = None # The numeric mode to assign to newly-created directories, when uploading files. # The value should be a mode as you'd pass to os.chmod; -# see https://docs.python.org/3/library/os.html#files-and-directories. +# see https://docs.python.org/library/os.html#files-and-directories. FILE_UPLOAD_DIRECTORY_PERMISSIONS = None # Python module path where user will place custom format definition. @@ -325,39 +320,39 @@ def gettext_noop(s): FORMAT_MODULE_PATH = None # Default formatting for date objects. See all available format strings here: -# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'N j, Y' # Default formatting for datetime objects. See all available format strings here: -# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATETIME_FORMAT = 'N j, Y, P' # Default formatting for time objects. See all available format strings here: -# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date TIME_FORMAT = 'P' # Default formatting for date objects when only the year and month are relevant. # See all available format strings here: -# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date YEAR_MONTH_FORMAT = 'F Y' # Default formatting for date objects when only the month and day are relevant. # See all available format strings here: -# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date MONTH_DAY_FORMAT = 'F j' # Default short formatting for date objects. See all available format strings here: -# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date SHORT_DATE_FORMAT = 'm/d/Y' # Default short formatting for datetime objects. # See all available format strings here: -# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date SHORT_DATETIME_FORMAT = 'm/d/Y P' # Default formats to be used when parsing dates from input boxes, in order # See all available format string here: -# http://docs.python.org/library/datetime.html#strftime-behavior +# https://docs.python.org/library/datetime.html#strftime-behavior # * Note that these format strings are different from the ones to display dates DATE_INPUT_FORMATS = [ '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06' @@ -369,7 +364,7 @@ def gettext_noop(s): # Default formats to be used when parsing times from input boxes, in order # See all available format string here: -# http://docs.python.org/library/datetime.html#strftime-behavior +# https://docs.python.org/library/datetime.html#strftime-behavior # * Note that these format strings are different from the ones to display dates TIME_INPUT_FORMATS = [ '%H:%M:%S', # '14:30:59' @@ -380,7 +375,7 @@ def gettext_noop(s): # Default formats to be used when parsing dates and times from input boxes, # in order # See all available format string here: -# http://docs.python.org/library/datetime.html#strftime-behavior +# https://docs.python.org/library/datetime.html#strftime-behavior # * Note that these format strings are different from the ones to display dates DATETIME_INPUT_FORMATS = [ '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' @@ -447,12 +442,7 @@ def gettext_noop(s): # List of middleware to use. Order is important; in the request phase, these # middleware will be applied in the order given, and in the response # phase the middleware will be applied in reverse order. -MIDDLEWARE_CLASSES = [ - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', -] - -MIDDLEWARE = None +MIDDLEWARE = [] ############ # SESSIONS # @@ -464,14 +454,17 @@ def gettext_noop(s): SESSION_COOKIE_NAME = 'sessionid' # Age of cookie, in seconds (default: 2 weeks). SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2 -# A string like ".example.com", or None for standard domain cookie. +# A string like "example.com", or None for standard domain cookie. SESSION_COOKIE_DOMAIN = None # Whether the session cookie should be secure (https:// only). SESSION_COOKIE_SECURE = False # The path of the session cookie. SESSION_COOKIE_PATH = '/' -# Whether to use the non-RFC standard httpOnly flag (IE, FF3+, others) +# Whether to use the HttpOnly flag. SESSION_COOKIE_HTTPONLY = True +# Whether to set the flag restricting cookie leaks on cross-site requests. +# This can be 'Lax', 'Strict', or None to disable the flag. +SESSION_COOKIE_SAMESITE = 'Lax' # Whether to save the session data on every request. SESSION_SAVE_EVERY_REQUEST = False # Whether a user's session cookie expires when the Web browser is closed. @@ -523,7 +516,6 @@ def gettext_noop(s): 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', 'django.contrib.auth.hashers.Argon2PasswordHasher', 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', - 'django.contrib.auth.hashers.BCryptPasswordHasher', ] AUTH_PASSWORD_VALIDATORS = [] @@ -549,6 +541,7 @@ def gettext_noop(s): CSRF_COOKIE_PATH = '/' CSRF_COOKIE_SECURE = False CSRF_COOKIE_HTTPONLY = False +CSRF_COOKIE_SAMESITE = 'Lax' CSRF_HEADER_NAME = 'HTTP_X_CSRFTOKEN' CSRF_TRUSTED_ORIGINS = [] CSRF_USE_SESSIONS = False diff --git a/django/conf/locale/__init__.py b/django/conf/locale/__init__.py index 2cdda3cffd8f..720045dadce1 100644 --- a/django/conf/locale/__init__.py +++ b/django/conf/locale/__init__.py @@ -1,6 +1,3 @@ -# -*- encoding: utf-8 -*- -from __future__ import unicode_literals - """ LANG_INFO is a dictionary structure to provide meta information about languages. @@ -251,6 +248,12 @@ 'name': 'Hungarian', 'name_local': 'Magyar', }, + 'hy': { + 'bidi': False, + 'code': 'hy', + 'name': 'Armenian', + 'name_local': 'հայերեն', + }, 'ia': { 'bidi': False, 'code': 'ia', @@ -293,6 +296,12 @@ 'name': 'Georgian', 'name_local': 'ქართული', }, + 'kab': { + 'bidi': False, + 'code': 'kab', + 'name': 'Kabyle', + 'name_local': 'taqbaylit', + }, 'kk': { 'bidi': False, 'code': 'kk', diff --git a/django/conf/locale/af/LC_MESSAGES/django.mo b/django/conf/locale/af/LC_MESSAGES/django.mo index 673404e3cfbd..ea048a7ec6bd 100644 Binary files a/django/conf/locale/af/LC_MESSAGES/django.mo and b/django/conf/locale/af/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/af/LC_MESSAGES/django.po b/django/conf/locale/af/LC_MESSAGES/django.po index e662bd6aa440..f85a36df4ba3 100644 --- a/django/conf/locale/af/LC_MESSAGES/django.po +++ b/django/conf/locale/af/LC_MESSAGES/django.po @@ -1,15 +1,16 @@ # This file is distributed under the same license as the Django package. # # Translators: +# F Wolff , 2019 # Stephen Cox , 2011-2012 -# unklphil , 2014 +# unklphil , 2014,2019 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-02-18 22:37+0000\n" +"Last-Translator: unklphil \n" "Language-Team: Afrikaans (http://www.transifex.com/django/django/language/" "af/)\n" "MIME-Version: 1.0\n" @@ -25,7 +26,7 @@ msgid "Arabic" msgstr "Arabies" msgid "Asturian" -msgstr "" +msgstr "Asturies" msgid "Azerbaijani" msgstr "Aserbeidjans" @@ -61,7 +62,7 @@ msgid "German" msgstr "Duits" msgid "Lower Sorbian" -msgstr "" +msgstr "Neder-Sorbies" msgid "Greek" msgstr "Grieks" @@ -85,7 +86,7 @@ msgid "Argentinian Spanish" msgstr "Argentynse Spaans" msgid "Colombian Spanish" -msgstr "" +msgstr "Kolombiaanse Spaans" msgid "Mexican Spanish" msgstr "Meksikaanse Spaans" @@ -118,7 +119,7 @@ msgid "Irish" msgstr "Iers" msgid "Scottish Gaelic" -msgstr "" +msgstr "Skots-Gaelies" msgid "Galician" msgstr "Galicies" @@ -133,11 +134,14 @@ msgid "Croatian" msgstr "Kroaties" msgid "Upper Sorbian" -msgstr "" +msgstr "Opper-Sorbies" msgid "Hungarian" msgstr "Hongaars" +msgid "Armenian" +msgstr "Armeens" + msgid "Interlingua" msgstr "Interlingua" @@ -145,7 +149,7 @@ msgid "Indonesian" msgstr "Indonesies" msgid "Ido" -msgstr "" +msgstr "Ido" msgid "Icelandic" msgstr "Yslands" @@ -159,6 +163,9 @@ msgstr "Japannees" msgid "Georgian" msgstr "Georgian" +msgid "Kabyle" +msgstr "Kabilies" + msgid "Kazakh" msgstr "Kazakh" @@ -169,7 +176,7 @@ msgid "Kannada" msgstr "Kannada" msgid "Korean" -msgstr "Koreaanse" +msgstr "Koreaans" msgid "Luxembourgish" msgstr "Luxemburgs" @@ -190,13 +197,13 @@ msgid "Mongolian" msgstr "Mongools" msgid "Marathi" -msgstr "" +msgstr "Marathi" msgid "Burmese" msgstr "Birmaans" msgid "Norwegian Bokmål" -msgstr "" +msgstr "Noorweegse Bokmål" msgid "Nepali" msgstr "Nepalees" @@ -229,10 +236,10 @@ msgid "Russian" msgstr "Russiese" msgid "Slovak" -msgstr "Slowaakse" +msgstr "Slowaaks" msgid "Slovenian" -msgstr "Sloveens" +msgstr "Sloweens" msgid "Albanian" msgstr "Albanees" @@ -259,7 +266,7 @@ msgid "Thai" msgstr "Thai" msgid "Turkish" -msgstr "Turkish" +msgstr "Turks" msgid "Tatar" msgstr "Tataars" @@ -271,7 +278,7 @@ msgid "Ukrainian" msgstr "Oekraïens" msgid "Urdu" -msgstr "Urdu" +msgstr "Oerdoe" msgid "Vietnamese" msgstr "Viëtnamees" @@ -280,69 +287,79 @@ msgid "Simplified Chinese" msgstr "Vereenvoudigde Sjinees" msgid "Traditional Chinese" -msgstr "Tradisionele Chinese" +msgstr "Tradisionele Sjinees" msgid "Messages" -msgstr "" +msgstr "Boodskappe" msgid "Site Maps" -msgstr "" +msgstr "Werfkaarte" msgid "Static Files" -msgstr "" +msgstr "Statiese lêers" msgid "Syndication" msgstr "Sindikasie" +msgid "That page number is not an integer" +msgstr "Daai bladsynommer is nie 'n heelgetal nie" + +msgid "That page number is less than 1" +msgstr "Daai bladsynommer is minder as 1" + +msgid "That page contains no results" +msgstr "Daai bladsy bevat geen resultate nie" + msgid "Enter a valid value." -msgstr "Sleutel 'n geldige waarde in." +msgstr "Gee 'n geldige waarde." msgid "Enter a valid URL." -msgstr "Sleutel 'n geldige URL in." +msgstr "Gee ’n geldige URL." msgid "Enter a valid integer." -msgstr "Sleutel 'n geldige heelgetal in." +msgstr "Gee ’n geldige heelgetal." msgid "Enter a valid email address." -msgstr "Sleutel 'n geldige e-pos adres in." +msgstr "Gee ’n geldige e-posadres." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" -"Sleutel 'n geldige \"slak\" wat bestaan ​​uit letters, syfers, beklemtoon of " -"koppel." +"Gee ’n geldige \"slak\" in wat bestaan ​​uit letters, syfers, onderstreep of " +"koppelteken." msgid "" "Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or " "hyphens." msgstr "" +"Gee ’n geldige “slak” in wat bestaan ​​uit Unicode-letters, syfers, " +"onderstreep of koppelteken." msgid "Enter a valid IPv4 address." -msgstr "Sleutel 'n geldige IPv4-adres in." +msgstr "Gee ’n geldige IPv4-adres." msgid "Enter a valid IPv6 address." -msgstr "Voer 'n geldige IPv6-adres in." +msgstr "Gee ’n geldige IPv6-adres." msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Voer 'n geldige IPv4 of IPv6-adres in." +msgstr "Gee ’n geldige IPv4- of IPv6-adres." msgid "Enter only digits separated by commas." -msgstr "Sleutel slegs syfers in wat deur kommas geskei is." +msgstr "Gee slegs syfers in wat deur kommas geskei is." #, python-format msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." msgstr "" -"Maak seker dat hierdie waarde %(limit_value)s is (dit is %(show_value)s )." +"Maak seker dat hierdie waarde %(limit_value)s is (dit is %(show_value)s)." #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." -msgstr "" -"Maak seker dat hierdie waarde minder as of gelyk aan %(limit_value)s is." +msgstr "Maak seker dat hierdie waarde kleiner of gelyk is aan %(limit_value)s." #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." -msgstr "" -"Maak seker dat hierdie waarde groter as of gelyk aan %(limit_value)s is." +msgstr "Maak seker dat hierdie waarde groter of gelyk is aan %(limit_value)s." #, python-format msgid "" @@ -372,6 +389,9 @@ msgstr[1] "" "Maak seker hierdie waarde het op die meeste %(limit_value)d karakters (dit " "het %(show_value)d)." +msgid "Enter a number." +msgstr "Gee ’n getal." + #, 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." @@ -394,6 +414,17 @@ msgstr[0] "" msgstr[1] "" "Maak seker dat daar nie meer as %(max)s syfers voor die desimale punt is nie." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Lêeruitbreiding “%(extension)s” word nie toegelaat nie. Toegelate " +"uitbreidings is: “%(allowed_extensions)s”." + +msgid "Null characters are not allowed." +msgstr "Nul-karakters word nie toegelaat nie." + msgid "and" msgstr "en" @@ -403,7 +434,7 @@ msgstr "%(model_name)s met hierdie %(field_labels)s bestaan alreeds." #, python-format msgid "Value %(value)r is not a valid choice." -msgstr "Waarde %(value)r is nie 'n geldige keuse nie." +msgstr "Waarde %(value)r is nie ’n geldige keuse nie." msgid "This field cannot be null." msgstr "Hierdie veld kan nie nil wees nie." @@ -421,50 +452,54 @@ msgstr "%(model_name)s met hierdie %(field_label)s bestaan ​​alreeds." msgid "" "%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." msgstr "" -"%(field_label)s moet uniek wees vir %(date_field_label)s %(lookup_type)s." +"%(field_label)s moet uniek wees per %(date_field_label)s %(lookup_type)s." #, python-format msgid "Field of type: %(field_type)s" -msgstr "Veld van type: %(field_type)s " +msgstr "Veld van tipe: %(field_type)s " msgid "Integer" msgstr "Heelgetal" #, python-format msgid "'%(value)s' value must be an integer." -msgstr "'%(value)s' waarde moet 'n heelgetal wees." +msgstr "Die waarde “%(value)s” moet ’n heelgetal wees." msgid "Big (8 byte) integer" msgstr "Groot (8 greep) heelgetal" #, python-format msgid "'%(value)s' value must be either True or False." -msgstr "'%(value)s' waarde moet óf True of False wees." +msgstr "Die waarde “%(value)s” moet óf True (waar) óf False (vals) wees." + +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "Die waarde “%(value)s” moet True, False of None wees." msgid "Boolean (Either True or False)" -msgstr "Boole (Eder waar of vals)" +msgstr "Boole (True of False)" #, python-format msgid "String (up to %(max_length)s)" -msgstr "String (tot %(max_length)s)" +msgstr "String (hoogstens %(max_length)s karakters)" msgid "Comma-separated integers" -msgstr "Kommas geskeide heelgetalle" +msgstr "Heelgetalle geskei met kommas" #, python-format msgid "" "'%(value)s' value has an invalid date format. It must be in YYYY-MM-DD " "format." msgstr "" -"'%(value)s' waarde het 'n ongeldige datumformaat. Dit met in die JJJJ-MM-DD " -"formaat wees." +"Die waarde “%(value)s” het ’n ongeldige datumformaat. Dit moet in die " +"formaat JJJJ-MM-DD wees." #, python-format msgid "" "'%(value)s' value has the correct format (YYYY-MM-DD) but it is an invalid " "date." msgstr "" -"'%(value)s' waarde het die korrekte formaat (JJJJ-MM-DD), maar dit is 'n " +"Die waarde “%(value)s” het die korrekte formaat (JJJJ-MM-DD), maar dit is ’n " "ongeldige datum." msgid "Date (without time)" @@ -475,23 +510,23 @@ msgid "" "'%(value)s' value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." "uuuuuu]][TZ] format." msgstr "" -"'%(value)s' waarde se formaat is ongeldig. Dit moet in JJJJ-MM-DD HH:MM[:ss[." -"uuuuuu]][TZ] formaat wees." +"Die waarde “%(value)s” se formaat is ongeldig. Dit moet in die formaat JJJJ-" +"MM-DD HH:MM[:ss[.uuuuuu]][TZ] wees." #, 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' waarde het die korrekte formaat (JJJJ-MM-DD HH:MM[:ss[.uuuuuu]]" -"[TZ]) maar dit is 'n ongeldige datum/tyd." +"Die waarde “%(value)s” het die korrekte formaat (JJJJ-MM-DD HH:MM[:ss[." +"uuuuuu]][TZ]) maar dit is ’n ongeldige datum/tyd." msgid "Date (with time)" msgstr "Datum (met die tyd)" #, python-format msgid "'%(value)s' value must be a decimal number." -msgstr "'%(value)s' waarde moet 'n desimale getal wees." +msgstr "Die waarde “%(value)s” moet ’n desimale getal wees." msgid "Decimal number" msgstr "Desimale getal" @@ -501,45 +536,47 @@ msgid "" "'%(value)s' value has an invalid format. It must be in [DD] [HH:[MM:]]ss[." "uuuuuu] format." msgstr "" +"Die waarde “%(value)s” het ’n ongeldige formaat. Dit moet in die formaat " +"[DD] [HH:[MM:]]ss[.uuuuuu] wees." msgid "Duration" -msgstr "" +msgstr "Duur" msgid "Email address" -msgstr "E-pos adres" +msgstr "E-posadres" msgid "File path" -msgstr "Lêer pad" +msgstr "Lêerpad" #, python-format msgid "'%(value)s' value must be a float." -msgstr "'%(value)s' waarde meote 'n dryfpunt getal wees." +msgstr "Die waarde “%(value)s” moete ’n dryfpuntgetal wees." msgid "Floating point number" -msgstr "Dryfpunt getal" +msgstr "Dryfpuntgetal" msgid "IPv4 address" -msgstr "IPv4 adres" +msgstr "IPv4-adres" msgid "IP address" -msgstr "IP adres" +msgstr "IP-adres" #, python-format msgid "'%(value)s' value must be either None, True or False." -msgstr "'%(value)s' waarde moet óf None, True of False wees." +msgstr "Die waarde “%(value)s” moet None, True of False wees." msgid "Boolean (Either True, False or None)" -msgstr "Boole (Eder waar, vals of niks)" +msgstr "Boole (True, False, of None)" msgid "Positive integer" msgstr "Positiewe heelgetal" msgid "Positive small integer" -msgstr "Positiewe klein heelgetal" +msgstr "Klein positiewe heelgetal" #, python-format msgid "Slug (up to %(max_length)s)" -msgstr "Slug (tot by %(max_length)s)" +msgstr "Slug (tot en met %(max_length)s karakters)" msgid "Small integer" msgstr "Klein heelgetal" @@ -552,16 +589,16 @@ msgid "" "'%(value)s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " "format." msgstr "" -"'%(value)s' waarde se formaat is ongeldig. Dit moet in HH:MM[:ss[.uuuuuu]] " -"formaat wees." +"Die waarde “%(value)s” se formaat is ongeldig. Dit moet in die formaat HH:" +"MM[:ss[.uuuuuu]] wees." #, python-format msgid "" "'%(value)s' value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " "invalid time." msgstr "" -"'%(value)s' waarde het die regte formaat (HH:MM[:ss[.uuuuuu]]) maar is nie " -"'n geldige tyd nie." +"Die waarde “%(value)s” het die regte formaat (HH:MM[:ss[.uuuuuu]]) maar is " +"nie ’n geldige tyd nie." msgid "Time" msgstr "Tyd" @@ -574,7 +611,10 @@ msgstr "Rou binêre data" #, python-format msgid "'%(value)s' is not a valid UUID." -msgstr "" +msgstr "“%(value)s” is nie ’n geldige UUID nie." + +msgid "Universally unique identifier" +msgstr "Universeel unieke identifiseerder" msgid "File" msgstr "Lêer" @@ -584,7 +624,7 @@ msgstr "Prent" #, python-format msgid "%(model)s instance with %(field)s %(value)r does not exist." -msgstr "" +msgstr "%(model)s-objek met %(field)s %(value)r bestaan nie." msgid "Foreign Key (type determined by related field)" msgstr "Vreemde sleutel (tipe bepaal deur verwante veld)" @@ -594,11 +634,11 @@ msgstr "Een-tot-een-verhouding" #, python-format msgid "%(from)s-%(to)s relationship" -msgstr "" +msgstr "%(from)s-%(to)s-verwantskap" #, python-format msgid "%(from)s-%(to)s relationships" -msgstr "" +msgstr "%(from)s-%(to)s-verwantskappe" msgid "Many-to-many relationship" msgstr "Baie-tot-baie-verwantskap" @@ -610,29 +650,30 @@ msgid ":?.!" msgstr ":?.!" msgid "This field is required." -msgstr "Die veld is verpligtend." +msgstr "Dié veld is verpligtend." msgid "Enter a whole number." -msgstr "Sleutel 'n hele getal in." - -msgid "Enter a number." -msgstr "Sleutel 'n nommer in." +msgstr "Tik ’n heelgetal in." msgid "Enter a valid date." -msgstr "Sleutel 'n geldige datum in." +msgstr "Tik ’n geldige datum in." msgid "Enter a valid time." -msgstr "Sleutel 'n geldige tyd in." +msgstr "Tik ’n geldige tyd in." msgid "Enter a valid date/time." -msgstr "Sleutel 'n geldige datum/tyd in." +msgstr "Tik ’n geldige datum/tyd in." msgid "Enter a valid duration." -msgstr "" +msgstr "Tik ’n geldige tydsduur in." + +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Die aantal dae moet tussen {min_days} en {max_days} wees." msgid "No file was submitted. Check the encoding type on the form." msgstr "" -"Geen lêer is ingedien nie. Maak seker die kodering tipe op die vorm is reg." +"Geen lêer is ingedien nie. Maak seker die koderingtipe op die vorm is reg." msgid "No file was submitted." msgstr "Geen lêer is ingedien nie." @@ -645,35 +686,35 @@ 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] "" -"Maak seker hierdie lêernaam het op die meeste %(max)d karakter (dit het " +"Maak seker hierdie lêernaam het hoogstens %(max)d karakter (dit het " "%(length)d)." msgstr[1] "" -"Maak seker hierdie lêernaam het op die meeste %(max)d karakters (dit het " +"Maak seker hierdie lêernaam het hoogstens %(max)d karakters (dit het " "%(length)d)." msgid "Please either submit a file or check the clear checkbox, not both." -msgstr "Stuur die lêer of tiek die maak skoon boksie, nie beide nie." +msgstr "Dien die lêer in óf merk die Maak skoon-boksie, nie altwee nie." msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "" -"Laai 'n geldige prent. Die lêer wat jy opgelaai het is nie 'n prent nie of " -"dit is 'n korrupte prent." +"Laai ’n geldige prent. Die lêer wat jy opgelaai het, is nie ’n prent nie of " +"dit is ’n korrupte prent." #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "" -"Kies 'n geldige keuse. %(value)s is nie een van die beskikbare keuses nie." +"Kies 'n geldige keuse. %(value)s is nie een van die beskikbare keuses nie." msgid "Enter a list of values." -msgstr "Sleatel 'n lys van waardes in." +msgstr "Tik ’n lys waardes in." msgid "Enter a complete value." -msgstr "Sleutel 'n volledige waarde in." +msgstr "Tik ’n volledige waarde in." msgid "Enter a valid UUID." -msgstr "" +msgstr "Tik ’n geldig UUID in." #. Translators: This is the default suffix added to form field labels msgid ":" @@ -684,7 +725,7 @@ msgid "(Hidden field %(name)s) %(error)s" msgstr "(Versteekte veld %(name)s) %(error)s" msgid "ManagementForm data is missing or has been tampered with" -msgstr "" +msgstr "Die ManagementForm-data ontbreek of is mee gepeuter" #, python-format msgid "Please submit %d or fewer forms." @@ -706,11 +747,11 @@ msgstr "Verwyder" #, python-format msgid "Please correct the duplicate data for %(field)s." -msgstr "Korrigeer die dubbele data vir %(field)s ." +msgstr "Korrigeer die dubbele data vir %(field)s." #, python-format msgid "Please correct the duplicate data for %(field)s, which must be unique." -msgstr "Korrigeer die dubbele data vir %(field)s , dit moet uniek wees." +msgstr "Korrigeer die dubbele data vir %(field)s, dit moet uniek wees." #, python-format msgid "" @@ -718,31 +759,33 @@ msgid "" "for the %(lookup)s in %(date_field)s." msgstr "" "Korrigeer die dubbele data vir %(field_name)s, dit moet uniek wees vir die " -"%(lookup)s in %(date_field)s ." +"%(lookup)s in %(date_field)s." msgid "Please correct the duplicate values below." msgstr "Korrigeer die dubbele waardes hieronder." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "" -"Die inlyn vreemde sleutel stem nie ooreen met die ouer primêre sleutel." +msgid "The inline value did not match the parent instance." +msgstr "Die waarde inlyn pas nie by die ouerobjek nie." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" -"Kies 'n geldige keuse. Daardie keuse is nie een van die beskikbare keuses " +"Kies ’n geldige keuse. Daardie keuse is nie een van die beskikbare keuses " "nie." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" is nie 'n geldige waarde vir 'n primêre sleutel nie." +msgid "\"%(pk)s\" is not a valid value." +msgstr "“%(pk)s” is nie 'n geldige waarde nie." #, 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 kon nie in tydsone %(current_timezone)s vertolk word nie; dit " -"mag dubbelsinnig wees, of nie bestaan nie." +"%(datetime)s kon nie in die tydsone %(current_timezone)s vertolk word nie; " +"dit is dalk dubbelsinnig, of bestaan nie." + +msgid "Clear" +msgstr "Maak skoon" msgid "Currently" msgstr "Tans" @@ -750,9 +793,6 @@ msgstr "Tans" msgid "Change" msgstr "Verander" -msgid "Clear" -msgstr "Maak skoon" - msgid "Unknown" msgstr "Onbekend" @@ -792,10 +832,10 @@ msgid "%s PB" msgstr "%s PB" msgid "p.m." -msgstr "nm" +msgstr "nm." msgid "a.m." -msgstr "vm" +msgstr "vm." msgid "PM" msgstr "NM" @@ -894,7 +934,7 @@ msgid "feb" msgstr "feb" msgid "mar" -msgstr "mar" +msgstr "mrt" msgid "apr" msgstr "apr" @@ -1020,19 +1060,19 @@ msgid "December" msgstr "Desember" msgid "This is not a valid IPv6 address." -msgstr "HIerdie is nie 'n geldige IPv6-adres nie." +msgstr "Hierdie is nie ’n geldige IPv6-adres nie." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s…" msgid "or" msgstr "of" #. Translators: This string is used as a separator between list elements msgid ", " -msgstr "," +msgstr ", " #, python-format msgid "%d year" @@ -1074,10 +1114,10 @@ msgid "0 minutes" msgstr "0 minute" msgid "Forbidden" -msgstr "Verbied" +msgstr "Verbode" msgid "CSRF verification failed. Request aborted." -msgstr "" +msgstr "CSRF-verifikasie het misluk. Versoek is laat val." msgid "" "You are seeing this message because this HTTPS site requires a 'Referer " @@ -1085,57 +1125,67 @@ msgid "" "required for security reasons, to ensure that your browser is not being " "hijacked by third parties." msgstr "" +"U sien hierdie boodskap omdat dié HTTPS-werf vereis dat u blaaier ’n " +"“Referer header” moet stuur, maar dit is nie gestuur nie. Hierdie header is " +"vir sekuriteitsredes nodig om te verseker dat u blaaier nie deur derde " +"partye gekaap is nie." 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 "" +"As “Referer headers” in u blaaier gedeaktiveer is, heraktiveer hulle asb. " +"ten minste vir dié werf, of vir HTTPS-verbindings, of vir “same-origin”-" +"versoeke." + +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." +msgstr "" +"Indien u die -etiket gebruik " +"of die “Referrer-Policy: no-referrer header” gebruik, verwyder hulle asb. " +"Die CSRF-beskerming vereis die “Referer header” om streng kontrole van die " +"verwysende bladsy te doen. Indien u besorg is oor privaatheid, gebruik " +"alternatiewe soos vir skakels na " +"derdepartywebwerwe." 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 "" +"U sien hierdie boodskap omdat dié werf ’n CSRF-koekie benodig wanneer vorms " +"ingedien word. Dié koekie word vir sekuriteitsredes benodig om te te " +"verseker dat u blaaier nie deur derde partye gekaap word nie." 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 "" +"Indien koekies in u blaaier gedeaktiveer is, heraktiveer hulle asb ten " +"minste vir dié werf, of vir “same-origin”-versoeke." msgid "More information is available with DEBUG=True." msgstr "Meer inligting is beskikbaar met DEBUG=True." -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" -msgstr "Geen jaar gespesifiseer" +msgstr "Geen jaar gespesifiseer nie" + +msgid "Date out of range" +msgstr "Datum buite omvang" msgid "No month specified" -msgstr "Geen maand gespesifiseer" +msgstr "Geen maand gespesifiseer nie" msgid "No day specified" -msgstr "Geen dag gespesifiseer" +msgstr "Geen dag gespesifiseer nie" msgid "No week specified" -msgstr "Geen week gespesifiseer" +msgstr "Geen week gespesifiseer nie" #, python-format msgid "No %(verbose_name_plural)s available" @@ -1146,13 +1196,12 @@ msgid "" "Future %(verbose_name_plural)s not available because %(class_name)s." "allow_future is False." msgstr "" -"Toekomstige %(verbose_name_plural)s is nie beskikbaar nie, omdat " +"Toekomstige %(verbose_name_plural)s is nie beskikbaar nie, omdat " "%(class_name)s.allow_future vals is." #, python-format msgid "Invalid date string '%(datestr)s' given format '%(format)s'" -msgstr "" -"Ongeldige datum string '%(datestr)s' die formaat moet wees '%(format)s'" +msgstr "Ongeldige datumstring “%(datestr)s” vir formaat “%(format)s”" #, python-format msgid "No %(verbose_name)s found matching the query" @@ -1160,8 +1209,7 @@ msgstr "Geen %(verbose_name)s gevind vir die soektog" msgid "Page is not 'last', nor can it be converted to an int." msgstr "" -"Bladsy is nie 'laaste' nie, en dit kan nie omgeskakel word na 'n heelgetal " -"nie." +"Bladsy is nie “last” nie, en dit kan nie omgeskakel word na ’n heelgetal nie." #, python-format msgid "Invalid page (%(page_number)s): %(message)s" @@ -1169,15 +1217,59 @@ msgstr "Ongeldige bladsy (%(page_number)s): %(message)s" #, python-format msgid "Empty list and '%(class_name)s.allow_empty' is False." -msgstr "Leë lys en ' %(class_name)s.allow_empty' is vals." +msgstr "Leë lys en “%(class_name)s.allow_empty” is vals." msgid "Directory indexes are not allowed here." -msgstr "Gids indekse word nie hier toegelaat nie." +msgstr "Gidsindekse word nie hier toegelaat nie." #, python-format msgid "\"%(path)s\" does not exist" -msgstr "\"%(path)s\" bestaan nie" +msgstr "“%(path)s” bestaan nie" #, python-format msgid "Index of %(directory)s" msgstr "Indeks van %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: die webraamwerk vir perfeksioniste met sperdatums." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Sien die vrystellingsnotas vir Django " +"%(version)s" + +msgid "The install worked successfully! Congratulations!" +msgstr "Die installasie was suksesvol! Geluk!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"U sien dié bladsy omdat DEBUG=True in die settings-lêer is en geen URL’e opgestel is nie." + +msgid "Django Documentation" +msgstr "Django-dokumentasie" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "Kom aan die gang met Django" + +msgid "Django Community" +msgstr "Django-gemeenskap" + +msgid "Connect, get help, or contribute" +msgstr "Kontak, kry hulp om dra by" diff --git a/django/conf/locale/ar/LC_MESSAGES/django.mo b/django/conf/locale/ar/LC_MESSAGES/django.mo index 78219160bd06..6330ab5d0ac9 100644 Binary files a/django/conf/locale/ar/LC_MESSAGES/django.mo and b/django/conf/locale/ar/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/ar/LC_MESSAGES/django.po b/django/conf/locale/ar/LC_MESSAGES/django.po index 1a1429b8374b..d0c82d86a1c5 100644 --- a/django/conf/locale/ar/LC_MESSAGES/django.po +++ b/django/conf/locale/ar/LC_MESSAGES/django.po @@ -10,9 +10,9 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-07-18 08:41+0000\n" -"Last-Translator: Bashar Al-Abdulhadi\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" +"Last-Translator: Jannis Leidel \n" "Language-Team: Arabic (http://www.transifex.com/django/django/language/ar/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -297,6 +297,15 @@ msgstr "الملفات الثابتة" msgid "Syndication" msgstr "توظيف النشر" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "أدخل قيمة صحيحة." @@ -309,6 +318,7 @@ msgstr "أدخل رقم صالح." msgid "Enter a valid email address." msgstr "أدخل عنوان بريد إلكتروني صحيح." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "أدخل اختصار 'slug' صحيح يتكوّن من أحرف، أرقام، شرطات سفلية وعاديّة." @@ -426,6 +436,15 @@ msgstr[3] "تحقق من أن تدخل %(max)s أرقام قبل الفاصل ا msgstr[4] "تحقق من أن تدخل %(max)s أرقام قبل الفاصل العشري لا أكثر." msgstr[5] "تحقق من أن تدخل %(max)s أرقام قبل الفاصل العشري لا أكثر." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "و" @@ -772,15 +791,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "رجاءً صحّح القيم المُكرّرة أدناه." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "حقل foreign key المحدد لا يطابق الحقل الرئيسي له." +msgid "The inline value did not match the parent instance." +msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "انتق خياراً صحيحاً. اختيارك ليس أحد الخيارات المتاحة." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" قيمة غير صحيحة للرقم المرجعي." +msgid "\"%(pk)s\" is not a valid value." +msgstr "" #, python-format msgid "" @@ -790,15 +809,15 @@ msgstr "" "%(datetime)s لا يمكن تفسيرها في المنطقة الزمنية %(current_timezone)s; قد " "تكون غامضة أو أنها غير موجودة." +msgid "Clear" +msgstr "تفريغ" + msgid "Currently" msgstr "حالياً" msgid "Change" msgstr "عدّل" -msgid "Clear" -msgstr "تفريغ" - msgid "Unknown" msgstr "مجهول" @@ -1172,6 +1191,14 @@ msgstr "" "بالنسبة لهذا الموقع، أو لاتصالات 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." +msgstr "" + 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 " @@ -1191,33 +1218,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "يتوفر مزيد من المعلومات عند ضبط الخيار DEBUG=True." -msgid "Welcome to Django" -msgstr "مرحبا بك في جانغو" - -msgid "It worked!" -msgstr "أنه فعّال!" - -msgid "Congratulations on your first Django-powered page." -msgstr "تهانينا على صفحتك الأولى بدعم من جانغو." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"وبطبيعة الحال، لم يتم عمل أي عمل فعلي حتى الآن. الخطوة التالية هي تشغيل أول " -"تطبيق لك عبر الأمر التالي\n" -" python manage.py startapp [app_label] ." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"تظهر لك هذه الرسالة لأنه لديك DEBUG = True في ملف إعدادات جانغو " -"الخاص بك، و ايضا لعدم تكوين أي عناوين المواقع. إبدأ العمل!" - msgid "No year specified" msgstr "لم تحدد السنة" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "لم تحدد الشهر" @@ -1268,3 +1274,41 @@ msgstr "المسار \"%(path)s\" غير موجود." #, python-format msgid "Index of %(directory)s" msgstr "فهرس لـ %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/ar/formats.py b/django/conf/locale/ar/formats.py index 1cdba2d98440..19cc8601b75f 100644 --- a/django/conf/locale/ar/formats.py +++ b/django/conf/locale/ar/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j F، Y' TIME_FORMAT = 'g:i A' # DATETIME_FORMAT = @@ -15,7 +12,7 @@ # FIRST_DAY_OF_WEEK = # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # DATE_INPUT_FORMATS = # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = diff --git a/django/conf/locale/ast/LC_MESSAGES/django.mo b/django/conf/locale/ast/LC_MESSAGES/django.mo index be190bcf5052..6c1e32e8c0aa 100644 Binary files a/django/conf/locale/ast/LC_MESSAGES/django.mo and b/django/conf/locale/ast/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/ast/LC_MESSAGES/django.po b/django/conf/locale/ast/LC_MESSAGES/django.po index 60d6e2eba090..14ac3f6600b1 100644 --- a/django/conf/locale/ast/LC_MESSAGES/django.po +++ b/django/conf/locale/ast/LC_MESSAGES/django.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Asturian (http://www.transifex.com/django/django/language/" "ast/)\n" @@ -293,6 +293,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "Introduz un valor válidu." @@ -305,6 +314,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "Introduz una direición de corréu válida." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -390,6 +400,15 @@ msgstr[0] "" msgstr[1] "" "Asegúrate que nun hai más de %(max)s díxitos enantes del puntu decimal." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "y" @@ -707,9 +726,8 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Por favor, igua los valores duplicaos embaxo" -msgid "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." msgstr "" -"La calve foriata en llinia nun concasa cola clave primaria d'instancia pá." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" @@ -717,8 +735,8 @@ msgstr "" "disponibles." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" nun ye un valor válidu pa la clave primaria." +msgid "\"%(pk)s\" is not a valid value." +msgstr "" #, python-format msgid "" @@ -728,15 +746,15 @@ msgstr "" "Nun pudo interpretase %(datetime)s nel fusu horariu %(current_timezone)s; " "pue ser ambiguu o pue nun esistir." +msgid "Clear" +msgstr "Llimpiar" + msgid "Currently" msgstr "Anguaño" msgid "Change" msgstr "Camudar" -msgid "Clear" -msgstr "Llimpiar" - msgid "Unknown" msgstr "Desconocíu" @@ -1076,6 +1094,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1090,28 +1116,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "Nun s'especificó l'añu" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "Nun s'especificó'l mes" @@ -1162,3 +1172,41 @@ msgstr "\"%(path)s\" nun esiste" #, python-format msgid "Index of %(directory)s" msgstr "Índiz de %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/az/LC_MESSAGES/django.mo b/django/conf/locale/az/LC_MESSAGES/django.mo index 5ed900c94e95..d0489e2227db 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 4e60389c69fd..60ddc200e58b 100644 --- a/django/conf/locale/az/LC_MESSAGES/django.po +++ b/django/conf/locale/az/LC_MESSAGES/django.po @@ -1,14 +1,15 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Emin Mastizada , 2018 # Emin Mastizada , 2015-2016 # Metin Amiroff , 2011 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-08-05 11:00+0000\n" +"POT-Creation-Date: 2018-05-17 11:49+0200\n" +"PO-Revision-Date: 2018-09-09 12:46+0000\n" "Last-Translator: Emin Mastizada \n" "Language-Team: Azerbaijani (http://www.transifex.com/django/django/language/" "az/)\n" @@ -159,6 +160,9 @@ msgstr "Yaponca" msgid "Georgian" msgstr "Gürcücə" +msgid "Kabyle" +msgstr "Kabile" + msgid "Kazakh" msgstr "Qazax" @@ -294,6 +298,15 @@ msgstr "Statik Fayllar" msgid "Syndication" msgstr "Sindikasiya" +msgid "That page number is not an integer" +msgstr "Səhifə nömrəsi rəqəm deyil" + +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" + msgid "Enter a valid value." msgstr "Düzgün qiymət daxil edin." @@ -306,6 +319,7 @@ msgstr "Düzgün rəqəm daxil edin." msgid "Enter a valid email address." msgstr "Düzgün e-poçt ünvanı daxil edin." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -373,6 +387,9 @@ msgstr[1] "" "Bu dəyərin ən çox %(limit_value)d simvol olduğuna əmin olun (%(show_value)d " "var)" +msgid "Enter a number." +msgstr "Ədəd daxil edin." + #, 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." @@ -393,6 +410,17 @@ msgid_plural "" msgstr[0] "Onluq hissədən əvvəl %(max)s rəqəmdən çox olmadığına əmin olun." msgstr[1] "Onluq hissədən əvvəl %(max)s rəqəmdən çox olmadığına əmin olun." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"'%(extension)s' fayl uzantısına icazə verilmir. İcazə verilən fayl " +"uzantıları: '%(allowed_extensions)s'" + +msgid "Null characters are not allowed." +msgstr "Null simvollara icazə verilmir." + msgid "and" msgstr "və" @@ -441,6 +469,10 @@ msgstr "Böyük (8 bayt) tam ədəd" msgid "'%(value)s' value must be either True or False." msgstr "'%(value)s' dəyəri True və ya False olmalıdır." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "'%(value)s' dəyəri True, False və ya None olmalıdır." + msgid "Boolean (Either True or False)" msgstr "Bul (ya Doğru, ya Yalan)" @@ -456,15 +488,14 @@ msgid "" "'%(value)s' value has an invalid date format. It must be in YYYY-MM-DD " "format." msgstr "" -"'%(value)s' dəyəri səhv tarix formatındadır. Bu İİİİ-AA-GG formatında " -"olmalıdır." +"'%(value)s' dəyəri səhv tarix formatındadır. Formatı YYYY-MM-DD olmalıdır." #, python-format 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 (İİİİ-AA-GG) amma bu xətalı tarixdir." +"'%(value)s dəyəri düzgün formatdadır (YYYY-MM-DD) amma bu xətalı tarixdir." msgid "Date (without time)" msgstr "Tarix (saatsız)" @@ -474,19 +505,23 @@ msgid "" "'%(value)s' value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." "uuuuuu]][TZ] format." msgstr "" +"'%(value)s' dəyərinin formatı səhvdir. Formatı YYYY-MM-DD HH:MM[:ss[.uuuuuu]]" +"[TZ] olmalıdır." #, 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' dəyərinin formatı düzgündür (YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) " +"ancaq tarix səhvdir." msgid "Date (with time)" msgstr "Tarix (vaxt ilə)" #, python-format msgid "'%(value)s' value must be a decimal number." -msgstr "" +msgstr "'%(value)s' dəyəri decimal rəqəm olmalıdır." msgid "Decimal number" msgstr "Rasional ədəd" @@ -496,6 +531,8 @@ msgid "" "'%(value)s' value has an invalid format. It must be in [DD] [HH:[MM:]]ss[." "uuuuuu] format." msgstr "" +"'%(value)s' dəyərinin formatı səhvdir. Formatı [DD] [HH:[MM:]]ss[.uuuuuu] " +"olmalıdır." msgid "Duration" msgstr "Müddət" @@ -508,7 +545,7 @@ msgstr "Faylın ünvanı" #, python-format msgid "'%(value)s' value must be a float." -msgstr "" +msgstr "'%(value)s' dəyəri float olmalıdır." msgid "Floating point number" msgstr "Sürüşən vergüllü ədəd" @@ -521,7 +558,7 @@ msgstr "IP ünvan" #, python-format msgid "'%(value)s' value must be either None, True or False." -msgstr "" +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ə)" @@ -547,12 +584,15 @@ msgid "" "'%(value)s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " "format." msgstr "" +"'%(value)s' dəyərinin formatı səhvdir. Formatı HH:MM[:ss[.uuuuuu]] olmalıdır." #, python-format msgid "" "'%(value)s' value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " "invalid time." msgstr "" +"'%(value)s' dəyəri düzgün formatdadır (HH:MM[:ss[.uuuuuu]]), ancaq vaxtı " +"səhvdir." msgid "Time" msgstr "Vaxt" @@ -561,7 +601,7 @@ msgid "URL" msgstr "URL" msgid "Raw binary data" -msgstr "" +msgstr "Düz ikili (binary) məlumat" #, python-format msgid "'%(value)s' is not a valid UUID." @@ -575,7 +615,7 @@ msgstr "Şəkil" #, python-format msgid "%(model)s instance with %(field)s %(value)r does not exist." -msgstr "" +msgstr "%(field)s dəyəri %(value)r olan %(model)s mövcud deyil." msgid "Foreign Key (type determined by related field)" msgstr "Xarici açar (bağlı olduğu sahəyə uyğun tipi alır)" @@ -585,11 +625,11 @@ msgstr "Birin-birə münasibət" #, python-format msgid "%(from)s-%(to)s relationship" -msgstr "" +msgstr "%(from)s-%(to)s əlaqəsi" #, python-format msgid "%(from)s-%(to)s relationships" -msgstr "" +msgstr "%(from)s-%(to)s əlaqələri" msgid "Many-to-many relationship" msgstr "Çoxun-çoxa münasibət" @@ -606,9 +646,6 @@ msgstr "Bu sahə vacibdir." msgid "Enter a whole number." msgstr "Tam ədəd daxil edin." -msgid "Enter a number." -msgstr "Ədəd daxil edin." - msgid "Enter a valid date." msgstr "Düzgün tarix daxil edin." @@ -621,6 +658,10 @@ msgstr "Düzgün tarix/vaxt daxil edin." msgid "Enter a valid duration." msgstr "Keçərli müddət daxil edin." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Günlərin sayı {min_days} ilə {max_days} arasında olmalıdır." + msgid "No file was submitted. Check the encoding type on the form." msgstr "Fayl göndərilməyib. Vərəqənin (\"form\") şifrələmə tipini yoxlayın." @@ -635,7 +676,9 @@ 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] "" +"Bu fayl adının ən çox %(max)d simvol olduğuna əmin olun (%(length)d var)." msgstr[1] "" +"Bu fayl adının ən çox %(max)d simvol olduğuna əmin olun (%(length)d var)." msgid "Please either submit a file or check the clear checkbox, not both." msgstr "" @@ -670,7 +713,7 @@ msgid "(Hidden field %(name)s) %(error)s" msgstr "(Gizli %(name)s sahəsi) %(error)s" msgid "ManagementForm data is missing or has been tampered with" -msgstr "" +msgstr "ManagementForm məlumatları əksikdir və ya korlanıb" #, python-format msgid "Please submit %d or fewer forms." @@ -711,15 +754,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Aşağıda təkrarlanan qiymətlərə düzəliş edin." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "Xarici açar ana obyektin əsas açarı ilə üst-üstə düşmür." +msgid "The inline value did not match the parent instance." +msgstr "Sətiriçi dəyər ana nüsxəyə uyğun deyil." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Düzgün seçim edin. Bu seçim mümkün deyil." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" əsas açar üçün keçərli dəyər deyil." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" düzgün dəyər deyil." #, python-format msgid "" @@ -729,15 +772,15 @@ msgstr "" "%(datetime)s %(current_timezone)s zaman qurşağında ifadə oluna bilmir; ya " "duallıq, ya da yanlışlıq var." +msgid "Clear" +msgstr "Təmizlə" + msgid "Currently" msgstr "Hal-hazırda" msgid "Change" msgstr "Dəyiş" -msgid "Clear" -msgstr "Təmizlə" - msgid "Unknown" msgstr "Məlum deyil" @@ -1070,49 +1113,57 @@ msgid "" "required for security reasons, to ensure that your browser is not being " "hijacked by third parties." msgstr "" +"Bu HTTPS sayt səyyahınız tərəfindən 'Referer header' göndərilməsini tələb " +"edir, amma göndərilmir. Bu başlıq səyyahınızın üçüncü biri tərəfindən hack-" +"lənmədiyinə əmin olmaq üçün istifadə edilir." 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 "" +"Əgər səyyahınızın 'Referer' başlığını göndərməsini söndürmüsünüzsə, lütfən " +"bu sayt üçün, HTTPS əlaqələr üçün və ya 'same-origin' sorğular üçün aktiv " +"edin." + +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." +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." 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 "" +"Bu sayt formaları göndərmək üçün CSRF çərəzini işlədir. Bu çərəz " +"səyyahınızın üçüncü biri tərəfindən hack-lənmədiyinə əmin olmaq üçün " +"istifadə edilir. " 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 "" +"Əgər səyyahınızda çərəzlər söndürülübsə, lütfən bu sayt və ya 'same-origin' " +"sorğular üçün aktiv edin." msgid "More information is available with DEBUG=True." msgstr "Daha ətraflı məlumat DEBUG=True ilə mövcuddur." -msgid "Welcome to Django" -msgstr "Djangoya Xoş Gəldiniz" - -msgid "It worked!" -msgstr "İşlədi!" - -msgid "Congratulations on your first Django-powered page." -msgstr "İlk Django ilə işləyən səhifəniz münasibəti ilə təbrik edirik." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "İl göstərilməyib" +msgid "Date out of range" +msgstr "Tarix aralığın xaricindədir" + msgid "No month specified" msgstr "Ay göstərilməyib" @@ -1163,3 +1214,47 @@ msgstr "\"%(path)s\" mövcud deyil" #, python-format msgid "Index of %(directory)s" msgstr "%(directory)s-nin indeksi" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: tələsən mükəmməlləkçilər üçün Web framework." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Django %(version)s üçün buraxılış " +"qeydlərinə baxın" + +msgid "The install worked successfully! Congratulations!" +msgstr "Quruluş uğurla tamamlandı! Təbriklər!" + +#, python-format +msgid "" +"You are seeing this page because 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." + +msgid "Django Documentation" +msgstr "Django Sənədləri" + +msgid "Topics, references, & how-to's" +msgstr "Mövzular, istinadlar və nümunələr" + +msgid "Tutorial: A Polling App" +msgstr "Məşğələ: Səsvermə Tətbiqi" + +msgid "Get started with Django" +msgstr "Django-ya başla" + +msgid "Django Community" +msgstr "Django İcması" + +msgid "Connect, get help, or contribute" +msgstr "Qoşul, kömək al və dəstək ol" diff --git a/django/conf/locale/az/formats.py b/django/conf/locale/az/formats.py index ceb8165d651b..49dd0fa90c8e 100644 --- a/django/conf/locale/az/formats.py +++ b/django/conf/locale/az/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j E Y' TIME_FORMAT = 'G:i' DATETIME_FORMAT = 'j E Y, G:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d.%m.%Y', # '25.10.2006' '%d.%m.%y', # '25.10.06' diff --git a/django/conf/locale/be/LC_MESSAGES/django.mo b/django/conf/locale/be/LC_MESSAGES/django.mo index 4375d4b44081..84f6e43fe71c 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 7d984e5e0b83..25ecb7d50875 100644 --- a/django/conf/locale/be/LC_MESSAGES/django.po +++ b/django/conf/locale/be/LC_MESSAGES/django.po @@ -2,14 +2,14 @@ # # Translators: # Viktar Palstsiuk , 2014-2015 -# znotdead , 2016 +# znotdead , 2016-2017 # Дмитрий Шатера , 2016 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-09-15 07:39+0000\n" +"POT-Creation-Date: 2017-12-01 21:10+0100\n" +"PO-Revision-Date: 2017-12-02 06:40+0000\n" "Last-Translator: znotdead \n" "Language-Team: Belarusian (http://www.transifex.com/django/django/language/" "be/)\n" @@ -162,6 +162,9 @@ msgstr "Японская" msgid "Georgian" msgstr "Грузінская" +msgid "Kabyle" +msgstr "Кабільскі" + msgid "Kazakh" msgstr "Казаская" @@ -297,6 +300,15 @@ msgstr "Cтатычныя файлы" msgid "Syndication" msgstr "Сындыкацыя" +msgid "That page number is not an integer" +msgstr "Лік гэтай старонкі не з'яўляецца цэлым лікам" + +msgid "That page number is less than 1" +msgstr "Лік старонкі менш чым 1" + +msgid "That page contains no results" +msgstr "Гэтая старонка не мае ніякіх вынікаў" + msgid "Enter a valid value." msgstr "Пазначце правільнае значэньне." @@ -309,6 +321,7 @@ msgstr "Увядзіце цэлы лік." msgid "Enter a valid email address." msgstr "Увядзіце сапраўдны адрас электроннай пошты." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "Бірка можа зьмяшчаць літары, лічбы, знакі падкрэсьліваньня ды злучкі." @@ -412,6 +425,17 @@ msgstr[1] "Упэўніцеся, што набралі ня болей за %(ma msgstr[2] "Упэўніцеся, што набралі ня болей за %(max)s лічбаў да коскі." msgstr[3] "Упэўніцеся, што набралі ня болей за %(max)s лічбаў да коскі." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Пашырэнне файла '%(extension)s' не дапускаецца. Дапушчальныя пашырэння: " +"'%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Null сімвалы не дапускаюцца." + msgid "and" msgstr "і" @@ -753,15 +777,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Выпраўце зьвесткі, якія паўтараюцца (гл. ніжэй)." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "Вонкавы ключ не супадае з бацькоўскім першасным ключом." +msgid "The inline value did not match the parent instance." +msgstr "Убудаванае значэнне не супадае з бацькоўскім значэннем." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Абярыце дазволенае. Абранага няма ў даступных значэньнях." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" мае недапушчальнае значэнне для першаснага ключа." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" не сапраўднае значэнне" #, python-format msgid "" @@ -771,15 +795,15 @@ msgstr "" "У часавым абсягу «%(current_timezone)s» нельга зразумець дату %(datetime)s: " "яна можа быць неадназначнаю або яе можа не існаваць." +msgid "Clear" +msgstr "Ачысьціць" + msgid "Currently" msgstr "Зараз" msgid "Change" msgstr "Зьмяніць" -msgid "Clear" -msgstr "Ачысьціць" - msgid "Unknown" msgstr "Невядома" @@ -1140,6 +1164,19 @@ msgstr "" "загалоўкамі, калі ласка дазвольце іх хаця б для гэтага сайту, ці для 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." +msgstr "" +"Калі вы выкарыстоўваеце тэг " +"ці дадалі загаловак 'Referrer-Policy: no-referrer', калі ласка выдаліце іх. " +"CSRF абароне неабходны 'Referer' загаловак для строгай праверкі. Калі Вы " +"турбуецеся аб прыватнасці, выкарыстоўвайце альтэрнатывы, напрыклад , для спасылкі на сайты трэціх асоб." + 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 " @@ -1159,33 +1196,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Больш падрабязная інфармацыя даступная з DEBUG=True." -msgid "Welcome to Django" -msgstr "Вітаем у Django" - -msgid "It worked!" -msgstr "Гэта спрацавала!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Віншуем Вас з першай старонкай на базе Django." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Канешне вы не зрабілі яшчэ ніякай работы. Далей, пачніце вашу першую " -"праграму выканаўшы python manage.py startapp [app_label]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Вы бачыце гэта паведамленне, таму што вы маеце DEBUG = True у " -"вашым файлу настройцы Django і вы не сканфігуравалі ніякіх спасылак URL. За " -"працу!" - msgid "No year specified" msgstr "Не пазначылі год" +msgid "Date out of range" +msgstr "Дата выходзіць за межы дыяпазону" + msgid "No month specified" msgstr "Не пазначылі месяц" @@ -1239,3 +1255,47 @@ msgstr "Шлях «%(path)s» не існуе." #, python-format msgid "Index of %(directory)s" msgstr "Файлы каталёґа «%(directory)s»" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Джанга: Web рамкі для перфекцыяністаў з крайнімі тэрмінамі." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Паглядзець заўвагі да выпуску для Джангі " +"%(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." +msgstr "" +"Вы бачыце гэту старонку таму што DEBUG=True у вашым файле налад і вы не сканфігурыравалі ніякіх URL." + +msgid "Django Documentation" +msgstr "Дакументацыя Джангі" + +msgid "Topics, references, & how-to's" +msgstr "Тэмы, спасылкі, & як зрабіць" + +msgid "Tutorial: A Polling App" +msgstr "Падручнік: Дадатак для галасавання" + +msgid "Get started with Django" +msgstr "Пачніце з Джангаю" + +msgid "Django Community" +msgstr "Джанга супольнасць" + +msgid "Connect, get help, or contribute" +msgstr "Злучайцеся, атрымлівайце дапамогу, ці спрыяйце" diff --git a/django/conf/locale/bg/LC_MESSAGES/django.mo b/django/conf/locale/bg/LC_MESSAGES/django.mo index 0e2606d586fe..ed5cd50c9bbb 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 a0fcca0614f9..8a85adb88961 100644 --- a/django/conf/locale/bg/LC_MESSAGES/django.po +++ b/django/conf/locale/bg/LC_MESSAGES/django.po @@ -5,15 +5,15 @@ # Jannis Leidel , 2011 # Lyuboslav Petrov , 2014 # Todor Lubenov , 2013-2015 -# Venelin Stoykov , 2015-2016 +# Venelin Stoykov , 2015-2017 # vestimir , 2014 # Alexander Atanasov , 2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 17:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-17 09:05+0000\n" "Last-Translator: Venelin Stoykov \n" "Language-Team: Bulgarian (http://www.transifex.com/django/django/language/" "bg/)\n" @@ -299,6 +299,15 @@ msgstr "Статични файлове" msgid "Syndication" msgstr "Syndication" +msgid "That page number is not an integer" +msgstr "Номерът на страницата не е цяло число" + +msgid "That page number is less than 1" +msgstr "Номерът на страницата е по-малък от 1" + +msgid "That page contains no results" +msgstr "В тази страница няма резултати" + msgid "Enter a valid value." msgstr "Въведете валидна стойност. " @@ -311,6 +320,7 @@ msgstr "Въведете валидно число." msgid "Enter a valid email address." msgstr "Въведете валиден имейл адрес." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -355,10 +365,10 @@ msgid_plural "" "Ensure this value has at least %(limit_value)d characters (it has " "%(show_value)d)." msgstr[0] "" -"Уверете се, тази стойност е най-малко %(limit_value)d знака (тя има " +"Уверете се, че тази стойност е най-малко %(limit_value)d знака (тя има " "%(show_value)d )." msgstr[1] "" -"Уверете се, тази стойност е най-малко %(limit_value)d знака (тя има " +"Уверете се, че тази стойност е най-малко %(limit_value)d знака (тя има " "%(show_value)d)." #, python-format @@ -372,7 +382,7 @@ msgstr[0] "" "Уверете се, тази стойност има най-много %(limit_value)d знака (тя има " "%(show_value)d)." msgstr[1] "" -"Уверете се, тази стойност има най-много %(limit_value)d знака (тя има " +"Уверете се, че тази стойност има най-много %(limit_value)d знака (тя има " "%(show_value)d)." #, python-format @@ -399,6 +409,17 @@ msgstr[0] "" msgstr[1] "" "Уверете се, че има не повече от %(max)s цифри преди десетичната запетая." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Не са разрешени файлове с раширение '%(extension)s'. Позволените разширения " +"са: '%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "и" @@ -654,7 +675,8 @@ msgid_plural "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." msgstr[0] "Уверете се, това име е най-много %(max)d знака (то има %(length)d)." msgstr[1] "" -"Уверете се, това файлово име има най-много %(max)d знаци (има %(length)d)." +"Уверете се, че това файлово име има най-много %(max)d знаци (има " +"%(length)d)." msgid "Please either submit a file or check the clear checkbox, not both." msgstr "" @@ -730,15 +752,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Моля, коригирайте повтарящите се стойности по-долу." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "Невалидна избрана стойност." +msgid "The inline value did not match the parent instance." +msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Направете валиден избор. Този не е един от възможните избори. " #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" не е валидна стойност за първичен ключ." +msgid "\"%(pk)s\" is not a valid value." +msgstr "" #, python-format msgid "" @@ -748,15 +770,15 @@ msgstr "" "%(datetime)s не може да бъде разчетено в %(current_timezone)s; може да е " "двусмислен или да не съществува" +msgid "Clear" +msgstr "Изчисти" + msgid "Currently" msgstr "Сега" msgid "Change" msgstr "Промени" -msgid "Clear" -msgstr "Изчисти" - msgid "Unknown" msgstr "Неизвестно" @@ -1103,6 +1125,14 @@ msgstr "" "ги активирате отново, поне за този сайт, или за 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." +msgstr "" + 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 " @@ -1123,28 +1153,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Повече информация е на разположение с DEBUG=True." -msgid "Welcome to Django" -msgstr "Добре дошли в Django" - -msgid "It worked!" -msgstr "Той работи!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Поздравления за първата си Django страница." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "Не е посочена година" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "Не е посочен месец" @@ -1195,3 +1209,45 @@ 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 "" + +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." +msgstr "" +"Вие виждате тази страница защото DEBUG=True е във вашият settings файл и не сте конфигурирали никакви " +"URL-и" + +msgid "Django Documentation" +msgstr "Django Документация" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "Започнете с Django" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/bg/formats.py b/django/conf/locale/bg/formats.py index 98820e703fef..b7d0c3b53dd1 100644 --- a/django/conf/locale/bg/formats.py +++ b/django/conf/locale/bg/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'd F Y' TIME_FORMAT = 'H:i' # DATETIME_FORMAT = @@ -15,7 +12,7 @@ # FIRST_DAY_OF_WEEK = # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # DATE_INPUT_FORMATS = # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = diff --git a/django/conf/locale/bn/LC_MESSAGES/django.mo b/django/conf/locale/bn/LC_MESSAGES/django.mo index 7ca743d03163..7c325caee8c9 100644 Binary files a/django/conf/locale/bn/LC_MESSAGES/django.mo and b/django/conf/locale/bn/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/bn/LC_MESSAGES/django.po b/django/conf/locale/bn/LC_MESSAGES/django.po index 5f8213b7eaa3..05aeea19c7ec 100644 --- a/django/conf/locale/bn/LC_MESSAGES/django.po +++ b/django/conf/locale/bn/LC_MESSAGES/django.po @@ -2,15 +2,15 @@ # # Translators: # Jannis Leidel , 2011 -# nsmgr8 , 2013 +# M Nasimul Haque , 2013 # Tahmid Rafi , 2012-2013 # Tahmid Rafi , 2013 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Bengali (http://www.transifex.com/django/django/language/" "bn/)\n" @@ -296,6 +296,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "একটি বৈধ মান দিন।" @@ -308,6 +317,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "একটি বৈধ ইমেইল ঠিকানা লিখুন." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -383,6 +393,15 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "এবং" @@ -692,14 +711,14 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "" -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "ইনলাইন ফরেন কি টি প্যারেন্ট ইনস্ট্যান্সের প্রাইমারি কি এর সমান নয়।" +msgid "The inline value did not match the parent instance." +msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "এটি বৈধ নয়। অনুগ্রহ করে আরেকটি সিলেক্ট করুন।" #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -708,15 +727,15 @@ msgid "" "may be ambiguous or it may not exist." msgstr "" +msgid "Clear" +msgstr "পরিষ্কার করুন" + msgid "Currently" msgstr "এই মুহুর্তে" msgid "Change" msgstr "পরিবর্তন" -msgid "Clear" -msgstr "পরিষ্কার করুন" - msgid "Unknown" msgstr "অজানা" @@ -1056,6 +1075,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1070,28 +1097,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "কোন বছর উল্লেখ করা হয়নি" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "কোন মাস উল্লেখ করা হয়নি" @@ -1140,3 +1151,41 @@ msgstr "\"%(path)s\" এর অস্তিত্ব নেই" #, python-format msgid "Index of %(directory)s" msgstr "%(directory)s এর ইনডেক্স" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/bn/formats.py b/django/conf/locale/bn/formats.py index 2348c3afa356..6205fb95cb76 100644 --- a/django/conf/locale/bn/formats.py +++ b/django/conf/locale/bn/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j F, Y' TIME_FORMAT = 'g:i A' # DATETIME_FORMAT = @@ -12,13 +9,24 @@ MONTH_DAY_FORMAT = 'j F' SHORT_DATE_FORMAT = 'j M, Y' # SHORT_DATETIME_FORMAT = -# FIRST_DAY_OF_WEEK = +FIRST_DAY_OF_WEEK = 6 # Saturday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior -# DATE_INPUT_FORMATS = -# TIME_INPUT_FORMATS = -# DATETIME_INPUT_FORMATS = +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior +DATE_INPUT_FORMATS = [ + '%d/%m/%Y', # 25/10/2016 + '%d/%m/%y', # 25/10/16 + '%d-%m-%Y', # 25-10-2016 + '%d-%m-%y', # 25-10-16 +] +TIME_INPUT_FORMATS = [ + '%H:%M:%S', # 14:30:59 + '%H:%M', # 14:30 +] +DATETIME_INPUT_FORMATS = [ + '%d/%m/%Y %H:%M:%S', # 25/10/2006 14:30:59 + '%d/%m/%Y %H:%M', # 25/10/2006 14:30 +] DECIMAL_SEPARATOR = '.' THOUSAND_SEPARATOR = ',' # NUMBER_GROUPING = diff --git a/django/conf/locale/br/LC_MESSAGES/django.mo b/django/conf/locale/br/LC_MESSAGES/django.mo index 07ae6cefa666..89e7e40f120d 100644 Binary files a/django/conf/locale/br/LC_MESSAGES/django.mo and b/django/conf/locale/br/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/br/LC_MESSAGES/django.po b/django/conf/locale/br/LC_MESSAGES/django.po index 143e36a91f21..9c99f7c429a4 100644 --- a/django/conf/locale/br/LC_MESSAGES/django.po +++ b/django/conf/locale/br/LC_MESSAGES/django.po @@ -6,15 +6,19 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2018-05-17 11:49+0200\n" +"PO-Revision-Date: 2018-05-18 00:21+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Breton (http://www.transifex.com/django/django/language/br/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: br\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"Plural-Forms: nplurals=5; plural=((n%10 == 1) && (n%100 != 11) && (n%100 !" +"=71) && (n%100 !=91) ? 0 :(n%10 == 2) && (n%100 != 12) && (n%100 !=72) && (n" +"%100 !=92) ? 1 :(n%10 ==3 || n%10==4 || n%10==9) && (n%100 < 10 || n% 100 > " +"19) && (n%100 < 70 || n%100 > 79) && (n%100 < 90 || n%100 > 99) ? 2 :(n != 0 " +"&& n % 1000000 == 0) ? 3 : 4);\n" msgid "Afrikaans" msgstr "Afrikaneg" @@ -157,6 +161,9 @@ msgstr "Japaneg" msgid "Georgian" msgstr "Jorjianeg" +msgid "Kabyle" +msgstr "" + msgid "Kazakh" msgstr "kazak" @@ -292,6 +299,15 @@ msgstr "Restroù statek" msgid "Syndication" msgstr "Sindikadur" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "Merkit un talvoud reizh" @@ -304,6 +320,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "Merkit ur chomlec'h postel reizh" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -350,6 +367,9 @@ msgid_plural "" "%(show_value)d)." msgstr[0] "" msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" #, python-format msgid "" @@ -360,18 +380,30 @@ msgid_plural "" "%(show_value)d)." msgstr[0] "" msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" + +msgid "Enter a number." +msgstr "Merkit un niver." #, 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] "" #, 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] "" #, python-format msgid "" @@ -380,6 +412,18 @@ msgid_plural "" "Ensure that there are no more than %(max)s digits before the decimal point." msgstr[0] "" msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" + +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" msgid "and" msgstr "ha" @@ -427,6 +471,10 @@ msgstr "Anterin bras (8 okted)" msgid "'%(value)s' value must be either True or False." msgstr "" +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "" + msgid "Boolean (Either True or False)" msgstr "Boulean (gwir pe gaou)" @@ -589,9 +637,6 @@ msgstr "Rekis eo leuniañ ar vaezienn." msgid "Enter a whole number." msgstr "Merkit un niver anterin." -msgid "Enter a number." -msgstr "Merkit un niver." - msgid "Enter a valid date." msgstr "Merkit un deiziad reizh" @@ -604,6 +649,10 @@ msgstr "Merkit un eur/deiziad reizh" msgid "Enter a valid duration." msgstr "" +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "" + msgid "No file was submitted. Check the encoding type on the form." msgstr "N'eus ket kaset restr ebet. Gwiriit ar seurt enkodañ evit ar restr" @@ -619,6 +668,9 @@ msgid_plural "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." msgstr[0] "" msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" msgid "Please either submit a file or check the clear checkbox, not both." msgstr "Kasit ur restr pe askit al log riñsañ; an eil pe egile" @@ -659,12 +711,18 @@ 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] "" #, 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 "Order" msgstr "Urzh" @@ -693,16 +751,14 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Reizhañ ar roadennoù e doubl zo a-is" -msgid "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." msgstr "" -"Ne glot ket an alc'hwez estren enlinenn gant alc'hwez-mamm an urzhiataer " -"galloudel kar" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Diuzit un dibab reizh. N'emañ ket an dibab-mañ e-touez ar re bosupl." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -713,15 +769,15 @@ msgstr "" "N'eo ket bete komprenet an talvoud %(datetime)s er werzhid eur " "%(current_timezone)s; pe eo amjestr pe n'eus ket anezhañ." +msgid "Clear" +msgstr "Riñsañ" + msgid "Currently" msgstr "Evit ar mare" msgid "Change" msgstr "Kemmañ" -msgid "Clear" -msgstr "Riñsañ" - msgid "Unknown" msgstr "Dianav" @@ -739,6 +795,9 @@ msgid "%(size)d byte" msgid_plural "%(size)d bytes" msgstr[0] "%(size)d okted" msgstr[1] "%(size)d okted" +msgstr[2] "%(size)d okted" +msgstr[3] "%(size)d okted" +msgstr[4] "%(size)d okted" #, python-format msgid "%s KB" @@ -1008,36 +1067,54 @@ msgid "%d year" msgid_plural "%d years" msgstr[0] "%d bloaz" msgstr[1] "%d bloaz" +msgstr[2] "%d bloaz" +msgstr[3] "%d bloaz" +msgstr[4] "%d bloaz" #, python-format msgid "%d month" msgid_plural "%d months" msgstr[0] "%d miz" msgstr[1] "%d miz" +msgstr[2] "%d miz" +msgstr[3] "%d miz" +msgstr[4] "%d miz" #, python-format msgid "%d week" msgid_plural "%d weeks" msgstr[0] "%d sizhun" msgstr[1] "%d sizhun" +msgstr[2] "%d sizhun" +msgstr[3] "%d sizhun" +msgstr[4] "%d sizhun" #, python-format msgid "%d day" msgid_plural "%d days" msgstr[0] "%d deiz" msgstr[1] "%d deiz" +msgstr[2] "%d deiz" +msgstr[3] "%d deiz" +msgstr[4] "%d deiz" #, python-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "%d eur" msgstr[1] "%d eur" +msgstr[2] "%d eur" +msgstr[3] "%d eur" +msgstr[4] "%d eur" #, python-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d munud" msgstr[1] "%d munud" +msgstr[2] "%d munud" +msgstr[3] "%d munud" +msgstr[4] "%d munud" msgid "0 minutes" msgstr "0 munud" @@ -1061,6 +1138,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1075,28 +1160,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "N'eus bet resisaet bloavezh ebet" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "N'eus bet resisaet miz ebet" @@ -1150,3 +1219,41 @@ msgstr "N'eus ket eus \"%(path)s\"" #, python-format msgid "Index of %(directory)s" msgstr "Meneger %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/bs/LC_MESSAGES/django.mo b/django/conf/locale/bs/LC_MESSAGES/django.mo index 6851d6fcb8d5..57fec04f48cc 100644 Binary files a/django/conf/locale/bs/LC_MESSAGES/django.mo and b/django/conf/locale/bs/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/bs/LC_MESSAGES/django.po b/django/conf/locale/bs/LC_MESSAGES/django.po index ce663c72d838..967fd81fa92d 100644 --- a/django/conf/locale/bs/LC_MESSAGES/django.po +++ b/django/conf/locale/bs/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Bosnian (http://www.transifex.com/django/django/language/" "bs/)\n" @@ -295,6 +295,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "Unesite ispravnu vrijednost." @@ -307,6 +316,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -389,6 +399,15 @@ msgstr[0] "" msgstr[1] "" msgstr[2] "" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "i" @@ -704,15 +723,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Ispravite duple vrijednosti dole." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "Strani ključ se nije poklopio sa instancom roditeljskog ključa." +msgid "The inline value did not match the parent instance." +msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" "Odabrana vrijednost nije među ponuđenima. Odaberite jednu od ponuđenih." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -721,15 +740,15 @@ msgid "" "may be ambiguous or it may not exist." msgstr "" +msgid "Clear" +msgstr "Očisti" + msgid "Currently" msgstr "Trenutno" msgid "Change" msgstr "Izmjeni" -msgid "Clear" -msgstr "Očisti" - msgid "Unknown" msgstr "Nepoznato" @@ -1076,6 +1095,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1090,28 +1117,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "Godina nije naznačena" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "Mjesec nije naznačen" @@ -1160,3 +1171,41 @@ msgstr "" #, python-format msgid "Index of %(directory)s" msgstr "" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/bs/formats.py b/django/conf/locale/bs/formats.py index cce3900f1e51..25d9b40e454e 100644 --- a/django/conf/locale/bs/formats.py +++ b/django/conf/locale/bs/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j. N Y.' TIME_FORMAT = 'G:i' DATETIME_FORMAT = 'j. N. Y. G:i T' @@ -15,7 +12,7 @@ # FIRST_DAY_OF_WEEK = # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # DATE_INPUT_FORMATS = # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = diff --git a/django/conf/locale/ca/LC_MESSAGES/django.mo b/django/conf/locale/ca/LC_MESSAGES/django.mo index 8eb40deed3e7..9ce89a903dc3 100644 Binary files a/django/conf/locale/ca/LC_MESSAGES/django.mo and b/django/conf/locale/ca/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/ca/LC_MESSAGES/django.po b/django/conf/locale/ca/LC_MESSAGES/django.po index 197fa291547e..2511e0a7b17c 100644 --- a/django/conf/locale/ca/LC_MESSAGES/django.po +++ b/django/conf/locale/ca/LC_MESSAGES/django.po @@ -1,9 +1,10 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Antoni Aloy , 2012,2015-2016 +# Antoni Aloy , 2012,2015-2017 # Carles Barrobés , 2011-2012,2014 # duub qnnp, 2015 +# Gil Obradors Via , 2019 # Jannis Leidel , 2011 # Manuel Miranda , 2015 # Roger Pons , 2015 @@ -11,9 +12,9 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-28 21:13+0000\n" +"Last-Translator: Gil Obradors Via \n" "Language-Team: Catalan (http://www.transifex.com/django/django/language/" "ca/)\n" "MIME-Version: 1.0\n" @@ -65,7 +66,7 @@ msgid "German" msgstr "alemany" msgid "Lower Sorbian" -msgstr "" +msgstr "Lower Sorbian" msgid "Greek" msgstr "grec" @@ -137,11 +138,14 @@ msgid "Croatian" msgstr "croat" msgid "Upper Sorbian" -msgstr "" +msgstr "Upper Sorbian" msgid "Hungarian" msgstr "hongarès" +msgid "Armenian" +msgstr "Armeni" + msgid "Interlingua" msgstr "Interlingua" @@ -163,6 +167,9 @@ msgstr "japonès" msgid "Georgian" msgstr "georgià" +msgid "Kabyle" +msgstr "Cabilenc" + msgid "Kazakh" msgstr "Kazakh" @@ -200,7 +207,7 @@ msgid "Burmese" msgstr "Burmès" msgid "Norwegian Bokmål" -msgstr "" +msgstr "Norwegian Bokmål" msgid "Nepali" msgstr "Nepalí" @@ -298,6 +305,15 @@ msgstr "Arxius estàtics" msgid "Syndication" msgstr "Sindicació" +msgid "That page number is not an integer" +msgstr "Aquesta plana no és un sencer" + +msgid "That page number is less than 1" +msgstr "El nombre de plana és inferior a 1" + +msgid "That page contains no results" +msgstr "La plana no conté cap resultat" + msgid "Enter a valid value." msgstr "Introduïu un valor vàlid." @@ -310,6 +326,7 @@ msgstr "Introduïu un enter vàlid." msgid "Enter a valid email address." msgstr "Introdueix una adreça de correu electrònic vàlida" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -377,6 +394,9 @@ msgstr[1] "" "Assegureu-vos que aquest valor té com a molt %(limit_value)d caràcters (en " "té %(show_value)d)." +msgid "Enter a number." +msgstr "Introduïu un número." + #, 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." @@ -399,6 +419,17 @@ msgstr[0] "" msgstr[1] "" "Assegureu-vos que no hi ha més de %(max)s dígits abans de la coma decimal." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"L'extensió d'arxiu '%(extension)s' no es permesa. Les extensions permeses " +"són: '%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Caràcters nul no estan permesos." + msgid "and" msgstr "i" @@ -446,6 +477,10 @@ msgstr "Enter gran (8 bytes)" msgid "'%(value)s' value must be either True or False." msgstr "El valor '%(value)s' ha de ser \"True\" o \"False\"." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "El valor '%(value)s' ha de ser cert, fals o cap." + msgid "Boolean (Either True or False)" msgstr "Booleà (Cert o Fals)" @@ -583,6 +618,9 @@ msgstr "Dades binàries" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' no és un UUID vàlid." +msgid "Universally unique identifier" +msgstr "Identificador únic universal" + msgid "File" msgstr "Arxiu" @@ -601,11 +639,11 @@ msgstr "Inter-relació un-a-un" #, python-format msgid "%(from)s-%(to)s relationship" -msgstr "" +msgstr "relació %(from)s-%(to)s " #, python-format msgid "%(from)s-%(to)s relationships" -msgstr "" +msgstr "relacions %(from)s-%(to)s " msgid "Many-to-many relationship" msgstr "Inter-relació molts-a-molts" @@ -622,9 +660,6 @@ msgstr "Aquest camp és obligatori." msgid "Enter a whole number." msgstr "Introduïu un número sencer." -msgid "Enter a number." -msgstr "Introduïu un número." - msgid "Enter a valid date." msgstr "Introduïu una data vàlida." @@ -637,6 +672,10 @@ msgstr "Introduïu una data/hora vàlides." msgid "Enter a valid duration." msgstr "Introdueixi una durada vàlida." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "El número de dies ha de ser entre {min_days} i {max_days}." + msgid "No file was submitted. Check the encoding type on the form." msgstr "" "No s'ha enviat cap fitxer. Comproveu el tipus de codificació del formulari." @@ -733,18 +772,16 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Si us plau, corregiu els valors duplicats a sota." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "" -"La clau forana en línia no coincideix amb la clau primària de la instància " -"mare." +msgid "The inline value did not match the parent instance." +msgstr "El valor en línia no coincideix la instancia mare ." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" "Esculli una opció vàlida. Aquesta opció no és una de les opcions disponibles." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" no és un valor vàlid per a una clau primària." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" no és un valor vàlid" #, python-format msgid "" @@ -754,15 +791,15 @@ msgstr "" "No s'ha pogut interpretar %(datetime)s a la zona horària " "%(current_timezone)s; potser és ambigua o no existeix." +msgid "Clear" +msgstr "Netejar" + msgid "Currently" msgstr "Actualment" msgid "Change" msgstr "Modificar" -msgid "Clear" -msgstr "Netejar" - msgid "Unknown" msgstr "Desconegut" @@ -1034,7 +1071,7 @@ msgstr "Aquesta no és una adreça IPv6 vàlida." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." +msgid "%(truncated_text)s…" msgstr "%(truncated_text)s..." msgid "or" @@ -1109,6 +1146,20 @@ msgstr "" "sisplau torna-les a habilitar, com a mínim per a aquest lloc, o per a " "connexions HTTPs, o per a peticions amb el mateix orígen." +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." +msgstr "" +"Si utilitza l'etiqueta o " +"inclou la capçalera 'Referrer-Policy: no-referrer' , si et plau elimina-la. " +"La protecció CSRF requereix la capçalera 'Referer' per a fer una " +"comprovació estricte. Si està preocupat en quan a la privacitat, utilitzi " +"alternatives com per enllaçar a aplicacions de " +"tercers." + 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 " @@ -1130,33 +1181,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Més informació disponible amb DEBUG=True." -msgid "Welcome to Django" -msgstr "Benvingut a Django" - -msgid "It worked!" -msgstr "Funciona!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Enhorabona per la teva primera plana amb Django." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Per suposat, encara no has fet cap feina real encara. Seguidament crea la " -"teva primera aplicació executant python manage.py startapp " -"[app_label]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Veieu aquest missatge perquè teniu DEBUG = True a la " -"configuració del Django settings i no heu configurat cap URL. A treballar!" - msgid "No year specified" msgstr "No s'ha especificat any" +msgid "Date out of range" +msgstr "Data fora de rang" + msgid "No month specified" msgstr "No s'ha especificat mes" @@ -1207,3 +1237,48 @@ msgstr "\"%(path)s\" no existeix" #, python-format msgid "Index of %(directory)s" msgstr "Índex de %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: l'entorn de treball per a perfeccionistes de temps rècord." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Visualitza notes de llançament per Django " +"%(version)s" + +msgid "The install worked successfully! Congratulations!" +msgstr "La instal·lació ha estat un èxit! Felicitats!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Està veient aquesta pàgina degut a que el paràmetre DEBUG=Trueconsta al fitxer de configuració i no teniu " +"direccions URLs configurades." + +msgid "Django Documentation" +msgstr "Documentació de Django" + +msgid "Topics, references, & how-to's" +msgstr "Temes, referències, & Com es fa" + +msgid "Tutorial: A Polling App" +msgstr "Programa d'aprenentatge: Una aplicació enquesta" + +msgid "Get started with Django" +msgstr "Primers passos amb Django" + +msgid "Django Community" +msgstr "Comunitat Django" + +msgid "Connect, get help, or contribute" +msgstr "Connecta, obté ajuda, o col·labora" diff --git a/django/conf/locale/ca/formats.py b/django/conf/locale/ca/formats.py index d42016b71651..746d08fdd288 100644 --- a/django/conf/locale/ca/formats.py +++ b/django/conf/locale/ca/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = r'j \d\e F \d\e Y' TIME_FORMAT = 'G:i' DATETIME_FORMAT = r'j \d\e F \d\e Y \a \l\e\s G:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ # '31/12/2009', '31/12/09' '%d/%m/%Y', '%d/%m/%y' diff --git a/django/conf/locale/cs/LC_MESSAGES/django.mo b/django/conf/locale/cs/LC_MESSAGES/django.mo index 46873b38e84f..7da05abda367 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 52acaf7d15a7..f173a43b9a34 100644 --- a/django/conf/locale/cs/LC_MESSAGES/django.po +++ b/django/conf/locale/cs/LC_MESSAGES/django.po @@ -6,20 +6,21 @@ # Jirka Vejrazka , 2011 # Tomáš Ehrlich , 2015 # Vláďa Macek , 2012-2014 -# Vláďa Macek , 2015-2016 +# Vláďa Macek , 2015-2019 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-09-16 22:28+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-02-19 07:53+0000\n" "Last-Translator: Vláďa Macek \n" "Language-Team: Czech (http://www.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" "Language: cs\n" -"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" +"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n " +"<= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" msgid "Afrikaans" msgstr "afrikánsky" @@ -28,10 +29,10 @@ msgid "Arabic" msgstr "arabsky" msgid "Asturian" -msgstr "Asturian" +msgstr "asturštinou" msgid "Azerbaijani" -msgstr "Ázerbájdžánština" +msgstr "ázerbájdžánštinou" msgid "Bulgarian" msgstr "bulharsky" @@ -64,7 +65,7 @@ msgid "German" msgstr "německy" msgid "Lower Sorbian" -msgstr "Dolnolužická srbština" +msgstr "dolnolužickou srbštinou" msgid "Greek" msgstr "řecky" @@ -91,10 +92,10 @@ msgid "Colombian Spanish" msgstr "kolumbijskou španělštinou" msgid "Mexican Spanish" -msgstr "Mexická španělština" +msgstr "mexickou španělštinou" msgid "Nicaraguan Spanish" -msgstr "Nikaragujskou španělštinou" +msgstr "nikaragujskou španělštinou" msgid "Venezuelan Spanish" msgstr "venezuelskou španělštinou" @@ -136,11 +137,14 @@ msgid "Croatian" msgstr "chorvatsky" msgid "Upper Sorbian" -msgstr "Hornolužická srbština" +msgstr "hornolužickou srbštinou" msgid "Hungarian" msgstr "maďarsky" +msgid "Armenian" +msgstr "arménštinou" + msgid "Interlingua" msgstr "interlingua" @@ -160,7 +164,10 @@ msgid "Japanese" msgstr "japonsky" msgid "Georgian" -msgstr "gruzínsky" +msgstr "gruzínštinou" + +msgid "Kabyle" +msgstr "kabylštinou" msgid "Kazakh" msgstr "kazašsky" @@ -193,13 +200,13 @@ msgid "Mongolian" msgstr "mongolsky" msgid "Marathi" -msgstr "Marathi" +msgstr "marathi" msgid "Burmese" msgstr "barmštinou" msgid "Norwegian Bokmål" -msgstr "Bokmål Norština" +msgstr "bokmål norštinou" msgid "Nepali" msgstr "nepálsky" @@ -274,7 +281,7 @@ msgid "Ukrainian" msgstr "ukrajinsky" msgid "Urdu" -msgstr "Urdština" +msgstr "urdsky" msgid "Vietnamese" msgstr "vietnamsky" @@ -297,6 +304,15 @@ msgstr "Statické soubory" msgid "Syndication" msgstr "Syndikace" +msgid "That page number is not an integer" +msgstr "Číslo stránky není celé číslo." + +msgid "That page number is less than 1" +msgstr "Číslo stránky je menší než 1" + +msgid "That page contains no results" +msgstr "Stránka je bez výsledků" + msgid "Enter a valid value." msgstr "Zadejte platnou hodnotu." @@ -309,6 +325,7 @@ msgstr "Zadejte platné celé číslo." msgid "Enter a valid email address." msgstr "Zadejte platnou e-mailovou adresu." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -359,6 +376,8 @@ msgstr[1] "" "Tato hodnota má mít nejméně %(limit_value)d znaky (nyní má %(show_value)d)." msgstr[2] "" "Tato hodnota má mít nejméně %(limit_value)d znaků (nyní má %(show_value)d)." +msgstr[3] "" +"Tato hodnota má mít nejméně %(limit_value)d znaků (nyní má %(show_value)d)." #, python-format msgid "" @@ -373,6 +392,11 @@ msgstr[1] "" "Tato hodnota má mít nejvýše %(limit_value)d znaky (nyní má %(show_value)d)." msgstr[2] "" "Tato hodnota má mít nejvýše %(limit_value)d znaků (nyní má %(show_value)d)." +msgstr[3] "" +"Tato hodnota má mít nejvýše %(limit_value)d znaků (nyní má %(show_value)d)." + +msgid "Enter a number." +msgstr "Zadejte číslo." #, python-format msgid "Ensure that there are no more than %(max)s digit in total." @@ -380,6 +404,7 @@ msgid_plural "Ensure that there are no more than %(max)s digits in total." msgstr[0] "Ujistěte se, že pole neobsahuje celkem více než %(max)s číslici." msgstr[1] "Ujistěte se, že pole neobsahuje celkem více než %(max)s číslice." msgstr[2] "Ujistěte se, že pole neobsahuje celkem více než %(max)s číslic." +msgstr[3] "Ujistěte se, že pole neobsahuje celkem více než %(max)s číslic." #, python-format msgid "Ensure that there are no more than %(max)s decimal place." @@ -387,6 +412,7 @@ msgid_plural "Ensure that there are no more than %(max)s decimal places." msgstr[0] "Ujistěte se, že pole neobsahuje více než %(max)s desetinné místo." msgstr[1] "Ujistěte se, že pole neobsahuje více než %(max)s desetinná místa." msgstr[2] "Ujistěte se, že pole neobsahuje více než %(max)s desetinných míst." +msgstr[3] "Ujistěte se, že pole neobsahuje více než %(max)s desetinných míst." #, python-format msgid "" @@ -402,6 +428,20 @@ msgstr[1] "" msgstr[2] "" "Ujistěte se, že hodnota neobsahuje více než %(max)s míst před desetinnou " "čárkou (tečkou)." +msgstr[3] "" +"Ujistěte se, že hodnota neobsahuje více než %(max)s míst před desetinnou " +"čárkou (tečkou)." + +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Přípona souboru '%(extension)s' není povolena. Povolené jsou tyto: " +"'%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Nulové znaky nejsou povoleny." msgid "and" msgstr "a" @@ -454,6 +494,10 @@ msgstr "Velké číslo (8 bajtů)" msgid "'%(value)s' value must be either True or False." msgstr "Hodnota '%(value)s' musí být buď True nebo False." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "Hodnota '%(value)s' musí být buď True, False nebo None." + msgid "Boolean (Either True or False)" msgstr "Pravdivost (buď Ano (True), nebo Ne (False))" @@ -588,6 +632,9 @@ msgstr "Přímá binární data" msgid "'%(value)s' is not a valid UUID." msgstr "\"%(value)s\" není platná hodnota typu UUID." +msgid "Universally unique identifier" +msgstr "Všeobecně jedinečný identifikátor" + msgid "File" msgstr "Soubor" @@ -628,9 +675,6 @@ msgstr "Toto pole je třeba vyplnit." msgid "Enter a whole number." msgstr "Zadejte celé číslo." -msgid "Enter a number." -msgstr "Zadejte číslo." - msgid "Enter a valid date." msgstr "Zadejte platné datum." @@ -643,6 +687,10 @@ msgstr "Zadejte platné datum a čas." msgid "Enter a valid duration." msgstr "Zadejte platnou délku trvání." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Počet dní musí být mezi {min_days} a {max_days}." + msgid "No file was submitted. Check the encoding type on the form." msgstr "" "Soubor nebyl odeslán. Zkontrolujte parametr \"encoding type\" formuláře." @@ -663,6 +711,8 @@ msgstr[1] "" "Tento název souboru má mít nejvýše %(max)d znaky (nyní má %(length)d)." msgstr[2] "" "Tento název souboru má mít nejvýše %(max)d znaků (nyní má %(length)d)." +msgstr[3] "" +"Tento název souboru má mít nejvýše %(max)d znaků (nyní má %(length)d)." msgid "Please either submit a file or check the clear checkbox, not both." msgstr "Musíte vybrat cestu k souboru nebo vymazat výběr, ne obojí." @@ -703,6 +753,7 @@ msgid_plural "Please submit %d or fewer forms." msgstr[0] "Odešlete %d nebo méně formulářů." msgstr[1] "Odešlete %d nebo méně formulářů." msgstr[2] "Odešlete %d nebo méně formulářů." +msgstr[3] "Odešlete %d nebo méně formulářů." #, python-format msgid "Please submit %d or more forms." @@ -710,6 +761,7 @@ msgid_plural "Please submit %d or more forms." msgstr[0] "Odešlete %d nebo více formulářů." msgstr[1] "Odešlete %d nebo více formulářů." msgstr[2] "Odešlete %d nebo více formulářů." +msgstr[3] "Odešlete %d nebo více formulářů." msgid "Order" msgstr "Pořadí" @@ -736,16 +788,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Odstraňte duplicitní hodnoty níže." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "" -"Cizí klíč typu inline neodpovídá primárnímu klíči v rodičovské položce." +msgid "The inline value did not match the parent instance." +msgstr "Hodnota typu inline neodpovídá rodičovské položce." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Vyberte platnou možnost. Tato není k dispozici." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "Hodnota \"%(pk)s\" není platný primární klíč." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" není platná hodnota." #, python-format msgid "" @@ -755,15 +806,15 @@ msgstr "" "Hodnotu %(datetime)s nelze interpretovat v časové zóně %(current_timezone)s; " "může to být nejednoznačné nebo nemusí existovat." +msgid "Clear" +msgstr "Zrušit" + msgid "Currently" msgstr "Aktuálně" msgid "Change" msgstr "Změnit" -msgid "Clear" -msgstr "Zrušit" - msgid "Unknown" msgstr "Neznámé" @@ -782,6 +833,7 @@ msgid_plural "%(size)d bytes" msgstr[0] "%(size)d bajt" msgstr[1] "%(size)d bajty" msgstr[2] "%(size)d bajtů" +msgstr[3] "%(size)d bajtů" #, python-format msgid "%s KB" @@ -1036,8 +1088,8 @@ msgstr "Toto není platná adresa typu IPv6." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s…" msgid "or" msgstr "nebo" @@ -1052,6 +1104,7 @@ msgid_plural "%d years" msgstr[0] "%d rok" msgstr[1] "%d roky" msgstr[2] "%d let" +msgstr[3] "%d let" #, python-format msgid "%d month" @@ -1059,6 +1112,7 @@ msgid_plural "%d months" msgstr[0] "%d měsíc" msgstr[1] "%d měsíce" msgstr[2] "%d měsíců" +msgstr[3] "%d měsíců" #, python-format msgid "%d week" @@ -1066,6 +1120,7 @@ msgid_plural "%d weeks" msgstr[0] "%d týden" msgstr[1] "%d týdny" msgstr[2] "%d týdnů" +msgstr[3] "%d týdnů" #, python-format msgid "%d day" @@ -1073,6 +1128,7 @@ msgid_plural "%d days" msgstr[0] "%d den" msgstr[1] "%d dny" msgstr[2] "%d dní" +msgstr[3] "%d dní" #, python-format msgid "%d hour" @@ -1080,6 +1136,7 @@ msgid_plural "%d hours" msgstr[0] "%d hodina" msgstr[1] "%d hodiny" msgstr[2] "%d hodin" +msgstr[3] "%d hodin" #, python-format msgid "%d minute" @@ -1087,6 +1144,7 @@ msgid_plural "%d minutes" msgstr[0] "%d minuta" msgstr[1] "%d minuty" msgstr[2] "%d minut" +msgstr[3] "%d minut" msgid "0 minutes" msgstr "0 minut" @@ -1117,6 +1175,19 @@ msgstr "" "alespoň pro tento web nebo pro spojení typu HTTPS nebo pro požadavky typu " "\"stejný původ\" (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." +msgstr "" +"Pokud používáte značku nebo " +"záhlaví 'Referrer-Policy: no-referrer', odeberte je. Ochrana typu CSRF " +"vyžaduje, aby záhlaví zajišťovalo striktní hlídání refereru. Pokud je pro " +"vás soukromí důležité, použije k odkazům na cizí weby alternativní možnosti " +"jako například ." + 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 " @@ -1136,34 +1207,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "V případě zapnutí volby DEBUG=True bude k dispozici více informací." -msgid "Welcome to Django" -msgstr "Vítejte v systému Django" - -msgid "It worked!" -msgstr "Funguje to!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Gratulujeme, toto je vaše první stránka generována v prostředí Django." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Samozřejmě máte před sebou ještě spoustu práce. Začněte vytvořením Vaší " -"první aplikace pomocí příkazu python manage.py startapp [app_label]. " - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -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 nastavena žádná URL. S chutí do " -"práce!" - msgid "No year specified" msgstr "Nebyl specifikován rok" +msgid "Date out of range" +msgstr "Datum je mimo rozsah" + msgid "No month specified" msgstr "Nebyl specifikován měsíc" @@ -1214,3 +1263,48 @@ msgstr "\"%(path)s\" neexistuje" #, python-format msgid "Index of %(directory)s" msgstr "Index adresáře %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: Webový framework pro perfekcionisty, kteří mají termín" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Zobrazit poznámky k vydání frameworku Django " +"%(version)s" + +msgid "The install worked successfully! Congratulations!" +msgstr "Instalace proběhla úspěšně, gratulujeme!" + +#, python-format +msgid "" +"You are seeing this page because 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 " +"nastavena žádná URL." + +msgid "Django Documentation" +msgstr "Dokumentace frameworku Django" + +msgid "Topics, references, & how-to's" +msgstr "Témata, odkazy & how-to" + +msgid "Tutorial: A Polling App" +msgstr "Tutoriál: Hlasovací aplikace" + +msgid "Get started with Django" +msgstr "Začínáme s frameworkem Django" + +msgid "Django Community" +msgstr "Komunita kolem frameworku Django" + +msgid "Connect, get help, or contribute" +msgstr "Propojte se, získejte pomoc, podílejte se" diff --git a/django/conf/locale/cs/formats.py b/django/conf/locale/cs/formats.py index 3f2d3fa70ed9..cab29daf596f 100644 --- a/django/conf/locale/cs/formats.py +++ b/django/conf/locale/cs/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j. E Y' TIME_FORMAT = 'G:i' DATETIME_FORMAT = 'j. E Y G:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d.%m.%Y', '%d.%m.%y', # '05.01.2006', '05.01.06' '%d. %m. %Y', '%d. %m. %y', # '5. 1. 2006', '5. 1. 06' diff --git a/django/conf/locale/cy/LC_MESSAGES/django.mo b/django/conf/locale/cy/LC_MESSAGES/django.mo index e4e6be2c4a45..3cbe28027611 100644 Binary files a/django/conf/locale/cy/LC_MESSAGES/django.mo and b/django/conf/locale/cy/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/cy/LC_MESSAGES/django.po b/django/conf/locale/cy/LC_MESSAGES/django.po index 26949a4ba422..38c153453800 100644 --- a/django/conf/locale/cy/LC_MESSAGES/django.po +++ b/django/conf/locale/cy/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Welsh (http://www.transifex.com/django/django/language/cy/)\n" "MIME-Version: 1.0\n" @@ -294,6 +294,15 @@ msgstr "Ffeiliau Statig" msgid "Syndication" msgstr "Syndicetiad" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "Rhowch werth dilys." @@ -306,6 +315,7 @@ msgstr "Rhowch gyfanrif dilys." msgid "Enter a valid email address." msgstr "Rhowch gyfeiriad ebost dilys." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -408,6 +418,15 @@ msgstr[1] "Sicrhewch nad oes mwy na %(max)s ddigid cyn y pwynt degol." msgstr[2] "Sicrhewch nad oes mwy na %(max)s digid cyn y pwynt degol." msgstr[3] "Sicrhewch nad oes mwy na %(max)s digid cyn y pwynt degol." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "a" @@ -743,10 +762,8 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Cywirwch y gwerthoedd dyblyg isod." -msgid "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." msgstr "" -"Nid yw'r allwedd estron mewnlin yn cydfynd gyda allwedd gynradd enghraifft y " -"rhiant." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" @@ -754,8 +771,8 @@ msgstr "" "gael." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "Nid yw \"%(pk)s\" yn werth dilys ar gyfer allwedd cynradd." +msgid "\"%(pk)s\" is not a valid value." +msgstr "" #, python-format msgid "" @@ -765,15 +782,15 @@ msgstr "" "Ni ellir dehongli %(datetime)s yn y gylchfa amser %(current_timezone)s; " "mae'n amwys neu ddim yn bodoli." +msgid "Clear" +msgstr "Clirio" + msgid "Currently" msgstr "Ar hyn o bryd" msgid "Change" msgstr "Newid" -msgid "Clear" -msgstr "Clirio" - msgid "Unknown" msgstr "Anhysbys" @@ -1133,6 +1150,14 @@ msgstr "" "oleiaf ar gyfer y safle hwn neu ar gyfer cysylltiadau HTTPS neu ar gyfer " "ceisiadau '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." +msgstr "" + 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 " @@ -1152,28 +1177,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Mae mwy o wybodaeth ar gael gyda DEBUG=True" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "Dim blwyddyn wedi’i bennu" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "Dim mis wedi’i bennu" @@ -1225,3 +1234,41 @@ msgstr "Nid yw \"%(path)s\" yn bodoli" #, python-format msgid "Index of %(directory)s" msgstr "Mynegai %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/cy/formats.py b/django/conf/locale/cy/formats.py index 4a69a88e6a04..41518a9db9ba 100644 --- a/django/conf/locale/cy/formats.py +++ b/django/conf/locale/cy/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j F Y' # '25 Hydref 2006' TIME_FORMAT = 'P' # '2:30 y.b.' DATETIME_FORMAT = 'j F Y, P' # '25 Hydref 2006, 2:30 y.b.' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # 'Dydd Llun' # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d/%m/%Y', '%d/%m/%y', # '25/10/2006', '25/10/06' ] diff --git a/django/conf/locale/da/LC_MESSAGES/django.mo b/django/conf/locale/da/LC_MESSAGES/django.mo index c43539b2f583..e0e64f998738 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 6c5af92e33bb..c36e6a531ec7 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 Wognsen , 2013-2016 +# Erik Wognsen , 2013-2019 # Finn Gruwier Larsen, 2011 # Jannis Leidel , 2011 # jonaskoelker , 2012 @@ -13,8 +13,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 19:27+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 07:05+0000\n" "Last-Translator: Erik Wognsen \n" "Language-Team: Danish (http://www.transifex.com/django/django/language/da/)\n" "MIME-Version: 1.0\n" @@ -143,6 +143,9 @@ msgstr "øvresorbisk" msgid "Hungarian" msgstr "ungarsk" +msgid "Armenian" +msgstr "armensk" + msgid "Interlingua" msgstr "interlingua" @@ -164,6 +167,9 @@ msgstr "japansk" msgid "Georgian" msgstr "georgisk" +msgid "Kabyle" +msgstr "kabylsk" + msgid "Kazakh" msgstr "kasakhisk" @@ -299,6 +305,15 @@ msgstr "Static Files" msgid "Syndication" msgstr "Syndication" +msgid "That page number is not an integer" +msgstr "Det sidetal er ikke et heltal" + +msgid "That page number is less than 1" +msgstr "Det sidetal er mindre end 1" + +msgid "That page contains no results" +msgstr "Den side indeholder ingen resultater" + msgid "Enter a valid value." msgstr "Indtast en gyldig værdi." @@ -311,6 +326,7 @@ msgstr "Indtast et gyldigt heltal." msgid "Enter a valid email address." msgstr "Indtast en gyldig e-mail-adresse." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -372,6 +388,9 @@ msgstr[0] "" msgstr[1] "" "Denne værdi må højst have %(limit_value)d tegn (den har %(show_value)d)." +msgid "Enter a number." +msgstr "Indtast et tal." + #, 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." @@ -392,6 +411,17 @@ msgid_plural "" msgstr[0] "Der må maksimalt være %(max)s ciffer før kommaet." msgstr[1] "Der må maksimalt være %(max)s cifre før kommaet." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Filendelse '%(extension)s' er ikke tilladt. Tilladte filendelser er: " +"'%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Null-tegn er ikke tilladte." + msgid "and" msgstr "og" @@ -439,6 +469,10 @@ msgstr "Stort heltal (8 byte)" msgid "'%(value)s' value must be either True or False." msgstr "'%(value)s'-værdien skal være enten True eller False." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "'%(value)s' værdien skal være enten True, False eller None." + msgid "Boolean (Either True or False)" msgstr "Boolsk (enten True eller False)" @@ -576,6 +610,9 @@ msgstr "Rå binære data" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' er ikke et gyldigt UUID." +msgid "Universally unique identifier" +msgstr "Universelt unik identifikator" + msgid "File" msgstr "Fil" @@ -615,9 +652,6 @@ msgstr "Dette felt er påkrævet." msgid "Enter a whole number." msgstr "Indtast et heltal." -msgid "Enter a number." -msgstr "Indtast et tal." - msgid "Enter a valid date." msgstr "Indtast en gyldig dato." @@ -630,6 +664,10 @@ msgstr "Indtast gyldig dato/tid." msgid "Enter a valid duration." msgstr "Indtast en gyldig varighed." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Antallet af dage skal være mellem {min_days} og {max_days}." + msgid "No file was submitted. Check the encoding type on the form." msgstr "Ingen fil blev indsendt. Kontroller kodningstypen i formularen." @@ -721,9 +759,8 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Ret venligst de duplikerede data herunder." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "" -"Den indlejrede fremmednøgle passede ikke med forælderinstansens primærnøgle." +msgid "The inline value did not match the parent instance." +msgstr "Den indlejrede værdi passede ikke med forældreinstansen." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" @@ -731,8 +768,8 @@ msgstr "" "tilgængelige valgmuligheder." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" er ikke en gyldig værdi for en primærnøgle." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" er ikke en gyldig værdi." #, python-format msgid "" @@ -742,15 +779,15 @@ msgstr "" "%(datetime)s kunne ikke fortolkes i tidszonen %(current_timezone)s; den kan " "være tvetydig eller den eksisterer måske ikke." +msgid "Clear" +msgstr "Afmarkér" + msgid "Currently" msgstr "Aktuelt" msgid "Change" msgstr "Ret" -msgid "Clear" -msgstr "Afmarkér" - msgid "Unknown" msgstr "Ukendt" @@ -1022,8 +1059,8 @@ msgstr "Dette er ikke en gyldig IPv6-adresse." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s…" msgid "or" msgstr "eller" @@ -1097,6 +1134,19 @@ msgstr "" "dig slå dem til igen, i hvert fald for denne webside, eller for HTTPS-" "forbindelser, eller for 'same-origin'-forespørgsler." +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." +msgstr "" +"Hvis du bruger tagget eller " +"inkluderer headeren 'Referrer-Policy: no-referrer', så fjern dem venligst. " +"CSRF-beskyttelsen afhænger af at 'Referer'-headeren udfører stringent " +"referer-kontrol. Hvis du er bekymret om privatliv, så brug alternativer så " +"som for links til tredjepartswebsider." + 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 " @@ -1116,32 +1166,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Mere information er tilgængeligt med DEBUG=True." -msgid "Welcome to Django" -msgstr "Velkommen til Django" - -msgid "It worked!" -msgstr "Det virkede!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Tillykke med din første Django-drevne side." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Du har dog ikke gjort noget endnu. Start din første app ved at køre " -"python manage.py startapp [app_label]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Du ser denne besked fordi du har DEBUG = True i din Django " -"indstillingsfil og du har ikke konfigureret nogen URLs endnu. Kom i sving!" - msgid "No year specified" msgstr "Intet år specificeret" +msgid "Date out of range" +msgstr "Dato uden for rækkevidde" + msgid "No month specified" msgstr "Ingen måned specificeret" @@ -1192,3 +1222,46 @@ msgstr "\" %(path)s\" eksisterer ikke" #, python-format msgid "Index of %(directory)s" msgstr "Indeks for %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: Webframework'et for perfektionister med deadlines." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Vis udgivelsesnoter for Django %(version)s" + +msgid "The install worked successfully! Congratulations!" +msgstr "Installationen virkede! Tillykke!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Du ser denne side fordi du har DEBUG=True i din settings-fil og ikke har opsat nogen URL'er." + +msgid "Django Documentation" +msgstr "Django-dokumentation" + +msgid "Topics, references, & how-to's" +msgstr "Emner, referencer & how-to's" + +msgid "Tutorial: A Polling App" +msgstr "Gennemgang: En afstemnings-app" + +msgid "Get started with Django" +msgstr "Kom i gang med Django" + +msgid "Django Community" +msgstr "Django-fællesskabet" + +msgid "Connect, get help, or contribute" +msgstr "Forbind, få hjælp eller bidrag" diff --git a/django/conf/locale/da/formats.py b/django/conf/locale/da/formats.py index 90ba056f79bc..6237a7209d5a 100644 --- a/django/conf/locale/da/formats.py +++ b/django/conf/locale/da/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j. F Y' TIME_FORMAT = 'H:i' DATETIME_FORMAT = 'j. F Y H:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d.%m.%Y', # '25.10.2006' ] diff --git a/django/conf/locale/de/LC_MESSAGES/django.mo b/django/conf/locale/de/LC_MESSAGES/django.mo index dcd2e800679d..48165d6756c8 100644 Binary files a/django/conf/locale/de/LC_MESSAGES/django.mo and b/django/conf/locale/de/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/de/LC_MESSAGES/django.po b/django/conf/locale/de/LC_MESSAGES/django.po index 63eddf105773..efc1a522f231 100644 --- a/django/conf/locale/de/LC_MESSAGES/django.po +++ b/django/conf/locale/de/LC_MESSAGES/django.po @@ -3,18 +3,19 @@ # Translators: # André Hagenbruch, 2011-2012 # Florian Apolloner , 2011 -# Dunedan , 2016 +# Daniel Roschka , 2016 +# Florian Apolloner , 2018 # Jannis Vajen, 2011,2013 -# Jannis Leidel , 2013-2016 +# Jannis Leidel , 2013-2018 # Jannis Vajen, 2016 -# Markus Holtermann , 2013,2015 +# Markus Holtermann , 2013,2015 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-07-26 11:37+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2018-05-17 11:49+0200\n" +"PO-Revision-Date: 2018-08-14 08:25+0000\n" +"Last-Translator: Florian Apolloner \n" "Language-Team: German (http://www.transifex.com/django/django/language/de/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -163,6 +164,9 @@ msgstr "Japanisch" msgid "Georgian" msgstr "Georgisch" +msgid "Kabyle" +msgstr "Kabylisch" + msgid "Kazakh" msgstr "Kasachisch" @@ -206,7 +210,7 @@ msgid "Nepali" msgstr "Nepali" msgid "Dutch" -msgstr "Holländisch" +msgstr "Niederländisch" msgid "Norwegian Nynorsk" msgstr "Norwegisch (Nynorsk)" @@ -298,6 +302,15 @@ msgstr "Statische Dateien" msgid "Syndication" msgstr "Syndication" +msgid "That page number is not an integer" +msgstr "Diese Seitennummer ist keine Ganzzahl" + +msgid "That page number is less than 1" +msgstr "Diese Seitennummer ist kleiner als 1" + +msgid "That page contains no results" +msgstr "Diese Seite enthält keine Ergebnisse" + msgid "Enter a valid value." msgstr "Bitte einen gültigen Wert eingeben." @@ -310,6 +323,7 @@ msgstr "Bitte eine gültige Ganzzahl eingeben." msgid "Enter a valid email address." msgstr "Bitte gültige E-Mail-Adresse eingeben." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -339,7 +353,7 @@ msgstr "Bitte nur durch Komma getrennte Ziffern eingeben." msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." msgstr "" "Bitte sicherstellen, dass der Wert %(limit_value)s ist. (Er ist " -"%(show_value)s)" +"%(show_value)s.)" #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." @@ -358,10 +372,10 @@ msgid_plural "" "%(show_value)d)." msgstr[0] "" "Bitte sicherstellen, dass der Wert aus mindestens %(limit_value)d Zeichen " -"besteht. (Er besteht aus %(show_value)d Zeichen)." +"besteht. (Er besteht aus %(show_value)d Zeichen.)" msgstr[1] "" "Bitte sicherstellen, dass der Wert aus mindestens %(limit_value)d Zeichen " -"besteht. (Er besteht aus %(show_value)d Zeichen)." +"besteht. (Er besteht aus %(show_value)d Zeichen.)" #, python-format msgid "" @@ -372,10 +386,13 @@ msgid_plural "" "%(show_value)d)." msgstr[0] "" "Bitte sicherstellen, dass der Wert aus höchstens %(limit_value)d Zeichen " -"besteht. (Er besteht aus %(show_value)d Zeichen)." +"besteht. (Er besteht aus %(show_value)d Zeichen.)" msgstr[1] "" "Bitte sicherstellen, dass der Wert aus höchstens %(limit_value)d Zeichen " -"besteht. (Er besteht aus %(show_value)d Zeichen)." +"besteht. (Er besteht aus %(show_value)d Zeichen.)" + +msgid "Enter a number." +msgstr "Bitte eine Zahl eingeben." #, python-format msgid "Ensure that there are no more than %(max)s digit in total." @@ -405,6 +422,17 @@ msgstr[1] "" "Bitte sicherstellen, dass der Wert höchstens %(max)s Ziffern vor dem Komma " "enthält." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Dateiendung „%(extension)s“ ist nicht erlaubt. Erlaubte Dateiendungen sind: " +"„%(allowed_extensions)s“." + +msgid "Null characters are not allowed." +msgstr "Nullzeichen sind nicht erlaubt." + msgid "and" msgstr "und" @@ -443,14 +471,18 @@ msgstr "Ganzzahl" #, python-format msgid "'%(value)s' value must be an integer." -msgstr "‚%(value)s‛ Wert muss eine Ganzzahl sein." +msgstr "„%(value)s“ Wert muss eine Ganzzahl sein." msgid "Big (8 byte) integer" msgstr "Große Ganzzahl (8 Byte)" #, python-format msgid "'%(value)s' value must be either True or False." -msgstr "‚%(value)s‛ Wert muss entweder True oder False sein." +msgstr "„%(value)s“ Wert muss entweder True oder False sein." + +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "„%(value)s“ Wert muss True, False oder None sein." msgid "Boolean (Either True or False)" msgstr "Boolescher Wert (True oder False)" @@ -467,7 +499,7 @@ msgid "" "'%(value)s' value has an invalid date format. It must be in YYYY-MM-DD " "format." msgstr "" -"‚%(value)s‛ Wert hat ein ungültiges Datumsformat. Es muss YYYY-MM-DD " +"„%(value)s“ Wert hat ein ungültiges Datumsformat. Es muss YYYY-MM-DD " "entsprechen." #, python-format @@ -475,7 +507,7 @@ msgid "" "'%(value)s' value has the correct format (YYYY-MM-DD) but it is an invalid " "date." msgstr "" -"‚%(value)s‛ hat das korrekte Format (YYYY-MM-DD) aber ein ungültiges Datum." +"„%(value)s“ hat das korrekte Format (YYYY-MM-DD) aber ein ungültiges Datum." msgid "Date (without time)" msgstr "Datum (ohne Uhrzeit)" @@ -485,7 +517,7 @@ msgid "" "'%(value)s' value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." "uuuuuu]][TZ] format." msgstr "" -"‚%(value)s‛ Wert hat ein ungültiges Format. Es muss YYYY-MM-DD HH:MM[:ss[." +"„%(value)s“ Wert hat ein ungültiges Format. Es muss YYYY-MM-DD HH:MM[:ss[." "uuuuuu]][TZ] entsprechen." #, python-format @@ -493,7 +525,7 @@ 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‛ Wert hat das korrekte Format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]" +"„%(value)s“ Wert hat das korrekte Format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]" "[TZ]) aber eine ungültige Zeit-/Datumsangabe." msgid "Date (with time)" @@ -501,7 +533,7 @@ msgstr "Datum (mit Uhrzeit)" #, python-format msgid "'%(value)s' value must be a decimal number." -msgstr "‚%(value)s‛ Wert muss eine Dezimalzahl sein." +msgstr "„%(value)s“ Wert muss eine Dezimalzahl sein." msgid "Decimal number" msgstr "Dezimalzahl" @@ -511,7 +543,7 @@ msgid "" "'%(value)s' value has an invalid format. It must be in [DD] [HH:[MM:]]ss[." "uuuuuu] format." msgstr "" -"'%(value)s' Wert hat ein ungültiges Format. Es muss der Form [DD] [HH:" +"„%(value)s“ Wert hat ein ungültiges Format. Es muss der Form [DD] [HH:" "[MM:]]ss[.uuuuuu] entsprechen." msgid "Duration" @@ -525,7 +557,7 @@ msgstr "Dateipfad" #, python-format msgid "'%(value)s' value must be a float." -msgstr "‚%(value)s‛ Wert muss eine Fließkommazahl sein." +msgstr "„%(value)s“ Wert muss eine Fließkommazahl sein." msgid "Floating point number" msgstr "Gleitkommazahl" @@ -538,7 +570,7 @@ msgstr "IP-Adresse" #, python-format msgid "'%(value)s' value must be either None, True or False." -msgstr "‚%(value)s‛ Wert muss entweder None, True oder False sein." +msgstr "„%(value)s“ Wert muss entweder None, True oder False sein." msgid "Boolean (Either True, False or None)" msgstr "Boolescher Wert (True, False oder None)" @@ -564,7 +596,7 @@ msgid "" "'%(value)s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " "format." msgstr "" -"‚%(value)s‛ Wert hat ein ungültiges Format. Es muss HH:MM[:ss[.uuuuuu]] " +"„%(value)s“ Wert hat ein ungültiges Format. Es muss HH:MM[:ss[.uuuuuu]] " "entsprechen." #, python-format @@ -572,7 +604,7 @@ msgid "" "'%(value)s' value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " "invalid time." msgstr "" -"‚%(value)s‛ Wert hat das korrekte Format (HH:MM[:ss[.uuuuuu]]) aber ist eine " +"„%(value)s“ Wert hat das korrekte Format (HH:MM[:ss[.uuuuuu]]) aber ist eine " "ungültige Zeitangabe." msgid "Time" @@ -586,7 +618,7 @@ msgstr "Binärdaten" #, python-format msgid "'%(value)s' is not a valid UUID." -msgstr "Wert '%(value)s' ist keine gültige UUID." +msgstr "Wert „%(value)s“ ist keine gültige UUID." msgid "File" msgstr "Datei" @@ -627,9 +659,6 @@ msgstr "Dieses Feld ist zwingend erforderlich." msgid "Enter a whole number." msgstr "Bitte eine ganze Zahl eingeben." -msgid "Enter a number." -msgstr "Bitte eine Zahl eingeben." - msgid "Enter a valid date." msgstr "Bitte ein gültiges Datum eingeben." @@ -642,6 +671,10 @@ msgstr "Bitte ein gültiges Datum und Uhrzeit eingeben." msgid "Enter a valid duration." msgstr "Bitte eine gültige Zeitspanne eingeben." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Die Anzahl der Tage muss zwischen {min_days} und {max_days} sein." + msgid "No file was submitted. Check the encoding type on the form." msgstr "" "Es wurde keine Datei übertragen. Überprüfen Sie das Encoding des Formulars." @@ -658,10 +691,10 @@ msgid_plural "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." msgstr[0] "" "Bitte sicherstellen, dass der Dateiname aus höchstens %(max)d Zeichen " -"besteht. (Er besteht aus %(length)d Zeichen)." +"besteht. (Er besteht aus %(length)d Zeichen.)" msgstr[1] "" "Bitte sicherstellen, dass der Dateiname aus höchstens %(max)d Zeichen " -"besteht. (Er besteht aus %(length)d Zeichen)." +"besteht. (Er besteht aus %(length)d Zeichen.)" msgid "Please either submit a file or check the clear checkbox, not both." msgstr "" @@ -738,17 +771,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Bitte die unten aufgeführten doppelten Werte korrigieren." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "" -"Der Inline-Fremdschlüssel passt nicht zum Primärschlüssel der übergeordneten " -"Instanz." +msgid "The inline value did not match the parent instance." +msgstr "Der Inline-Wert passt nicht zur übergeordneten Instanz." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Bitte eine gültige Auswahl treffen. Dies ist keine gültige Auswahl." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" ist kein gültiger Wert für einen Primärschlüssel." +msgid "\"%(pk)s\" is not a valid value." +msgstr "„%(pk)s“ ist kein gültiger Wert." #, python-format msgid "" @@ -758,15 +789,15 @@ msgstr "" "%(datetime)s konnte mit der Zeitzone %(current_timezone)s nicht eindeutig " "interpretiert werden, da es doppeldeutig oder eventuell inkorrekt ist." +msgid "Clear" +msgstr "Zurücksetzen" + msgid "Currently" msgstr "Derzeit" msgid "Change" msgstr "Ändern" -msgid "Clear" -msgstr "Zurücksetzen" - msgid "Unknown" msgstr "Unbekannt" @@ -1114,6 +1145,19 @@ msgstr "" "für sichere HTTPS-Verbindungen oder für „Same-Origin“-Verbindungen " "reaktivieren." +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." +msgstr "" +"Wenn der Tag „“ oder der " +"„Referrer-Policy: no-referrer“-Header verwendet wird, entfernen Sie sie " +"bitte. Der „Referer“-Header wird zur korrekten CSRF-Verifizierung benötigt. " +"Falls es datenschutzrechtliche Gründe gibt, benutzen Sie bitte Alternativen " +"wie „“ für Links zu Drittseiten." + 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 " @@ -1134,33 +1178,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Mehr Information ist verfügbar mit DEBUG=True." -msgid "Welcome to Django" -msgstr "Willkommen zu Django" - -msgid "It worked!" -msgstr "Es hat geklappt!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Herzlichen Glückwunsch zur ersten Django-basierten Seite." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Natürlich ist hier noch nichts weiter zu sehen. Bitte als Nächstes eine neue " -"Anwendung durch Ausführen von python manage.py startapp [app_label] anlegen." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Diese Mitteiling ist sichtbar weil in der settings.py-Datei DEBUG = " -"True steht und die URLs noch nicht konfiguriert sind." - msgid "No year specified" msgstr "Kein Jahr angegeben" +msgid "Date out of range" +msgstr "Datum außerhalb des zulässigen Bereichs" + msgid "No month specified" msgstr "Kein Monat angegeben" @@ -1184,7 +1207,7 @@ msgstr "" #, python-format msgid "Invalid date string '%(datestr)s' given format '%(format)s'" -msgstr "Ungültiges Datum '%(datestr)s' für das Format '%(format)s'" +msgstr "Ungültiges Datum „%(datestr)s“ für das Format „%(format)s“" #, python-format msgid "No %(verbose_name)s found matching the query" @@ -1192,7 +1215,7 @@ msgstr "Konnte keine %(verbose_name)s mit diesen Parametern finden." msgid "Page is not 'last', nor can it be converted to an int." msgstr "" -"Weder ist dies die letzte Seite ('last') noch konnte sie in einen " +"Weder ist dies die letzte Seite („last“) noch konnte sie in einen " "ganzzahligen Wert umgewandelt werden." #, python-format @@ -1201,15 +1224,60 @@ msgstr "Ungültige Seite (%(page_number)s): %(message)s" #, python-format msgid "Empty list and '%(class_name)s.allow_empty' is False." -msgstr "Leere Liste und '%(class_name)s.allow_empty' ist False." +msgstr "Leere Liste und „%(class_name)s.allow_empty“ ist False." msgid "Directory indexes are not allowed here." msgstr "Dateilisten sind untersagt." #, python-format msgid "\"%(path)s\" does not exist" -msgstr "\"%(path)s\" ist nicht vorhanden" +msgstr "„%(path)s“ ist nicht vorhanden" #, python-format msgid "Index of %(directory)s" msgstr "Verzeichnis %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: Das Webframework für Perfektionisten mit Termindruck." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Versionshinweise für Django %(version)s " +"anzeigen" + +msgid "The install worked successfully! Congratulations!" +msgstr "Die Installation war erfolgreich. Herzlichen Glückwunsch!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Diese Seite ist sichtbar weil in der Settings-Datei DEBUG = True steht und die URLs noch nicht konfiguriert " +"sind." + +msgid "Django Documentation" +msgstr "Django-Dokumentation" + +msgid "Topics, references, & how-to's" +msgstr "Themen, Referenz, & Kurzanleitungen" + +msgid "Tutorial: A Polling App" +msgstr "Tutorial: Eine Umfrage-App" + +msgid "Get started with Django" +msgstr "Los geht's mit Django" + +msgid "Django Community" +msgstr "Django-Community" + +msgid "Connect, get help, or contribute" +msgstr "Nimm Kontakt auf, erhalte Hilfe oder arbeite an Django mit" diff --git a/django/conf/locale/de/formats.py b/django/conf/locale/de/formats.py index cf1283b2a523..5e09b2cbca17 100644 --- a/django/conf/locale/de/formats.py +++ b/django/conf/locale/de/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j. F Y' TIME_FORMAT = 'H:i' DATETIME_FORMAT = 'j. F Y H:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d.%m.%Y', '%d.%m.%y', # '25.10.2006', '25.10.06' # '%d. %B %Y', '%d. %b. %Y', # '25. October 2006', '25. Oct. 2006' diff --git a/django/conf/locale/de_CH/formats.py b/django/conf/locale/de_CH/formats.py index f8a9b90631e9..b1c1e837e084 100644 --- a/django/conf/locale/de_CH/formats.py +++ b/django/conf/locale/de_CH/formats.py @@ -1,11 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date -from __future__ import unicode_literals - +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j. F Y' TIME_FORMAT = 'H:i' DATETIME_FORMAT = 'j. F Y H:i' @@ -16,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d.%m.%Y', '%d.%m.%y', # '25.10.2006', '25.10.06' # '%d. %B %Y', '%d. %b. %Y', # '25. October 2006', '25. Oct. 2006' diff --git a/django/conf/locale/dsb/LC_MESSAGES/django.mo b/django/conf/locale/dsb/LC_MESSAGES/django.mo index e9654b5d1ebb..a85f85d04446 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 8ee6011dc9dc..0f0ca4fb7dd7 100644 --- a/django/conf/locale/dsb/LC_MESSAGES/django.po +++ b/django/conf/locale/dsb/LC_MESSAGES/django.po @@ -1,13 +1,13 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Michael Wolf , 2016 +# Michael Wolf , 2016-2019 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-07-01 21:52+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-02-19 10:16+0000\n" "Last-Translator: Michael Wolf \n" "Language-Team: Lower Sorbian (http://www.transifex.com/django/django/" "language/dsb/)\n" @@ -138,6 +138,9 @@ msgstr "Górnoserbšćina" msgid "Hungarian" msgstr "Hungoršćina" +msgid "Armenian" +msgstr "Armeńšćina" + msgid "Interlingua" msgstr "Interlingua" @@ -159,6 +162,9 @@ msgstr "Japańšćina" msgid "Georgian" msgstr "Georgišćina" +msgid "Kabyle" +msgstr "Kabylšćina" + msgid "Kazakh" msgstr "Kazachšćina" @@ -294,6 +300,15 @@ msgstr "Statiske dataje" msgid "Syndication" msgstr "Syndikacija" +msgid "That page number is not an integer" +msgstr "Toś ten numer boka njejo ceła licba" + +msgid "That page number is less than 1" +msgstr "Numer boka jo mjeńšy ako 1" + +msgid "That page contains no results" +msgstr "Toś ten bok njewopśimujo wuslědki" + msgid "Enter a valid value." msgstr "Zapódajśo płaśiwu gódnotu." @@ -306,6 +321,7 @@ msgstr "Zapódajśo płaśiwu cełu licbu." msgid "Enter a valid email address." msgstr "Zapódajśo płaśiwu e-mailowu adresu." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -385,6 +401,9 @@ msgstr[3] "" "Zawěććo, až toś ta gódnota ma maksimalnje %(limit_value)d znamuškow (ma " "%(show_value)d)." +msgid "Enter a number." +msgstr "Zapódajśo licbu." + #, 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." @@ -411,6 +430,17 @@ msgstr[1] "Zawěsććo, až njejo wěcej ako %(max)s cyfrowu pśed decimalneju k msgstr[2] "Zawěsććo, až njejo wěcej ako %(max)s cyfrow pśed decimalneju komu." msgstr[3] "Zawěsććo, až njejo wěcej ako %(max)s cyfrow pśed decimalneju komu." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Datajowy sufiks ' %(extension)s' njejo dowólony. Dowólone sufikse su: ' " +"%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Znamuška nul njejsu dowólone." + msgid "and" msgstr "a" @@ -458,6 +488,10 @@ msgstr "Big (8 bajtow) integer" msgid "'%(value)s' value must be either True or False." msgstr "Gódnota '%(value)s musy pak True pak False byś." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "Gódnota '%(value)s' musy pak True, False pak None byś." + msgid "Boolean (Either True or False)" msgstr "Boolean (pak True pak False)" @@ -594,6 +628,9 @@ msgstr "Gropne binarne daty" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' njejo płaśiwy UUID." +msgid "Universally unique identifier" +msgstr "Uniwerselnje jadnorazowy identifikator" + msgid "File" msgstr "Dataja" @@ -633,9 +670,6 @@ msgstr "Toś to pólo jo trěbne." msgid "Enter a whole number." msgstr "Zapódajśo cełu licbu." -msgid "Enter a number." -msgstr "Zapódajśo licbu." - msgid "Enter a valid date." msgstr "Zapódajśo płaśiwy datum." @@ -648,6 +682,10 @@ msgstr "Zapódajśo płaśiwy datum/cas." msgid "Enter a valid duration." msgstr "Zapódaśe płaśiwe traśe." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Licba dnjow musy mjazy {min_days} a {max_days} byś." + msgid "No file was submitted. Check the encoding type on the form." msgstr "" "Dataja njejo se wótpósłała. Pśeglědujśo koděrowański typ na formularje. " @@ -755,10 +793,8 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Pšosym korigěrujśo slědujuce dwójne gódnoty." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "" -"Nutśkowny cuzy kluc njejo primarnemu klucoju nadrědowaneje instance " -"wótpowědował." +msgid "The inline value did not match the parent instance." +msgstr "Gódnota inline nadrědowanej instance njewótpowědujo." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" @@ -766,8 +802,8 @@ msgstr "" "wóleńskich móžnosćow." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" njejo płaśiwa gódnota za primarny kluc." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" njejo płaśiwa gódnota." #, python-format msgid "" @@ -777,15 +813,15 @@ msgstr "" "%(datetime)s njedajo se w casowej conje %(current_timezone)s " "interpretěrowaś; jo dwójozmysłowy abo snaź njeeksistěrujo." +msgid "Clear" +msgstr "Lašowaś" + msgid "Currently" msgstr "Tuchylu" msgid "Change" msgstr "Změniś" -msgid "Clear" -msgstr "Lašowaś" - msgid "Unknown" msgstr "Njeznaty" @@ -1059,8 +1095,8 @@ msgstr "To njejo płaśiwa IPv6-adresa." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s…" msgid "or" msgstr "abo" @@ -1146,6 +1182,19 @@ msgstr "" "znjemóžnili, zmóžniśo je pšosym zasej, nanejmjenjej za toś to sedło, za " "HTTPS-zwiski abo za napšašowanja '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." +msgstr "" +"Jolic woznamjenje wužywaśo " +"abo głowu 'Referrer-Policy: no-referrer' zapśimujośo, wótwónoźćo je. CSRF-" +"šćit pomina se głowu 'Referer', aby striktnu kontrolu referera pśewjasć. " +"Jolic se wó swóju priwatnosć staraśo, wužywajśo alternatiwy ako za wótkazy k sedłam tśeśich." + 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 " @@ -1166,33 +1215,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Dalšne informacije su k dispoziciji z DEBUG=True." -msgid "Welcome to Django" -msgstr "Witajśo k Django" - -msgid "It worked!" -msgstr "Jo funkcioněrowało!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Glukužycenje za waš prědny bok, kótaryž spěchujo se wót Django." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Zawěsće, pópšawem hyšći njejsćo žedno źěło gótował. Wuwjeźćo ako pśiduce " -"python manage.py startapp [app_label], aby swójo prědne " -"nałoženje startował." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Wiźiśo toś tu powěźeńku, dokulaž maśo DEBUG = True w swójej " -"dataji nastajenjow Django a njejsćo URL konfigurěrował. Dajśo se na źěło!" - msgid "No year specified" msgstr "Žedno lěto pódane" +msgid "Date out of range" +msgstr "Datum zwenka wobcerka" + msgid "No month specified" msgstr "Žeden mjasec pódany" @@ -1244,3 +1272,48 @@ msgstr "\"%(path)s\" njeeksistěrujo" #, python-format msgid "Index of %(directory)s" msgstr "Indeks %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django? Web-framework za perfekcionisty z terminami." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Wersijowe informacije za Django %(version)s " +"pokazaś" + +msgid "The install worked successfully! Congratulations!" +msgstr "Instalacija jo była wuspěšna! Gratulacija!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Wiźiśo toś ten bok, dokulaž DEBUG=True jo w swójej dataji nastajenjow a njejsćo konfigurěrował " +"URL." + +msgid "Django Documentation" +msgstr "Dokumentacija Django" + +msgid "Topics, references, & how-to's" +msgstr "Temy, reference a rozpokazanja" + +msgid "Tutorial: A Polling App" +msgstr "Rozpokazanje: Napšašowańske nałoženje" + +msgid "Get started with Django" +msgstr "Prědne kšace z Django" + +msgid "Django Community" +msgstr "Zgromaźeństwo Django" + +msgid "Connect, get help, or contribute" +msgstr "Zwězajśo, wobsarajśo se pomoc abo źěłajśo sobu" diff --git a/django/conf/locale/el/LC_MESSAGES/django.mo b/django/conf/locale/el/LC_MESSAGES/django.mo index bf587e00383e..d5c9ba29c2b1 100644 Binary files a/django/conf/locale/el/LC_MESSAGES/django.mo and b/django/conf/locale/el/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/el/LC_MESSAGES/django.po b/django/conf/locale/el/LC_MESSAGES/django.po index f3cda44e4f42..881df9042330 100644 --- a/django/conf/locale/el/LC_MESSAGES/django.po +++ b/django/conf/locale/el/LC_MESSAGES/django.po @@ -2,9 +2,10 @@ # # Translators: # Apostolis Bessas , 2013 -# Dimitris Glezos , 2011,2013 +# Dimitris Glezos , 2011,2013,2017 # Giannis Meletakis , 2015 # Jannis Leidel , 2011 +# Nick Mavrakis , 2017-2019 # Nikolas Demiridis , 2014 # Nick Mavrakis , 2016 # Pãnoș , 2014 @@ -16,8 +17,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-07-14 16:48+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-05-23 19:25+0000\n" "Last-Translator: Nick Mavrakis \n" "Language-Team: Greek (http://www.transifex.com/django/django/language/el/)\n" "MIME-Version: 1.0\n" @@ -146,6 +147,9 @@ msgstr "Άνω Σορβικά" msgid "Hungarian" msgstr "Ουγγρικά" +msgid "Armenian" +msgstr "Αρμενικά" + msgid "Interlingua" msgstr "Ιντερλίνγκουα" @@ -167,6 +171,9 @@ msgstr "Γιαπωνέζικα" msgid "Georgian" msgstr "Γεωργιανά" +msgid "Kabyle" +msgstr "Kabyle" + msgid "Kazakh" msgstr "Καζακστά" @@ -302,6 +309,15 @@ msgstr "Στατικά Αρχεία" msgid "Syndication" msgstr "Syndication" +msgid "That page number is not an integer" +msgstr "Ο αριθμός αυτής της σελίδας δεν είναι ακέραιος" + +msgid "That page number is less than 1" +msgstr "Ο αριθμός αυτής της σελίδας είναι μικρότερος του 1" + +msgid "That page contains no results" +msgstr "Η σελίδα αυτή δεν περιέχει αποτελέσματα" + msgid "Enter a valid value." msgstr "Εισάγετε μια έγκυρη τιμή." @@ -314,6 +330,7 @@ msgstr "Εισάγετε έναν έγκυρο ακέραιο." msgid "Enter a valid email address." msgstr "Εισάγετε μια έγκυρη διεύθυνση ηλ. ταχυδρομείου." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -364,8 +381,8 @@ msgstr[0] "" "Βεβαιωθείται πως η τιμή αυτή έχει τουλάχιστον %(limit_value)d χαρακτήρες " "(έχει %(show_value)d)." msgstr[1] "" -"Βεβαιωθείται πως η τιμή αυτή έχει τουλάχιστον %(limit_value)d χαρακτήρες " -"(έχει %(show_value)d)." +"Βεβαιωθείτε πως η τιμή έχει τουλάχιστον %(limit_value)d χαρακτήρες (έχει " +"%(show_value)d)." #, python-format msgid "" @@ -378,8 +395,11 @@ msgstr[0] "" "Βεβαιωθείται πως η τιμή αυτή έχει τοπολύ %(limit_value)d χαρακτήρες (έχει " "%(show_value)d)." msgstr[1] "" -"Βεβαιωθείται πως η τιμή αυτή έχει τουλάχιστον %(limit_value)d χαρακτήρες " -"(έχει %(show_value)d)." +"Βεβαιωθείτε πως η τιμή έχει το πολύ %(limit_value)d χαρακτήρες (έχει " +"%(show_value)d)." + +msgid "Enter a number." +msgstr "Εισάγετε έναν αριθμό." #, python-format msgid "Ensure that there are no more than %(max)s digit in total." @@ -405,6 +425,17 @@ msgstr[0] "" msgstr[1] "" "Βεβαιωθείτε ότι δεν υπάρχουν πάνω από %(max)s ψηφία πριν την υποδιαστολή." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Η επέκταση '%(extension)s' του αρχείου δεν επιτρέπεται. Οι επιτρεπόμενες " +"επεκτάσεις είναι: '%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Δεν επιτρέπονται null (μηδενικοί) χαρακτήρες" + msgid "and" msgstr "και" @@ -453,6 +484,10 @@ msgstr "Μεγάλος ακέραιος - big integer (8 bytes)" msgid "'%(value)s' value must be either True or False." msgstr "Η τιμή '%(value)s' πρέπει να είναι είτε True ή False." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "Η τιμή '%(value)s' πρέπει να είναι True, False, ή None." + msgid "Boolean (Either True or False)" msgstr "Boolean (Είτε Αληθές ή Ψευδές)" @@ -590,6 +625,9 @@ msgstr "Δυαδικά δεδομένα" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' δεν είναι ένα έγκυρο UUID." +msgid "Universally unique identifier" +msgstr "Καθολικά μοναδικό αναγνωριστικό" + msgid "File" msgstr "Αρχείο" @@ -630,9 +668,6 @@ msgstr "Αυτό το πεδίο είναι απαραίτητο." msgid "Enter a whole number." msgstr "Εισάγετε έναν ακέραιο αριθμό." -msgid "Enter a number." -msgstr "Εισάγετε έναν αριθμό." - msgid "Enter a valid date." msgstr "Εισάγετε μια έγκυρη ημερομηνία." @@ -645,6 +680,10 @@ msgstr "Εισάγετε μια έγκυρη ημερομηνία/ώρα." msgid "Enter a valid duration." msgstr "Εισάγετε μια έγκυρη διάρκεια." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Ο αριθμός των ημερών πρέπει να είναι μεταξύ {min_days} και {max_days}." + msgid "No file was submitted. Check the encoding type on the form." msgstr "" "Δεν έχει υποβληθεί κάποιο αρχείο. Ελέγξτε τον τύπο κωδικοποίησης στη φόρμα." @@ -744,10 +783,8 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Έχετε ξαναεισάγει την ίδια τιμη. Βεβαιωθείτε ότι είναι μοναδική." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "" -"Το inline foreign key δεν αντιστοιχεί με το primary κλειδί του γονικού " -"object." +msgid "The inline value did not match the parent instance." +msgstr "Η τιμή δεν είναι ίση με την αντίστοιχη τιμή του γονικού object." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" @@ -755,8 +792,8 @@ msgstr "" "επιλογές." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "Το \"%(pk)s\" δεν είναι έγκυρη τιμή για πρωτεύων κλειδί" +msgid "\"%(pk)s\" is not a valid value." +msgstr "Το \"%(pk)s\" δεν είναι έγκυρη τιμή." #, python-format msgid "" @@ -766,15 +803,15 @@ msgstr "" "Η ημερομηνία %(datetime)s δεν μπόρεσε να μετατραπεί στην ζώνη ώρας " "%(current_timezone)s; ίσως να είναι ασαφής ή να μην υπάρχει." +msgid "Clear" +msgstr "Εκκαθάριση" + msgid "Currently" msgstr "Τώρα" msgid "Change" msgstr "Επεξεργασία" -msgid "Clear" -msgstr "Εκκαθάριση" - msgid "Unknown" msgstr "Άγνωστο" @@ -1046,8 +1083,8 @@ msgstr "Αυτή δεν είναι έγκυρη διεύθυνση IPv6." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s…" msgid "or" msgstr "ή" @@ -1121,6 +1158,20 @@ msgstr "" "παρακαλούμε να τους ξανά-ενεργοποιήσετε, τουλάχιστον για αυτό το site ή για " "τις συνδέσεις HTTPS ή για τα 'same-origin' requests." +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." +msgstr "" +"Αν χρησιμοποιείτε την ετικέτα ή συμπεριλαμβάνετε την κεφαλίδα (header) 'Referrer-Policy: no-referrer', " +"παρακαλούμε αφαιρέστε τα. Η προστασία CSRF απαιτεί την κεφαλίδα 'Referer' να " +"κάνει αυστηρό έλεγχο στον referer. Αν κύριο μέλημα σας είναι η ιδιωτικότητα, " +"σκεφτείτε να χρησιμοποιήσετε εναλλακτικές μεθόδους όπως για συνδέσμους από άλλες ιστοσελίδες." + 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 " @@ -1141,33 +1192,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Περισσότερες πληροφορίες είναι διαθέσιμες με DEBUG=True." -msgid "Welcome to Django" -msgstr "Καλωσήρθατε στο Django" - -msgid "It worked!" -msgstr "Δούλεψε!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Συγχαρητήρια στην πρώτη Django-τροφοδοτούμενη σελίδα σας." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Φυσίκα, δεν έχετε κάνει καμία δουλειά ακόμα. Ξεκινήστε την πρώτη σας " -"εφαρμογή εκτελώντας python manage.py startapp [app_label]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Βλέπετε αυτό το μήνυμα επείδη έχετε DEBUG = True στο αρχείο " -"settings του Django σας και δεν έχετε ρυθμίσει καμία URL. Στρωθείτε στην " -"δουλειά!" - msgid "No year specified" msgstr "Δεν έχει οριστεί χρονιά" +msgid "Date out of range" +msgstr "Ημερομηνία εκτός εύρους" + msgid "No month specified" msgstr "Δεν έχει οριστεί μήνας" @@ -1222,3 +1252,48 @@ msgstr "Το \"%(path)s\" δεν υπάρχει" #, python-format msgid "Index of %(directory)s" msgstr "Ευρετήριο του %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: το Web framework για τελειομανείς με προθεσμίες." + +#, 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." +msgstr "" +"Βλέπετε αυτό το μήνυμα επειδή έχετε DEBUG=True στο αρχείο settings και δεν έχετε ρυθμίσει κανένα URL στο " +"αρχείο urls.py. Στρωθείτε στην δουλειά!" + +msgid "Django Documentation" +msgstr "Εγχειρίδιο Django" + +msgid "Topics, references, & how-to's" +msgstr "Θέματα, αναφορές & \"πως να...\"" + +msgid "Tutorial: A Polling App" +msgstr "Εγχειρίδιο: Ένα App Ψηφοφορίας" + +msgid "Get started with Django" +msgstr "Ξεκινήστε με το Django" + +msgid "Django Community" +msgstr "Κοινότητα Django" + +msgid "Connect, get help, or contribute" +msgstr "Συνδεθείτε, λάβετε βοήθεια, ή συνεισφέρετε" diff --git a/django/conf/locale/el/formats.py b/django/conf/locale/el/formats.py index bafa461094e6..62b9977cde7f 100644 --- a/django/conf/locale/el/formats.py +++ b/django/conf/locale/el/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'd/m/Y' TIME_FORMAT = 'P' DATETIME_FORMAT = 'd/m/Y P' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 0 # Sunday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d/%m/%Y', '%d/%m/%y', '%Y-%m-%d', # '25/10/2006', '25/10/06', '2006-10-25', ] diff --git a/django/conf/locale/en/LC_MESSAGES/django.po b/django/conf/locale/en/LC_MESSAGES/django.po index 01fa3b1534b5..2c52175822ef 100644 --- a/django/conf/locale/en/LC_MESSAGES/django.po +++ b/django/conf/locale/en/LC_MESSAGES/django.po @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: Django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -12,240 +12,249 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: conf/global_settings.py:51 +#: conf/global_settings.py:52 msgid "Afrikaans" msgstr "" -#: conf/global_settings.py:52 +#: conf/global_settings.py:53 msgid "Arabic" msgstr "" -#: conf/global_settings.py:53 +#: conf/global_settings.py:54 msgid "Asturian" msgstr "" -#: conf/global_settings.py:54 +#: conf/global_settings.py:55 msgid "Azerbaijani" msgstr "" -#: conf/global_settings.py:55 +#: conf/global_settings.py:56 msgid "Bulgarian" msgstr "" -#: conf/global_settings.py:56 +#: conf/global_settings.py:57 msgid "Belarusian" msgstr "" -#: conf/global_settings.py:57 +#: conf/global_settings.py:58 msgid "Bengali" msgstr "" -#: conf/global_settings.py:58 +#: conf/global_settings.py:59 msgid "Breton" msgstr "" -#: conf/global_settings.py:59 +#: conf/global_settings.py:60 msgid "Bosnian" msgstr "" -#: conf/global_settings.py:60 +#: conf/global_settings.py:61 msgid "Catalan" msgstr "" -#: conf/global_settings.py:61 +#: conf/global_settings.py:62 msgid "Czech" msgstr "" -#: conf/global_settings.py:62 +#: conf/global_settings.py:63 msgid "Welsh" msgstr "" -#: conf/global_settings.py:63 +#: conf/global_settings.py:64 msgid "Danish" msgstr "" -#: conf/global_settings.py:64 +#: conf/global_settings.py:65 msgid "German" msgstr "" -#: conf/global_settings.py:70 +#: conf/global_settings.py:66 msgid "Lower Sorbian" msgstr "" -#: conf/global_settings.py:71 +#: conf/global_settings.py:67 msgid "Greek" msgstr "" -#: conf/global_settings.py:66 +#: conf/global_settings.py:68 msgid "English" msgstr "" -#: conf/global_settings.py:67 +#: conf/global_settings.py:69 msgid "Australian English" msgstr "" -#: conf/global_settings.py:68 +#: conf/global_settings.py:70 msgid "British English" msgstr "" -#: conf/global_settings.py:69 +#: conf/global_settings.py:71 msgid "Esperanto" msgstr "" -#: conf/global_settings.py:70 +#: conf/global_settings.py:72 msgid "Spanish" msgstr "" -#: conf/global_settings.py:71 +#: conf/global_settings.py:73 msgid "Argentinian Spanish" msgstr "" -#: conf/global_settings.py:72 +#: conf/global_settings.py:74 msgid "Colombian Spanish" msgstr "" -#: conf/global_settings.py:72 +#: conf/global_settings.py:75 msgid "Mexican Spanish" msgstr "" -#: conf/global_settings.py:73 +#: conf/global_settings.py:76 msgid "Nicaraguan Spanish" msgstr "" -#: conf/global_settings.py:74 +#: conf/global_settings.py:77 msgid "Venezuelan Spanish" msgstr "" -#: conf/global_settings.py:75 +#: conf/global_settings.py:78 msgid "Estonian" msgstr "" -#: conf/global_settings.py:76 +#: conf/global_settings.py:79 msgid "Basque" msgstr "" -#: conf/global_settings.py:77 +#: conf/global_settings.py:80 msgid "Persian" msgstr "" -#: conf/global_settings.py:78 +#: conf/global_settings.py:81 msgid "Finnish" msgstr "" -#: conf/global_settings.py:79 +#: conf/global_settings.py:82 msgid "French" msgstr "" -#: conf/global_settings.py:80 +#: conf/global_settings.py:83 msgid "Frisian" msgstr "" -#: conf/global_settings.py:81 +#: conf/global_settings.py:84 msgid "Irish" msgstr "" -#: conf/global_settings.py:83 +#: conf/global_settings.py:85 msgid "Scottish Gaelic" msgstr "" -#: conf/global_settings.py:82 +#: conf/global_settings.py:86 msgid "Galician" msgstr "" -#: conf/global_settings.py:83 +#: conf/global_settings.py:87 msgid "Hebrew" msgstr "" -#: conf/global_settings.py:84 +#: conf/global_settings.py:88 msgid "Hindi" msgstr "" -#: conf/global_settings.py:85 +#: conf/global_settings.py:89 msgid "Croatian" msgstr "" -#: conf/global_settings.py:94 +#: conf/global_settings.py:90 msgid "Upper Sorbian" msgstr "" -#: conf/global_settings.py:95 +#: conf/global_settings.py:91 msgid "Hungarian" msgstr "" -#: conf/global_settings.py:87 +#: conf/global_settings.py:92 +msgid "Armenian" +msgstr "" + +#: conf/global_settings.py:93 msgid "Interlingua" msgstr "" -#: conf/global_settings.py:88 +#: conf/global_settings.py:94 msgid "Indonesian" msgstr "" -#: conf/global_settings.py:89 +#: conf/global_settings.py:95 msgid "Ido" msgstr "" -#: conf/global_settings.py:90 +#: conf/global_settings.py:96 msgid "Icelandic" msgstr "" -#: conf/global_settings.py:91 +#: conf/global_settings.py:97 msgid "Italian" msgstr "" -#: conf/global_settings.py:92 +#: conf/global_settings.py:98 msgid "Japanese" msgstr "" -#: conf/global_settings.py:93 +#: conf/global_settings.py:99 msgid "Georgian" msgstr "" -#: conf/global_settings.py:94 +#: conf/global_settings.py:100 +msgid "Kabyle" +msgstr "" + +#: conf/global_settings.py:101 msgid "Kazakh" msgstr "" -#: conf/global_settings.py:95 +#: conf/global_settings.py:102 msgid "Khmer" msgstr "" -#: conf/global_settings.py:96 +#: conf/global_settings.py:103 msgid "Kannada" msgstr "" -#: conf/global_settings.py:97 +#: conf/global_settings.py:104 msgid "Korean" msgstr "" -#: conf/global_settings.py:98 +#: conf/global_settings.py:105 msgid "Luxembourgish" msgstr "" -#: conf/global_settings.py:99 +#: conf/global_settings.py:106 msgid "Lithuanian" msgstr "" -#: conf/global_settings.py:100 +#: conf/global_settings.py:107 msgid "Latvian" msgstr "" -#: conf/global_settings.py:101 +#: conf/global_settings.py:108 msgid "Macedonian" msgstr "" -#: conf/global_settings.py:102 +#: conf/global_settings.py:109 msgid "Malayalam" msgstr "" -#: conf/global_settings.py:103 +#: conf/global_settings.py:110 msgid "Mongolian" msgstr "" -#: conf/global_settings.py:104 +#: conf/global_settings.py:111 msgid "Marathi" msgstr "" -#: conf/global_settings.py:105 +#: conf/global_settings.py:112 msgid "Burmese" msgstr "" @@ -253,115 +262,115 @@ msgstr "" msgid "Norwegian Bokmål" msgstr "" -#: conf/global_settings.py:107 +#: conf/global_settings.py:114 msgid "Nepali" msgstr "" -#: conf/global_settings.py:108 +#: conf/global_settings.py:115 msgid "Dutch" msgstr "" -#: conf/global_settings.py:109 +#: conf/global_settings.py:116 msgid "Norwegian Nynorsk" msgstr "" -#: conf/global_settings.py:110 +#: conf/global_settings.py:117 msgid "Ossetic" msgstr "" -#: conf/global_settings.py:111 +#: conf/global_settings.py:118 msgid "Punjabi" msgstr "" -#: conf/global_settings.py:112 +#: conf/global_settings.py:119 msgid "Polish" msgstr "" -#: conf/global_settings.py:113 +#: conf/global_settings.py:120 msgid "Portuguese" msgstr "" -#: conf/global_settings.py:114 +#: conf/global_settings.py:121 msgid "Brazilian Portuguese" msgstr "" -#: conf/global_settings.py:115 +#: conf/global_settings.py:122 msgid "Romanian" msgstr "" -#: conf/global_settings.py:116 +#: conf/global_settings.py:123 msgid "Russian" msgstr "" -#: conf/global_settings.py:117 +#: conf/global_settings.py:124 msgid "Slovak" msgstr "" -#: conf/global_settings.py:118 +#: conf/global_settings.py:125 msgid "Slovenian" msgstr "" -#: conf/global_settings.py:119 +#: conf/global_settings.py:126 msgid "Albanian" msgstr "" -#: conf/global_settings.py:120 +#: conf/global_settings.py:127 msgid "Serbian" msgstr "" -#: conf/global_settings.py:121 +#: conf/global_settings.py:128 msgid "Serbian Latin" msgstr "" -#: conf/global_settings.py:122 +#: conf/global_settings.py:129 msgid "Swedish" msgstr "" -#: conf/global_settings.py:123 +#: conf/global_settings.py:130 msgid "Swahili" msgstr "" -#: conf/global_settings.py:124 +#: conf/global_settings.py:131 msgid "Tamil" msgstr "" -#: conf/global_settings.py:125 +#: conf/global_settings.py:132 msgid "Telugu" msgstr "" -#: conf/global_settings.py:126 +#: conf/global_settings.py:133 msgid "Thai" msgstr "" -#: conf/global_settings.py:127 +#: conf/global_settings.py:134 msgid "Turkish" msgstr "" -#: conf/global_settings.py:128 +#: conf/global_settings.py:135 msgid "Tatar" msgstr "" -#: conf/global_settings.py:129 +#: conf/global_settings.py:136 msgid "Udmurt" msgstr "" -#: conf/global_settings.py:130 +#: conf/global_settings.py:137 msgid "Ukrainian" msgstr "" -#: conf/global_settings.py:131 +#: conf/global_settings.py:138 msgid "Urdu" msgstr "" -#: conf/global_settings.py:132 +#: conf/global_settings.py:139 msgid "Vietnamese" msgstr "" -#: conf/global_settings.py:133 +#: conf/global_settings.py:140 msgid "Simplified Chinese" msgstr "" -#: conf/global_settings.py:134 +#: conf/global_settings.py:141 msgid "Traditional Chinese" msgstr "" @@ -373,7 +382,7 @@ msgstr "" msgid "Site Maps" msgstr "" -#: contrib/staticfiles/apps.py:7 +#: contrib/staticfiles/apps.py:9 msgid "Static Files" msgstr "" @@ -381,65 +390,78 @@ msgstr "" msgid "Syndication" msgstr "" -#: core/validators.py:33 +#: core/paginator.py:45 +msgid "That page number is not an integer" +msgstr "" + +#: core/paginator.py:47 +msgid "That page number is less than 1" +msgstr "" + +#: core/paginator.py:52 +msgid "That page contains no results" +msgstr "" + +#: core/validators.py:31 msgid "Enter a valid value." msgstr "" -#: core/validators.py:98 forms/fields.py:677 +#: core/validators.py:102 forms/fields.py:658 msgid "Enter a valid URL." msgstr "" -#: core/validators.py:141 +#: core/validators.py:154 msgid "Enter a valid integer." msgstr "" -#: core/validators.py:152 +#: core/validators.py:165 msgid "Enter a valid email address." msgstr "" -#: core/validators.py:225 +#. Translators: "letters" means latin letters: a-z and A-Z. +#: core/validators.py:239 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" -#: core/validators.py:232 +#: core/validators.py:246 msgid "" "Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or " "hyphens." msgstr "" -#: core/validators.py:237 core/validators.py:256 +#: core/validators.py:255 core/validators.py:275 msgid "Enter a valid IPv4 address." msgstr "" -#: core/validators.py:242 core/validators.py:257 +#: core/validators.py:260 core/validators.py:276 msgid "Enter a valid IPv6 address." msgstr "" -#: core/validators.py:252 core/validators.py:255 +#: core/validators.py:270 core/validators.py:274 msgid "Enter a valid IPv4 or IPv6 address." msgstr "" -#: core/validators.py:284 db/models/fields/__init__.py:1147 +#: core/validators.py:304 msgid "Enter only digits separated by commas." msgstr "" -#: core/validators.py:292 +#: core/validators.py:310 #, python-format msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." msgstr "" -#: core/validators.py:318 +#: core/validators.py:342 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "" -#: core/validators.py:325 +#: core/validators.py:351 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "" -#: core/validators.py:334 +#: core/validators.py:361 #, python-format msgid "" "Ensure this value has at least %(limit_value)d character (it has " @@ -450,7 +472,7 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: core/validators.py:345 +#: core/validators.py:376 #, python-format msgid "" "Ensure this value has at most %(limit_value)d character (it has " @@ -461,21 +483,25 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: core/validators.py:359 +#: core/validators.py:395 forms/fields.py:290 forms/fields.py:325 +msgid "Enter a number." +msgstr "" + +#: core/validators.py:397 #, 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] "" -#: core/validators.py:364 +#: core/validators.py:402 #, 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] "" -#: core/validators.py:369 +#: core/validators.py:407 #, python-format msgid "" "Ensure that there are no more than %(max)s digit before the decimal point." @@ -484,301 +510,322 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: db/models/base.py:1095 forms/models.py:727 +#: core/validators.py:469 +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +#: core/validators.py:521 +msgid "Null characters are not allowed." +msgstr "" + +#: db/models/base.py:1156 forms/models.py:756 msgid "and" msgstr "" -#: db/models/base.py:1097 +#: db/models/base.py:1158 #, python-format msgid "%(model_name)s with this %(field_labels)s already exists." msgstr "" -#: db/models/fields/__init__.py:110 +#: db/models/fields/__init__.py:104 #, python-format msgid "Value %(value)r is not a valid choice." msgstr "" -#: db/models/fields/__init__.py:111 +#: db/models/fields/__init__.py:105 msgid "This field cannot be null." msgstr "" -#: db/models/fields/__init__.py:112 +#: db/models/fields/__init__.py:106 msgid "This field cannot be blank." msgstr "" -#: db/models/fields/__init__.py:113 +#: db/models/fields/__init__.py:107 #, python-format msgid "%(model_name)s with this %(field_label)s already exists." msgstr "" #. 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:117 +#: db/models/fields/__init__.py:111 #, python-format msgid "" "%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." msgstr "" -#: db/models/fields/__init__.py:134 +#: db/models/fields/__init__.py:128 #, python-format msgid "Field of type: %(field_type)s" msgstr "" -#: db/models/fields/__init__.py:930 db/models/fields/__init__.py:1834 +#: db/models/fields/__init__.py:899 db/models/fields/__init__.py:1766 msgid "Integer" msgstr "" -#: db/models/fields/__init__.py:934 db/models/fields/__init__.py:1832 +#: db/models/fields/__init__.py:903 db/models/fields/__init__.py:1764 #, python-format msgid "'%(value)s' value must be an integer." msgstr "" -#: db/models/fields/__init__.py:960 db/models/fields/__init__.py:1849 +#: db/models/fields/__init__.py:978 db/models/fields/__init__.py:1832 msgid "Big (8 byte) integer" msgstr "" -#: db/models/fields/__init__.py:972 +#: db/models/fields/__init__.py:990 #, python-format msgid "'%(value)s' value must be either True or False." msgstr "" -#: db/models/fields/__init__.py:1011 +#: db/models/fields/__init__.py:991 +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "" + +#: db/models/fields/__init__.py:993 msgid "Boolean (Either True or False)" msgstr "" -#: db/models/fields/__init__.py:1086 +#: db/models/fields/__init__.py:1034 #, python-format msgid "String (up to %(max_length)s)" msgstr "" -#: db/models/fields/__init__.py:1142 +#: db/models/fields/__init__.py:1098 msgid "Comma-separated integers" msgstr "" -#: db/models/fields/__init__.py:1191 +#: db/models/fields/__init__.py:1147 #, python-format msgid "" "'%(value)s' value has an invalid date format. It must be in YYYY-MM-DD " "format." msgstr "" -#: db/models/fields/__init__.py:1193 db/models/fields/__init__.py:1336 +#: db/models/fields/__init__.py:1149 db/models/fields/__init__.py:1292 #, python-format msgid "" "'%(value)s' value has the correct format (YYYY-MM-DD) but it is an invalid " "date." msgstr "" -#: db/models/fields/__init__.py:1196 +#: db/models/fields/__init__.py:1152 msgid "Date (without time)" msgstr "" -#: db/models/fields/__init__.py:1334 +#: db/models/fields/__init__.py:1290 #, python-format msgid "" "'%(value)s' value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." "uuuuuu]][TZ] format." msgstr "" -#: db/models/fields/__init__.py:1338 +#: db/models/fields/__init__.py:1294 #, 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 "" -#: db/models/fields/__init__.py:1342 +#: db/models/fields/__init__.py:1298 msgid "Date (with time)" msgstr "" -#: db/models/fields/__init__.py:1494 +#: db/models/fields/__init__.py:1446 #, python-format msgid "'%(value)s' value must be a decimal number." msgstr "" -#: db/models/fields/__init__.py:1496 +#: db/models/fields/__init__.py:1448 msgid "Decimal number" msgstr "" -#: db/models/fields/__init__.py:1653 +#: db/models/fields/__init__.py:1587 #, python-format msgid "" "'%(value)s' value has an invalid format. It must be in [DD] [HH:[MM:]]ss[." "uuuuuu] format." msgstr "" -#: db/models/fields/__init__.py:1656 +#: db/models/fields/__init__.py:1590 msgid "Duration" msgstr "" -#: db/models/fields/__init__.py:1707 +#: db/models/fields/__init__.py:1640 msgid "Email address" msgstr "" -#: db/models/fields/__init__.py:1731 +#: db/models/fields/__init__.py:1663 msgid "File path" msgstr "" -#: db/models/fields/__init__.py:1798 +#: db/models/fields/__init__.py:1729 #, python-format msgid "'%(value)s' value must be a float." msgstr "" -#: db/models/fields/__init__.py:1800 +#: db/models/fields/__init__.py:1731 msgid "Floating point number" msgstr "" -#: db/models/fields/__init__.py:1864 +#: db/models/fields/__init__.py:1848 msgid "IPv4 address" msgstr "" -#: db/models/fields/__init__.py:1947 +#: db/models/fields/__init__.py:1879 msgid "IP address" msgstr "" -#: db/models/fields/__init__.py:2031 +#: db/models/fields/__init__.py:1959 db/models/fields/__init__.py:1960 #, python-format msgid "'%(value)s' value must be either None, True or False." msgstr "" -#: db/models/fields/__init__.py:2033 +#: db/models/fields/__init__.py:1962 msgid "Boolean (Either True, False or None)" msgstr "" -#: db/models/fields/__init__.py:2093 +#: db/models/fields/__init__.py:1997 msgid "Positive integer" msgstr "" -#: db/models/fields/__init__.py:2105 +#: db/models/fields/__init__.py:2010 msgid "Positive small integer" msgstr "" -#: db/models/fields/__init__.py:2118 +#: db/models/fields/__init__.py:2024 #, python-format msgid "Slug (up to %(max_length)s)" msgstr "" -#: db/models/fields/__init__.py:2152 +#: db/models/fields/__init__.py:2056 msgid "Small integer" msgstr "" -#: db/models/fields/__init__.py:2159 +#: db/models/fields/__init__.py:2063 msgid "Text" msgstr "" -#: db/models/fields/__init__.py:2185 +#: db/models/fields/__init__.py:2091 #, python-format msgid "" "'%(value)s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " "format." msgstr "" -#: db/models/fields/__init__.py:2187 +#: db/models/fields/__init__.py:2093 #, python-format msgid "" "'%(value)s' value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " "invalid time." msgstr "" -#: db/models/fields/__init__.py:2190 +#: db/models/fields/__init__.py:2096 msgid "Time" msgstr "" -#: db/models/fields/__init__.py:2318 +#: db/models/fields/__init__.py:2222 msgid "URL" msgstr "" -#: db/models/fields/__init__.py:2341 +#: db/models/fields/__init__.py:2244 msgid "Raw binary data" msgstr "" -#: db/models/fields/__init__.py:2385 +#: db/models/fields/__init__.py:2294 #, python-format msgid "'%(value)s' is not a valid UUID." msgstr "" -#: db/models/fields/files.py:237 +#: db/models/fields/__init__.py:2296 +msgid "Universally unique identifier" +msgstr "" + +#: db/models/fields/files.py:221 msgid "File" msgstr "" -#: db/models/fields/files.py:392 +#: db/models/fields/files.py:360 msgid "Image" msgstr "" -#: db/models/fields/related.py:723 +#: db/models/fields/related.py:778 #, python-format msgid "%(model)s instance with %(field)s %(value)r does not exist." msgstr "" -#: db/models/fields/related.py:725 +#: db/models/fields/related.py:780 msgid "Foreign Key (type determined by related field)" msgstr "" -#: db/models/fields/related.py:983 +#: db/models/fields/related.py:1007 msgid "One-to-one relationship" msgstr "" -#: db/models/fields/related.py:1049 +#: db/models/fields/related.py:1057 #, python-format msgid "%(from)s-%(to)s relationship" msgstr "" -#: db/models/fields/related.py:1050 +#: db/models/fields/related.py:1058 #, python-format msgid "%(from)s-%(to)s relationships" msgstr "" -#: db/models/fields/related.py:1092 +#: db/models/fields/related.py:1100 msgid "Many-to-many relationship" msgstr "" #. 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:167 +#: forms/boundfield.py:146 msgid ":?.!" msgstr "" -#: forms/fields.py:65 +#: forms/fields.py:53 msgid "This field is required." msgstr "" -#: forms/fields.py:254 +#: forms/fields.py:245 msgid "Enter a whole number." msgstr "" -#: forms/fields.py:299 forms/fields.py:336 -msgid "Enter a number." -msgstr "" - -#: forms/fields.py:414 forms/fields.py:1158 +#: forms/fields.py:396 forms/fields.py:1126 msgid "Enter a valid date." msgstr "" -#: forms/fields.py:438 forms/fields.py:1159 +#: forms/fields.py:420 forms/fields.py:1127 msgid "Enter a valid time." msgstr "" -#: forms/fields.py:460 +#: forms/fields.py:442 msgid "Enter a valid date/time." msgstr "" -#: forms/fields.py:489 +#: forms/fields.py:471 msgid "Enter a valid duration." msgstr "" -#: forms/fields.py:556 +#: forms/fields.py:472 +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "" + +#: forms/fields.py:532 msgid "No file was submitted. Check the encoding type on the form." msgstr "" -#: forms/fields.py:557 +#: forms/fields.py:533 msgid "No file was submitted." msgstr "" -#: forms/fields.py:558 +#: forms/fields.py:534 msgid "The submitted file is empty." msgstr "" -#: forms/fields.py:560 +#: forms/fields.py:536 #, python-format msgid "Ensure this filename has at most %(max)d character (it has %(length)d)." msgid_plural "" @@ -786,191 +833,191 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: forms/fields.py:563 +#: forms/fields.py:539 msgid "Please either submit a file or check the clear checkbox, not both." msgstr "" -#: forms/fields.py:625 +#: forms/fields.py:600 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "" -#: forms/fields.py:792 forms/fields.py:886 forms/models.py:1230 +#: forms/fields.py:762 forms/fields.py:852 forms/models.py:1270 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "" -#: forms/fields.py:887 forms/fields.py:1002 forms/models.py:1229 +#: forms/fields.py:853 forms/fields.py:968 forms/models.py:1269 msgid "Enter a list of values." msgstr "" -#: forms/fields.py:1003 +#: forms/fields.py:969 msgid "Enter a complete value." msgstr "" -#: forms/fields.py:1217 +#: forms/fields.py:1185 msgid "Enter a valid UUID." msgstr "" #. Translators: This is the default suffix added to form field labels -#: forms/forms.py:84 +#: forms/forms.py:86 msgid ":" msgstr "" -#: forms/forms.py:191 +#: forms/forms.py:212 #, python-format msgid "(Hidden field %(name)s) %(error)s" msgstr "" -#: forms/formsets.py:97 +#: forms/formsets.py:91 msgid "ManagementForm data is missing or has been tampered with" msgstr "" -#: forms/formsets.py:345 +#: forms/formsets.py:338 #, python-format msgid "Please submit %d or fewer forms." msgid_plural "Please submit %d or fewer forms." msgstr[0] "" msgstr[1] "" -#: forms/formsets.py:352 +#: forms/formsets.py:345 #, python-format msgid "Please submit %d or more forms." msgid_plural "Please submit %d or more forms." msgstr[0] "" msgstr[1] "" -#: forms/formsets.py:380 forms/formsets.py:382 +#: forms/formsets.py:371 forms/formsets.py:373 msgid "Order" msgstr "" -#: forms/formsets.py:384 +#: forms/formsets.py:375 msgid "Delete" msgstr "" -#: forms/models.py:721 +#: forms/models.py:751 #, python-format msgid "Please correct the duplicate data for %(field)s." msgstr "" -#: forms/models.py:725 +#: forms/models.py:755 #, python-format msgid "Please correct the duplicate data for %(field)s, which must be unique." msgstr "" -#: forms/models.py:731 +#: forms/models.py:761 #, 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 "" -#: forms/models.py:739 +#: forms/models.py:770 msgid "Please correct the duplicate values below." msgstr "" -#: forms/models.py:1063 -msgid "The inline foreign key did not match the parent instance primary key." +#: forms/models.py:1091 +msgid "The inline value did not match the parent instance." msgstr "" -#: forms/models.py:1123 +#: forms/models.py:1158 msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" -#: forms/models.py:1232 +#: forms/models.py:1272 #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" -#: forms/utils.py:172 +#: forms/utils.py:162 #, 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 "" -#: forms/widgets.py:353 -msgid "Currently" +#: forms/widgets.py:383 +msgid "Clear" msgstr "" -#: forms/widgets.py:354 -msgid "Change" +#: forms/widgets.py:384 +msgid "Currently" msgstr "" -#: forms/widgets.py:355 -msgid "Clear" +#: forms/widgets.py:385 +msgid "Change" msgstr "" -#: forms/widgets.py:570 +#: forms/widgets.py:699 msgid "Unknown" msgstr "" -#: forms/widgets.py:571 +#: forms/widgets.py:700 msgid "Yes" msgstr "" -#: forms/widgets.py:572 +#: forms/widgets.py:701 msgid "No" msgstr "" -#: template/defaultfilters.py:867 +#: template/defaultfilters.py:788 msgid "yes,no,maybe" msgstr "" -#: template/defaultfilters.py:896 template/defaultfilters.py:908 +#: template/defaultfilters.py:817 template/defaultfilters.py:834 #, python-format msgid "%(size)d byte" msgid_plural "%(size)d bytes" msgstr[0] "" msgstr[1] "" -#: template/defaultfilters.py:910 +#: template/defaultfilters.py:836 #, python-format msgid "%s KB" msgstr "" -#: template/defaultfilters.py:912 +#: template/defaultfilters.py:838 #, python-format msgid "%s MB" msgstr "" -#: template/defaultfilters.py:914 +#: template/defaultfilters.py:840 #, python-format msgid "%s GB" msgstr "" -#: template/defaultfilters.py:916 +#: template/defaultfilters.py:842 #, python-format msgid "%s TB" msgstr "" -#: template/defaultfilters.py:918 +#: template/defaultfilters.py:844 #, python-format msgid "%s PB" msgstr "" -#: utils/dateformat.py:61 +#: utils/dateformat.py:62 msgid "p.m." msgstr "" -#: utils/dateformat.py:62 +#: utils/dateformat.py:63 msgid "a.m." msgstr "" -#: utils/dateformat.py:67 +#: utils/dateformat.py:68 msgid "PM" msgstr "" -#: utils/dateformat.py:68 +#: utils/dateformat.py:69 msgid "AM" msgstr "" -#: utils/dateformat.py:151 +#: utils/dateformat.py:150 msgid "midnight" msgstr "" -#: utils/dateformat.py:153 +#: utils/dateformat.py:152 msgid "noon" msgstr "" @@ -1030,296 +1077,296 @@ msgstr "" msgid "Sun" msgstr "" -#: utils/dates.py:18 +#: utils/dates.py:14 msgid "January" msgstr "" -#: utils/dates.py:18 +#: utils/dates.py:14 msgid "February" msgstr "" -#: utils/dates.py:18 +#: utils/dates.py:14 msgid "March" msgstr "" -#: utils/dates.py:18 +#: utils/dates.py:14 msgid "April" msgstr "" -#: utils/dates.py:18 +#: utils/dates.py:14 msgid "May" msgstr "" -#: utils/dates.py:18 +#: utils/dates.py:14 msgid "June" msgstr "" -#: utils/dates.py:19 +#: utils/dates.py:15 msgid "July" msgstr "" -#: utils/dates.py:19 +#: utils/dates.py:15 msgid "August" msgstr "" -#: utils/dates.py:19 +#: utils/dates.py:15 msgid "September" msgstr "" -#: utils/dates.py:19 +#: utils/dates.py:15 msgid "October" msgstr "" -#: utils/dates.py:19 +#: utils/dates.py:15 msgid "November" msgstr "" -#: utils/dates.py:20 +#: utils/dates.py:16 msgid "December" msgstr "" -#: utils/dates.py:23 +#: utils/dates.py:19 msgid "jan" msgstr "" -#: utils/dates.py:23 +#: utils/dates.py:19 msgid "feb" msgstr "" -#: utils/dates.py:23 +#: utils/dates.py:19 msgid "mar" msgstr "" -#: utils/dates.py:23 +#: utils/dates.py:19 msgid "apr" msgstr "" -#: utils/dates.py:23 +#: utils/dates.py:19 msgid "may" msgstr "" -#: utils/dates.py:23 +#: utils/dates.py:19 msgid "jun" msgstr "" -#: utils/dates.py:24 +#: utils/dates.py:20 msgid "jul" msgstr "" -#: utils/dates.py:24 +#: utils/dates.py:20 msgid "aug" msgstr "" -#: utils/dates.py:24 +#: utils/dates.py:20 msgid "sep" msgstr "" -#: utils/dates.py:24 +#: utils/dates.py:20 msgid "oct" msgstr "" -#: utils/dates.py:24 +#: utils/dates.py:20 msgid "nov" msgstr "" -#: utils/dates.py:24 +#: utils/dates.py:20 msgid "dec" msgstr "" -#: utils/dates.py:31 +#: utils/dates.py:23 msgctxt "abbrev. month" msgid "Jan." msgstr "" -#: utils/dates.py:32 +#: utils/dates.py:24 msgctxt "abbrev. month" msgid "Feb." msgstr "" -#: utils/dates.py:33 +#: utils/dates.py:25 msgctxt "abbrev. month" msgid "March" msgstr "" -#: utils/dates.py:34 +#: utils/dates.py:26 msgctxt "abbrev. month" msgid "April" msgstr "" -#: utils/dates.py:35 +#: utils/dates.py:27 msgctxt "abbrev. month" msgid "May" msgstr "" -#: utils/dates.py:36 +#: utils/dates.py:28 msgctxt "abbrev. month" msgid "June" msgstr "" -#: utils/dates.py:37 +#: utils/dates.py:29 msgctxt "abbrev. month" msgid "July" msgstr "" -#: utils/dates.py:38 +#: utils/dates.py:30 msgctxt "abbrev. month" msgid "Aug." msgstr "" -#: utils/dates.py:39 +#: utils/dates.py:31 msgctxt "abbrev. month" msgid "Sept." msgstr "" -#: utils/dates.py:40 +#: utils/dates.py:32 msgctxt "abbrev. month" msgid "Oct." msgstr "" -#: utils/dates.py:41 +#: utils/dates.py:33 msgctxt "abbrev. month" msgid "Nov." msgstr "" -#: utils/dates.py:42 +#: utils/dates.py:34 msgctxt "abbrev. month" msgid "Dec." msgstr "" -#: utils/dates.py:45 +#: utils/dates.py:37 msgctxt "alt. month" msgid "January" msgstr "" -#: utils/dates.py:46 +#: utils/dates.py:38 msgctxt "alt. month" msgid "February" msgstr "" -#: utils/dates.py:47 +#: utils/dates.py:39 msgctxt "alt. month" msgid "March" msgstr "" -#: utils/dates.py:48 +#: utils/dates.py:40 msgctxt "alt. month" msgid "April" msgstr "" -#: utils/dates.py:49 +#: utils/dates.py:41 msgctxt "alt. month" msgid "May" msgstr "" -#: utils/dates.py:50 +#: utils/dates.py:42 msgctxt "alt. month" msgid "June" msgstr "" -#: utils/dates.py:51 +#: utils/dates.py:43 msgctxt "alt. month" msgid "July" msgstr "" -#: utils/dates.py:52 +#: utils/dates.py:44 msgctxt "alt. month" msgid "August" msgstr "" -#: utils/dates.py:53 +#: utils/dates.py:45 msgctxt "alt. month" msgid "September" msgstr "" -#: utils/dates.py:54 +#: utils/dates.py:46 msgctxt "alt. month" msgid "October" msgstr "" -#: utils/dates.py:55 +#: utils/dates.py:47 msgctxt "alt. month" msgid "November" msgstr "" -#: utils/dates.py:56 +#: utils/dates.py:48 msgctxt "alt. month" msgid "December" msgstr "" -#: utils/ipv6.py:10 +#: utils/ipv6.py:8 msgid "This is not a valid IPv6 address." msgstr "" -#: utils/text.py:77 +#: utils/text.py:67 #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." +msgid "%(truncated_text)s…" msgstr "" -#: utils/text.py:246 +#: utils/text.py:233 msgid "or" msgstr "" #. Translators: This string is used as a separator between list elements -#: utils/text.py:265 utils/timesince.py:63 +#: utils/text.py:252 utils/timesince.py:83 msgid ", " msgstr "" -#: utils/timesince.py:11 +#: utils/timesince.py:9 #, python-format msgid "%d year" msgid_plural "%d years" msgstr[0] "" msgstr[1] "" -#: utils/timesince.py:12 +#: utils/timesince.py:10 #, python-format msgid "%d month" msgid_plural "%d months" msgstr[0] "" msgstr[1] "" -#: utils/timesince.py:13 +#: utils/timesince.py:11 #, python-format msgid "%d week" msgid_plural "%d weeks" msgstr[0] "" msgstr[1] "" -#: utils/timesince.py:14 +#: utils/timesince.py:12 #, python-format msgid "%d day" msgid_plural "%d days" msgstr[0] "" msgstr[1] "" -#: utils/timesince.py:15 +#: utils/timesince.py:13 #, python-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "" msgstr[1] "" -#: utils/timesince.py:16 +#: utils/timesince.py:14 #, python-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "" msgstr[1] "" -#: utils/timesince.py:52 +#: utils/timesince.py:72 msgid "0 minutes" msgstr "" -#: views/csrf.py:107 +#: views/csrf.py:110 msgid "Forbidden" msgstr "" -#: views/csrf.py:108 +#: views/csrf.py:111 msgid "CSRF verification failed. Request aborted." msgstr "" -#: views/csrf.py:112 +#: views/csrf.py:115 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 " @@ -1327,116 +1374,154 @@ msgid "" "hijacked by third parties." msgstr "" -#: views/csrf.py:117 +#: views/csrf.py:120 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 "" -#: views/csrf.py:122 +#: views/csrf.py:124 +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." +msgstr "" + +#: views/csrf.py:132 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 "" -#: views/csrf.py:127 +#: views/csrf.py:137 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 "" -#: views/csrf.py:132 +#: views/csrf.py:142 msgid "More information is available with DEBUG=True." msgstr "" -#: views/debug.py:508 -msgid "Welcome to Django" -msgstr "" - -#: views/debug.py:509 -msgid "It worked!" -msgstr "" - -#: views/debug.py:510 -msgid "Congratulations on your first Django-powered page." -msgstr "" - -#: views/debug.py:511 -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -#: views/debug.py:513 -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" +#: views/generic/dates.py:41 +msgid "No year specified" msgstr "" -#: views/generic/dates.py:48 -msgid "No year specified" +#: views/generic/dates.py:61 views/generic/dates.py:111 +#: views/generic/dates.py:208 +msgid "Date out of range" msgstr "" -#: views/generic/dates.py:104 +#: views/generic/dates.py:90 msgid "No month specified" msgstr "" -#: views/generic/dates.py:163 +#: views/generic/dates.py:142 msgid "No day specified" msgstr "" -#: views/generic/dates.py:219 +#: views/generic/dates.py:188 msgid "No week specified" msgstr "" -#: views/generic/dates.py:378 views/generic/dates.py:406 +#: views/generic/dates.py:338 views/generic/dates.py:367 #, python-format msgid "No %(verbose_name_plural)s available" msgstr "" -#: views/generic/dates.py:660 +#: views/generic/dates.py:589 #, python-format msgid "" "Future %(verbose_name_plural)s not available because %(class_name)s." "allow_future is False." msgstr "" -#: views/generic/dates.py:694 +#: views/generic/dates.py:623 #, python-format msgid "Invalid date string '%(datestr)s' given format '%(format)s'" msgstr "" -#: views/generic/detail.py:55 +#: views/generic/detail.py:54 #, python-format msgid "No %(verbose_name)s found matching the query" msgstr "" -#: views/generic/list.py:76 +#: views/generic/list.py:67 msgid "Page is not 'last', nor can it be converted to an int." msgstr "" -#: views/generic/list.py:81 +#: views/generic/list.py:72 #, python-format msgid "Invalid page (%(page_number)s): %(message)s" msgstr "" -#: views/generic/list.py:172 +#: views/generic/list.py:154 #, python-format msgid "Empty list and '%(class_name)s.allow_empty' is False." msgstr "" -#: views/static.py:58 +#: views/static.py:40 msgid "Directory indexes are not allowed here." msgstr "" -#: views/static.py:60 +#: views/static.py:42 #, python-format msgid "\"%(path)s\" does not exist" msgstr "" -#: views/static.py:100 +#: views/static.py:80 #, python-format msgid "Index of %(directory)s" msgstr "" + +#: views/templates/default_urlconf.html:6 +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#: views/templates/default_urlconf.html:345 +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +#: views/templates/default_urlconf.html:367 +msgid "The install worked successfully! Congratulations!" +msgstr "" + +#: views/templates/default_urlconf.html:368 +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" + +#: views/templates/default_urlconf.html:383 +msgid "Django Documentation" +msgstr "" + +#: views/templates/default_urlconf.html:384 +msgid "Topics, references, & how-to's" +msgstr "" + +#: views/templates/default_urlconf.html:395 +msgid "Tutorial: A Polling App" +msgstr "" + +#: views/templates/default_urlconf.html:396 +msgid "Get started with Django" +msgstr "" + +#: views/templates/default_urlconf.html:407 +msgid "Django Community" +msgstr "" + +#: views/templates/default_urlconf.html:408 +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/en/formats.py b/django/conf/locale/en/formats.py index 63b23fa260d0..74abad58c519 100644 --- a/django/conf/locale/en/formats.py +++ b/django/conf/locale/en/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'N j, Y' TIME_FORMAT = 'P' DATETIME_FORMAT = 'N j, Y, P' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 0 # Sunday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # Kept ISO formats as they are in first position DATE_INPUT_FORMATS = [ '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06' diff --git a/django/conf/locale/en_AU/LC_MESSAGES/django.mo b/django/conf/locale/en_AU/LC_MESSAGES/django.mo index 3496b922a3cc..7fbbf94032b5 100644 Binary files a/django/conf/locale/en_AU/LC_MESSAGES/django.mo and b/django/conf/locale/en_AU/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/en_AU/LC_MESSAGES/django.po b/django/conf/locale/en_AU/LC_MESSAGES/django.po index cb67af8054b7..ce059c995b78 100644 --- a/django/conf/locale/en_AU/LC_MESSAGES/django.po +++ b/django/conf/locale/en_AU/LC_MESSAGES/django.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: English (Australia) (http://www.transifex.com/django/django/" "language/en_AU/)\n" @@ -293,6 +293,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "Enter a valid value." @@ -305,6 +314,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "Enter a valid email address." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -389,6 +399,15 @@ msgstr[0] "" msgstr[1] "" "Ensure that there are no more than %(max)s digits before the decimal point." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "and" @@ -701,16 +720,16 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Please correct the duplicate values below." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." +msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" "Select a valid choice. That choice is not one of the available choices." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." +msgstr "" #, python-format msgid "" @@ -720,15 +739,15 @@ msgstr "" "%(datetime)s couldn't be interpreted in time zone %(current_timezone)s; it " "may be ambiguous or it may not exist." +msgid "Clear" +msgstr "Clear" + msgid "Currently" msgstr "Currently" msgid "Change" msgstr "Change" -msgid "Clear" -msgstr "Clear" - msgid "Unknown" msgstr "Unknown" @@ -1068,6 +1087,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1082,28 +1109,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "No year specified" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "No month specified" @@ -1154,3 +1165,41 @@ msgstr "\"%(path)s\" does not exist" #, python-format msgid "Index of %(directory)s" msgstr "Index of %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/en_AU/formats.py b/django/conf/locale/en_AU/formats.py index fe97ea98c195..c28d75efe57b 100644 --- a/django/conf/locale/en_AU/formats.py +++ b/django/conf/locale/en_AU/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j M Y' # '25 Oct 2006' TIME_FORMAT = 'P' # '2:30 p.m.' DATETIME_FORMAT = 'j M Y, P' # '25 Oct 2006, 2:30 p.m.' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 0 # Sunday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d/%m/%Y', '%d/%m/%y', # '25/10/2006', '25/10/06' # '%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006' diff --git a/django/conf/locale/en_GB/LC_MESSAGES/django.mo b/django/conf/locale/en_GB/LC_MESSAGES/django.mo index 951a882f3d84..9c7be4ce6e3b 100644 Binary files a/django/conf/locale/en_GB/LC_MESSAGES/django.mo and b/django/conf/locale/en_GB/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/en_GB/LC_MESSAGES/django.po b/django/conf/locale/en_GB/LC_MESSAGES/django.po index 1ca44df693bd..381069888f49 100644 --- a/django/conf/locale/en_GB/LC_MESSAGES/django.po +++ b/django/conf/locale/en_GB/LC_MESSAGES/django.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: English (United Kingdom) (http://www.transifex.com/django/" "django/language/en_GB/)\n" @@ -295,6 +295,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "Enter a valid value." @@ -307,6 +316,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -381,6 +391,15 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "and" @@ -691,15 +710,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Please correct the duplicate values below." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." +msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" "Select a valid choice. That choice is not one of the available choices." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -710,15 +729,15 @@ msgstr "" "%(datetime)s couldn't be interpreted in time zone %(current_timezone)s; it " "may be ambiguous or it may not exist." +msgid "Clear" +msgstr "Clear" + msgid "Currently" msgstr "Currently" msgid "Change" msgstr "Change" -msgid "Clear" -msgstr "Clear" - msgid "Unknown" msgstr "Unknown" @@ -1058,6 +1077,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1072,28 +1099,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "No year specified" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "No month specified" @@ -1144,3 +1155,41 @@ msgstr "\"%(path)s\" does not exist" #, python-format msgid "Index of %(directory)s" msgstr "Index of %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/en_GB/formats.py b/django/conf/locale/en_GB/formats.py index 190ec7316d01..00451d0e99a6 100644 --- a/django/conf/locale/en_GB/formats.py +++ b/django/conf/locale/en_GB/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j M Y' # '25 Oct 2006' TIME_FORMAT = 'P' # '2:30 p.m.' DATETIME_FORMAT = 'j M Y, P' # '25 Oct 2006, 2:30 p.m.' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d/%m/%Y', '%d/%m/%y', # '25/10/2006', '25/10/06' # '%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006' diff --git a/django/conf/locale/eo/LC_MESSAGES/django.mo b/django/conf/locale/eo/LC_MESSAGES/django.mo index 423989015afb..64c1b8fabca5 100644 Binary files a/django/conf/locale/eo/LC_MESSAGES/django.mo and b/django/conf/locale/eo/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/eo/LC_MESSAGES/django.po b/django/conf/locale/eo/LC_MESSAGES/django.po index 1d76cd1fab7f..d3fa9a185528 100644 --- a/django/conf/locale/eo/LC_MESSAGES/django.po +++ b/django/conf/locale/eo/LC_MESSAGES/django.po @@ -2,18 +2,20 @@ # # Translators: # Baptiste Darthenay , 2012-2013 -# Baptiste Darthenay , 2013-2016 +# Baptiste Darthenay , 2013-2019 # batisteo , 2011 # Dinu Gherman , 2011 # kristjan , 2011 +# Nikolay Korotkiy , 2017-2018 +# Robin van der Vliet , 2019 # Adamo Mesha , 2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-09-29 18:22+0000\n" -"Last-Translator: Baptiste Darthenay \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-02-04 21:34+0000\n" +"Last-Translator: Robin van der Vliet \n" "Language-Team: Esperanto (http://www.transifex.com/django/django/language/" "eo/)\n" "MIME-Version: 1.0\n" @@ -142,6 +144,9 @@ msgstr "Suprasoraba" msgid "Hungarian" msgstr "Hungara" +msgid "Armenian" +msgstr "Armena" + msgid "Interlingua" msgstr "Interlingvaa" @@ -163,6 +168,9 @@ msgstr "Japana" msgid "Georgian" msgstr "Kartvela" +msgid "Kabyle" +msgstr "Kabila" + msgid "Kazakh" msgstr "Kazaĥa" @@ -298,6 +306,15 @@ msgstr "Statikaj dosieroj" msgid "Syndication" msgstr "Abonrilato" +msgid "That page number is not an integer" +msgstr "Tuo paĝnumero ne estas entjero" + +msgid "That page number is less than 1" +msgstr "Tuo paĝnumero estas malpli ol 1" + +msgid "That page contains no results" +msgstr "Tiu paĝo ne enhavas rezultojn" + msgid "Enter a valid value." msgstr "Enigu validan valoron." @@ -310,6 +327,7 @@ msgstr "Enigu validan entjero." msgid "Enter a valid email address." msgstr "Enigu validan retpoŝtan adreson." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -355,10 +373,10 @@ msgid_plural "" "Ensure this value has at least %(limit_value)d characters (it has " "%(show_value)d)." msgstr[0] "" -"Certigu, ke tiu valuto havas %(limit_value)d karaktero (ĝi havas " +"Certigu, ke tiu valoro havas %(limit_value)d signon (ĝi havas " "%(show_value)d)." msgstr[1] "" -"Certigu, ke tiu valuto havas %(limit_value)d karakteroj (ĝi havas " +"Certigu, ke tiu valoro havas %(limit_value)d signojn (ĝi havas " "%(show_value)d)." #, python-format @@ -372,9 +390,12 @@ msgstr[0] "" "Certigu, ke tio valuto maksimume havas %(limit_value)d karakterojn (ĝi havas " "%(show_value)d)." msgstr[1] "" -"Certigu, ke tio valuto maksimume havas %(limit_value)d karakterojn (ĝi havas " +"Certigu, ke tiu valoro maksimume havas %(limit_value)d signojn (ĝi havas " "%(show_value)d)." +msgid "Enter a number." +msgstr "Enigu nombron." + #, 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." @@ -395,6 +416,17 @@ msgid_plural "" msgstr[0] "Certigu ke ne estas pli ol %(max)s ciferoj antaŭ la dekuma punkto." msgstr[1] "Certigu ke ne estas pli ol %(max)s ciferoj antaŭ la dekuma punkto." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"dosiersufikso '%(extension)s' ne estas permesita. Permesitaj dosiersufiksoj " +"estas: '%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Nulsignoj ne estas permesitaj." + msgid "and" msgstr "kaj" @@ -442,6 +474,10 @@ msgstr "Granda (8 bitoka) entjero" msgid "'%(value)s' value must be either True or False." msgstr "'%(value)s' valoro devas esti Vera aŭ Malvera" +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "“%(value)s” valoro devas esti Vera, Malvera aŭ Neniu." + msgid "Boolean (Either True or False)" msgstr "Bulea (Vera aŭ Malvera)" @@ -579,6 +615,9 @@ msgstr "Kruda binara datumo" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' ne estas valida UUID." +msgid "Universally unique identifier" +msgstr "Universe unika identigilo" + msgid "File" msgstr "Dosiero" @@ -618,9 +657,6 @@ msgstr "Ĉi tiu kampo estas deviga." msgid "Enter a whole number." msgstr "Enigu plenan nombron." -msgid "Enter a number." -msgstr "Enigu nombron." - msgid "Enter a valid date." msgstr "Enigu validan daton." @@ -633,6 +669,10 @@ msgstr "Enigu validan daton/tempon." msgid "Enter a valid duration." msgstr "Enigu validan daŭron." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "La nombro da tagoj devas esti inter {min_days} kaj {max_days}." + msgid "No file was submitted. Check the encoding type on the form." msgstr "" "Neniu dosiero estis alŝutita. Kontrolu la kodoprezentan tipon en la " @@ -652,7 +692,7 @@ msgstr[0] "" "Certigu, ke tio dosiernomo maksimume havas %(max)d karakteron (ĝi havas " "%(length)d)." msgstr[1] "" -"Certigu, ke tio dosiernomo maksimume havas %(max)d karakterojn (ĝi havas " +"Certigu, ke tiu dosiernomo maksimume havas %(max)d signojn (ĝi havas " "%(length)d)." msgid "Please either submit a file or check the clear checkbox, not both." @@ -728,15 +768,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Bonvolu ĝustigi la duoblan valoron sube." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "La enteksta fremda ŝlosilo ne egalis la ĉefŝlosilon de patra apero." +msgid "The inline value did not match the parent instance." +msgstr "La enteksta valoro ne egalas la patran aperon." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Elektu validan elekton. Ĉi tiu elekto ne estas el la eblaj elektoj." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" ne estas valida valuto por la ĉefa ŝlosilo." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" ne estas valida valoro." #, python-format msgid "" @@ -746,15 +786,15 @@ msgstr "" "%(datetime)s ne povus esti interpretita en horzono %(current_timezone)s; ĝi " "povas esti plursenca aŭ ne ekzistas." +msgid "Clear" +msgstr "Vakigi" + msgid "Currently" msgstr "Nuntempe" msgid "Change" msgstr "Ŝanĝi" -msgid "Clear" -msgstr "Vakigi" - msgid "Unknown" msgstr "Nekonate" @@ -1026,8 +1066,8 @@ msgstr "Tiu ne estas valida IPv6-adreso." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s…" msgid "or" msgstr "aŭ" @@ -1101,6 +1141,19 @@ msgstr "" "reaktivigi ilin, almenaŭ por tiu ĉi retejo, aŭ por HTTPS rilatoj, aŭ por " "“samoriginaj” petoj." +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." +msgstr "" +"Se vi uzas la markon aŭ " +"inkluzivante la 'Referrer-Policy: no-referrer' titolo, bonvolu forigi ilin. " +"La CSRFa protekto postulas ke la 'Referer' titolo faru striktan " +"referencantan kontroladon. Se vi estas koncernita pri privateco, uzu " +"alternativojn kiel por ligoj al aliaj retejoj." + 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 " @@ -1120,33 +1173,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Pliaj informoj estas videblaj kun DEBUG=True." -msgid "Welcome to Django" -msgstr "Bonvenon en Dĵango" - -msgid "It worked!" -msgstr "Sukcesis!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Gratulojn por via unua Dĵanga paĝo." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Kompreneble, vi ankoraŭ ne reale faris ajnan laboron. Poste, komencu vian " -"unuan aplikaĵon lanĉante python manage.py startapp [aplikaĵo_etikedo]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Vi vidas ĉi tiun mesaĝon ĉar vi havas DEBUG = True en viaj " -"Dĵangaj agordaj dosieron kaj vi ne agordis ajna URLoj. Eklaboru!" - msgid "No year specified" msgstr "Neniu jaro specifita" +msgid "Date out of range" +msgstr "Dato ne en la intervalo" + msgid "No month specified" msgstr "Neniu monato specifita" @@ -1199,3 +1231,46 @@ msgstr "\"%(path)s\" ne ekzistas" #, python-format msgid "Index of %(directory)s" msgstr "Indekso de %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Dĵango: la retframo por perfektemuloj kun limdatoj" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Vidu eldonajn notojn por Dĵango %(version)s" + +msgid "The install worked successfully! Congratulations!" +msgstr "La instalado sukcesis! Gratulojn!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Vi vidas ĉi tiun paĝon ĉar DEBUG = " +"True estas en via agorda dosiero kaj vi ne agordis ajnan URL." + +msgid "Django Documentation" +msgstr "Djanga dokumentaro" + +msgid "Topics, references, & how-to's" +msgstr "Temoj, referencoj & manlibroj" + +msgid "Tutorial: A Polling App" +msgstr "Instruilo: apo pri enketoj" + +msgid "Get started with Django" +msgstr "Komencu kun Dĵango" + +msgid "Django Community" +msgstr "Djanga komunumo" + +msgid "Connect, get help, or contribute" +msgstr "Konektiĝu, ricevu helpon aŭ kontribuu" diff --git a/django/conf/locale/eo/formats.py b/django/conf/locale/eo/formats.py index 1e61912442a1..4edfed594d13 100644 --- a/django/conf/locale/eo/formats.py +++ b/django/conf/locale/eo/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = r'j\-\a \d\e F Y' # '26-a de julio 1887' TIME_FORMAT = 'H:i' # '18:59' DATETIME_FORMAT = r'j\-\a \d\e F Y\, \j\e H:i' # '26-a de julio 1887, je 18:59' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # Monday (lundo) # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%Y-%m-%d', # '1887-07-26' '%y-%m-%d', # '87-07-26' diff --git a/django/conf/locale/es/LC_MESSAGES/django.mo b/django/conf/locale/es/LC_MESSAGES/django.mo index 9511eb3c46c8..7ebd8f82f3ba 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 34d27f9aaadd..5749ffea110e 100644 --- a/django/conf/locale/es/LC_MESSAGES/django.po +++ b/django/conf/locale/es/LC_MESSAGES/django.po @@ -1,33 +1,37 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Abraham Estrada , 2013 +# Abraham Estrada, 2013 # albertoalcolea , 2014 -# Antoni Aloy , 2011-2014 +# Amanda Copete, 2017 +# Antoni Aloy , 2011-2014,2017 # Diego Andres Sanabria Martin , 2012 # Diego Schulz , 2012 # Ernesto Avilés Vázquez , 2015-2016 # Ernesto Avilés Vázquez , 2014 +# Ernesto Rico-Schmidt , 2017 # franchukelly , 2011 +# Ignacio José Lizarán Rus , 2019 # Igor Támara , 2015 # Jannis Leidel , 2011 -# Yusuf (Josè) Luis , 2016 +# José Luis , 2016 # Josue Naaman Nistal Guerra , 2014 # Leonardo J. Caballero G. , 2011,2013 +# Luigy, 2019 # Marc Garcia , 2011 # monobotsoft , 2012 # ntrrgc , 2013 # ntrrgc , 2013 # Pablo, 2015 -# Sebastián Ramírez Magrí , 2013 +# Sebastián Magrí, 2013 # Veronicabh , 2015 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-08-03 15:35+0000\n" -"Last-Translator: Ernesto Avilés Vázquez \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-02-19 08:50+0000\n" +"Last-Translator: Ignacio José Lizarán Rus \n" "Language-Team: Spanish (http://www.transifex.com/django/django/language/" "es/)\n" "MIME-Version: 1.0\n" @@ -37,7 +41,7 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" msgid "Afrikaans" -msgstr "Afrikaans" +msgstr "Africano" msgid "Arabic" msgstr "Árabe" @@ -156,6 +160,9 @@ msgstr "Alto sorbio" msgid "Hungarian" msgstr "Húngaro" +msgid "Armenian" +msgstr "Armenio/a" + msgid "Interlingua" msgstr "Interlingua" @@ -177,6 +184,9 @@ msgstr "Japonés" msgid "Georgian" msgstr "Georgiano" +msgid "Kabyle" +msgstr "Cabilio" + msgid "Kazakh" msgstr "Kazajo" @@ -312,8 +322,17 @@ msgstr "Archivos estáticos" msgid "Syndication" msgstr "Sindicación" +msgid "That page number is not an integer" +msgstr "Este número de página no es un entero" + +msgid "That page number is less than 1" +msgstr "Este número de página es menor que 1" + +msgid "That page contains no results" +msgstr "Esa página no contiene resultados" + msgid "Enter a valid value." -msgstr "Introduzca un valor correcto." +msgstr "Introduzca un valor válido." msgid "Enter a valid URL." msgstr "Introduzca una URL válida." @@ -324,6 +343,7 @@ msgstr "Introduzca un número entero válido." msgid "Enter a valid email address." msgstr "Introduzca una dirección de correo electrónico válida." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -334,8 +354,7 @@ msgid "" "Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or " "hyphens." msgstr "" -"Ingrese un 'slug' consistente en letras, números, subrayados o guiones " -"Unicodes." +"Introduzca un 'slug' consistente en letras, números, subrayados o guiones." msgid "Enter a valid IPv4 address." msgstr "Introduzca una dirección IPv4 válida." @@ -391,6 +410,9 @@ msgstr[1] "" "Asegúrese de que este valor tenga menos de %(limit_value)d caracteres (tiene " "%(show_value)d)." +msgid "Enter a number." +msgstr "Introduzca un número." + #, 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." @@ -413,6 +435,17 @@ msgstr[0] "" msgstr[1] "" "Asegúrese de que no haya más de %(max)s dígitos antes del punto decimal." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"La extensión de fichero '%(extension)s' no está permitida. Únicamente se " +"permiten: '%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Los caracteres nulos no están permitidos." + msgid "and" msgstr "y" @@ -460,6 +493,10 @@ msgstr "Entero grande (8 bytes)" msgid "'%(value)s' value must be either True or False." msgstr "El valor '%(value)s' debe ser verdadero o falso." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "El valor '%(value)s' debe ser Verdadero, Falso o Ninguno" + msgid "Boolean (Either True or False)" msgstr "Booleano (Verdadero o Falso)" @@ -597,6 +634,9 @@ msgstr "Data de binarios brutos" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' no es un UUID válido." +msgid "Universally unique identifier" +msgstr "Identificador universal único" + msgid "File" msgstr "Archivo" @@ -608,7 +648,7 @@ msgid "%(model)s instance with %(field)s %(value)r does not exist." msgstr "La instancia de %(model)s con %(field)s %(value)r no existe." msgid "Foreign Key (type determined by related field)" -msgstr "Llave foránea (tipo determinado por el campo relacionado)" +msgstr "Clave foránea (tipo determinado por el campo relacionado)" msgid "One-to-one relationship" msgstr "Relación uno-a-uno" @@ -636,9 +676,6 @@ msgstr "Este campo es obligatorio." msgid "Enter a whole number." msgstr "Introduzca un número entero." -msgid "Enter a number." -msgstr "Introduzca un número." - msgid "Enter a valid date." msgstr "Introduzca una fecha válida." @@ -651,6 +688,10 @@ msgstr "Introduzca una fecha/hora válida." msgid "Enter a valid duration." msgstr "Introduzca una duración válida." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "El número de días debe estar entre {min_days} y {max_days}." + msgid "No file was submitted. Check the encoding type on the form." msgstr "" "No se ha enviado ningún fichero. Compruebe el tipo de codificación en el " @@ -704,7 +745,7 @@ msgstr ":" #, python-format msgid "(Hidden field %(name)s) %(error)s" -msgstr "(Compo oculto %(name)s) *%(error)s" +msgstr "(Campo oculto %(name)s) *%(error)s" msgid "ManagementForm data is missing or has been tampered with" msgstr "Los datos de ManagementForm faltan o han sido manipulados" @@ -712,14 +753,14 @@ msgstr "Los datos de ManagementForm faltan o han sido manipulados" #, python-format msgid "Please submit %d or fewer forms." msgid_plural "Please submit %d or fewer forms." -msgstr[0] "Por favor, envíe %d o menos formas." -msgstr[1] "Por favor, envíe %d o menos formas." +msgstr[0] "Por favor, envíe %d formulario o menos." +msgstr[1] "Por favor, envíe %d formularios o menos" #, python-format msgid "Please submit %d or more forms." msgid_plural "Please submit %d or more forms." -msgstr[0] "Por favor, envíe %d o más formas." -msgstr[1] "Por favor, envíe %d o más formas." +msgstr[0] "Por favor, envíe %d formulario o más." +msgstr[1] "Por favor, envíe %d formularios o más." msgid "Order" msgstr "Orden" @@ -747,17 +788,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Por favor, corrija los valores duplicados abajo." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "" -"La clave foránea en linea no coincide con la clave primaria de la instancia " -"padre." +msgid "The inline value did not match the parent instance." +msgstr "El valor en línea no coincide con la instancia principal." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Escoja una opción válida. Esa opción no está entre las disponibles." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" no es un valor válido para una llave primaria." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" no es un valor válido." #, python-format msgid "" @@ -767,15 +806,15 @@ msgstr "" "%(datetime)s no puede interpretarse en la zona temporal " "%(current_timezone)s; puede ser ambiguo o puede no existir." +msgid "Clear" +msgstr "Limpiar" + msgid "Currently" msgstr "Actualmente" msgid "Change" msgstr "Modificar" -msgid "Clear" -msgstr "Limpiar" - msgid "Unknown" msgstr "Desconocido" @@ -920,7 +959,7 @@ msgid "mar" msgstr "mar" msgid "apr" -msgstr "apr" +msgstr "abr" msgid "may" msgstr "may" @@ -1047,7 +1086,7 @@ msgstr "Esta no es una dirección IPv6 válida." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." +msgid "%(truncated_text)s…" msgstr "%(truncated_text)s..." msgid "or" @@ -1122,6 +1161,20 @@ msgstr "" "favor vuélvelas a activar, al menos para esta web, o para conexiones HTTPS, " "o para peticiones 'mismo-origen'." +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." +msgstr "" +"Si estás usando la etiqueta " +"o incluyendo el encabezamiento 'Referrer-Policy: no-referrer' , por favor " +"retíralos. La protección CSRF requiere del encabezamiento 'Referer' para " +"hacer control estricto de referencia . Si estás preocupado por la " +"privacidad, usa alternativas como para enlaces a " +"sitios de terceros." + 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 " @@ -1141,33 +1194,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Se puede ver más información si se establece DEBUG=True." -msgid "Welcome to Django" -msgstr "Bienvenido a Django" - -msgid "It worked!" -msgstr "¡Funcionó!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Enhorabuena por tu primer página hecha en Django." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Por supuesto, todavía no has hecho ningún trabajo. Para continuar, inicia tu " -"primera aplicación ejecutando python manage.py startapp [app_label]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Ves este mensaje porque tienes DEBUG = True en el archivo de " -"configuración de Django y no has configurado ninguna URL. ¡A trabajar!" - msgid "No year specified" msgstr "No se ha indicado el año" +msgid "Date out of range" +msgstr "Fecha fuera de rango" + msgid "No month specified" msgstr "No se ha indicado el mes" @@ -1218,3 +1250,48 @@ msgstr "\"%(path)s\" no existe" #, python-format msgid "Index of %(directory)s" msgstr "Índice de %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: el marco web para perfeccionistas con plazos." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Ve la notas de la versión de Django " +"%(version)s" + +msgid "The install worked successfully! Congratulations!" +msgstr "¡La instalación funcionó con éxito! ¡Felicitaciones!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Estás viendo esta página porque DEBUG=True está en tu archivo de configuración y no has configurado " +"ningún URL." + +msgid "Django Documentation" +msgstr "Documentación de Django" + +msgid "Topics, references, & how-to's" +msgstr "Temas, referencias y cómo hacerlos" + +msgid "Tutorial: A Polling App" +msgstr "Tutorial: Una aplicación de encuesta" + +msgid "Get started with Django" +msgstr "Comienza con Django" + +msgid "Django Community" +msgstr "Comunidad Django" + +msgid "Connect, get help, or contribute" +msgstr "Conéctate, obtén ayuda o contribuye" diff --git a/django/conf/locale/es/formats.py b/django/conf/locale/es/formats.py index 701032aefda5..b7aca789887d 100644 --- a/django/conf/locale/es/formats.py +++ b/django/conf/locale/es/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = r'j \d\e F \d\e Y' TIME_FORMAT = 'H:i' DATETIME_FORMAT = r'j \d\e F \d\e Y \a \l\a\s H:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ # '31/12/2009', '31/12/09' '%d/%m/%Y', '%d/%m/%y' diff --git a/django/conf/locale/es_AR/LC_MESSAGES/django.mo b/django/conf/locale/es_AR/LC_MESSAGES/django.mo index 14eaa0fb399f..35e8cfdd63fe 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 fa10c5072b52..36ed3b3b5cc5 100644 --- a/django/conf/locale/es_AR/LC_MESSAGES/django.po +++ b/django/conf/locale/es_AR/LC_MESSAGES/django.po @@ -4,13 +4,13 @@ # Jannis Leidel , 2011 # lardissone , 2014 # poli , 2014 -# Ramiro Morales, 2013-2016 +# Ramiro Morales, 2013-2019 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-07-30 23:44+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-03-20 14:07+0000\n" "Last-Translator: Ramiro Morales\n" "Language-Team: Spanish (Argentina) (http://www.transifex.com/django/django/" "language/es_AR/)\n" @@ -140,6 +140,9 @@ msgstr "alto sorabo" msgid "Hungarian" msgstr "húngaro" +msgid "Armenian" +msgstr "Armenio" + msgid "Interlingua" msgstr "Interlingua" @@ -161,6 +164,9 @@ msgstr "japonés" msgid "Georgian" msgstr "georgiano" +msgid "Kabyle" +msgstr "cabilio" + msgid "Kazakh" msgstr "kazajo" @@ -296,6 +302,15 @@ msgstr "Archivos estáticos" msgid "Syndication" msgstr "Sindicación" +msgid "That page number is not an integer" +msgstr "El número de página no es un entero" + +msgid "That page number is less than 1" +msgstr "El número de página es menor a 1" + +msgid "That page contains no results" +msgstr "Esa página no contiene resultados" + msgid "Enter a valid value." msgstr "Introduzca un valor válido." @@ -308,6 +323,7 @@ msgstr "Introduzca un valor numérico entero válido." msgid "Enter a valid email address." msgstr "Introduzca una dirección de email válida." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -375,6 +391,9 @@ msgstr[1] "" "Asegúrese de que este valor tenga como máximo %(limit_value)d caracteres " "(tiene %(show_value)d)." +msgid "Enter a number." +msgstr "Introduzca un número." + #, 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." @@ -397,6 +416,17 @@ msgstr[0] "" msgstr[1] "" "Asegúrese de que no existan mas de %(max)s dígitos antes del punto decimal." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"La extensión de archivo '%(extension)s' no está permitida. Las extensiones " +"aceptadas son: '%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "No se admiten caracteres nulos." + msgid "and" msgstr "y" @@ -445,6 +475,10 @@ msgstr "Entero grande (8 bytes)" msgid "'%(value)s' value must be either True or False." msgstr "El valor de '%(value)s' debe ser Verdadero o Falso." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "El valor de '%(value)s' debe ser Verdadero, Falso o None." + msgid "Boolean (Either True or False)" msgstr "Booleano (Verdadero o Falso)" @@ -582,6 +616,9 @@ msgstr "Datos binarios crudos" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' no es un UUID válido." +msgid "Universally unique identifier" +msgstr "Identificador universalmente único" + msgid "File" msgstr "Archivo" @@ -621,9 +658,6 @@ msgstr "Este campo es obligatorio." msgid "Enter a whole number." msgstr "Introduzca un número entero." -msgid "Enter a number." -msgstr "Introduzca un número." - msgid "Enter a valid date." msgstr "Introduzca una fecha válida." @@ -636,6 +670,10 @@ msgstr "Introduzca un valor de fecha/hora válido." msgid "Enter a valid duration." msgstr "Introduzca una duración válida." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "La cantidad de días debe tener valores entre {min_days} y {max_days}." + msgid "No file was submitted. Check the encoding type on the form." msgstr "" "No se envió un archivo. Verifique el tipo de codificación en el formulario." @@ -733,10 +771,8 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Por favor, corrija los valores duplicados detallados mas abajo." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "" -"La clave foránea del modelo inline no coincide con la clave primaria de la " -"instancia padre." +msgid "The inline value did not match the parent instance." +msgstr "El valor inline no coincide con el de la instancia padre." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" @@ -744,8 +780,8 @@ msgstr "" "disponibles." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" no es un valor válido para una clave primaria." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" no es un valor válido." #, python-format msgid "" @@ -755,15 +791,15 @@ msgstr "" "%(datetime)s no puede ser interpretado en la zona horaria " "%(current_timezone)s; ya que podría ser ambiguo o podría no existir." +msgid "Clear" +msgstr "Eliminar" + msgid "Currently" msgstr "Actualmente" msgid "Change" msgstr "Modificar" -msgid "Clear" -msgstr "Eliminar" - msgid "Unknown" msgstr "Desconocido" @@ -984,59 +1020,59 @@ msgstr "Dic." msgctxt "alt. month" msgid "January" -msgstr "Enero" +msgstr "enero" msgctxt "alt. month" msgid "February" -msgstr "Febrero" +msgstr "febrero" msgctxt "alt. month" msgid "March" -msgstr "Marzo" +msgstr "marzo" msgctxt "alt. month" msgid "April" -msgstr "Abril" +msgstr "abril" msgctxt "alt. month" msgid "May" -msgstr "Mayo" +msgstr "mayo" msgctxt "alt. month" msgid "June" -msgstr "Junio" +msgstr "junio" msgctxt "alt. month" msgid "July" -msgstr "Julio" +msgstr "julio" msgctxt "alt. month" msgid "August" -msgstr "Agosto" +msgstr "agosto" msgctxt "alt. month" msgid "September" -msgstr "Setiembre" +msgstr "setiembre" msgctxt "alt. month" msgid "October" -msgstr "Octubre" +msgstr "octubre" msgctxt "alt. month" msgid "November" -msgstr "Noviembre" +msgstr "noviembre" msgctxt "alt. month" msgid "December" -msgstr "Diciembre" +msgstr "diciembre" msgid "This is not a valid IPv6 address." -msgstr "Esta no es una direción IPv6 válida." +msgstr "Esta no es una dirección IPv6 válida." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s…" msgid "or" msgstr "o" @@ -1111,6 +1147,20 @@ msgstr "" "favor activelas al menos para este sitio, o para conexiones HTTPS o para " "peticiones generadas desde el mismo origen." +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." +msgstr "" +"Si está usando la etiqueta " +"o está incluyendo el encabezado 'Referrer-Policy: no-referrer' por favor " +"quítelos. La protección CSRF necesita el encabezado 'Referer' para realizar " +"una comprobación estricta de los referers. Si le preocupa la privacidad " +"tiene alternativas tales como usar en los enlaces " +"a sitios de terceros." + 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 " @@ -1132,33 +1182,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Hay mas información disponible. Para ver la misma use DEBUG=True." -msgid "Welcome to Django" -msgstr "Bienvenido a Django" - -msgid "It worked!" -msgstr "¡Funcionó!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Felicitationes por tu primera página Django-powered." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Obviamente en realidad todavia no has hecho nada. Ahora crea tu primera " -"aplicación ejecutando python manage.py startapp [app_label]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Estás viendo este mensaje porque tienes DEBUG = True en tu " -"archivo de settings Django y porque no has configurado ninguna URL. ¡A " -"trabajar!" - msgid "No year specified" msgstr "No se ha especificado el valor año" +msgid "Date out of range" +msgstr "Fecha fuera de rango" + msgid "No month specified" msgstr "No se ha especificado el valor mes" @@ -1211,3 +1240,46 @@ msgstr "\"%(path)s\" no existe" #, python-format msgid "Index of %(directory)s" msgstr "Listado de %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: El framework Web para perfeccionistas con deadlines." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Ver las release notes de Django %(version)s" + +msgid "The install worked successfully! Congratulations!" +msgstr "La instalación ha sido exitosa. ¡Felicitaciones!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Está viendo esta página porque el archivo de configuración contiene DEBUG=True y no ha configurado ninguna URL." + +msgid "Django Documentation" +msgstr "Documentación de Django" + +msgid "Topics, references, & how-to's" +msgstr "Tópicos, referencia & how-to's" + +msgid "Tutorial: A Polling App" +msgstr "Tutorial: Una app de encuesta" + +msgid "Get started with Django" +msgstr "Comience a aprender Django" + +msgid "Django Community" +msgstr "Comunidad Django" + +msgid "Connect, get help, or contribute" +msgstr "Conéctese, consiga ayuda o contribuya" diff --git a/django/conf/locale/es_AR/formats.py b/django/conf/locale/es_AR/formats.py index 9daf38d8eff1..e856c4a26598 100644 --- a/django/conf/locale/es_AR/formats.py +++ b/django/conf/locale/es_AR/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = r'j N Y' TIME_FORMAT = r'H:i' DATETIME_FORMAT = r'j N Y H:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 0 # 0: Sunday, 1: Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d/%m/%Y', # '31/12/2009' '%d/%m/%y', # '31/12/09' diff --git a/django/conf/locale/es_CO/LC_MESSAGES/django.mo b/django/conf/locale/es_CO/LC_MESSAGES/django.mo index 4e174bb72308..9b6221d4984e 100644 Binary files a/django/conf/locale/es_CO/LC_MESSAGES/django.mo and b/django/conf/locale/es_CO/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/es_CO/LC_MESSAGES/django.po b/django/conf/locale/es_CO/LC_MESSAGES/django.po index a94e4636a433..2cae813c9e02 100644 --- a/django/conf/locale/es_CO/LC_MESSAGES/django.po +++ b/django/conf/locale/es_CO/LC_MESSAGES/django.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Spanish (Colombia) (http://www.transifex.com/django/django/" "language/es_CO/)\n" @@ -293,6 +293,15 @@ msgstr "Archivos estáticos" msgid "Syndication" msgstr "Sindicación" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "Ingrese un valor válido." @@ -305,6 +314,7 @@ msgstr "Ingrese un entero válido." msgid "Enter a valid email address." msgstr "Ingrese una dirección de correo electrónico válida." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -392,6 +402,15 @@ msgstr[0] "" msgstr[1] "" "Asegúrese de que no hayan más de %(max)s dígitos antes del punto decimal" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "y" @@ -726,17 +745,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Por favor, corrija los valores duplicados abajo." -msgid "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." msgstr "" -"La clave foránea en linea no coincide con la clave primaria de la instancia " -"padre." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Escoja una opción válida. Esa opción no está entre las disponibles." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" no es un valor válido para una llave primaria." +msgid "\"%(pk)s\" is not a valid value." +msgstr "" #, python-format msgid "" @@ -746,15 +763,15 @@ msgstr "" "%(datetime)s no puede interpretarse en el huso horario %(current_timezone)s; " "puede ser ambiguo o puede no existir." +msgid "Clear" +msgstr "Limpiar" + msgid "Currently" msgstr "Actualmente" msgid "Change" msgstr "Cambiar" -msgid "Clear" -msgstr "Limpiar" - msgid "Unknown" msgstr "Desconocido" @@ -1101,6 +1118,14 @@ msgstr "" "favor vuélvelas a activar, al menos para esta web, o para conexiones HTTPS, " "o para peticiones '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." +msgstr "" + 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 " @@ -1120,33 +1145,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Se puede ver más información si se establece DEBUG=True." -msgid "Welcome to Django" -msgstr "Bienvenido a Django" - -msgid "It worked!" -msgstr "¡Funcionó!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Enhorabuena por tu primer página hecha en Django." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Por supuesto, todavía no has hecho ningún trabajo. Para continuar, inicia tu " -"primera aplicación ejecutando python manage.py startapp [app_label]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Ves este mensaje porque tienes DEBUG = True en el archivo de " -"configuración de Django y no has configurado ninguna URL. ¡A trabajar!" - msgid "No year specified" msgstr "No se ha indicado el año" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "No se ha indicado el mes" @@ -1197,3 +1201,41 @@ msgstr "\"%(path)s\" no existe" #, python-format msgid "Index of %(directory)s" msgstr "Índice de %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/es_CO/formats.py b/django/conf/locale/es_CO/formats.py index 3671a0a25428..cefbe26dae5e 100644 --- a/django/conf/locale/es_CO/formats.py +++ b/django/conf/locale/es_CO/formats.py @@ -1,8 +1,5 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - DATE_FORMAT = r'j \d\e F \d\e Y' TIME_FORMAT = 'H:i' DATETIME_FORMAT = r'j \d\e F \d\e Y \a \l\a\s H:i' diff --git a/django/conf/locale/es_MX/LC_MESSAGES/django.mo b/django/conf/locale/es_MX/LC_MESSAGES/django.mo index 61c78d23762d..68ab9eeb0786 100644 Binary files a/django/conf/locale/es_MX/LC_MESSAGES/django.mo and b/django/conf/locale/es_MX/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/es_MX/LC_MESSAGES/django.po b/django/conf/locale/es_MX/LC_MESSAGES/django.po index 1d0649e96059..44f60f873c3a 100644 --- a/django/conf/locale/es_MX/LC_MESSAGES/django.po +++ b/django/conf/locale/es_MX/LC_MESSAGES/django.po @@ -1,14 +1,14 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Abraham Estrada , 2011-2013 +# Abraham Estrada, 2011-2013 # zodman , 2011 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Spanish (Mexico) (http://www.transifex.com/django/django/" "language/es_MX/)\n" @@ -294,6 +294,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "Introduzca un valor válido." @@ -306,6 +315,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "Introduzca una dirección de correo electrónico válida." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -385,6 +395,15 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "y" @@ -699,10 +718,8 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Por favor, corrija los valores duplicados detallados mas abajo." -msgid "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." msgstr "" -"La clave foránea del modelo inline no coincide con la clave primaria de la " -"instancia padre." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" @@ -710,7 +727,7 @@ msgstr "" "disponibles." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -721,15 +738,15 @@ msgstr "" "La fecha %(datetime)s no puede se interpretada en la zona horaria " "%(current_timezone)s; ya que puede ser ambigua o que no pueden existir." +msgid "Clear" +msgstr "Borrar" + msgid "Currently" msgstr "Actualmente" msgid "Change" msgstr "Modificar" -msgid "Clear" -msgstr "Borrar" - msgid "Unknown" msgstr "Desconocido" @@ -1069,6 +1086,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1083,28 +1108,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "No se ha especificado el valor año" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "No se ha especificado el valor mes" @@ -1155,3 +1164,41 @@ msgstr "\"%(path)s\" no existe" #, python-format msgid "Index of %(directory)s" msgstr "Índice de %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/es_MX/formats.py b/django/conf/locale/es_MX/formats.py index d15041660d79..760edcfa1c3e 100644 --- a/django/conf/locale/es_MX/formats.py +++ b/django/conf/locale/es_MX/formats.py @@ -1,8 +1,5 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - DATE_FORMAT = r'j \d\e F \d\e Y' TIME_FORMAT = 'H:i' DATETIME_FORMAT = r'j \d\e F \d\e Y \a \l\a\s H:i' @@ -24,5 +21,5 @@ '%d/%m/%y %H:%M', ] DECIMAL_SEPARATOR = '.' # ',' is also official (less common): NOM-008-SCFI-2002 -THOUSAND_SEPARATOR = '\xa0' # non-breaking space +THOUSAND_SEPARATOR = ',' NUMBER_GROUPING = 3 diff --git a/django/conf/locale/es_NI/formats.py b/django/conf/locale/es_NI/formats.py index cc03b2cd2c2e..2eacf506ee51 100644 --- a/django/conf/locale/es_NI/formats.py +++ b/django/conf/locale/es_NI/formats.py @@ -1,8 +1,5 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - DATE_FORMAT = r'j \d\e F \d\e Y' TIME_FORMAT = 'H:i' DATETIME_FORMAT = r'j \d\e F \d\e Y \a \l\a\s H:i' diff --git a/django/conf/locale/es_PR/formats.py b/django/conf/locale/es_PR/formats.py index c6680f7975b4..7f53ef9fe295 100644 --- a/django/conf/locale/es_PR/formats.py +++ b/django/conf/locale/es_PR/formats.py @@ -1,8 +1,5 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - DATE_FORMAT = r'j \d\e F \d\e Y' TIME_FORMAT = 'H:i' DATETIME_FORMAT = r'j \d\e F \d\e Y \a \l\a\s H:i' diff --git a/django/conf/locale/es_VE/LC_MESSAGES/django.mo b/django/conf/locale/es_VE/LC_MESSAGES/django.mo index 36493b10ec51..51ee3a565111 100644 Binary files a/django/conf/locale/es_VE/LC_MESSAGES/django.mo and b/django/conf/locale/es_VE/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/es_VE/LC_MESSAGES/django.po b/django/conf/locale/es_VE/LC_MESSAGES/django.po index 17bcf513150c..db2d59279214 100644 --- a/django/conf/locale/es_VE/LC_MESSAGES/django.po +++ b/django/conf/locale/es_VE/LC_MESSAGES/django.po @@ -1,14 +1,16 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Eduardo , 2017 # Leonardo J. Caballero G. , 2016 -# Sebastián Ramírez Magrí , 2011 +# Sebastián Magrí , 2011 +# Yoel Acevedo, 2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Spanish (Venezuela) (http://www.transifex.com/django/django/" "language/es_VE/)\n" @@ -61,7 +63,7 @@ msgid "German" msgstr "Alemán" msgid "Lower Sorbian" -msgstr "" +msgstr "Sorbio Inferior" msgid "Greek" msgstr "Griego" @@ -133,7 +135,7 @@ msgid "Croatian" msgstr "Croata" msgid "Upper Sorbian" -msgstr "" +msgstr "Sorbio Superior" msgid "Hungarian" msgstr "Húngaro" @@ -196,7 +198,7 @@ msgid "Burmese" msgstr "Birmano" msgid "Norwegian Bokmål" -msgstr "" +msgstr "Noruego" msgid "Nepali" msgstr "Nepalí" @@ -294,6 +296,15 @@ msgstr "Archivos estáticos" msgid "Syndication" msgstr "Sindicación" +msgid "That page number is not an integer" +msgstr "Ese número de página no es un número entero" + +msgid "That page number is less than 1" +msgstr "Ese número de página es menor que 1" + +msgid "That page contains no results" +msgstr "Esa página no contiene resultados" + msgid "Enter a valid value." msgstr "Introduzca un valor válido." @@ -306,6 +317,7 @@ msgstr "Ingrese un valor válido." msgid "Enter a valid email address." msgstr "Ingrese una dirección de correo electrónico válida." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -393,6 +405,17 @@ msgstr[0] "" msgstr[1] "" "Asegúrese de que no hayan más de %(max)s dígitos antes del punto decimal." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"La extensión de archivo '%(extension)s' no está permitida. Las extensiones " +"permitidas son ' %(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "y" @@ -595,11 +618,11 @@ msgstr "Relación uno a uno" #, python-format msgid "%(from)s-%(to)s relationship" -msgstr "" +msgstr "Relación %(from)s - %(to)s " #, python-format msgid "%(from)s-%(to)s relationships" -msgstr "" +msgstr "Relaciones %(from)s - %(to)s" msgid "Many-to-many relationship" msgstr "Relación muchos a muchos" @@ -725,18 +748,16 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Por favor, corrija los valores duplicados abajo." -msgid "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." msgstr "" -"La clave foránea en linea no coincide con la clave primaria de la instancia " -"padre." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" "Escoja una opción válida. Esa opción no está entre las opciones disponibles." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" no es un valor válido para una llave primaria." +msgid "\"%(pk)s\" is not a valid value." +msgstr "" #, python-format msgid "" @@ -746,15 +767,15 @@ msgstr "" "%(datetime)s no puede interpretarse en la zona horaria %(current_timezone)s; " "puede ser ambiguo o puede no existir." +msgid "Clear" +msgstr "Limpiar" + msgid "Currently" msgstr "Actualmente" msgid "Change" msgstr "Cambiar" -msgid "Clear" -msgstr "Limpiar" - msgid "Unknown" msgstr "Desconocido" @@ -1101,6 +1122,14 @@ msgstr "" "favor vuélvelas a activar, al menos para esta web, o para conexiones HTTPS, " "o para peticiones '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." +msgstr "" + 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 " @@ -1120,33 +1149,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Se puede ver más información si se establece DEBUG=True." -msgid "Welcome to Django" -msgstr "Bienvenido a Django" - -msgid "It worked!" -msgstr "¡Funcionó!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Felicitaciones por su primera página con Django." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Por supuesto, todavía no has hecho ningún trabajo. Para continuar, inicia tu " -"primera aplicación ejecutando python manage.py startapp [app_label]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Ves este mensaje porque tienes DEBUG = True en el archivo de " -"configuración de Django y no has configurado ninguna URL. ¡A trabajar!" - msgid "No year specified" msgstr "No se ha indicado el año" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "No se ha indicado el mes" @@ -1197,3 +1205,41 @@ msgstr "\"%(path)s\" no existe" #, python-format msgid "Index of %(directory)s" msgstr "Índice de %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/et/LC_MESSAGES/django.mo b/django/conf/locale/et/LC_MESSAGES/django.mo index 0a9889729382..23acf41b776e 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 67fa0edcc974..9102e8f8d7b5 100644 --- a/django/conf/locale/et/LC_MESSAGES/django.po +++ b/django/conf/locale/et/LC_MESSAGES/django.po @@ -6,15 +6,15 @@ # Janno Liivak , 2013-2015 # madisvain , 2011 # Martin Pajuste , 2014-2015 -# Martin Pajuste , 2016 +# Martin Pajuste , 2016-2017,2019 # Marti Raudsepp , 2014,2016 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-07-18 21:42+0000\n" -"Last-Translator: Marti Raudsepp \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 16:27+0000\n" +"Last-Translator: Martin Pajuste \n" "Language-Team: Estonian (http://www.transifex.com/django/django/language/" "et/)\n" "MIME-Version: 1.0\n" @@ -143,6 +143,9 @@ msgstr "ülemsorbi" msgid "Hungarian" msgstr "ungari" +msgid "Armenian" +msgstr "armeenia" + msgid "Interlingua" msgstr "interlingua" @@ -164,6 +167,9 @@ msgstr "jaapani" msgid "Georgian" msgstr "gruusia" +msgid "Kabyle" +msgstr "" + msgid "Kazakh" msgstr "kasahhi" @@ -299,6 +305,15 @@ msgstr "Staatilised failid" msgid "Syndication" msgstr "Sündikeerimine" +msgid "That page number is not an integer" +msgstr "See lehe number ei ole täisarv" + +msgid "That page number is less than 1" +msgstr "See lehe number on väiksem kui 1" + +msgid "That page contains no results" +msgstr "See leht ei sisalda tulemusi" + msgid "Enter a valid value." msgstr "Sisestage korrektne väärtus." @@ -311,6 +326,7 @@ msgstr "Sisestage korrektne täisarv." msgid "Enter a valid email address." msgstr "Sisestage korrektne e-posti aadress." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -375,6 +391,9 @@ msgstr[1] "" "Väärtuses võib olla kõige rohkem %(limit_value)d tähemärki (praegu on " "%(show_value)d)." +msgid "Enter a number." +msgstr "Sisestage arv." + #, 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." @@ -397,6 +416,17 @@ msgstr[0] "" msgstr[1] "" "Veenduge, et komast vasakul olevaid numbreid ei oleks rohkem kui %(max)s." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Faililaiend '%(extension)s' ei ole lubatud. Lubatud laiendid on: " +"'%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Tühjad tähemärgid ei ole lubatud." + msgid "and" msgstr "ja" @@ -445,6 +475,10 @@ msgstr "Suur (8 baiti) täisarv" msgid "'%(value)s' value must be either True or False." msgstr "'%(value)s' väärtus peab olema kas Tõene või Väär." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "" + msgid "Boolean (Either True or False)" msgstr "Tõeväärtus (Kas tõene või väär)" @@ -581,6 +615,9 @@ msgstr "Töötlemata binaarandmed" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' ei ole korrektne UUID." +msgid "Universally unique identifier" +msgstr "" + msgid "File" msgstr "Fail" @@ -620,9 +657,6 @@ msgstr "See lahter on nõutav." msgid "Enter a whole number." msgstr "Sisestage täisarv." -msgid "Enter a number." -msgstr "Sisestage arv." - msgid "Enter a valid date." msgstr "Sisestage korrektne kuupäev." @@ -635,6 +669,10 @@ msgstr "Sisestage korrektne kuupäev ja kellaaeg." msgid "Enter a valid duration." msgstr "Sisestage korrektne kestus." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Päevade arv peab jääma vahemikku {min_days} kuni {max_days}." + msgid "No file was submitted. Check the encoding type on the form." msgstr "Ühtegi faili ei saadetud. Kontrollige vormi kodeeringutüüpi." @@ -727,15 +765,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Palun parandage allolevad duplikaat-väärtused" -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "Pesastatud välisvõti ei sobi ülemobjekti primaarvõtmega." +msgid "The inline value did not match the parent instance." +msgstr "Pesastatud väärtus ei sobi ülemobjektiga." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Valige korrektne väärtus. Valitud väärtus ei ole valitav." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" ei ole sobiv väärtus primaarvõtmeks." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" ei ole korrektne väärtus." #, python-format msgid "" @@ -745,15 +783,15 @@ msgstr "" "%(datetime)s ei saanud tõlgendada ajavööndis %(current_timezone)s; see on " "kas puudu või mitmetähenduslik." +msgid "Clear" +msgstr "Tühjenda" + msgid "Currently" msgstr "Hetkel" msgid "Change" msgstr "Muuda" -msgid "Clear" -msgstr "Tühjenda" - msgid "Unknown" msgstr "Tundmatu" @@ -1025,8 +1063,8 @@ msgstr "See ei ole korrektne IPv6 aadress." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "" msgid "or" msgstr "või" @@ -1100,6 +1138,14 @@ msgstr "" "lülitage need taas sisse vähemalt antud lehe jaoks või HTTPS üheduste jaoks " "või 'sama-allika' päringute jaoks." +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." +msgstr "" + 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 " @@ -1119,32 +1165,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Saadaval on rohkem infot kasutades DEBUG=True" -msgid "Welcome to Django" -msgstr "Teretulemast Django juurde" - -msgid "It worked!" -msgstr "See töötas!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Õnnitleme Teie esimese Django-põhise lehe puhul." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Muidugi ei ole sa veel midagi tegelikult teinud. Järgmiseks käivita oma " -"rakendus käsuga python manage.py startapp [app_label]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Näete seda teadet, kuna teil on määratud DEBUG = True Django " -"seadete failis ja te ei ole ühtki URLi seadistanud. Ruttu tööle!" - msgid "No year specified" msgstr "Aasta on valimata" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "Kuu on valimata" @@ -1195,3 +1221,44 @@ msgstr "\"%(path)s\" ei eksisteeri" #, python-format msgid "Index of %(directory)s" msgstr "%(directory)s sisuloend" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" +"Näete seda lehte, kuna teil on määratud DEBUG=True Django seadete failis ja te ei ole ühtki URLi seadistanud." + +msgid "Django Documentation" +msgstr "Django dokumentatsioon" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/et/formats.py b/django/conf/locale/et/formats.py index d43da89dd3e2..1e1e458e75ee 100644 --- a/django/conf/locale/et/formats.py +++ b/django/conf/locale/et/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j. F Y' TIME_FORMAT = 'G:i' # DATETIME_FORMAT = @@ -15,7 +12,7 @@ # FIRST_DAY_OF_WEEK = # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # DATE_INPUT_FORMATS = # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = diff --git a/django/conf/locale/eu/LC_MESSAGES/django.mo b/django/conf/locale/eu/LC_MESSAGES/django.mo index 76718a01cb99..3a88f999b8ea 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 bbf65ea26e56..a18089d29596 100644 --- a/django/conf/locale/eu/LC_MESSAGES/django.po +++ b/django/conf/locale/eu/LC_MESSAGES/django.po @@ -1,21 +1,23 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Aitzol Naberan , 2013 +# Aitzol Naberan , 2013,2016 # Ander Martínez , 2013-2014 +# Eneko Illarramendi , 2017-2019 # Jannis Leidel , 2011 # jazpillaga , 2011 -# julen , 2011-2012 -# julen , 2013,2015 +# julen, 2011-2012 +# julen, 2013,2015 # totorika93 , 2012 # Unai Zalakain , 2013 +# Urtzi Odriozola , 2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-22 10:02+0000\n" +"Last-Translator: Eneko Illarramendi \n" "Language-Team: Basque (http://www.transifex.com/django/django/language/eu/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -30,16 +32,16 @@ msgid "Arabic" msgstr "Arabiera" msgid "Asturian" -msgstr "" +msgstr "Asturiera" msgid "Azerbaijani" -msgstr "Azerbaianera" +msgstr "Azerbaijanera" msgid "Bulgarian" msgstr "Bulgariera" msgid "Belarusian" -msgstr "Belarusiera" +msgstr "Bielorrusiera" msgid "Bengali" msgstr "Bengalera" @@ -57,16 +59,16 @@ msgid "Czech" msgstr "Txekiera" msgid "Welsh" -msgstr "Gales" +msgstr "Galesa" msgid "Danish" msgstr "Daniera" msgid "German" -msgstr "Alemaniera" +msgstr "Alemana" msgid "Lower Sorbian" -msgstr "" +msgstr "Behe-sorbiera" msgid "Greek" msgstr "Greziera" @@ -75,7 +77,7 @@ msgid "English" msgstr "Ingelesa" msgid "Australian English" -msgstr "" +msgstr "Australiar ingelesa" msgid "British English" msgstr "Ingelesa" @@ -84,22 +86,22 @@ msgid "Esperanto" msgstr "Esperantoa" msgid "Spanish" -msgstr "Espainola" +msgstr "Gaztelania" msgid "Argentinian Spanish" -msgstr "Espainola (Argentina)" +msgstr "Gaztelania (Argentina)" msgid "Colombian Spanish" -msgstr "" +msgstr "Gaztelania (Kolonbia)" msgid "Mexican Spanish" -msgstr "Espainola (Mexiko)" +msgstr "Gaztelania (Mexiko)" msgid "Nicaraguan Spanish" -msgstr "Espainola (Nikaragua)" +msgstr "Gaztelania (Nikaragua)" msgid "Venezuelan Spanish" -msgstr "Venezuelako gaztelera" +msgstr "Gaztelania (Venezuela)" msgid "Estonian" msgstr "Estoniera" @@ -120,10 +122,10 @@ msgid "Frisian" msgstr "Frisiera" msgid "Irish" -msgstr "Irlandako gaelera" +msgstr "Irlandako gaelikoa" msgid "Scottish Gaelic" -msgstr "" +msgstr "Eskoziako gaelikoa" msgid "Galician" msgstr "Galiziera" @@ -135,14 +137,17 @@ msgid "Hindi" msgstr "Hindi" msgid "Croatian" -msgstr "Kroaziarra" +msgstr "Kroaziera" msgid "Upper Sorbian" -msgstr "" +msgstr "Goi-sorbiera" msgid "Hungarian" msgstr "Hungariera" +msgid "Armenian" +msgstr "Armeniera" + msgid "Interlingua" msgstr "Interlingua" @@ -150,7 +155,7 @@ msgid "Indonesian" msgstr "Indonesiera" msgid "Ido" -msgstr "" +msgstr "Ido" msgid "Icelandic" msgstr "Islandiera" @@ -164,14 +169,17 @@ msgstr "Japoniera" msgid "Georgian" msgstr "Georgiera" +msgid "Kabyle" +msgstr "Kabylera" + msgid "Kazakh" msgstr "Kazakhera" msgid "Khmer" -msgstr "Khemerera" +msgstr "Khmerera" msgid "Kannada" -msgstr "Kanadiera" +msgstr "Kannada" msgid "Korean" msgstr "Koreera" @@ -195,22 +203,22 @@ msgid "Mongolian" msgstr "Mongoliera" msgid "Marathi" -msgstr "" +msgstr "Marathera" msgid "Burmese" msgstr "Birmaniera" msgid "Norwegian Bokmål" -msgstr "" +msgstr "Bokmåla (Norvegia)" msgid "Nepali" msgstr "Nepalera" msgid "Dutch" -msgstr "Holandera" +msgstr "Nederlandera" msgid "Norwegian Nynorsk" -msgstr "Nynorsk" +msgstr "Nynorsk (Norvegia)" msgid "Ossetic" msgstr "Osetiera" @@ -222,10 +230,10 @@ msgid "Polish" msgstr "Poloniera" msgid "Portuguese" -msgstr "Portugalera" +msgstr "Portugesa" msgid "Brazilian Portuguese" -msgstr "Portugalera (Brazil)" +msgstr "Portugesa (Brazil)" msgid "Romanian" msgstr "Errumaniera" @@ -258,7 +266,7 @@ msgid "Tamil" msgstr "Tamilera" msgid "Telugu" -msgstr "Telegu hizkuntza" +msgstr "Telugua" msgid "Thai" msgstr "Thailandiera" @@ -270,7 +278,7 @@ msgid "Tatar" msgstr "Tatarera" msgid "Udmurt" -msgstr "Udmurt" +msgstr "Udmurtera" msgid "Ukrainian" msgstr "Ukrainera" @@ -279,7 +287,7 @@ msgid "Urdu" msgstr "Urdua" msgid "Vietnamese" -msgstr "Vietnamamera" +msgstr "Vietnamera" msgid "Simplified Chinese" msgstr "Txinera (sinpletua)" @@ -288,29 +296,39 @@ msgid "Traditional Chinese" msgstr "Txinera (tradizionala)" msgid "Messages" -msgstr "" +msgstr "Mezuak" msgid "Site Maps" -msgstr "" +msgstr "Sitemap-ak" msgid "Static Files" msgstr "Fitxategi estatikoak" msgid "Syndication" -msgstr "" +msgstr "Sindikazioa" + +msgid "That page number is not an integer" +msgstr "Orrialde hori ez da zenbaki bat" + +msgid "That page number is less than 1" +msgstr "Orrialde zenbaki hori 1 baino txikiagoa da" + +msgid "That page contains no results" +msgstr "Orrialde horrek ez du emaitzarik" msgid "Enter a valid value." -msgstr "Idatzi balio zuzena." +msgstr "Idatzi baleko balio bat." msgid "Enter a valid URL." -msgstr "Idatzi baliozko URL bat." +msgstr "Idatzi baleko URL bat." msgid "Enter a valid integer." -msgstr "Idatzi baliozko osoko zenbakia." +msgstr "Idatzi baleko zenbaki bat." msgid "Enter a valid email address." -msgstr "Gehitu baleko email helbide bat" +msgstr "Idatzi baleko helbide elektroniko bat." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -320,15 +338,17 @@ msgid "" "Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or " "hyphens." msgstr "" +"Idatzi Unicode hizki, zenbaki, azpimarra edo marratxoz osatutako baleko " +"'slug' bat." msgid "Enter a valid IPv4 address." -msgstr "Sartu IPv4 helbide zuzena." +msgstr "Idatzi baleko IPv4 sare-helbide bat." msgid "Enter a valid IPv6 address." -msgstr "Sartu IPv6 helbide zuzena" +msgstr "Idatzi baleko IPv6 sare-helbide bat." msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Sartu IPv4 edo IPv6 helbide zuzena." +msgstr "Idatzi baleko IPv4 edo IPv6 sare-helbide bat." msgid "Enter only digits separated by commas." msgstr "Idatzi komaz bereizitako digitoak soilik." @@ -336,8 +356,7 @@ msgstr "Idatzi komaz bereizitako digitoak soilik." #, python-format msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." msgstr "" -"Ziurtatu balioak %(limit_value)s gutxienez karaktere dituela (orain " -"%(show_value)s dauzka)." +"Ziurtatu balio hau gutxienez %(limit_value)s dela (orain %(show_value)s da)." #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." @@ -375,6 +394,9 @@ msgstr[1] "" "Ziurtatu balio honek gehienez %(limit_value)d karaktere dituela " "(%(show_value)d ditu)." +msgid "Enter a number." +msgstr "Idatzi zenbaki bat." + #, 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." @@ -395,22 +417,33 @@ msgid_plural "" msgstr[0] "Ziurtatu ez dagoela digitu %(max)s baino gehiago komaren aurretik." msgstr[1] "Ziurtatu ez dagoela %(max)s digitu baino gehiago komaren aurretik." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"'%(extension)s' fitxategi-luzapena ez da balekoa. Hauek dira onartutako " +"fitxategi-luzapenak: '%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Null karaktereak ez daude baimenduta." + msgid "and" msgstr "eta" #, python-format msgid "%(model_name)s with this %(field_labels)s already exists." -msgstr "" +msgstr "%(field_labels)s hauek dauzkan %(model_name)s dagoeneko existitzen da." #, python-format msgid "Value %(value)r is not a valid choice." -msgstr "%(value)r balioa ez da baliozko aukera." +msgstr "%(value)r balioa ez da baleko aukera bat." msgid "This field cannot be null." msgstr "Eremu hau ezin daiteke hutsa izan (null)." msgid "This field cannot be blank." -msgstr "Eremu hau ezin da hutsik egon." +msgstr "Eremu honek ezin du hutsik egon." #, python-format msgid "%(model_name)s with this %(field_label)s already exists." @@ -422,6 +455,7 @@ msgstr "%(field_label)s hori daukan %(model_name)s dagoeneko existitzen da." msgid "" "%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." msgstr "" +"%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." #, python-format msgid "Field of type: %(field_type)s" @@ -432,21 +466,25 @@ msgstr "Zenbaki osoa" #, python-format msgid "'%(value)s' value must be an integer." -msgstr "" +msgstr "'%(value)s' balioak integer bat izan behar du." msgid "Big (8 byte) integer" msgstr "Zenbaki osoa (handia 8 byte)" #, python-format msgid "'%(value)s' value must be either True or False." -msgstr "" +msgstr "'%(value)s' balioak True edo False izan behar du." + +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "'%(value)s' balioak True, False edo None izan behar du." msgid "Boolean (Either True or False)" -msgstr "Boolearra (egia ala gezurra)" +msgstr "Boolearra (True edo False)" #, python-format msgid "String (up to %(max_length)s)" -msgstr "Katea (%(max_length)s gehienez)" +msgstr "String-a (%(max_length)s gehienez)" msgid "Comma-separated integers" msgstr "Komaz bereiztutako zenbaki osoak" @@ -456,12 +494,16 @@ msgid "" "'%(value)s' value has an invalid date format. It must be in YYYY-MM-DD " "format." msgstr "" +"'%(value)s' balioak ez dauka data formatu zuzena. Formatu zuzena UUUU-HH-EE " +"da." #, python-format msgid "" "'%(value)s' value has the correct format (YYYY-MM-DD) but it is an invalid " "date." msgstr "" +"'%(value)s' balioak formatu zuzena (UUUU-HH-EE) dauka, baina ez da data " +"zuzen bat." msgid "Date (without time)" msgstr "Data (ordurik gabe)" @@ -471,19 +513,24 @@ msgid "" "'%(value)s' value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." "uuuuuu]][TZ] format." msgstr "" +"'%(value)s' balioak ez dauka formatu zuzena. Formatu zuzena UUUU-HH-EE OO:" +"MM[:ss[.uuuuuu]][TZ] da." #, 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' balioak formatu zuzena dauka (UUUU-HH-EE OO:MM[:ss[.uuuuuu]]" +"[TZ]),\n" +"baina ez da data/ordu zuzena." msgid "Date (with time)" msgstr "Data (orduarekin)" #, python-format msgid "'%(value)s' value must be a decimal number." -msgstr "" +msgstr "'%(value)s' balioak zenbaki hamartarra izan behar du." msgid "Decimal number" msgstr "Zenbaki hamartarra" @@ -493,35 +540,37 @@ msgid "" "'%(value)s' value has an invalid format. It must be in [DD] [HH:[MM:]]ss[." "uuuuuu] format." msgstr "" +"'%(value)s' balioak ez dauka foramtu zuzena. [EE] [OO:[MM:]]ss[.uuuuuu] " +"formatuan egon behar da." msgid "Duration" -msgstr "" +msgstr "Iraupena" msgid "Email address" -msgstr "Eposta helbidea" +msgstr "Helbide elektronikoa" msgid "File path" msgstr "Fitxategiaren bidea" #, python-format msgid "'%(value)s' value must be a float." -msgstr "" +msgstr "'%(value)s' balioak float bat izan behar du." msgid "Floating point number" msgstr "Koma higikorreko zenbakia (float)" msgid "IPv4 address" -msgstr "IPv4 helbidea" +msgstr "IPv4 sare-helbidea" msgid "IP address" msgstr "IP helbidea" #, python-format msgid "'%(value)s' value must be either None, True or False." -msgstr "" +msgstr "'%(value)s' balioak None, True, edo False izan behar du." msgid "Boolean (Either True, False or None)" -msgstr "Boolearra (egia, gezurra edo hutsa[None])" +msgstr "Boolearra (True, False edo None)" msgid "Positive integer" msgstr "Osoko positiboa" @@ -544,12 +593,17 @@ msgid "" "'%(value)s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " "format." msgstr "" +"'%(value)s' balioak ez dauka formatu zuzena. OO:MM[:ss[.uuuuuu]] formatuan " +"egon behar du." #, python-format msgid "" "'%(value)s' value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " "invalid time." msgstr "" +"'%(value)s' balioak formatu zuzena dauka (OO:MM[:ss[.uuuuuu]]) baina ez da " +"ordu \n" +"zuzena" msgid "Time" msgstr "Ordua" @@ -562,7 +616,10 @@ msgstr "Datu bitar gordinak" #, python-format msgid "'%(value)s' is not a valid UUID." -msgstr "" +msgstr "'%(value)s' ez da baleko UUID bat." + +msgid "Universally unique identifier" +msgstr "\"Universally unique identifier\"" msgid "File" msgstr "Fitxategia" @@ -573,6 +630,8 @@ msgstr "Irudia" #, python-format msgid "%(model)s instance with %(field)s %(value)r does not exist." msgstr "" +"%(field)s %(value)r edukidun %(model)s modeloko instantziarik ez da " +"exiistitzen." msgid "Foreign Key (type determined by related field)" msgstr "1-N (mota erlazionatutako eremuaren arabera)" @@ -582,11 +641,11 @@ msgstr "Bat-bat erlazioa" #, python-format msgid "%(from)s-%(to)s relationship" -msgstr "" +msgstr "%(from)s-%(to)s erlazioa" #, python-format msgid "%(from)s-%(to)s relationships" -msgstr "" +msgstr "%(from)s-%(to)s erlazioak" msgid "Many-to-many relationship" msgstr "M:N erlazioa" @@ -603,23 +662,24 @@ msgstr "Eremu hau beharrezkoa da." msgid "Enter a whole number." msgstr "Idatzi zenbaki oso bat." -msgid "Enter a number." -msgstr "Idatzi zenbaki bat." - msgid "Enter a valid date." -msgstr "Idatzi baliozko data bat." +msgstr "Idatzi baleko data bat." msgid "Enter a valid time." -msgstr "Idatzi baliozko ordu bat." +msgstr "Idatzi baleko ordu bat." msgid "Enter a valid date/time." -msgstr "Idatzi baliozko data/ordua." +msgstr "Idatzi baleko data/ordu bat." msgid "Enter a valid duration." -msgstr "" +msgstr "Idatzi baleko iraupen bat." + +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Egun kopuruak {min_days} eta {max_days} artean egon behar du." msgid "No file was submitted. Check the encoding type on the form." -msgstr "Ez da fitxategirik bidali. Egiaztatu inprimakiaren kodeketa-mota." +msgstr "Ez da fitxategirik bidali. Egiaztatu formularioaren kodeketa-mota." msgid "No file was submitted." msgstr "Ez da fitxategirik bidali." @@ -645,21 +705,21 @@ msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "" -"Bidali baliozko irudia. Zuk bidalitako fitxategia ez da irudia edo akatsa " -"dauka." +"Igo baleko irudi bat. Zuk igotako fitxategia ez da irudi bat edo akatsen bat " +"du." #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." -msgstr "Hautatu baliozko aukera bat. %(value)s ez dago erabilgarri." +msgstr "Hautatu baleko aukera bat. %(value)s ez dago erabilgarri." msgid "Enter a list of values." msgstr "Idatzi balio-zerrenda bat." msgid "Enter a complete value." -msgstr "" +msgstr "Sartu balio osoa." msgid "Enter a valid UUID." -msgstr "" +msgstr "Idatzi baleko UUID bat." #. Translators: This is the default suffix added to form field labels msgid ":" @@ -670,19 +730,19 @@ msgid "(Hidden field %(name)s) %(error)s" msgstr "(%(name)s eremu ezkutua) %(error)s" msgid "ManagementForm data is missing or has been tampered with" -msgstr "" +msgstr "ManagementForm daturik ez dago edo ez da balekoa." #, python-format msgid "Please submit %d or fewer forms." msgid_plural "Please submit %d or fewer forms." -msgstr[0] "Bidali inprimaki %d ala gutxiago, mesedez." -msgstr[1] "Bidali %d inprimaki ala gutxiago, mesedez." +msgstr[0] "Bidali formulario %d edo gutxiago, mesedez." +msgstr[1] "Bidali %d formulario edo gutxiago, mesedez." #, python-format msgid "Please submit %d or more forms." msgid_plural "Please submit %d or more forms." -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Gehitu formulario %d edo gehiago" +msgstr[1] "Bidali %d formulario edo gehiago, mesedez." msgid "Order" msgstr "Ordena" @@ -709,15 +769,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Zuzendu hurrengo balio bikoiztuak." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "Barneko gakoa eta gurasoaren gakoa ez datoz bat." +msgid "The inline value did not match the parent instance." +msgstr "Barneko balioa eta gurasoaren instantzia ez datoz bat." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Hautatu aukera zuzen bat. Hautatutakoa ez da zuzena." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" ez da balio egokia lehen mailako gakoentzat." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" ez da balio egokia." #, python-format msgid "" @@ -727,15 +787,15 @@ msgstr "" "%(datetime)s ezin da interpretatu %(current_timezone)s ordu-eremuan;\n" "baliteke ez existitzea edo anbiguoa izatea" +msgid "Clear" +msgstr "Garbitu" + msgid "Currently" msgstr "Orain" msgid "Change" msgstr "Aldatu" -msgid "Clear" -msgstr "Garbitu" - msgid "Unknown" msgstr "Ezezaguna" @@ -793,82 +853,82 @@ msgid "noon" msgstr "eguerdia" msgid "Monday" -msgstr "Astelehena" +msgstr "astelehena" msgid "Tuesday" -msgstr "Asteartea" +msgstr "asteartea" msgid "Wednesday" -msgstr "Asteazkena" +msgstr "asteazkena" msgid "Thursday" -msgstr "Osteguna" +msgstr "osteguna" msgid "Friday" -msgstr "Ostirala" +msgstr "ostirala" msgid "Saturday" -msgstr "Larunbata" +msgstr "larunbata" msgid "Sunday" -msgstr "Igandea" +msgstr "igandea" msgid "Mon" -msgstr "Al" +msgstr "al" msgid "Tue" -msgstr "Ar" +msgstr "ar" msgid "Wed" -msgstr "Az" +msgstr "az" msgid "Thu" -msgstr "Og" +msgstr "og" msgid "Fri" -msgstr "Ol" +msgstr "ol" msgid "Sat" -msgstr "Lr" +msgstr "lr" msgid "Sun" -msgstr "Ig" +msgstr "ig" msgid "January" -msgstr "Urtarrila" +msgstr "urtarrila" msgid "February" -msgstr "Otsaila" +msgstr "otsaila" msgid "March" -msgstr "Martxoa" +msgstr "martxoa" msgid "April" -msgstr "Apirila" +msgstr "apirila" msgid "May" -msgstr "Maiatza" +msgstr "maiatza" msgid "June" -msgstr "Ekaina" +msgstr "ekaina" msgid "July" -msgstr "Uztaila" +msgstr "uztaila" msgid "August" -msgstr "Abuztua" +msgstr "abuztua" msgid "September" -msgstr "Iraila" +msgstr "iraila" msgid "October" -msgstr "Urria" +msgstr "urria" msgid "November" -msgstr "Azaroa" +msgstr "azaroa" msgid "December" -msgstr "Abendua" +msgstr "abendua" msgid "jan" msgstr "urt" @@ -908,107 +968,107 @@ msgstr "abe" msgctxt "abbrev. month" msgid "Jan." -msgstr "Urt." +msgstr "urt." msgctxt "abbrev. month" msgid "Feb." -msgstr "Ots." +msgstr "ots." msgctxt "abbrev. month" msgid "March" -msgstr "Mar." +msgstr "mar." msgctxt "abbrev. month" msgid "April" -msgstr "Api." +msgstr "api." msgctxt "abbrev. month" msgid "May" -msgstr "Mai." +msgstr "mai." msgctxt "abbrev. month" msgid "June" -msgstr "Eka." +msgstr "eka." msgctxt "abbrev. month" msgid "July" -msgstr "Uzt." +msgstr "uzt." msgctxt "abbrev. month" msgid "Aug." -msgstr "Abu." +msgstr "abu." msgctxt "abbrev. month" msgid "Sept." -msgstr "Ira." +msgstr "ira." msgctxt "abbrev. month" msgid "Oct." -msgstr "Urr." +msgstr "urr." msgctxt "abbrev. month" msgid "Nov." -msgstr "Aza." +msgstr "aza." msgctxt "abbrev. month" msgid "Dec." -msgstr "Abe." +msgstr "abe." msgctxt "alt. month" msgid "January" -msgstr "Urtarrila" +msgstr "urtarrila" msgctxt "alt. month" msgid "February" -msgstr "Otsaila" +msgstr "otsaila" msgctxt "alt. month" msgid "March" -msgstr "Martxoa" +msgstr "martxoa" msgctxt "alt. month" msgid "April" -msgstr "Apirila" +msgstr "apirila" msgctxt "alt. month" msgid "May" -msgstr "Maiatza" +msgstr "maiatza" msgctxt "alt. month" msgid "June" -msgstr "Ekaina" +msgstr "ekaina" msgctxt "alt. month" msgid "July" -msgstr "Uztaila" +msgstr "uztaila" msgctxt "alt. month" msgid "August" -msgstr "Abuztua" +msgstr "abuztua" msgctxt "alt. month" msgid "September" -msgstr "Iraila" +msgstr "iraila" msgctxt "alt. month" msgid "October" -msgstr "Urria" +msgstr "urria" msgctxt "alt. month" msgid "November" -msgstr "Azaroa" +msgstr "azaroa" msgctxt "alt. month" msgid "December" -msgstr "Abendua" +msgstr "abendua" msgid "This is not a valid IPv6 address." -msgstr "" +msgstr "Hau ez da baleko IPv6 helbide bat." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s…" msgid "or" msgstr "edo" @@ -1057,7 +1117,7 @@ msgid "0 minutes" msgstr "0 minutu" msgid "Forbidden" -msgstr "" +msgstr "Debekatuta" msgid "CSRF verification failed. Request aborted." msgstr "CSRF egiaztapenak huts egin du. Eskaera abortatu da." @@ -1068,49 +1128,58 @@ msgid "" "required for security reasons, to ensure that your browser is not being " "hijacked by third parties." msgstr "" +"Mezu hau ikusten ari zara HTTPS gune honek, zure nabigatzaileak 'Referer " +"header' bat bidaltzea behar duelako, baina ez du batere bidali. Goiburuko " +"hau zure nabigatzailea beste norbaitek ordeztu ez duela ziurtatzeko eskatzen " +"da." 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 "" +"Zure nabigatzailera 'Refere' goiburukoak desgaitzeko konfiguratu baldin " +"baduzu, mesedez, gune honetarako, HTTPS konexio edo 'same-origin' " +"eskaeretarako gaitu berriro." + +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." +msgstr "" +" etiketa erabiltzen ari " +"bazara edo 'Referrer-Policy: no-referrer' goiburukoa, mesedez ezabatu " +"itzazu. CSRF babesak 'Referer' goiburukoa behar du egiaztapen zorrotza " +"egiteko. Pribatutasunaz kezkatuta bazaude, erabili bezalako alternatibak hirugarrenen webgune loturentzat." 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 "" +"Formularioa bidaltzean gune honek CSRF cookie bat behar duelako ikusten duzu " +"mezu hau. Cookie hau beharrezkoa da segurtasun arrazoiengatik, zure " +"nabigatzailea beste batek ordezkatzen ez duela ziurtatzeko." 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 "" +"Nabigatzailea cookiak desgaitzeko konfiguratu baldin baduzu, mesedez " +"aktibatu behintzat gune honetarako, edo 'same-origin' eskaeretarako." msgid "More information is available with DEBUG=True." msgstr "Informazio gehiago erabilgarri dago DEBUG=True ezarrita." -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "Ez da urterik zehaztu" +msgid "Date out of range" +msgstr "Data baliozko tartetik kanpo" + msgid "No month specified" msgstr "Ez da hilabeterik zehaztu" @@ -1129,8 +1198,8 @@ msgid "" "Future %(verbose_name_plural)s not available because %(class_name)s." "allow_future is False." msgstr "" -"Etorkizuneko %(verbose_name_plural)s ez dago aukeran \n" -"%(class_name)s.alloe_future False delako" +"Etorkizuneko %(verbose_name_plural)s ez dago aukeran %(class_name)s." +"allow_future False delako" #, python-format msgid "Invalid date string '%(datestr)s' given format '%(format)s'" @@ -1161,3 +1230,48 @@ msgstr "\"%(path)s\" ez da existitzen" #, python-format msgid "Index of %(directory)s" msgstr "%(directory)s zerrenda" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: epeekin perfekzionistak direnentzat Web frameworka." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Ikusi Django %(version)s-ren argitaratze " +"oharrak" + +msgid "The install worked successfully! Congratulations!" +msgstr "Instalazioak arrakastaz funtzionatu du! Zorionak!" + +#, python-format +msgid "" +"You are seeing this page because 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." + +msgid "Django Documentation" +msgstr "Django dokumentazioa" + +msgid "Topics, references, & how-to's" +msgstr "Gaiak, erreferentziak, & laguntzak" + +msgid "Tutorial: A Polling App" +msgstr "Tutoriala: Galdetegi aplikazioa" + +msgid "Get started with Django" +msgstr "Hasi Djangorekin" + +msgid "Django Community" +msgstr "Django Komunitatea" + +msgid "Connect, get help, or contribute" +msgstr "Konektatu, lortu laguntza edo lagundu" diff --git a/django/conf/locale/eu/formats.py b/django/conf/locale/eu/formats.py index 4ddf04ef5ab8..33e6305352f4 100644 --- a/django/conf/locale/eu/formats.py +++ b/django/conf/locale/eu/formats.py @@ -1,24 +1,21 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date -DATE_FORMAT = r'Yeko M\re\n d\a' +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +DATE_FORMAT = r'Y\k\o N j\a' TIME_FORMAT = 'H:i' -# DATETIME_FORMAT = -# YEAR_MONTH_FORMAT = -# MONTH_DAY_FORMAT = -SHORT_DATE_FORMAT = 'Y M j' -# SHORT_DATETIME_FORMAT = -# FIRST_DAY_OF_WEEK = +DATETIME_FORMAT = r'Y\k\o N j\a, H:i' +YEAR_MONTH_FORMAT = r'Y\k\o F' +MONTH_DAY_FORMAT = r'F\r\e\n j\a' +SHORT_DATE_FORMAT = 'Y-m-d' +SHORT_DATETIME_FORMAT = 'Y-m-d H:i' +FIRST_DAY_OF_WEEK = 1 # Astelehena # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # DATE_INPUT_FORMATS = # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = DECIMAL_SEPARATOR = ',' THOUSAND_SEPARATOR = '.' -# NUMBER_GROUPING = +NUMBER_GROUPING = 3 diff --git a/django/conf/locale/fa/LC_MESSAGES/django.mo b/django/conf/locale/fa/LC_MESSAGES/django.mo index 5949444db814..ca829e48b31b 100644 Binary files a/django/conf/locale/fa/LC_MESSAGES/django.mo and b/django/conf/locale/fa/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/fa/LC_MESSAGES/django.po b/django/conf/locale/fa/LC_MESSAGES/django.po index e6f8bbcb8dfc..da3111a40326 100644 --- a/django/conf/locale/fa/LC_MESSAGES/django.po +++ b/django/conf/locale/fa/LC_MESSAGES/django.po @@ -3,9 +3,12 @@ # Translators: # Ali Vakilzade , 2015 # Arash Fazeli , 2012 +# Eric Hamiter , 2019 # Jannis Leidel , 2011 # Mazdak Badakhshan , 2014 -# Mohammad Hossein Mojtahedi , 2013 +# Milad Hazrati , 2019 +# MJafar Mashhadi , 2018 +# Mohammad Hossein Mojtahedi , 2013,2019 # Pouya Abbassi, 2016 # Reza Mohammadi , 2013-2016 # Saeed , 2011 @@ -14,16 +17,16 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-07-01 20:23+0000\n" -"Last-Translator: Pouya Abbassi\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-03-22 09:42+0000\n" +"Last-Translator: Milad Hazrati \n" "Language-Team: Persian (http://www.transifex.com/django/django/language/" "fa/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: fa\n" -"Plural-Forms: nplurals=1; plural=0;\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" msgid "Afrikaans" msgstr "آفریکانس" @@ -92,7 +95,7 @@ msgid "Argentinian Spanish" msgstr "اسپانیایی آرژانتینی" msgid "Colombian Spanish" -msgstr "کلمبیائی اسپانیایی" +msgstr "اسپانیایی کلمبیایی" msgid "Mexican Spanish" msgstr "اسپانیولی مکزیکی" @@ -145,6 +148,9 @@ msgstr "صربستانی بالا" msgid "Hungarian" msgstr "مجاری" +msgid "Armenian" +msgstr "ارمنی" + msgid "Interlingua" msgstr "اینترلینگوا" @@ -166,6 +172,9 @@ msgstr "ژاپنی" msgid "Georgian" msgstr "گرجی" +msgid "Kabyle" +msgstr "قبایلی" + msgid "Kazakh" msgstr "قزاقستان" @@ -301,6 +310,15 @@ msgstr "پرونده‌های استاتیک" msgid "Syndication" msgstr "پیوند" +msgid "That page number is not an integer" +msgstr "شمارهٔ صفحه باید یک عدد باشد" + +msgid "That page number is less than 1" +msgstr "شمارهٔ صفحه باید بزرگتر از ۱ باشد" + +msgid "That page contains no results" +msgstr "این صفحه خالی از اطلاعات است" + msgid "Enter a valid value." msgstr "یک مقدار معتبر وارد کنید." @@ -313,6 +331,7 @@ msgstr "یک عدد معتبر وارد کنید." msgid "Enter a valid email address." msgstr "یک ایمیل آدرس معتبر وارد کنید." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "یک 'slug' معتبر شامل حروف، ارقام، خط زیر و یا خط تیره وارد کنید." @@ -356,6 +375,9 @@ msgid_plural "" msgstr[0] "" "طول این مقدار باید حداقل %(limit_value)d کاراکتر باشد (طولش %(show_value)d " "است)." +msgstr[1] "" +"طول این مقدار باید حداقل %(limit_value)d کاراکتر باشد (طولش %(show_value)d " +"است)." #, python-format msgid "" @@ -367,16 +389,24 @@ msgid_plural "" msgstr[0] "" "طول این مقدار باید حداکثر %(limit_value)d کاراکتر باشد (طولش %(show_value)d " "است)." +msgstr[1] "" +"طول این مقدار باید حداکثر %(limit_value)d کاراکتر باشد (طولش %(show_value)d " +"است)." + +msgid "Enter a number." +msgstr "یک عدد وارد کنید." #, 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] "نباید در مجموع بیش از %(max)s رقم داشته باشد." +msgstr[1] "نباید در مجموع بیش از %(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[1] "نباید بیش از %(max)s رقم اعشار داشته باشد." #, python-format msgid "" @@ -384,6 +414,18 @@ msgid "" msgid_plural "" "Ensure that there are no more than %(max)s digits before the decimal point." msgstr[0] "نباید بیش از %(max)s رقم قبل ممیز داشته باشد." +msgstr[1] "نباید بیش از %(max)s رقم قبل ممیز داشته باشد." + +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"استفاده از پرونده با پسوند '%(extension)s' مجاز نیست. پسوند‌های مجاز عبارتند " +"از: '%(allowed_extensions)s'" + +msgid "Null characters are not allowed." +msgstr "کاراکترهای تهی مجاز نیستند." msgid "and" msgstr "و" @@ -432,6 +474,10 @@ msgstr "بزرگ (8 بایت) عدد صحیح" msgid "'%(value)s' value must be either True or False." msgstr "مقدار «%(value)s» باید یا True باشد و یا False." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "مقدار «%(value)s» باید یا None باشد یا True و یا False." + msgid "Boolean (Either True or False)" msgstr "بولی (درست یا غلط)" @@ -569,6 +615,9 @@ msgstr "دادهٔ دودویی خام" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' یک UUID معتبر نیست." +msgid "Universally unique identifier" +msgstr "شناسه منحصر به فرد سراسری" + msgid "File" msgstr "پرونده" @@ -608,9 +657,6 @@ msgstr "این فیلد لازم است." msgid "Enter a whole number." msgstr "به طور کامل یک عدد وارد کنید." -msgid "Enter a number." -msgstr "یک عدد وارد کنید." - msgid "Enter a valid date." msgstr "یک تاریخ معتبر وارد کنید." @@ -623,6 +669,10 @@ msgstr "یک تاریخ/زمان معتبر وارد کنید." msgid "Enter a valid duration." msgstr "یک بازهٔ زمانی معتبر وارد کنید." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "عدد روز باید بین {min_days} و {max_days} باشد." + msgid "No file was submitted. Check the encoding type on the form." msgstr "پرونده‌ای ارسال نشده است. نوع کدگذاری فرم را بررسی کنید." @@ -638,6 +688,8 @@ msgid_plural "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." msgstr[0] "" "طول عنوان پرونده باید حداقل %(max)d کاراکتر باشد (طولش %(length)d است)." +msgstr[1] "" +"طول عنوان پرونده باید حداقل %(max)d کاراکتر باشد (طولش %(length)d است)." msgid "Please either submit a file or check the clear checkbox, not both." msgstr "لطفا یا فایل ارسال کنید یا دکمه پاک کردن را علامت بزنید، نه هردو." @@ -677,11 +729,13 @@ msgstr "اطلاعات ManagementForm ناقص است و یا دستکاری ش msgid "Please submit %d or fewer forms." msgid_plural "Please submit %d or fewer forms." msgstr[0] "لطفاً %d یا کمتر فرم بفرستید." +msgstr[1] "لطفاً %d یا کمتر فرم بفرستید." #, python-format msgid "Please submit %d or more forms." msgid_plural "Please submit %d or more forms." msgstr[0] "لطفاً %d یا بیشتر فرم بفرستید." +msgstr[1] "لطفاً %d یا بیشتر فرم بفرستید." msgid "Order" msgstr "ترتیب:" @@ -708,15 +762,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "لطفا مقدار تکراری را اصلاح کنید." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "کلید های درون خطی خارجی با هم مطابقت ندارند ." +msgid "The inline value did not match the parent instance." +msgstr "مقدار درون خطی موجود با نمونه والد آن مطابقت ندارد." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "یک گزینهٔ معتبر انتخاب کنید. آن گزینه از گزینه‌های موجود نیست." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "‏«‎%(pk)s» مقدار معتبری برای کلید اصلی نیست." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" یک مقدار معتبر نیست." #, python-format msgid "" @@ -726,15 +780,15 @@ msgstr "" "%(datetime)s نمیتواند در %(current_timezone)s معنی شود.شاید این زمان مبهم " "است و یا وجود ندارد." +msgid "Clear" +msgstr "پاک کردن" + msgid "Currently" msgstr "در حال حاضر" msgid "Change" msgstr "تغییر" -msgid "Clear" -msgstr "پاک کردن" - msgid "Unknown" msgstr "ناشناخته" @@ -751,6 +805,7 @@ msgstr "بله،خیر،شاید" msgid "%(size)d byte" msgid_plural "%(size)d bytes" msgstr[0] "%(size)d بایت" +msgstr[1] "%(size)d بایت" #, python-format msgid "%s KB" @@ -1005,8 +1060,8 @@ msgstr "این مقدار آدرس IPv6 معتبری نیست." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "" msgid "or" msgstr "یا" @@ -1019,31 +1074,37 @@ msgstr "،" msgid "%d year" msgid_plural "%d years" msgstr[0] "%d سال" +msgstr[1] "%d سال" #, python-format msgid "%d month" msgid_plural "%d months" msgstr[0] "%d ماه" +msgstr[1] "%d ماه" #, python-format msgid "%d week" msgid_plural "%d weeks" msgstr[0] "%d هفته" +msgstr[1] "%d هفته" #, python-format msgid "%d day" msgid_plural "%d days" msgstr[0] "%d روز" +msgstr[1] "%d روز" #, python-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "%d ساعت" +msgstr[1] "%d ساعت" #, python-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d دقیقه" +msgstr[1] "%d دقیقه" msgid "0 minutes" msgstr "0 دقیقه" @@ -1060,27 +1121,41 @@ msgid "" "required for security reasons, to ensure that your browser is not being " "hijacked by third parties." msgstr "" -"شما این پیغام را میبینید چون این سایتِ HTTPS نیازمند یک «تیتر ارجاع» برای " -"ارسال به بروزر شماست، ولی هیچ چیزی ارسال نشده است. این تیتر به دلایل امنیتی " -"مورد نیاز است، برای اینکه از هایجک نشدن بروزر اطمینان حاصل شود." +"شما این پیام را می‌بینید چون این سایتِ 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 "" -"اگر بزوزر خود را برای غیر فعال کردن تیترهای «ارجاع» تنظیم کرده‌اید، لطفا " -"مجددا این ویژگی را فعال کنید، حداقل برای این وبسایت، یا برای اتصالات HTTPS، " -"یا برای درخواستهایی با «مبدا یکسان»." +"اگر تیترهای «ارجاع (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." +msgstr "" +"اگر از تگ یا هدر " +"'Referrer-Policy: no-referrer' استفاده می کنید، لطفا حذفشان کنید. محافظ CSRF " +"به هدر 'Referer' برای بررسی قوی ارجاع دهنده نیازمند است. اگر شما نگران حریم " +"خصوصی هستید، از جایگزین هایی مانند پیوندهای به " +"سایت های دیگر استفاده کنید. " 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 "" -"شما این پیغام را میبینید چون این سایت نیازمند کوکی «جعل درخواست میان وبگاهی» " -"در زمان ارائه ی فورم میباشد. این کوکی‌ها برای مسائل امنیتی ضروری هستند، برای " -"اطمینان از اینکه بروزر شما توسط شخص ثالثی هایجک نشده باشد." +"شما این پیام را میبینید چون این سایت نیازمند کوکی «جعل درخواست میان وبگاهی " +"(CSRF)» است. این کوکی برای امنیت شما ضروری است. با این کوکی می‌توانیم از " +"اینکه شخص ثالثی کنترل مرورگرتان را به دست نگرفته است اطمینان پیدا کنیم." msgid "" "If you have configured your browser to disable cookies, please re-enable " @@ -1092,32 +1167,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "اطلاعات بیشتر با DEBUG=True ارائه خواهد شد." -msgid "Welcome to Django" -msgstr "به Django خوش آمدید" - -msgid "It worked!" -msgstr "کار کرد!" - -msgid "Congratulations on your first Django-powered page." -msgstr "تبریک فراوان بابت اولین صفحهٔ Djangoای‌تان." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"مطمئنا شما هنوز کاری انجام نداده اید. اولین اپلیکیشن خود را با اجرای دستور " -"python manage.py startapp [app_label] شروع کنید." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"شما این پیغام را میبینید چون DEBUG = True در فایل تنظیمات جنگو " -"تنیظم شده و هیچ URLـی تنظیم نکرده اید. به کار خود ادامه دهید." - msgid "No year specified" msgstr "هیچ سالی مشخص نشده است" +msgid "Date out of range" +msgstr "تاریخ غیرمجاز است" + msgid "No month specified" msgstr "هیچ ماهی مشخص نشده است" @@ -1168,3 +1223,48 @@ msgstr "\"%(path)s\" وجود ندارد" #, python-format msgid "Index of %(directory)s" msgstr "فهرست %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "جنگو: فریمورک وب برای کمال گرایانی که محدودیت زمانی دارند." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"نمایش release notes برای نسخه %(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." +msgstr "" +"شما این صفحه را به این دلیل مشاهده می کنید که DEBUG=True در فایل تنظیمات شما وجود دارد و شما هیچ URL " +"تنظیم نکرده اید." + +msgid "Django Documentation" +msgstr "مستندات جنگو" + +msgid "Topics, references, & how-to's" +msgstr "مباحث، ارجاعات و سوالات آغاز شونده با \"چگونه؟\"" + +msgid "Tutorial: A Polling App" +msgstr "آموزش گام به گام: برنامکی برای رأی‌گیری" + +msgid "Get started with Django" +msgstr "شروع به کار با جنگو" + +msgid "Django Community" +msgstr "جامعهٔ جنگو" + +msgid "Connect, get help, or contribute" +msgstr "متصل شوید، کمک بگیرید یا مشارکت کنید" diff --git a/django/conf/locale/fa/formats.py b/django/conf/locale/fa/formats.py index c1678b81cddf..c8666f7a035d 100644 --- a/django/conf/locale/fa/formats.py +++ b/django/conf/locale/fa/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j F Y' TIME_FORMAT = 'G:i' DATETIME_FORMAT = 'j F Y، ساعت G:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 6 # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # DATE_INPUT_FORMATS = # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = diff --git a/django/conf/locale/fi/LC_MESSAGES/django.mo b/django/conf/locale/fi/LC_MESSAGES/django.mo index ea96fdac6dac..c3c7baa15460 100644 Binary files a/django/conf/locale/fi/LC_MESSAGES/django.mo and b/django/conf/locale/fi/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/fi/LC_MESSAGES/django.po b/django/conf/locale/fi/LC_MESSAGES/django.po index 90e0d40e29e5..4cef64f303e0 100644 --- a/django/conf/locale/fi/LC_MESSAGES/django.po +++ b/django/conf/locale/fi/LC_MESSAGES/django.po @@ -1,18 +1,19 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Aarni Koskela, 2015 +# Aarni Koskela, 2015,2017-2018 # Antti Kaihola , 2011 # Jannis Leidel , 2011 # Lasse Liehu , 2015 +# Mika Mäkelä , 2018 # Klaus Dahlén , 2011 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 00:44+0000\n" +"Last-Translator: Ramiro Morales\n" "Language-Team: Finnish (http://www.transifex.com/django/django/language/" "fi/)\n" "MIME-Version: 1.0\n" @@ -64,7 +65,7 @@ msgid "German" msgstr "saksa" msgid "Lower Sorbian" -msgstr "" +msgstr "Alasorbi" msgid "Greek" msgstr "kreikka" @@ -136,11 +137,14 @@ msgid "Croatian" msgstr "kroatia" msgid "Upper Sorbian" -msgstr "" +msgstr "Yläsorbi" msgid "Hungarian" msgstr "unkari" +msgid "Armenian" +msgstr "" + msgid "Interlingua" msgstr "interlingua" @@ -162,6 +166,9 @@ msgstr "japani" msgid "Georgian" msgstr "georgia" +msgid "Kabyle" +msgstr "Kabyle" + msgid "Kazakh" msgstr "kazakin kieli" @@ -199,7 +206,7 @@ msgid "Burmese" msgstr "burman kieli" msgid "Norwegian Bokmål" -msgstr "" +msgstr "norja (bokmål)" msgid "Nepali" msgstr "nepalin kieli" @@ -297,6 +304,15 @@ msgstr "Staattiset tiedostot" msgid "Syndication" msgstr "Syndikointi" +msgid "That page number is not an integer" +msgstr "Annettu sivunumero ei ole kokonaisluku" + +msgid "That page number is less than 1" +msgstr "Annettu sivunumero on alle 1" + +msgid "That page contains no results" +msgstr "Annetulla sivulla ei ole tuloksia" + msgid "Enter a valid value." msgstr "Syötä oikea arvo." @@ -309,6 +325,7 @@ msgstr "Syötä kelvollinen kokonaisluku." msgid "Enter a valid email address." msgstr "Syötä kelvollinen sähköpostiosoite." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -374,6 +391,9 @@ msgstr[1] "" "Varmista, että tämä arvo on enintään %(limit_value)d merkkiä pitkä (tällä " "hetkellä %(show_value)d)." +msgid "Enter a number." +msgstr "Syötä luku." + #, 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." @@ -396,6 +416,17 @@ msgstr[0] "" msgstr[1] "" "Tässä luvussa saa olla enintään %(max)s numeroa ennen desimaalipilkkua." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Pääte \"%(extension)s\" ei ole sallittu. Sallittuja päätteitä ovat " +"\"%(allowed_extensions)s\"." + +msgid "Null characters are not allowed." +msgstr "Tyhjiä merkkejä (null) ei sallita." + msgid "and" msgstr "ja" @@ -444,6 +475,10 @@ msgstr "Suuri (8-tavuinen) kokonaisluku" msgid "'%(value)s' value must be either True or False." msgstr "%(value)s-arvo pitää olla joko tosi tai epätosi." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "%(value)s-arvo pitää olla joko tosi, epätosi tai ei mitään." + msgid "Boolean (Either True or False)" msgstr "Totuusarvo: joko tosi (True) tai epätosi (False)" @@ -577,6 +612,9 @@ msgstr "Raaka binaaridata" msgid "'%(value)s' is not a valid UUID." msgstr "%(value)s ei ole kelvollinen UUID." +msgid "Universally unique identifier" +msgstr "" + msgid "File" msgstr "Tiedosto" @@ -595,11 +633,11 @@ msgstr "Yksi-yhteen relaatio" #, python-format msgid "%(from)s-%(to)s relationship" -msgstr "" +msgstr "%(from)s-%(to)s -suhde" #, python-format msgid "%(from)s-%(to)s relationships" -msgstr "" +msgstr "%(from)s-%(to)s -suhteet" msgid "Many-to-many relationship" msgstr "Moni-moneen relaatio" @@ -616,9 +654,6 @@ msgstr "Tämä kenttä vaaditaan." msgid "Enter a whole number." msgstr "Syötä kokonaisluku." -msgid "Enter a number." -msgstr "Syötä luku." - msgid "Enter a valid date." msgstr "Syötä oikea päivämäärä." @@ -631,6 +666,10 @@ msgstr "Syötä oikea pvm/kellonaika." msgid "Enter a valid duration." msgstr "Syötä oikea kesto." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Päivien määrä täytyy olla välillä {min_days} ja {max_days}." + msgid "No file was submitted. Check the encoding type on the form." msgstr "Tiedostoa ei lähetetty. Tarkista lomakkeen koodaus (encoding)." @@ -721,15 +760,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Korjaa allaolevat kaksoisarvot." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "Liittyvä perusavain ei vastannut vanhemman perusavainta." +msgid "The inline value did not match the parent instance." +msgstr "Liittyvä arvo ei vastannut vanhempaa instanssia." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Valitse oikea vaihtoehto. Valintasi ei löydy vaihtoehtojen joukosta." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" ei ole kelvollinen pääavainarvo." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" ei ole kelvollinen arvo." #, python-format msgid "" @@ -739,15 +778,15 @@ msgstr "" "%(datetime)s -arvoa ei pystytty lukemaan aikavyöhykkeellä " "%(current_timezone)s; se saattaa olla moniarvoinen tai määrittämätön." +msgid "Clear" +msgstr "Poista" + msgid "Currently" msgstr "Tällä hetkellä" msgid "Change" msgstr "Muokkaa" -msgid "Clear" -msgstr "Poista" - msgid "Unknown" msgstr "Tuntematon" @@ -1019,8 +1058,8 @@ msgstr "Tämä ei ole kelvollinen IPv6-osoite." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s…" +msgid "%(truncated_text)s…" +msgstr "" msgid "or" msgstr "tai" @@ -1094,6 +1133,19 @@ msgstr "" "hyvä ja kytke otsake takaisin päälle ainakin tälle sivulle, HTTPS-" "yhteyksille tai saman lähteen (\"same-origin\") pyynnöille." +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." +msgstr "" +"Jos käytät -tagia tai " +"\"Referrer-Policy: no-referrer\" -otsaketta, ole hyvä ja poista ne. CSRF-" +"suojaus vaatii Referer-otsakkeen tehdäkseen tarkan referer-tarkistuksen. Jos " +"vaadit yksityisyyttä, käytä vaihtoehtoja kuten linkittääksesi kolmannen osapuolen sivuille." + 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 " @@ -1114,33 +1166,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Lisätietoja `DEBUG=True`-konfiguraatioasetuksella." -msgid "Welcome to Django" -msgstr "Tervetuloa Djangoon" - -msgid "It worked!" -msgstr "Kappas, sehän toimi!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Onnittelut ensimmäisestä Django-sivustasi!" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Et toki ole vielä tehnyt yhtään oikeaa työtä. Seuraavaksi voit aloittaa " -"ensimmäisen sovelluksesi ajamalla python manage.py startapp " -"[sovelluksen_nimike]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Näet tämän viestin, koska asetuksissasi on DEBUG = True etkä " -"ole konfiguroinut yhtään URL-osoitetta. Töihin siitä!" - msgid "No year specified" msgstr "Vuosi puuttuu" +msgid "Date out of range" +msgstr "Päivämäärä ei alueella" + msgid "No month specified" msgstr "Kuukausi puuttuu" @@ -1191,3 +1222,46 @@ msgstr "\"%(path)s\" ei ole olemassa" #, python-format msgid "Index of %(directory)s" msgstr "Hakemistolistaus: %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Katso Django %(version)s julkaisutiedot" + +msgid "The install worked successfully! Congratulations!" +msgstr "Asennus toimi! Onneksi olkoon!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Näet tämän viestin, koska asetuksissasi on DEBUG = True etkä ole konfiguroinut yhtään URL-osoitetta." + +msgid "Django Documentation" +msgstr "Django-dokumentaatio" + +msgid "Topics, references, & how-to's" +msgstr "Aiheet, viittaukset & how-tot" + +msgid "Tutorial: A Polling App" +msgstr "Tutoriaali: kyselyapplikaatio" + +msgid "Get started with Django" +msgstr "Miten päästä alkuun Djangolla" + +msgid "Django Community" +msgstr "Django-yhteisö" + +msgid "Connect, get help, or contribute" +msgstr "Verkostoidu, saa apua tai jatkokehitä" diff --git a/django/conf/locale/fi/formats.py b/django/conf/locale/fi/formats.py index b5c74211a3a8..b6afe22f9ebd 100644 --- a/django/conf/locale/fi/formats.py +++ b/django/conf/locale/fi/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j. E Y' TIME_FORMAT = 'G.i' DATETIME_FORMAT = r'j. E Y \k\e\l\l\o G.i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d.%m.%Y', # '20.3.2014' '%d.%m.%y', # '20.3.14' diff --git a/django/conf/locale/fr/LC_MESSAGES/django.mo b/django/conf/locale/fr/LC_MESSAGES/django.mo index 43bde9f4f18a..92fc64e8c212 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 f1947e825fc8..fad7a32a592b 100644 --- a/django/conf/locale/fr/LC_MESSAGES/django.po +++ b/django/conf/locale/fr/LC_MESSAGES/django.po @@ -1,8 +1,8 @@ # This file is distributed under the same license as the Django package. # # Translators: -# charettes , 2012 -# Claude Paroz , 2013-2016 +# Simon Charette , 2012 +# Claude Paroz , 2013-2019 # Claude Paroz , 2011 # Jannis Leidel , 2011 # Jean-Baptiste Mora, 2014 @@ -12,8 +12,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-07-05 14:01+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 17:30+0000\n" "Last-Translator: Claude Paroz \n" "Language-Team: French (http://www.transifex.com/django/django/language/fr/)\n" "MIME-Version: 1.0\n" @@ -142,6 +142,9 @@ msgstr "Haut-sorabe" msgid "Hungarian" msgstr "Hongrois" +msgid "Armenian" +msgstr "Arménien" + msgid "Interlingua" msgstr "Interlingua" @@ -163,6 +166,9 @@ msgstr "Japonais" msgid "Georgian" msgstr "Géorgien" +msgid "Kabyle" +msgstr "Kabyle" + msgid "Kazakh" msgstr "Kazakh" @@ -298,6 +304,15 @@ msgstr "Fichiers statiques" msgid "Syndication" msgstr "Syndication" +msgid "That page number is not an integer" +msgstr "Ce numéro de page n'est pas un nombre entier" + +msgid "That page number is less than 1" +msgstr "Ce numéro de page est plus petit que 1" + +msgid "That page contains no results" +msgstr "Cette page ne contient aucun résultat" + msgid "Enter a valid value." msgstr "Saisissez une valeur valide." @@ -310,6 +325,7 @@ msgstr "Saisissez un nombre entier valide." msgid "Enter a valid email address." msgstr "Saisissez une adresse de courriel valide." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -379,6 +395,9 @@ msgstr[1] "" "Assurez-vous que cette valeur comporte au plus %(limit_value)d caractères " "(actuellement %(show_value)d)." +msgid "Enter a number." +msgstr "Saisissez un nombre." + #, 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." @@ -403,6 +422,17 @@ msgstr[0] "" msgstr[1] "" "Assurez-vous qu'il n'y a pas plus de %(max)s chiffres avant la virgule." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"L'extension de fichier « %(extension)s » n'est pas autorisée. Les extensions " +"autorisées sont : %(allowed_extensions)s." + +msgid "Null characters are not allowed." +msgstr "Le caractère nul n'est pas autorisé." + msgid "and" msgstr "et" @@ -451,6 +481,11 @@ msgstr "Grand entier (8 octets)" msgid "'%(value)s' value must be either True or False." msgstr "La valeur « %(value)s » doit être soit True (vrai), soit False (faux)." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "" +"La valeur « %(value)s » doit être True (vrai), False (faux) ou None (aucun)." + msgid "Boolean (Either True or False)" msgstr "Booléen (soit vrai ou faux)" @@ -590,6 +625,9 @@ msgstr "Données binaires brutes" msgid "'%(value)s' is not a valid UUID." msgstr "La valeur « %(value)s » n'est pas un UUID valide." +msgid "Universally unique identifier" +msgstr "Identifiant unique universel" + msgid "File" msgstr "Fichier" @@ -629,9 +667,6 @@ msgstr "Ce champ est obligatoire." msgid "Enter a whole number." msgstr "Saisissez un nombre entier." -msgid "Enter a number." -msgstr "Saisissez un nombre." - msgid "Enter a valid date." msgstr "Saisissez une date valide." @@ -644,6 +679,10 @@ msgstr "Saisissez une date et une heure valides." msgid "Enter a valid duration." msgstr "Saisissez une durée valide." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Le nombre de jours doit être entre {min_days} et {max_days}." + msgid "No file was submitted. Check the encoding type on the form." msgstr "" "Aucun fichier n'a été soumis. Vérifiez le type d'encodage du formulaire." @@ -740,10 +779,8 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Corrigez les valeurs à double ci-dessous." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "" -"La clé étrangère en ligne ne correspond pas à la clé primaire de l'instance " -"parente." +msgid "The inline value did not match the parent instance." +msgstr "La valeur en ligne ne correspond pas à l’instance parente." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" @@ -751,8 +788,8 @@ msgstr "" "disponibles." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "« %(pk)s » n'est pas une valeur correcte pour une clé primaire." +msgid "\"%(pk)s\" is not a valid value." +msgstr "« %(pk)s » n’est pas une valeur correcte." #, python-format msgid "" @@ -762,15 +799,15 @@ msgstr "" "La valeur %(datetime)s n'a pas pu être interprétée dans le fuseau horaire " "%(current_timezone)s ; elle est peut-être ambigüe ou elle n'existe pas." +msgid "Clear" +msgstr "Effacer" + msgid "Currently" msgstr "Actuellement" msgid "Change" msgstr "Modifier" -msgid "Clear" -msgstr "Effacer" - msgid "Unknown" msgstr "Inconnu" @@ -1042,7 +1079,7 @@ msgstr "Ceci n'est pas une adresse IPv6 valide." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." +msgid "%(truncated_text)s…" msgstr "%(truncated_text)s…" msgid "or" @@ -1118,6 +1155,20 @@ msgstr "" "connexions HTTPS, ou encore pour les requêtes de même origine (« 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." +msgstr "" +"Si vous utilisez la balise " +"ou que vous incluez l’en-tête « Referrer-Policy: no-referrer », il est " +"préférable de les enlever. La protection CSRF exige que l’en-tête " +"``Referer`` effectue un contrôle de référant strict. Si vous vous souciez de " +"la confidentialité, utilisez des alternatives comme pour les liens vers des sites tiers." + 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 " @@ -1141,34 +1192,12 @@ msgstr "" "Des informations plus détaillées sont affichées lorsque la variable DEBUG " "vaut True." -msgid "Welcome to Django" -msgstr "Bienvenue dans Django" - -msgid "It worked!" -msgstr "Ça fonctionne !" - -msgid "Congratulations on your first Django-powered page." -msgstr "Félicitations pour votre première page produite par Django." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Naturellement, il ne s'agit pas encore d'un résultat utilisable. La " -"prochaine étape est de créer une application en exécutant python " -"manage.py startapp [nom_app]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Vous voyez ce message car votre fichier de réglages Django contient " -"DEBUG = True et que vous n'avez pas encore configuré d'URL. Au " -"travail !" - msgid "No year specified" msgstr "Aucune année indiquée" +msgid "Date out of range" +msgstr "Date hors limites" + msgid "No month specified" msgstr "Aucun mois indiqué" @@ -1223,3 +1252,48 @@ msgstr "« %(path)s » n'existe pas" #, python-format msgid "Index of %(directory)s" msgstr "Index de %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django : le cadriciel Web pour les perfectionnistes sous contrainte." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Afficher les notes de publication de " +"Django %(version)s" + +msgid "The install worked successfully! Congratulations!" +msgstr "L’installation s'est déroulée avec succès. Félicitations !" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Vous voyez cette page parce que votre fichier de réglages contient DEBUG=True et que vous n’avez pas encore " +"configuré d’URL." + +msgid "Django Documentation" +msgstr "Documentation de Django" + +msgid "Topics, references, & how-to's" +msgstr "Thématiques, références et guides pratiques" + +msgid "Tutorial: A Polling App" +msgstr "Tutoriel : une application de sondage" + +msgid "Get started with Django" +msgstr "Premiers pas avec Django" + +msgid "Django Community" +msgstr "Communauté Django" + +msgid "Connect, get help, or contribute" +msgstr "Se connecter, obtenir de l’aide ou contribuer" diff --git a/django/conf/locale/fr/formats.py b/django/conf/locale/fr/formats.py index ed6d8c7aaaa6..557c3885b0c6 100644 --- a/django/conf/locale/fr/formats.py +++ b/django/conf/locale/fr/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j F Y' TIME_FORMAT = 'H:i' DATETIME_FORMAT = 'j F Y H:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d/%m/%Y', '%d/%m/%y', # '25/10/2006', '25/10/06' '%d.%m.%Y', '%d.%m.%y', # Swiss [fr_CH), '25.10.2006', '25.10.06' diff --git a/django/conf/locale/fy/LC_MESSAGES/django.mo b/django/conf/locale/fy/LC_MESSAGES/django.mo index 586f7d40860a..258b89171fdb 100644 Binary files a/django/conf/locale/fy/LC_MESSAGES/django.mo and b/django/conf/locale/fy/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/fy/LC_MESSAGES/django.po b/django/conf/locale/fy/LC_MESSAGES/django.po index b9636cb9274a..35c00bbbf8c2 100644 --- a/django/conf/locale/fy/LC_MESSAGES/django.po +++ b/django/conf/locale/fy/LC_MESSAGES/django.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Western Frisian (http://www.transifex.com/django/django/" "language/fy/)\n" @@ -293,6 +293,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "Jou in falide wearde." @@ -305,6 +314,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -380,6 +390,15 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "" @@ -691,7 +710,7 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "" -msgid "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." @@ -699,7 +718,7 @@ msgstr "" "Selektearje in falide kar. Dizze kar is net ien fan de beskikbere karren." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -708,13 +727,13 @@ msgid "" "may be ambiguous or it may not exist." msgstr "" -msgid "Currently" +msgid "Clear" msgstr "" -msgid "Change" +msgid "Currently" msgstr "" -msgid "Clear" +msgid "Change" msgstr "" msgid "Unknown" @@ -1056,6 +1075,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1070,26 +1097,10 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" +msgid "No year specified" msgstr "" -msgid "No year specified" +msgid "Date out of range" msgstr "" msgid "No month specified" @@ -1140,3 +1151,41 @@ msgstr "" #, python-format msgid "Index of %(directory)s" msgstr "" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/fy/formats.py b/django/conf/locale/fy/formats.py index 330c2f296e08..3825be444501 100644 --- a/django/conf/locale/fy/formats.py +++ b/django/conf/locale/fy/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date # DATE_FORMAT = # TIME_FORMAT = # DATETIME_FORMAT = @@ -15,7 +12,7 @@ # FIRST_DAY_OF_WEEK = # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # DATE_INPUT_FORMATS = # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = diff --git a/django/conf/locale/ga/LC_MESSAGES/django.mo b/django/conf/locale/ga/LC_MESSAGES/django.mo index 71513ea3e183..e37c64177103 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 d3daf0a32982..e3481243956f 100644 --- a/django/conf/locale/ga/LC_MESSAGES/django.po +++ b/django/conf/locale/ga/LC_MESSAGES/django.po @@ -11,8 +11,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Irish (http://www.transifex.com/django/django/language/ga/)\n" "MIME-Version: 1.0\n" @@ -298,6 +298,15 @@ msgstr "Comhaid Statach" msgid "Syndication" msgstr "Sindeacáitiú" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "Iontráil luach bailí" @@ -310,6 +319,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -403,6 +413,15 @@ msgstr[2] "" msgstr[3] "" msgstr[4] "" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "agus" @@ -726,16 +745,14 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Le do thoil ceartaigh na luachanna dúbail thíos." -msgid "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." msgstr "" -"Ní raibh an eochair eachtrach comhoiriúnach leis an tuismitheoir ásc príomh-" -"eochair." 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 for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -746,15 +763,15 @@ msgstr "" "Ní féidir an %(datetime)s a léirmhíniú i gcrios ama %(current_timezone)s; " "B'fhéidir go bhfuil sé débhríoch nó nach bhfuil sé ann." +msgid "Clear" +msgstr "Glan" + msgid "Currently" msgstr "Faoi láthair" msgid "Change" msgstr "Athraigh" -msgid "Clear" -msgstr "Glan" - msgid "Unknown" msgstr "Anaithnid" @@ -1115,6 +1132,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1129,28 +1154,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Tá tuilleadh eolais ar fáil le DEBUG=True." -msgid "Welcome to Django" -msgstr "Fáilte go Django" - -msgid "It worked!" -msgstr "D'oibrigh sé!" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "Bliain gan sonrú" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "Mí gan sonrú" @@ -1203,3 +1212,41 @@ 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 "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/ga/formats.py b/django/conf/locale/ga/formats.py index b3b19740767c..eb3614abd91c 100644 --- a/django/conf/locale/ga/formats.py +++ b/django/conf/locale/ga/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j F Y' TIME_FORMAT = 'H:i' # DATETIME_FORMAT = @@ -15,7 +12,7 @@ # FIRST_DAY_OF_WEEK = # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # DATE_INPUT_FORMATS = # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = diff --git a/django/conf/locale/gd/LC_MESSAGES/django.mo b/django/conf/locale/gd/LC_MESSAGES/django.mo index 1d5abef58dc6..953763451238 100644 Binary files a/django/conf/locale/gd/LC_MESSAGES/django.mo and b/django/conf/locale/gd/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/gd/LC_MESSAGES/django.po b/django/conf/locale/gd/LC_MESSAGES/django.po index d53e1886c4fa..103853e5d5f3 100644 --- a/django/conf/locale/gd/LC_MESSAGES/django.po +++ b/django/conf/locale/gd/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ # # Translators: # Michael Bauer, 2014 -# GunChleoc, 2015-2016 +# GunChleoc, 2015-2017 # GunChleoc, 2015 # GunChleoc, 2014-2015 # Michael Bauer, 2014 @@ -10,8 +10,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-07-03 09:57+0000\n" +"POT-Creation-Date: 2018-05-17 11:49+0200\n" +"PO-Revision-Date: 2018-05-29 09:31+0000\n" "Last-Translator: GunChleoc\n" "Language-Team: Gaelic, Scottish (http://www.transifex.com/django/django/" "language/gd/)\n" @@ -163,6 +163,9 @@ msgstr "Seapanais" msgid "Georgian" msgstr "Cairtbheilis" +msgid "Kabyle" +msgstr "Kabyle" + msgid "Kazakh" msgstr "Casachais" @@ -298,6 +301,15 @@ msgstr "Faidhlichean stadastaireachd" msgid "Syndication" msgstr "Siondacaideadh" +msgid "That page number is not an integer" +msgstr "Chan eil àireamh na duilleige seo 'na àireamh slàn" + +msgid "That page number is less than 1" +msgstr "Tha àireamh na duilleige seo nas lugha na 1" + +msgid "That page contains no results" +msgstr "Chan eil toradh aig an duilleag seo" + msgid "Enter a valid value." msgstr "Cuir a-steach luach dligheach." @@ -310,6 +322,7 @@ msgstr "Cuir a-steach àireamh slàin dhligheach." msgid "Enter a valid email address." msgstr "Cuir a-steach seòladh puist-d dligheach." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -393,6 +406,9 @@ msgstr[3] "" "Dèan cinnteach gu bheil %(limit_value)d caractar aig an luach seo air a’ " "char as motha (tha %(show_value)d aige an-dràsta)." +msgid "Enter a number." +msgstr "Cuir a-steach àireamh." + #, 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." @@ -431,6 +447,17 @@ msgstr[3] "" "Dèan cinnteach nach eil barrachd air %(max)s àireamh ann ron phuing " "dheicheach." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Chan eil an leudachan faidhle \"%(extension)s\" ceadaichte. Seo na " +"leudachain a tha ceadaichte: \"%(allowed_extensions)s\"." + +msgid "Null characters are not allowed." +msgstr "Chan eil caractaran null ceadaichte." + msgid "and" msgstr "agus" @@ -479,6 +506,10 @@ msgstr "Mòr-àireamh shlàn (8 baidht)" msgid "'%(value)s' value must be either True or False." msgstr "Feumaidh “%(value)s” a bhith True no False." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "Feumaidh “%(value)s” a bhith True, False no None." + msgid "Boolean (Either True or False)" msgstr "Booleach (True no False)" @@ -657,9 +688,6 @@ msgstr "Tha an raon seo riatanach." msgid "Enter a whole number." msgstr "Cuir a-steach àireamh shlàn." -msgid "Enter a number." -msgstr "Cuir a-steach àireamh." - msgid "Enter a valid date." msgstr "Cuir a-steach ceann-là dligheach." @@ -672,6 +700,11 @@ msgstr "Cuir a-steach ceann-là ’s àm dligheach." msgid "Enter a valid duration." msgstr "Cuir a-steach faid dhligheach." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "" +"Feumaidh an àireamh de làithean a bhith eadar {min_days} is {max_days}." + msgid "No file was submitted. Check the encoding type on the form." msgstr "" "Cha deach faidhle a chur a-null. Dearbhaich seòrsa a’ chòdachaidh air an " @@ -779,17 +812,16 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Ceartaich na luachan dùblaichte gu h-ìosal." -msgid "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." msgstr "" -"Chan eil an iuchair chèin am broinn na loidhne a’ freagairt ri prìomh-" -"iuchair an ionstans-pàraint." +"Chan eil an luach am broinn na loidhne a’ freagairt ris an ionstans-pàraint." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Tagh rud dligheach. Chan eil an rud seo ’na roghainn dhut." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "Chan e luach dligheach a tha ann an “%(pk)s” airson prìomh-iuchair." +msgid "\"%(pk)s\" is not a valid value." +msgstr "Chan e luach dligheach a tha ann an “%(pk)s”." #, python-format msgid "" @@ -799,15 +831,15 @@ msgstr "" "Cha chiall dha %(datetime)s san roinn-tìde %(current_timezone)s; dh’fhaoidte " "gu bheil e dà-sheaghach no nach eil e ann." +msgid "Clear" +msgstr "Falamhaich" + msgid "Currently" msgstr "An-dràsta" msgid "Change" msgstr "Atharraich" -msgid "Clear" -msgstr "Falamhaich" - msgid "Unknown" msgstr "Chan eil fhios" @@ -1169,6 +1201,20 @@ msgstr "" "comas, cuir an comas iad a-rithist, co-dhiù airson na làraich seo no airson " "ceanglaichean HTTPS no airson iarrtasan “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." +msgstr "" +"Ma tha thu a’ cleachdadh taga no a’ gabhail a-staigh bann-cinn “'Referrer-Policy: no-referrer” feuch " +"an doir thu air falbh iad. Iarraidh an dìon CSRF bann-cinn “Referer” gus na " +"referers a dhearbhadh gu teann. Ma tha thu iomagaineach a thaobh do " +"prìobhaideachd, cleachd roghainnean eile mar " +"airson ceangal gu làraichean-lìn threas-phàrtaidhean." + 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 " @@ -1190,34 +1236,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Gheibh thu barrachd fiosrachaidh le DEBUG=True." -msgid "Welcome to Django" -msgstr "Fàilte gu Django" - -msgid "It worked!" -msgstr "Dh’obraich e!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Meal do naidheachd gu bheil a’ chiad duilleag le cumhachd Django agad." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Gu nàdarra, cha do rinn thu obair dha-rìribh fhathast. Nise, tòisich a’ " -"chiad aplacaid agad ’s tu a’ ruith python manage.py aplacaid_toisich " -"[leubail_aplacaid]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Chì thu an teachdaireachd seo air sgàth ’s gu bheil DEBUG = True ann am faidhle nan roghainnean Django agad agus cha do rèitich thu URL " -"sam bith fhathast. Siuthad is rèitich fear!" - msgid "No year specified" msgstr "Cha deach bliadhna a shònrachadh" +msgid "Date out of range" +msgstr "Tha ceann-là taobh thar na rainse" + msgid "No month specified" msgstr "Cha deach mìos a shònrachadh" @@ -1272,3 +1296,48 @@ msgstr "Chan eil “%(path)s” ann" #, python-format msgid "Index of %(directory)s" msgstr "Clàr-amais dhe %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: am frèam-obrach-lìn leis a choileanas foirfichean cinn-ama." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Seall na nòtaichean sgaoilidh airson Django " +"%(version)s" + +msgid "The install worked successfully! Congratulations!" +msgstr "Chaidh a stàladh! Meal do naidheachd!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Chì thu an duilleag seo on a tha DEBUG=True ann am faidhle nan roghainnean agad agus cha do rèitich " +"thu URL sam bith fhathast." + +msgid "Django Documentation" +msgstr "Docamaideadh Django" + +msgid "Topics, references, & how-to's" +msgstr "Cuspairean, iomraidhean ⁊ treòirichean" + +msgid "Tutorial: A Polling App" +msgstr "Oideachadh: Aplacaid cunntais-bheachd" + +msgid "Get started with Django" +msgstr "Dèan toiseach-tòiseachaidh le Django" + +msgid "Django Community" +msgstr "Coimhearsnachd Django" + +msgid "Connect, get help, or contribute" +msgstr "Dèan ceangal, faigh taic no cuidich" diff --git a/django/conf/locale/gd/formats.py b/django/conf/locale/gd/formats.py index 0eac9726690f..19b42ee015bd 100644 --- a/django/conf/locale/gd/formats.py +++ b/django/conf/locale/gd/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j F Y' TIME_FORMAT = 'h:ia' DATETIME_FORMAT = 'j F Y h:ia' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # DATE_INPUT_FORMATS = # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = diff --git a/django/conf/locale/gl/LC_MESSAGES/django.mo b/django/conf/locale/gl/LC_MESSAGES/django.mo index 192264dcb3c5..1845798e6674 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 201d68c1526f..efdfd3888142 100644 --- a/django/conf/locale/gl/LC_MESSAGES/django.po +++ b/django/conf/locale/gl/LC_MESSAGES/django.po @@ -4,15 +4,16 @@ # fasouto , 2011-2012 # fonso , 2011,2013 # fonso , 2013 +# fasouto , 2017 # Jannis Leidel , 2011 # Leandro Regueiro , 2013 -# Oscar Carballal , 2012 +# Oscar Carballal , 2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Galician (http://www.transifex.com/django/django/language/" "gl/)\n" @@ -29,7 +30,7 @@ msgid "Arabic" msgstr "Árabe" msgid "Asturian" -msgstr "" +msgstr "Asturiano" msgid "Azerbaijani" msgstr "azerí" @@ -59,7 +60,7 @@ msgid "Welsh" msgstr "Galés" msgid "Danish" -msgstr "dinamarqués" +msgstr "Dinamarqués" msgid "German" msgstr "Alemán" @@ -74,7 +75,7 @@ msgid "English" msgstr "Inglés" msgid "Australian English" -msgstr "" +msgstr "Inglés australiano" msgid "British English" msgstr "inglés británico" @@ -116,7 +117,7 @@ msgid "French" msgstr "Francés" msgid "Frisian" -msgstr "frisón" +msgstr "Frisón" msgid "Irish" msgstr "irlandés" @@ -155,7 +156,7 @@ msgid "Icelandic" msgstr "islandés" msgid "Italian" -msgstr "italiano" +msgstr "Italiano" msgid "Japanese" msgstr "xaponés" @@ -173,7 +174,7 @@ msgid "Kannada" msgstr "canará" msgid "Korean" -msgstr "coreano" +msgstr "Coreano" msgid "Luxembourgish" msgstr "luxemburgués" @@ -298,6 +299,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "Insira un valor válido." @@ -310,6 +320,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "Insira un enderezo de correo electrónico válido." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -369,8 +380,8 @@ msgstr[1] "" #, 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[0] "Asegure que non hai mais de %(max)s díxito en total." +msgstr[1] "Asegure que non hai mais de %(max)s díxitos en total." #, python-format msgid "Ensure that there are no more than %(max)s decimal place." @@ -386,6 +397,15 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "e" @@ -395,7 +415,7 @@ msgstr "" #, python-format msgid "Value %(value)r is not a valid choice." -msgstr "" +msgstr "O valor %(value)r non é unha opción válida." msgid "This field cannot be null." msgstr "Este campo non pode ser nulo." @@ -652,7 +672,7 @@ msgid "Enter a complete value." msgstr "" msgid "Enter a valid UUID." -msgstr "" +msgstr "Insira un UUID válido." #. Translators: This is the default suffix added to form field labels msgid ":" @@ -702,9 +722,8 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Corrixa os valores duplicados de abaixo." -msgid "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." msgstr "" -"A clave estranxeira en liña non coincide coa clave primaria da instancia nai." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" @@ -712,7 +731,7 @@ msgstr "" "dispoñíbeis" #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -723,15 +742,15 @@ msgstr "" "%(datetime)s non se puido interpretar na zona hora horaria " "%(current_timezone)s; pode ser ambiguo ou non existir." +msgid "Clear" +msgstr "Limpar" + msgid "Currently" msgstr "Actualmente" msgid "Change" msgstr "Modificar" -msgid "Clear" -msgstr "Limpar" - msgid "Unknown" msgstr "Descoñecido" @@ -1071,6 +1090,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1083,30 +1110,14 @@ msgid "" msgstr "" msgid "More information is available with DEBUG=True." -msgstr "" - -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" +msgstr "Pode ver máis información se establece DEBUG=True." msgid "No year specified" msgstr "Non se especificou ningún ano" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "Non se especificou ningún mes" @@ -1157,3 +1168,41 @@ msgstr "\"%(path)s\" non existe" #, python-format msgid "Index of %(directory)s" msgstr "Índice de %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/gl/formats.py b/django/conf/locale/gl/formats.py index 996b8cd6b4f5..9f29c239dfc8 100644 --- a/django/conf/locale/gl/formats.py +++ b/django/conf/locale/gl/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = r'j \d\e F \d\e Y' TIME_FORMAT = 'H:i' DATETIME_FORMAT = r'j \d\e F \d\e Y \á\s H:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # DATE_INPUT_FORMATS = # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = diff --git a/django/conf/locale/he/LC_MESSAGES/django.mo b/django/conf/locale/he/LC_MESSAGES/django.mo index d425cfa2a8bd..cc04701d3659 100644 Binary files a/django/conf/locale/he/LC_MESSAGES/django.mo and b/django/conf/locale/he/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/he/LC_MESSAGES/django.po b/django/conf/locale/he/LC_MESSAGES/django.po index ea9829b7ca4e..4dcccb45fb62 100644 --- a/django/conf/locale/he/LC_MESSAGES/django.po +++ b/django/conf/locale/he/LC_MESSAGES/django.po @@ -3,20 +3,21 @@ # Translators: # Alex Gaynor , 2011-2012 # Jannis Leidel , 2011 -# Meir Kriheli , 2011-2015 +# Meir Kriheli , 2011-2015,2017,2019 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-03-19 16:32+0000\n" +"Last-Translator: Meir Kriheli \n" "Language-Team: Hebrew (http://www.transifex.com/django/django/language/he/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: he\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % " +"1 == 0) ? 1: (n % 10 == 0 && n % 1 == 0 && n > 10) ? 2 : 3;\n" msgid "Afrikaans" msgstr "אפריקאנס" @@ -61,7 +62,7 @@ msgid "German" msgstr "גרמנית" msgid "Lower Sorbian" -msgstr "" +msgstr "סורבית תחתונה" msgid "Greek" msgstr "יוונית" @@ -133,11 +134,14 @@ msgid "Croatian" msgstr "קרואטית" msgid "Upper Sorbian" -msgstr "" +msgstr "סורבית עילית" msgid "Hungarian" msgstr "הונגרית" +msgid "Armenian" +msgstr "ארמנית" + msgid "Interlingua" msgstr "אינטרלינגואה" @@ -159,6 +163,9 @@ msgstr "יפנית" msgid "Georgian" msgstr "גיאורגית" +msgid "Kabyle" +msgstr "קבילה" + msgid "Kazakh" msgstr "קזחית" @@ -196,7 +203,7 @@ msgid "Burmese" msgstr "בּוּרְמֶזִית" msgid "Norwegian Bokmål" -msgstr "" +msgstr "נורבגית ספרותית" msgid "Nepali" msgstr "נפאלית" @@ -294,6 +301,15 @@ msgstr "קבצים סטטיים" msgid "Syndication" msgstr "הפצת תכנים" +msgid "That page number is not an integer" +msgstr "מספר העמוד אינו מספר שלם" + +msgid "That page number is less than 1" +msgstr "מספר העמוד קטן מ־1" + +msgid "That page contains no results" +msgstr "עמוד זה אינו מכיל תוצאות" + msgid "Enter a valid value." msgstr "יש להזין ערך חוקי." @@ -306,6 +322,7 @@ msgstr "יש להזין מספר שלם חוקי." msgid "Enter a valid email address." msgstr "נא להזין כתובת דוא\"ל חוקית" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "יש להזין ערך המכיל אותיות, ספרות, קווים תחתונים ומקפים בלבד." @@ -351,6 +368,10 @@ msgstr[0] "" "נא לוודא שערך זה מכיל תו %(limit_value)d לכל הפחות (מכיל %(show_value)d)." msgstr[1] "" "נא לוודא שערך זה מכיל %(limit_value)d תווים לכל הפחות (מכיל %(show_value)d)." +msgstr[2] "" +"נא לוודא שערך זה מכיל %(limit_value)d תווים לכל הפחות (מכיל %(show_value)d)." +msgstr[3] "" +"נא לוודא שערך זה מכיל %(limit_value)d תווים לכל הפחות (מכיל %(show_value)d)." #, python-format msgid "" @@ -363,18 +384,29 @@ msgstr[0] "" "נא לוודא שערך זה מכיל תו %(limit_value)d לכל היותר (מכיל %(show_value)d)." msgstr[1] "" "נא לוודא שערך זה מכיל %(limit_value)d תווים לכל היותר (מכיל %(show_value)d)." +msgstr[2] "" +"נא לוודא שערך זה מכיל %(limit_value)d תווים לכל היותר (מכיל %(show_value)d)." +msgstr[3] "" +"נא לוודא שערך זה מכיל %(limit_value)d תווים לכל היותר (מכיל %(show_value)d)." + +msgid "Enter a number." +msgstr "נא להזין מספר." #, 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] "נא לוודא שאין יותר מספרה %(max)s בסה\"כ." msgstr[1] "נא לוודא שאין יותר מ־%(max)s ספרות בסה\"כ." +msgstr[2] "נא לוודא שאין יותר מ־%(max)s ספרות בסה\"כ." +msgstr[3] "נא לוודא שאין יותר מ־%(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[1] "נא לוודא שאין יותר מ־%(max)s ספרות אחרי הנקודה." +msgstr[2] "נא לוודא שאין יותר מ־%(max)s ספרות אחרי הנקודה." +msgstr[3] "נא לוודא שאין יותר מ־%(max)s ספרות אחרי הנקודה." #, python-format msgid "" @@ -383,6 +415,19 @@ msgid_plural "" "Ensure that there are no more than %(max)s digits before the decimal point." msgstr[0] "נא לוודא שאין יותר מספרה %(max)s לפני הנקודה העשרונית" msgstr[1] "נא לוודא שאין יותר מ־%(max)s ספרות לפני הנקודה העשרונית" +msgstr[2] "נא לוודא שאין יותר מ־%(max)s ספרות לפני הנקודה העשרונית" +msgstr[3] "נא לוודא שאין יותר מ־%(max)s ספרות לפני הנקודה העשרונית" + +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"סיומת הקובץ '%(extension)s' אסורה. הסיומות המותרות הן: " +"'%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "תווי NULL אינם מותרים. " msgid "and" msgstr "ו" @@ -431,6 +476,10 @@ msgstr "מספר שלם גדול (8 בתים)" msgid "'%(value)s' value must be either True or False." msgstr "הערך '%(value)s' חייב להיות אמת או שקר." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "'%(value)s' חייב להיות אחד מ־True‏, False, או None." + msgid "Boolean (Either True or False)" msgstr "בוליאני (אמת או שקר)" @@ -563,6 +612,9 @@ msgstr "מידע בינארי גולמי" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' אינו UUID חוקי." +msgid "Universally unique identifier" +msgstr "מזהה ייחודי אוניברסלי" + msgid "File" msgstr "קובץ" @@ -581,11 +633,11 @@ msgstr "יחס של אחד לאחד" #, python-format msgid "%(from)s-%(to)s relationship" -msgstr "" +msgstr "קשר %(from)s-%(to)s" #, python-format msgid "%(from)s-%(to)s relationships" -msgstr "" +msgstr "קשרי %(from)s-%(to)s" msgid "Many-to-many relationship" msgstr "יחס של רבים לרבים" @@ -602,9 +654,6 @@ msgstr "יש להזין תוכן בשדה זה." msgid "Enter a whole number." msgstr "נא להזין מספר שלם." -msgid "Enter a number." -msgstr "נא להזין מספר." - msgid "Enter a valid date." msgstr "יש להזין תאריך חוקי." @@ -617,6 +666,10 @@ msgstr "יש להזין תאריך ושעה חוקיים." msgid "Enter a valid duration." msgstr "יש להזין משך חוקי." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "מספר הימים חייב להיות בין {min_days} ל־{max_days}." + msgid "No file was submitted. Check the encoding type on the form." msgstr "לא נשלח שום קובץ. נא לבדוק את סוג הקידוד של הטופס." @@ -633,6 +686,10 @@ msgid_plural "" msgstr[0] "נא לוודא ששם קובץ זה מכיל תו %(max)d לכל היותר (מכיל %(length)d)." msgstr[1] "" "נא לוודא ששם קובץ זה מכיל %(max)d תווים לכל היותר (מכיל %(length)d)." +msgstr[2] "" +"נא לוודא ששם קובץ זה מכיל %(max)d תווים לכל היותר (מכיל %(length)d)." +msgstr[3] "" +"נא לוודא ששם קובץ זה מכיל %(max)d תווים לכל היותר (מכיל %(length)d)." msgid "Please either submit a file or check the clear checkbox, not both." msgstr "נא לשים קובץ או סימן את התיבה לניקוי, לא שניהם." @@ -671,12 +728,16 @@ msgid "Please submit %d or fewer forms." msgid_plural "Please submit %d or fewer forms." msgstr[0] "נא לשלוח טופס %d לכל היותר." msgstr[1] "נא לשלוח %d טפסים לכל היותר." +msgstr[2] "נא לשלוח %d טפסים לכל היותר." +msgstr[3] "נא לשלוח %d טפסים לכל היותר." #, python-format msgid "Please submit %d or more forms." msgid_plural "Please submit %d or more forms." msgstr[0] "נא לשלוח טופס %d או יותר." msgstr[1] "נא לשלוח %d טפסים או יותר." +msgstr[2] "נא לשלוח %d טפסים או יותר." +msgstr[3] "נא לשלוח %d טפסים או יותר." msgid "Order" msgstr "מיון" @@ -703,15 +764,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "נא לתקן את הערכים הכפולים למטה." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "המפתח הזר ה־inline לא התאים למפתח הראשי של האב." +msgid "The inline value did not match the parent instance." +msgstr "הערך הפנימי אינו תואם לאב." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "יש לבחור אפשרות חוקית; אפשרות זו אינה אחת מהזמינות." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" אינו ערך חוקי עבור מפתח ראשי." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" אינו ערך חוקי." #, python-format msgid "" @@ -721,15 +782,15 @@ msgstr "" "לא ניתן לפרש את %(datetime)s באזור זמן %(current_timezone)s; הוא עשוי להיות " "דו-משמעי או לא קיים." +msgid "Clear" +msgstr "לסלק" + msgid "Currently" msgstr "עכשיו" msgid "Change" msgstr "שינוי" -msgid "Clear" -msgstr "לסלק" - msgid "Unknown" msgstr "לא ידוע" @@ -747,6 +808,8 @@ msgid "%(size)d byte" msgid_plural "%(size)d bytes" msgstr[0] "בית %(size)d " msgstr[1] "%(size)d בתים" +msgstr[2] "%(size)d בתים" +msgstr[3] "%(size)d בתים" #, python-format msgid "%s KB" @@ -1001,8 +1064,8 @@ msgstr "זו אינה כתובת IPv6 חוקית." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s‮…" msgid "or" msgstr "או" @@ -1016,36 +1079,48 @@ msgid "%d year" msgid_plural "%d years" msgstr[0] "שנה %d" msgstr[1] "%d שנים" +msgstr[2] "%d שנים" +msgstr[3] "%d שנים" #, python-format msgid "%d month" msgid_plural "%d months" msgstr[0] "חודש %d" msgstr[1] "%d חודשים" +msgstr[2] "%d חודשים" +msgstr[3] "%d חודשים" #, python-format msgid "%d week" msgid_plural "%d weeks" msgstr[0] "שבוע %d" msgstr[1] "%d שבועות" +msgstr[2] "%d שבועות" +msgstr[3] "%d שבועות" #, python-format msgid "%d day" msgid_plural "%d days" msgstr[0] "יום %d" msgstr[1] "%d ימים" +msgstr[2] "%d ימים" +msgstr[3] "%d ימים" #, python-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "שעה %d" msgstr[1] "%d שעות" +msgstr[2] "%d שעות" +msgstr[3] "%d שעות" #, python-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "דקה %d" msgstr[1] "%d דקות" +msgstr[2] "%d דקות" +msgstr[3] "%d דקות" msgid "0 minutes" msgstr "0 דקות" @@ -1074,6 +1149,18 @@ msgstr "" "אם הגדרת את הדפדפן שלך לביטול ‎ 'Referer' headers, נא לאפשר אותם, לפחות עבור " "אתר זה, לחיבורי 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." +msgstr "" +"אם תג בשימוש או header‏ " +"'Referrer-Policy: no-referrer', נא להסיר אותם. הגנת ה־CSRF דורשת את ה־" +"header‏ 'Referer' כדי לבצע בדיקה מפנה מדוקדקת. במקרה של דאגה לפרטיות, יש " +"להשתמש בתחליפים כמו עבור קישורים לאתרים צד ג'." + 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 " @@ -1092,32 +1179,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "מידע נוסף זמין עם " -msgid "Welcome to Django" -msgstr "ברוכים הבאים אל Django" - -msgid "It worked!" -msgstr "זה עבד!" - -msgid "Congratulations on your first Django-powered page." -msgstr "ברכות על העמוד מבוסס Django הראשון שלך." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"ברור שלא עשית עדיין כלום. בשלב הבא יש להתחיל את היישום הראשון שלך ע\"י הרצת " -"python manage.py startapp [app_label]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"הודעה זו מופיעה בגלל שיש לך DEBUG = True בקובץ הגדרות ה-Django " -"שלך ולא הגדרת URLs. להפשיל שרוולים ולגשת למלאכה." - msgid "No year specified" msgstr "לא צויינה שנה" +msgid "Date out of range" +msgstr "תאריך מחוץ לטווח" + msgid "No month specified" msgstr "לא צויין חודש" @@ -1168,3 +1235,46 @@ 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." +msgstr "" +"עמוד זה מופיע בעקבות המצאות DEBUG=True בקובץ ההגדרות שלך ולא הגדרת שום URLs." + +msgid "Django Documentation" +msgstr "תיעוד Django" + +msgid "Topics, references, & how-to's" +msgstr "נושאים, הפניות ומדריכים" + +msgid "Tutorial: A Polling App" +msgstr "מדריך ללומד: יישום לסקרים." + +msgid "Get started with Django" +msgstr "התחילו לעבוד עם Django" + +msgid "Django Community" +msgstr "קהילת Django" + +msgid "Connect, get help, or contribute" +msgstr "יצירת קשר, קבלת עזרה או השתתפות" diff --git a/django/conf/locale/he/formats.py b/django/conf/locale/he/formats.py index 274996f3a048..23145654429d 100644 --- a/django/conf/locale/he/formats.py +++ b/django/conf/locale/he/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j בF Y' TIME_FORMAT = 'H:i' DATETIME_FORMAT = 'j בF Y H:i' @@ -15,7 +12,7 @@ # FIRST_DAY_OF_WEEK = # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # DATE_INPUT_FORMATS = # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = diff --git a/django/conf/locale/hi/LC_MESSAGES/django.mo b/django/conf/locale/hi/LC_MESSAGES/django.mo index b27a42fb82a3..e9469313e000 100644 Binary files a/django/conf/locale/hi/LC_MESSAGES/django.mo and b/django/conf/locale/hi/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/hi/LC_MESSAGES/django.po b/django/conf/locale/hi/LC_MESSAGES/django.po index 57d1a442bf47..6a4946486e5a 100644 --- a/django/conf/locale/hi/LC_MESSAGES/django.po +++ b/django/conf/locale/hi/LC_MESSAGES/django.po @@ -9,8 +9,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Hindi (http://www.transifex.com/django/django/language/hi/)\n" "MIME-Version: 1.0\n" @@ -295,6 +295,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "एक मान्य मूल्य दर्ज करें" @@ -307,6 +316,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "वैध डाक पता प्रविष्ट करें।" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "एक वैध 'काउंटर' वर्णों, संख्याओं,रेखांकित चिन्ह ,या हाइफ़न से मिलाकर दर्ज करें ।" @@ -382,6 +392,15 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "और" @@ -690,14 +709,14 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "कृपया डुप्लिकेट मानों को सही करें." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "इनलाइन विदेशी कुंजी पैरेंट आवृत्ति प्राथमिक कुंजी से मेल नहीं खाता है ." +msgid "The inline value did not match the parent instance." +msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "मान्य विकल्प चयन करें । यह विकल्प उपस्थित विकल्पों में नहीं है ।" #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -708,15 +727,15 @@ msgstr "" "%(current_timezone)s समय क्षेत्र में %(datetime)s का व्याख्या नहीं कर सकता है, यह " "अस्पष्ट हो सकता है या नहीं मौजूद हो सकते हैं." +msgid "Clear" +msgstr "रिक्त करें" + msgid "Currently" msgstr "फिलहाल" msgid "Change" msgstr "बदलें" -msgid "Clear" -msgstr "रिक्त करें" - msgid "Unknown" msgstr "अनजान" @@ -1056,6 +1075,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1070,28 +1097,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "कोई साल निर्दिष्ट नहीं किया गया " +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "कोई महीने निर्दिष्ट नहीं किया गया " @@ -1142,3 +1153,41 @@ msgstr "\"%(path)s\" मौजूद नहीं है" #, python-format msgid "Index of %(directory)s" msgstr "%(directory)s का अनुक्रमणिका" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/hi/formats.py b/django/conf/locale/hi/formats.py index a2ea2e0bf6de..923967ac51d1 100644 --- a/django/conf/locale/hi/formats.py +++ b/django/conf/locale/hi/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j F Y' TIME_FORMAT = 'g:i A' # DATETIME_FORMAT = @@ -15,7 +12,7 @@ # FIRST_DAY_OF_WEEK = # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # DATE_INPUT_FORMATS = # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = diff --git a/django/conf/locale/hr/LC_MESSAGES/django.mo b/django/conf/locale/hr/LC_MESSAGES/django.mo index 79e6323ec939..e7cd5987d2ca 100644 Binary files a/django/conf/locale/hr/LC_MESSAGES/django.mo and b/django/conf/locale/hr/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/hr/LC_MESSAGES/django.po b/django/conf/locale/hr/LC_MESSAGES/django.po index fb23b90a20fe..084ef35374a2 100644 --- a/django/conf/locale/hr/LC_MESSAGES/django.po +++ b/django/conf/locale/hr/LC_MESSAGES/django.po @@ -4,8 +4,9 @@ # aljosa , 2011,2013 # berislavlopac , 2013 # Bojan Mihelač , 2012 +# Boni Đukić , 2017 # Jannis Leidel , 2011 -# Mislav Cimperšak , 2015 +# Mislav Cimperšak , 2015-2016 # Nino , 2013 # senko , 2012 # Ylodi , 2011 @@ -14,8 +15,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Croatian (http://www.transifex.com/django/django/language/" "hr/)\n" @@ -33,7 +34,7 @@ msgid "Arabic" msgstr "Arapski" msgid "Asturian" -msgstr "" +msgstr "Asturijski" msgid "Azerbaijani" msgstr "Azarbejdžanac" @@ -69,7 +70,7 @@ msgid "German" msgstr "Njemački" msgid "Lower Sorbian" -msgstr "" +msgstr "Donjolužičkosrpski" msgid "Greek" msgstr "Grčki" @@ -126,7 +127,7 @@ msgid "Irish" msgstr "Irski" msgid "Scottish Gaelic" -msgstr "" +msgstr "Škotski gaelski" msgid "Galician" msgstr "Galičanski" @@ -141,7 +142,7 @@ msgid "Croatian" msgstr "Hrvatski" msgid "Upper Sorbian" -msgstr "" +msgstr "Gornjolužičkosrpski" msgid "Hungarian" msgstr "Mađarski" @@ -204,7 +205,7 @@ msgid "Burmese" msgstr "Burmanski" msgid "Norwegian Bokmål" -msgstr "" +msgstr "Bokmål" msgid "Nepali" msgstr "Nepalski" @@ -294,7 +295,7 @@ msgid "Messages" msgstr "Poruke" msgid "Site Maps" -msgstr "" +msgstr "Mape stranica" msgid "Static Files" msgstr "Statične datoteke" @@ -302,6 +303,15 @@ msgstr "Statične datoteke" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "Broj stranice nije cijeli broj" + +msgid "That page number is less than 1" +msgstr "Broj stranice je manji od 1" + +msgid "That page contains no results" +msgstr "Stranica ne sadrži rezultate" + msgid "Enter a valid value." msgstr "Unesite ispravnu vrijednost." @@ -314,6 +324,7 @@ msgstr "Unesite vrijednost u obliku cijelog broja." msgid "Enter a valid email address." msgstr "Unesite ispravnu e-mail adresu." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -416,6 +427,17 @@ msgstr[2] "" "Osigurajte da nema više od ukupno %(max)s numberičkih znakova prije " "decimalne točke." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Ekstenzija datoteke '%(extension)s' nije dopuštena. Dopuštene ekstenzije su: " +"'%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "i" @@ -498,12 +520,16 @@ msgid "" "'%(value)s' value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." "uuuuuu]][TZ] format." msgstr "" +"'%(value)s' vrijednost je neispravnog formata. Vrijednost mora biti u YYYY-" +"MM-DD HH:MM[:ss[.uuuuuu]][TZ] formatu." #, 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' vrijednost je u točnom formatu (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]" +"[TZ]), ali je datum/vrijeme neispravno." msgid "Date (with time)" msgstr "Datum (sa vremenom/satima)" @@ -573,12 +599,16 @@ msgid "" "'%(value)s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " "format." msgstr "" +"'%(value)s' vrijednost je neispravnog formata. Vrijednost mora biti u HH:MM[:" +"ss[.uuuuuu]] formatu." #, python-format msgid "" "'%(value)s' value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " "invalid time." msgstr "" +"'%(value)s' vrijednost je u točnom formatu (HH:MM[:ss[.uuuuuu]]), ali je " +"datum/vrijeme neispravno." msgid "Time" msgstr "Vrijeme" @@ -587,7 +617,7 @@ msgid "URL" msgstr "URL" msgid "Raw binary data" -msgstr "" +msgstr "Binarni podaci" #, python-format msgid "'%(value)s' is not a valid UUID." @@ -601,7 +631,7 @@ msgstr "Slika" #, python-format msgid "%(model)s instance with %(field)s %(value)r does not exist." -msgstr "" +msgstr "%(model)s instanca sa %(field)s %(value)r ne postoji." msgid "Foreign Key (type determined by related field)" msgstr "Foreign Key (type determined by related field)" @@ -611,11 +641,11 @@ msgstr "One-to-one relationship" #, python-format msgid "%(from)s-%(to)s relationship" -msgstr "" +msgstr "%(from)s-%(to)s veza" #, python-format msgid "%(from)s-%(to)s relationships" -msgstr "" +msgstr "%(from)s-%(to)s veze" msgid "Many-to-many relationship" msgstr "Many-to-many relationship" @@ -661,8 +691,11 @@ 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] "" +"Osigurajte da naziv datoteke ima najviše %(max)d znak (ima %(length)d)." msgstr[1] "" +"Osigurajte da naziv datoteke ima najviše %(max)d znakova (ima %(length)d)." msgstr[2] "" +"Osigurajte da naziv datoteke ima najviše %(max)d znakova (ima %(length)d)." msgid "Please either submit a file or check the clear checkbox, not both." msgstr "Molimo Vas da pošaljete ili datoteku ili označite izbor, a ne oboje." @@ -682,7 +715,7 @@ msgid "Enter a list of values." msgstr "Unesite listu vrijednosti." msgid "Enter a complete value." -msgstr "" +msgstr "Unesite kompletnu vrijednost." msgid "Enter a valid UUID." msgstr "Unesite ispravan UUID." @@ -693,10 +726,10 @@ msgstr ":" #, python-format msgid "(Hidden field %(name)s) %(error)s" -msgstr "" +msgstr "(Skriveno polje %(name)s) %(error)s" msgid "ManagementForm data is missing or has been tampered with" -msgstr "" +msgstr "ManagementForm podaci nedostaju ili su promijenjeni" #, python-format msgid "Please submit %d or fewer forms." @@ -739,15 +772,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Molimo ispravite duplicirane vrijednosti ispod." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." +msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Izaberite ispravnu opciju. Ta opcija nije jedna od dostupnih opcija." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" nije ispravna vrijednost za primarni ključ." +msgid "\"%(pk)s\" is not a valid value." +msgstr "" #, python-format msgid "" @@ -757,15 +790,15 @@ msgstr "" "%(datetime)s ne može biti interpretirano u vremenskoj zoni " "%(current_timezone)s; možda je dvosmisleno ili ne postoji." +msgid "Clear" +msgstr "Isprazni" + msgid "Currently" msgstr "Trenutno" msgid "Change" msgstr "Promijeni" -msgid "Clear" -msgstr "Isprazni" - msgid "Unknown" msgstr "Nepoznat pojam" @@ -1094,10 +1127,10 @@ msgid "0 minutes" msgstr "0 minuta" msgid "Forbidden" -msgstr "" +msgstr "Zabranjeno" msgid "CSRF verification failed. Request aborted." -msgstr "" +msgstr "CSRF verifikacija nije uspjela. Zahtjev je prekinut." msgid "" "You are seeing this message because this HTTPS site requires a 'Referer " @@ -1105,49 +1138,55 @@ msgid "" "required for security reasons, to ensure that your browser is not being " "hijacked by third parties." msgstr "" +"Ova poruka je prikazana jer ova HTTPS stranica zahtijeva da 'zaglavlje " +"preporučitelja' bude poslano od strane internetskog preglednika, ali ono " +"nije poslano. Ovo zaglavlje je potrebno iz sigurnosnih razloga, kako bi se " +"osiguralo da vaš internetski preglednik ne bude otet od strane trećih osoba." 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 konfigurirali svoj internetski preglednik da onemogući 'zaglavlje " +"preporučitelja', molimo da ga ponovno omogućite barem za ovu stranicu, na " +"svim HTTPS vezama, ili za zahtjeve 'istog podrijetla'." + +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." +msgstr "" 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 vam se prikazuje jer stranica na kojoj se nalazite zahtjeva CSRF " +"kolačić prilikom slanja forme. Navedeni kolačić je obavezan iz sigurnosnih " +"razloga, kako bi se osiguralo da vaš internetski preglednik ne bude otet od " +"strane trećih osoba." 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 ste konfigurirali svoj internetski preglednik da onemogući kolačiće, " +"molimo da ih ponovno omogućite barem za ovu stranicu ili za zahtjeve 'istog " +"podrijetla'." msgid "More information is available with DEBUG=True." -msgstr "" - -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "Radi!" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" +msgstr "Dodatne informacije su dostupne sa postavkom DEBUG=True." msgid "No year specified" msgstr "Nije navedena godina" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "Nije naveden mjesec" @@ -1198,3 +1237,41 @@ msgstr "\"%(path)s\" ne postoji" #, python-format msgid "Index of %(directory)s" msgstr "Sadržaj direktorija %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/hr/formats.py b/django/conf/locale/hr/formats.py index 59bcb8657d3f..3235f5a4e439 100644 --- a/django/conf/locale/hr/formats.py +++ b/django/conf/locale/hr/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j. E Y.' TIME_FORMAT = 'H:i' DATETIME_FORMAT = 'j. E Y. H:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # Kept ISO formats as they are in first position DATE_INPUT_FORMATS = [ '%Y-%m-%d', # '2006-10-25' diff --git a/django/conf/locale/hsb/LC_MESSAGES/django.mo b/django/conf/locale/hsb/LC_MESSAGES/django.mo index 515aea267c36..6c80bb57bd88 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 7d6cef6806aa..6e97b1bb93d2 100644 --- a/django/conf/locale/hsb/LC_MESSAGES/django.po +++ b/django/conf/locale/hsb/LC_MESSAGES/django.po @@ -1,13 +1,13 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Michael Wolf , 2016 +# Michael Wolf , 2016-2019 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-07-01 21:52+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-03-04 13:50+0000\n" "Last-Translator: Michael Wolf \n" "Language-Team: Upper Sorbian (http://www.transifex.com/django/django/" "language/hsb/)\n" @@ -138,6 +138,9 @@ msgstr "Hornjoserbšćina" msgid "Hungarian" msgstr "Madźaršćina" +msgid "Armenian" +msgstr "Armenšćina" + msgid "Interlingua" msgstr "Interlingua" @@ -159,6 +162,9 @@ msgstr "Japanšćina" msgid "Georgian" msgstr "Georgišćina" +msgid "Kabyle" +msgstr "Kabylšćina" + msgid "Kazakh" msgstr "Kazachšćina" @@ -294,6 +300,15 @@ msgstr "Statiske dataje" msgid "Syndication" msgstr "Syndikacija" +msgid "That page number is not an integer" +msgstr "Tute čisko strony cyła ličba njeje." + +msgid "That page number is less than 1" +msgstr "Tute čisło strony je mjeńše hač 1." + +msgid "That page contains no results" +msgstr "Tuta strona wuslědki njewobsahuje" + msgid "Enter a valid value." msgstr "Zapodajće płaćiwu hódnotu." @@ -306,6 +321,7 @@ msgstr "Zapodajće płaćiwu cyłu ličbu." msgid "Enter a valid email address." msgstr "Zapodajće płaćiwu e-mejlowu adresu." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -383,6 +399,9 @@ msgstr[3] "" "Zawěsćće, zo tuta hódnota ma maksimalnje %(limit_value)d znamješkow (ima " "%(show_value)d)." +msgid "Enter a number." +msgstr "Zapodajće ličbu." + #, 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." @@ -409,6 +428,17 @@ msgstr[1] "Zawěsćće, zo njeje wjace hač %(max)s cyfrow před decimalnej komu msgstr[2] "Zawěsćće, zo njeje wjace hač %(max)s cyfrow před decimalnej komu." msgstr[3] "Zawěsćće, zo njeje wjace hač %(max)s cyfrow před decimalnej komu." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Datajowy sufiks ' %(extension)s' dowoleny njeje. Dowolene sufiksy su: ' " +"%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Prózdne znamješka dowolene njejsu." + msgid "and" msgstr "a" @@ -456,6 +486,10 @@ msgstr "Big (8 byte) integer" msgid "'%(value)s' value must be either True or False." msgstr "Hódnota '%(value)s' dyrbi pak True pak False być." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "Hódnota '%(value)s' dyrbi pak True, False pak None być." + msgid "Boolean (Either True or False)" msgstr "Boolean (pak True pak False)" @@ -592,6 +626,9 @@ msgstr "Hrube binarne daty" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' płaćiwy UUID njeje." +msgid "Universally unique identifier" +msgstr "Uniwerselnje jónkróćny identifikator" + msgid "File" msgstr "Dataja" @@ -631,9 +668,6 @@ msgstr "Tute polo je trěbne." msgid "Enter a whole number." msgstr "Zapodajće cyłu ličbu." -msgid "Enter a number." -msgstr "Zapodajće ličbu." - msgid "Enter a valid date." msgstr "Zapodajće płaćiwy datum." @@ -646,6 +680,10 @@ msgstr "Zapodajće płaćiwy datum/čas." msgid "Enter a valid duration." msgstr "Zapodajće płaćiwe traće." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Ličba dnjow dyrbi mjez {min_days} a {max_days} być." + msgid "No file was submitted. Check the encoding type on the form." msgstr "Žana dataja je so pósłała. Přepruwujće kodowanski typ we formularje." @@ -750,9 +788,8 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Prošu porjedźće slědowace dwójne hódnoty." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "" -"Nutřkowny cuzy kluč primarnemu klučej nadrjadowaneje instancy njewotpowěduje." +msgid "The inline value did not match the parent instance." +msgstr "Hódnota inline nadrjadowanej instancy njewotpowěduje." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" @@ -760,8 +797,8 @@ msgstr "" "dispoziciji stejacych wolenskich móžnosćow njeje." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" płaćiwa hódnota za primarny kluč njeje." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" płaćiwa hódnota njeje." #, python-format msgid "" @@ -771,15 +808,15 @@ msgstr "" "%(datetime)s njeda so w časowym pasmje %(current_timezone)s interpretować; " "je snano dwuzmyslny abo njeeksistuje." +msgid "Clear" +msgstr "Zhašeć" + msgid "Currently" msgstr "Tuchwilu" msgid "Change" msgstr "Změnić" -msgid "Clear" -msgstr "Zhašeć" - msgid "Unknown" msgstr "Njeznaty" @@ -790,7 +827,7 @@ msgid "No" msgstr "Ně" msgid "yes,no,maybe" -msgstr "ha,ně,snano" +msgstr "haj,ně,snano" #, python-format msgid "%(size)d byte" @@ -1053,8 +1090,8 @@ msgstr "To płaćiwa IPv6-adresa njeje." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s…" msgid "or" msgstr "abo" @@ -1140,6 +1177,19 @@ msgstr "" "znjemóžnjene, zmóžńće je, znajmjeńša za tute sydło abo za HTTPS-zwiski abo " "za naprašowanja 'sameorigin'." +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." +msgstr "" +"Jeli značku wužiwaće abo " +"hłowu 'Referrer-Policy: no-referrer' zapřijimaće, wotstrońće je prošu. CSRF-" +"škit trjeba hłowu 'Referer' , zo by striktnu kontrolu referer přewjedźe. " +"Jeli so wo priwatnosć staraće, wužiwajće alternatiwy kaž za wotkazy k sydłam třećich." + 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 " @@ -1159,33 +1209,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Z DEBUG=True su dalše informacije k dispoziciji." -msgid "Welcome to Django" -msgstr "Witajće k Django" - -msgid "It worked!" -msgstr "Je so fungowało!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Zbožopřeće k wašej prěnjej stronje spěchowanej přez Django." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Njejsće wězo hišće žane dźěło činił. Wuwjedźće tuž jako přichodne " -"python manage.py startapp [app_label], zo byšće swóje prěnje " -"nałoženje startował." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Widźiće tutu zdźělenku, dokelž maće DEBUG = True w swojej " -"dataji nastajenjow Django a njejsće URL skonfigurował. Dajće do dźěła!" - msgid "No year specified" msgstr "Žane lěto podate" +msgid "Date out of range" +msgstr "Datum zwonka wobłuka" + msgid "No month specified" msgstr "Žadyn měsac podaty" @@ -1237,3 +1266,47 @@ msgstr "\"%(path)s\" njeeksistuje" #, python-format msgid "Index of %(directory)s" msgstr "Indeks %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: Web framework za perfekcionistow z terminami." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Čitajće wersijowe informacije za Django " +"%(version)s" + +msgid "The install worked successfully! Congratulations!" +msgstr "Instalacija bě wuspěšna! Zbožopřeće!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Widźiće tutu stronu, dokelž DEBUG=True je we wašej dataji nastajenjow a njejsće URL skonfigurował." + +msgid "Django Documentation" +msgstr "Dokumentacija Django" + +msgid "Topics, references, & how-to's" +msgstr "Temy, referency a nawody" + +msgid "Tutorial: A Polling App" +msgstr "Nawod: Naprašowanske nałoženje" + +msgid "Get started with Django" +msgstr "Prěnje kroki z Django" + +msgid "Django Community" +msgstr "Zhromadźenstwo Django" + +msgid "Connect, get help, or contribute" +msgstr "Zwjazać, pomoc wobstarać abo přinošować" diff --git a/django/conf/locale/hu/LC_MESSAGES/django.mo b/django/conf/locale/hu/LC_MESSAGES/django.mo index 9717bba5127a..0489b6c1c769 100644 Binary files a/django/conf/locale/hu/LC_MESSAGES/django.mo and b/django/conf/locale/hu/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/hu/LC_MESSAGES/django.po b/django/conf/locale/hu/LC_MESSAGES/django.po index c87ff00f3808..a53d082374bd 100644 --- a/django/conf/locale/hu/LC_MESSAGES/django.po +++ b/django/conf/locale/hu/LC_MESSAGES/django.po @@ -1,18 +1,20 @@ # This file is distributed under the same license as the Django package. # # Translators: -# András Veres-Szentkirályi, 2016 +# Akos Zsolt Hochrein , 2018 +# András Veres-Szentkirályi, 2016-2019 # Attila Nagy <>, 2012 +# Dóra Szendrei , 2017 # Jannis Leidel , 2011 -# János Péter Ronkay , 2011-2012,2014 +# János R (Hangya), 2011-2012,2014 # Máté Őry , 2013 # Szilveszter Farkas , 2011 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-07-19 07:35+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-04-17 07:38+0000\n" "Last-Translator: András Veres-Szentkirályi\n" "Language-Team: Hungarian (http://www.transifex.com/django/django/language/" "hu/)\n" @@ -142,6 +144,9 @@ msgstr "Felsőszorb" msgid "Hungarian" msgstr "Magyar" +msgid "Armenian" +msgstr "Örmény" + msgid "Interlingua" msgstr "Interlingua" @@ -163,6 +168,9 @@ msgstr "Japán" msgid "Georgian" msgstr "Grúz" +msgid "Kabyle" +msgstr "Kabil" + msgid "Kazakh" msgstr "Kazak" @@ -298,6 +306,15 @@ msgstr "Statikus fájlok" msgid "Syndication" msgstr "Szindikáció" +msgid "That page number is not an integer" +msgstr "Az oldalszám nem egész szám." + +msgid "That page number is less than 1" +msgstr "Az oldalszám kisebb, mint 1" + +msgid "That page contains no results" +msgstr "Az oldal nem tartalmaz találatokat" + msgid "Enter a valid value." msgstr "Adjon meg egy érvényes értéket." @@ -310,6 +327,7 @@ msgstr "Adjon meg egy érvényes számot." msgid "Enter a valid email address." msgstr "Írjon be egy érvényes e-mail címet." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -377,6 +395,9 @@ msgstr[1] "" "Bizonyosodjon meg arról, hogy ez az érték legfeljebb %(limit_value)d " "karaktert tartalmaz (jelenlegi hossza: %(show_value)d)." +msgid "Enter a number." +msgstr "Adj meg egy számot." + #, 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." @@ -403,6 +424,17 @@ msgstr[1] "" "Bizonyosodjon meg arról, hogy legfeljebb %(max)s számjegy van a " "tizedesvessző előtt." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"'%(extension)s' kiterjesztés nem engedélyezett. Az engedélyezett " +"kiterjesztések a következők: '%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Null karakterek használata nem megengedett." + msgid "and" msgstr "és" @@ -451,6 +483,10 @@ msgstr "Nagy egész szám (8 bájtos)" msgid "'%(value)s' value must be either True or False." msgstr "'%(value)s' érték csak igaz (True) vagy hamis (False) lehet." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "'%(value)s' értéknek True, False vagy None-nak kell lennie." + msgid "Boolean (Either True or False)" msgstr "Logikai (True vagy False)" @@ -589,6 +625,9 @@ msgstr "Nyers bináris adat" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' nem egy érvényes UUID." +msgid "Universally unique identifier" +msgstr "Univerzálisan egyedi azonosító" + msgid "File" msgstr "Fájl" @@ -628,9 +667,6 @@ msgstr "Ennek a mezőnek a megadása kötelező." msgid "Enter a whole number." msgstr "Adjon meg egy egész számot." -msgid "Enter a number." -msgstr "Adj meg egy számot." - msgid "Enter a valid date." msgstr "Adjon meg egy érvényes dátumot." @@ -643,6 +679,10 @@ msgstr "Adjon meg egy érvényes dátumot/időt." msgid "Enter a valid duration." msgstr "Adjon meg egy érvényes időtartamot." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "A napok számának {min_days} és {max_days} közé kell esnie." + msgid "No file was submitted. Check the encoding type on the form." msgstr "Nem küldött el fájlt. Ellenőrizze a kódolás típusát az űrlapon." @@ -739,10 +779,8 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Javítsa az alábbi duplikált értékeket." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "" -"A beágyazott idegen kulcs nem egyezik meg a szülő példány elsődleges " -"kulcsával." +msgid "The inline value did not match the parent instance." +msgstr "A beágyazott érték nem egyezik meg a szülő példányéval." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" @@ -750,8 +788,8 @@ msgstr "" "között." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" egy érvénytelen elsődleges kulcs érték." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" egy érvénytelen érték." #, python-format msgid "" @@ -761,15 +799,15 @@ msgstr "" "%(datetime)s értelmezhetetlen a megadott %(current_timezone)s időzónában; " "vagy félreérthető, vagy nem létezik." +msgid "Clear" +msgstr "Törlés" + msgid "Currently" msgstr "Jelenleg" msgid "Change" msgstr "Módosítás" -msgid "Clear" -msgstr "Törlés" - msgid "Unknown" msgstr "Ismeretlen" @@ -1041,8 +1079,8 @@ msgstr "Ez nem egy érvényes IPv6 cím." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s…" msgid "or" msgstr "vagy" @@ -1116,6 +1154,19 @@ msgstr "" "azokat, legalább erre a weboldalra, vagy azonos forrásból ('same-origin') " "származó kérésekre." +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." +msgstr "" +"Ha a taget használod vagy " +"'Referrer-Policy: no-referrer' fejlécet állítottál be, távolítsd el őket. A " +"CSRF védelemnek szüksége van a 'Referer' fejlécre a szigorú referer " +"ellenőrzéshez. Ha adatvédelmi okokból döntöttél így, a külső oldalakra " +"mutató linkeknél egy használható alternatíva az ." + 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 " @@ -1135,34 +1186,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "További információ DEBUG=True beállítással érhető el." -msgid "Welcome to Django" -msgstr "Üdvözli a Django" - -msgid "It worked!" -msgstr "Működik!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Gratulálunk az első Django alapú oldalhoz!" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Természetesen még hátra van a munka nagy része. Következő lépésként indítsa " -"el az első appot a python manage.py startapp [app_label] " -"paranccsal." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Azért jelenik meg ez az üzenet, mert a DEBUG = True szerepel a " -"Django settings fájlban, és még nem került beállításra egy URL sem. Jó " -"munkát!" - msgid "No year specified" msgstr "Nincs év megadva" +msgid "Date out of range" +msgstr "A dátum a megengedett tartományon kívül esik." + msgid "No month specified" msgstr "Nincs hónap megadva" @@ -1214,3 +1243,49 @@ msgstr "\"%(path)s\" nem létezik" #, python-format msgid "Index of %(directory)s" msgstr "A %(directory)s könyvtár tartalma" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" +"Django: webes keretrendszer azoknak, akiknek a tökéletesség határidőre kell." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"A Django %(version)s kiadási megjegyzéseinek " +"megtekintése" + +msgid "The install worked successfully! Congratulations!" +msgstr "A telepítés sikeresen végződött! Gratulálunk!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Azért látod ezt az oldalt, mert a DEBUG=True szerepel a settings fájlban, és még nem került beállításra " +"egy URL sem." + +msgid "Django Documentation" +msgstr "Django Dokumentáció" + +msgid "Topics, references, & how-to's" +msgstr "Témakörök, hivatkozások & hogyanok" + +msgid "Tutorial: A Polling App" +msgstr "Gyakorlat: egy szavazó app" + +msgid "Get started with Django" +msgstr "Első lépések a Djangóval" + +msgid "Django Community" +msgstr "Django Közösség" + +msgid "Connect, get help, or contribute" +msgstr "Lépj kapcsolatba, kérj segítséget, vagy járulj hozzá" diff --git a/django/conf/locale/hu/formats.py b/django/conf/locale/hu/formats.py index e17e8c99adc4..0f304bdb466b 100644 --- a/django/conf/locale/hu/formats.py +++ b/django/conf/locale/hu/formats.py @@ -1,32 +1,29 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'Y. F j.' -TIME_FORMAT = 'G.i' -DATETIME_FORMAT = 'Y. F j. G.i' +TIME_FORMAT = 'H:i' +DATETIME_FORMAT = 'Y. F j. H:i' YEAR_MONTH_FORMAT = 'Y. F' MONTH_DAY_FORMAT = 'F j.' SHORT_DATE_FORMAT = 'Y.m.d.' -SHORT_DATETIME_FORMAT = 'Y.m.d. G.i' +SHORT_DATETIME_FORMAT = 'Y.m.d. H:i' FIRST_DAY_OF_WEEK = 1 # Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%Y.%m.%d.', # '2006.10.25.' ] TIME_INPUT_FORMATS = [ - '%H.%M.%S', # '14.30.59' - '%H.%M', # '14.30' + '%H:%M:%S', # '14:30:59' + '%H:%M', # '14:30' ] DATETIME_INPUT_FORMATS = [ - '%Y.%m.%d. %H.%M.%S', # '2006.10.25. 14.30.59' - '%Y.%m.%d. %H.%M.%S.%f', # '2006.10.25. 14.30.59.000200' - '%Y.%m.%d. %H.%M', # '2006.10.25. 14.30' + '%Y.%m.%d. %H:%M:%S', # '2006.10.25. 14:30:59' + '%Y.%m.%d. %H:%M:%S.%f', # '2006.10.25. 14:30:59.000200' + '%Y.%m.%d. %H:%M', # '2006.10.25. 14:30' '%Y.%m.%d.', # '2006.10.25.' ] DECIMAL_SEPARATOR = ',' diff --git a/django/conf/locale/hy/LC_MESSAGES/django.mo b/django/conf/locale/hy/LC_MESSAGES/django.mo new file mode 100644 index 000000000000..3e96347069e0 Binary files /dev/null and b/django/conf/locale/hy/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/hy/LC_MESSAGES/django.po b/django/conf/locale/hy/LC_MESSAGES/django.po new file mode 100644 index 000000000000..3d73c48e8cc9 --- /dev/null +++ b/django/conf/locale/hy/LC_MESSAGES/django.po @@ -0,0 +1,1243 @@ +# This file is distributed under the same license as the Django package. +# +# Translators: +# Սմբատ Պետրոսյան , 2014 +msgid "" +msgstr "" +"Project-Id-Version: django\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-05-17 11:49+0200\n" +"PO-Revision-Date: 2018-11-01 20:32+0000\n" +"Last-Translator: Ruben Harutyunov \n" +"Language-Team: Armenian (http://www.transifex.com/django/django/language/" +"hy/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: hy\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Afrikaans" +msgstr "Աֆրիկաանս" + +msgid "Arabic" +msgstr "Արաբերեն" + +msgid "Asturian" +msgstr "Աստուրերեն" + +msgid "Azerbaijani" +msgstr "Ադրբեջաներեն" + +msgid "Bulgarian" +msgstr "Բուլղարերեն" + +msgid "Belarusian" +msgstr "Բելոռուսերեն" + +msgid "Bengali" +msgstr "Բենգալերեն" + +msgid "Breton" +msgstr "Բրետոներեն" + +msgid "Bosnian" +msgstr "Բոսնիերեն" + +msgid "Catalan" +msgstr "Կատալաներեն" + +msgid "Czech" +msgstr "Չեխերեն" + +msgid "Welsh" +msgstr "Վալլիերեն" + +msgid "Danish" +msgstr "Դանիերեն" + +msgid "German" +msgstr "Գերմաներեն" + +msgid "Lower Sorbian" +msgstr "" + +msgid "Greek" +msgstr "Հունարեն" + +msgid "English" +msgstr "Անգլերեն" + +msgid "Australian English" +msgstr "Ավստրալական Անգլերեն" + +msgid "British English" +msgstr "Բրիտանական Անգլերեն" + +msgid "Esperanto" +msgstr "Էսպերանտո" + +msgid "Spanish" +msgstr "Իսպաներեն" + +msgid "Argentinian Spanish" +msgstr "Արգենտինական իսպաներեն" + +msgid "Colombian Spanish" +msgstr "Կոլումբիական իսպաներեն" + +msgid "Mexican Spanish" +msgstr "Մեքսիկական իսպաներեն" + +msgid "Nicaraguan Spanish" +msgstr "Նիկարագուական իսպաներեն" + +msgid "Venezuelan Spanish" +msgstr "Վենեսուելլական իսպաներեն" + +msgid "Estonian" +msgstr "Էստոներեն" + +msgid "Basque" +msgstr "Բասկերեն" + +msgid "Persian" +msgstr "Պարսկերեն" + +msgid "Finnish" +msgstr "Ֆիներեն" + +msgid "French" +msgstr "Ֆրանսերեն" + +msgid "Frisian" +msgstr "Ֆրիզերեն" + +msgid "Irish" +msgstr "Իռլանդերեն" + +msgid "Scottish Gaelic" +msgstr "Գելական շոտլանդերեն" + +msgid "Galician" +msgstr "Գալիսերեն" + +msgid "Hebrew" +msgstr "Եբրայերեն" + +msgid "Hindi" +msgstr "Հինդի" + +msgid "Croatian" +msgstr "Խորվաթերեն" + +msgid "Upper Sorbian" +msgstr "" + +msgid "Hungarian" +msgstr "Հունգարերեն" + +msgid "Interlingua" +msgstr "Ինտերլինգուա" + +msgid "Indonesian" +msgstr "Ինդոնեզերեն" + +msgid "Ido" +msgstr "Իդո" + +msgid "Icelandic" +msgstr "Իսլանդերեն" + +msgid "Italian" +msgstr "Իտալերեն" + +msgid "Japanese" +msgstr "Ճապոներեն" + +msgid "Georgian" +msgstr "Վրացերեն" + +msgid "Kabyle" +msgstr "" + +msgid "Kazakh" +msgstr "Ղազախերեն" + +msgid "Khmer" +msgstr "Քեմերերեն" + +msgid "Kannada" +msgstr "Կանադա" + +msgid "Korean" +msgstr "Կորեերեն" + +msgid "Luxembourgish" +msgstr "Լյուքսեմբուրգերեն" + +msgid "Lithuanian" +msgstr "Լիտվերեն" + +msgid "Latvian" +msgstr "Լատիշերեն" + +msgid "Macedonian" +msgstr "Մակեդոներեն" + +msgid "Malayalam" +msgstr "Մալայալամ" + +msgid "Mongolian" +msgstr "Մոնղոլերեն" + +msgid "Marathi" +msgstr "Մարատխի" + +msgid "Burmese" +msgstr "Բիրմաներեն" + +msgid "Norwegian Bokmål" +msgstr "" + +msgid "Nepali" +msgstr "Նեպալերեն" + +msgid "Dutch" +msgstr "Հոլանդերեն" + +msgid "Norwegian Nynorsk" +msgstr "Նորվեգերեն (Նյունորսկ)" + +msgid "Ossetic" +msgstr "Օսերեն" + +msgid "Punjabi" +msgstr "Փանջաբի" + +msgid "Polish" +msgstr "Լեհերեն" + +msgid "Portuguese" +msgstr "Պորտուգալերեն" + +msgid "Brazilian Portuguese" +msgstr "Բրազիլական պորտուգալերեն" + +msgid "Romanian" +msgstr "Ռումիներեն" + +msgid "Russian" +msgstr "Ռուսերեն" + +msgid "Slovak" +msgstr "Սլովակերեն" + +msgid "Slovenian" +msgstr "Սլովեներեն" + +msgid "Albanian" +msgstr "Ալբաներեն" + +msgid "Serbian" +msgstr "Սերբերեն" + +msgid "Serbian Latin" +msgstr "Սերբերեն (լատինատառ)" + +msgid "Swedish" +msgstr "Շվեդերեն" + +msgid "Swahili" +msgstr "Սվահիլի" + +msgid "Tamil" +msgstr "Թամիլերեն" + +msgid "Telugu" +msgstr "Թելուգու" + +msgid "Thai" +msgstr "Թայերեն" + +msgid "Turkish" +msgstr "Թուրքերեն" + +msgid "Tatar" +msgstr "Թաթարերեն" + +msgid "Udmurt" +msgstr "Ումուրտերեն" + +msgid "Ukrainian" +msgstr "Ուկրաիներեն" + +msgid "Urdu" +msgstr "Ուրդու" + +msgid "Vietnamese" +msgstr "Վիետնամերեն" + +msgid "Simplified Chinese" +msgstr "Հեշտացված չինարեն" + +msgid "Traditional Chinese" +msgstr "Ավանդական չինարեն" + +msgid "Messages" +msgstr "Հաղորդագրություններ" + +msgid "Site Maps" +msgstr "Կայքի քարտեզ" + +msgid "Static Files" +msgstr "Ստատիկ ֆայլեր\t" + +msgid "Syndication" +msgstr "Նորություններ" + +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + +msgid "Enter a valid value." +msgstr "Մուտքագրեք ճիշտ արժեք" + +msgid "Enter a valid URL." +msgstr "Մուտքագրեք ճիշտ URL" + +msgid "Enter a valid integer." +msgstr "Մուտքագրեք ամբողջ թիվ" + +msgid "Enter a valid email address." +msgstr "Մուտքագրեք ճիշտ էլեկտրոնային փոստի հասցե" + +#. Translators: "letters" means latin letters: a-z and A-Z. +msgid "" +"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." +msgstr "" +"Արժեքը պետք է բաղկացած լինի տառերից, թվերից, ընդգծումներից կամ դեֆիսներից" + +msgid "" +"Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or " +"hyphens." +msgstr "" +"Արժեքը պետք է բաղկացած լինի Unicode ստանդարտի տառերից, թվերից, ընդգծումներից " +"կամ դեֆիսներից" + +msgid "Enter a valid IPv4 address." +msgstr "Մուտքագրեք ճիշտ IPv4 հասցե" + +msgid "Enter a valid IPv6 address." +msgstr "Մուտքագրեք ճիշտ IPv6 հասցե" + +msgid "Enter a valid IPv4 or IPv6 address." +msgstr "Մուտքագրեք ճիշտ IPv4 կամ IPv6 հասցե" + +msgid "Enter only digits separated by commas." +msgstr "Մուտքագրեք միայն ստորակետով բաժանված թվեր" + +#, python-format +msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." +msgstr "Համոզվեք, որ այս արժեքը %(limit_value)s (հիմա այն — %(show_value)s)" + +#, python-format +msgid "Ensure this value is less than or equal to %(limit_value)s." +msgstr "Համոզվեք, որ այս արժեքը փոքր է, կամ հավասար %(limit_value)s" + +#, python-format +msgid "Ensure this value is greater than or equal to %(limit_value)s." +msgstr "Համոզվեք, որ այս արժեքը մեծ է, համ հավասար %(limit_value)s" + +#, python-format +msgid "" +"Ensure this value has at least %(limit_value)d character (it has " +"%(show_value)d)." +msgid_plural "" +"Ensure this value has at least %(limit_value)d characters (it has " +"%(show_value)d)." +msgstr[0] "" +"Համոզվեք, որ արժեքը պարունակում է ամենաքիչը %(limit_value)d նիշ (այն " +"պարունակում է %(show_value)d)." +msgstr[1] "" +"Համոզվեք, որ արժեքը պարունակում է ամենաքիչը %(limit_value)d նիշ (այն " +"պարունակում է %(show_value)d)." + +#, python-format +msgid "" +"Ensure this value has at most %(limit_value)d character (it has " +"%(show_value)d)." +msgid_plural "" +"Ensure this value has at most %(limit_value)d characters (it has " +"%(show_value)d)." +msgstr[0] "" +"Համոզվեք, որ արժեքը պարունակում է ամենաքիչը %(limit_value)d նիշ (այն " +"պարունակում է %(show_value)d)." +msgstr[1] "" +"Համոզվեք, որ արժեքը պարունակում է ամենաքիչը %(limit_value)d նիշ (այն " +"պարունակում է %(show_value)d)." + +msgid "Enter a number." +msgstr "Մուտքագրեք թիվ" + +#, 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] "Համոզվեք, որ թվերի քանակը մեծ չէ %(max)s -ից" +msgstr[1] "Համոզվեք, որ թվերի քանակը մեծ չէ %(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[1] "Համոզվեք, որ ստորակետից հետո թվերի քանակը մեծ չէ %(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[1] "Համոզվեք, որ ստորակետից առաջ թվերի քանակը մեծ չէ %(max)s -ից" + +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + +msgid "and" +msgstr "և" + +#, python-format +msgid "%(model_name)s with this %(field_labels)s already exists." +msgstr "" +"%(field_labels)s դաշտերի այս արժեքով %(model_name)s արդեն գոյություն ունի" + +#, python-format +msgid "Value %(value)r is not a valid choice." +msgstr "%(value)r արժեքը չի մտնում թույլատրված տարբերակների մեջ" + +msgid "This field cannot be null." +msgstr "Այս դաշտը չի կարող ունենալ NULL արժեք " + +msgid "This field cannot be blank." +msgstr "Այս դաշտը չի կարող լինել դատարկ" + +#, python-format +msgid "%(model_name)s with this %(field_label)s already exists." +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" +#, python-format +msgid "" +"%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." +msgstr "" +"«%(field_label)s» դաշտի արժեքը պետք է լինի միակը %(date_field_label)s " +"%(lookup_type)s համար" + +#, python-format +msgid "Field of type: %(field_type)s" +msgstr "%(field_type)s տիպի դաշտ" + +msgid "Integer" +msgstr "Ամբողջ" + +#, python-format +msgid "'%(value)s' value must be an integer." +msgstr "'%(value)s' արժեքը պետք է լինի ամբողջ թիվ" + +msgid "Big (8 byte) integer" +msgstr "Մեծ (8 բայթ) ամբողջ թիվ" + +#, python-format +msgid "'%(value)s' value must be either True or False." +msgstr "'%(value)s' արժեքը պետք է լինի True կամ False" + +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "" + +msgid "Boolean (Either True or False)" +msgstr "Տրամաբանական (True կամ False)" + +#, python-format +msgid "String (up to %(max_length)s)" +msgstr "Տող (մինչև %(max_length)s երկարությամբ)" + +msgid "Comma-separated integers" +msgstr "Ստորակետով բաժանված ամբողջ թվեր" + +#, python-format +msgid "" +"'%(value)s' value has an invalid date format. It must be in YYYY-MM-DD " +"format." +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 "" +"'%(value)s' արժեքը ունի ճիշտ YYYY-MM-DD ֆորմատ, բայց այն սխալ ամսաթիվ է" + +msgid "Date (without time)" +msgstr "Ամսաթիվ (առանց ժամանակի)" + +#, python-format +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 "'%(value)s' արժեքը պետք է լինի տասնորդական թիվ" + +msgid "Decimal number" +msgstr "Տասնորդական թիվ" + +#, python-format +msgid "" +"'%(value)s' value has an invalid format. It must be in [DD] [HH:[MM:]]ss[." +"uuuuuu] format." +msgstr "" +"'%(value)s' արժեքը սխալ է։ Այն պետք է լինի [DD] [HH:[MM:]]ss[.uuuuuu] " +"ֆորմատի" + +msgid "Duration" +msgstr "Տևողություն" + +msgid "Email address" +msgstr "Email հասցե" + +msgid "File path" +msgstr "Ֆայլի ճանապարհ" + +#, python-format +msgid "'%(value)s' value must be a float." +msgstr "'%(value)s' արժեքը պետք է լինի float" + +msgid "Floating point number" +msgstr "Floating point թիվ" + +msgid "IPv4 address" +msgstr "IPv4 հասցե" + +msgid "IP address" +msgstr "IP հասցե" + +#, python-format +msgid "'%(value)s' value must be either None, True or False." +msgstr "'%(value)s' արժեքը պետք է լինի None, True կամ False" + +msgid "Boolean (Either True, False or None)" +msgstr "Տրամաբանական (Either True, False կամ None)" + +msgid "Positive integer" +msgstr "Դրական ամբողջ թիվ" + +msgid "Positive small integer" +msgstr "Դրայան փոքր ամբողջ թիվ" + +#, python-format +msgid "Slug (up to %(max_length)s)" +msgstr "Slug (մինչև %(max_length)s նիշ)" + +msgid "Small integer" +msgstr "Փոքր ամբողջ թիվ" + +msgid "Text" +msgstr "Տեքստ" + +#, python-format +msgid "" +"'%(value)s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " +"format." +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 "" +"'%(value)s' արժեքը ունի ճիշտ HH:MM[:ss[.uuuuuu]] ֆորմատ, բայց այն սխալ " +"ժամանակ է" + +msgid "Time" +msgstr "Ժամանակ" + +msgid "URL" +msgstr "URL" + +msgid "Raw binary data" +msgstr "Երկուական տվյալներ" + +#, python-format +msgid "'%(value)s' is not a valid UUID." +msgstr "'%(value)s' արժեքը սխալ UUID է" + +msgid "File" +msgstr "Ֆայլ" + +msgid "Image" +msgstr "Պատկեր" + +#, 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 "Արտաքին բանալի (տեսակը որոշվում է հարակից դաշտից)" + +msgid "One-to-one relationship" +msgstr "Մեկը մեկին կապ" + +#, python-format +msgid "%(from)s-%(to)s relationship" +msgstr "" + +#, python-format +msgid "%(from)s-%(to)s relationships" +msgstr "" + +msgid "Many-to-many relationship" +msgstr "Մի քանիսը մի քանիսին կապ" + +#. Translators: If found as last label character, these punctuation +#. characters will prevent the default label_suffix to be appended to the +#. label +msgid ":?.!" +msgstr ":?.!" + +msgid "This field is required." +msgstr "Այս դաշտը պարտադիր է" + +msgid "Enter a whole number." +msgstr "Մուտքագրեք ամբողջ թիվ" + +msgid "Enter a valid date." +msgstr "Մուտքագրեք ճիշտ ամսաթիվ" + +msgid "Enter a valid time." +msgstr "Մուտքագրեք ճիշտ ժամանակ" + +msgid "Enter a valid date/time." +msgstr "Մուտքագրեք ճիշտ ամսաթիվ/ժամանակ" + +msgid "Enter a valid duration." +msgstr "Մուտքագրեք ճիշտ տևողություն" + +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "" + +msgid "No file was submitted. Check the encoding type on the form." +msgstr "Ոչ մի ֆայլ չի ուղարկվել։ Ստուգեք ձևաթղթի կոդավորում տեսակը" + +msgid "No file was submitted." +msgstr "Ոչ մի ֆայլ չի ուղարկվել" + +msgid "The submitted file is empty." +msgstr "Ուղարկված ֆայլը դատարկ է" + +#, python-format +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] "" +"Համոզվեք, որ ֆայլի անունը պարունակում է ամենաշատը %(max)d նիշ (այն " +"պարունակում է %(length)d)" +msgstr[1] "" +"Համոզվեք, որ ֆայլի անունը պարունակում է ամենաշատը %(max)d նիշ (այն " +"պարունակում է %(length)d)" + +msgid "Please either submit a file or check the clear checkbox, not both." +msgstr "" +"Ուղարկեք ֆայլ, կամ ակտիվացրեք մաքրելու նշման վանդակը, ոչ թե երկուսը միասին" + +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "Ուղարկեք ճիշտ պատկեր․ Ուղարկված ֆայլը պատկեր չէ, կամ վնասված է" + +#, python-format +msgid "Select a valid choice. %(value)s is not one of the available choices." +msgstr "Ընտրեք ճիշտ տարբերակ։ %(value)s արժեքը չի մտնում ճիշտ արժեքների մեջ" + +msgid "Enter a list of values." +msgstr "Մուտքագրեք արժեքների ցուցակ" + +msgid "Enter a complete value." +msgstr "Մուտքագրեք ամբողջական արժեք" + +msgid "Enter a valid UUID." +msgstr "Մուտքագրեք ճիշտ UUID" + +#. Translators: This is the default suffix added to form field labels +msgid ":" +msgstr ":" + +#, python-format +msgid "(Hidden field %(name)s) %(error)s" +msgstr "(Թաքցված դաշտ %(name)s) %(error)s" + +msgid "ManagementForm data is missing or has been tampered with" +msgstr "Կառավարման ձևաթղթի տվյալները բացակայում են, կամ վնասված են" + +#, python-format +msgid "Please submit %d or fewer forms." +msgid_plural "Please submit %d or fewer forms." +msgstr[0] "Ուղարկեք %d կամ քիչ ձևաթղթեր" +msgstr[1] "Ուղարկեք %d կամ քիչ ձևաթղթեր" + +#, python-format +msgid "Please submit %d or more forms." +msgid_plural "Please submit %d or more forms." +msgstr[0] "Ուղարկեք %d կամ շատ ձևաթղթեր" +msgstr[1] "Ուղարկեք %d կամ շատ ձևաթղթեր" + +msgid "Order" +msgstr "Հերթականություն" + +msgid "Delete" +msgstr "Հեռացնել" + +#, python-format +msgid "Please correct the duplicate data for %(field)s." +msgstr "Ուղղեք %(field)s դաշտի կրկնվող տվյալները" + +#, python-format +msgid "Please correct the duplicate data for %(field)s, which must be unique." +msgstr "Ուղղեք %(field)s դաշտի կրկնվող տվյալները, որոնք պետք է լինեն եզակի" + +#, 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 "" +"Ուղղեք %(field_name)s դաշտի կրկնվող տվյալները, որոնք պետք է լինեն եզակի " +"%(date_field)s-ում %(lookup)s֊ի համար" + +msgid "Please correct the duplicate values below." +msgstr "Ուղղեք կրկնվող տվյալները" + +msgid "The inline value did not match the parent instance." +msgstr "" + +msgid "Select a valid choice. That choice is not one of the available choices." +msgstr "Ընտրեք ճիշտ տարբերակ։ Այս արժեքը չի մտնում ճիշտ արժեքների մեջ" + +#, python-format +msgid "\"%(pk)s\" is not a valid value." +msgstr "" + +#, 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 "Մաքրել" + +msgid "Currently" +msgstr "Տվյալ պահին" + +msgid "Change" +msgstr "Փոխել" + +msgid "Unknown" +msgstr "Անհայտ" + +msgid "Yes" +msgstr "Այո" + +msgid "No" +msgstr "Ոչ" + +msgid "yes,no,maybe" +msgstr "այո,ոչ,միգուցե" + +#, python-format +msgid "%(size)d byte" +msgid_plural "%(size)d bytes" +msgstr[0] "%(size)d բայթ" +msgstr[1] "%(size)d բայթ" + +#, python-format +msgid "%s KB" +msgstr "%s ԿԲ" + +#, python-format +msgid "%s MB" +msgstr "%s ՄԲ" + +#, python-format +msgid "%s GB" +msgstr "%s ԳԲ" + +#, python-format +msgid "%s TB" +msgstr "%s ՏԲ" + +#, python-format +msgid "%s PB" +msgstr "%s ՊԲ" + +msgid "p.m." +msgstr "p.m." + +msgid "a.m." +msgstr "a.m." + +msgid "PM" +msgstr "PM" + +msgid "AM" +msgstr "AM" + +msgid "midnight" +msgstr "կեսգիշեր" + +msgid "noon" +msgstr "կեսօր" + +msgid "Monday" +msgstr "Երկուշաբթի" + +msgid "Tuesday" +msgstr "Երեքշաբթի" + +msgid "Wednesday" +msgstr "Չորեքշաբթի" + +msgid "Thursday" +msgstr "Հինգշաբթի" + +msgid "Friday" +msgstr "Ուրբաթ" + +msgid "Saturday" +msgstr "Շաբաթ" + +msgid "Sunday" +msgstr "Կիրակի" + +msgid "Mon" +msgstr "Երկ" + +msgid "Tue" +msgstr "Երք" + +msgid "Wed" +msgstr "Չրք" + +msgid "Thu" +msgstr "Հնգ" + +msgid "Fri" +msgstr "Ուրբ" + +msgid "Sat" +msgstr "Շբթ" + +msgid "Sun" +msgstr "Կիր" + +msgid "January" +msgstr "Հունվար" + +msgid "February" +msgstr "Փետրվար" + +msgid "March" +msgstr "Մարտ" + +msgid "April" +msgstr "Ապրիլ" + +msgid "May" +msgstr "Մայիս" + +msgid "June" +msgstr "Հունիս" + +msgid "July" +msgstr "Հուլիս" + +msgid "August" +msgstr "Օգոստոս" + +msgid "September" +msgstr "Սեպտեմբեր" + +msgid "October" +msgstr "Հոկտեմբեր" + +msgid "November" +msgstr "Նոյեմբեր" + +msgid "December" +msgstr "Դեկտեմբեր" + +msgid "jan" +msgstr "հուն" + +msgid "feb" +msgstr "փետ" + +msgid "mar" +msgstr "մար" + +msgid "apr" +msgstr "ապր" + +msgid "may" +msgstr "մայ" + +msgid "jun" +msgstr "հուն" + +msgid "jul" +msgstr "հուլ" + +msgid "aug" +msgstr "օգտ" + +msgid "sep" +msgstr "սեպ" + +msgid "oct" +msgstr "հոկ" + +msgid "nov" +msgstr "նոյ" + +msgid "dec" +msgstr "դեկ" + +msgctxt "abbrev. month" +msgid "Jan." +msgstr "Հուն․" + +msgctxt "abbrev. month" +msgid "Feb." +msgstr "Փետ․" + +msgctxt "abbrev. month" +msgid "March" +msgstr "Մարտ" + +msgctxt "abbrev. month" +msgid "April" +msgstr "Մարտ" + +msgctxt "abbrev. month" +msgid "May" +msgstr "Մայիս" + +msgctxt "abbrev. month" +msgid "June" +msgstr "Հունիս" + +msgctxt "abbrev. month" +msgid "July" +msgstr "Հուլիս" + +msgctxt "abbrev. month" +msgid "Aug." +msgstr "Օգոստ․" + +msgctxt "abbrev. month" +msgid "Sept." +msgstr "Սեպտ․" + +msgctxt "abbrev. month" +msgid "Oct." +msgstr "Հոկտ․" + +msgctxt "abbrev. month" +msgid "Nov." +msgstr "Նոյ․" + +msgctxt "abbrev. month" +msgid "Dec." +msgstr "Դեկ․" + +msgctxt "alt. month" +msgid "January" +msgstr "Հունվար" + +msgctxt "alt. month" +msgid "February" +msgstr "Փետրվար" + +msgctxt "alt. month" +msgid "March" +msgstr "Մարտ" + +msgctxt "alt. month" +msgid "April" +msgstr "Ապրիլ" + +msgctxt "alt. month" +msgid "May" +msgstr "Մայիս" + +msgctxt "alt. month" +msgid "June" +msgstr "Հունիս" + +msgctxt "alt. month" +msgid "July" +msgstr "Հուլիս" + +msgctxt "alt. month" +msgid "August" +msgstr "Օգոստոս" + +msgctxt "alt. month" +msgid "September" +msgstr "Սեպտեմբեր" + +msgctxt "alt. month" +msgid "October" +msgstr "Հոկտեմբեր" + +msgctxt "alt. month" +msgid "November" +msgstr "Նոյեմբեր" + +msgctxt "alt. month" +msgid "December" +msgstr "Դեկտեմբեր" + +msgid "This is not a valid IPv6 address." +msgstr "Սա ճիշտ IPv6 հասցե չէ" + +#, python-format +msgctxt "String to return when truncating text" +msgid "%(truncated_text)s..." +msgstr "%(truncated_text)s..." + +msgid "or" +msgstr "կամ" + +#. Translators: This string is used as a separator between list elements +msgid ", " +msgstr ", " + +#, python-format +msgid "%d year" +msgid_plural "%d years" +msgstr[0] "%d տարի" +msgstr[1] "%d տարի" + +#, python-format +msgid "%d month" +msgid_plural "%d months" +msgstr[0] "%d ամիս" +msgstr[1] "%d ամիս" + +#, python-format +msgid "%d week" +msgid_plural "%d weeks" +msgstr[0] "%d շաբաթ" +msgstr[1] "%d շաբաթ" + +#, python-format +msgid "%d day" +msgid_plural "%d days" +msgstr[0] "%d օր" +msgstr[1] "%d օր" + +#, python-format +msgid "%d hour" +msgid_plural "%d hours" +msgstr[0] "%d ժամ" +msgstr[1] "%d ժամ" + +#, python-format +msgid "%d minute" +msgid_plural "%d minutes" +msgstr[0] "%d րոպե" +msgstr[1] "%d րոպե" + +msgid "0 minutes" +msgstr "0 րոպե" + +msgid "Forbidden" +msgstr "Արգելված" + +msgid "CSRF verification failed. Request aborted." +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 " +"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." +msgstr "" + +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 "" +"Դուք տեսնում եք այս հաղորդագրությունը, քանի որ այս կայքը ձևաթերթերը " +"ուղարկելու համար պահանջում է CSRF cookie։ Այն անհրաժեշտ է անվտանգության " +"նկատառումներից ելնելով, համոզվելու համար, որ ձեր բրաուզերը չի գտնվում երրորդ " +"անձանց կառավարման տակ։" + +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 "" +"Դուք անջատել եք cookies֊ների օգտագործումը ձեր բրաուզերից։ Միացրեք այն այս " +"կայքի կամ 'same-origin' հարցումների համար" + +msgid "More information is available with DEBUG=True." +msgstr "Ավելի մանրամասն տեղեկությունը հասանելի է DEBUG=True֊ի ժամանակ" + +msgid "No year specified" +msgstr "Տարին նշված չէ" + +msgid "Date out of range" +msgstr "" + +msgid "No month specified" +msgstr "Ամիսը նշված չէ" + +msgid "No day specified" +msgstr "Օրը նշված չէ" + +msgid "No week specified" +msgstr "Շաբաթը նշված չէ" + +#, python-format +msgid "No %(verbose_name_plural)s available" +msgstr "Ոչ մի %(verbose_name_plural)s հասանելի չէ" + +#, python-format +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 արժեք" + +#, python-format +msgid "Invalid date string '%(datestr)s' given format '%(format)s'" +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 "Եջը չի պարունակում 'last' և չի կարող վերափոխվել int֊ի" + +#, python-format +msgid "Invalid page (%(page_number)s): %(message)s" +msgstr "Սխալ էջ (%(page_number)s): %(message)s" + +#, python-format +msgid "Empty list and '%(class_name)s.allow_empty' is False." +msgstr "Դատարկ ցուցակ և '%(class_name)s.allow_empty'֊ն ունի False արժեք" + +msgid "Directory indexes are not allowed here." +msgstr "Կատալոգների ինդեքսավորումը թույլատրված չէ այստեղ" + +#, python-format +msgid "\"%(path)s\" does not exist" +msgstr "\"%(path)s\" գոյություն չունի" + +#, python-format +msgid "Index of %(directory)s" +msgstr "%(directory)s֊ի ինդեքսը" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/ia/LC_MESSAGES/django.mo b/django/conf/locale/ia/LC_MESSAGES/django.mo index f1d5ffbbf098..e92b8425fdf1 100644 Binary files a/django/conf/locale/ia/LC_MESSAGES/django.mo and b/django/conf/locale/ia/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/ia/LC_MESSAGES/django.po b/django/conf/locale/ia/LC_MESSAGES/django.po index 406dcf0024e6..10f0b35eae75 100644 --- a/django/conf/locale/ia/LC_MESSAGES/django.po +++ b/django/conf/locale/ia/LC_MESSAGES/django.po @@ -1,13 +1,13 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Martijn Dekker , 2012,2014 +# Martijn Dekker , 2012,2014,2016 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Interlingua (http://www.transifex.com/django/django/language/" "ia/)\n" @@ -60,7 +60,7 @@ msgid "German" msgstr "germano" msgid "Lower Sorbian" -msgstr "" +msgstr "sorabo inferior" msgid "Greek" msgstr "greco" @@ -84,7 +84,7 @@ msgid "Argentinian Spanish" msgstr "espaniol argentin" msgid "Colombian Spanish" -msgstr "" +msgstr "espaniol colombian" msgid "Mexican Spanish" msgstr "espaniol mexican" @@ -117,7 +117,7 @@ msgid "Irish" msgstr "irlandese" msgid "Scottish Gaelic" -msgstr "" +msgstr "gaelico scotese" msgid "Galician" msgstr "galiciano" @@ -132,7 +132,7 @@ msgid "Croatian" msgstr "croato" msgid "Upper Sorbian" -msgstr "" +msgstr "sorabo superior" msgid "Hungarian" msgstr "hungaro" @@ -189,13 +189,13 @@ msgid "Mongolian" msgstr "mongolico" msgid "Marathi" -msgstr "" +msgstr "marathi" msgid "Burmese" -msgstr "" +msgstr "burmese" msgid "Norwegian Bokmål" -msgstr "" +msgstr "norvegianio bokmål" msgid "Nepali" msgstr "nepali" @@ -207,7 +207,7 @@ msgid "Norwegian Nynorsk" msgstr "norvegiano, nynorsk" msgid "Ossetic" -msgstr "" +msgstr "ossetico" msgid "Punjabi" msgstr "punjabi" @@ -264,7 +264,7 @@ msgid "Tatar" msgstr "tartaro" msgid "Udmurt" -msgstr "" +msgstr "udmurto" msgid "Ukrainian" msgstr "ukrainiano" @@ -282,7 +282,7 @@ msgid "Traditional Chinese" msgstr "chinese traditional" msgid "Messages" -msgstr "" +msgstr "Messages" msgid "Site Maps" msgstr "Mappas de sito" @@ -293,6 +293,15 @@ msgstr "Files static" msgid "Syndication" msgstr "Syndication" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "Specifica un valor valide." @@ -305,6 +314,7 @@ msgstr "Specifica un numero integre valide." msgid "Enter a valid email address." msgstr "Specifica un adresse de e-mail valide." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -315,6 +325,8 @@ msgid "" "Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or " "hyphens." msgstr "" +"Specifica un 'slug' valide, consistente de litteras, numeros, tractos de " +"sublineamento o tractos de union in Unicode." msgid "Enter a valid IPv4 address." msgstr "Specifica un adresse IPv4 valide." @@ -372,14 +384,16 @@ msgstr[1] "" #, 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[0] "Assecura te que il non ha plus de %(max)s digito in total." +msgstr[1] "Assecura te que il non ha plus de %(max)s digitos in total." #, 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] "" +"Assecura te que il non ha plus de %(max)s cifra post le comma decimal." msgstr[1] "" +"Assecura te que il non ha plus de %(max)s cifras post le comma decimal." #, python-format msgid "" @@ -387,7 +401,18 @@ msgid "" msgid_plural "" "Ensure that there are no more than %(max)s digits before the decimal point." msgstr[0] "" +"Assecura te que il non ha plus de %(max)s cifra ante le comma decimal." msgstr[1] "" +"Assecura te que il non ha plus de %(max)s cifras ante le comma decimal." + +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" msgid "and" msgstr "e" @@ -470,19 +495,23 @@ msgid "" "'%(value)s' value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." "uuuuuu]][TZ] format." msgstr "" +"Le valor '%(value)s' es in un formato invalide. Debe esser in formato AAAA-" +"MM-DD HH:MM[:ss[.uuuuuu]][FH]." #, 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 "" +"Le valor '%(value)s' es in le formato correcte (YYYY-MM-DD HH:MM[:ss[." +"uuuuuu]][FH]) ma es un data/hora invalide." msgid "Date (with time)" msgstr "Data (con hora)" #, python-format msgid "'%(value)s' value must be a decimal number." -msgstr "" +msgstr "Le valor '%(value)s' debe esser un numero decimal." msgid "Decimal number" msgstr "Numero decimal" @@ -492,9 +521,11 @@ msgid "" "'%(value)s' value has an invalid format. It must be in [DD] [HH:[MM:]]ss[." "uuuuuu] format." msgstr "" +"Le valor '%(value)s' es in un formato invalide. Debe esser in formato [DD] " +"[HH:[MM:]]ss[.uuuuuu]." msgid "Duration" -msgstr "" +msgstr "Duration" msgid "Email address" msgstr "Adresse de e-mail" @@ -504,7 +535,7 @@ msgstr "Cammino de file" #, python-format msgid "'%(value)s' value must be a float." -msgstr "" +msgstr "Le valor '%(value)s' debe esser un numero a comma flottante." msgid "Floating point number" msgstr "Numero a comma flottante" @@ -517,7 +548,7 @@ msgstr "Adresse IP" #, python-format msgid "'%(value)s' value must be either None, True or False." -msgstr "" +msgstr "Le valor '%(value)s'' debe esser None/Nulle, True/Ver o False." msgid "Boolean (Either True, False or None)" msgstr "Booleano (ver, false o nulle)" @@ -543,12 +574,16 @@ msgid "" "'%(value)s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " "format." msgstr "" +"Le valor '%(value)s' es in un formato invalide. Debe esser in formato 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 "" +"Le valor '%(value)s' es in le formato correcte (HH:MM[:ss[.uuuuuu]]) ma es " +"un hora invalide." msgid "Time" msgstr "Hora" @@ -557,11 +592,11 @@ msgid "URL" msgstr "URL" msgid "Raw binary data" -msgstr "" +msgstr "Datos binari crude" #, python-format msgid "'%(value)s' is not a valid UUID." -msgstr "" +msgstr "'%(value)s' non es un UUID valide." msgid "File" msgstr "File" @@ -571,7 +606,7 @@ msgstr "Imagine" #, python-format msgid "%(model)s instance with %(field)s %(value)r does not exist." -msgstr "" +msgstr "Le instantia de %(model)s con %(field)s %(value)r non existe." msgid "Foreign Key (type determined by related field)" msgstr "Clave estranier (typo determinate per le campo associate)" @@ -581,11 +616,11 @@ msgstr "Relation un a un" #, python-format msgid "%(from)s-%(to)s relationship" -msgstr "" +msgstr "Relation %(from)s a %(to)s" #, python-format msgid "%(from)s-%(to)s relationships" -msgstr "" +msgstr "Relationes %(from)s a %(to)s" msgid "Many-to-many relationship" msgstr "Relation multes a multes" @@ -615,7 +650,7 @@ msgid "Enter a valid date/time." msgstr "Specifica un data e hora valide." msgid "Enter a valid duration." -msgstr "" +msgstr "Specifica un duration valide." msgid "No file was submitted. Check the encoding type on the form." msgstr "" @@ -633,7 +668,10 @@ 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] "" +"Assecura te que iste valor ha al plus %(max)d character (illo ha %(length)d)." msgstr[1] "" +"Assecura te que iste valor ha al plus %(max)d characteres (illo ha " +"%(length)d)." msgid "Please either submit a file or check the clear checkbox, not both." msgstr "Per favor o submitte un file o marca le quadrato \"rader\", non ambes." @@ -654,10 +692,10 @@ msgid "Enter a list of values." msgstr "Scribe un lista de valores." msgid "Enter a complete value." -msgstr "" +msgstr "Specifica un valor complete." msgid "Enter a valid UUID." -msgstr "" +msgstr "Specifica un UUID valide." #. Translators: This is the default suffix added to form field labels msgid ":" @@ -665,22 +703,22 @@ msgstr "" #, python-format msgid "(Hidden field %(name)s) %(error)s" -msgstr "" +msgstr "(Campo celate %(name)s) %(error)s" msgid "ManagementForm data is missing or has been tampered with" -msgstr "" +msgstr "Le datos ManagementForm manca o ha essite manipulate" #, python-format msgid "Please submit %d or fewer forms." msgid_plural "Please submit %d or fewer forms." -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Per favor, submitte %d o minus formularios." +msgstr[1] "Per favor, submitte %d o minus formularios." #, python-format msgid "Please submit %d or more forms." msgid_plural "Please submit %d or more forms." -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Per favor, submitte %d o plus formularios." +msgstr[1] "Per favor, submitte %d o plus formularios." msgid "Order" msgstr "Ordine" @@ -708,10 +746,8 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Per favor corrige le sequente valores duplicate." -msgid "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." msgstr "" -"Le clave estranier incorporate non correspondeva al clave primari del " -"instantia genitor." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" @@ -719,7 +755,7 @@ msgstr "" "disponibile." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -730,15 +766,15 @@ msgstr "" "%(datetime)s non poteva esser interpretate in le fuso horari " "%(current_timezone)s; illo pote esser ambigue o illo pote non exister." +msgid "Clear" +msgstr "Rader" + msgid "Currently" msgstr "Actualmente" msgid "Change" msgstr "Cambiar" -msgid "Clear" -msgstr "Rader" - msgid "Unknown" msgstr "Incognite" @@ -1006,7 +1042,7 @@ msgid "December" msgstr "Decembre" msgid "This is not a valid IPv6 address." -msgstr "" +msgstr "Isto non es un adresse IPv6 valide." #, python-format msgctxt "String to return when truncating text" @@ -1023,47 +1059,47 @@ msgstr ", " #, python-format msgid "%d year" msgid_plural "%d years" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%d anno" +msgstr[1] "%d annos" #, python-format msgid "%d month" msgid_plural "%d months" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%d mense" +msgstr[1] "%d menses" #, python-format msgid "%d week" msgid_plural "%d weeks" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%d septimana" +msgstr[1] "%d septimanas" #, python-format msgid "%d day" msgid_plural "%d days" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%d die" +msgstr[1] "%d dies" #, python-format msgid "%d hour" msgid_plural "%d hours" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%d horas" +msgstr[1] "%d horas" #, python-format msgid "%d minute" msgid_plural "%d minutes" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%d minuta" +msgstr[1] "%d minutas" msgid "0 minutes" -msgstr "" +msgstr "0 minutas" msgid "Forbidden" -msgstr "" +msgstr "Prohibite" msgid "CSRF verification failed. Request aborted." -msgstr "" +msgstr "Verification CSRF fallite. Requesta abortate." msgid "" "You are seeing this message because this HTTPS site requires a 'Referer " @@ -1071,49 +1107,54 @@ msgid "" "required for security reasons, to ensure that your browser is not being " "hijacked by third parties." msgstr "" +"Tu vide iste message perque iste sito HTTPS require que un capite 'Referer' " +"sia inviate per tu navigator Web, ma nulle tal capite esseva inviate. Iste " +"capite es requirite pro motivos de securitate, pro assecurar que tu " +"navigator non es sequestrate per tertie personas." 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 "" +"Si tu ha disactivate le invio de capites 'Referer' in tu navigator, per " +"favor re-activa isto, al minus pro iste sito, o pro connexiones HTTPS, o pro " +"requestas del 'mesme origine'." + +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." +msgstr "" 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 "" +"Tu vide iste message perque iste sito require un cookie CSRF durante le " +"submission de formularios. Iste cookie es requirite pro motivos de " +"securitate, pro assecurar que tu navigator non es sequestrate per tertie " +"personas." 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 "" +"Si tu ha disactivate le cookies in tu navigator, per favor re-activa los, al " +"minus pro iste sito, o pro requestas del 'mesme origine'." msgid "More information is available with DEBUG=True." -msgstr "" - -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" +msgstr "Plus information es disponibile con DEBUG=True." msgid "No year specified" msgstr "Nulle anno specificate" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "Nulle mense specificate" @@ -1148,7 +1189,7 @@ msgstr "Pagina non es 'last', ni pote esser convertite in un numero integre." #, python-format msgid "Invalid page (%(page_number)s): %(message)s" -msgstr "" +msgstr "Pagina invalide (%(page_number)s): %(message)s" #, python-format msgid "Empty list and '%(class_name)s.allow_empty' is False." @@ -1164,3 +1205,41 @@ msgstr "\"%(path)s\" non existe" #, python-format msgid "Index of %(directory)s" msgstr "Indice de %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/id/LC_MESSAGES/django.mo b/django/conf/locale/id/LC_MESSAGES/django.mo index 353e61e0a4a9..6d0b6776ff9a 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 bc782f34e32d..9fbe179488e4 100644 --- a/django/conf/locale/id/LC_MESSAGES/django.po +++ b/django/conf/locale/id/LC_MESSAGES/django.po @@ -1,20 +1,23 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Fery Setiawan , 2015-2016 +# Adiyat Mubarak , 2017 +# Claude Paroz , 2018 +# Fery Setiawan , 2015-2019 # Jannis Leidel , 2011 # M Asep Indrayana , 2015 # oon arfiandwi , 2016 # rodin , 2011 -# rodin , 2013-2015 -# Sutrisno Efendi , 2015 +# rodin , 2013-2016 +# sage , 2018-2019 +# Sutrisno Efendi , 2015,2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-08-23 04:48+0000\n" -"Last-Translator: Fery Setiawan \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-05-05 04:23+0000\n" +"Last-Translator: sage \n" "Language-Team: Indonesian (http://www.transifex.com/django/django/language/" "id/)\n" "MIME-Version: 1.0\n" @@ -66,7 +69,7 @@ msgid "German" msgstr "Jerman" msgid "Lower Sorbian" -msgstr "Lower Sorbian" +msgstr "Sorbian Bawah" msgid "Greek" msgstr "Yunani" @@ -75,7 +78,7 @@ msgid "English" msgstr "Inggris" msgid "Australian English" -msgstr "Australia Inggris" +msgstr "Inggris Australia" msgid "British English" msgstr "Inggris Britania" @@ -90,7 +93,7 @@ msgid "Argentinian Spanish" msgstr "Spanyol Argentina" msgid "Colombian Spanish" -msgstr "Kolombia Spanyol" +msgstr "Spanyol Kolombia" msgid "Mexican Spanish" msgstr "Spanyol Meksiko" @@ -123,7 +126,7 @@ msgid "Irish" msgstr "Irlandia" msgid "Scottish Gaelic" -msgstr "Gaelik Skotlandia" +msgstr "Skolandia Gaelik" msgid "Galician" msgstr "Galicia" @@ -138,11 +141,14 @@ msgid "Croatian" msgstr "Kroasia" msgid "Upper Sorbian" -msgstr "Upper Sorbian" +msgstr "Sorbian Atas" msgid "Hungarian" msgstr "Hungaria" +msgid "Armenian" +msgstr "Armenian" + msgid "Interlingua" msgstr "Interlingua" @@ -150,7 +156,7 @@ msgid "Indonesian" msgstr "Indonesia" msgid "Ido" -msgstr "ido" +msgstr "Ido" msgid "Icelandic" msgstr "Islandia" @@ -164,6 +170,9 @@ msgstr "Jepang" msgid "Georgian" msgstr "Georgia" +msgid "Kabyle" +msgstr "Kabyle" + msgid "Kazakh" msgstr "Kazakhstan" @@ -201,7 +210,7 @@ msgid "Burmese" msgstr "Burma" msgid "Norwegian Bokmål" -msgstr "Bokmål Norwegian" +msgstr "Norwegia Bokmål" msgid "Nepali" msgstr "Nepal" @@ -294,10 +303,19 @@ msgid "Site Maps" msgstr "Peta Situs" msgid "Static Files" -msgstr "Bidang Tetap" +msgstr "Berkas statis" msgid "Syndication" -msgstr "Kongsi" +msgstr "Sindikasi" + +msgid "That page number is not an integer" +msgstr "Nomor halaman itu bukan sebuah integer" + +msgid "That page number is less than 1" +msgstr "Nomor halaman itu kurang dari 1" + +msgid "That page contains no results" +msgstr "Tidak ada hasil untuk halaman tersebut" msgid "Enter a valid value." msgstr "Masukkan nilai yang valid." @@ -311,6 +329,7 @@ msgstr "Masukan sebuah bilangan bulat yang benar" msgid "Enter a valid email address." msgstr "Masukkan alamat email yang valid." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -321,8 +340,8 @@ msgid "" "Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or " "hyphens." msgstr "" -"Masukkan 'slug' yang terdiri dari huruf, bilangan, garis bawah, atau tanda " -"minus." +"Masukkan 'slug' valid yang terdiri dari karakter, bilangan, garis bawah, " +"atau tanda minus." msgid "Enter a valid IPv4 address." msgstr "Masukkan alamat IPv4 yang valid." @@ -370,6 +389,9 @@ msgstr[0] "" "Pastikan nilai ini mengandung paling banyak %(limit_value)d karakter " "(sekarang %(show_value)d karakter)." +msgid "Enter a number." +msgstr "Masukkan sebuah bilangan." + #, 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." @@ -389,6 +411,17 @@ msgstr[0] "" "Pastikan jumlah angka sebelum desimal pada bilangan tidak memiliki lebih " "dari %(max)s angka." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Tambahan berkas '%(extension)s' tidak diizinkan. Tambahan diizinkan adalah: " +"'%(allowed_extensions)s'. " + +msgid "Null characters are not allowed." +msgstr "Karakter null tidak diperbolehkan." + msgid "and" msgstr "dan" @@ -398,7 +431,7 @@ msgstr "%(model_name)s dengan %(field_labels)s ini tidak ada." #, python-format msgid "Value %(value)r is not a valid choice." -msgstr "Nilai %(value)r bukan pilihan yang sah." +msgstr "Nilai %(value)r bukan pilihan yang valid." msgid "This field cannot be null." msgstr "Field ini tidak boleh null." @@ -427,14 +460,18 @@ msgstr "Bilangan Asli" #, python-format msgid "'%(value)s' value must be an integer." -msgstr "%(value)s' nilai harus lah sebuah bilangan bulat" +msgstr "%(value)s' nilai harus merupakan bilangan bulat." msgid "Big (8 byte) integer" msgstr "Bilangan asli raksasa (8 byte)" #, python-format msgid "'%(value)s' value must be either True or False." -msgstr "nilai '%(value)s' haruslah berupa Benar atau Salah" +msgstr "Nilai '%(value)s' haruslah bernilai Benar atau Salah." + +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "Nilai '%(value)s' harus True, False, atau None." msgid "Boolean (Either True or False)" msgstr "Nilai Boolean (Salah satu dari True atau False)" @@ -451,16 +488,16 @@ msgid "" "'%(value)s' value has an invalid date format. It must be in YYYY-MM-DD " "format." msgstr "" -"nilai '%(value)s' tidak sesuai format penanggalan. Formatnya harus dalam " -"YYYY-MM-DD." +"Nilai '%(value)s' tidak sesuai format penanggalan. Formatnya harus dalam " +"TTTT-BB-HH." #, python-format msgid "" "'%(value)s' value has the correct format (YYYY-MM-DD) but it is an invalid " "date." msgstr "" -"nilai '%(value)s' memiliki format yang sesuai (YYYY-MM-DD) tetap tanggalnya " -"tidak benar." +"Nilai '%(value)s' memiliki format yang sesuai (TTTT-BB-HH) tetap tanggalnya " +"tidak valid." msgid "Date (without time)" msgstr "Tanggal (tanpa waktu)" @@ -470,8 +507,8 @@ msgid "" "'%(value)s' value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." "uuuuuu]][TZ] format." msgstr "" -"Nilai '%(value)s' mempunyai bentuk tidak sah. Itu harus dalam bentuk YYYY-MM-" -"DD HH:MM[:ss[.uuuuuu]][TZ]." +"Format nilai '%(value)s' tidak valid. Formatnya harus dalam TTTT-BB-HH JJ:" +"MM[:dd[.mmmmmm]][TZ]." #, python-format msgid "" @@ -486,7 +523,7 @@ msgstr "Tanggal (dengan waktu)" #, python-format msgid "'%(value)s' value must be a decimal number." -msgstr "Nilai '%(value)s' haruslah sebuah bilangan desimal" +msgstr "Nilai '%(value)s' haruslah berupa bilangan desimal." msgid "Decimal number" msgstr "Bilangan desimal" @@ -573,6 +610,9 @@ msgstr "Data biner mentah" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' bukan UUID yang benar" +msgid "Universally unique identifier" +msgstr "Penciri unik secara universal" + msgid "File" msgstr "Berkas" @@ -612,9 +652,6 @@ msgstr "Bidang ini tidak boleh kosong." msgid "Enter a whole number." msgstr "Masukkan keseluruhan angka bilangan." -msgid "Enter a number." -msgstr "Masukkan sebuah bilangan." - msgid "Enter a valid date." msgstr "Masukkan tanggal yang valid." @@ -625,7 +662,11 @@ msgid "Enter a valid date/time." msgstr "Masukkan tanggal/waktu yang valid." msgid "Enter a valid duration." -msgstr "Masukan sebuah durasi waktu yang benar" +msgstr "Masukan durasi waktu yang benar." + +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Jumlah hari harus diantara {min_days} dan {max_days}." msgid "No file was submitted. Check the encoding type on the form." msgstr "Tidak ada berkas yang dikirimkan. Periksa tipe pengaksaraan formulir." @@ -669,7 +710,7 @@ msgid "Enter a complete value." msgstr "Masukan sebuah nilai dengan komplit" msgid "Enter a valid UUID." -msgstr "Masukan sebuah UUID yang benar" +msgstr "Masukan UUID yang benar." #. Translators: This is the default suffix added to form field labels msgid ":" @@ -717,9 +758,8 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Perbaiki nilai ganda di bawah ini." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "" -"Kunci asing 'inline' tidak cocok dengan kunci utama 'instance' milik induk." +msgid "The inline value did not match the parent instance." +msgstr "Nilai dibarisan tidak cocok dengan instance induk." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" @@ -727,8 +767,8 @@ msgstr "" "yang tersedia." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" bukan nilai yang benar untuk kunci utama." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"1%(pk)s\" bukan nilai yang benar." #, python-format msgid "" @@ -738,15 +778,15 @@ msgstr "" "%(datetime)s tidak dapat diinterpretasikan pada zona waktu " "%(current_timezone)s; mungkin nilainya ambigu atau mungkin tidak ada." +msgid "Clear" +msgstr "Hapus" + msgid "Currently" msgstr "Saat ini" msgid "Change" msgstr "Ubah" -msgid "Clear" -msgstr "Hapus" - msgid "Unknown" msgstr "Tidak diketahui" @@ -1017,8 +1057,8 @@ msgstr "Ini bukan alamat IPv6 yang benar" #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s…" msgid "or" msgstr "atau" @@ -1064,7 +1104,7 @@ msgid "Forbidden" msgstr "Terlarang" msgid "CSRF verification failed. Request aborted." -msgstr "verifikasi CSRF gagal, permintann di batalkan" +msgstr "Verifikasi CSRF gagal, Permintaan dibatalkan." msgid "" "You are seeing this message because this HTTPS site requires a 'Referer " @@ -1072,67 +1112,60 @@ msgid "" "required for security reasons, to ensure that your browser is not being " "hijacked by third parties." msgstr "" -"Anda melihat pesan ini karena situs HTTP ini membutuhkan 'Referer header' " -"dikirim dari Web browser anda, tapi tidak terkirim. Header tersebut wajib " -"karena alasan keamanan, untuk memastikan bahwa browser anda tidak dibajak " -"oleh pihak ketiga." +"Anda melihat pesan ini karena situs HTTP ini membutuhkan header 'Referrer' " +"dikirim dari web browser Anda, tetapi tidak terkirim. Header tersebut " +"dibutuhkan karena alasan keamanan, untuk memastikan bahwa browser Anda tidak " +"dibajak oleh pihak ketiga." 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 "" -"Jika anda menonaktifkan 'Referer' headers pada konfigurasi browser anda, " +"Jika Anda menonaktifkan header 'Referrer' pada konfigurasi browser Anda, " "mohon aktfikan kembali, setidaknya untuk situs ini atau untuk koneksi HTTPS, " -"atau untuk 'same-origin' requests." +"atau untuk request '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." +msgstr "" +"Jika Anda menggunakan tag " +"atau menyertakan kepala 'Referrer-Policy: no-referrer', harap hapus mereka. " +"Perlindungan CSRF membutuhkan kepala 'Referrer' untuk melakukan pemeriksaan " +"pengarahan ketat. Jika Anda khawatir mengenai privasi, gunakan cara lain " +"seperti untuk tautan pada situs pihak ketiga." 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 "" -"Kamu melihat pesan ini karena situs ini membutuhkan sebuah CSRF cookie " -"ketika mengirimkan sebuah form. Cookie ini dibutuhkan for alasalan keamanan, " -"untuk memastikan bahwa browser Anda tidak sedang dibajak oleh pihak ketiga." +"Anda melihat pesan ini karena situs ini membutuhkan sebuah CSRF cookie " +"ketika mengirimkan sebuah formulir. Cookie ini dibutuhkan untuk alasan " +"keamanan, untuk memastikan bahwa browser Anda tidak sedang dibajak oleh " +"pihak ketiga." 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 "" -"Jika browser kamu memiliki konfigurasi untuk menyalakan cookies, maka " -"nyalakan kembali, setidak nya untuk website ini." +"Jika Anda telah mengatur browser Anda untuk menonaktifkan cookies, maka " +"aktifkanlah kembali, setidaknya untuk website ini, atau untuk request 'same-" +"origin'." msgid "More information is available with DEBUG=True." msgstr "Informasi lebih lanjut tersedia dengan DEBUG=True" -msgid "Welcome to Django" -msgstr "Selamat datang di Django" - -msgid "It worked!" -msgstr "Berhasil!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Selamat untuk halaman Django pertama Anda." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Tentu saja, anda belum menyelesaikan pekerjaan apapun. Selanjutnya, mulai " -"aplikasi pertama anda dengan ekseskusi python manage.py startapp " -"[app_label]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Anda dapat meilhat pesan ini karena ada DEBUG = True di berkas " -"konfigurasi Django dan belum dikonfigurasi untuk suatu URL apapun. Get to " -"work!" - msgid "No year specified" msgstr "Tidak ada tahun dipilih" +msgid "Date out of range" +msgstr "Tanggal diluar kisaran" + msgid "No month specified" msgstr "Tidak ada bulan dipilih" @@ -1185,3 +1218,48 @@ msgstr "\"%(path)s\" tidak ada" #, python-format msgid "Index of %(directory)s" msgstr "Daftar isi %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" +"Django: Kerangka kerja Web untuk sang perfeksionis dengan tenggat waktu." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Lihat catatan rilis untuk Django %(version)s" + +msgid "The install worked successfully! Congratulations!" +msgstr "Selamat! Pemasangan berjalan lancar!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Anda sedang melihat halaman ini karena DEBUG=True berada di berkas pengaturan Anda dan Anda belum " +"mengonfigurasi URL apa pun." + +msgid "Django Documentation" +msgstr "Dokumentasi Django" + +msgid "Topics, references, & how-to's" +msgstr "Topik, referensi & cara pemakaian" + +msgid "Tutorial: A Polling App" +msgstr "Tutorial: Sebuah aplikasi jajak pendapat" + +msgid "Get started with Django" +msgstr "Memulai dengan Django" + +msgid "Django Community" +msgstr "Komunitas Django" + +msgid "Connect, get help, or contribute" +msgstr "Terhubung, minta bantuan, atau membantu" diff --git a/django/conf/locale/id/formats.py b/django/conf/locale/id/formats.py index dc3dbf9dd805..1458230c28f6 100644 --- a/django/conf/locale/id/formats.py +++ b/django/conf/locale/id/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j N Y' DATETIME_FORMAT = "j N Y, G.i" TIME_FORMAT = 'G.i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d-%m-%y', '%d/%m/%y', # '25-10-09', 25/10/09' '%d-%m-%Y', '%d/%m/%Y', # '25-10-2009', 25/10/2009' diff --git a/django/conf/locale/io/LC_MESSAGES/django.mo b/django/conf/locale/io/LC_MESSAGES/django.mo index d2d6b480b87e..de6c03c9cdad 100644 Binary files a/django/conf/locale/io/LC_MESSAGES/django.mo and b/django/conf/locale/io/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/io/LC_MESSAGES/django.po b/django/conf/locale/io/LC_MESSAGES/django.po index dbdefd1db1ca..3efe6fdb0b20 100644 --- a/django/conf/locale/io/LC_MESSAGES/django.po +++ b/django/conf/locale/io/LC_MESSAGES/django.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Ido (http://www.transifex.com/django/django/language/io/)\n" "MIME-Version: 1.0\n" @@ -292,6 +292,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "Skribez valida datumo." @@ -304,6 +313,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "Skribez valida e-posto adreso." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -388,6 +398,15 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "e" @@ -702,16 +721,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Korektigez la duopligata datumi infre." -msgid "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." msgstr "" -"La interna exterklefo ne koincidis kun la prima klefo dil patro instanco." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" "Selektez valida selekto. Ita selekto ne esas un de la disponebla selekti." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -722,15 +740,15 @@ msgstr "" "La %(datetime)s ne povis esar interpretata en la horala zono " "%(current_timezone)s; forsan, olu esas ambigua o ne existas." +msgid "Clear" +msgstr "Vakuigar" + msgid "Currently" msgstr "Aktuale" msgid "Change" msgstr "Modifikar" -msgid "Clear" -msgstr "Vakuigar" - msgid "Unknown" msgstr "Nekonocata" @@ -1070,6 +1088,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1084,28 +1110,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "La yaro ne specizigesis" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "La monato ne specizigesis" @@ -1156,3 +1166,41 @@ msgstr "\"%(path)s\" ne existas" #, python-format msgid "Index of %(directory)s" msgstr "Indexi di %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/is/LC_MESSAGES/django.mo b/django/conf/locale/is/LC_MESSAGES/django.mo index 1191517eedc9..4859aafe2253 100644 Binary files a/django/conf/locale/is/LC_MESSAGES/django.mo and b/django/conf/locale/is/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/is/LC_MESSAGES/django.po b/django/conf/locale/is/LC_MESSAGES/django.po index 30031a8f5f87..60dc67ef295c 100644 --- a/django/conf/locale/is/LC_MESSAGES/django.po +++ b/django/conf/locale/is/LC_MESSAGES/django.po @@ -1,18 +1,20 @@ # This file is distributed under the same license as the Django package. # # Translators: -# gudmundur , 2011 +# gudmundur , 2011 # Hafsteinn Einarsson , 2011-2012 # Jannis Leidel , 2011 +# Matt R, 2018 # saevarom , 2011 # saevarom , 2013,2015 +# Thordur Sigurdsson , 2016-2019 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 15:48+0000\n" +"Last-Translator: Thordur Sigurdsson \n" "Language-Team: Icelandic (http://www.transifex.com/django/django/language/" "is/)\n" "MIME-Version: 1.0\n" @@ -22,13 +24,13 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n % 10 != 1 || n % 100 == 11);\n" msgid "Afrikaans" -msgstr "" +msgstr "Afríkanska" msgid "Arabic" msgstr "Arabíska" msgid "Asturian" -msgstr "" +msgstr "Astúríska" msgid "Azerbaijani" msgstr "Aserbaídsjíska" @@ -37,13 +39,13 @@ msgid "Bulgarian" msgstr "Búlgarska" msgid "Belarusian" -msgstr "" +msgstr "Hvítrússneska" msgid "Bengali" msgstr "Bengalska" msgid "Breton" -msgstr "" +msgstr "Bretónska" msgid "Bosnian" msgstr "Bosníska" @@ -64,7 +66,7 @@ msgid "German" msgstr "Þýska" msgid "Lower Sorbian" -msgstr "" +msgstr "Neðri sorbíska" msgid "Greek" msgstr "Gríska" @@ -73,13 +75,13 @@ msgid "English" msgstr "Enska" msgid "Australian English" -msgstr "" +msgstr "Áströlsk enska" msgid "British English" msgstr "Bresk enska" msgid "Esperanto" -msgstr "" +msgstr "Esperanto" msgid "Spanish" msgstr "Spænska" @@ -88,19 +90,19 @@ msgid "Argentinian Spanish" msgstr "Argentínsk spænska" msgid "Colombian Spanish" -msgstr "" +msgstr "Kólumbísk spænska" msgid "Mexican Spanish" -msgstr "Mexíkósk Spænska" +msgstr "Mexíkósk spænska" msgid "Nicaraguan Spanish" msgstr "Níkaragva spænska" msgid "Venezuelan Spanish" -msgstr "" +msgstr "Venesúelsk spænska" msgid "Estonian" -msgstr "Eistland" +msgstr "Eistneska" msgid "Basque" msgstr "Baskneska" @@ -121,7 +123,7 @@ msgid "Irish" msgstr "Írska" msgid "Scottish Gaelic" -msgstr "" +msgstr "Skosk gelíska" msgid "Galician" msgstr "Galíska" @@ -136,19 +138,22 @@ msgid "Croatian" msgstr "Króatíska" msgid "Upper Sorbian" -msgstr "" +msgstr "Efri sorbíska" msgid "Hungarian" msgstr "Ungverska" +msgid "Armenian" +msgstr "Armenska" + msgid "Interlingua" -msgstr "" +msgstr "Interlingua" msgid "Indonesian" msgstr "Indónesíska" msgid "Ido" -msgstr "" +msgstr "Ido" msgid "Icelandic" msgstr "Íslenska" @@ -162,9 +167,12 @@ msgstr "Japanska" msgid "Georgian" msgstr "Georgíska" -msgid "Kazakh" +msgid "Kabyle" msgstr "" +msgid "Kazakh" +msgstr "Kasakska" + msgid "Khmer" msgstr "Kmeríska" @@ -175,7 +183,7 @@ msgid "Korean" msgstr "Kóreska" msgid "Luxembourgish" -msgstr "" +msgstr "Lúxemborgíska" msgid "Lithuanian" msgstr "Litháenska" @@ -193,16 +201,16 @@ msgid "Mongolian" msgstr "Mongólska" msgid "Marathi" -msgstr "" +msgstr "Maratí" msgid "Burmese" -msgstr "" +msgstr "Búrmíska" msgid "Norwegian Bokmål" -msgstr "" +msgstr "Norskt bókmál" msgid "Nepali" -msgstr "" +msgstr "Nepalska" msgid "Dutch" msgstr "Hollenska" @@ -211,7 +219,7 @@ msgid "Norwegian Nynorsk" msgstr "Nýnorska" msgid "Ossetic" -msgstr "" +msgstr "Ossetíska" msgid "Punjabi" msgstr "Púndjabíska" @@ -223,7 +231,7 @@ msgid "Portuguese" msgstr "Portúgalska" msgid "Brazilian Portuguese" -msgstr "Brasilísk Portúgalska" +msgstr "Brasilísk portúgalska" msgid "Romanian" msgstr "Rúmenska" @@ -232,7 +240,7 @@ msgid "Russian" msgstr "Rússneska" msgid "Slovak" -msgstr "Slóvaska" +msgstr "Slóvakíska" msgid "Slovenian" msgstr "Slóvenska" @@ -250,7 +258,7 @@ msgid "Swedish" msgstr "Sænska" msgid "Swahili" -msgstr "" +msgstr "Svahílí" msgid "Tamil" msgstr "Tamílska" @@ -265,10 +273,10 @@ msgid "Turkish" msgstr "Tyrkneska" msgid "Tatar" -msgstr "" +msgstr "Tataríska" msgid "Udmurt" -msgstr "" +msgstr "Údmúrt" msgid "Ukrainian" msgstr "Úkraínska" @@ -286,7 +294,7 @@ msgid "Traditional Chinese" msgstr "Hefðbundin kínverska" msgid "Messages" -msgstr "" +msgstr "Skilaboð" msgid "Site Maps" msgstr "" @@ -297,6 +305,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "Þetta síðunúmer er ekki heiltala" + +msgid "That page number is less than 1" +msgstr "Þetta síðunúmer er minna en 1" + +msgid "That page contains no results" +msgstr "Þessi síða hefur engar niðurstöður" + msgid "Enter a valid value." msgstr "Sláðu inn gilt gildi." @@ -304,21 +321,24 @@ msgid "Enter a valid URL." msgstr "Sláðu inn gilt veffang (URL)." msgid "Enter a valid integer." -msgstr "" +msgstr "Sláðu inn gilda heiltölu." msgid "Enter a valid email address." msgstr "Sláðu inn gilt netfang." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" -"Settu inn gildan vefslóðartitil sem samanstendur af latneskum bókstöfum, " -"númerin, undirstrikum og bandstrikum." +"Settu inn gildan vefslóðartitil sem má innihalda latneska bókstafi, " +"tölustafi, undirstrik og bandstrik." msgid "" "Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or " "hyphens." msgstr "" +"Settu inn gildan vefslóðartitil sem má innihalda unicode bókstafi, " +"tölustafi, undirstrik og bandstrik." msgid "Enter a valid IPv4 address." msgstr "Sláðu inn gilda IPv4 tölu." @@ -355,10 +375,10 @@ msgid_plural "" "Ensure this value has at least %(limit_value)d characters (it has " "%(show_value)d)." msgstr[0] "" -"Gildið má mest vera %(limit_value)d stafur að lengd (það er %(show_value)d " +"Gildið má minnst vera %(limit_value)d stafur að lengd (það er %(show_value)d " "nú)" msgstr[1] "" -"Gildið má mest vera %(limit_value)d stafir að lengd (það er %(show_value)d " +"Gildið má minnst vera %(limit_value)d stafir að lengd (það er %(show_value)d " "nú)" #, python-format @@ -369,7 +389,14 @@ msgid_plural "" "Ensure this value has at most %(limit_value)d characters (it has " "%(show_value)d)." msgstr[0] "" +"Gildið má mest vera %(limit_value)d stafur að lengd (það er %(show_value)d " +"nú)" msgstr[1] "" +"Gildið má mest vera %(limit_value)d stafir að lengd (það er %(show_value)d " +"nú)" + +msgid "Enter a number." +msgstr "Sláðu inn tölu." #, python-format msgid "Ensure that there are no more than %(max)s digit in total." @@ -380,27 +407,39 @@ msgstr[1] "Gildið má ekki hafa fleiri en %(max)s tölur." #, 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[0] "Gildið má ekki hafa meira en %(max)s tugatölustaf (decimal places)." msgstr[1] "" +"Gildið má ekki hafa meira en %(max)s tugatölustafi (decimal places)." #, 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] "" -msgstr[1] "" +msgstr[0] "Gildið má ekki hafa fleiri en %(max)s tölu fyrir tugabrotskil." +msgstr[1] "Gildið má ekki hafa fleiri en %(max)s tölur fyrir tugabrotskil." + +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Skrár með endingunni '%(extension)s' eru ekki leyfðar. Leyfilegar endingar " +"eru: '%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Núlltákn eru ekki leyfileg." msgid "and" msgstr "og" #, python-format msgid "%(model_name)s with this %(field_labels)s already exists." -msgstr "" +msgstr "%(model_name)s með þessi %(field_labels)s er nú þegar til." #, python-format msgid "Value %(value)r is not a valid choice." -msgstr "" +msgstr "Gildið %(value)r er ógilt." msgid "This field cannot be null." msgstr "Þessi reitur getur ekki haft tómgildi (null)." @@ -418,6 +457,8 @@ msgstr "%(model_name)s með þetta %(field_label)s er nú þegar til." msgid "" "%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." msgstr "" +"%(field_label)s verður að vera einkvæmt fyrir %(date_field_label)s " +"%(lookup_type)s." #, python-format msgid "Field of type: %(field_type)s" @@ -428,14 +469,18 @@ msgstr "Heiltala" #, python-format msgid "'%(value)s' value must be an integer." -msgstr "" +msgstr "Gildi '%(value)s' verður að vera heiltala." msgid "Big (8 byte) integer" msgstr "Stór (8 bæta) heiltala" #, python-format msgid "'%(value)s' value must be either True or False." -msgstr "" +msgstr "'%(value)s' verður að vera annaðhvort satt eða ósatt." + +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "'%(value)s' verður að vera eitt eftirtalinna: True, False eða None." msgid "Boolean (Either True or False)" msgstr "Boole-gildi (True eða False)" @@ -452,12 +497,14 @@ msgid "" "'%(value)s' value has an invalid date format. It must be in YYYY-MM-DD " "format." msgstr "" +"'%(value)s' er ógilt dagsetningarsnið. Það verður að vera á sniðinu 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' hefur rétt snið (YYYY-MM-DD) en dagsetningin er ógild." msgid "Date (without time)" msgstr "Dagsetning (án tíma)" @@ -467,19 +514,23 @@ msgid "" "'%(value)s' value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." "uuuuuu]][TZ] format." msgstr "" +"'%(value)s' hefur ógilt snið. Það verður að vera á sniðinu: 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' hefur rétt snið (YYYY-MM-DD HH:MM [:ss[.uuuuuu]][TZ]) en það er " +"ógild dagsetning/tími." msgid "Date (with time)" msgstr "Dagsetning (með tíma)" #, python-format msgid "'%(value)s' value must be a decimal number." -msgstr "" +msgstr "'%(value)s' verður að vera tugatala." msgid "Decimal number" msgstr "Tugatala" @@ -489,9 +540,11 @@ msgid "" "'%(value)s' value has an invalid format. It must be in [DD] [HH:[MM:]]ss[." "uuuuuu] format." msgstr "" +"'%(value)s' er á ógildu sniði. Það verður að vera á sniðinu [DD] [HH:" +"[MM:]]ss[.uuuuuu]." msgid "Duration" -msgstr "" +msgstr "Tímalengd" msgid "Email address" msgstr "Netfang" @@ -501,7 +554,7 @@ msgstr "Skjalaslóð" #, python-format msgid "'%(value)s' value must be a float." -msgstr "" +msgstr "'%(value)s' verður að vera fleytitala." msgid "Floating point number" msgstr "Fleytitala (floating point number)" @@ -514,7 +567,7 @@ msgstr "IP tala" #, python-format msgid "'%(value)s' value must be either None, True or False." -msgstr "" +msgstr "'%(value)s' verður að vera eitt eftirtalinna: None, True eða False." msgid "Boolean (Either True, False or None)" msgstr "Boole-gildi (True, False eða None)" @@ -540,12 +593,16 @@ msgid "" "'%(value)s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " "format." msgstr "" +"'%(value)s' er á ógildu sniði. Það verður að vera á sniðinu 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 "" +"'%(value)s' er á réttu sniði (HH:MM[:ss[.uuuuuu]]), en það er ógild " +"dagsetning/tími." msgid "Time" msgstr "Tími" @@ -554,10 +611,13 @@ msgid "URL" msgstr "Veffang" msgid "Raw binary data" -msgstr "" +msgstr "Hrá tvíundargögn (binary data)" #, python-format msgid "'%(value)s' is not a valid UUID." +msgstr "'%(value)s' er ekki gilt UUID." + +msgid "Universally unique identifier" msgstr "" msgid "File" @@ -568,7 +628,7 @@ msgstr "Mynd" #, python-format msgid "%(model)s instance with %(field)s %(value)r does not exist." -msgstr "" +msgstr "%(model)s hlutur með %(field)s %(value)r er ekki til." msgid "Foreign Key (type determined by related field)" msgstr "Ytri lykill (Gerð ákveðin af skyldum reit)" @@ -578,11 +638,11 @@ msgstr "Einn-á-einn samband." #, python-format msgid "%(from)s-%(to)s relationship" -msgstr "" +msgstr "%(from)s-%(to)s samband" #, python-format msgid "%(from)s-%(to)s relationships" -msgstr "" +msgstr "%(from)s-%(to)s sambönd" msgid "Many-to-many relationship" msgstr "Margir-til-margra samband." @@ -591,16 +651,13 @@ msgstr "Margir-til-margra samband." #. characters will prevent the default label_suffix to be appended to the #. label msgid ":?.!" -msgstr "" +msgstr ":?.!" msgid "This field is required." msgstr "Þennan reit þarf að fylla út." msgid "Enter a whole number." -msgstr "Sláðu inn heila tölu." - -msgid "Enter a number." -msgstr "Sláðu inn heila tölu." +msgstr "Sláðu inn heiltölu." msgid "Enter a valid date." msgstr "Sláðu inn gilda dagsetningu." @@ -614,6 +671,10 @@ msgstr "Sláðu inn gilda dagsetningu ásamt tíma." msgid "Enter a valid duration." msgstr "Sláðu inn gilt tímabil." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Fjöldi daga verður að vera á milli {min_days} og {max_days}." + msgid "No file was submitted. Check the encoding type on the form." msgstr "Engin skrá var send. Athugaðu kótunartegund á forminu (encoding type)." @@ -658,7 +719,7 @@ msgstr "Sláðu inn gilt UUID." #. Translators: This is the default suffix added to form field labels msgid ":" -msgstr "" +msgstr ":" #, python-format msgid "(Hidden field %(name)s) %(error)s" @@ -705,8 +766,8 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Vinsamlegast lagfærðu tvítöldu gögnin fyrir neðan." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "Ytri lykill virðist ekki passa við aðallykil eiganda." +msgid "The inline value did not match the parent instance." +msgstr "Innra gildið passar ekki við eiganda." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" @@ -714,8 +775,8 @@ msgstr "" "valmöguleikum." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "'%(pk)s' er ekki gilt sem lykill." +msgid "\"%(pk)s\" is not a valid value." +msgstr "„%(pk)s“ er ekki gilt gildi." #, python-format msgid "" @@ -725,15 +786,15 @@ msgstr "" "%(datetime)s er ekki hægt að túlka í tímabelti %(current_timezone)s, það " "getur verið óljóst eða að það er ekki til." +msgid "Clear" +msgstr "Hreinsa" + msgid "Currently" msgstr "Eins og er:" msgid "Change" msgstr "Breyta" -msgid "Clear" -msgstr "Hreinsa" - msgid "Unknown" msgstr "Óþekkt" @@ -812,25 +873,25 @@ msgid "Sunday" msgstr "sunnudagur" msgid "Mon" -msgstr "Mán" +msgstr "mán" msgid "Tue" -msgstr "Þri" +msgstr "þri" msgid "Wed" -msgstr "Mið" +msgstr "mið" msgid "Thu" -msgstr "Fim" +msgstr "fim" msgid "Fri" -msgstr "Fös" +msgstr "fös" msgid "Sat" -msgstr "Lau" +msgstr "lau" msgid "Sun" -msgstr "Sun" +msgstr "sun" msgid "January" msgstr "janúar" @@ -869,7 +930,7 @@ msgid "December" msgstr "desember" msgid "jan" -msgstr "Jan" +msgstr "jan" msgid "feb" msgstr "feb" @@ -906,107 +967,107 @@ msgstr "des" msgctxt "abbrev. month" msgid "Jan." -msgstr "Jan." +msgstr "jan." msgctxt "abbrev. month" msgid "Feb." -msgstr "Feb." +msgstr "feb." msgctxt "abbrev. month" msgid "March" -msgstr "Mars" +msgstr "mars" msgctxt "abbrev. month" msgid "April" -msgstr "Apríl" +msgstr "apríl" msgctxt "abbrev. month" msgid "May" -msgstr "Maí" +msgstr "maí" msgctxt "abbrev. month" msgid "June" -msgstr "Júní" +msgstr "júní" msgctxt "abbrev. month" msgid "July" -msgstr "Júlí" +msgstr "júlí" msgctxt "abbrev. month" msgid "Aug." -msgstr "Ág." +msgstr "ág." msgctxt "abbrev. month" msgid "Sept." -msgstr "Sept." +msgstr "sept." msgctxt "abbrev. month" msgid "Oct." -msgstr "Okt." +msgstr "okt." msgctxt "abbrev. month" msgid "Nov." -msgstr "Nóv." +msgstr "nóv." msgctxt "abbrev. month" msgid "Dec." -msgstr "Des." +msgstr "des." msgctxt "alt. month" msgid "January" -msgstr "Janúar" +msgstr "janúar" msgctxt "alt. month" msgid "February" -msgstr "Febrúar" +msgstr "febrúar" msgctxt "alt. month" msgid "March" -msgstr "Mars" +msgstr "mars" msgctxt "alt. month" msgid "April" -msgstr "Apríl" +msgstr "apríl" msgctxt "alt. month" msgid "May" -msgstr "Maí" +msgstr "maí" msgctxt "alt. month" msgid "June" -msgstr "Júní" +msgstr "júní" msgctxt "alt. month" msgid "July" -msgstr "Júlí" +msgstr "júlí" msgctxt "alt. month" msgid "August" -msgstr "Ágúst" +msgstr "ágúst" msgctxt "alt. month" msgid "September" -msgstr "September" +msgstr "september" msgctxt "alt. month" msgid "October" -msgstr "Október" +msgstr "október" msgctxt "alt. month" msgid "November" -msgstr "Nóvember" +msgstr "nóvember" msgctxt "alt. month" msgid "December" -msgstr "Desember" +msgstr "desember" msgid "This is not a valid IPv6 address." msgstr "Þetta er ekki gilt IPv6 vistfang." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s…" msgid "or" msgstr "eða" @@ -1066,53 +1127,55 @@ msgid "" "required for security reasons, to ensure that your browser is not being " "hijacked by third parties." msgstr "" +"Þú ert að fá þessi skilaboð því þetta HTTPS vefsvæði þarfnast að vafrinn " +"þinn sendi ‚Referer‘ haus (e. referer header) sem var ekki sendur. Þessi " +"haus er nauðsynlegur af öryggisástæðum til að ganga úr skugga um að " +"utanaðkomandi aðili sé ekki að senda fyrirspurnir úr vafranum þínum." 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 "" +"Ef þú hefur stillt vafrann þinn til að gera ‚Referer‘ hausa óvirka þarftu að " +"virkja þá aftur. Að minnsta kosti fyrir þetta vefsvæði, eða HTTPS tengingar " +"eða ‚same-origin‘ fyrirspurnir." + +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." +msgstr "" 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 "" +"Þú ert að fá þessi skilaboð því þetta vefsvæði þarfnast að CSRF kaka (e. " +"cookie) sé send þegar form eru send. Þessi kaka er nauðsynleg af " +"öryggisástæðum til að ganga úr skugga um að utanaðkomandi aðili sé ekki að " +"senda fyrirspurnir úr vafranum þínum." 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 "" +"Ef þú hefur stillt vafrann þinn til að gera kökur óvirkar þarftu að virkja " +"þær aftur. Að minnsta kosti fyrir þetta vefsvæði eða ‚same-origin‘ " +"fyrirspurnir." msgid "More information is available with DEBUG=True." msgstr "Meiri upplýsingar fást með DEBUG=True." -msgid "Welcome to Django" -msgstr "Velkomin/n í Django" - -msgid "It worked!" -msgstr "Það tókst!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Til hamingju með fyrstu Django síðuna þína." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Þetta er auðvitað ekki nóg. Næst skaltu búa til fyrsta appið þitt með því að " -"nota skipuninapython manage.py startapp [app_heiti]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Þú sérð þessi skilaboð vegna þess að þú hefur DEBUG = True í " -"Django stillingunum þínum og hefur ekki sett upp neinar vefslóðir." - msgid "No year specified" msgstr "Ekkert ár tilgreint" +msgid "Date out of range" +msgstr "Dagsetning utan tímabils" + msgid "No month specified" msgstr "Enginn mánuður tilgreindur" @@ -1124,7 +1187,7 @@ msgstr "Engin vika tilgreind" #, python-format msgid "No %(verbose_name_plural)s available" -msgstr "Ekkert %(verbose_name_plural)s í boði." +msgstr "Ekkert %(verbose_name_plural)s í boði" #, python-format msgid "" @@ -1163,3 +1226,41 @@ msgstr "\"%(path)s\" er ekki til" #, python-format msgid "Index of %(directory)s" msgstr "Innihald %(directory)s " + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/is/formats.py b/django/conf/locale/is/formats.py index 1b328eff1f25..e6cc7d51edc0 100644 --- a/django/conf/locale/is/formats.py +++ b/django/conf/locale/is/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j. F Y' TIME_FORMAT = 'H:i' # DATETIME_FORMAT = @@ -15,7 +12,7 @@ # FIRST_DAY_OF_WEEK = # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # DATE_INPUT_FORMATS = # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = diff --git a/django/conf/locale/it/LC_MESSAGES/django.mo b/django/conf/locale/it/LC_MESSAGES/django.mo index c55552559fc3..be6bd5ad4184 100644 Binary files a/django/conf/locale/it/LC_MESSAGES/django.mo and b/django/conf/locale/it/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/it/LC_MESSAGES/django.po b/django/conf/locale/it/LC_MESSAGES/django.po index 90473ed12dd4..ca12fe3ba4f3 100644 --- a/django/conf/locale/it/LC_MESSAGES/django.po +++ b/django/conf/locale/it/LC_MESSAGES/django.po @@ -1,25 +1,29 @@ # This file is distributed under the same license as the Django package. # # Translators: +# AndreiCR , 2017 # Carlo Miron , 2011 # Carlo Miron , 2014 +# Carlo Miron , 2018-2019 # Denis Darii , 2011 # Flavio Curella , 2013,2016 # Jannis Leidel , 2011 # Themis Savvidis , 2013 # Luciano De Falco Alfano, 2016 # Marco Bonetti, 2014 +# Mirco Grillo , 2018 # Nicola Larosa , 2013 -# palmux , 2014-2015 +# palmux , 2014-2015,2017 # Mattia Procopio , 2015 -# Stefano Brentegani , 2014-2016 +# Riccardo Magliocchetti , 2017 +# Stefano Brentegani , 2014-2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-09-28 11:39+0000\n" -"Last-Translator: Stefano Brentegani \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-02-19 10:28+0000\n" +"Last-Translator: Carlo Miron \n" "Language-Team: Italian (http://www.transifex.com/django/django/language/" "it/)\n" "MIME-Version: 1.0\n" @@ -148,6 +152,9 @@ msgstr "Sorabo superiore" msgid "Hungarian" msgstr "Ungherese" +msgid "Armenian" +msgstr "Armeno" + msgid "Interlingua" msgstr "Interlingua" @@ -169,6 +176,9 @@ msgstr "Giapponese" msgid "Georgian" msgstr "Georgiano" +msgid "Kabyle" +msgstr "Cabilo" + msgid "Kazakh" msgstr "Kazako" @@ -304,6 +314,15 @@ msgstr "File statici" msgid "Syndication" msgstr "Aggregazione" +msgid "That page number is not an integer" +msgstr "Quel numero di pagina non è un integer" + +msgid "That page number is less than 1" +msgstr "Quel numero di pagina è minore di 1" + +msgid "That page contains no results" +msgstr "Quella pagina non presenta alcun risultato" + msgid "Enter a valid value." msgstr "Inserisci un valore valido." @@ -316,6 +335,7 @@ msgstr "Inserire un numero intero valido." msgid "Enter a valid email address." msgstr "Inserisci un indirizzo email valido." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -382,6 +402,9 @@ msgstr[1] "" "Assicurati che questo valore non contenga più di %(limit_value)d caratteri " "(ne ha %(show_value)d)." +msgid "Enter a number." +msgstr "Inserisci un numero." + #, 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." @@ -403,6 +426,17 @@ msgstr[0] "Assicurati che non vi sia più di %(max)s cifra prima della virgola." msgstr[1] "" "Assicurati che non vi siano più di %(max)s cifre prima della virgola." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Il file con estensione '%(extension)s' non e' permesso. Le estensioni " +"permesse sono: '%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "I caratteri null non sono ammessi." + msgid "and" msgstr "e" @@ -441,14 +475,18 @@ msgstr "Intero" #, python-format msgid "'%(value)s' value must be an integer." -msgstr "Il valore di '%(value)s' deve essere un intero." +msgstr "Il valore '%(value)s' deve essere un intero." msgid "Big (8 byte) integer" msgstr "Intero grande (8 byte)" #, python-format msgid "'%(value)s' value must be either True or False." -msgstr "Il valore dir '%(value)s' deve essere Vero oppure Falso." +msgstr "Il valore '%(value)s' deve essere True oppure False." + +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "Il valore di %(value)s deve essere True, False o None" msgid "Boolean (Either True or False)" msgstr "Booleano (Vero o Falso)" @@ -465,7 +503,7 @@ msgid "" "'%(value)s' value has an invalid date format. It must be in YYYY-MM-DD " "format." msgstr "" -"Il valore di '%(value)s' ha un formato di data non valido. Deve essere nel " +"Il valore '%(value)s' ha un formato di data invalido. Deve essere nel " "formato AAAA-MM-GG." #, python-format @@ -484,8 +522,8 @@ msgid "" "'%(value)s' value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." "uuuuuu]][TZ] format." msgstr "" -"Il valore di '%(value)s' ha un formato non valido. Deve essere nel formato " -"AAAA-MM-GG HH:MM[:ss[.uuuuuu]][TZ]" +"Il valore '%(value)s' ha un formato non valido. Deve essere nel formato AAAA-" +"MM-GG HH:MM[:ss[.uuuuuu]][TZ]" #, python-format msgid "" @@ -500,7 +538,7 @@ msgstr "Data (con ora)" #, python-format msgid "'%(value)s' value must be a decimal number." -msgstr "Il valore di '%(value)s' deve essere un numero decimale." +msgstr "Il valore '%(value)s' deve essere un numero decimale." msgid "Decimal number" msgstr "Numero decimale" @@ -510,8 +548,8 @@ msgid "" "'%(value)s' value has an invalid format. It must be in [DD] [HH:[MM:]]ss[." "uuuuuu] format." msgstr "" -"Il valore di '%(value)s' ha un formato non valido. Deve essere nel formato " -"[GG][HH:[MM:]]ss[.uuuuuu]" +"Il valore '%(value)s' ha un formato non valido. Deve essere nel formato [GG]" +"[HH:[MM:]]ss[.uuuuuu]." msgid "Duration" msgstr "Durata" @@ -524,7 +562,7 @@ msgstr "Percorso file" #, python-format msgid "'%(value)s' value must be a float." -msgstr "Il valore di '%(value)s' deve essere un numero a virgola mobile." +msgstr "Il valore '%(value)s' deve essere un numero a virgola mobile." msgid "Floating point number" msgstr "Numero in virgola mobile" @@ -537,7 +575,7 @@ msgstr "Indirizzo IP" #, python-format msgid "'%(value)s' value must be either None, True or False." -msgstr "Il valore di '%(value)s' deve essere None, True oppure False." +msgstr "Il valore '%(value)s' deve essere None, True oppure False." msgid "Boolean (Either True, False or None)" msgstr "Booleano (True, False o None)" @@ -563,8 +601,8 @@ msgid "" "'%(value)s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " "format." msgstr "" -"Il valore di '%(value)s' ha un formato non valido. Deve essere nel formato " -"HH:MM[:ss[.uuuuuu]]." +"Il valore '%(value)s' ha un formato non valido. Deve essere nel formato HH:" +"MM[:ss[.uuuuuu]]." #, python-format msgid "" @@ -587,6 +625,9 @@ msgstr "Dati binari grezzi" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' non è uno UUID valido." +msgid "Universally unique identifier" +msgstr "Identificatore univoco universale" + msgid "File" msgstr "File" @@ -626,9 +667,6 @@ msgstr "Questo campo è obbligatorio." msgid "Enter a whole number." msgstr "Inserisci un numero intero." -msgid "Enter a number." -msgstr "Inserisci un numero." - msgid "Enter a valid date." msgstr "Inserisci una data valida." @@ -641,6 +679,10 @@ msgstr "Inserisci una data/ora valida." msgid "Enter a valid duration." msgstr "Inserisci una durata valida." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Il numero di giorni deve essere compreso tra {min_days} e {max_days}" + msgid "No file was submitted. Check the encoding type on the form." msgstr "Non è stato inviato alcun file. Verifica il tipo di codifica sul form." @@ -735,9 +777,8 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Si prega di correggere i dati duplicati qui sotto." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "" -"La foreign key inline non concorda con la chiave primaria dell'istanza padre." +msgid "The inline value did not match the parent instance." +msgstr "Il valore inline non corrisponde all'istanza padre." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" @@ -745,8 +786,8 @@ msgstr "" "disponibili." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" non è un valore valido per una chiave primaria." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" non è un valore valido." #, python-format msgid "" @@ -756,15 +797,15 @@ msgstr "" " %(datetime)s non può essere interpretato nel fuso orario " "%(current_timezone)s: potrebbe essere ambiguo o non esistere." +msgid "Clear" +msgstr "Svuota" + msgid "Currently" msgstr "Attualmente" msgid "Change" msgstr "Cambia" -msgid "Clear" -msgstr "Svuota" - msgid "Unknown" msgstr "Sconosciuto" @@ -1036,8 +1077,8 @@ msgstr "Questo non è un indirizzo IPv6 valido." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr " %(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s…" msgid "or" msgstr "o" @@ -1111,6 +1152,19 @@ msgstr "" "intestazioni \"Referer\", riattiva questo invio, almeno per questo sito, o " "per le connessioni HTTPS, o per le connessioni \"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." +msgstr "" +"Se usi il tag o includi " +"header 'Referrer-Policy: no-referrer', per favore rimuovili. Per la " +"protezione CSRF è necessario eseguire un controllo rigoroso sull'header " +"'Referer'. Se ti preoccupano le ricadute sulla privacy, puoi ricorrere ad " +"alternative come per i link a siti di terze parti." + 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 " @@ -1130,34 +1184,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Maggiorni informazioni sono disponibili con DEBUG=True" -msgid "Welcome to Django" -msgstr "Benvenuti in Django" - -msgid "It worked!" -msgstr "Ha funzionato!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Congratulazioni per la tua prima pagina Django-powered." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Naturalmente non hai ancora svolto alcun lavoro. Il prossimo passo è creare " -"la tua prima app inserendo il comando python manage.py startapp " -"[nome_app]" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Stai ricevendo questo messaggio perchè nel tuo file di configurazione di " -"Django è presente DEBUG = True e non hai ancora configurato " -"alcun URL. Al lavoro!" - msgid "No year specified" msgstr "Anno non specificato" +msgid "Date out of range" +msgstr "Data al di fuori dell'intervallo" + msgid "No month specified" msgstr "Mese non specificato" @@ -1208,3 +1240,48 @@ msgstr "\"%(path)s\" non esiste" #, python-format msgid "Index of %(directory)s" msgstr "Indice di %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: il framework Web per i perfezionisti con delle scadenze." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Leggi le note di rilascio per Django " +"%(version)s" + +msgid "The install worked successfully! Congratulations!" +msgstr "Installazione completata con successo! Congratulazioni!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Stai vedendo questa pagina perché hai impostato DEBUG=True nel tuo file di configurazione e non hai " +"configurato nessun URL." + +msgid "Django Documentation" +msgstr "Documentazione di Django" + +msgid "Topics, references, & how-to's" +msgstr "Temi, riferimenti, & guide" + +msgid "Tutorial: A Polling App" +msgstr "Tutorial: un'app per sondaggi" + +msgid "Get started with Django" +msgstr "Iniziare con Django" + +msgid "Django Community" +msgstr "La Community di Django" + +msgid "Connect, get help, or contribute" +msgstr "Connettiti, chiedi aiuto, o contribuisci." diff --git a/django/conf/locale/it/formats.py b/django/conf/locale/it/formats.py index 054b973f0a13..f026a4aa2141 100644 --- a/django/conf/locale/it/formats.py +++ b/django/conf/locale/it/formats.py @@ -1,21 +1,18 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'd F Y' # 25 Ottobre 2006 TIME_FORMAT = 'H:i' # 14:30 DATETIME_FORMAT = 'l d F Y H:i' # Mercoledì 25 Ottobre 2006 14:30 YEAR_MONTH_FORMAT = 'F Y' # Ottobre 2006 -MONTH_DAY_FORMAT = 'j/F' # 10/2006 +MONTH_DAY_FORMAT = 'j F' # 25 Ottobre SHORT_DATE_FORMAT = 'd/m/Y' # 25/12/2009 SHORT_DATETIME_FORMAT = 'd/m/Y H:i' # 25/10/2009 14:30 FIRST_DAY_OF_WEEK = 1 # Lunedì # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d/%m/%Y', '%Y/%m/%d', # '25/10/2006', '2008/10/25' '%d-%m-%Y', '%Y-%m-%d', # '25-10-2006', '2008-10-25' diff --git a/django/conf/locale/ja/LC_MESSAGES/django.mo b/django/conf/locale/ja/LC_MESSAGES/django.mo index 83c6a1c49114..4440f6339ae9 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 abfbd05b491b..9a610ab3c295 100644 --- a/django/conf/locale/ja/LC_MESSAGES/django.po +++ b/django/conf/locale/ja/LC_MESSAGES/django.po @@ -2,17 +2,21 @@ # # Translators: # xiu1 , 2016 +# GOTO Hayato , 2019 # Jannis Leidel , 2011 -# Kentaro Hori , 2015 -# Shinya Okano , 2012-2016 +# Kentaro Matsuzaki , 2015 +# Masashi SHIBATA , 2017 +# Nikita K , 2019 +# Shinichi Katsumata , 2019 +# Shinya Okano , 2012-2019 # Tetsuya Morimoto , 2011 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-07-10 13:16+0000\n" -"Last-Translator: xiu1 \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-06-07 08:56+0000\n" +"Last-Translator: GOTO Hayato \n" "Language-Team: Japanese (http://www.transifex.com/django/django/language/" "ja/)\n" "MIME-Version: 1.0\n" @@ -141,6 +145,9 @@ msgstr "高地ソルブ語" msgid "Hungarian" msgstr "ハンガリー語" +msgid "Armenian" +msgstr "アルメニア" + msgid "Interlingua" msgstr "インターリングア" @@ -162,6 +169,9 @@ msgstr "日本語" msgid "Georgian" msgstr "グルジア語" +msgid "Kabyle" +msgstr "カビル語" + msgid "Kazakh" msgstr "カザフ語" @@ -297,6 +307,15 @@ msgstr "静的ファイル" msgid "Syndication" msgstr "シンジケーション" +msgid "That page number is not an integer" +msgstr "このページ番号は整数ではありません。" + +msgid "That page number is less than 1" +msgstr "ページ番号が 1 よりも小さいです。" + +msgid "That page contains no results" +msgstr "このページには結果が含まれていません。" + msgid "Enter a valid value." msgstr "値を正しく入力してください。" @@ -309,16 +328,17 @@ msgstr "整数を正しく入力してください。" msgid "Enter a valid email address." msgstr "有効なメールアドレスを入力してください。" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." -msgstr "slug には半角の英数字、アンダースコア、ハイフン以外は使用できません。" +msgstr "スラグには半角の英数字、アンダースコア、ハイフン以外は使用できません。" msgid "" "Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or " "hyphens." msgstr "" -"ユニコード文字、数字、アンダースコアまたはハイフンで構成された、有効な" -"「slug」を入力してください" +"ユニコード文字、数字、アンダースコアまたはハイフンで構成された、有効なスラグ" +"を入力してください" msgid "Enter a valid IPv4 address." msgstr "有効なIPアドレス (IPv4) を入力してください。" @@ -368,6 +388,9 @@ msgstr[0] "" "この値は %(limit_value)d 文字以下でなければなりません( %(show_value)d 文字に" "なっています)。" +msgid "Enter a number." +msgstr "数値を入力してください。" + #, 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." @@ -385,6 +408,17 @@ msgid_plural "" "Ensure that there are no more than %(max)s digits before the decimal point." msgstr[0] "この値は小数点より前が合計 %(max)s 桁以内でなければなりません。" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"ファイル拡張子 '%(extension)s' は許可されていません。許可されている拡張子は " +"'%(allowed_extensions)s' です。" + +msgid "Null characters are not allowed." +msgstr "何か文字を入力してください。" + msgid "and" msgstr "と" @@ -433,6 +467,10 @@ msgstr "大きな(8バイト)整数" msgid "'%(value)s' value must be either True or False." msgstr "'%(value)s' は真偽値にしなければなりません。" +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "'%(value)s' はTrue、FalseまたはNoneの値でなければなりません。" + msgid "Boolean (Either True or False)" msgstr "ブール値 (真: True または偽: False)" @@ -565,6 +603,9 @@ msgstr "生のバイナリデータ" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' は有効なUUIDではありません。" +msgid "Universally unique identifier" +msgstr "汎用一意識別子" + msgid "File" msgstr "ファイル" @@ -604,9 +645,6 @@ msgstr "このフィールドは必須です。" msgid "Enter a whole number." msgstr "整数を入力してください。" -msgid "Enter a number." -msgstr "整数を入力してください。" - msgid "Enter a valid date." msgstr "日付を正しく入力してください。" @@ -614,14 +652,18 @@ msgid "Enter a valid time." msgstr "時間を正しく入力してください。" msgid "Enter a valid date/time." -msgstr "日付/時間を正しく入力してください。" +msgstr "日時を正しく入力してください。" msgid "Enter a valid duration." msgstr "時間差分を正しく入力してください。" +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "日数は{min_days}から{max_days}の間でなければなりません。" + msgid "No file was submitted. Check the encoding type on the form." msgstr "" -"ファイルが取得できませんでした。formのencoding typeを確認してください。" +"ファイルが取得できませんでした。フォームのencoding typeを確認してください。" msgid "No file was submitted." msgstr "ファイルが送信されていません。" @@ -671,7 +713,7 @@ msgid "(Hidden field %(name)s) %(error)s" msgstr "(隠しフィールド %(name)s) %(error)s" msgid "ManagementForm data is missing or has been tampered with" -msgstr "ManagementFormデータが見つからないか、改竄されています。" +msgstr "マネジメントフォームのデータが見つからないか、改竄されています。" #, python-format msgid "Please submit %d or fewer forms." @@ -710,15 +752,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "下記の重複したデータを修正してください。" -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "インライン外部キーが親インスタンスの主キーと一致しません。" +msgid "The inline value did not match the parent instance." +msgstr "インライン値が親のインスタンスに一致しません。" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "正しく選択してください。選択したものは候補にありません。" #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" は主キーとして無効な値です。" +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" は無効な値です。" #, python-format msgid "" @@ -728,15 +770,15 @@ msgstr "" "%(datetime)s は%(current_timezone)sのタイムゾーンでは解釈できませんでした。そ" "れは曖昧であるか、存在しない可能性があります。" +msgid "Clear" +msgstr "クリア" + msgid "Currently" msgstr "現在" msgid "Change" msgstr "変更" -msgid "Clear" -msgstr "クリア" - msgid "Unknown" msgstr "不明" @@ -1007,8 +1049,8 @@ msgstr "これは有効なIPv6アドレスではありません。" #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s…" msgid "or" msgstr "または" @@ -1075,6 +1117,19 @@ msgstr "" "もしブラウザのリファラーヘッダを無効に設定しているならば、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." +msgstr "" +"もし タグを使用しているか " +"'Referrer-Policy: no-referrer' ヘッダーを含んでいる場合は、削除してください。" +"CSRFプロテクションは、厳密に referer をチェックするために 'Referer' ヘッダー" +"が必要です。プライバシーについて心配がある場合は、" +"等を用いて、外部のサイトにリンクしてください。" + 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 " @@ -1094,33 +1149,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "詳細な情報は DEBUG=True を設定すると利用できます。" -msgid "Welcome to Django" -msgstr "Djangoへようこそ" - -msgid "It worked!" -msgstr "うまくいった!" - -msgid "Congratulations on your first Django-powered page." -msgstr "おめでとうございます、Djangoで出力された最初のページです。" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"もちろん、実際にはまだ少しも作業をしていません。次は、 python manage." -"py startapp [app_label] を実行して、最初のアプリを開始します。" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"このメッセージは、Djangoのsettingsファイルに DEBUG = True が含ま" -"れ、まだURLが何も設定されていないため表示されています。さあ、仕事に取り掛かり" -"ましょう!" - msgid "No year specified" msgstr "年が未指定です" +msgid "Date out of range" +msgstr "日付が有効範囲外です" + msgid "No month specified" msgstr "月が未指定です" @@ -1162,12 +1196,55 @@ msgid "Empty list and '%(class_name)s.allow_empty' is False." msgstr "空の一覧かつ '%(class_name)s.allow_empty' がFalseです。" msgid "Directory indexes are not allowed here." -msgstr "Directory indexes are not allowed here." +msgstr "ここではディレクトリインデックスが許可されていません。" #, python-format msgid "\"%(path)s\" does not exist" -msgstr "\"%(path)s\" does not exist" +msgstr "\"%(path)s\" が存在しません。" #, python-format msgid "Index of %(directory)s" -msgstr "Index of %(directory)s" +msgstr "%(directory)sのディレクトリインデックス" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: 納期を逃さない完璧主義者のためのWebフレームワーク" + +#, 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." +msgstr "" +"このページは、設定ファイルでDEBUG=Trueが指定され、何もURLが設定されていない時に表示されます。" + +msgid "Django Documentation" +msgstr "Django ドキュメント" + +msgid "Topics, references, & how-to's" +msgstr "トピック、リファレンス、ハウツー" + +msgid "Tutorial: A Polling App" +msgstr "チュートリアル: 投票アプリケーション" + +msgid "Get started with Django" +msgstr "Djangoを始めよう" + +msgid "Django Community" +msgstr "Djangoのコミュニティ" + +msgid "Connect, get help, or contribute" +msgstr "つながり、助け合い、貢献しよう" diff --git a/django/conf/locale/ja/formats.py b/django/conf/locale/ja/formats.py index 63f043872a22..2f1faa69ad97 100644 --- a/django/conf/locale/ja/formats.py +++ b/django/conf/locale/ja/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'Y年n月j日' TIME_FORMAT = 'G:i' DATETIME_FORMAT = 'Y年n月j日G:i' @@ -15,7 +12,7 @@ # FIRST_DAY_OF_WEEK = # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # DATE_INPUT_FORMATS = # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = diff --git a/django/conf/locale/ka/LC_MESSAGES/django.mo b/django/conf/locale/ka/LC_MESSAGES/django.mo index f1d45568f32f..39b30fbd102f 100644 Binary files a/django/conf/locale/ka/LC_MESSAGES/django.mo and b/django/conf/locale/ka/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/ka/LC_MESSAGES/django.po b/django/conf/locale/ka/LC_MESSAGES/django.po index fe020ab33c6c..7e27909d7528 100644 --- a/django/conf/locale/ka/LC_MESSAGES/django.po +++ b/django/conf/locale/ka/LC_MESSAGES/django.po @@ -2,22 +2,24 @@ # # Translators: # André Bouatchidzé , 2013-2015 -# avsd05 , 2011 +# David A. , 2019 +# David A. , 2011 # Jannis Leidel , 2011 +# Tornike Beradze , 2018 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-25 09:06+0000\n" +"Last-Translator: David A. \n" "Language-Team: Georgian (http://www.transifex.com/django/django/language/" "ka/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ka\n" -"Plural-Forms: nplurals=1; plural=0;\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" msgid "Afrikaans" msgstr "აფრიკაანსი" @@ -62,7 +64,7 @@ msgid "German" msgstr "გერმანული" msgid "Lower Sorbian" -msgstr "" +msgstr "ქვემო სორბული" msgid "Greek" msgstr "ბერძნული" @@ -86,7 +88,7 @@ msgid "Argentinian Spanish" msgstr "არგენტინის ესპანური" msgid "Colombian Spanish" -msgstr "" +msgstr "კოლუმბიური ესპანური" msgid "Mexican Spanish" msgstr "მექსიკური ესპანური" @@ -119,7 +121,7 @@ msgid "Irish" msgstr "ირლანდიური" msgid "Scottish Gaelic" -msgstr "" +msgstr "შოტლანდიური-გელური" msgid "Galician" msgstr "გალიციური" @@ -134,11 +136,14 @@ msgid "Croatian" msgstr "ხორვატიული" msgid "Upper Sorbian" -msgstr "" +msgstr "ზემო სორბიული" msgid "Hungarian" msgstr "უნგრული" +msgid "Armenian" +msgstr "სომხური" + msgid "Interlingua" msgstr "ინტერლინგუა" @@ -160,6 +165,9 @@ msgstr "იაპონური" msgid "Georgian" msgstr "ქართული" +msgid "Kabyle" +msgstr "კაბილური" + msgid "Kazakh" msgstr "ყაზახური" @@ -197,7 +205,7 @@ msgid "Burmese" msgstr "ბირმული" msgid "Norwegian Bokmål" -msgstr "" +msgstr "ნორვეგიული Bokmål" msgid "Nepali" msgstr "ნეპალური" @@ -295,6 +303,15 @@ msgstr "სტატიკური ფაილები" msgid "Syndication" msgstr "სინდიკაცია" +msgid "That page number is not an integer" +msgstr "გვერდის ნომერი არ არის მთელი რიცხვი" + +msgid "That page number is less than 1" +msgstr "გვერდის ნომერი ნაკლებია 1-ზე" + +msgid "That page contains no results" +msgstr "გვერდი არ შეიცავს მონაცემებს" + msgid "Enter a valid value." msgstr "შეიყვანეთ სწორი მნიშვნელობა." @@ -307,6 +324,7 @@ msgstr "შეიყვანეთ სწორი მთელი რიცხ msgid "Enter a valid email address." msgstr "შეიყვანეთ მართებული ელფოსტის მისამართი." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -317,6 +335,8 @@ msgid "" "Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or " "hyphens." msgstr "" +"შეიყვანეთ სწორი 'slug' მნიშვნელობა, რომელიც უნდა შეიცავდეს Unicode ასოებს, " +"ციფრებს, ხაზგასმის ნიშნებს, ან დეფისებს." msgid "Enter a valid IPv4 address." msgstr "შეიყვანეთ სწორი IPv4 მისამართი." @@ -352,6 +372,9 @@ msgid_plural "" msgstr[0] "" "მნიშვნელობას უნდა ჰქონდეს სულ ცოტა %(limit_value)d სიმბოლო (მას აქვს " "%(show_value)d)." +msgstr[1] "" +"მნიშვნელობას უნდა ჰქონდეს სულ ცოტა %(limit_value)d სიმბოლო (მას აქვს " +"%(show_value)d)." #, python-format msgid "" @@ -363,16 +386,26 @@ msgid_plural "" msgstr[0] "" "მნიშვნელობას უნდა ჰქონდეს არაუმეტეს %(limit_value)d სიმბოლოსი (მას აქვს " "%(show_value)d)." +msgstr[1] "" +"მნიშვნელობას უნდა ჰქონდეს არაუმეტეს %(limit_value)d სიმბოლოსი (მას აქვს " +"%(show_value)d)." + +msgid "Enter a number." +msgstr "შეიყვანეთ რიცხვი." #, 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[0] "ციფრების სრული რაოდენობა %(max)s-ს არ უნდა აღემატებოდეს." +msgstr[1] "ციფრების სრული რაოდენობა %(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[1] "" +"ათობითი გამყოფის შემდეგ ციფრების რაოდენობა %(max)s-ს არ უნდა აღემატებოდეს." #, python-format msgid "" @@ -380,6 +413,20 @@ msgid "" msgid_plural "" "Ensure that there are no more than %(max)s digits before the decimal point." msgstr[0] "" +"ათობითი გამყოფის შემდეგ ციფრების რაოდენობა %(max)s-ს არ უნდა აღემატებოდეს." +msgstr[1] "" +"ათობითი გამყოფის წინ ციფრების რაოდენობა %(max)s-ს არ უნდა აღემატებოდეს." + +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"ფაილის გაფართოება \"%(extension)s\" დაუშვებელია. დასაშვები გაფართოებებია: " +"\"%(allowed_extensions)s\"." + +msgid "Null characters are not allowed." +msgstr "Null მნიშვნელობები დაუშვებელია." msgid "and" msgstr "და" @@ -429,6 +476,10 @@ msgstr "დიდი მთელი (8-ბაიტიანი)" msgid "'%(value)s' value must be either True or False." msgstr "მნიშვნელობა '%(value)s' უნდა იყოს True ან False." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "\"%(value)s\"-ის მნიშვნელობა შეიძლება იყოს True, False ან None." + msgid "Boolean (Either True or False)" msgstr "ლოგიკური (True ან False)" @@ -503,7 +554,7 @@ msgstr "გზა ფაილისაკენ" #, python-format msgid "'%(value)s' value must be a float." -msgstr "" +msgstr "\"%(value)s\"-ის მნიშვნელობა უნდა იყოს float ტიპის." msgid "Floating point number" msgstr "რიცხვი მცოცავი წერტილით" @@ -542,12 +593,16 @@ msgid "" "'%(value)s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " "format." 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 "" +"\"%(value)s\"-ს აქვს სწორი ფორმატი (HH:MM[:ss[.uuuuuu]]), მაგრამ დროის " +"მნიშვნელობა არასწორია." msgid "Time" msgstr "დრო" @@ -556,11 +611,14 @@ msgid "URL" msgstr "URL" msgid "Raw binary data" -msgstr "" +msgstr "დაუმუშავებელი ორობითი მონაცემები" #, python-format msgid "'%(value)s' is not a valid UUID." -msgstr "" +msgstr "\"%(value)s\"-ს აქვს დაუშვებელი UUID-ის მნიშვნელობა." + +msgid "Universally unique identifier" +msgstr "უნივერსალური უნიკალური იდენტიფიკატორი." msgid "File" msgstr "ფაილი" @@ -601,9 +659,6 @@ msgstr "ეს ველი აუცილებელია." msgid "Enter a whole number." msgstr "შეიყვანეთ მთელი რიცხვი" -msgid "Enter a number." -msgstr "შეიყვანეთ რიცხვი." - msgid "Enter a valid date." msgstr "შეიყვანეთ სწორი თარიღი." @@ -614,6 +669,10 @@ msgid "Enter a valid date/time." msgstr "შეიყვანეთ სწორი თარიღი და დრო." msgid "Enter a valid duration." +msgstr "შეიყვანეთ სწორი დროის პერიოდი." + +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." msgstr "" msgid "No file was submitted. Check the encoding type on the form." @@ -631,6 +690,7 @@ 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] "" +msgstr[1] "" msgid "Please either submit a file or check the clear checkbox, not both." msgstr "ან გამოგზავნეთ ფაილი, ან მონიშნეთ \"წაშლის\" დროშა." @@ -670,11 +730,13 @@ msgstr "" msgid "Please submit %d or fewer forms." msgid_plural "Please submit %d or fewer forms." msgstr[0] "" +msgstr[1] "" #, python-format msgid "Please submit %d or more forms." msgid_plural "Please submit %d or more forms." msgstr[0] "" +msgstr[1] "" msgid "Order" msgstr "დალაგება" @@ -703,14 +765,14 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "გთხოვთ, შეასწოროთ დუბლირებული მნიშვნელობები." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "გარე გასაღების მნიშვნელობა მშობლის პირველად გასაღებს არ ემთხვევა." +msgid "The inline value did not match the parent instance." +msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "აირჩიეთ დასაშვები მნიშვნელობა. ეს არჩევანი დასაშვები არ არის." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -719,15 +781,15 @@ msgid "" "may be ambiguous or it may not exist." msgstr "" +msgid "Clear" +msgstr "წაშლა" + msgid "Currently" msgstr "ამჟამად" msgid "Change" msgstr "შეცვლა" -msgid "Clear" -msgstr "წაშლა" - msgid "Unknown" msgstr "გაურკვეველი" @@ -744,6 +806,7 @@ msgstr "კი,არა,შესაძლოა" msgid "%(size)d byte" msgid_plural "%(size)d bytes" msgstr[0] "%(size)d ბაიტი" +msgstr[1] "%(size)d ბაიტი" #, python-format msgid "%s KB" @@ -998,8 +1061,8 @@ msgstr "ეს არ არის სწორი IPv6 მისამართ #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "" msgid "or" msgstr "ან" @@ -1012,31 +1075,37 @@ msgstr ", " msgid "%d year" msgid_plural "%d years" msgstr[0] "%d წელი" +msgstr[1] "%d წელი" #, python-format msgid "%d month" msgid_plural "%d months" msgstr[0] "%d თვე" +msgstr[1] "%d თვე" #, python-format msgid "%d week" msgid_plural "%d weeks" msgstr[0] "%d კვირა" +msgstr[1] "%d კვირა" #, python-format msgid "%d day" msgid_plural "%d days" msgstr[0] "%d დღე" +msgstr[1] "%d დღე" #, python-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "%d საათი" +msgstr[1] "%d საათი" #, python-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d წუთი" +msgstr[1] "%d წუთი" msgid "0 minutes" msgstr "0 წუთი" @@ -1060,6 +1129,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1074,28 +1151,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "მეტი ინფორმაცია მისაწვდომია DEBUG=True-ს მეშვეობით." -msgid "Welcome to Django" -msgstr "კეთილი იყოს თქვენი მობრძანება Django-ში" - -msgid "It worked!" -msgstr "ამან იმუშავა!" - -msgid "Congratulations on your first Django-powered page." -msgstr "გილოცავთ თქვენს პრიველ Django-ზე მომუშავე გვერდს." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "არ არის მითითებული წელი" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "არ არის მითითებული თვე" @@ -1147,3 +1208,41 @@ msgstr "\"%(path)s\" არ არსებობს" #, python-format msgid "Index of %(directory)s" msgstr "%(directory)s-ის იდექსი" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/ka/formats.py b/django/conf/locale/ka/formats.py index 226f5f721ea2..e4c86a7195d8 100644 --- a/django/conf/locale/ka/formats.py +++ b/django/conf/locale/ka/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'l, j F, Y' TIME_FORMAT = 'h:i a' DATETIME_FORMAT = 'j F, Y h:i a' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # (Monday) # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # Kept ISO formats as they are in first position DATE_INPUT_FORMATS = [ '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06' diff --git a/django/conf/locale/kab/LC_MESSAGES/django.mo b/django/conf/locale/kab/LC_MESSAGES/django.mo new file mode 100644 index 000000000000..4d5b834bef97 Binary files /dev/null and b/django/conf/locale/kab/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/kab/LC_MESSAGES/django.po b/django/conf/locale/kab/LC_MESSAGES/django.po new file mode 100644 index 000000000000..f21e906f41d0 --- /dev/null +++ b/django/conf/locale/kab/LC_MESSAGES/django.po @@ -0,0 +1,1182 @@ +# This file is distributed under the same license as the Django package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" +"Last-Translator: Jannis Leidel \n" +"Language-Team: Kabyle (http://www.transifex.com/django/django/language/" +"kab/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: kab\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Afrikaans" +msgstr "Tafrikanst" + +msgid "Arabic" +msgstr "Taɛṛabt" + +msgid "Asturian" +msgstr "Tasturyant" + +msgid "Azerbaijani" +msgstr "Tazeṛbayǧant" + +msgid "Bulgarian" +msgstr "Tabulgarit" + +msgid "Belarusian" +msgstr "Tabilurusit" + +msgid "Bengali" +msgstr "Tabelgalit" + +msgid "Breton" +msgstr "Tabrutunt" + +msgid "Bosnian" +msgstr "Tabusnit" + +msgid "Catalan" +msgstr "Takaṭalant" + +msgid "Czech" +msgstr "Tačikit" + +msgid "Welsh" +msgstr "Takusit" + +msgid "Danish" +msgstr "Tadanit" + +msgid "German" +msgstr "Talmanit" + +msgid "Lower Sorbian" +msgstr "Tasiṛbit n wadda" + +msgid "Greek" +msgstr "Tagrigit" + +msgid "English" +msgstr "Taglizit" + +msgid "Australian English" +msgstr "Taglizit n Ustralya" + +msgid "British English" +msgstr "Taglizit (UK)" + +msgid "Esperanto" +msgstr "Taspirantit" + +msgid "Spanish" +msgstr "Taspanit" + +msgid "Argentinian Spanish" +msgstr "Taspanit n Arjuntin" + +msgid "Colombian Spanish" +msgstr "Taspanit n Kulumbya" + +msgid "Mexican Spanish" +msgstr "Taspanit n Miksik" + +msgid "Nicaraguan Spanish" +msgstr "Taspanit n Nikaragwa" + +msgid "Venezuelan Spanish" +msgstr "Taspanit n Vinizwila" + +msgid "Estonian" +msgstr "Tastunit" + +msgid "Basque" +msgstr "Tabaskit" + +msgid "Persian" +msgstr "Tafarsit" + +msgid "Finnish" +msgstr "Tafinit" + +msgid "French" +msgstr "Tafṛansist" + +msgid "Frisian" +msgstr "" + +msgid "Irish" +msgstr "" + +msgid "Scottish Gaelic" +msgstr "" + +msgid "Galician" +msgstr "" + +msgid "Hebrew" +msgstr "" + +msgid "Hindi" +msgstr "Tahendit" + +msgid "Croatian" +msgstr "Takarwasit" + +msgid "Upper Sorbian" +msgstr "" + +msgid "Hungarian" +msgstr "Tahungarit" + +msgid "Interlingua" +msgstr "" + +msgid "Indonesian" +msgstr "Tandunizit" + +msgid "Ido" +msgstr "" + +msgid "Icelandic" +msgstr "Taslandit" + +msgid "Italian" +msgstr "Taṭelyanit" + +msgid "Japanese" +msgstr "" + +msgid "Georgian" +msgstr "Tajyuṛjit" + +msgid "Kazakh" +msgstr "Takazaxt" + +msgid "Khmer" +msgstr "" + +msgid "Kannada" +msgstr "Takannadat" + +msgid "Korean" +msgstr "Takurit" + +msgid "Luxembourgish" +msgstr "" + +msgid "Lithuanian" +msgstr "Talitwanit" + +msgid "Latvian" +msgstr "Talitunit" + +msgid "Macedonian" +msgstr "Tamasidunit" + +msgid "Malayalam" +msgstr "Tamayalamt" + +msgid "Mongolian" +msgstr "" + +msgid "Marathi" +msgstr "" + +msgid "Burmese" +msgstr "Tabirmanit" + +msgid "Norwegian Bokmål" +msgstr "" + +msgid "Nepali" +msgstr "Tanipalit" + +msgid "Dutch" +msgstr "Tahulandit" + +msgid "Norwegian Nynorsk" +msgstr "" + +msgid "Ossetic" +msgstr "" + +msgid "Punjabi" +msgstr "Tabenjabit" + +msgid "Polish" +msgstr "Tapulandit" + +msgid "Portuguese" +msgstr "Tapurtugit" + +msgid "Brazilian Portuguese" +msgstr "" + +msgid "Romanian" +msgstr "Tarumanit" + +msgid "Russian" +msgstr "Tarusit" + +msgid "Slovak" +msgstr "Tasluvakt" + +msgid "Slovenian" +msgstr "" + +msgid "Albanian" +msgstr "Talbanit" + +msgid "Serbian" +msgstr "Tasiṛbit" + +msgid "Serbian Latin" +msgstr "" + +msgid "Swedish" +msgstr "Taswidit" + +msgid "Swahili" +msgstr "Taswahilit" + +msgid "Tamil" +msgstr "Taṭamult" + +msgid "Telugu" +msgstr "" + +msgid "Thai" +msgstr "" + +msgid "Turkish" +msgstr "Taṭurkit" + +msgid "Tatar" +msgstr "" + +msgid "Udmurt" +msgstr "" + +msgid "Ukrainian" +msgstr "" + +msgid "Urdu" +msgstr "" + +msgid "Vietnamese" +msgstr "" + +msgid "Simplified Chinese" +msgstr "" + +msgid "Traditional Chinese" +msgstr "" + +msgid "Messages" +msgstr "Iznan" + +msgid "Site Maps" +msgstr "" + +msgid "Static Files" +msgstr "" + +msgid "Syndication" +msgstr "" + +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + +msgid "Enter a valid value." +msgstr "Sekcem azal ameɣtu." + +msgid "Enter a valid URL." +msgstr "" + +msgid "Enter a valid integer." +msgstr "" + +msgid "Enter a valid email address." +msgstr "Sekcem tansa imayl tameɣtut." + +#. Translators: "letters" means latin letters: a-z and A-Z. +msgid "" +"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." +msgstr "" + +msgid "" +"Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or " +"hyphens." +msgstr "" + +msgid "Enter a valid IPv4 address." +msgstr "Sekcem tansa IPv4 tameɣtut." + +msgid "Enter a valid IPv6 address." +msgstr "" + +msgid "Enter a valid IPv4 or IPv6 address." +msgstr "" + +msgid "Enter only digits separated by commas." +msgstr "" + +#, python-format +msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." +msgstr "" + +#, python-format +msgid "Ensure this value is less than or equal to %(limit_value)s." +msgstr "" + +#, python-format +msgid "Ensure this value is greater than or equal to %(limit_value)s." +msgstr "" + +#, python-format +msgid "" +"Ensure this value has at least %(limit_value)d character (it has " +"%(show_value)d)." +msgid_plural "" +"Ensure this value has at least %(limit_value)d characters (it has " +"%(show_value)d)." +msgstr[0] "" +msgstr[1] "" + +#, python-format +msgid "" +"Ensure this value has at most %(limit_value)d character (it has " +"%(show_value)d)." +msgid_plural "" +"Ensure this value has at most %(limit_value)d characters (it has " +"%(show_value)d)." +msgstr[0] "" +msgstr[1] "" + +#, 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] "" + +#, 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] "" + +#, 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] "" +msgstr[1] "" + +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + +msgid "and" +msgstr "akked" + +#, python-format +msgid "%(model_name)s with this %(field_labels)s already exists." +msgstr "" + +#, python-format +msgid "Value %(value)r is not a valid choice." +msgstr "" + +msgid "This field cannot be null." +msgstr "" + +msgid "This field cannot be blank." +msgstr "" + +#, python-format +msgid "%(model_name)s with this %(field_label)s already exists." +msgstr "" + +#. 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 "" + +#, python-format +msgid "Field of type: %(field_type)s" +msgstr "" + +msgid "Integer" +msgstr "Ummid" + +#, python-format +msgid "'%(value)s' value must be an integer." +msgstr "" + +msgid "Big (8 byte) integer" +msgstr "" + +#, python-format +msgid "'%(value)s' value must be either True or False." +msgstr "" + +msgid "Boolean (Either True or False)" +msgstr "" + +#, python-format +msgid "String (up to %(max_length)s)" +msgstr "" + +msgid "Comma-separated integers" +msgstr "" + +#, python-format +msgid "" +"'%(value)s' value has an invalid date format. It must be in YYYY-MM-DD " +"format." +msgstr "" + +#, python-format +msgid "" +"'%(value)s' value has the correct format (YYYY-MM-DD) but it is an invalid " +"date." +msgstr "" + +msgid "Date (without time)" +msgstr "" + +#, python-format +msgid "" +"'%(value)s' value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." +"uuuuuu]][TZ] format." +msgstr "" + +#, 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 "" + +msgid "Date (with time)" +msgstr "Azemz (s wakud)" + +#, python-format +msgid "'%(value)s' value must be a decimal number." +msgstr "" + +msgid "Decimal number" +msgstr "" + +#, python-format +msgid "" +"'%(value)s' value has an invalid format. It must be in [DD] [HH:[MM:]]ss[." +"uuuuuu] format." +msgstr "" + +msgid "Duration" +msgstr "Tanzagt" + +msgid "Email address" +msgstr "Tansa email" + +msgid "File path" +msgstr "Abrid n ufaylu" + +#, python-format +msgid "'%(value)s' value must be a float." +msgstr "" + +msgid "Floating point number" +msgstr "" + +msgid "IPv4 address" +msgstr "" + +msgid "IP address" +msgstr "Tansa IP" + +#, python-format +msgid "'%(value)s' value must be either None, True or False." +msgstr "" + +msgid "Boolean (Either True, False or None)" +msgstr "" + +msgid "Positive integer" +msgstr "" + +msgid "Positive small integer" +msgstr "" + +#, python-format +msgid "Slug (up to %(max_length)s)" +msgstr "" + +msgid "Small integer" +msgstr "" + +msgid "Text" +msgstr "Aḍris" + +#, python-format +msgid "" +"'%(value)s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " +"format." +msgstr "" + +#, python-format +msgid "" +"'%(value)s' value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " +"invalid time." +msgstr "" + +msgid "Time" +msgstr "Akud" + +msgid "URL" +msgstr "URL" + +msgid "Raw binary data" +msgstr "" + +#, python-format +msgid "'%(value)s' is not a valid UUID." +msgstr "" + +msgid "File" +msgstr "Afaylu" + +msgid "Image" +msgstr "Tugna" + +#, python-format +msgid "%(model)s instance with %(field)s %(value)r does not exist." +msgstr "" + +msgid "Foreign Key (type determined by related field)" +msgstr "" + +msgid "One-to-one relationship" +msgstr "" + +#, python-format +msgid "%(from)s-%(to)s relationship" +msgstr "" + +#, python-format +msgid "%(from)s-%(to)s relationships" +msgstr "" + +msgid "Many-to-many relationship" +msgstr "" + +#. Translators: If found as last label character, these punctuation +#. characters will prevent the default label_suffix to be appended to the +#. label +msgid ":?.!" +msgstr ":?.!" + +msgid "This field is required." +msgstr "" + +msgid "Enter a whole number." +msgstr "Sekcem amḍan ummid." + +msgid "Enter a number." +msgstr "Sekcem amḍan." + +msgid "Enter a valid date." +msgstr "" + +msgid "Enter a valid time." +msgstr "" + +msgid "Enter a valid date/time." +msgstr "" + +msgid "Enter a valid duration." +msgstr "" + +msgid "No file was submitted. Check the encoding type on the form." +msgstr "" + +msgid "No file was submitted." +msgstr "Afaylu ur yettwazen ara." + +msgid "The submitted file is empty." +msgstr "" + +#, python-format +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] "" +msgstr[1] "" + +msgid "Please either submit a file or check the clear checkbox, not both." +msgstr "" + +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" + +#, python-format +msgid "Select a valid choice. %(value)s is not one of the available choices." +msgstr "" + +msgid "Enter a list of values." +msgstr "" + +msgid "Enter a complete value." +msgstr "Sekcem azal ummid." + +msgid "Enter a valid UUID." +msgstr "" + +#. Translators: This is the default suffix added to form field labels +msgid ":" +msgstr ":" + +#, python-format +msgid "(Hidden field %(name)s) %(error)s" +msgstr "" + +msgid "ManagementForm data is missing or has been tampered with" +msgstr "" + +#, python-format +msgid "Please submit %d or fewer forms." +msgid_plural "Please submit %d or fewer forms." +msgstr[0] "" +msgstr[1] "" + +#, python-format +msgid "Please submit %d or more forms." +msgid_plural "Please submit %d or more forms." +msgstr[0] "" +msgstr[1] "" + +msgid "Order" +msgstr "Amizwer" + +msgid "Delete" +msgstr "KKES" + +#, python-format +msgid "Please correct the duplicate data for %(field)s." +msgstr "" + +#, python-format +msgid "Please correct the duplicate data for %(field)s, which must be unique." +msgstr "" + +#, 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 "" + +msgid "Please correct the duplicate values below." +msgstr "" + +msgid "The inline value did not match the parent instance." +msgstr "" + +msgid "Select a valid choice. That choice is not one of the available choices." +msgstr "" + +#, python-format +msgid "\"%(pk)s\" is not a valid value." +msgstr "" + +#, 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 "" + +msgid "Clear" +msgstr "Sfeḍ" + +msgid "Currently" +msgstr "Tura" + +msgid "Change" +msgstr "Beddel" + +msgid "Unknown" +msgstr "Arussin" + +msgid "Yes" +msgstr "Ih" + +msgid "No" +msgstr "Uhu" + +msgid "yes,no,maybe" +msgstr "" + +#, python-format +msgid "%(size)d byte" +msgid_plural "%(size)d bytes" +msgstr[0] "" +msgstr[1] "" + +#, python-format +msgid "%s KB" +msgstr "%s KAṬ" + +#, python-format +msgid "%s MB" +msgstr "%s MAṬ" + +#, python-format +msgid "%s GB" +msgstr "%s GAṬ" + +#, python-format +msgid "%s TB" +msgstr "%s TAṬ" + +#, python-format +msgid "%s PB" +msgstr "%s PAṬ" + +msgid "p.m." +msgstr "m.d." + +msgid "a.m." +msgstr "f.t." + +msgid "PM" +msgstr "MD" + +msgid "AM" +msgstr "FT" + +msgid "midnight" +msgstr "ttnaṣfa n yiḍ" + +msgid "noon" +msgstr "ttnaṣfa n uzal" + +msgid "Monday" +msgstr "Arim" + +msgid "Tuesday" +msgstr "Aram" + +msgid "Wednesday" +msgstr "Ahad" + +msgid "Thursday" +msgstr "Amhad" + +msgid "Friday" +msgstr "Sem" + +msgid "Saturday" +msgstr "Sed" + +msgid "Sunday" +msgstr "Acer" + +msgid "Mon" +msgstr "Ari" + +msgid "Tue" +msgstr "Ara" + +msgid "Wed" +msgstr "Aha" + +msgid "Thu" +msgstr "Amh" + +msgid "Fri" +msgstr "Sem" + +msgid "Sat" +msgstr "Sed" + +msgid "Sun" +msgstr "Ace" + +msgid "January" +msgstr "Yennayer" + +msgid "February" +msgstr "Fuṛaṛ" + +msgid "March" +msgstr "Meɣres" + +msgid "April" +msgstr "Yebrir" + +msgid "May" +msgstr "Mayyu" + +msgid "June" +msgstr "Yunyu" + +msgid "July" +msgstr "Yulyu" + +msgid "August" +msgstr "Ɣuct" + +msgid "September" +msgstr "Ctamber" + +msgid "October" +msgstr "Tuber" + +msgid "November" +msgstr "Wamber" + +msgid "December" +msgstr "Dujamber" + +msgid "jan" +msgstr "yen" + +msgid "feb" +msgstr "fuṛ" + +msgid "mar" +msgstr "meɣ" + +msgid "apr" +msgstr "yeb" + +msgid "may" +msgstr "may" + +msgid "jun" +msgstr "yun" + +msgid "jul" +msgstr "yul" + +msgid "aug" +msgstr "ɣuc" + +msgid "sep" +msgstr "cte" + +msgid "oct" +msgstr "tub" + +msgid "nov" +msgstr "wam" + +msgid "dec" +msgstr "duj" + +msgctxt "abbrev. month" +msgid "Jan." +msgstr "Yen." + +msgctxt "abbrev. month" +msgid "Feb." +msgstr "Fuṛ." + +msgctxt "abbrev. month" +msgid "March" +msgstr "Meɣres" + +msgctxt "abbrev. month" +msgid "April" +msgstr "Yebrir" + +msgctxt "abbrev. month" +msgid "May" +msgstr "Mayyu" + +msgctxt "abbrev. month" +msgid "June" +msgstr "Yunyu" + +msgctxt "abbrev. month" +msgid "July" +msgstr "Yulyu" + +msgctxt "abbrev. month" +msgid "Aug." +msgstr "Ɣuc." + +msgctxt "abbrev. month" +msgid "Sept." +msgstr "" + +msgctxt "abbrev. month" +msgid "Oct." +msgstr "Tub." + +msgctxt "abbrev. month" +msgid "Nov." +msgstr "Wam." + +msgctxt "abbrev. month" +msgid "Dec." +msgstr "Duj." + +msgctxt "alt. month" +msgid "January" +msgstr "Yennayer" + +msgctxt "alt. month" +msgid "February" +msgstr "Fuṛaṛ" + +msgctxt "alt. month" +msgid "March" +msgstr "Meɣres" + +msgctxt "alt. month" +msgid "April" +msgstr "Yebrir" + +msgctxt "alt. month" +msgid "May" +msgstr "Mayyu" + +msgctxt "alt. month" +msgid "June" +msgstr "Yunyu" + +msgctxt "alt. month" +msgid "July" +msgstr "Yulyu" + +msgctxt "alt. month" +msgid "August" +msgstr "Ɣuct" + +msgctxt "alt. month" +msgid "September" +msgstr "Ctamber" + +msgctxt "alt. month" +msgid "October" +msgstr "Tuber" + +msgctxt "alt. month" +msgid "November" +msgstr "Wamber" + +msgctxt "alt. month" +msgid "December" +msgstr "Dujamber" + +msgid "This is not a valid IPv6 address." +msgstr "" + +#, python-format +msgctxt "String to return when truncating text" +msgid "%(truncated_text)s..." +msgstr "" + +msgid "or" +msgstr "neɣ" + +#. Translators: This string is used as a separator between list elements +msgid ", " +msgstr ", " + +#, python-format +msgid "%d year" +msgid_plural "%d years" +msgstr[0] "" +msgstr[1] "" + +#, python-format +msgid "%d month" +msgid_plural "%d months" +msgstr[0] "" +msgstr[1] "" + +#, python-format +msgid "%d week" +msgid_plural "%d weeks" +msgstr[0] "" +msgstr[1] "" + +#, python-format +msgid "%d day" +msgid_plural "%d days" +msgstr[0] "" +msgstr[1] "" + +#, python-format +msgid "%d hour" +msgid_plural "%d hours" +msgstr[0] "" +msgstr[1] "" + +#, python-format +msgid "%d minute" +msgid_plural "%d minutes" +msgstr[0] "" +msgstr[1] "" + +msgid "0 minutes" +msgstr "0 n tisdatin" + +msgid "Forbidden" +msgstr "Yegdel" + +msgid "CSRF verification failed. Request aborted." +msgstr "" + +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 "" + +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 "" + +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." +msgstr "" + +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 "" + +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 "" + +msgid "More information is available with DEBUG=True." +msgstr "" + +msgid "No year specified" +msgstr "" + +msgid "Date out of range" +msgstr "" + +msgid "No month specified" +msgstr "" + +msgid "No day specified" +msgstr "" + +msgid "No week specified" +msgstr "" + +#, python-format +msgid "No %(verbose_name_plural)s available" +msgstr "" + +#, python-format +msgid "" +"Future %(verbose_name_plural)s not available because %(class_name)s." +"allow_future is False." +msgstr "" + +#, python-format +msgid "Invalid date string '%(datestr)s' given format '%(format)s'" +msgstr "" + +#, python-format +msgid "No %(verbose_name)s found matching the query" +msgstr "" + +msgid "Page is not 'last', nor can it be converted to an int." +msgstr "" + +#, python-format +msgid "Invalid page (%(page_number)s): %(message)s" +msgstr "" + +#, python-format +msgid "Empty list and '%(class_name)s.allow_empty' is False." +msgstr "" + +msgid "Directory indexes are not allowed here." +msgstr "" + +#, python-format +msgid "\"%(path)s\" does not exist" +msgstr "" + +#, python-format +msgid "Index of %(directory)s" +msgstr "" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "Bdu s Django" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/kk/LC_MESSAGES/django.mo b/django/conf/locale/kk/LC_MESSAGES/django.mo index a25f3f005710..0c426b6c0516 100644 Binary files a/django/conf/locale/kk/LC_MESSAGES/django.mo and b/django/conf/locale/kk/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/kk/LC_MESSAGES/django.po b/django/conf/locale/kk/LC_MESSAGES/django.po index b763701485bc..01e7bb766e21 100644 --- a/django/conf/locale/kk/LC_MESSAGES/django.po +++ b/django/conf/locale/kk/LC_MESSAGES/django.po @@ -3,21 +3,22 @@ # Translators: # Baurzhan Muftakhidinov , 2015 # Zharzhan Kulmyrza , 2011 +# Leo Trubach , 2017 # Nurlan Rakhimzhanov , 2011 # yun_man_ger , 2011 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 00:44+0000\n" +"Last-Translator: Ramiro Morales\n" "Language-Team: Kazakh (http://www.transifex.com/django/django/language/kk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: kk\n" -"Plural-Forms: nplurals=1; plural=0;\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" msgid "Afrikaans" msgstr "" @@ -139,6 +140,9 @@ msgstr "" msgid "Hungarian" msgstr "Венгрия" +msgid "Armenian" +msgstr "" + msgid "Interlingua" msgstr "" @@ -160,6 +164,9 @@ msgstr "Жапон" msgid "Georgian" msgstr "Грузин" +msgid "Kabyle" +msgstr "" + msgid "Kazakh" msgstr "Қазақша" @@ -295,6 +302,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "Тура мәнін енгізіңіз." @@ -307,6 +323,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -353,6 +370,7 @@ msgid_plural "" "Ensure this value has at least %(limit_value)d characters (it has " "%(show_value)d)." msgstr[0] "" +msgstr[1] "" #, python-format msgid "" @@ -362,16 +380,22 @@ msgid_plural "" "Ensure this value has at most %(limit_value)d characters (it has " "%(show_value)d)." msgstr[0] "" +msgstr[1] "" + +msgid "Enter a number." +msgstr "Сан енгізіңіз." #, 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] "" #, 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] "" #, python-format msgid "" @@ -379,6 +403,16 @@ msgid "" msgid_plural "" "Ensure that there are no more than %(max)s digits before the decimal point." msgstr[0] "" +msgstr[1] "" + +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" msgid "and" msgstr "және" @@ -426,6 +460,10 @@ msgstr "Ұзын (8 байт) бүтін сан" msgid "'%(value)s' value must be either True or False." msgstr "" +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "" + msgid "Boolean (Either True or False)" msgstr "Boolean (True немесе False)" @@ -549,6 +587,9 @@ msgstr "" msgid "'%(value)s' is not a valid UUID." msgstr "" +msgid "Universally unique identifier" +msgstr "" + msgid "File" msgstr "" @@ -588,9 +629,6 @@ msgstr "Бұл өрісті толтыру міндетті." msgid "Enter a whole number." msgstr "Толық санды енгізіңіз." -msgid "Enter a number." -msgstr "Сан енгізіңіз." - msgid "Enter a valid date." msgstr "Дұрыс күнді енгізіңіз." @@ -603,6 +641,10 @@ msgstr "Дұрыс күнді/уақытты енгізіңіз." msgid "Enter a valid duration." msgstr "" +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "" + msgid "No file was submitted. Check the encoding type on the form." msgstr "Ешқандай файл жіберілмеді. Форманың кодтау түрін тексеріңіз." @@ -617,6 +659,7 @@ 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] "" +msgstr[1] "" msgid "Please either submit a file or check the clear checkbox, not both." msgstr "Файлды жіберіңіз немесе тазалауды белгіленіз, екеуін бірге емес." @@ -655,11 +698,13 @@ msgstr "" msgid "Please submit %d or fewer forms." msgid_plural "Please submit %d or fewer forms." msgstr[0] "" +msgstr[1] "" #, python-format msgid "Please submit %d or more forms." msgid_plural "Please submit %d or more forms." msgstr[0] "" +msgstr[1] "" msgid "Order" msgstr "Сұрыптау" @@ -686,15 +731,14 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Қайталанатын мәндерді түзетіңіз." -msgid "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." msgstr "" -"Кірістірілген сыртқы кілт аталық дананың бастапқы кілтімен сәйкес келмейді." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Дұрыс нұсқаны таңдаңыз. Бұл нұсқа дұрыс таңдаулардың арасында жоқ." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -703,15 +747,15 @@ msgid "" "may be ambiguous or it may not exist." msgstr "" +msgid "Clear" +msgstr "Тазалау" + msgid "Currently" msgstr "Ағымдағы" msgid "Change" msgstr "Түзету" -msgid "Clear" -msgstr "Тазалау" - msgid "Unknown" msgstr "Белгісіз" @@ -728,6 +772,7 @@ msgstr "иә,жоқ,мүмкін" msgid "%(size)d byte" msgid_plural "%(size)d bytes" msgstr[0] "%(size)d байт" +msgstr[1] "%(size)d байт" #, python-format msgid "%s KB" @@ -883,11 +928,11 @@ msgstr "жел" msgctxt "abbrev. month" msgid "Jan." -msgstr "Ақп." +msgstr "Қаң." msgctxt "abbrev. month" msgid "Feb." -msgstr "Қаң." +msgstr "Ақп." msgctxt "abbrev. month" msgid "March" @@ -982,7 +1027,7 @@ msgstr "" #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." +msgid "%(truncated_text)s…" msgstr "" msgid "or" @@ -996,31 +1041,37 @@ msgstr ", " msgid "%d year" msgid_plural "%d years" msgstr[0] "" +msgstr[1] "" #, python-format msgid "%d month" msgid_plural "%d months" msgstr[0] "" +msgstr[1] "" #, python-format msgid "%d week" msgid_plural "%d weeks" msgstr[0] "" +msgstr[1] "" #, python-format msgid "%d day" msgid_plural "%d days" msgstr[0] "" +msgstr[1] "" #, python-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "" +msgstr[1] "" #, python-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "" +msgstr[1] "" msgid "0 minutes" msgstr "" @@ -1044,6 +1095,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1058,28 +1117,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "Жыл таңдалмаған" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "Ай таңдалмаған" @@ -1130,3 +1173,41 @@ msgstr "" #, python-format msgid "Index of %(directory)s" msgstr "" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/km/LC_MESSAGES/django.mo b/django/conf/locale/km/LC_MESSAGES/django.mo index 2ffa89d3411a..78826d7e3729 100644 Binary files a/django/conf/locale/km/LC_MESSAGES/django.mo and b/django/conf/locale/km/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/km/LC_MESSAGES/django.po b/django/conf/locale/km/LC_MESSAGES/django.po index 2a94fc846d22..2e2bef8f2e7f 100644 --- a/django/conf/locale/km/LC_MESSAGES/django.po +++ b/django/conf/locale/km/LC_MESSAGES/django.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Khmer (http://www.transifex.com/django/django/language/km/)\n" "MIME-Version: 1.0\n" @@ -292,6 +292,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "" @@ -304,6 +313,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -372,6 +382,15 @@ msgid_plural "" "Ensure that there are no more than %(max)s digits before the decimal point." msgstr[0] "" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "និង" @@ -675,14 +694,14 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "" -msgid "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -691,15 +710,15 @@ msgid "" "may be ambiguous or it may not exist." msgstr "" +msgid "Clear" +msgstr "" + msgid "Currently" msgstr "" msgid "Change" msgstr "ផ្លាស់ប្តូរ" -msgid "Clear" -msgstr "" - msgid "Unknown" msgstr "មិន​ដឹង" @@ -1032,6 +1051,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1046,26 +1073,10 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" +msgid "No year specified" msgstr "" -msgid "No year specified" +msgid "Date out of range" msgstr "" msgid "No month specified" @@ -1116,3 +1127,41 @@ msgstr "" #, python-format msgid "Index of %(directory)s" msgstr "" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/km/formats.py b/django/conf/locale/km/formats.py index 52ff4f95e844..b704e9c62d60 100644 --- a/django/conf/locale/km/formats.py +++ b/django/conf/locale/km/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j ខែ F ឆ្នាំ Y' TIME_FORMAT = 'G:i' DATETIME_FORMAT = 'j ខែ F ឆ្នាំ Y, G:i' @@ -15,7 +12,7 @@ # FIRST_DAY_OF_WEEK = # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # DATE_INPUT_FORMATS = # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = diff --git a/django/conf/locale/kn/LC_MESSAGES/django.mo b/django/conf/locale/kn/LC_MESSAGES/django.mo index 3140001b7bf8..ccae161f3146 100644 Binary files a/django/conf/locale/kn/LC_MESSAGES/django.mo and b/django/conf/locale/kn/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/kn/LC_MESSAGES/django.po b/django/conf/locale/kn/LC_MESSAGES/django.po index fd9331d77535..cb0e8edc99b2 100644 --- a/django/conf/locale/kn/LC_MESSAGES/django.po +++ b/django/conf/locale/kn/LC_MESSAGES/django.po @@ -8,16 +8,16 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 00:44+0000\n" +"Last-Translator: Ramiro Morales\n" "Language-Team: Kannada (http://www.transifex.com/django/django/language/" "kn/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: kn\n" -"Plural-Forms: nplurals=1; plural=0;\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" msgid "Afrikaans" msgstr "" @@ -139,6 +139,9 @@ msgstr "" msgid "Hungarian" msgstr "ಹಂಗೇರಿಯನ್" +msgid "Armenian" +msgstr "" + msgid "Interlingua" msgstr "" @@ -160,6 +163,9 @@ msgstr "ಜಾಪನೀಸ್" msgid "Georgian" msgstr "ಜಾರ್ಜೆಯನ್ " +msgid "Kabyle" +msgstr "" + msgid "Kazakh" msgstr "" @@ -295,6 +301,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "ಸಿಂಧುವಾದ ಮೌಲ್ಯವನ್ನು ನಮೂದಿಸಿ." @@ -307,6 +322,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -355,6 +371,7 @@ msgid_plural "" "Ensure this value has at least %(limit_value)d characters (it has " "%(show_value)d)." msgstr[0] "" +msgstr[1] "" #, python-format msgid "" @@ -364,16 +381,22 @@ msgid_plural "" "Ensure this value has at most %(limit_value)d characters (it has " "%(show_value)d)." msgstr[0] "" +msgstr[1] "" + +msgid "Enter a number." +msgstr "ಒಂದು ಸಂಖ್ಯೆಯನ್ನು ನಮೂದಿಸಿ." #, 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] "" #, 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] "" #, python-format msgid "" @@ -381,6 +404,16 @@ msgid "" msgid_plural "" "Ensure that there are no more than %(max)s digits before the decimal point." msgstr[0] "" +msgstr[1] "" + +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" msgid "and" msgstr "ಮತ್ತು" @@ -429,6 +462,10 @@ msgstr "ಬೃಹತ್ (೮ ಬೈಟ್) ಪೂರ್ಣ ಸಂಖ್ಯೆ" msgid "'%(value)s' value must be either True or False." msgstr "" +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "" + msgid "Boolean (Either True or False)" msgstr "ಬೂಲಿಯನ್ (ಹೌದು ಅಥವ ಅಲ್ಲ)" @@ -552,6 +589,9 @@ msgstr "" msgid "'%(value)s' is not a valid UUID." msgstr "" +msgid "Universally unique identifier" +msgstr "" + msgid "File" msgstr "" @@ -591,9 +631,6 @@ msgstr "ಈ ಸ್ಥಳವು ಅಗತ್ಯವಿರುತ್ತದೆ." msgid "Enter a whole number." msgstr "ಪೂರ್ಣಾಂಕವೊಂದನ್ನು ನಮೂದಿಸಿ." -msgid "Enter a number." -msgstr "ಒಂದು ಸಂಖ್ಯೆಯನ್ನು ನಮೂದಿಸಿ." - msgid "Enter a valid date." msgstr "ಸರಿಯಾದ ದಿನಾಂಕವನ್ನು ನಮೂದಿಸಿ." @@ -606,6 +643,10 @@ msgstr "ಸರಿಯಾದ ದಿನಾಂಕ/ಸಮಯವನ್ನು ನಮೂ msgid "Enter a valid duration." msgstr "" +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "" + msgid "No file was submitted. Check the encoding type on the form." msgstr "" "ಯಾವದೇ ಕಡತವನ್ನೂ ಸಲ್ಲಿಸಲಾಗಿಲ್ಲ. ನಮೂನೆಯ ಮೇಲಿನ ಸಂಕೇತೀಕರಣ (ಎನ್ಕೋಡಿಂಗ್) ಬಗೆಯನ್ನು " @@ -622,6 +663,7 @@ 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] "" +msgstr[1] "" msgid "Please either submit a file or check the clear checkbox, not both." msgstr "" @@ -663,11 +705,13 @@ msgstr "" msgid "Please submit %d or fewer forms." msgid_plural "Please submit %d or fewer forms." msgstr[0] "" +msgstr[1] "" #, python-format msgid "Please submit %d or more forms." msgid_plural "Please submit %d or more forms." msgstr[0] "" +msgstr[1] "" msgid "Order" msgstr "ಕ್ರಮ" @@ -696,14 +740,14 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "ದಯವಿಟ್ಟು ಈ ಕೆಳಗೆ ಎರಡು ಬಾರಿ ನಮೂದಿಸಲಾದ ಮೌಲ್ಯವನ್ನು ಸರಿಪಡಿಸಿ." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "ಸಾಲಿನೊಳಗಿನ ಪ್ರಾಥಮಿಕ ಕೀಲಿಯು ಮೂಲ ಇನ್‌ಸ್ಟನ್ಸ್‍ ಪ್ರಾಥಮಿಕ ಕೀಲಿಗೆ ತಾಳೆಯಾಗುತ್ತಿಲ್ಲ." +msgid "The inline value did not match the parent instance." +msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "ಸರಿಯಾದ ಒಂದು ಆಯ್ಕೆಯನ್ನು ಆರಿಸಿ. ಆ ಆಯ್ಕೆಯು ಲಭ್ಯವಿರುವ ಆಯ್ಕೆಗಳಲ್ಲಿ ಇಲ್ಲ." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -712,15 +756,15 @@ msgid "" "may be ambiguous or it may not exist." msgstr "" +msgid "Clear" +msgstr "ಮುಕ್ತಗೊಳಿಸು" + msgid "Currently" msgstr "ಪ್ರಸಕ್ತ" msgid "Change" msgstr "ಬದಲಾವಣೆ" -msgid "Clear" -msgstr "ಮುಕ್ತಗೊಳಿಸು" - msgid "Unknown" msgstr "ಗೊತ್ತಿರದ" @@ -737,6 +781,7 @@ msgstr "ಹೌದು,ಇಲ್ಲ,ಇರಬಹುದು" msgid "%(size)d byte" msgid_plural "%(size)d bytes" msgstr[0] "%(size)d ಬೈಟ್‌ಗಳು" +msgstr[1] "%(size)d ಬೈಟ್‌ಗಳು" #, python-format msgid "%s KB" @@ -991,7 +1036,7 @@ msgstr "" #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." +msgid "%(truncated_text)s…" msgstr "" msgid "or" @@ -1005,31 +1050,37 @@ msgstr ", " msgid "%d year" msgid_plural "%d years" msgstr[0] "" +msgstr[1] "" #, python-format msgid "%d month" msgid_plural "%d months" msgstr[0] "" +msgstr[1] "" #, python-format msgid "%d week" msgid_plural "%d weeks" msgstr[0] "" +msgstr[1] "" #, python-format msgid "%d day" msgid_plural "%d days" msgstr[0] "" +msgstr[1] "" #, python-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "" +msgstr[1] "" #, python-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "" +msgstr[1] "" msgid "0 minutes" msgstr "" @@ -1053,6 +1104,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1067,28 +1126,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "ಯಾವುದೆ ವರ್ಷವನ್ನು ಸೂಚಿಲಾಗಿಲ್ಲ" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "ಯಾವುದೆ ತಿಂಗಳನ್ನು ಸೂಚಿಸಲಾಗಿಲ್ಲ" @@ -1141,3 +1184,41 @@ msgstr "" #, python-format msgid "Index of %(directory)s" msgstr "" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/kn/formats.py b/django/conf/locale/kn/formats.py index 4b8355165e0d..5003c6441b0a 100644 --- a/django/conf/locale/kn/formats.py +++ b/django/conf/locale/kn/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j F Y' TIME_FORMAT = 'h:i A' # DATETIME_FORMAT = @@ -15,7 +12,7 @@ # FIRST_DAY_OF_WEEK = # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # DATE_INPUT_FORMATS = # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = diff --git a/django/conf/locale/ko/LC_MESSAGES/django.mo b/django/conf/locale/ko/LC_MESSAGES/django.mo index de0026924d51..dfbc08446a0a 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 d731b63dca33..13ba0d3f563f 100644 --- a/django/conf/locale/ko/LC_MESSAGES/django.po +++ b/django/conf/locale/ko/LC_MESSAGES/django.po @@ -2,22 +2,28 @@ # # Translators: # BJ Jang , 2014 +# JunGu Kang , 2017 # Jiyoon, Ha , 2016 +# Park Hyunwoo , 2017 +# hoseung2 , 2017 # Ian Y. Choi , 2015 # Jaehong Kim , 2011 # Jannis Leidel , 2011 -# Jeong Seongtae , 2014,2016 +# Le Tartuffe , 2014,2016 +# Jonghwa Seo , 2019 # JuneHyeon Bae , 2014 -# Chr0m3 , 2015 +# JunGu Kang , 2015 +# Kagami Sascha Rosylight , 2017 +# Noh Seho , 2018 # Subin Choi , 2016 # Taesik Yoon , 2015 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-09-16 06:09+0000\n" -"Last-Translator: Jiyoon, Ha \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-04-28 13:30+0000\n" +"Last-Translator: Jonghwa Seo \n" "Language-Team: Korean (http://www.transifex.com/django/django/language/ko/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -145,6 +151,9 @@ msgstr "고지 소르브어" msgid "Hungarian" msgstr "헝가리어" +msgid "Armenian" +msgstr "아르메니아어" + msgid "Interlingua" msgstr "인테르링구아어" @@ -166,6 +175,9 @@ msgstr "일본어" msgid "Georgian" msgstr "조지아어" +msgid "Kabyle" +msgstr "커바일어" + msgid "Kazakh" msgstr "카자흐어" @@ -301,6 +313,15 @@ msgstr "정적 파일" msgid "Syndication" msgstr "신디케이션" +msgid "That page number is not an integer" +msgstr "페이지 번호가 정수가 아닙니다." + +msgid "That page number is less than 1" +msgstr "페이지 번호가 1보다 작습니다." + +msgid "That page contains no results" +msgstr "해당 페이지에 결과가 없습니다." + msgid "Enter a valid value." msgstr "올바른 값을 입력하세요." @@ -313,6 +334,7 @@ msgstr "올바른 정수를 입력하세요." msgid "Enter a valid email address." msgstr "올바른 이메일 주소를 입력하세요." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "문자, 숫자, '_', '-'만 가능합니다." @@ -372,6 +394,9 @@ msgstr[0] "" "이 값이 최대 %(limit_value)d 개의 글자인지 확인하세요(입력값 %(show_value)d " "자)." +msgid "Enter a number." +msgstr "숫자를 입력하세요." + #, 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." @@ -389,6 +414,17 @@ msgid_plural "" "Ensure that there are no more than %(max)s digits before the decimal point." msgstr[0] "전체 유효자리 개수가 %(max)s 개를 넘지 않도록 해주세요." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"파일 확장자 '%(extension)s'는 허용되지 않습니다. 허용된 확장자 : " +"'%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "null 문자는 사용할 수 없습니다. " + msgid "and" msgstr "또한" @@ -437,6 +473,10 @@ msgstr "큰 정수 (8 byte)" msgid "'%(value)s' value must be either True or False." msgstr "'%(value)s' 값은 값이 없거나, 참 또는 거짓 중 하나 여야 합니다." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "'%(value)s'값은 반드시 True, False, None 중 하나여야만 합니다." + msgid "Boolean (Either True or False)" msgstr "boolean(참 또는 거짓)" @@ -571,6 +611,9 @@ msgstr "Raw binary data" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' 은 유효하지 않은 UUID 입니다." +msgid "Universally unique identifier" +msgstr "" + msgid "File" msgstr "파일" @@ -610,9 +653,6 @@ msgstr "필수 항목입니다." msgid "Enter a whole number." msgstr "정수를 입력하세요." -msgid "Enter a number." -msgstr "숫자를 입력하세요." - msgid "Enter a valid date." msgstr "올바른 날짜를 입력하세요." @@ -625,6 +665,10 @@ msgstr "올바른 날짜/시각을 입력하세요." msgid "Enter a valid duration." msgstr "올바른 기간을 입력하세요." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "날짜는 {min_days}와 {max_days} 사이여야 합니다." + msgid "No file was submitted. Check the encoding type on the form." msgstr "등록된 파일이 없습니다. 인코딩 형식을 확인하세요." @@ -641,7 +685,8 @@ 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 " @@ -709,15 +754,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "아래의 중복된 값들을 고쳐주세요." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "부모 오브젝트의 primary key와 inline foreign key가 맞지 않습니다." +msgid "The inline value did not match the parent instance." +msgstr "Inline 값이 부모 인스턴스와 일치하지 않습니다." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "올바르게 선택해 주세요. 선택하신 것이 선택가능항목에 없습니다." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\"은/는 primary key로 적합하지 않습니다." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" 은/는 유효한 값이 아닙니다." #, python-format msgid "" @@ -727,15 +772,15 @@ msgstr "" "%(datetime)s 은/는 %(current_timezone)s 시간대에서 해석될 수 없습니다; 정보" "가 모호하거나 존재하지 않을 수 있습니다." +msgid "Clear" +msgstr "취소" + msgid "Currently" msgstr "현재" msgid "Change" msgstr "변경" -msgid "Clear" -msgstr "취소" - msgid "Unknown" msgstr "알 수 없습니다." @@ -1006,8 +1051,8 @@ msgstr "올바른 IPv6 주소가 아닙니다." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s ..." +msgid "%(truncated_text)s…" +msgstr "" msgid "or" msgstr "또는" @@ -1074,6 +1119,19 @@ msgstr "" "만약 브라우저 설정에서 '참조' 헤더를 비활성화 시켰을 경우, 적어도 이 사이트" "나 HTTPS 연결, '동일-출처' 요청에 대해서는 이를 다시 활성화 시키십시오. " +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." +msgstr "" +"태그나 'Referrer-Policy: no-" +"referrer' 헤더를 포함하고 있다면, 제거해주시기 바랍니다. CSRF 방지를 위한 리" +"퍼러 검사를 위해 'Referer' 헤더가 필요합니다. 개인 정보에 대해 우려가 있는 경" +"우, 서드 파티 사이트에 대한 링크에 와 같은 대안을 " +"사용할 수 있습니다." + 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 " @@ -1093,32 +1151,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "DEBUG=True 로 더 많은 정보를 확인할 수 있습니다." -msgid "Welcome to Django" -msgstr "Django에 오신 것을 환영합니다!" - -msgid "It worked!" -msgstr "작동중!" - -msgid "Congratulations on your first Django-powered page." -msgstr "첫 번째 Django 페이지를 만든 것을 축하드립니다." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"물론, 당신은 아직 어떤 결과물을 만든 것은 아닙니다. 첫 어플리케이션은 다음 명" -"령을 실행하여 시작하세요. python manage.py startapp [app_label]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"이 메세지가 보이는 이유는 당신의 Django 설정 파일에 DEBUG = True" -"가 있고, 아직 아무런 URL을 설정하지 않았기 때문입니다." - msgid "No year specified" msgstr "년도가 없습니다." +msgid "Date out of range" +msgstr "유효 범위 밖의 날짜" + msgid "No month specified" msgstr "월이 없습니다." @@ -1169,3 +1207,46 @@ msgstr "\"%(path)s\" 가 존재하지 않습니다." #, python-format msgid "Index of %(directory)s" msgstr "Index of %(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." +msgstr "" +"이 페이지는 어떤 URL도 지정되지 않았고, settings 파일에 DEBUG=True가 설정되어 있을 때 표시됩니다." + +msgid "Django Documentation" +msgstr "Django 문서" + +msgid "Topics, references, & how-to's" +msgstr "주제, 레퍼런스, & 입문참조하다" + +msgid "Tutorial: A Polling App" +msgstr "튜토리얼: 폴링 애플리케이션" + +msgid "Get started with Django" +msgstr "Django와 함께 시작하기" + +msgid "Django Community" +msgstr "Django 커뮤니티" + +msgid "Connect, get help, or contribute" +msgstr "연결하고, 도움을 받거나 기여하기" diff --git a/django/conf/locale/ko/formats.py b/django/conf/locale/ko/formats.py index 0344e9e16102..be2004c1b5a0 100644 --- a/django/conf/locale/ko/formats.py +++ b/django/conf/locale/ko/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'Y년 n월 j일' TIME_FORMAT = 'A g:i' DATETIME_FORMAT = 'Y년 n월 j일 g:i A' @@ -15,7 +12,7 @@ # FIRST_DAY_OF_WEEK = # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # Kept ISO formats as they are in first position DATE_INPUT_FORMATS = [ '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06' diff --git a/django/conf/locale/lb/LC_MESSAGES/django.mo b/django/conf/locale/lb/LC_MESSAGES/django.mo index a7c873d4ec2a..661e98ecf5db 100644 Binary files a/django/conf/locale/lb/LC_MESSAGES/django.mo and b/django/conf/locale/lb/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/lb/LC_MESSAGES/django.po b/django/conf/locale/lb/LC_MESSAGES/django.po index 43d9b349e8fb..9ee5eb21129c 100644 --- a/django/conf/locale/lb/LC_MESSAGES/django.po +++ b/django/conf/locale/lb/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Luxembourgish (http://www.transifex.com/django/django/" "language/lb/)\n" @@ -294,6 +294,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "Gëff en validen Wärt an." @@ -306,6 +315,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "Gëff eng valid e-mail Adress an." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -379,6 +389,15 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "an" @@ -685,14 +704,14 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "" -msgid "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -701,15 +720,15 @@ msgid "" "may be ambiguous or it may not exist." msgstr "" +msgid "Clear" +msgstr "Maach eidel" + msgid "Currently" msgstr "Momentan" msgid "Change" msgstr "Änner" -msgid "Clear" -msgstr "Maach eidel" - msgid "Unknown" msgstr "Onbekannt" @@ -1049,6 +1068,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1063,26 +1090,10 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" +msgid "No year specified" msgstr "" -msgid "No year specified" +msgid "Date out of range" msgstr "" msgid "No month specified" @@ -1133,3 +1144,41 @@ msgstr "" #, python-format msgid "Index of %(directory)s" msgstr "" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/lt/LC_MESSAGES/django.mo b/django/conf/locale/lt/LC_MESSAGES/django.mo index 3d71e458e3a8..23004a59f134 100644 Binary files a/django/conf/locale/lt/LC_MESSAGES/django.mo and b/django/conf/locale/lt/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/lt/LC_MESSAGES/django.po b/django/conf/locale/lt/LC_MESSAGES/django.po index 0e78097cd69c..0af50ea79422 100644 --- a/django/conf/locale/lt/LC_MESSAGES/django.po +++ b/django/conf/locale/lt/LC_MESSAGES/django.po @@ -4,7 +4,7 @@ # Jannis Leidel , 2011 # Kostas , 2011 # lauris , 2011 -# Matas Dailyda , 2015-2016 +# Matas Dailyda , 2015-2019 # naktinis , 2012 # Nikolajus Krauklis , 2013 # Povilas Balzaravičius , 2011-2012 @@ -14,8 +14,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-07-18 09:11+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 10:33+0000\n" "Last-Translator: Matas Dailyda \n" "Language-Team: Lithuanian (http://www.transifex.com/django/django/language/" "lt/)\n" @@ -23,8 +23,9 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: lt\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n" -"%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=4; plural=(n % 10 == 1 && (n % 100 > 19 || n % 100 < " +"11) ? 0 : (n % 10 >= 2 && n % 10 <=9) && (n % 100 > 19 || n % 100 < 11) ? " +"1 : n % 1 != 0 ? 2: 3);\n" msgid "Afrikaans" msgstr "Afrikiečių" @@ -146,6 +147,9 @@ msgstr "Aukštutinė Sorbų" msgid "Hungarian" msgstr "Vengrų" +msgid "Armenian" +msgstr "Armėnų" + msgid "Interlingua" msgstr "Interlingua" @@ -167,6 +171,9 @@ msgstr "Japonų" msgid "Georgian" msgstr "Gruzinų" +msgid "Kabyle" +msgstr "Kabilų" + msgid "Kazakh" msgstr "Kazachų" @@ -302,6 +309,15 @@ msgstr "Statiniai failai" msgid "Syndication" msgstr "Sindikacija" +msgid "That page number is not an integer" +msgstr "To puslapio numeris nėra sveikasis skaičius." + +msgid "That page number is less than 1" +msgstr "To numerio puslapis yra mažesnis už 1" + +msgid "That page contains no results" +msgstr "Tas puslapis neturi jokių rezultatų" + msgid "Enter a valid value." msgstr "Įveskite tinkamą reikšmę." @@ -314,6 +330,7 @@ msgstr "Įveskite tinkamą sveikąjį skaičių." msgid "Enter a valid email address." msgstr "Įveskite teisingą el. pašto adresą." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -369,6 +386,9 @@ msgstr[1] "" msgstr[2] "" "Įsitikinkite, kad reikšmė sudaryta iš nemažiau kaip %(limit_value)d ženklų " "(dabartinis ilgis %(show_value)d)." +msgstr[3] "" +"Įsitikinkite, kad reikšmė sudaryta iš nemažiau kaip %(limit_value)d ženklų " +"(dabartinis ilgis %(show_value)d)." #, python-format msgid "" @@ -386,6 +406,12 @@ msgstr[1] "" msgstr[2] "" "Įsitikinkite, kad reikšmė sudaryta iš nedaugiau kaip %(limit_value)d ženklų " "(dabartinis ilgis %(show_value)d)." +msgstr[3] "" +"Įsitikinkite, kad reikšmė sudaryta iš nedaugiau kaip %(limit_value)d ženklų " +"(dabartinis ilgis %(show_value)d)." + +msgid "Enter a number." +msgstr "Įveskite skaičių." #, python-format msgid "Ensure that there are no more than %(max)s digit in total." @@ -393,6 +419,7 @@ msgid_plural "Ensure that there are no more than %(max)s digits in total." msgstr[0] "Įsitikinkite, kad yra nedaugiau nei %(max)s skaitmuo." msgstr[1] "Įsitikinkite, kad yra nedaugiau nei %(max)s skaitmenys." msgstr[2] "Įsitikinkite, kad yra nedaugiau nei %(max)s skaitmenų." +msgstr[3] "Įsitikinkite, kad yra nedaugiau nei %(max)s skaitmenų." #, python-format msgid "Ensure that there are no more than %(max)s decimal place." @@ -400,6 +427,7 @@ msgid_plural "Ensure that there are no more than %(max)s decimal places." msgstr[0] "Įsitikinkite, kad yra nedaugiau nei %(max)s skaitmuo po kablelio." msgstr[1] "Įsitikinkite, kad yra nedaugiau nei %(max)s skaitmenys po kablelio." msgstr[2] "Įsitikinkite, kad yra nedaugiau nei %(max)s skaitmenų po kablelio." +msgstr[3] "Įsitikinkite, kad yra nedaugiau nei %(max)s skaitmenų po kablelio." #, python-format msgid "" @@ -411,6 +439,19 @@ msgstr[1] "" "Įsitikinkite, kad yra nedaugiau nei %(max)s skaitmenys prieš kablelį." msgstr[2] "" "Įsitikinkite, kad yra nedaugiau nei %(max)s skaitmenų prieš kablelį." +msgstr[3] "" +"Įsitikinkite, kad yra nedaugiau nei %(max)s skaitmenų prieš kablelį." + +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Bylos tipas '%(extension)s' negalimas. Galimi tipai yra: " +"'%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Nuliniai simboliai neleidžiami." msgid "and" msgstr "ir" @@ -459,6 +500,10 @@ msgstr "Didelis (8 baitų) sveikas skaičius" msgid "'%(value)s' value must be either True or False." msgstr "'%(value)s' reikšmė turi būti arba True, arba False." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "'%(value)s' reikšmė turi būti True, False, arba None." + msgid "Boolean (Either True or False)" msgstr "Loginė reikšmė (Tiesa arba Netiesa)" @@ -596,6 +641,9 @@ msgstr "Neapdorota informacija" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' yra netinkama UUID reikšmė." +msgid "Universally unique identifier" +msgstr "Universaliai unikalus identifikatorius" + msgid "File" msgstr "Failas" @@ -635,9 +683,6 @@ msgstr "Šis laukas yra privalomas." msgid "Enter a whole number." msgstr "Įveskite pilną skaičių." -msgid "Enter a number." -msgstr "Įveskite skaičių." - msgid "Enter a valid date." msgstr "Įveskite tinkamą datą." @@ -650,6 +695,10 @@ msgstr "Įveskite tinkamą datą/laiką." msgid "Enter a valid duration." msgstr "Įveskite tinkamą trukmę." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Dienų skaičius turi būti tarp {min_days} ir {max_days}." + msgid "No file was submitted. Check the encoding type on the form." msgstr "Nebuvo nurodytas failas. Patikrinkite formos koduotę." @@ -672,6 +721,9 @@ msgstr[1] "" msgstr[2] "" "Įsitikinkite, kad failo pavadinimas sudarytas iš nedaugiau kaip %(max)d " "ženklų (dabartinis ilgis %(length)d)." +msgstr[3] "" +"Įsitikinkite, kad failo pavadinimas sudarytas iš nedaugiau kaip %(max)d " +"ženklų (dabartinis ilgis %(length)d)." msgid "Please either submit a file or check the clear checkbox, not both." msgstr "Nurodykite failą arba pažymėkite išvalyti. Abu pasirinkimai negalimi." @@ -713,6 +765,7 @@ msgid_plural "Please submit %d or fewer forms." msgstr[0] "Prašome pateikti %d arba mažiau formų." msgstr[1] "Prašome pateikti %d arba mažiau formų." msgstr[2] "Prašome pateikti %d arba mažiau formų." +msgstr[3] "Prašome pateikti %d arba mažiau formų." #, python-format msgid "Please submit %d or more forms." @@ -720,6 +773,7 @@ msgid_plural "Please submit %d or more forms." msgstr[0] "Prašome pateikti %d arba daugiau formų." msgstr[1] "Prašome pateikti %d arba daugiau formų." msgstr[2] "Prašome pateikti %d arba daugiau formų." +msgstr[3] "Prašome pateikti %d arba daugiau formų." msgid "Order" msgstr "Nurodyti" @@ -748,15 +802,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Pataisykite žemiau esančias pasikartojančias reikšmes." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "Išorinis raktas neatitinka tėvinio objekto pirminio rakto." +msgid "The inline value did not match the parent instance." +msgstr "Reikšmė nesutapo su pirminiu objektu." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Pasirinkite tinkamą reikšmę. Parinkta reikšmė nėra galima." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" nėra pirminiam raktui tinkama reikšmė." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" nėra tinkama reikšmė." #, python-format msgid "" @@ -766,15 +820,15 @@ msgstr "" "Nepavyko interpretuoti %(datetime)s %(current_timezone)s laiko juostoje; " "Data gali turėti keletą reikšmių arba neegzistuoti." +msgid "Clear" +msgstr "Išvalyti" + msgid "Currently" msgstr "Šiuo metu" msgid "Change" msgstr "Pakeisti" -msgid "Clear" -msgstr "Išvalyti" - msgid "Unknown" msgstr "Nežinomas" @@ -793,6 +847,7 @@ msgid_plural "%(size)d bytes" msgstr[0] "%(size)d baitas" msgstr[1] "%(size)d baitai" msgstr[2] "%(size)d baitai" +msgstr[3] "%(size)d baitai" #, python-format msgid "%s KB" @@ -1047,7 +1102,7 @@ msgstr "Tai nėra teisingas IPv6 adresas." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." +msgid "%(truncated_text)s…" msgstr "%(truncated_text)s..." msgid "or" @@ -1063,6 +1118,7 @@ msgid_plural "%d years" msgstr[0] "%d metas" msgstr[1] "%d metai" msgstr[2] "%d metų" +msgstr[3] "%d metų" #, python-format msgid "%d month" @@ -1070,6 +1126,7 @@ msgid_plural "%d months" msgstr[0] "%d mėnuo" msgstr[1] "%d mėnesiai" msgstr[2] "%d mėnesių" +msgstr[3] "%d mėnesių" #, python-format msgid "%d week" @@ -1077,6 +1134,7 @@ msgid_plural "%d weeks" msgstr[0] "%d savaitė" msgstr[1] "%d savaitės" msgstr[2] "%d savaičių" +msgstr[3] "%d savaičių" #, python-format msgid "%d day" @@ -1084,6 +1142,7 @@ msgid_plural "%d days" msgstr[0] "%d diena" msgstr[1] "%d dienos" msgstr[2] "%d dienų" +msgstr[3] "%d dienų" #, python-format msgid "%d hour" @@ -1091,6 +1150,7 @@ msgid_plural "%d hours" msgstr[0] "%d valanda" msgstr[1] "%d valandos" msgstr[2] "%d valandų" +msgstr[3] "%d valandų" #, python-format msgid "%d minute" @@ -1098,6 +1158,7 @@ msgid_plural "%d minutes" msgstr[0] "%d minutė" msgstr[1] "%d minutės" msgstr[2] "%d minučių" +msgstr[3] "%d minučių" msgid "0 minutes" msgstr "0 minučių" @@ -1128,6 +1189,19 @@ msgstr "" "jau šitame tinklalapyje, arba HTTPS prisijungimams, arba 'same-origin' " "užklausoms." +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." +msgstr "" +"Jei naudojate žymeną " +"pridedate 'Referrer-Policy: no-referrer' antraštę, prašome juo panaikinti. " +"CSRF apsauga reikalauja 'Referer' antraštės vykdyti griežtą patikrinimą. Jei " +"esate susirūpinę privatumu, naudokite tokias alternatyvas nuorodoms į " +"išorinius tinklalapius kaip ." + 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 " @@ -1147,32 +1221,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Gauti daugiau informacijos galima su DEBUG=True nustatymu." -msgid "Welcome to Django" -msgstr "Sveiki, tai Django" - -msgid "It worked!" -msgstr "Suveikė!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Sveikiname Jus su Jūsų pirmuoju Django tinklalapiu." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Kolkas Jūs neatlikote jokių darbų. Paleiskite savo pirmąją aplikaciją " -"suvesdami python manage.py startapp [app_label]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Jūs matote šią žinutę dėl to kad Django nustatymų faile įvesta DEBUG = " -"True ir Jūs nenustatėte jokių URL'ų." - msgid "No year specified" msgstr "Nenurodyti metai" +msgid "Date out of range" +msgstr "Data išeina iš ribų" + msgid "No month specified" msgstr "Nenurodytas mėnuo" @@ -1224,3 +1278,47 @@ msgstr "\"%(path)s\" neegzistuoja" #, python-format msgid "Index of %(directory)s" msgstr "%(directory)s indeksas" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: Žiniatinklio karkasas perfekcionistams su terminais." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Žiūrėti Django %(version)s išleidimo " +"pastabas" + +msgid "The install worked successfully! Congratulations!" +msgstr "Diegimas pavyko! Sveikiname!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Jūs matote šią žinutę dėl to kad Django nustatymų faile įvesta DEBUG = True ir Jūs nenustatėte jokių URL'ų." + +msgid "Django Documentation" +msgstr "Django dokumentacija" + +msgid "Topics, references, & how-to's" +msgstr "Temos, nuorodos ir & kaip tai padaryti" + +msgid "Tutorial: A Polling App" +msgstr "Pamoka: Apklausos aplikacija" + +msgid "Get started with Django" +msgstr "Pradėti su Django" + +msgid "Django Community" +msgstr "Django Bendrija" + +msgid "Connect, get help, or contribute" +msgstr "Prisijunk, gauk pagalbą arba prisidėk" diff --git a/django/conf/locale/lt/formats.py b/django/conf/locale/lt/formats.py index d688669b7890..f28477fd7d34 100644 --- a/django/conf/locale/lt/formats.py +++ b/django/conf/locale/lt/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = r'Y \m. E j \d.' TIME_FORMAT = 'H:i' DATETIME_FORMAT = r'Y \m. E j \d., H:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%Y-%m-%d', '%d.%m.%Y', '%d.%m.%y', # '2006-10-25', '25.10.2006', '25.10.06' ] diff --git a/django/conf/locale/lv/LC_MESSAGES/django.mo b/django/conf/locale/lv/LC_MESSAGES/django.mo index 6c36efea224f..3c750c15e40c 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 03f017ccdef3..6e4209748e1a 100644 --- a/django/conf/locale/lv/LC_MESSAGES/django.po +++ b/django/conf/locale/lv/LC_MESSAGES/django.po @@ -2,15 +2,21 @@ # # Translators: # edgars , 2011 +# NullIsNot0 , 2017 +# NullIsNot0 , 2017-2018 # Jannis Leidel , 2011 # krikulis , 2014 +# Māris Nartišs , 2016 +# Mārtiņš Šulcs , 2018 +# NullIsNot0 , 2018-2019 +# peterisb , 2016-2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-02-18 17:00+0000\n" +"Last-Translator: NullIsNot0 \n" "Language-Team: Latvian (http://www.transifex.com/django/django/language/" "lv/)\n" "MIME-Version: 1.0\n" @@ -21,13 +27,13 @@ msgstr "" "2);\n" msgid "Afrikaans" -msgstr "āfrikāņu" +msgstr "afrikāņu" msgid "Arabic" msgstr "arābu" msgid "Asturian" -msgstr "" +msgstr "asturiešu" msgid "Azerbaijani" msgstr "azerbaidžāņu" @@ -63,7 +69,7 @@ msgid "German" msgstr "vācu" msgid "Lower Sorbian" -msgstr "" +msgstr "apakšsorbu" msgid "Greek" msgstr "grieķu" @@ -72,7 +78,7 @@ msgid "English" msgstr "angļu" msgid "Australian English" -msgstr "" +msgstr "Austrālijas angļu" msgid "British English" msgstr "Lielbritānijas angļu" @@ -84,19 +90,19 @@ msgid "Spanish" msgstr "spāņu" msgid "Argentinian Spanish" -msgstr "" +msgstr "Argentīnas spāņu" msgid "Colombian Spanish" -msgstr "" +msgstr "Kolumbijas spāņu" msgid "Mexican Spanish" -msgstr "" +msgstr "Meksikas spāņu" msgid "Nicaraguan Spanish" -msgstr "" +msgstr "Nikaragvas spāņu" msgid "Venezuelan Spanish" -msgstr "" +msgstr "Venecuēlas spāņu" msgid "Estonian" msgstr "igauņu" @@ -120,7 +126,7 @@ msgid "Irish" msgstr "īru" msgid "Scottish Gaelic" -msgstr "" +msgstr "skotu gēlu" msgid "Galician" msgstr "galīciešu" @@ -129,17 +135,20 @@ msgid "Hebrew" msgstr "ebreju" msgid "Hindi" -msgstr "Hindi" +msgstr "hindu" msgid "Croatian" msgstr "horvātu" msgid "Upper Sorbian" -msgstr "" +msgstr "augšsorbu" msgid "Hungarian" msgstr "ungāru" +msgid "Armenian" +msgstr "Armēņu" + msgid "Interlingua" msgstr "modernā latīņu valoda" @@ -147,7 +156,7 @@ msgid "Indonesian" msgstr "indonēziešu" msgid "Ido" -msgstr "" +msgstr "ido" msgid "Icelandic" msgstr "islandiešu" @@ -161,6 +170,9 @@ msgstr "Japāņu" msgid "Georgian" msgstr "vācu" +msgid "Kabyle" +msgstr "kabiliešu" + msgid "Kazakh" msgstr "kazahu" @@ -174,7 +186,7 @@ msgid "Korean" msgstr "korejiešu" msgid "Luxembourgish" -msgstr "" +msgstr "luksemburgiešu" msgid "Lithuanian" msgstr "lietuviešu" @@ -186,34 +198,34 @@ msgid "Macedonian" msgstr "maķedoniešu" msgid "Malayalam" -msgstr "" +msgstr "malajalu" msgid "Mongolian" msgstr "mongoļu" msgid "Marathi" -msgstr "" +msgstr "maratiešu" msgid "Burmese" -msgstr "" +msgstr "birmiešu" msgid "Norwegian Bokmål" -msgstr "" +msgstr "norvēģu bokmål" msgid "Nepali" -msgstr "" +msgstr "nepāliešu" msgid "Dutch" msgstr "holandiešu" msgid "Norwegian Nynorsk" -msgstr "" +msgstr "norvēģu nynorsk" msgid "Ossetic" -msgstr "" +msgstr "osetiešu" msgid "Punjabi" -msgstr "" +msgstr "pandžabu" msgid "Polish" msgstr "poļu" @@ -267,13 +279,13 @@ msgid "Tatar" msgstr "tatāru" msgid "Udmurt" -msgstr "" +msgstr "udmurtu" msgid "Ukrainian" msgstr "ukraiņu" msgid "Urdu" -msgstr "" +msgstr "urdu" msgid "Vietnamese" msgstr "vjetnamiešu" @@ -285,7 +297,7 @@ msgid "Traditional Chinese" msgstr "tradicionālā ķīniešu" msgid "Messages" -msgstr "" +msgstr "Ziņojumi" msgid "Site Maps" msgstr "Lapas kartes" @@ -296,6 +308,15 @@ msgstr "Statiski faili" msgid "Syndication" msgstr "Sindikācija" +msgid "That page number is not an integer" +msgstr "Lapas numurs nav cipars" + +msgid "That page number is less than 1" +msgstr "Lapas numurs ir mazāks par 1" + +msgid "That page contains no results" +msgstr "Lapa nesatur rezultātu" + msgid "Enter a valid value." msgstr "Ievadiet korektu vērtību." @@ -308,6 +329,7 @@ msgstr "Ievadiet veselu skaitli." msgid "Enter a valid email address." msgstr "Ievadiet korektu e-pasta adresi" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -318,6 +340,8 @@ msgid "" "Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or " "hyphens." msgstr "" +"Ievadiet korektu 'vienkāršā teksta' vērtību, kas satur tikai burtus, " +"numurus, apakšsvītras vai šķērssvītras." msgid "Enter a valid IPv4 address." msgstr "Ievadiet korektu IPv4 adresi." @@ -371,19 +395,25 @@ msgstr[1] "" msgstr[2] "" "Vērtībai jābūt ne vairāk kā %(limit_value)d zīmēm (tai ir %(show_value)d)." +msgid "Enter a number." +msgstr "Ievadiet skaitli." + #, 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[0] "Pārliecinieties, ka kopā nav vairāk par %(max)s ciparu." +msgstr[1] "Pārliecinieties, ka kopā nav vairāk par %(max)s cipariem." +msgstr[2] "Pārliecinieties, ka kopā nav vairāk par %(max)s cipariem." #, 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] "" +"Pārliecinieties, ka aiz decimālās zīmes nav vairāk par %(max)s ciparu." msgstr[1] "" +"Pārliecinieties, ka aiz decimālās zīmes nav vairāk par %(max)s cipariem." msgstr[2] "" +"Pārliecinieties, ka aiz decimālās zīmes nav vairāk par %(max)s cipariem." #, python-format msgid "" @@ -391,36 +421,50 @@ msgid "" msgid_plural "" "Ensure that there are no more than %(max)s digits before the decimal point." msgstr[0] "" +"Pārliecinieties, ka pirms decimālās zīmes nav vairāk par %(max)s ciparu." msgstr[1] "" +"Pārliecinieties, ka pirms decimālās zīmes nav vairāk par %(max)s cipariem." msgstr[2] "" +"Pārliecinieties, ka pirms decimālās zīmes nav vairāk par %(max)s cipariem." + +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Faila paplašinājums '%(extension)s' nav atļauts. Atļautie paplašinājumi ir: " +"'%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Nulles rakstzīmes nav atļautas." msgid "and" msgstr "un" #, python-format msgid "%(model_name)s with this %(field_labels)s already exists." -msgstr "" +msgstr "%(model_name)s ar šādu lauka %(field_labels)s vērtību jau eksistē." #, python-format msgid "Value %(value)r is not a valid choice." -msgstr "" +msgstr "Vērtība %(value)r ir nederīga izvēle." msgid "This field cannot be null." -msgstr "Šis lauks nevar neksistēt (būt null)." +msgstr "Šis lauks nevar būt tukšs, null." msgid "This field cannot be blank." msgstr "Šis lauks nevar būt tukšs" #, python-format msgid "%(model_name)s with this %(field_label)s already exists." -msgstr "%(model_name)s ar nosaukumu %(field_label)s jau eksistē." +msgstr "%(model_name)s ar šādu lauka %(field_label)s vērtību jau eksistē." #. 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 "" +msgstr "%(field_label)s jābūt unikālam %(date_field_label)s %(lookup_type)s." #, python-format msgid "Field of type: %(field_type)s" @@ -431,14 +475,18 @@ msgstr "Vesels skaitlis" #, python-format msgid "'%(value)s' value must be an integer." -msgstr "" +msgstr "'%(value)s' vērtībai ir jābūt veselam skaitlim." msgid "Big (8 byte) integer" msgstr "Liels (8 baitu) vesels skaitlis" #, python-format msgid "'%(value)s' value must be either True or False." -msgstr "" +msgstr "'%(value)s' vērtībai ir jābūt vai nu True vai False." + +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "'%(value)s' vērtībai jābūt True, False, vai None." msgid "Boolean (Either True or False)" msgstr "Boolean (True vai False)" @@ -455,12 +503,15 @@ msgid "" "'%(value)s' value has an invalid date format. It must be in YYYY-MM-DD " "format." msgstr "" +"'%(value)s' vērtība ir nepareizā datuma formātā. Pareizs formāts ir GGGG-MM-" +"DD." #, python-format msgid "" "'%(value)s' value has the correct format (YYYY-MM-DD) but it is an invalid " "date." msgstr "" +"'%(value)s' vērtība ir pareizā formātā (GGGG-MM-DD), bet ir nederīgs datums." msgid "Date (without time)" msgstr "Datums (bez laika)" @@ -470,19 +521,23 @@ msgid "" "'%(value)s' value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." "uuuuuu]][TZ] format." msgstr "" +"'%(value)s' vērtība satur nekorektu formātu. Tai jābūt YYYY-MM-DD HH:MM[:ss[." +"uuuuuu]][TZ] formātā." #, 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' vērtība ir pareizā formātā (YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]), " +"bet tā satur nederīgu datumu/laiku." msgid "Date (with time)" msgstr "Datums (ar laiku)" #, python-format msgid "'%(value)s' value must be a decimal number." -msgstr "" +msgstr "'%(value)s' vērtībai jābūt decimālam skaitlim." msgid "Decimal number" msgstr "Decimāls skaitlis" @@ -492,9 +547,11 @@ msgid "" "'%(value)s' value has an invalid format. It must be in [DD] [HH:[MM:]]ss[." "uuuuuu] format." msgstr "" +"'%(value)s' vērtība satur nekorektu formātu. Tai jābūt [DD] [HH:[MM:]]ss[." +"uuuuuu] formātā." msgid "Duration" -msgstr "" +msgstr "Ilgums" msgid "Email address" msgstr "E-pasta adrese" @@ -504,7 +561,7 @@ msgstr "Faila ceļš" #, python-format msgid "'%(value)s' value must be a float." -msgstr "" +msgstr "'%(value)s' vērtībai ir jābūt daļskaitlim." msgid "Floating point number" msgstr "Plūstošā punkta skaitlis" @@ -517,7 +574,7 @@ msgstr "IP adrese" #, python-format msgid "'%(value)s' value must be either None, True or False." -msgstr "" +msgstr "'%(value)s' vērtībai ir jābūt vai nu None vai True, vai False." msgid "Boolean (Either True, False or None)" msgstr "Boolean (jā, nē vai neviens)" @@ -543,12 +600,16 @@ msgid "" "'%(value)s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " "format." msgstr "" +"'%(value)s' vērtība satur nekorektu formātu. Tai jābūt HH:MM[:ss[.uuuuuu]] " +"formātā." #, python-format msgid "" "'%(value)s' value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " "invalid time." msgstr "" +"'%(value)s' vērtība ir pareizā formātā (HH:MM[:ss[.uuuuuu]]), bet tā satur " +"nederīgu laiku." msgid "Time" msgstr "Laiks" @@ -561,7 +622,10 @@ msgstr "Bināri dati" #, python-format msgid "'%(value)s' is not a valid UUID." -msgstr "" +msgstr "'%(value)s' ir nederīgs UUID." + +msgid "Universally unique identifier" +msgstr "Universāli unikāls identifikators" msgid "File" msgstr "Fails" @@ -571,7 +635,7 @@ msgstr "Attēls" #, python-format msgid "%(model)s instance with %(field)s %(value)r does not exist." -msgstr "" +msgstr "%(model)s instance ar %(field)s %(value)r neeksistē." msgid "Foreign Key (type determined by related field)" msgstr "Ārējā atslēga (tipu nosaka lauks uz kuru attiecas)" @@ -581,11 +645,11 @@ msgstr "Attiecība viens pret vienu" #, python-format msgid "%(from)s-%(to)s relationship" -msgstr "" +msgstr "%(from)s-%(to)s attiecība" #, python-format msgid "%(from)s-%(to)s relationships" -msgstr "" +msgstr "%(from)s-%(to)s attiecības" msgid "Many-to-many relationship" msgstr "Attiecība daudzi pret daudziem" @@ -594,7 +658,7 @@ msgstr "Attiecība daudzi pret daudziem" #. characters will prevent the default label_suffix to be appended to the #. label msgid ":?.!" -msgstr "" +msgstr ":?.!" msgid "This field is required." msgstr "Šis lauks ir obligāts." @@ -602,9 +666,6 @@ msgstr "Šis lauks ir obligāts." msgid "Enter a whole number." msgstr "Ievadiet veselu skaitli." -msgid "Enter a number." -msgstr "Ievadiet skaitli." - msgid "Enter a valid date." msgstr "Ievadiet korektu datumu." @@ -615,7 +676,11 @@ msgid "Enter a valid date/time." msgstr "Ievadiet korektu datumu/laiku." msgid "Enter a valid duration." -msgstr "" +msgstr "Ievadiet korektu ilgumu." + +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Dienu skaitam jābūt no {min_days} līdz {max_days}." msgid "No file was submitted. Check the encoding type on the form." msgstr "Nav nosūtīts fails. Pārbaudiet formas kodējuma tipu." @@ -631,11 +696,15 @@ 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] "" +"Faila nosaukuma garumam jābūt ne vairāk kā %(max)d zīmēm (tas ir %(length)d)." msgstr[1] "" +"Faila nosaukuma garumam jābūt ne vairāk kā %(max)d zīmei (tas ir %(length)d)." msgstr[2] "" +"Faila nosaukuma garumam jābūt ne vairāk kā %(max)d zīmēm (tas ir %(length)d)." msgid "Please either submit a file or check the clear checkbox, not both." msgstr "" +"Vai nu iesniedziet failu, vai atzīmējiet tukšo izvēles rūtiņu, bet ne abus." msgid "" "Upload a valid image. The file you uploaded was either not an image or a " @@ -655,15 +724,15 @@ msgid "Enter a complete value." msgstr "Ievadiet pilnu vērtību." msgid "Enter a valid UUID." -msgstr "" +msgstr "Ievadi derīgu UUID." #. Translators: This is the default suffix added to form field labels msgid ":" -msgstr "" +msgstr ":" #, python-format msgid "(Hidden field %(name)s) %(error)s" -msgstr "" +msgstr "(Slēpts lauks %(name)s) %(error)s" msgid "ManagementForm data is missing or has been tampered with" msgstr "Trūkst ManagementForm dati vai arī tie ir bojāti" @@ -708,31 +777,33 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Lūdzu izlabojiet dublicētās vērtības zemāk." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "Iekļautā ārējā atslēga nesakrita ar vecāka elementa primāro atslēgu" +msgid "The inline value did not match the parent instance." +msgstr "Iekļautā vērtība nesakrita ar vecāka instanci." msgid "Select a valid choice. That choice is not one of the available choices." -msgstr "Izvēlaties pareizu izvēli. Jūsu izvēlele neietilpst pieejamo sarakstā." +msgstr "Izvēlieties pareizu izvēli. Jūsu izvēle neietilpst pieejamo sarakstā." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" nav derīga vērtība primārajai atslēgai." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" nav derīga vērtība." #, 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 nevar tikt attēlots %(current_timezone)s laika zonā; tas var " +"būt neskaidrs vai var neeksistēt." + +msgid "Clear" +msgstr "Notīrīt" msgid "Currently" -msgstr "" +msgstr "Pašlaik" msgid "Change" msgstr "Izmainīt" -msgid "Clear" -msgstr "" - msgid "Unknown" msgstr "Nezināms" @@ -754,23 +825,23 @@ msgstr[2] "%(size)d baitu" #, python-format msgid "%s KB" -msgstr "" +msgstr "%s KB" #, python-format msgid "%s MB" -msgstr "" +msgstr "%s MB" #, python-format msgid "%s GB" -msgstr "" +msgstr "%s GB" #, python-format msgid "%s TB" -msgstr "" +msgstr "%s TB" #, python-format msgid "%s PB" -msgstr "" +msgstr "%s PB" msgid "p.m." msgstr "p.m." @@ -800,7 +871,7 @@ msgid "Wednesday" msgstr "trešdiena" msgid "Thursday" -msgstr "ceturdiena" +msgstr "ceturtdiena" msgid "Friday" msgstr "piektdiena" @@ -906,11 +977,11 @@ msgstr "dec" msgctxt "abbrev. month" msgid "Jan." -msgstr "" +msgstr "Jan." msgctxt "abbrev. month" msgid "Feb." -msgstr "" +msgstr "Feb." msgctxt "abbrev. month" msgid "March" @@ -934,23 +1005,23 @@ msgstr "jūlijs" msgctxt "abbrev. month" msgid "Aug." -msgstr "" +msgstr "Aug." msgctxt "abbrev. month" msgid "Sept." -msgstr "" +msgstr "Sept." msgctxt "abbrev. month" msgid "Oct." -msgstr "" +msgstr "Okt." msgctxt "abbrev. month" msgid "Nov." -msgstr "" +msgstr "Nov." msgctxt "abbrev. month" msgid "Dec." -msgstr "" +msgstr "Dec." msgctxt "alt. month" msgid "January" @@ -1005,60 +1076,60 @@ msgstr "Šī nav derīga IPv6 adrese." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "" +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s..." msgid "or" msgstr "vai" #. Translators: This string is used as a separator between list elements msgid ", " -msgstr "" +msgstr ", " #, python-format msgid "%d year" msgid_plural "%d years" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "%d gadi" +msgstr[1] "%d gads" +msgstr[2] "%d gadi" #, python-format msgid "%d month" msgid_plural "%d months" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "%d mēneši" +msgstr[1] "%d mēnesis" +msgstr[2] "%d mēneši" #, python-format msgid "%d week" msgid_plural "%d weeks" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "%d nedēļas" +msgstr[1] "%d nedēļa" +msgstr[2] "%d nedēļas" #, python-format msgid "%d day" msgid_plural "%d days" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "%d dienas" +msgstr[1] "%d diena" +msgstr[2] "%d dienas" #, python-format msgid "%d hour" msgid_plural "%d hours" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "%d stundas" +msgstr[1] "%d stunda" +msgstr[2] "%d stundas" #, python-format msgid "%d minute" msgid_plural "%d minutes" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "%d minūtes" +msgstr[1] "%d minūte" +msgstr[2] "%d minūtes" msgid "0 minutes" -msgstr "" +msgstr "0 minūšu" msgid "Forbidden" msgstr "Aizliegts" @@ -1072,49 +1143,61 @@ msgid "" "required for security reasons, to ensure that your browser is not being " "hijacked by third parties." msgstr "" +"Jūs redzat šo ziņojumu, jo šai HTTPS vietnei nepieciešams \"Referer header" +"\", kuru nosūtīs jūsu tīmekļa pārlūkprogramma, bet neviens netika nosūtīts. " +"Šis headeris ir vajadzīgs drošības apsvērumu dēļ, lai pārliecinātos, ka " +"trešās puses nepārņems kontroli pār jūsu pārlūkprogrammu." 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 "" +"Ja esat konfigurējis savu pārlūkprogrammu, lai atspējotu \"Referer\" " +"headerus, lūdzu, atkārtoti iespējojiet tos vismaz šai vietnei, HTTPS " +"savienojumiem vai \"vienas izcelsmes\" pieprasījumiem." + +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." +msgstr "" +"Ja jūs izmantojat tagu vai " +"iekļaujat \"Referrer-Policy: no-referrer\" headeri, lūdzu noņemiet tos. CSRF " +"aizsardzībai ir nepieciešams, lai \"Referrer\" headerī tiktu veikta stingra " +"pārvirzītāja pārbaude. Ja jūs domājiet par konfidencialitāti, izmantojiet " +"tādas alternatīvas kā , lai veidotu saites uz " +"trešo pušu vietnēm." 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 "" +"Jūs redzat šo ziņojumu, jo, iesniedzot veidlapas, šai vietnei ir " +"nepieciešams CSRF sīkfails. Šis sīkfails ir vajadzīgs drošības apsvērumu " +"dēļ, lai pārliecinātos, ka trešās personas nepārņems kontroli pār jūsu " +"pārlūkprogrammu." 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 "" +"Ja esat konfigurējis pārlūkprogrammu, lai atspējotu sīkfailus, lūdzu, " +"atkārtoti iespējojiet tos vismaz šai vietnei vai \"vienas izcelsmes\" " +"pieprasījumiem." msgid "More information is available with DEBUG=True." msgstr "Vairāk informācijas ir pieejams ar DEBUG=True" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "Nav norādīts gads" +msgid "Date out of range" +msgstr "Datums ir ārpus diapazona" + msgid "No month specified" msgstr "Nav norādīts mēnesis" @@ -1126,35 +1209,37 @@ msgstr "Nav norādīta nedēļa" #, python-format msgid "No %(verbose_name_plural)s available" -msgstr "" +msgstr "%(verbose_name_plural)s nav pieejami" #, python-format msgid "" "Future %(verbose_name_plural)s not available because %(class_name)s." "allow_future is False." msgstr "" +"Nākotne %(verbose_name_plural)s nav pieejama, jo %(class_name)s.allow_future " +"ir False." #, python-format msgid "Invalid date string '%(datestr)s' given format '%(format)s'" -msgstr "" +msgstr "Nepareiza datuma rinda '%(datestr)s' norādītajā formātā '%(format)s'" #, python-format msgid "No %(verbose_name)s found matching the query" -msgstr "" +msgstr "Neviens %(verbose_name)s netika atrasts" msgid "Page is not 'last', nor can it be converted to an int." -msgstr "" +msgstr "Lapa nav 'pēdējā', kā arī tā nevar tikt konvertēta par ciparu." #, python-format msgid "Invalid page (%(page_number)s): %(message)s" -msgstr "" +msgstr "Nepareiza lapa (%(page_number)s): %(message)s" #, python-format msgid "Empty list and '%(class_name)s.allow_empty' is False." -msgstr "" +msgstr "Tukšs saraksts un '%(class_name)s.allow_empty' ir False." msgid "Directory indexes are not allowed here." -msgstr "" +msgstr "Direktoriju indeksi nav atļauti." #, python-format msgid "\"%(path)s\" does not exist" @@ -1162,4 +1247,48 @@ msgstr "\"%(path)s\" neeksistē" #, python-format msgid "Index of %(directory)s" +msgstr "%(directory)s saturs" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: Web izstrādes ietvars perfekcionistiem ar izpildes termiņiem." + +#, python-format +msgid "" +"View release notes for Django %(version)s" msgstr "" +"Apskatīt laidiena piezīmes Django %(version)s" + +msgid "The install worked successfully! Congratulations!" +msgstr "Instalācija veiksmīga! Apsveicam!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Jūs redziet šo lapu, jo DEBUG=True ir iestatījumu failā un Jūs neesiet konfigurējis nevienu " +"saiti." + +msgid "Django Documentation" +msgstr "Django Dokumentācija" + +msgid "Topics, references, & how-to's" +msgstr "Tēmas, atsauces, & how-to" + +msgid "Tutorial: A Polling App" +msgstr "Apmācība: Balsošanas aplikācija" + +msgid "Get started with Django" +msgstr "Sāciet ar Django" + +msgid "Django Community" +msgstr "Django Komūna" + +msgid "Connect, get help, or contribute" +msgstr "Pievienojaties, saņemiet palīdzību vai dodiet ieguldījumu" diff --git a/django/conf/locale/lv/formats.py b/django/conf/locale/lv/formats.py index e30a65388c62..45e6f605d117 100644 --- a/django/conf/locale/lv/formats.py +++ b/django/conf/locale/lv/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = r'Y. \g\a\d\a j. F' TIME_FORMAT = 'H:i' DATETIME_FORMAT = r'Y. \g\a\d\a j. F, H:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # Kept ISO formats as they are in first position DATE_INPUT_FORMATS = [ '%Y-%m-%d', '%d.%m.%Y', '%d.%m.%y', # '2006-10-25', '25.10.2006', '25.10.06' diff --git a/django/conf/locale/mk/LC_MESSAGES/django.mo b/django/conf/locale/mk/LC_MESSAGES/django.mo index 4ffceeb03c4d..659db90057a1 100644 Binary files a/django/conf/locale/mk/LC_MESSAGES/django.mo and b/django/conf/locale/mk/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/mk/LC_MESSAGES/django.po b/django/conf/locale/mk/LC_MESSAGES/django.po index 371752271212..f1c2a8f58291 100644 --- a/django/conf/locale/mk/LC_MESSAGES/django.po +++ b/django/conf/locale/mk/LC_MESSAGES/django.po @@ -3,16 +3,16 @@ # Translators: # dekomote , 2015 # Jannis Leidel , 2011 -# Vasil Vangelovski , 2016 +# Vasil Vangelovski , 2016-2017 # Vasil Vangelovski , 2013-2015 # Vasil Vangelovski , 2011-2013 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-07-08 09:35+0000\n" -"Last-Translator: Vasil Vangelovski \n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" +"Last-Translator: Jannis Leidel \n" "Language-Team: Macedonian (http://www.transifex.com/django/django/language/" "mk/)\n" "MIME-Version: 1.0\n" @@ -297,6 +297,15 @@ msgstr "Статички датотеки" msgid "Syndication" msgstr "Синдикација" +msgid "That page number is not an integer" +msgstr "Тој број на страна не е цел број" + +msgid "That page number is less than 1" +msgstr "Тој број на страна е помал од 1" + +msgid "That page contains no results" +msgstr "Таа страна не содржи резултати" + msgid "Enter a valid value." msgstr "Внесете правилна вредност." @@ -309,6 +318,7 @@ msgstr "Внесете валиден цел број." msgid "Enter a valid email address." msgstr "Внесете валидна email адреса." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -400,6 +410,17 @@ msgstr[0] "" msgstr[1] "" "Осигурајте се дека нема повеќе од %(max)s цифри пред децималната запирка." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Еџтензијата '%(extension)s' не е дозволена. Дозволени екстензии се: " +"'%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "и" @@ -733,17 +754,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Ве молам поправете ги дуплираните вредности подолу." -msgid "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." msgstr "" -"Надворешниот клуч на вгезденото поле не се совпаѓа со примарниот клуч на " -"родителската инстанца." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Изберете правилно. Тоа не е еден од можните избори." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" не е правилна вредност за примарен клуч." +msgid "\"%(pk)s\" is not a valid value." +msgstr "" #, python-format msgid "" @@ -753,15 +772,15 @@ msgstr "" "%(datetime)s не може да се толкува во временска зона %(current_timezone)s; " "можеби е двосмислена или не постои." +msgid "Clear" +msgstr "Исчисти" + msgid "Currently" msgstr "Моментално" msgid "Change" msgstr "Измени" -msgid "Clear" -msgstr "Исчисти" - msgid "Unknown" msgstr "Непознато" @@ -1108,6 +1127,14 @@ msgstr "" "'Referer' хедерот, ве молиме овозможето праќањето барем за овој сајт или за " "HTTPS конекции или за барања од 'ист извор'." +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." +msgstr "" + 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 " @@ -1129,34 +1156,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Повеќе информации се достапни со DEBUG = True." -msgid "Welcome to Django" -msgstr "Добредојдовте во Django" - -msgid "It worked!" -msgstr "Работи!" - -msgid "Congratulations on your first Django-powered page." -msgstr "" -"Ви честитаме на поставување на вашата прва страница подржана од Django." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Се разбира, сеуште немате направено ништо. Наредно, направете ја вашата прва " -"апликација со повикување на командата python manage.py startapp " -"[app_label]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Ја гледате оваа порака биејќи имате DEBUG = True во датотеката " -"со Django подесувања и сеуште немате дефинирано URL-а. Фатете се за работа!" - msgid "No year specified" msgstr "Не е дадена година" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "Не е даден месец" @@ -1209,3 +1214,41 @@ msgstr "\"%(path)s\" не постои" #, python-format msgid "Index of %(directory)s" msgstr "Индекс на %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/mk/formats.py b/django/conf/locale/mk/formats.py index 2f01147a0a1a..6c55bcc9afbe 100644 --- a/django/conf/locale/mk/formats.py +++ b/django/conf/locale/mk/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'd F Y' TIME_FORMAT = 'H:i' DATETIME_FORMAT = 'j. F Y H:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d.%m.%Y', '%d.%m.%y', # '25.10.2006', '25.10.06' '%d. %m. %Y', '%d. %m. %y', # '25. 10. 2006', '25. 10. 06' diff --git a/django/conf/locale/ml/LC_MESSAGES/django.mo b/django/conf/locale/ml/LC_MESSAGES/django.mo index ce3f3db30131..b81790b7e664 100644 Binary files a/django/conf/locale/ml/LC_MESSAGES/django.mo and b/django/conf/locale/ml/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/ml/LC_MESSAGES/django.po b/django/conf/locale/ml/LC_MESSAGES/django.po index 316d54c1dc9f..4689d22ad5c2 100644 --- a/django/conf/locale/ml/LC_MESSAGES/django.po +++ b/django/conf/locale/ml/LC_MESSAGES/django.po @@ -1,18 +1,21 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Anivar Aravind , 2013 +# c1007a0b890405f1fbddfacebc4c6ef7, 2013 +# Hrishikesh , 2019 # Jannis Leidel , 2011 +# Jaseem KM , 2019 # Jeffy , 2012 +# Jibin Mathew , 2019 # Rag sagar , 2016 # Rajeesh Nair , 2011-2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-03-10 08:55+0000\n" +"Last-Translator: Hrishikesh \n" "Language-Team: Malayalam (http://www.transifex.com/django/django/language/" "ml/)\n" "MIME-Version: 1.0\n" @@ -52,7 +55,7 @@ msgid "Catalan" msgstr "കാറ്റലന്‍" msgid "Czech" -msgstr "ചെക്" +msgstr "ചെൿ" msgid "Welsh" msgstr "വെല്‍ഷ്" @@ -64,19 +67,19 @@ msgid "German" msgstr "ജര്‍മന്‍" msgid "Lower Sorbian" -msgstr "" +msgstr "ലോവർ സോർബിയൻ " msgid "Greek" msgstr "ഗ്രീക്ക്" msgid "English" -msgstr "ഇംഗ്ളീഷ്" +msgstr "ഇംഗ്ലീഷ്" msgid "Australian English" msgstr "ആസ്ട്രേലിയൻ ഇംഗ്ലീഷ്" msgid "British English" -msgstr "ബ്രിട്ടീഷ് ഇംഗ്ളീഷ്" +msgstr "ബ്രിട്ടീഷ് ഇംഗ്ലീഷ്" msgid "Esperanto" msgstr "എസ്പെരാന്റോ" @@ -121,13 +124,13 @@ msgid "Irish" msgstr "ഐറിഷ്" msgid "Scottish Gaelic" -msgstr "സ്കോട്ടിഷ് ഗൈലിക്ക്" +msgstr "സ്കോട്ടിഷ് ഗൈലിൿ" msgid "Galician" msgstr "ഗലിഷ്യന്‍" msgid "Hebrew" -msgstr "ഹീബ്റു" +msgstr "ഹീബ്രു" msgid "Hindi" msgstr "ഹിന്ദി" @@ -136,11 +139,14 @@ msgid "Croatian" msgstr "ക്രൊയേഷ്യന്‍" msgid "Upper Sorbian" -msgstr "" +msgstr "അപ്പർ സോർബിയൻ " msgid "Hungarian" msgstr "ഹംഗേറിയന്‍" +msgid "Armenian" +msgstr "അർമേനിയൻ" + msgid "Interlingua" msgstr "ഇന്റര്‍ലിംഗ്വാ" @@ -151,7 +157,7 @@ msgid "Ido" msgstr "ഈടോ" msgid "Icelandic" -msgstr "ഐസ്ലാന്‍ഡിക്" +msgstr "ഐസ്ലാന്‍ഡിൿ" msgid "Italian" msgstr "ഇറ്റാലിയന്‍" @@ -162,8 +168,11 @@ msgstr "ജാപ്പനീസ്" msgid "Georgian" msgstr "ജോര്‍ജിയന്‍" +msgid "Kabyle" +msgstr "കാബയെൽ " + msgid "Kazakh" -msgstr "കസാക്" +msgstr "കസാഖ്" msgid "Khmer" msgstr "ഖ്മേര്‍" @@ -223,7 +232,7 @@ msgid "Portuguese" msgstr "പോര്‍ചുഗീസ്" msgid "Brazilian Portuguese" -msgstr "ബ്റസീലിയന്‍ പോര്‍ചുഗീസ്" +msgstr "ബ്രസീലിയന്‍ പോര്‍ച്ചുഗീസ്" msgid "Romanian" msgstr "റൊമാനിയന്‍" @@ -232,7 +241,7 @@ msgid "Russian" msgstr "റഷ്യന്‍" msgid "Slovak" -msgstr "സ്ളൊവാക്" +msgstr "സ്ലൊവാൿ" msgid "Slovenian" msgstr "സ്ളൊവേനിയന്‍" @@ -280,7 +289,7 @@ msgid "Vietnamese" msgstr "വിയറ്റ്നാമീസ്" msgid "Simplified Chinese" -msgstr "ലഘു ചൈനീസ്" +msgstr "സിമ്പ്ലിഫൈഡ് ചൈനീസ്" msgid "Traditional Chinese" msgstr "പരമ്പരാഗത ചൈനീസ്" @@ -289,48 +298,58 @@ msgid "Messages" msgstr "സന്ദേശങ്ങൾ" msgid "Site Maps" -msgstr "സൈറ്റ് മാപ്പ്" +msgstr "സൈറ്റ് മാപ്പുകൾ" msgid "Static Files" -msgstr " സ്റ്റാറ്റിക്ക് ഫയൽസ്" +msgstr " സ്റ്റാറ്റിൿ ഫയലുകൾ" msgid "Syndication" msgstr "വിതരണം " +msgid "That page number is not an integer" +msgstr "ആ പേജ് നമ്പർ ഒരു ഇന്റിജറല്ല" + +msgid "That page number is less than 1" +msgstr "ആ പേജ് നമ്പർ 1 നെ കാൾ ചെറുതാണ് " + +msgid "That page contains no results" +msgstr "ആ പേജിൽ റിസൾട്ടുകൾ ഒന്നും ഇല്ല " + msgid "Enter a valid value." -msgstr "സാധുതയുള്ള മൂല്യം നല്‍കുക." +msgstr "ശരിയായ വാല്യു നൽകുക." msgid "Enter a valid URL." -msgstr "സാധുതയുള്ള URL നല്‍കുക" +msgstr "ശരിയായ URL നല്‍കുക" msgid "Enter a valid integer." -msgstr "സാധുതയുള്ള അക്കം നല്കുക." +msgstr "ശരിയായ ഇന്റിജർ നൽകുക." msgid "Enter a valid email address." -msgstr "സാധുതയുള്ള ഇമെയില്‍ വിലാസം നല്‍കുക" +msgstr "ശരിയായ ഇമെയില്‍ വിലാസം നല്‍കുക." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" -"അക്ഷരങ്ങള്‍, അക്കങ്ങള്‍, അണ്ടര്‍സ്കോര്‍, ഹൈഫന്‍ എന്നിവ മാത്രം അടങ്ങിയ സാധുതയുള്ള ഒരുവാക്ക് " -"ചുരുക്കവാക്കായി നല്‍കുക " +"അക്ഷരങ്ങള്‍, അക്കങ്ങള്‍, അണ്ടര്‍സ്കോര്‍, ഹൈഫന്‍ എന്നിവ മാത്രം അടങ്ങിയ ശരിയായ ഒരു 'സ്ലഗ്' നൽകുക. " msgid "" "Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or " "hyphens." msgstr "" +"യൂണികോഡ് അക്ഷരങ്ങൾ, നമ്പറുകൾ, ഹൈഫൺ, അണ്ടർസ്കോർ എന്നിവ അടങ്ങിയ ശെരിയായ ‌ഒരു സ്ലഗ് എഴുതുക ." msgid "Enter a valid IPv4 address." -msgstr "ശരിയായ IPv4 വിലാസം നല്കണം" +msgstr "ശരിയായ IPv4 വിലാസം നൽകുക." msgid "Enter a valid IPv6 address." -msgstr "ശരിയായ ഒരു IPv6 വിലാസം നല്കുക." +msgstr "ശരിയായ ഒരു IPv6 വിലാസം നൽകുക." msgid "Enter a valid IPv4 or IPv6 address." -msgstr "ശരിയായ ഒരു IPv4 വിലാസമോ IPv6 വിലാസമോ നല്കുക." +msgstr "ശരിയായ ഒരു IPv4 വിലാസമോ IPv6 വിലാസമോ നൽകുക." msgid "Enter only digits separated by commas." -msgstr "അക്കങ്ങള്‍ മാത്രം (കോമയിട്ടു വേര്‍തിരിച്ചത്)" +msgstr "കോമകൾ ഉപയോഗിച്ച് വേർതിരിച്ച രീതിയിലുള്ള അക്കങ്ങൾ മാത്രം നൽകുക." #, python-format msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." @@ -352,7 +371,11 @@ msgid_plural "" "Ensure this value has at least %(limit_value)d characters (it has " "%(show_value)d)." msgstr[0] "" +"ഈ വാല്യൂയിൽ %(limit_value)d ക്യാരക്ടർ എങ്കിലും ഉണ്ടെന്നു ഉറപ്പു വരുത്തുക(ഇതിൽ " +"%(show_value)d ഉണ്ട് )" msgstr[1] "" +"ഈ വാല്യൂയിൽ %(limit_value)dക്യാരക്ടേർസ് എങ്കിലും ഉണ്ടെന്നു ഉറപ്പു വരുത്തുക(ഇതിൽ " +"%(show_value)d ഉണ്ട് )" #, python-format msgid "" @@ -362,38 +385,56 @@ msgid_plural "" "Ensure this value has at most %(limit_value)d characters (it has " "%(show_value)d)." msgstr[0] "" +"ഈ വാല്യൂയിൽ %(limit_value)d ക്യാരക്ടർ 1 ഇൽ കൂടുതൽ ഇല്ലെന്നു ഉറപ്പു വരുത്തുക(ഇതിൽ 2 " +"%(show_value)d ഉണ്ട് )" msgstr[1] "" +"ഈ വാല്യൂയിൽ %(limit_value)d ക്യാരക്ടർസ് 1 ഇൽ കൂടുതൽ ഇല്ലെന്നു ഉറപ്പു വരുത്തുക(ഇതിൽ 2 " +"%(show_value)d ഉണ്ട് )" + +msgid "Enter a number." +msgstr "ഒരു സംഖ്യ നല്കുക." #, 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[0] "%(max)s ഡിജിറ്റിൽ കൂടുതൽ ഇല്ല എന്ന് ഉറപ്പു വരുത്തുക ." +msgstr[1] "%(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] "" -msgstr[1] "" +msgstr[0] "%(max)sകൂടുതൽ ഡെസിമൽ പോയന്റില്ല എന്ന് ഉറപ്പു വരുത്തുക. " +msgstr[1] "%(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] "" -msgstr[1] "" +msgstr[0] "%(max)sഡിജിറ്റ് ഡെസിമൽ പോയിന്റിനു മുൻപ് ഇല്ല എന്ന് ഉറപ്പു വരുത്തുക." +msgstr[1] "%(max)sഡിജിറ്റ്സ് ഡെസിമൽ പോയിന്റിനു മുൻപ് ഇല്ല എന്ന് ഉറപ്പു വരുത്തുക. " + +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"'%(extension)s' എന്ന ഫയൽ എക്സ്റ്റൻഷൻ അനുവദനീയമല്ല. അനുവദനീയമായ എക്സറ്റന്ഷനുകൾ ഇവയാണ് : " +"'%(allowed_extensions)s'" + +msgid "Null characters are not allowed." +msgstr "Null ക്യാരക്ടറുകൾ അനുവദനീയമല്ല." msgid "and" -msgstr "ഉം" +msgstr "പിന്നെ" #, python-format msgid "%(model_name)s with this %(field_labels)s already exists." -msgstr "" +msgstr "%(field_labels)sഉള്ള %(model_name)sനിലവിലുണ്ട്." #, python-format msgid "Value %(value)r is not a valid choice." -msgstr "" +msgstr "%(value)r എന്ന വാല്യൂ ശെരിയായ ചോയ്സ് അല്ല. " msgid "This field cannot be null." msgstr "ഈ കളം (ഫീല്‍ഡ്) ഒഴിച്ചിടരുത്." @@ -431,6 +472,10 @@ msgstr "8 ബൈറ്റ് പൂര്‍ണസംഖ്യ." msgid "'%(value)s' value must be either True or False." msgstr "'%(value)s' മൂല്യം True അഥവാ False ആയിരിക്കണം." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "%(value)sഎന്ന വാല്യൂ True, False, അല്ലെങ്കിൽ None എന്നിവയിൽ ഒന്നായിരിക്കണം." + msgid "Boolean (Either True or False)" msgstr "ശരിയോ തെറ്റോ (True അഥവാ False)" @@ -462,12 +507,17 @@ 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] \n" +"ഫോർമാറ്റിലായിരിക്കണം." #, 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]) പക്ഷേ " +"തെറ്റായ date/time ആണ്. " msgid "Date (with time)" msgstr "തീയതി (സമയത്തോടൊപ്പം)" @@ -560,6 +610,9 @@ msgstr "റോ ബൈനറി ഡാറ്റ" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' ഒരു സാധുവായ യു യു ഐ ഡി അല്ലാ." +msgid "Universally unique identifier" +msgstr "എല്ലായിടത്തും യുണീക്കായ ഐഡന്റിഫൈയർ." + msgid "File" msgstr "ഫയല്‍" @@ -568,7 +621,7 @@ msgstr "ചിത്രം" #, python-format msgid "%(model)s instance with %(field)s %(value)r does not exist." -msgstr "" +msgstr "%(field)s%(value)r ഉള്ള%(model)s ഇൻസ്റ്റൻസ് നിലവിൽ ഇല്ല." msgid "Foreign Key (type determined by related field)" msgstr "ഫോറിന്‍ കീ (ടൈപ്പ് ബന്ധപ്പെട്ട ഫീല്‍ഡില്‍ നിന്നും നിര്‍ണ്ണയിക്കുന്നതാണ്)" @@ -578,11 +631,11 @@ msgstr "വണ്‍-ടു-വണ്‍ ബന്ധം" #, python-format msgid "%(from)s-%(to)s relationship" -msgstr "" +msgstr "%(from)s-%(to)s റിലേഷൻഷിപ്‌." #, python-format msgid "%(from)s-%(to)s relationships" -msgstr "" +msgstr "%(from)s-%(to)sറിലേഷൻഷിപ്‌സ്. " msgid "Many-to-many relationship" msgstr "മെനി-ടു-മെനി ബന്ധം" @@ -599,9 +652,6 @@ msgstr "ഈ കള്ളി(ഫീല്‍ഡ്) നിര്‍ബന്ധ msgid "Enter a whole number." msgstr "ഒരു പൂര്‍ണസംഖ്യ നല്കുക." -msgid "Enter a number." -msgstr "ഒരു സംഖ്യ നല്കുക." - msgid "Enter a valid date." msgstr "ശരിയായ തീയതി നല്കുക." @@ -614,6 +664,10 @@ msgstr "ശരിയായ തീയതിയും സമയവും നല് msgid "Enter a valid duration." msgstr "സാധുതയുള്ള കാലയളവ് നല്കുക." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "ദിവസങ്ങളുടെ എണ്ണം {min_days}, {max_days} എന്നിവയുടെ ഇടയിലായിരിക്കണം." + msgid "No file was submitted. Check the encoding type on the form." msgstr "ഫയലൊന്നും ലഭിച്ചിട്ടില്ല. ഫോമിലെ എന്‍-കോഡിംഗ് പരിശോധിക്കുക." @@ -628,7 +682,9 @@ 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] "" +"ഈ ഫയൽ നെയ്മിൽ%(max)dക്യാരക്ടറിൽ കൂടുതലില്ല എന്ന് ഉറപ്പു വരുത്തുക (അതിൽ %(length)dഉണ്ട്) . " msgstr[1] "" +"ഈ ഫയൽ നെയ്മിൽ%(max)dക്യാരക്ടേഴ്‌സിൽ കൂടുതലില്ല എന്ന് ഉറപ്പു വരുത്തുക (അതിൽ %(length)dഉണ്ട്)." msgid "Please either submit a file or check the clear checkbox, not both." msgstr "" @@ -661,22 +717,22 @@ msgstr ":" #, python-format msgid "(Hidden field %(name)s) %(error)s" -msgstr "" +msgstr "(ഹിഡൻ ഫീൽഡ് %(name)s)%(error)s" msgid "ManagementForm data is missing or has been tampered with" -msgstr "" +msgstr "ManagementForm ടാറ്റ കാണ്മാനില്ല അല്ലെങ്കിൽ തിരിമറി നടത്തപ്പെട്ടു ." #, python-format msgid "Please submit %d or fewer forms." msgid_plural "Please submit %d or fewer forms." -msgstr[0] "" -msgstr[1] "" +msgstr[0] "ദയവായി%d അല്ലെങ്കിൽ കുറവ് ഫോമുകൾ സമർപ്പിക്കുക." +msgstr[1] "ദയവായി%d അല്ലെങ്കിൽ കുറവ് ഫോമുകൾ സമർപ്പിക്കുക." #, python-format msgid "Please submit %d or more forms." msgid_plural "Please submit %d or more forms." -msgstr[0] "" -msgstr[1] "" +msgstr[0] "ദയവായി %d അല്ലെങ്കിൽ കൂടുതൽ ഫോമുകൾ സമർപ്പിക്കുക. " +msgstr[1] "ദയവായി%d അല്ലെങ്കിൽ കൂടുതൽ ഫോമുകൾ സമർപ്പിക്കുക. " msgid "Order" msgstr "ക്രമം" @@ -703,15 +759,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "താഴെ കൊടുത്തവയില്‍ ആവര്‍ത്തനം ഒഴിവാക്കുക." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "ഇന്‍ലൈനായി നല്കിയ ഫോറിന്‍ കീ മാത്രു വസ്തുവിന്റെ പ്രാഥമിക കീയുമായി യോജിക്കുന്നില്ല." +msgid "The inline value did not match the parent instance." +msgstr "ഇൻലൈൻ വാല്യൂ, പാരെന്റ് ഇൻസ്റ്റൻസുമായി ചേരുന്നില്ല." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "യോഗ്യമായത് തെരഞ്ഞെടുക്കുക. നിങ്ങള്‍ നല്കിയത് ലഭ്യമായവയില്‍ ഉള്‍പ്പെടുന്നില്ല." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "" +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" ശെരിയായ ഒരു വാല്യൂ അല്ല." #, python-format msgid "" @@ -721,15 +777,15 @@ msgstr "" "%(datetime)s %(current_timezone)s എന്ന സമയമേഖലയിലേക്ക് വ്യാഖ്യാനിക്കാന്‍ " "സാധിച്ചിട്ടില്ല; ഇത് ഒന്നുകില്‍ അവ്യക്തമാണ്, അല്ലെങ്കില്‍ നിലവിലില്ല." +msgid "Clear" +msgstr "കാലിയാക്കുക" + msgid "Currently" msgstr "നിലവിലുള്ളത്" msgid "Change" msgstr "മാറ്റുക" -msgid "Clear" -msgstr "കാലിയാക്കുക" - msgid "Unknown" msgstr "അജ്ഞാതം" @@ -787,25 +843,25 @@ msgid "noon" 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 "ശനിയാഴ്ച" msgid "Sunday" -msgstr "ഞായര്‍" +msgstr "ഞായറാഴ്ച" msgid "Mon" msgstr "തിങ്കള്‍" @@ -1001,7 +1057,7 @@ msgstr "ഇതു സാധുവായ IPv6 വിലാസമല്ല." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." +msgid "%(truncated_text)s…" msgstr "%(truncated_text)s..." msgid "or" @@ -1039,7 +1095,7 @@ msgstr[1] "%d ദിവസങ്ങൾ" msgid "%d hour" msgid_plural "%d hours" msgstr[0] "%d മണിക്കൂർ" -msgstr[1] "%d മണിക്കൂരുകൾ" +msgstr[1] "%d മണിക്കൂറുകൾ" #, python-format msgid "%d minute" @@ -1069,11 +1125,22 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 "" +"ഫോം സമർപ്പിക്കുമ്പോൾ ഒരു CSRF കുക്കി ഈ സൈറ്റിൽ ആവശ്യമാണ് എന്നതിനാലാണ് നിങ്ങൾ ഈ സന്ദേശം " +"കാണുന്നത്. മറ്റുള്ളവരാരെങ്കിലും നിങ്ങളുടെ ബ്രൗസറിനെ നിയന്ത്രിക്കുന്നില്ല എന്ന് ഉറപ്പുവരുത്താനായി ഈ " +"കുക്കി ആവശ്യമാണ്. " msgid "" "If you have configured your browser to disable cookies, please re-enable " @@ -1083,28 +1150,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Debug=True എന്നു കൊടുത്താൽ കൂടുതൽ കാര്യങ്ങൾ അറിയാൻ കഴിയും." -msgid "Welcome to Django" -msgstr "ജാങ്കോയിലേക്ക് സ്വാഗതം" - -msgid "It worked!" -msgstr "ഇതു പ്രവർത്തിക്കിന്നുണ്ട്" - -msgid "Congratulations on your first Django-powered page." -msgstr "താങ്കളുടെ ആദ്യത്തെ ജാങ്കോ നിർമ്മിത പേജിന് അഭിനന്ദനങ്ങൾ" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "വര്‍ഷം പരാമര്‍ശിച്ചിട്ടില്ല" +msgid "Date out of range" +msgstr "ഡാറ്റ പരിധിയുടെ പുറത്താണ്" + msgid "No month specified" msgstr "മാസം പരാമര്‍ശിച്ചിട്ടില്ല" @@ -1156,3 +1207,41 @@ msgstr "\"%(path)s\" നിലവിലില്ല" #, python-format msgid "Index of %(directory)s" msgstr "%(directory)s യുടെ സൂചിക" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "ജാംഗോ: സമയപരിമിതികളുള്ള പൂർണ്ണതാമോഹികൾക്കായുള്ള വെബ് ഫ്രെയിംവർക്ക്. " + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "ജാംഗോ ഡോക്യുമെന്റേഷൻ" + +msgid "Topics, references, & how-to's" +msgstr "വിഷയങ്ങൾ, അനുബന്ധ വിഷയങ്ങൾ & നിർദ്ദേശക്കുറിപ്പുകൾ" + +msgid "Tutorial: A Polling App" +msgstr "പരിശീലനം: ഒരു പോളിങ്ങ് ആപ്പ്" + +msgid "Get started with Django" +msgstr "ജാംഗോയുമായി പരിചയത്തിലാവുക" + +msgid "Django Community" +msgstr "ജാംഗോ കമ്യൂണിറ്റി" + +msgid "Connect, get help, or contribute" +msgstr "കൂട്ടുകൂടൂ, സഹായം തേടൂ, അല്ലെങ്കിൽ സഹകരിക്കൂ" diff --git a/django/conf/locale/ml/formats.py b/django/conf/locale/ml/formats.py index 63b23fa260d0..74abad58c519 100644 --- a/django/conf/locale/ml/formats.py +++ b/django/conf/locale/ml/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'N j, Y' TIME_FORMAT = 'P' DATETIME_FORMAT = 'N j, Y, P' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 0 # Sunday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # Kept ISO formats as they are in first position DATE_INPUT_FORMATS = [ '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06' diff --git a/django/conf/locale/mn/LC_MESSAGES/django.mo b/django/conf/locale/mn/LC_MESSAGES/django.mo index 1b40af4d51f7..f09d90040bd7 100644 Binary files a/django/conf/locale/mn/LC_MESSAGES/django.mo and b/django/conf/locale/mn/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/mn/LC_MESSAGES/django.po b/django/conf/locale/mn/LC_MESSAGES/django.po index 910407a940e5..756331e83d09 100644 --- a/django/conf/locale/mn/LC_MESSAGES/django.po +++ b/django/conf/locale/mn/LC_MESSAGES/django.po @@ -2,22 +2,23 @@ # # Translators: # Ankhbayar , 2013 -# Bayarkhuu Bataa, 2014 -# Jacara , 2011 +# Bayarkhuu Bataa, 2014,2017-2018 +# Baskhuu Lodoikhuu , 2011 # Jannis Leidel , 2011 # jargalan , 2011 # Tsolmon , 2011 -# Zorig , 2013-2014,2016 -# Анхбаяр Анхаа , 2013-2015 -# Баясгалан Цэвлээ , 2011,2015 +# Zorig, 2013-2014,2016,2018 +# Zorig, 2019 +# Анхбаяр Анхаа , 2013-2016,2018-2019 +# Баясгалан Цэвлээ , 2011,2015,2017 # Ганзориг БП , 2011 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-02-19 08:42+0000\n" +"Last-Translator: Анхбаяр Анхаа \n" "Language-Team: Mongolian (http://www.transifex.com/django/django/language/" "mn/)\n" "MIME-Version: 1.0\n" @@ -69,7 +70,7 @@ msgid "German" msgstr "Герман" msgid "Lower Sorbian" -msgstr "" +msgstr "Доод Сорбин" msgid "Greek" msgstr "Грек" @@ -141,11 +142,14 @@ msgid "Croatian" msgstr "Хорват" msgid "Upper Sorbian" -msgstr "" +msgstr "Дээд Сорбин" msgid "Hungarian" msgstr "Унгар" +msgid "Armenian" +msgstr "Армен" + msgid "Interlingua" msgstr "Interlingua" @@ -167,6 +171,9 @@ msgstr "Япон" msgid "Georgian" msgstr "Гүрж" +msgid "Kabyle" +msgstr "Кабилэ" + msgid "Kazakh" msgstr "Казак" @@ -204,7 +211,7 @@ msgid "Burmese" msgstr "Бирм" msgid "Norwegian Bokmål" -msgstr "" +msgstr "Норвеги Бокмал" msgid "Nepali" msgstr "Непал" @@ -302,6 +309,15 @@ msgstr "Статик файлууд" msgid "Syndication" msgstr "Нэгтгэл" +msgid "That page number is not an integer" +msgstr "Хуудасны дугаар бүхэл тоо / Integer / биш байна" + +msgid "That page number is less than 1" +msgstr "Хуудасны дугаар 1-ээс байга байна" + +msgid "That page contains no results" +msgstr "Хуудас үр дүн агуулаагүй байна" + msgid "Enter a valid value." msgstr "Зөв утга оруулна уу." @@ -314,6 +330,7 @@ msgstr "Бүхэл тоо оруулна уу" msgid "Enter a valid email address." msgstr "Зөв имэйл хаяг оруулна уу" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -379,6 +396,9 @@ msgstr[1] "" "Ensure this value has at most %(limit_value)d characters (it has " "%(show_value)d)." +msgid "Enter a number." +msgstr "Тоон утга оруулна уу." + #, 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." @@ -399,6 +419,17 @@ msgid_plural "" msgstr[0] "Энд бутархайн таслалаас өмнө %(max)s-аас олонгүй цифр байх ёстой." msgstr[1] "Энд бутархайн таслалаас өмнө %(max)s-аас олонгүй цифр байх ёстой." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Файлын '%(extension)s' өргөтгөл зөвшөөрөгдөөгүй байна. Дараах өргөтгөлүүд " +"зөвшөөрөгдсөн: '%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Хоосон тэмдэгт зөвшөөрөгдөхгүй." + msgid "and" msgstr "ба" @@ -447,6 +478,10 @@ msgstr "Том (8 байт) бүхэл тоо" msgid "'%(value)s' value must be either True or False." msgstr "'%(value)s' заавал True эсвэл False утга авах." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "'%(value)s' утга True,False, None ийн аль нэг байх ёстой." + msgid "Boolean (Either True or False)" msgstr "Boolean (Үнэн худлын аль нэг нь)" @@ -580,6 +615,9 @@ msgstr "Бинари өгөгдөл" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' утга зөв UUID биш байна." +msgid "Universally unique identifier" +msgstr "UUID" + msgid "File" msgstr "Файл" @@ -598,11 +636,11 @@ msgstr "Нэг-нэг холбоос" #, python-format msgid "%(from)s-%(to)s relationship" -msgstr "" +msgstr "%(from)s-%(to)s холбоос" #, python-format msgid "%(from)s-%(to)s relationships" -msgstr "" +msgstr "%(from)s-%(to)s холбоосууд" msgid "Many-to-many relationship" msgstr "Олон-олон холбоос" @@ -619,9 +657,6 @@ msgstr "Энэ талбарыг бөглөх шаардлагатай." msgid "Enter a whole number." msgstr "Бүхэл тоон утга оруулна уу." -msgid "Enter a number." -msgstr "Тоон утга оруулна уу." - msgid "Enter a valid date." msgstr "Зөв огноо оруулна уу." @@ -634,6 +669,10 @@ msgstr "Огноо/цаг-ыг зөв оруулна уу." msgid "Enter a valid duration." msgstr "Үргэлжилэх хугацааг зөв оруулна уу." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Өдөрийн утга {min_days} ээс {max_days} ийн хооронд байх ёстой." + msgid "No file was submitted. Check the encoding type on the form." msgstr "Файл оруулаагүй байна. Маягтаас кодлох төрлийг чагтал. " @@ -727,16 +766,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Доорх давхардсан утгуудыг засна уу." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "" -"Inline обектийн гадаад түлхүүр Эцэг обектийн түлхүүртэй таарахгүй байна. " +msgid "The inline value did not match the parent instance." +msgstr "Inline утга эцэг обекттой таарахгүй байна." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Зөв сонголт хийнэ үү. Энэ утга сонголтонд алга." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" нь primary key талбарт тохирохгүй утга байна." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" зөв утга биш байна." #, python-format msgid "" @@ -746,15 +784,15 @@ msgstr "" "%(datetime)s цагийн бүсийг хөрвүүлэж чадахгүй байна. %(current_timezone)s; " "цагийн бүс буруу эсвэл байхгүй байж магадгүй." +msgid "Clear" +msgstr "Цэвэрлэх" + msgid "Currently" msgstr "Одоогийн" msgid "Change" msgstr "Засах" -msgid "Clear" -msgstr "Цэвэрлэх" - msgid "Unknown" msgstr "Тодорхойгүй" @@ -1026,8 +1064,8 @@ msgstr "Энэ буруу IPv6 хаяг байна." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s…" msgid "or" msgstr "буюу" @@ -1099,6 +1137,19 @@ 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." +msgstr "" +"Хэрвээ та таг ашиглаж " +"байгаа бол эсвэл 'Referrer-Policy: no-referrer' толгойг нэмсэн бол, " +"эдгээрийг устгана уу. CSRF хамгаалалт 'Referer' толгойг чанд шалгалт хийхийг " +"шаарддаг. Хэрвээ та хувийн аюулгүй байдалд санаа тавьдаг бол 3-дагч сайтыг " +"холбохдоо ашиглана уу." + 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 " @@ -1118,32 +1169,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "DEBUG=True үед дэлгэрэнгүй мэдээлэл харах боломжтой." -msgid "Welcome to Django" -msgstr "Джанго-д тавтай морилоно уу" - -msgid "It worked!" -msgstr "Өө ажилчихлаа!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Баяр хүргэе!. Таний эхний Django-оор хийсэн хуудас." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Мэдээж таны ажил дөнгөж эхлэж байна. Үүний дараа дараах коммандыг ажиллуулаж " -"эхний app аа үүсгэнэ. python manage.py startapp [app_label]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Та Джангогийнхоо тохиргооны файлд DEBUG = True гэсэн ба ямарч " -"URLs тохируулаагүй учир энэ мэдэгдлийг уншиж байна. Ажиллаж байна!" - msgid "No year specified" msgstr "Он тодорхойлоогүй байна" +msgid "Date out of range" +msgstr "Хугацааны хязгаар хэтэрсэн байна" + msgid "No month specified" msgstr "Сар тодорхойлоогүй байна" @@ -1196,3 +1227,48 @@ msgstr "\"%(path)s\" байхгүй байна." #, python-format msgid "Index of %(directory)s" msgstr "%(directory)s ийн жагсаалт" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Джанго: Чанартай бөгөөд хугацаанд нь хийхэд зориулсан Web framework." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Джанго %(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." +msgstr "" +"Таний тохиргооны файл дээр DEBUG=TRUE гэж тохируулсан мөн URLs дээр тохиргоо хийгээгүй учраас " +"энэ хуудасыг харж байна." + +msgid "Django Documentation" +msgstr "Джанго баримтжуулалт" + +msgid "Topics, references, & how-to's" +msgstr "Сэдэв, лавлахууд болон заавар-ууд" + +msgid "Tutorial: A Polling App" +msgstr "Хичээл: Санал асуулга App" + +msgid "Get started with Django" +msgstr "Джанготой ажиллаж эхлэх" + +msgid "Django Community" +msgstr "Django Бүлгэм" + +msgid "Connect, get help, or contribute" +msgstr "Холбогдох, тусламж авах эсвэл хувь нэмрээ оруулах" diff --git a/django/conf/locale/mn/formats.py b/django/conf/locale/mn/formats.py index f41d532ba9ca..24c7dec8a768 100644 --- a/django/conf/locale/mn/formats.py +++ b/django/conf/locale/mn/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'd F Y' TIME_FORMAT = 'g:i A' # DATETIME_FORMAT = @@ -15,7 +12,7 @@ # FIRST_DAY_OF_WEEK = # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # DATE_INPUT_FORMATS = # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = diff --git a/django/conf/locale/mr/LC_MESSAGES/django.mo b/django/conf/locale/mr/LC_MESSAGES/django.mo index 9fb3597ffc4e..a57b656b1469 100644 Binary files a/django/conf/locale/mr/LC_MESSAGES/django.mo and b/django/conf/locale/mr/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/mr/LC_MESSAGES/django.po b/django/conf/locale/mr/LC_MESSAGES/django.po index e1349b2cfbad..6ba209b83347 100644 --- a/django/conf/locale/mr/LC_MESSAGES/django.po +++ b/django/conf/locale/mr/LC_MESSAGES/django.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Marathi (http://www.transifex.com/django/django/language/" "mr/)\n" @@ -293,6 +293,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "" @@ -305,6 +314,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -378,6 +388,15 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "" @@ -684,14 +703,14 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "" -msgid "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -700,13 +719,13 @@ msgid "" "may be ambiguous or it may not exist." msgstr "" -msgid "Currently" +msgid "Clear" msgstr "" -msgid "Change" +msgid "Currently" msgstr "" -msgid "Clear" +msgid "Change" msgstr "" msgid "Unknown" @@ -1048,6 +1067,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1062,26 +1089,10 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" +msgid "No year specified" msgstr "" -msgid "No year specified" +msgid "Date out of range" msgstr "" msgid "No month specified" @@ -1132,3 +1143,41 @@ msgstr "" #, python-format msgid "Index of %(directory)s" msgstr "" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/my/LC_MESSAGES/django.mo b/django/conf/locale/my/LC_MESSAGES/django.mo index 9b0693ce4a6e..c21bef54dcb2 100644 Binary files a/django/conf/locale/my/LC_MESSAGES/django.mo and b/django/conf/locale/my/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/my/LC_MESSAGES/django.po b/django/conf/locale/my/LC_MESSAGES/django.po index 9fc4cae6c6e8..f82236a46ad3 100644 --- a/django/conf/locale/my/LC_MESSAGES/django.po +++ b/django/conf/locale/my/LC_MESSAGES/django.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Burmese (http://www.transifex.com/django/django/language/" "my/)\n" @@ -293,6 +293,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "" @@ -305,6 +314,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -373,6 +383,15 @@ msgid_plural "" "Ensure that there are no more than %(max)s digits before the decimal point." msgstr[0] "" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "နှင့်" @@ -676,14 +695,14 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "" -msgid "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -692,13 +711,13 @@ msgid "" "may be ambiguous or it may not exist." msgstr "" -msgid "Currently" +msgid "Clear" msgstr "" -msgid "Change" +msgid "Currently" msgstr "" -msgid "Clear" +msgid "Change" msgstr "" msgid "Unknown" @@ -1033,6 +1052,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1047,26 +1074,10 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" +msgid "No year specified" msgstr "" -msgid "No year specified" +msgid "Date out of range" msgstr "" msgid "No month specified" @@ -1117,3 +1128,41 @@ msgstr "" #, python-format msgid "Index of %(directory)s" msgstr "" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/nb/LC_MESSAGES/django.mo b/django/conf/locale/nb/LC_MESSAGES/django.mo index 53c5ff213f88..35e4bb55ae7b 100644 Binary files a/django/conf/locale/nb/LC_MESSAGES/django.mo and b/django/conf/locale/nb/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/nb/LC_MESSAGES/django.po b/django/conf/locale/nb/LC_MESSAGES/django.po index 8c7a20f6979d..eb536cd2d274 100644 --- a/django/conf/locale/nb/LC_MESSAGES/django.po +++ b/django/conf/locale/nb/LC_MESSAGES/django.po @@ -5,19 +5,20 @@ # Eirik Krogstad , 2014 # Jannis Leidel , 2011 # jensadne , 2014-2015 -# Jon , 2015-2016 -# Jon , 2014 -# Jon , 2013 -# Jon , 2011 +# Jon , 2015-2016 +# Jon , 2014 +# Jon , 2017-2019 +# Jon , 2013 +# Jon , 2011 # Sigurd Gartmann , 2012 # Tommy Strand , 2013 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-07-27 08:59+0000\n" -"Last-Translator: Jon \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-05-06 13:02+0000\n" +"Last-Translator: Jon \n" "Language-Team: Norwegian Bokmål (http://www.transifex.com/django/django/" "language/nb/)\n" "MIME-Version: 1.0\n" @@ -146,6 +147,9 @@ msgstr "Høysorbisk" msgid "Hungarian" msgstr "Ungarsk" +msgid "Armenian" +msgstr "Armensk" + msgid "Interlingua" msgstr "Interlingua" @@ -167,6 +171,9 @@ msgstr "Japansk" msgid "Georgian" msgstr "Georgisk" +msgid "Kabyle" +msgstr "Kabylsk" + msgid "Kazakh" msgstr "Kasakhisk" @@ -302,6 +309,15 @@ msgstr "Statiske filer" msgid "Syndication" msgstr "Syndikering" +msgid "That page number is not an integer" +msgstr "Sidenummeret er ikke et heltall" + +msgid "That page number is less than 1" +msgstr "Sidenummeret er mindre enn 1" + +msgid "That page contains no results" +msgstr "Siden inneholder ingen resultater" + msgid "Enter a valid value." msgstr "Oppgi en gyldig verdi." @@ -314,6 +330,7 @@ msgstr "Skriv inn et gyldig heltall." msgid "Enter a valid email address." msgstr "Oppgi en gyldig e-postadresse" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -378,6 +395,9 @@ msgstr[1] "" "Sørg for at denne verdien har %(limit_value)d eller færre tegn (den har nå " "%(show_value)d)." +msgid "Enter a number." +msgstr "Oppgi et tall." + #, 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." @@ -398,6 +418,17 @@ msgid_plural "" msgstr[0] "Sørg for at det kun %(max)s tall før desimalpunkt." msgstr[1] "Sørg for at det er %(max)s eller færre tall før desimalpunkt." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Filendelsen '%(extension)s' er ikke tillatt. Tillatte filendelser er: " +"'%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Null-tegn er ikke tillatt." + msgid "and" msgstr "og" @@ -444,6 +475,10 @@ msgstr "Stort (8 byte) heltall" msgid "'%(value)s' value must be either True or False." msgstr "Verdien '%(value)s' må være enten True eller False." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "'%(value)s'-verdien må være enten True, False, eller None." + msgid "Boolean (Either True or False)" msgstr "Boolsk (True eller False)" @@ -580,6 +615,9 @@ msgstr "Rå binærdata" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' er ikke en gyldig UUID." +msgid "Universally unique identifier" +msgstr "Universelt unik identifikator" + msgid "File" msgstr "Fil" @@ -619,9 +657,6 @@ msgstr "Feltet er påkrevet." msgid "Enter a whole number." msgstr "Oppgi et heltall." -msgid "Enter a number." -msgstr "Oppgi et tall." - msgid "Enter a valid date." msgstr "Oppgi en gyldig dato." @@ -634,6 +669,10 @@ msgstr "Oppgi gyldig dato og tidspunkt." msgid "Enter a valid duration." msgstr "Oppgi en gyldig varighet." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Antall dager må være mellom {min_days} og {max_days}." + msgid "No file was submitted. Check the encoding type on the form." msgstr "Ingen fil ble sendt. Sjekk «encoding»-typen på skjemaet." @@ -722,15 +761,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Vennligst korriger de dupliserte verdiene nedenfor." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "Primærnøkkelen er ikke den samme som foreldreinstansens primærnøkkel." +msgid "The inline value did not match the parent instance." +msgstr "Inline-verdien var ikke i samsvar med foreldre-instansen." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Velg et gyldig valg. Valget er ikke av de tilgjengelige valgene." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "«%(pk)s» er ikke en gyldig verdi for en primærnøkkel." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" er ikke en gyldig verdi." #, python-format msgid "" @@ -740,15 +779,15 @@ msgstr "" "%(datetime)s kunne ikke tolkes i tidssonen %(current_timezone)s, det kan " "være tvetydig eller ikke eksistere." +msgid "Clear" +msgstr "Fjern" + msgid "Currently" msgstr "Nåværende" msgid "Change" msgstr "Endre" -msgid "Clear" -msgstr "Fjern" - msgid "Unknown" msgstr "Ukjent" @@ -1020,7 +1059,7 @@ msgstr "Dette er ikke en gyldig IPv6-adresse." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." +msgid "%(truncated_text)s…" msgstr "%(truncated_text)s…" msgid "or" @@ -1095,6 +1134,19 @@ msgstr "" "kan du aktivere dem, i hvert fall for dette nettstedet, eller for HTTPS-" "tilkoblinger, eller for 'same-origin'-forespørsler." +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." +msgstr "" +"Hvis du bruker -taggen eller " +"inkluderer 'Referrer-Policy: no-referrer'-header, vennligst fjern dem. CSRF-" +"beskyttelsen krever 'Referer'-headeren for å utføre streng kontroll av " +"referanser. Hvis du er bekymret for personvern, bruk alternativer som for koblinger til tredjeparts nettsteder." + 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 " @@ -1116,32 +1168,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Mer informasjon er tilgjengelig med DEBUG=True." -msgid "Welcome to Django" -msgstr "Velkommen til Django" - -msgid "It worked!" -msgstr "Det virket!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Gratulerer med din første Django-drevne side." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Selvfølgelig, du har ikke gjort noe faktisk arbeid enda. Nå kan du lage din " -"første app ved å kjøre python manage.py startapp [app_label]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Du ser denne meldingen fordi du har DEBUG = True i din Django-" -"innstillingsfil og ikke har konfigurert noen URL-er. Sett i gang!" - msgid "No year specified" msgstr "År ikke spesifisert" +msgid "Date out of range" +msgstr "Date utenfor rekkevidde" + msgid "No month specified" msgstr "Måned ikke spesifisert" @@ -1192,3 +1224,48 @@ msgstr "«%(path)s» finnes ikke" #, python-format msgid "Index of %(directory)s" msgstr "Innhold i %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: web-rammeverket for perfeksjonister med tidsfrister." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Se produktmerknader for Django " +"%(version)s" + +msgid "The install worked successfully! Congratulations!" +msgstr "Installasjonen var vellykket! Gratulerer!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Du ser denne siden fordi DEBUG=True er i din Django-innstillingsfil og du ikke har konfigurert " +"noen URL-er." + +msgid "Django Documentation" +msgstr "Django-dokumentasjon" + +msgid "Topics, references, & how-to's" +msgstr "Temaer, referanser & how-tos" + +msgid "Tutorial: A Polling App" +msgstr "Tutorial: en polling-app" + +msgid "Get started with Django" +msgstr "Kom i gang med Django" + +msgid "Django Community" +msgstr "Django nettsamfunn" + +msgid "Connect, get help, or contribute" +msgstr "Koble, få hjelp eller bidra" diff --git a/django/conf/locale/nb/formats.py b/django/conf/locale/nb/formats.py index 09fa857b4ce2..2180cf3328ac 100644 --- a/django/conf/locale/nb/formats.py +++ b/django/conf/locale/nb/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j. F Y' TIME_FORMAT = 'H:i' DATETIME_FORMAT = 'j. F Y H:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # Kept ISO formats as they are in first position DATE_INPUT_FORMATS = [ '%Y-%m-%d', '%d.%m.%Y', '%d.%m.%y', # '2006-10-25', '25.10.2006', '25.10.06' diff --git a/django/conf/locale/ne/LC_MESSAGES/django.mo b/django/conf/locale/ne/LC_MESSAGES/django.mo index 528edd700ce5..10fd9af7c0dd 100644 Binary files a/django/conf/locale/ne/LC_MESSAGES/django.mo and b/django/conf/locale/ne/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/ne/LC_MESSAGES/django.po b/django/conf/locale/ne/LC_MESSAGES/django.po index e876d8e32a5f..f15191787645 100644 --- a/django/conf/locale/ne/LC_MESSAGES/django.po +++ b/django/conf/locale/ne/LC_MESSAGES/django.po @@ -3,14 +3,14 @@ # Translators: # Jannis Leidel , 2014 # Paras Nath Chaudhary , 2012 -# Sagar Chalise , 2011-2012,2015 +# Sagar Chalise , 2011-2012,2015,2018 # Sagar Chalise , 2015 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-09-15 15:53+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-26 07:41+0000\n" "Last-Translator: Sagar Chalise \n" "Language-Team: Nepali (http://www.transifex.com/django/django/language/ne/)\n" "MIME-Version: 1.0\n" @@ -139,6 +139,9 @@ msgstr "माथिल्लो सोर्बियन " msgid "Hungarian" msgstr "हन्गेरियन" +msgid "Armenian" +msgstr "" + msgid "Interlingua" msgstr "ईन्टरलिन्गुवा" @@ -160,6 +163,9 @@ msgstr "जापनिज" msgid "Georgian" msgstr "जर्जीयन" +msgid "Kabyle" +msgstr "कबायल" + msgid "Kazakh" msgstr "कजाक" @@ -167,7 +173,7 @@ msgid "Khmer" msgstr "ख्मेर" msgid "Kannada" -msgstr "कन्नडा" +msgstr "कन्नड" msgid "Korean" msgstr "कोरियाली" @@ -295,6 +301,15 @@ msgstr "स्टेेटिक फाइलहरु" msgid "Syndication" msgstr "सिन्डिकेसन" +msgid "That page number is not an integer" +msgstr "पृष्ठ नं अंक होइन ।" + +msgid "That page number is less than 1" +msgstr "पृष्ठ नं १ भन्दा कम भयो ।" + +msgid "That page contains no results" +msgstr "पृष्ठमा नतिजा छैन ।" + msgid "Enter a valid value." msgstr "उपयुक्त मान राख्नुहोस ।" @@ -307,6 +322,7 @@ msgstr "उपयुक्त अंक राख्नुहोस ।" msgid "Enter a valid email address." msgstr "सही ई-मेल ठेगाना राख्नु होस ।" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "अक्षर, अंक, _ र - भएका 'स्लग' मात्र हाल्नुहोस ।" @@ -368,6 +384,9 @@ msgstr[1] "" "यो मान बढिमा पनि %(limit_value)d अक्षरहरु छ भन्ने निश्चित गर्नुहोस । (यसमा " "%(show_value)d छ ।)" +msgid "Enter a number." +msgstr "संख्या राख्नुहोस ।" + #, 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." @@ -388,6 +407,16 @@ msgid_plural "" msgstr[0] "दशमलव अघि %(max)s भन्दा बढी अक्षर नभएको निश्चित पार्नु होस ।" msgstr[1] "दशमलव अघि %(max)s भन्दा बढी अक्षरहरु नभएको निश्चित पार्नु होस ।" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"'%(extension)s' फाइलको अनुमति छैन। अनुमति भएका फाइलहरू: '%(allowed_extensions)s'" + +msgid "Null characters are not allowed." +msgstr "शून्य मान अनुमति छैन।" + msgid "and" msgstr "र" @@ -435,6 +464,10 @@ msgstr "ठूलो (८ बाइटको) अंक" msgid "'%(value)s' value must be either True or False." msgstr "%(value)s' को मान True अथवा False हुनुपर्दछ ।." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "'%(value)s' को मान True, False अथवा None हुनुपर्दछ ।" + msgid "Boolean (Either True or False)" msgstr "बुलियन (True अथवा False)" @@ -558,6 +591,9 @@ msgstr "र बाइनरी डाटा" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' मान्य UUID होइन ।" +msgid "Universally unique identifier" +msgstr "" + msgid "File" msgstr "फाइल" @@ -597,9 +633,6 @@ msgstr "यो फाँट अनिवार्य छ ।" msgid "Enter a whole number." msgstr "संख्या राख्नुहोस ।" -msgid "Enter a number." -msgstr "संख्या राख्नुहोस ।" - msgid "Enter a valid date." msgstr "उपयुक्त मिति राख्नुहोस ।" @@ -612,6 +645,10 @@ msgstr "उपयुक्त मिति/समय राख्नुहोस msgid "Enter a valid duration." msgstr "उपयुक्त अवधि राख्नुहोस ।" +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "दिन गन्ती {min_days} र {max_days} बीचमा हुनु पर्छ । " + msgid "No file was submitted. Check the encoding type on the form." msgstr "कुनै फाईल पेश गरिएको छैन । फारममा ईनकोडिङको प्रकार जाँच गर्नुहोस । " @@ -703,21 +740,26 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "कृपया तलका दोहोरिइका मानहरु सच्याउनुहोस ।" -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "भित्रि फोरेन की र अभिभावक प्राइमरी की मिलेन ।" +msgid "The inline value did not match the parent instance." +msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "उपयुक्त विकल्प छान्नुहोस । छानिएको विकल्प प्रस्तावित विकल्प होइन ।" #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" प्राइमरी कि का लागि मान्य छैन ।" +msgid "\"%(pk)s\" is not a valid value." +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 "सबै खाली गर्नु होस ।" msgid "Currently" msgstr "अहिले" @@ -725,9 +767,6 @@ msgstr "अहिले" msgid "Change" msgstr "फेर्नुहोस" -msgid "Clear" -msgstr "सबै खाली गर्नु होस ।" - msgid "Unknown" msgstr "अज्ञात" @@ -999,8 +1038,8 @@ msgstr "यो उपयुक्त IPv6 ठेगाना होइन ।" #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "" msgid "or" msgstr "अथवा" @@ -1060,6 +1099,8 @@ msgid "" "required for security reasons, to ensure that your browser is not being " "hijacked by third parties." msgstr "" +"यो सूचना तपाईँले 'Referer header' ब्राउजरले नपठाएको ले देख्दै हुनुहुन्छ । सुरक्षाको निम्ति " +"HTTPS साइट चलाउँदा 'Referer header' वेब ब्राउजरले पठाउनु पर्ने अनिवार्य छ ।" msgid "" "If you have configured your browser to disable 'Referer' headers, please re-" @@ -1067,6 +1108,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1081,30 +1130,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "DEBUG=True ले ज्यादा सुचना प्रदान गर्दछ ।" -msgid "Welcome to Django" -msgstr "Django स्वागत गर्दछ ।" - -msgid "It worked!" -msgstr "कार्य सफल !" - -msgid "Congratulations on your first Django-powered page." -msgstr "Django ले बनाइएको प्रथम वेब पृष्ठको लागी तपाईँलाई शुभकामना ।" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"सबै काम सिद्धिएको छैन । आफ्नो एप चलाउन python manage.py startapp " -"[app_label] राख्नु होस ।" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "साल तोकिएको छैन ।" +msgid "Date out of range" +msgstr "मिति मिलेन ।" + msgid "No month specified" msgstr "महिना तोकिएको छैन ।" @@ -1155,3 +1186,44 @@ msgstr "\"%(path)s\" नभएको पाइयो ।" #, python-format msgid "Index of %(directory)s" msgstr "%(directory)s को सूची" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "ज्याङ्गो : वेब साइट र एप्लिकेसन बनाउन सहयोगी औजार " + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"ज्याङ्गो %(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." +msgstr "" + +msgid "Django Documentation" +msgstr "ज्याङ्गो दस्तावेज ।" + +msgid "Topics, references, & how-to's" +msgstr "शीर्षक, सन्दर्भ तथा तरिकाहरू" + +msgid "Tutorial: A Polling App" +msgstr "मतदान एप उदाहरण " + +msgid "Get started with Django" +msgstr "ज्याङ्गो सुरु गर्नु होस ।" + +msgid "Django Community" +msgstr "ज्याङ्गो समुदाय" + +msgid "Connect, get help, or contribute" +msgstr "सहयोग अथवा योगदान गरी जोडिनु होस" diff --git a/django/conf/locale/nl/LC_MESSAGES/django.mo b/django/conf/locale/nl/LC_MESSAGES/django.mo index 13c72a660313..1dc23679d2fc 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 43ffbbeafe78..f749c183869e 100644 --- a/django/conf/locale/nl/LC_MESSAGES/django.po +++ b/django/conf/locale/nl/LC_MESSAGES/django.po @@ -3,23 +3,25 @@ # Translators: # Bas Peschier , 2011,2013 # Blue , 2011-2012 -# Bouke Haarsma , 2013 +# Bouke Haarsma , 2013 # Claude Paroz , 2014 # Erik Romijn , 2013 +# Evelijn Saaltink , 2016 # Harro van der Klauw , 2011-2012 # Ilja Maas , 2015 # Jannis Leidel , 2011 # Jeffrey Gelens , 2011-2012,2014 # Michiel Overtoom , 2014 -# Sander Steffann , 2014-2015 +# Sander Steffann , 2014-2015 # Tino de Bruijn , 2013 +# Tonnes , 2017,2019 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-02-24 14:48+0000\n" +"Last-Translator: Tonnes \n" "Language-Team: Dutch (http://www.transifex.com/django/django/language/nl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -37,7 +39,7 @@ msgid "Asturian" msgstr "Asturisch" msgid "Azerbaijani" -msgstr "Azerbaijani" +msgstr "Azerbeidzjaans" msgid "Bulgarian" msgstr "Bulgaars" @@ -58,7 +60,7 @@ msgid "Catalan" msgstr "Catalaans" msgid "Czech" -msgstr "Tjechisch" +msgstr "Tsjechisch" msgid "Welsh" msgstr "Welsh" @@ -70,7 +72,7 @@ msgid "German" msgstr "Duits" msgid "Lower Sorbian" -msgstr "" +msgstr "Nedersorbisch" msgid "Greek" msgstr "Grieks" @@ -91,10 +93,10 @@ msgid "Spanish" msgstr "Spaans" msgid "Argentinian Spanish" -msgstr "Argentijns-Spaans" +msgstr "Argentijns Spaans" msgid "Colombian Spanish" -msgstr "Columbiaans Spaans" +msgstr "Colombiaans Spaans" msgid "Mexican Spanish" msgstr "Mexicaans Spaans" @@ -133,7 +135,7 @@ msgid "Galician" msgstr "Galicisch" msgid "Hebrew" -msgstr "Hebreews" +msgstr "Hebreeuws" msgid "Hindi" msgstr "Hindi" @@ -142,11 +144,14 @@ msgid "Croatian" msgstr "Kroatisch" msgid "Upper Sorbian" -msgstr "" +msgstr "Oppersorbisch" msgid "Hungarian" msgstr "Hongaars" +msgid "Armenian" +msgstr "Armeens" + msgid "Interlingua" msgstr "Interlingua" @@ -168,6 +173,9 @@ msgstr "Japans" msgid "Georgian" msgstr "Georgisch" +msgid "Kabyle" +msgstr "Kabylisch" + msgid "Kazakh" msgstr "Kazachs" @@ -196,7 +204,7 @@ msgid "Malayalam" msgstr "Malayalam" msgid "Mongolian" -msgstr "Mongolisch" +msgstr "Mongools" msgid "Marathi" msgstr "Marathi" @@ -205,7 +213,7 @@ msgid "Burmese" msgstr "Birmaans" msgid "Norwegian Bokmål" -msgstr "" +msgstr "Noors Bokmål" msgid "Nepali" msgstr "Nepalees" @@ -214,7 +222,7 @@ msgid "Dutch" msgstr "Nederlands" msgid "Norwegian Nynorsk" -msgstr "Noorse Nynorsk" +msgstr "Noors Nynorsk" msgid "Ossetic" msgstr "Ossetisch" @@ -265,7 +273,7 @@ msgid "Telugu" msgstr "Telegu" msgid "Thai" -msgstr "Thais" +msgstr "Thai" msgid "Turkish" msgstr "Turks" @@ -303,42 +311,52 @@ msgstr "Statische bestanden" msgid "Syndication" msgstr "Syndicatie" +msgid "That page number is not an integer" +msgstr "Dat paginanummer is geen geheel getal" + +msgid "That page number is less than 1" +msgstr "Dat paginanummer is kleiner dan 1" + +msgid "That page contains no results" +msgstr "Die pagina bevat geen resultaten" + msgid "Enter a valid value." -msgstr "Geef een geldige waarde." +msgstr "Voer een geldige waarde in." msgid "Enter a valid URL." -msgstr "Geef een geldige URL op." +msgstr "Voer een geldige URL in." msgid "Enter a valid integer." -msgstr "Geef een geldig geheel getal op." +msgstr "Voer een geldig geheel getal in." msgid "Enter a valid email address." -msgstr "Vul een geldig emailadres in." +msgstr "Voer een geldig e-mailadres in." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" -"Vul een geldigde 'slug' in, bestaande uit letters, cijfers, liggende " +"Voer een geldige 'slug' in, bestaande uit letters, cijfers, liggende " "streepjes en verbindingsstreepjes." msgid "" "Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or " "hyphens." msgstr "" -"Vul een geldigde 'slug' in, bestaande uit Unicode letters, cijfers, liggende " -"streepjes of verbindingsstreepjes." +"Voer een geldige 'slug' in, bestaande uit Unicode-letters, cijfers, liggende " +"streepjes en verbindingsstreepjes." msgid "Enter a valid IPv4 address." -msgstr "Geef een geldig IPv4-adres op." +msgstr "Voer een geldig IPv4-adres in." msgid "Enter a valid IPv6 address." msgstr "Voer een geldig IPv6-adres in." msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Voer een geldig IPv4 of IPv6-adres in." +msgstr "Voer een geldig IPv4- of IPv6-adres in." msgid "Enter only digits separated by commas." -msgstr "Geef alleen cijfers op, gescheiden door komma's." +msgstr "Voer alleen cijfers in, gescheiden door komma's." #, python-format msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." @@ -382,6 +400,9 @@ msgstr[1] "" "Zorg dat deze waarde niet meer dan %(limit_value)d tekens bevat (het zijn er " "nu %(show_value)d)." +msgid "Enter a number." +msgstr "Voer een getal in." + #, 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." @@ -402,6 +423,17 @@ msgid_plural "" msgstr[0] "Zorg dat er niet meer dan %(max)s cijfer voor de komma staat." msgstr[1] "Zorg dat er niet meer dan %(max)s cijfers voor de komma staan." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Bestandsextensie '%(extension)s' is niet toegestaan. Toegestane extensies " +"zijn: '%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Null-tekens zijn niet toegestaan." + msgid "and" msgstr "en" @@ -440,21 +472,25 @@ msgstr "Geheel getal" #, python-format msgid "'%(value)s' value must be an integer." -msgstr "'%(value)s' waarde moet een geheel getal zijn." +msgstr "Waarde van '%(value)s' moet een geheel getal zijn." msgid "Big (8 byte) integer" msgstr "Groot (8 byte) geheel getal" #, python-format msgid "'%(value)s' value must be either True or False." -msgstr "'%(value)s' waarde moet True of False zijn." +msgstr "Waarde van '%(value)s' moet True of False zijn." + +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "Waarde van '%(value)s' moet True, False of None zijn." msgid "Boolean (Either True or False)" -msgstr "Boolean (True danwel False)" +msgstr "Boolean (True of False)" #, python-format msgid "String (up to %(max_length)s)" -msgstr "Karakterreeks (hooguit %(max_length)s)" +msgstr "Tekenreeks (hooguit %(max_length)s)" msgid "Comma-separated integers" msgstr "Komma-gescheiden gehele getallen" @@ -464,15 +500,15 @@ msgid "" "'%(value)s' value has an invalid date format. It must be in YYYY-MM-DD " "format." msgstr "" -"'%(value)s' waarde heeft een ongeldige datumnotatie. Deze moet in de YYYY-MM-" -"DD notatie opgegeven worden." +"Waarde van '%(value)s' heeft een ongeldige datumnotatie. De juiste notatie " +"is YYYY-MM-DD." #, python-format msgid "" "'%(value)s' value has the correct format (YYYY-MM-DD) but it is an invalid " "date." msgstr "" -"'%(value)s' waarde heeft een geldige notatie (YYYY-MM-DD) maar is een " +"Waarde van '%(value)s' heeft de juiste notatie (YYYY-MM-DD), maar het is een " "ongeldige datum." msgid "Date (without time)" @@ -483,23 +519,23 @@ msgid "" "'%(value)s' value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." "uuuuuu]][TZ] format." msgstr "" -"'%(value)s' waarde heeft een ongeldige notatie. Deze moet in de YYYY-MM-DD " -"HH:MM[:ss[.uuuuuu]][TZ] notatie opgegeven worden." +"Waarde van '%(value)s' heeft een ongeldige notatie. De juiste notatie is " +"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' waarde heeft een geldige notatie (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]" -"[TZ]) maar is een ongeldige datum/tijd." +"Waarde van '%(value)s' heeft de juiste notatie (YYYY-MM-DD HH:MM[:ss[." +"uuuuuu]][TZ]), maar is een ongeldige datum/tijd." msgid "Date (with time)" msgstr "Datum (met tijd)" #, python-format msgid "'%(value)s' value must be a decimal number." -msgstr "'%(value)s' waarde moet een decimaal getal zijn." +msgstr "Waarde van '%(value)s' moet een decimaal getal zijn." msgid "Decimal number" msgstr "Decimaal getal" @@ -509,8 +545,8 @@ msgid "" "'%(value)s' value has an invalid format. It must be in [DD] [HH:[MM:]]ss[." "uuuuuu] format." msgstr "" -"'%(value)s' waarde heeft een ongeldig formaat. Het juiste formaat is [DD] " -"[HH:[MM:]]ss[.uuuuuu]." +"Waarde van '%(value)s' heeft een ongeldige notatie. De juiste notatie is " +"[DD] [HH:[MM:]]ss[.uuuuuu]." msgid "Duration" msgstr "Tijdsduur" @@ -523,20 +559,20 @@ msgstr "Bestandspad" #, python-format msgid "'%(value)s' value must be a float." -msgstr "'%(value)s' waarde moet een decimaal getal zijn." +msgstr "Waarde van '%(value)s' moet een drijvende-kommagetal zijn." msgid "Floating point number" -msgstr "Decimaal getal" +msgstr "Drijvende-kommagetal" msgid "IPv4 address" -msgstr "IPv4 address" +msgstr "IPv4-adres" msgid "IP address" msgstr "IP-adres" #, python-format msgid "'%(value)s' value must be either None, True or False." -msgstr "'%(value)s' waarde moet None, True of False zijn." +msgstr "Waarde van '%(value)s' moet None, True of False zijn." msgid "Boolean (Either True, False or None)" msgstr "Boolean (True, False of None)" @@ -562,16 +598,16 @@ msgid "" "'%(value)s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " "format." msgstr "" -"'%(value)s' waarde heeft een ongeldige notatie. Deze moet in de HH:MM[:ss[." -"uuuuuu]] notatie opgegeven worden." +"Waarde van '%(value)s' heeft een ongeldige notatie. De juiste notatie is 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 "" -"'%(value)s' waarde heeft een geldige notatie (HH:MM[:ss[.uuuuuu]]) maar is " -"een ongeldige tijd." +"Waarde van '%(value)s' heeft de juiste notatie (HH:MM[:ss[.uuuuuu]]), maar " +"het is een ongeldige tijd." msgid "Time" msgstr "Tijd" @@ -580,38 +616,41 @@ msgid "URL" msgstr "URL" msgid "Raw binary data" -msgstr "Ruwe binaire data" +msgstr "Onbewerkte binaire gegevens" #, python-format msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' is geen geldige UUID." +msgid "Universally unique identifier" +msgstr "Universally unique identifier" + msgid "File" msgstr "Bestand" msgid "Image" -msgstr "Plaatje" +msgstr "Afbeelding" #, python-format msgid "%(model)s instance with %(field)s %(value)r does not exist." msgstr "%(model)s-instantie met %(field)s %(value)r bestaat niet." msgid "Foreign Key (type determined by related field)" -msgstr "Refererende sleutel (type wordt bepaalde door gerelateerde veld)" +msgstr "Refererende sleutel (type wordt bepaald door gerelateerde veld)" msgid "One-to-one relationship" -msgstr "Één-op-één relatie" +msgstr "Een-op-een-relatie" #, python-format msgid "%(from)s-%(to)s relationship" -msgstr "" +msgstr "%(from)s-%(to)s-relatie" #, python-format msgid "%(from)s-%(to)s relationships" -msgstr "" +msgstr "%(from)s-%(to)s-relaties" msgid "Many-to-many relationship" -msgstr "Veel-op-veel relatie" +msgstr "Veel-op-veel-relatie" #. Translators: If found as last label character, these punctuation #. characters will prevent the default label_suffix to be appended to the @@ -623,30 +662,30 @@ msgid "This field is required." msgstr "Dit veld is verplicht." msgid "Enter a whole number." -msgstr "Geef een geheel getal op." - -msgid "Enter a number." -msgstr "Geef een getal op." +msgstr "Voer een geheel getal in." msgid "Enter a valid date." -msgstr "Geef een geldige datum op." +msgstr "Voer een geldige datum in." msgid "Enter a valid time." -msgstr "Geef een geldige tijd op." +msgstr "Voer een geldige tijd in." msgid "Enter a valid date/time." -msgstr "Geef een geldige datum/tijd op." +msgstr "Voer een geldige datum/tijd in." msgid "Enter a valid duration." -msgstr "Geef een geldige tijdsduur op." +msgstr "Voer een geldige tijdsduur in." + +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Het aantal dagen moet tussen {min_days} en {max_days} liggen." msgid "No file was submitted. Check the encoding type on the form." msgstr "" -"Er was geen bestand verstuurd. Controleer het coderingstype van het " -"formulier." +"Er is geen bestand verstuurd. Controleer het coderingstype op het formulier." msgid "No file was submitted." -msgstr "Er was geen bestand verstuurd." +msgstr "Er is geen bestand verstuurd." msgid "The submitted file is empty." msgstr "Het verstuurde bestand is leeg." @@ -663,27 +702,27 @@ msgstr[1] "" "nu %(length)d)." msgid "Please either submit a file or check the clear checkbox, not both." -msgstr "Upload a.u.b. een bestand of vink de verwijder vink, niet allebei." +msgstr "Upload een bestand of vink het vakje Wissen aan, niet allebei." msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "" -"Bestand ongeldig. Het bestand dat is gegeven is geen afbeelding of is " -"beschadigd." +"Upload een geldige afbeelding. Het geüploade bestand is geen of een " +"beschadigde afbeelding." #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "Selecteer een geldige keuze. %(value)s is geen beschikbare keuze." msgid "Enter a list of values." -msgstr "Geef een lijst op met waardes." +msgstr "Voer een lijst met waarden in." msgid "Enter a complete value." -msgstr "Geef een volledige waarde op." +msgstr "Voer een volledige waarde in." msgid "Enter a valid UUID." -msgstr "Geef een geldige UUID op." +msgstr "Voer een geldige UUID in." #. Translators: This is the default suffix added to form field labels msgid ":" @@ -694,7 +733,7 @@ msgid "(Hidden field %(name)s) %(error)s" msgstr "(Verborgen veld %(name)s) %(error)s" msgid "ManagementForm data is missing or has been tampered with" -msgstr "ManagementForm gegevens missen of zijn mee geknoeid" +msgstr "ManagementForm-gegevens ontbreken, of er is mee geknoeid" #, python-format msgid "Please submit %d or fewer forms." @@ -705,8 +744,8 @@ msgstr[1] "Verstuur niet meer dan %d formulieren." #, python-format msgid "Please submit %d or more forms." msgid_plural "Please submit %d or more forms." -msgstr[0] "Geef alstublieft %d of meer formulieren op." -msgstr[1] "Geef alstublieft %d of meer formulieren op." +msgstr[0] "Verstuur %d of meer formulieren." +msgstr[1] "Verstuur %d of meer formulieren." msgid "Order" msgstr "Volgorde" @@ -716,34 +755,32 @@ msgstr "Verwijderen" #, python-format msgid "Please correct the duplicate data for %(field)s." -msgstr "Verbeter de dubbele gegevens voor %(field)s." +msgstr "Corrigeer de dubbele gegevens voor %(field)s." #, python-format msgid "Please correct the duplicate data for %(field)s, which must be unique." -msgstr "Verbeter de dubbele gegevens voor %(field)s, welke uniek moet zijn." +msgstr "Corrigeer de dubbele gegevens voor %(field)s, dat uniek moet zijn." #, 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 "" -"Verbeter de dubbele gegevens voor %(field_name)s, welke uniek moet zijn voor " +"Corrigeer de dubbele gegevens voor %(field_name)s, dat uniek moet zijn voor " "de %(lookup)s in %(date_field)s." msgid "Please correct the duplicate values below." -msgstr "Verbeter de dubbele waarden hieronder." +msgstr "Corrigeer de dubbele waarden hieronder." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "" -"De secundaire sleutel komt niet overeen met de primaire sleutel van de " -"bovenliggende instantie." +msgid "The inline value did not match the parent instance." +msgstr "De inline waarde komt niet overeen met de bovenliggende instantie." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Selecteer een geldige keuze. Deze keuze is niet beschikbaar." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" is geen geldige waarde voor een primaire sleutel." +msgid "\"%(pk)s\" is not a valid value." +msgstr "'%(pk)s' is geen geldige waarde." #, python-format msgid "" @@ -751,7 +788,10 @@ msgid "" "may be ambiguous or it may not exist." msgstr "" "%(datetime)s kon niet worden geïnterpreteerd in tijdzone " -"%(current_timezone)s. Waarschijnlijk is deze ambigu of bestaat niet." +"%(current_timezone)s; mogelijk is deze dubbelzinnig of bestaat deze niet." + +msgid "Clear" +msgstr "Wissen" msgid "Currently" msgstr "Huidige" @@ -759,9 +799,6 @@ msgstr "Huidige" msgid "Change" msgstr "Wijzigen" -msgid "Clear" -msgstr "Verwijder" - msgid "Unknown" msgstr "Onbekend" @@ -846,19 +883,19 @@ msgid "Tue" msgstr "di" msgid "Wed" -msgstr "woe" +msgstr "wo" msgid "Thu" -msgstr "don" +msgstr "do" msgid "Fri" -msgstr "vrij" +msgstr "vr" msgid "Sat" -msgstr "zat" +msgstr "za" msgid "Sun" -msgstr "zon" +msgstr "zo" msgid "January" msgstr "januari" @@ -1033,8 +1070,8 @@ msgstr "Dit is geen geldig IPv6-adres." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s…" msgid "or" msgstr "of" @@ -1047,7 +1084,7 @@ msgstr ", " msgid "%d year" msgid_plural "%d years" msgstr[0] "%d jaar" -msgstr[1] "%d jaren" +msgstr[1] "%d jaar" #, python-format msgid "%d month" @@ -1071,7 +1108,7 @@ msgstr[1] "%d dagen" msgid "%d hour" msgid_plural "%d hours" msgstr[0] "%d uur" -msgstr[1] "%d uren" +msgstr[1] "%d uur" #, python-format msgid "%d minute" @@ -1086,7 +1123,7 @@ msgid "Forbidden" msgstr "Verboden" msgid "CSRF verification failed. Request aborted." -msgstr "CSRF verificatie mislukt. Verzoek afgebroken." +msgstr "CSRF-verificatie mislukt. Aanvraag afgebroken." msgid "" "You are seeing this message because this HTTPS site requires a 'Referer " @@ -1094,68 +1131,59 @@ msgid "" "required for security reasons, to ensure that your browser is not being " "hijacked by third parties." msgstr "" -"U ziet deze melding omdat deze HTTPS website vereist dat uw webbrowser een " -"'Referer header' meestuurt, maar deze ontbreekt. Deze header is noodzakelijk " -"om veiligheidsredenen om er zeker van te zijn dat uw browser niet door " -"derden gekaapt wordt." +"U ziet deze melding, omdat deze HTTPS-website vereist dat uw webbrowser een " +"'Referer header' meestuurt, maar deze ontbreekt. Deze header is om " +"veiligheidsredenen vereist om er zeker van te zijn dat uw browser niet door " +"derden wordt gekaapt." 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 "" -"Als u uw webbrowser ingesteld heeft om geen 'Referer headers' mee te sturen, " -"schakelt u deze dan alstublieft weer in, op zijn minst voor deze website, " -"voor HTTPS verbindingen, of voor 'same-origin' verzoeken." +"Als u uw webbrowser hebt ingesteld om geen 'Referer headers' mee te sturen, " +"schakelt u deze dan weer in, op zijn minst voor deze website, of voor HTTPS-" +"verbindingen, of voor 'same-origin'-aanvragen." + +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." +msgstr "" +"Als u de tag gebruikt of de " +"header 'Referrer-Policy: no-referrer' opneemt, verwijder deze dan. De CSRF-" +"bescherming vereist de 'Referer'-header voor strenge referer-controle. Als u " +"bezorgd bent om privacy, gebruik dan alternatieven zoals voor koppelingen naar websites van derden." 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 "" -"U ziet deze melding omdat deze website vereist dat een CSRF cookie wordt " -"meegestuurd bij het versturen van formulieren. Dit cookie is vereist om " -"veiligheidsredenen om er zeker van te zijn dat uw browser niet door derden " -"gekaapt wordt." +"U ziet deze melding, omdat deze website vereist dat een CSRF-cookie wordt " +"meegestuurd bij het verzenden van formulieren. Dit cookie is om " +"veiligheidsredenen vereist om er zeker van te zijn dat uw browser niet door " +"derden wordt gekaapt." 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 "" -"Als u cookies in uw webbrowser heeft uitgeschakeld, schakel deze dan " -"alstublieft weer in, op zijn minst voor deze website of voor 'same-origin' " -"verzoeken." +"Als u cookies in uw webbrowser hebt uitgeschakeld, schakel deze dan weer in, " +"op zijn minst voor deze website, of voor 'same-origin'-aanvragen." msgid "More information is available with DEBUG=True." msgstr "Meer informatie is beschikbaar met DEBUG=True." -msgid "Welcome to Django" -msgstr "Welkom bij Django" - -msgid "It worked!" -msgstr "Het werkt!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Gefeliciteerd met uw eerste Django-aangedreven pagina." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"U heeft natuurlijk nog geen echt werk gedaan. Om verder te gaan start u uw " -"eerste app door python manage.py startapp [app_label] uit te " -"voeren." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"U ziet dit bericht omdat u DEBUG = True in uw Django settings " -"bestand heeft staan en u nog geen URLs geconfigureerd heeft. Aan het werk!" - msgid "No year specified" msgstr "Geen jaar opgegeven" +msgid "Date out of range" +msgstr "Datum buiten bereik" + msgid "No month specified" msgstr "Geen maand opgegeven" @@ -1174,12 +1202,12 @@ msgid "" "Future %(verbose_name_plural)s not available because %(class_name)s." "allow_future is False." msgstr "" -"Geen toekomstige %(verbose_name_plural)s beschikbaar omdat %(class_name)s." +"Geen toekomstige %(verbose_name_plural)s beschikbaar, omdat %(class_name)s." "allow_future de waarde False (Onwaar) heeft." #, python-format msgid "Invalid date string '%(datestr)s' given format '%(format)s'" -msgstr "Ongeldige datum tekst '%(datestr)s' op basis van formaat '%(format)s'" +msgstr "Ongeldige datumtekst '%(datestr)s' op basis van notatie '%(format)s'" #, python-format msgid "No %(verbose_name)s found matching the query" @@ -1187,7 +1215,8 @@ msgstr "Geen %(verbose_name)s gevonden die voldoet aan de query" msgid "Page is not 'last', nor can it be converted to an int." msgstr "" -"Pagina is niet 'last' en kan ook niet geconverteerd worden naar een int." +"Pagina is niet 'last' en kan ook niet naar een geheel getal worden " +"geconverteerd." #, python-format msgid "Invalid page (%(page_number)s): %(message)s" @@ -1199,12 +1228,56 @@ msgstr "" "Lege lijst en %(class_name)s.allow_empty heeft de waarde False (Onwaar)." msgid "Directory indexes are not allowed here." -msgstr "Directory overzicht is hier niet toegestaan" +msgstr "Directoryindexen zijn hier niet toegestaan." #, python-format msgid "\"%(path)s\" does not exist" -msgstr "\"%(path)s\" bestaat niet" +msgstr "'%(path)s' bestaat niet" #, python-format msgid "Index of %(directory)s" -msgstr "Overzicht van %(directory)s" +msgstr "Index van %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Uitgaveopmerkingen voor Django %(version)s " +"weergeven" + +msgid "The install worked successfully! Congratulations!" +msgstr "De installatie is gelukt! Gefeliciteerd!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"U ziet deze pagina, omdat uw instellingenbestand DEBUG=True bevat en u geen URL's hebt geconfigureerd." + +msgid "Django Documentation" +msgstr "Django-documentatie" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/nl/formats.py b/django/conf/locale/nl/formats.py index 581848f6e4a8..732af9817fc5 100644 --- a/django/conf/locale/nl/formats.py +++ b/django/conf/locale/nl/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j F Y' # '20 januari 2009' TIME_FORMAT = 'H:i' # '15:23' DATETIME_FORMAT = 'j F Y H:i' # '20 januari 2009 15:23' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # Monday (in Dutch 'maandag') # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d-%m-%Y', '%d-%m-%y', # '20-01-2009', '20-01-09' '%d/%m/%Y', '%d/%m/%y', # '20/01/2009', '20/01/09' diff --git a/django/conf/locale/nn/LC_MESSAGES/django.mo b/django/conf/locale/nn/LC_MESSAGES/django.mo index fa2036e99547..6ebecb9cf629 100644 Binary files a/django/conf/locale/nn/LC_MESSAGES/django.mo and b/django/conf/locale/nn/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/nn/LC_MESSAGES/django.po b/django/conf/locale/nn/LC_MESSAGES/django.po index 8ad79dd35584..abf9a418f80c 100644 --- a/django/conf/locale/nn/LC_MESSAGES/django.po +++ b/django/conf/locale/nn/LC_MESSAGES/django.po @@ -10,8 +10,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Norwegian Nynorsk (http://www.transifex.com/django/django/" "language/nn/)\n" @@ -297,6 +297,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "Oppgje ein gyldig verdi." @@ -309,6 +318,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "Oppgje ei gyldig e-postadresse." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -384,6 +394,15 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "og" @@ -695,15 +714,14 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Korriger dei dupliserte verdiane nedanfor." -msgid "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." msgstr "" -"Primærnøkkelen er ikkje den samme som foreldreinstansen sin primærnøkkel." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Velg eit gyldig valg. Valget er ikkje eit av dei tilgjengelege valga." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -714,15 +732,15 @@ msgstr "" "%(datetime)s kunne ikkje bli tolka i tidssona %(current_timezone)s. Verdien " "er anten tvetydig eller ugyldig." +msgid "Clear" +msgstr "Tøm" + msgid "Currently" msgstr "Noverande" msgid "Change" msgstr "Endre" -msgid "Clear" -msgstr "Tøm" - msgid "Unknown" msgstr "Ukjend" @@ -1062,6 +1080,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1076,28 +1102,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "Årstal ikkje spesifisert" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "Månad ikkje spesifisert" @@ -1148,3 +1158,41 @@ msgstr "«%(path)s» finst ikkje." #, python-format msgid "Index of %(directory)s" msgstr "Indeks for %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/nn/formats.py b/django/conf/locale/nn/formats.py index b2e654c1e148..b69ad3a6dd2a 100644 --- a/django/conf/locale/nn/formats.py +++ b/django/conf/locale/nn/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j. F Y' TIME_FORMAT = 'H:i' DATETIME_FORMAT = 'j. F Y H:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # Kept ISO formats as they are in first position DATE_INPUT_FORMATS = [ '%Y-%m-%d', '%d.%m.%Y', '%d.%m.%y', # '2006-10-25', '25.10.2006', '25.10.06' diff --git a/django/conf/locale/os/LC_MESSAGES/django.mo b/django/conf/locale/os/LC_MESSAGES/django.mo index 93fd284377a0..bf6863cf399b 100644 Binary files a/django/conf/locale/os/LC_MESSAGES/django.mo and b/django/conf/locale/os/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/os/LC_MESSAGES/django.po b/django/conf/locale/os/LC_MESSAGES/django.po index 9227333d7f64..4c2fdc6da95f 100644 --- a/django/conf/locale/os/LC_MESSAGES/django.po +++ b/django/conf/locale/os/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Ossetic (http://www.transifex.com/django/django/language/" "os/)\n" @@ -294,6 +294,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "Раст бӕрц бафысс." @@ -306,6 +315,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "Раст email адрис бафысс." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -391,6 +401,15 @@ msgstr[0] "" msgstr[1] "" "Дӕ хъус бадар цӕмӕй дӕсон стъӕлфы размӕ %(max)s цифрӕйӕ фылдӕр ма уа." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "ӕмӕ" @@ -707,15 +726,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Дӕ хорзӕхӕй, бындӕр цы дывӕр рардтӕ ис, уыдон сраст кӕн." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "Ӕддагон амонӕнӕн нӕ разынд хистӕры фыццаг амонӕн." +msgid "The inline value did not match the parent instance." +msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Раст фадат равзар. УКыцы фадат фадӕтты ӕхсӕн нӕй." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" фыццаг амонӕнӕн нӕ бӕззы." +msgid "\"%(pk)s\" is not a valid value." +msgstr "" #, python-format msgid "" @@ -725,15 +744,15 @@ msgstr "" "%(datetime)s нӕ бӕрӕг кӕны ацы рӕстӕджы тагы %(current_timezone)s; гӕнӕн ис " "бирӕнысанон у кӕнӕ та нӕй." +msgid "Clear" +msgstr "Сыгъдӕг" + msgid "Currently" msgstr "Ныр" msgid "Change" msgstr "Фӕивын" -msgid "Clear" -msgstr "Сыгъдӕг" - msgid "Unknown" msgstr "Ӕнӕбӕрӕг" @@ -1073,6 +1092,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1087,28 +1114,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "Аз амынд нӕ уыд" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "Мӕй амынд нӕ уыд" @@ -1159,3 +1170,41 @@ msgstr "\"%(path)s\" нӕй" #, python-format msgid "Index of %(directory)s" msgstr "%(directory)s-ы индекс" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/pa/LC_MESSAGES/django.mo b/django/conf/locale/pa/LC_MESSAGES/django.mo index 6184b9735351..f9ee02145369 100644 Binary files a/django/conf/locale/pa/LC_MESSAGES/django.mo and b/django/conf/locale/pa/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/pa/LC_MESSAGES/django.po b/django/conf/locale/pa/LC_MESSAGES/django.po index b480f4c6cbc1..cc7db80bfe3a 100644 --- a/django/conf/locale/pa/LC_MESSAGES/django.po +++ b/django/conf/locale/pa/LC_MESSAGES/django.po @@ -1,14 +1,14 @@ # This file is distributed under the same license as the Django package. # # Translators: -# A S Alam , 2011,2013,2015 +# A S Alam , 2011,2013,2015 # Jannis Leidel , 2011 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Panjabi (Punjabi) (http://www.transifex.com/django/django/" "language/pa/)\n" @@ -294,6 +294,15 @@ msgstr "ਸਥਿਰ ਫਾਈਲਾਂ" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "ਠੀਕ ਮੁੱਲ ਦਿਓ" @@ -306,6 +315,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "ਢੁੱਕਵਾਂ ਈਮੇਲ ਸਿਰਨਾਵਾਂ ਦਿਉ ਜੀ।" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -379,6 +389,15 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "ਅਤੇ" @@ -685,14 +704,14 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "" -msgid "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -701,15 +720,15 @@ msgid "" "may be ambiguous or it may not exist." msgstr "" +msgid "Clear" +msgstr "ਸਾਫ਼ ਕਰੋ" + msgid "Currently" msgstr "ਮੌਜੂਦਾ" msgid "Change" msgstr "ਬਦਲੋ" -msgid "Clear" -msgstr "ਸਾਫ਼ ਕਰੋ" - msgid "Unknown" msgstr "ਅਣਜਾਣ" @@ -1049,6 +1068,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1063,28 +1090,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "ਡਿਜਾਂਗੋ ਉੱਤੇ ਜੀ ਆਇਆਂ ਨੂੰ" - -msgid "It worked!" -msgstr "ਇਹ ਕੰਮ ਕਰਦਾ ਹੈ!" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "ਕੋਈ ਸਾਲ ਨਹੀਂ ਦਿੱਤਾ" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "ਕੋਈ ਮਹੀਨਾ ਨਹੀਂ ਦਿੱਤਾ" @@ -1133,3 +1144,41 @@ msgstr "\"%(path)s\" ਮੌਜੂਦ ਨਹੀਂ ਹੈ" #, python-format msgid "Index of %(directory)s" msgstr "%(directory)s ਦਾ ਇੰਡੈਕਸ" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/pl/LC_MESSAGES/django.mo b/django/conf/locale/pl/LC_MESSAGES/django.mo index 75624effb86f..ee95a2b0eee7 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 27a99062dc1a..3d593df6cf7a 100644 --- a/django/conf/locale/pl/LC_MESSAGES/django.po +++ b/django/conf/locale/pl/LC_MESSAGES/django.po @@ -8,13 +8,13 @@ # angularcircle, 2014 # Dariusz Paluch , 2015 # Jannis Leidel , 2011 -# Janusz Harkot , 2014-2015 +# Janusz Harkot , 2014-2015 # Kacper Krupa , 2013 # Karol , 2012 # konryd , 2011 # konryd , 2011 -# Łukasz Rekucki , 2011 -# m_aciek , 2016 +# Łukasz Rekucki (lqc) , 2011 +# m_aciek , 2016-2019 # m_aciek , 2015 # Michał Pasternak , 2013 # p , 2012 @@ -23,22 +23,24 @@ # Quadric , 2014 # Radek Czajka , 2013 # Radek Czajka , 2013 -# Roman Barczyński , 2012 +# Roman Barczyński, 2012 # sidewinder , 2014 +# Tomasz Kajtoch , 2016-2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-08-09 13:26+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-26 20:41+0000\n" "Last-Translator: m_aciek \n" "Language-Team: Polish (http://www.transifex.com/django/django/language/pl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pl\n" -"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " -"|| n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n" +"%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n" +"%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" msgid "Afrikaans" msgstr "afrykanerski" @@ -95,7 +97,7 @@ msgid "Australian English" msgstr "australijski angielski" msgid "British English" -msgstr "angielski brytyjski" +msgstr "brytyjski angielski" msgid "Esperanto" msgstr "esperanto" @@ -107,7 +109,7 @@ msgid "Argentinian Spanish" msgstr "hiszpański argentyński" msgid "Colombian Spanish" -msgstr "Kolumbijczyk hiszpański" +msgstr "hiszpański kolumbijski" msgid "Mexican Spanish" msgstr "hiszpański meksykański" @@ -160,6 +162,9 @@ msgstr "górnołużycki" msgid "Hungarian" msgstr "węgierski" +msgid "Armenian" +msgstr "ormiański" + msgid "Interlingua" msgstr "interlingua" @@ -181,6 +186,9 @@ msgstr "japoński" msgid "Georgian" msgstr "gruziński" +msgid "Kabyle" +msgstr "kabylski" + msgid "Kazakh" msgstr "kazachski" @@ -227,7 +235,7 @@ msgid "Dutch" msgstr "holenderski" msgid "Norwegian Nynorsk" -msgstr "norweski (Nynorsk)" +msgstr "norweski (nynorsk)" msgid "Ossetic" msgstr "osetyjski" @@ -242,7 +250,7 @@ msgid "Portuguese" msgstr "portugalski" msgid "Brazilian Portuguese" -msgstr "brazylijski portugalski" +msgstr "portugalski brazylijski" msgid "Romanian" msgstr "rumuński" @@ -316,6 +324,15 @@ msgstr "Pliki statyczne" msgid "Syndication" msgstr "Syndykacja treści" +msgid "That page number is not an integer" +msgstr "Ten numer strony nie jest liczbą całkowitą" + +msgid "That page number is less than 1" +msgstr "Ten numer strony jest mniejszy niż 1" + +msgid "That page contains no results" +msgstr "Ta strona nie zawiera wyników" + msgid "Enter a valid value." msgstr "Wpisz poprawną wartość." @@ -328,16 +345,19 @@ msgstr "Wprowadź poprawną liczbę całkowitą." msgid "Enter a valid email address." msgstr "Wprowadź poprawny adres email." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." -msgstr "To pole może zawierać jedynie litery, cyfry, podkreślenia i myślniki." +msgstr "" +"Wpisz poprawną uproszczoną nazwę zawierającą jedynie litery, cyfry, " +"podkreślenia i myślniki." msgid "" "Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or " "hyphens." msgstr "" -"Poprawna wartość składa się z poprawnych liter alfabetu unicode, cyfr, " -"podkreśleń lub myślników." +"Wpisz poprawną uproszczoną nazwę zawierającą jedynie litery Unicode, cyfry, " +"podkreślenia i myślniki." msgid "Enter a valid IPv4 address." msgstr "Wprowadź poprawny adres IPv4." @@ -379,6 +399,9 @@ msgstr[1] "" msgstr[2] "" "Upewnij się, że ta wartość ma przynajmniej %(limit_value)d znaków (obecnie " "ma %(show_value)d)." +msgstr[3] "" +"Upewnij się, że ta wartość ma przynajmniej %(limit_value)d znaków (obecnie " +"ma %(show_value)d)." #, python-format msgid "" @@ -396,13 +419,20 @@ msgstr[1] "" msgstr[2] "" "Upewnij się, że ta wartość ma co najwyżej %(limit_value)d znaków (obecnie ma " "%(show_value)d)." +msgstr[3] "" +"Upewnij się, że ta wartość ma co najwyżej %(limit_value)d znaków (obecnie ma " +"%(show_value)d)." + +msgid "Enter a number." +msgstr "Wpisz liczbę." #, 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] "Upewnij się, że jest nie więcej niż %(max)s cyfra." -msgstr[1] "Upewnij się, że jest nie więcej niż %(max)s cyfr." -msgstr[2] "Upewnij się, że jest nie więcej niż %(max)s cyfr." +msgstr[0] "Upewnij się, że łącznie nie ma więcej niż %(max)s cyfry." +msgstr[1] "Upewnij się, że łącznie nie ma więcej niż %(max)s cyfry." +msgstr[2] "Upewnij się, że łącznie nie ma więcej niż %(max)s cyfr." +msgstr[3] "Upewnij się, że łącznie nie ma więcej niż %(max)s cyfr." #, python-format msgid "Ensure that there are no more than %(max)s decimal place." @@ -412,6 +442,7 @@ msgstr[0] "" msgstr[1] "" "Upewnij się, że liczba ma nie więcej niż %(max)s cyfry po przecinku." msgstr[2] "Upewnij się, że liczba ma nie więcej niż %(max)s cyfr po przecinku." +msgstr[3] "Upewnij się, że liczba ma nie więcej niż %(max)s cyfr po przecinku." #, python-format msgid "" @@ -424,6 +455,19 @@ msgstr[1] "" "Upewnij się, że liczba ma nie więcej niż %(max)s cyfry przed przecinkiem." msgstr[2] "" "Upewnij się, że liczba ma nie więcej niż %(max)s cyfr przed przecinkiem." +msgstr[3] "" +"Upewnij się, że liczba ma nie więcej niż %(max)s cyfr przed przecinkiem." + +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Rozszerzenie pliku '%(extension)s' jest niedozwolone. Dozwolone rozszerzenia " +"to: '%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Znaki null są niedozwolone." msgid "and" msgstr "i" @@ -444,7 +488,7 @@ msgstr "To pole nie może być puste." #, python-format msgid "%(model_name)s with this %(field_label)s already exists." -msgstr "%(field_label)s już istnieje w %(model_name)s." +msgstr "Istnieje już %(model_name)s z tą wartością pola %(field_label)s." #. Translators: The 'lookup_type' is one of 'date', 'year' or 'month'. #. Eg: "Title must be unique for pub_date year" @@ -473,12 +517,16 @@ msgstr "Duża liczba całkowita (8 bajtów)" msgid "'%(value)s' value must be either True or False." msgstr "wartość '%(value)s' musi być True lub False." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "Wartość „%(value)s” musi być True, False lub None." + msgid "Boolean (Either True or False)" -msgstr "Wartość logiczna (True, False - prawda lub fałsz)" +msgstr "Wartość logiczna (True lub False – prawda lub fałsz)" #, python-format msgid "String (up to %(max_length)s)" -msgstr "Łańcuch (do %(max_length)s znaków)" +msgstr "Ciąg znaków (do %(max_length)s znaków)" msgid "Comma-separated integers" msgstr "Liczby całkowite rozdzielone przecinkami" @@ -488,14 +536,15 @@ msgid "" "'%(value)s' value has an invalid date format. It must be in YYYY-MM-DD " "format." msgstr "" -"wartość '%(value)s' ma nieprawidłowy format. Musi być w formacie YYYY-MM-DD." +"Wartość '%(value)s' ma nieprawidłowy format daty. Musi być ona w formacie " +"YYYY-MM-DD." #, python-format msgid "" "'%(value)s' value has the correct format (YYYY-MM-DD) but it is an invalid " "date." msgstr "" -"wartość '%(value)s' ma prawidłowy format (YYYY-MM-DD), ale jest " +"Wartość '%(value)s' ma prawidłowy format (YYYY-MM-DD), ale jest " "nieprawidłową datą." msgid "Date (without time)" @@ -506,15 +555,15 @@ msgid "" "'%(value)s' value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." "uuuuuu]][TZ] format." msgstr "" -"wartość '%(value)s' ma nieprawidłowy format. Musi być w formacie YYYY-MM-DD " -"HH:MM[:ss[.uuuuuu]][TZ]." +"Wartość '%(value)s' ma nieprawidłowy format. Musi być ona w formacie 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 "" -"wartość '%(value)s' ma prawidłowy format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]" +"Wartość '%(value)s' ma prawidłowy format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]" "[TZ]), ale jest nieprawidłową datą/godziną." msgid "Date (with time)" @@ -532,8 +581,8 @@ msgid "" "'%(value)s' value has an invalid format. It must be in [DD] [HH:[MM:]]ss[." "uuuuuu] format." msgstr "" -"wartość '%(value)s' ma błędny format. Poprawny to [DD] [HH:[MM:]]ss[.uuuuuu] " -"format." +"Wartość '%(value)s' ma błędny format. Poprawny format to [DD] [HH:[MM:]]ss[." +"uuuuuu]." msgid "Duration" msgstr "Czas trwania" @@ -559,10 +608,10 @@ msgstr "Adres IP" #, python-format msgid "'%(value)s' value must be either None, True or False." -msgstr "wartość '%(value)s' musi być None, True lub False." +msgstr "Wartość '%(value)s' musi być None, True lub False." msgid "Boolean (Either True, False or None)" -msgstr "Wartość logiczna (True, False, None - prawda, fałsz lub nic)" +msgstr "Wartość logiczna (True, False, None – prawda, fałsz lub nic)" msgid "Positive integer" msgstr "Dodatnia liczba całkowita" @@ -585,15 +634,15 @@ msgid "" "'%(value)s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " "format." msgstr "" -"wartość '%(value)s' ma nieprawidłowy format. Musi być w formacie HH:MM[:ss[." -"uuuuuu]]." +"Wartość '%(value)s' ma nieprawidłowy format. Musi być ona w formacie 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 "" -"wartość '%(value)s' ma prawidłowy format (HH:MM[:ss[.uuuuuu]]), ale jest " +"Wartość '%(value)s' ma prawidłowy format (HH:MM[:ss[.uuuuuu]]), ale jest " "nieprawidłową godziną." msgid "Time" @@ -609,6 +658,9 @@ msgstr "Dane w postaci binarnej" msgid "'%(value)s' is not a valid UUID." msgstr "Wartość '%(value)s' nie jest poprawnym UUID." +msgid "Universally unique identifier" +msgstr "Uniwersalnie unikalny identyfikator" + msgid "File" msgstr "Plik" @@ -634,7 +686,7 @@ msgid "%(from)s-%(to)s relationships" msgstr "powiązania %(from)s do %(to)s" msgid "Many-to-many relationship" -msgstr "Powiązanie wiele do wiele" +msgstr "Powiązanie wiele-do-wielu" #. Translators: If found as last label character, these punctuation #. characters will prevent the default label_suffix to be appended to the @@ -648,9 +700,6 @@ msgstr "To pole jest wymagane." msgid "Enter a whole number." msgstr "Wpisz liczbę całkowitą." -msgid "Enter a number." -msgstr "Wpisz liczbę." - msgid "Enter a valid date." msgstr "Wpisz poprawną datę." @@ -663,6 +712,10 @@ msgstr "Wpisz poprawną datę/godzinę." msgid "Enter a valid duration." msgstr "Wpisz poprawny czas trwania." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Liczba dni musi wynosić między {min_days} a {max_days}." + msgid "No file was submitted. Check the encoding type on the form." msgstr "Nie wysłano żadnego pliku. Sprawdź typ kodowania formularza." @@ -685,6 +738,9 @@ msgstr[1] "" msgstr[2] "" "Upewnij się, że nazwa pliku ma co najwyżej %(max)d znaków (obecnie ma " "%(length)d)." +msgstr[3] "" +"Upewnij się, że nazwa pliku ma co najwyżej %(max)d znaków (obecnie ma " +"%(length)d)." msgid "Please either submit a file or check the clear checkbox, not both." msgstr "Prześlij plik lub zaznacz by usunąć, ale nie oba na raz." @@ -693,13 +749,12 @@ msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "" -"Wgraj poprawny plik graficzny. Ten, który został wgrany, nie jest obrazem, " -"albo jest uszkodzony." +"Prześlij poprawny plik graficzny. Aktualnie przesłany plik nie jest " +"grafiką lub jest uszkodzony." #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." -msgstr "" -"Wybierz poprawną wartość. %(value)s nie jest jednym z dostępnych wyborów." +msgstr "Wybierz poprawną wartość. %(value)s nie jest żadną z dostępnych opcji." msgid "Enter a list of values." msgstr "Podaj listę wartości." @@ -727,6 +782,7 @@ msgid_plural "Please submit %d or fewer forms." msgstr[0] "Proszę wysłać %d lub mniej formularzy." msgstr[1] "Proszę wysłać %d lub mniej formularze." msgstr[2] "Proszę wysłać %d lub mniej formularzy." +msgstr[3] "Proszę wysłać %d lub mniej formularzy." #, python-format msgid "Please submit %d or more forms." @@ -734,9 +790,10 @@ msgid_plural "Please submit %d or more forms." msgstr[0] "Proszę wysłać %d lub więcej formularzy." msgstr[1] "Proszę wysłać %d lub więcej formularze." msgstr[2] "Proszę wysłać %d lub więcej formularzy." +msgstr[3] "Proszę wysłać %d lub więcej formularzy." msgid "Order" -msgstr "Porządek" +msgstr "Kolejność" msgid "Delete" msgstr "Usuń" @@ -747,7 +804,7 @@ msgstr "Popraw zduplikowane dane w %(field)s." #, python-format msgid "Please correct the duplicate data for %(field)s, which must be unique." -msgstr "Popraw zduplikowane dane w %(field)s, które wymaga unikalności." +msgstr "Popraw zduplikowane dane w %(field)s, które muszą być unikalne." #, python-format msgid "" @@ -760,15 +817,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Popraw poniższe zduplikowane wartości." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "Osadzony klucz obcy nie pasuje do klucza głównego obiektu rodzica." +msgid "The inline value did not match the parent instance." +msgstr "Wartość inline nie pasuje do obiektu rodzica." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Wybierz poprawną wartość. Podana nie jest jednym z dostępnych wyborów." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "„%(pk)s” nie jest poprawną wartością klucza głównego." +msgid "\"%(pk)s\" is not a valid value." +msgstr "„%(pk)s” nie jest poprawną wartością." #, python-format msgid "" @@ -778,15 +835,15 @@ msgstr "" "%(datetime)s nie może być interpretowany w strefie czasowej " "%(current_timezone)s; może być niejednoznaczne lub nie istnieć." +msgid "Clear" +msgstr "Wyczyść" + msgid "Currently" msgstr "Teraz" msgid "Change" msgstr "Zmień" -msgid "Clear" -msgstr "Wyczyść" - msgid "Unknown" msgstr "Nieznany" @@ -805,6 +862,7 @@ msgid_plural "%(size)d bytes" msgstr[0] "%(size)d bajt" msgstr[1] "%(size)d bajty" msgstr[2] "%(size)d bajtów" +msgstr[3] "%(size)d bajtów" #, python-format msgid "%s KB" @@ -926,53 +984,53 @@ msgid "jan" msgstr "sty" msgid "feb" -msgstr "luty" +msgstr "lut" msgid "mar" -msgstr "marz" +msgstr "mar" msgid "apr" -msgstr "kwie" +msgstr "kwi" msgid "may" msgstr "maj" msgid "jun" -msgstr "czerw" +msgstr "cze" msgid "jul" msgstr "lip" msgid "aug" -msgstr "sier" +msgstr "sie" msgid "sep" -msgstr "wrze" +msgstr "wrz" msgid "oct" msgstr "paź" msgid "nov" -msgstr "list" +msgstr "lis" msgid "dec" msgstr "gru" msgctxt "abbrev. month" msgid "Jan." -msgstr "Sty" +msgstr "Sty." msgctxt "abbrev. month" msgid "Feb." -msgstr "Lut" +msgstr "Lut." msgctxt "abbrev. month" msgid "March" -msgstr "Mar" +msgstr "Mar." msgctxt "abbrev. month" msgid "April" -msgstr "Kwi" +msgstr "Kwi." msgctxt "abbrev. month" msgid "May" @@ -980,27 +1038,27 @@ msgstr "Maj" msgctxt "abbrev. month" msgid "June" -msgstr "Cze" +msgstr "Cze." msgctxt "abbrev. month" msgid "July" -msgstr "Lip" +msgstr "Lip." msgctxt "abbrev. month" msgid "Aug." -msgstr "Sie" +msgstr "Sie." msgctxt "abbrev. month" msgid "Sept." -msgstr "Wrz" +msgstr "Wrz." msgctxt "abbrev. month" msgid "Oct." -msgstr "Paź" +msgstr "Paź." msgctxt "abbrev. month" msgid "Nov." -msgstr "Lis" +msgstr "Lis." msgctxt "abbrev. month" msgid "Dec." @@ -1059,8 +1117,8 @@ msgstr "To nie jest poprawny adres IPv6." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr " %(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s…" msgid "or" msgstr "lub" @@ -1075,6 +1133,7 @@ msgid_plural "%d years" msgstr[0] "%d rok" msgstr[1] "%d lata" msgstr[2] "%d lat" +msgstr[3] "%d lat" #, python-format msgid "%d month" @@ -1082,6 +1141,7 @@ msgid_plural "%d months" msgstr[0] "%d miesiąc" msgstr[1] "%d miesiące" msgstr[2] "%d miesięcy" +msgstr[3] "%d miesięcy" #, python-format msgid "%d week" @@ -1089,6 +1149,7 @@ msgid_plural "%d weeks" msgstr[0] "%d tydzień" msgstr[1] "%d tygodnie" msgstr[2] "%d tygodni" +msgstr[3] "%d tygodni" #, python-format msgid "%d day" @@ -1096,6 +1157,7 @@ msgid_plural "%d days" msgstr[0] "%d dzień" msgstr[1] "%d dni" msgstr[2] "%d dni" +msgstr[3] "%d dni" #, python-format msgid "%d hour" @@ -1103,6 +1165,7 @@ msgid_plural "%d hours" msgstr[0] "%d godzina" msgstr[1] "%d godziny" msgstr[2] "%d godzin" +msgstr[3] "%d godzin" #, python-format msgid "%d minute" @@ -1110,6 +1173,7 @@ msgid_plural "%d minutes" msgstr[0] "%d minuta" msgstr[1] "%d minuty" msgstr[2] "%d minut" +msgstr[3] "%d minut" msgid "0 minutes" msgstr "0 minut" @@ -1118,7 +1182,7 @@ msgid "Forbidden" msgstr "Dostęp zabroniony" msgid "CSRF verification failed. Request aborted." -msgstr "Niepoprawna weryfkacja CSRF zakończona. Żądanie zostało przerwane." +msgstr "Weryfikacja CSRF nie powiodła się. Żądanie zostało przerwane." msgid "" "You are seeing this message because this HTTPS site requires a 'Referer " @@ -1140,6 +1204,19 @@ msgstr "" "włącz je ponownie. Przynajmniej dla tej strony, połączeń HTTPS lub zapytań " "typu „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." +msgstr "" +"Jeśli używasz taga lub " +"umieszczasz nagłówek „Referrer-Policy: no-referrer”, prosimy je usunąć. " +"Ochrona przed atakami CSRF wymaga nagłówka „Referer”, aby wykonać ścisłe " +"sprawdzenie referera HTTP. Jeśli zależy ci na prywatności, użyj alternatyw " +"takich jak dla linków do stron osób trzecich." + 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 " @@ -1160,34 +1237,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Więcej informacji jest dostępnych po ustawieniu DEBUG=True." -msgid "Welcome to Django" -msgstr "Witaj w Django" - -msgid "It worked!" -msgstr "Zadziałało!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Gratulujemy utworzenia Twojej pierwszej strony w Django." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Oczywiście, nie zostało tu jeszcze dużo zrobione. Zacznij od utworzenia " -"swojej pierwszej aplikacji wpisując: python manage.py startapp " -"[app_label]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Widzisz ten komunikat, gdyż w ustawieniach Django masz ustawiony parametr " -"DEBUG = True oraz nie masz skonfigurowanych żadnych URL-i. " -"Zabieraj się do pracy!" - msgid "No year specified" msgstr "Nie określono roku" +msgid "Date out of range" +msgstr "Data poza zakresem" + msgid "No month specified" msgstr "Nie określono miesiąca" @@ -1199,7 +1254,7 @@ msgstr "Nie określono tygodnia" #, python-format msgid "No %(verbose_name_plural)s available" -msgstr "%(verbose_name_plural)s nie jest dostępny" +msgstr "%(verbose_name_plural)s nie są dostępne" #, python-format msgid "" @@ -1244,3 +1299,47 @@ msgstr "\" %(path)s \" nie istnieje" #, python-format msgid "Index of %(directory)s" msgstr "Zawartość %(directory)s " + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: framework WWW dla perfekcjonistów z deadline'ami." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Zobacz informacje o wydaniu dla Django " +"%(version)s" + +msgid "The install worked successfully! Congratulations!" +msgstr "Instalacja przebiegła pomyślnie! Gratulacje!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Widzisz tę stronę, ponieważ w swoim pliku ustawień masz DEBUG=True i nie skonfigurowałeś żadnych URL-i." + +msgid "Django Documentation" +msgstr "Dokumentacja Django" + +msgid "Topics, references, & how-to's" +msgstr "Przewodniki tematyczne, podręczniki i przewodniki „jak to zrobić”" + +msgid "Tutorial: A Polling App" +msgstr "Samouczek: Aplikacja ankietowa" + +msgid "Get started with Django" +msgstr "Pierwsze kroki z Django" + +msgid "Django Community" +msgstr "Społeczność Django" + +msgid "Connect, get help, or contribute" +msgstr "Nawiąż kontakt, uzyskaj pomoc lub wnieś swój wkład" diff --git a/django/conf/locale/pl/formats.py b/django/conf/locale/pl/formats.py index 9cc17764947f..6cddf75ae1a0 100644 --- a/django/conf/locale/pl/formats.py +++ b/django/conf/locale/pl/formats.py @@ -1,21 +1,18 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j E Y' TIME_FORMAT = 'H:i' DATETIME_FORMAT = 'j E Y H:i' YEAR_MONTH_FORMAT = 'F Y' -MONTH_DAY_FORMAT = 'j F' +MONTH_DAY_FORMAT = 'j E' SHORT_DATE_FORMAT = 'd-m-Y' SHORT_DATETIME_FORMAT = 'd-m-Y H:i' FIRST_DAY_OF_WEEK = 1 # Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d.%m.%Y', '%d.%m.%y', # '25.10.2006', '25.10.06' '%y-%m-%d', # '06-10-25' diff --git a/django/conf/locale/pt/LC_MESSAGES/django.mo b/django/conf/locale/pt/LC_MESSAGES/django.mo index 4299f23a5822..a5f444ac98ad 100644 Binary files a/django/conf/locale/pt/LC_MESSAGES/django.mo and b/django/conf/locale/pt/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/pt/LC_MESSAGES/django.po b/django/conf/locale/pt/LC_MESSAGES/django.po index a64317eb3b2c..790ee57e0726 100644 --- a/django/conf/locale/pt/LC_MESSAGES/django.po +++ b/django/conf/locale/pt/LC_MESSAGES/django.po @@ -7,16 +7,16 @@ # Jannis Leidel , 2011 # José Durães , 2014 # jorgecarleitao , 2014-2015 -# Nuno Mariz , 2011-2013,2015-2016 +# Nuno Mariz , 2011-2013,2015-2018 # Paulo Köch , 2011 # Raúl Pedro Fernandes Santos, 2014 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 00:44+0000\n" +"Last-Translator: Ramiro Morales\n" "Language-Team: Portuguese (http://www.transifex.com/django/django/language/" "pt/)\n" "MIME-Version: 1.0\n" @@ -68,7 +68,7 @@ msgid "German" msgstr "Alemão" msgid "Lower Sorbian" -msgstr "" +msgstr "Sorbedo inferior" msgid "Greek" msgstr "Grego" @@ -140,11 +140,14 @@ msgid "Croatian" msgstr "Croata" msgid "Upper Sorbian" -msgstr "" +msgstr "Sorbedo superior" msgid "Hungarian" msgstr "Húngaro" +msgid "Armenian" +msgstr "" + msgid "Interlingua" msgstr "Interlíngua" @@ -166,6 +169,9 @@ msgstr "Japonês" msgid "Georgian" msgstr "Georgiano" +msgid "Kabyle" +msgstr "Kabyle" + msgid "Kazakh" msgstr "Cazaque" @@ -203,7 +209,7 @@ msgid "Burmese" msgstr "Birmanês" msgid "Norwegian Bokmål" -msgstr "" +msgstr "Norueguês Bokmål" msgid "Nepali" msgstr "Nepali" @@ -301,6 +307,15 @@ msgstr "Ficheiros Estáticos" msgid "Syndication" msgstr "Syndication" +msgid "That page number is not an integer" +msgstr "Esse número de página não é um número inteiro" + +msgid "That page number is less than 1" +msgstr "Esse número de página é inferior a 1" + +msgid "That page contains no results" +msgstr "Essa página não contém resultados" + msgid "Enter a valid value." msgstr "Introduza um valor válido." @@ -313,6 +328,7 @@ msgstr "Introduza um número inteiro válido." msgid "Enter a valid email address." msgstr "Introduza um endereço de e-mail válido." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -377,6 +393,9 @@ msgstr[1] "" "Garanta que este valor tenha no máximo %(limit_value)d caracteres (tem " "%(show_value)d)." +msgid "Enter a number." +msgstr "Introduza um número." + #, 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." @@ -397,6 +416,17 @@ msgid_plural "" msgstr[0] "Garanta que não tem mais de %(max)s dígito antes do ponto decimal." msgstr[1] "Garanta que não tem mais de %(max)s dígitos antes do ponto decimal." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"A extensão do ficheiro '%(extension)s' não é permitida. As extensões " +"permitidas são: '%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Não são permitidos caracteres nulos." + msgid "and" msgstr "e" @@ -444,6 +474,10 @@ msgstr "Inteiro grande (8 byte)" msgid "'%(value)s' value must be either True or False." msgstr "O valor '%(value)s' deve ser True ou False." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "O valor '%(value)s' deve ser True, False ou None." + msgid "Boolean (Either True or False)" msgstr "Boolean (Pode ser True ou False)" @@ -581,6 +615,9 @@ msgstr "Dados binários simples" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' não é um UUID válido." +msgid "Universally unique identifier" +msgstr "" + msgid "File" msgstr "Ficheiro" @@ -599,11 +636,11 @@ msgstr "Relação de um-para-um" #, python-format msgid "%(from)s-%(to)s relationship" -msgstr "" +msgstr "Relação de %(from)s-%(to)s" #, python-format msgid "%(from)s-%(to)s relationships" -msgstr "" +msgstr "Relações de %(from)s-%(to)s" msgid "Many-to-many relationship" msgstr "Relação de muitos-para-muitos" @@ -620,9 +657,6 @@ msgstr "Este campo é obrigatório." msgid "Enter a whole number." msgstr "Introduza um número inteiro." -msgid "Enter a number." -msgstr "Introduza um número." - msgid "Enter a valid date." msgstr "Introduza uma data válida." @@ -635,6 +669,10 @@ msgstr "Introduza uma data/hora válida." msgid "Enter a valid duration." msgstr "Introduza uma duração válida." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "O número de dias deve ser entre {min_days} e {max_days}." + msgid "No file was submitted. Check the encoding type on the form." msgstr "" "Nenhum ficheiro foi submetido. Verifique o tipo de codificação do formulário." @@ -730,18 +768,16 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Por favor corrija os valores duplicados abaixo." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "" -"A chave estrangeira em linha não coincide com a chave primária na instância " -"pai." +msgid "The inline value did not match the parent instance." +msgstr "O valor em linha não corresponde à instância pai." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" "Selecione uma opção válida. Esse valor não se encontra opções disponíveis." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" não é um valor válido para uma chave primária." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" não é um valor válido." #, python-format msgid "" @@ -751,15 +787,15 @@ msgstr "" "%(datetime)s não pode ser interpretada de fuso horário %(current_timezone)s; " "pode ser ambígua ou não podem existir." +msgid "Clear" +msgstr "Limpar" + msgid "Currently" msgstr "Atualmente" msgid "Change" msgstr "Modificar" -msgid "Clear" -msgstr "Limpar" - msgid "Unknown" msgstr "Desconhecido" @@ -1031,8 +1067,8 @@ msgstr "Este não é um endereço IPv6 válido." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "" msgid "or" msgstr "ou" @@ -1106,6 +1142,19 @@ msgstr "" "favor active-os novamente, pelo menos para este site, ou para ligações " "HTTPS, ou para pedidos '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." +msgstr "" +"Se está a usar a tag ou a " +"incluir o cabeçalho 'Referrer-Policy: no-referrer', por favor remova. A " +"proteção CSRF requer o cabeçalho 'Referer' fazer uma verificação rigorosa do " +"referente. Se está preocupado com a privacidade, use alternativas como para links para sites de terceiros." + 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 " @@ -1125,32 +1174,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Está disponível mais informação com DEBUG=True." -msgid "Welcome to Django" -msgstr "Bem-vindo ao Django" - -msgid "It worked!" -msgstr "Funcionou!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Parabéns pela sua primeira página em Django." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Claro que ainda não fizeste qualquer trabalho. Cria a tua primeira aplicação " -"correndo python manage.py startapp [app_label]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Estás a ver esta mensagem porque tens DEBUG = True no ficheiro " -"settings do Django e ainda não configuraste nenhum URL. Toca a trabalhar!" - msgid "No year specified" msgstr "Nenhum ano especificado" +msgid "Date out of range" +msgstr "Data fora do alcance" + msgid "No month specified" msgstr "Nenhum mês especificado" @@ -1201,3 +1230,48 @@ msgstr "\"%(path)s\" não existe" #, python-format msgid "Index of %(directory)s" msgstr "Índice de %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: the Web framework for perfectionists with deadlines." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Visualizar notas de lançamento do Django " +"%(version)s" + +msgid "The install worked successfully! Congratulations!" +msgstr "A instalação funcionou com sucesso! Parabéns!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Está a visualizar esta página porque tem DEBUG=True no seu ficheiro settings do Django e não " +"configurou nenhum URLs." + +msgid "Django Documentation" +msgstr "Documentação do Django" + +msgid "Topics, references, & how-to's" +msgstr "Tópicos, referências, & how-to's" + +msgid "Tutorial: A Polling App" +msgstr "Tutorial: A Polling App" + +msgid "Get started with Django" +msgstr "Comece com o Django" + +msgid "Django Community" +msgstr "Comunidade Django" + +msgid "Connect, get help, or contribute" +msgstr "Conecte-se, obtenha ajuda ou contribua" diff --git a/django/conf/locale/pt/formats.py b/django/conf/locale/pt/formats.py index 143351c0cde3..5789cd8a5b09 100644 --- a/django/conf/locale/pt/formats.py +++ b/django/conf/locale/pt/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = r'j \d\e F \d\e Y' TIME_FORMAT = 'H:i' DATETIME_FORMAT = r'j \d\e F \d\e Y à\s H:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 0 # Sunday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # Kept ISO formats as they are in first position DATE_INPUT_FORMATS = [ '%Y-%m-%d', '%d/%m/%Y', '%d/%m/%y', # '2006-10-25', '25/10/2006', '25/10/06' diff --git a/django/conf/locale/pt_BR/LC_MESSAGES/django.mo b/django/conf/locale/pt_BR/LC_MESSAGES/django.mo index 5ccf898b8da5..ef046ada4fa5 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 146d73366b9b..86fafd78dd13 100644 --- a/django/conf/locale/pt_BR/LC_MESSAGES/django.po +++ b/django/conf/locale/pt_BR/LC_MESSAGES/django.po @@ -2,28 +2,37 @@ # # Translators: # Allisson Azevedo , 2014 +# amcorreia , 2018 # andrewsmedina , 2014-2015 +# Arthur Silva , 2017 # bruno.devpod , 2014 -# Carlos E C Leite , 2016 -# FilipeCifali , 2016 +# Camilo B. Moreira , 2017 +# Carlos Leite , 2016 +# Filipe Cifali Stangler , 2016 # dudanogueira , 2012 +# dudanogueira , 2019 # Elyézer Rezende , 2013 # Fábio C. Barrionuevo da Luz , 2014-2015 # Felipe Rodrigues , 2016 # Gladson , 2013 -# Guilherme Gondim, 2011-2014 +# semente, 2011-2014 +# Igor Cavalcante , 2017 # Jannis Leidel , 2011 # Lucas Infante , 2015 +# Luiz Boaretto , 2017 +# Marcelo Moro Brondani , 2018 # Sandro , 2011 # Sergio Garcia , 2015 +# Tânia Andrea , 2017 # Wiliam Souza , 2015 +# Xico Petry , 2018 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-07-04 00:25+0000\n" -"Last-Translator: andrewsmedina \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-02-18 17:13+0000\n" +"Last-Translator: dudanogueira \n" "Language-Team: Portuguese (Brazil) (http://www.transifex.com/django/django/" "language/pt_BR/)\n" "MIME-Version: 1.0\n" @@ -152,6 +161,9 @@ msgstr "Sorábio Alto" msgid "Hungarian" msgstr "Húngaro" +msgid "Armenian" +msgstr "Armênio" + msgid "Interlingua" msgstr "Interlíngua" @@ -173,6 +185,9 @@ msgstr "Japonês" msgid "Georgian" msgstr "Georgiano" +msgid "Kabyle" +msgstr "Cabila" + msgid "Kazakh" msgstr "Cazaque" @@ -308,6 +323,15 @@ msgstr "Arquivos Estáticos" msgid "Syndication" msgstr "Syndication" +msgid "That page number is not an integer" +msgstr "Esse número de página não é um número inteiro" + +msgid "That page number is less than 1" +msgstr "Esse número de página é menor que 1" + +msgid "That page contains no results" +msgstr "Essa página não contém resultados" + msgid "Enter a valid value." msgstr "Informe um valor válido." @@ -320,6 +344,7 @@ msgstr "Insira um número inteiro válido." msgid "Enter a valid email address." msgstr "Informe um endereço de email válido." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -385,6 +410,9 @@ msgstr[1] "" "Certifique-se de que o valor tenha no máximo %(limit_value)d caracteres (ele " "possui %(show_value)d)." +msgid "Enter a number." +msgstr "Informe um número." + #, 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." @@ -408,6 +436,17 @@ msgstr[1] "" "Certifique-se de que não tenha mais de %(max)s dígitos antes do ponto " "decimal." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"A extensão de arquivo '%(extension)s' não é permitida. As extensões " +"permitidas são: '%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Caracteres nulos não são permitidos." + msgid "and" msgstr "e" @@ -455,6 +494,10 @@ msgstr "Inteiro grande (8 byte)" msgid "'%(value)s' value must be either True or False." msgstr "'%(value)s' valor deve ser True ou False." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "O valor '%(value)s' deve ser True, False ou Nenhum." + msgid "Boolean (Either True or False)" msgstr "Booleano (Verdadeiro ou Falso)" @@ -592,6 +635,9 @@ msgstr "Dados binários bruto" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' não é um UUID válido." +msgid "Universally unique identifier" +msgstr "Identificador único universal" + msgid "File" msgstr "Arquivo" @@ -631,9 +677,6 @@ msgstr "Este campo é obrigatório." msgid "Enter a whole number." msgstr "Informe um número inteiro." -msgid "Enter a number." -msgstr "Informe um número." - msgid "Enter a valid date." msgstr "Informe uma data válida." @@ -646,6 +689,10 @@ msgstr "Informe uma data/hora válida." msgid "Enter a valid duration." msgstr "Insira uma duração válida." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "O número de dias deve ser entre {min_days} e {max_days}." + msgid "No file was submitted. Check the encoding type on the form." msgstr "Nenhum arquivo enviado. Verifique o tipo de codificação do formulário." @@ -738,17 +785,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Por favor, corrija os valores duplicados abaixo." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "" -"A chave estrangeira no inline não coincide com a chave primária na instância " -"pai." +msgid "The inline value did not match the parent instance." +msgstr "O valor na linha não correspondeu com a instância pai." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Faça uma escolha válida. Sua escolha não é uma das disponíveis." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" não é um valor válido para uma chave primária." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" não é um valor válido." #, python-format msgid "" @@ -758,15 +803,15 @@ msgstr "" " %(datetime)s não pôde ser interpretado no fuso horário " "%(current_timezone)s; pode estar ambíguo ou pode não existir." +msgid "Clear" +msgstr "Limpar" + msgid "Currently" msgstr "Atualmente" msgid "Change" msgstr "Modificar" -msgid "Clear" -msgstr "Limpar" - msgid "Unknown" msgstr "Desconhecido" @@ -1038,8 +1083,8 @@ msgstr "Este não é um endereço IPv6 válido." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr " %(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "" msgid "or" msgstr "ou" @@ -1113,6 +1158,19 @@ msgstr "" "'Referer', por favor ative-os novamente, pelo menos para este site, ou para " "conexões HTTPS ou para pedidos de 'mesma origem'." +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." +msgstr "" +"Se você estiver usando a tag ou incluindo o cabeçalho \"Referrer-Policy: no-referrer\", remova-os. A " +"proteção contra CSRF requer que o cabeçalho 'Referer' faça uma verificação " +"rigorosa do referenciador. Se você estiver preocupado com a privacidade, use " +"alternativas para links para sites de terceiros." + 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 " @@ -1133,34 +1191,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Mais informações estão disponíveis com DEBUG=True." -msgid "Welcome to Django" -msgstr "Bem-vindo ao Django" - -msgid "It worked!" -msgstr "Funcionou!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Parabéns pela sua primeira página feita com Django." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"É claro que você realmente não fez qualquer trabalho ainda. O próximo passo " -"é iniciar o seu primeiro app rodando python manage.py startapp " -"[app_label] ." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Você está vendo esta mensagem, porque você tem DEBUG = True no " -"seu arquivo de configurações do Django e você não configurou nenhum URLs. " -"Vamos ao trabalho!" - msgid "No year specified" msgstr "Ano não especificado" +msgid "Date out of range" +msgstr "Data fora de alcance" + msgid "No month specified" msgstr "Mês não especificado" @@ -1212,3 +1248,48 @@ msgstr "\"%(path)s\" não existe" #, python-format msgid "Index of %(directory)s" msgstr "Índice de %(directory)s " + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: o framework web para perfeccionistas com prazo de entrega." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Ver as notas de lançamento do Django " +"%(version)s" + +msgid "The install worked successfully! Congratulations!" +msgstr "A instalação foi com sucesso! Parabéns!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Você está vendo esta página pois possui DEBUG=True no seu arquivo de configurações e não configurou nenhuma " +"URL." + +msgid "Django Documentation" +msgstr "Documentação do Django" + +msgid "Topics, references, & how-to's" +msgstr "Tópicos, referências, & how-to's" + +msgid "Tutorial: A Polling App" +msgstr "Tutorial: Um aplicativo de votação" + +msgid "Get started with Django" +msgstr "Comece a usar Django" + +msgid "Django Community" +msgstr "Comunidade Django" + +msgid "Connect, get help, or contribute" +msgstr "Conecte-se, obtenha ajuda ou contribua" diff --git a/django/conf/locale/pt_BR/formats.py b/django/conf/locale/pt_BR/formats.py index 9f728783c58a..36005808e944 100644 --- a/django/conf/locale/pt_BR/formats.py +++ b/django/conf/locale/pt_BR/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = r'j \d\e F \d\e Y' TIME_FORMAT = 'H:i' DATETIME_FORMAT = r'j \d\e F \d\e Y à\s H:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 0 # Sunday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d/%m/%Y', '%d/%m/%y', # '25/10/2006', '25/10/06' # '%d de %b de %Y', '%d de %b, %Y', # '25 de Out de 2006', '25 Out, 2006' diff --git a/django/conf/locale/ro/LC_MESSAGES/django.mo b/django/conf/locale/ro/LC_MESSAGES/django.mo index 2aeaf003225e..62de5aa72130 100644 Binary files a/django/conf/locale/ro/LC_MESSAGES/django.mo and b/django/conf/locale/ro/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/ro/LC_MESSAGES/django.po b/django/conf/locale/ro/LC_MESSAGES/django.po index f87ec6ca8442..cdb621e9377f 100644 --- a/django/conf/locale/ro/LC_MESSAGES/django.po +++ b/django/conf/locale/ro/LC_MESSAGES/django.po @@ -1,20 +1,22 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Abel Radac , 2017 +# Bogdan Mateescu, 2018-2019 # mihneasim , 2011 -# Daniel Ursache-Dogariu , 2011 +# Daniel Ursache-Dogariu, 2011 # Denis Darii , 2011,2014 # Ionel Cristian Mărieș , 2012 # Jannis Leidel , 2011 # Răzvan Ionescu , 2015 -# Razvan Stefanescu , 2016 +# Razvan Stefanescu , 2016-2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 11:00+0000\n" +"Last-Translator: Bogdan Mateescu\n" "Language-Team: Romanian (http://www.transifex.com/django/django/language/" "ro/)\n" "MIME-Version: 1.0\n" @@ -67,7 +69,7 @@ msgid "German" msgstr "Germană" msgid "Lower Sorbian" -msgstr "" +msgstr "Soraba Inferioară" msgid "Greek" msgstr "Greacă" @@ -139,11 +141,14 @@ msgid "Croatian" msgstr "Croată" msgid "Upper Sorbian" -msgstr "" +msgstr "Soraba Superioară" msgid "Hungarian" msgstr "Ungară" +msgid "Armenian" +msgstr "Armeană" + msgid "Interlingua" msgstr "Interlingua" @@ -165,6 +170,9 @@ msgstr "Japoneză" msgid "Georgian" msgstr "Georgiană" +msgid "Kabyle" +msgstr "Kabyle" + msgid "Kazakh" msgstr "Kazahă" @@ -202,7 +210,7 @@ msgid "Burmese" msgstr "Burmeză" msgid "Norwegian Bokmål" -msgstr "" +msgstr "Norvegiana modernă" msgid "Nepali" msgstr "Nepaleză" @@ -300,6 +308,15 @@ msgstr "Fișiere statice" msgid "Syndication" msgstr "Sindicalizare" +msgid "That page number is not an integer" +msgstr "Numărul de pagină nu este întreg" + +msgid "That page number is less than 1" +msgstr "Numărul de pagină este mai mic decât 1" + +msgid "That page contains no results" +msgstr "Această pagină nu conține nici un rezultat" + msgid "Enter a valid value." msgstr "Introduceți o valoare validă." @@ -310,8 +327,9 @@ msgid "Enter a valid integer." msgstr "Introduceți un întreg valid." msgid "Enter a valid email address." -msgstr "Introduceți o adresă de email validaă." +msgstr "Introduceți o adresă de email validă." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -366,8 +384,8 @@ msgstr[1] "" "Asigurați-vă că această valoare are cel puțin %(limit_value)d caractere (are " "%(show_value)d)." msgstr[2] "" -"Asigurați-vă că această valoare are cel puțin %(limit_value)d caractere (are " -"%(show_value)d)." +"Asigurați-vă că această valoare are cel puțin %(limit_value)d de caractere " +"(are %(show_value)d)." #, python-format msgid "" @@ -383,22 +401,25 @@ msgstr[1] "" "Asigurați-vă că această valoare are cel mult %(limit_value)d caractere (are " "%(show_value)d)." msgstr[2] "" -"Asigurați-vă că această valoare are cel mult %(limit_value)d caractere (are " -"%(show_value)d)." +"Asigurați-vă că această valoare are cel mult %(limit_value)d de caractere " +"(are %(show_value)d)." + +msgid "Enter a number." +msgstr "Introduceţi un număr." #, 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] "Asigurați-vă că nu este mai mult de %(max)s cifră în total." msgstr[1] "Asigurați-vă că nu sunt mai mult de %(max)s cifre în total." -msgstr[2] "Asigurați-vă că nu sunt mai mult de %(max)s cifre în total." +msgstr[2] "Asigurați-vă că nu sunt mai mult de %(max)s de cifre în total." #, 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] "Asigurați-vă că nu este mai mult de %(max)s zecimală în total." msgstr[1] "Asigurați-vă că nu sunt mai mult de %(max)s zecimale în total." -msgstr[2] "Asigurați-vă că nu sunt mai mult de %(max)s zecimale în total." +msgstr[2] "Asigurați-vă că nu sunt mai mult de %(max)s de zecimale în total." #, python-format msgid "" @@ -410,7 +431,19 @@ msgstr[0] "" msgstr[1] "" "Asigurați-vă că nu sunt mai mult de %(max)s cifre înainte de punctul zecimal." msgstr[2] "" -"Asigurați-vă că nu sunt mai mult de %(max)s cifre înainte de punctul zecimal." +"Asigurați-vă că nu sunt mai mult de %(max)s de cifre înainte de punctul " +"zecimal." + +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Extensia '%(extension)s' nu este permisă. Extensiile permise sunt: " +"'%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Caracterele Null nu sunt permise." msgid "and" msgstr "și" @@ -460,6 +493,10 @@ msgstr "Întreg mare (8 octeți)" msgid "'%(value)s' value must be either True or False." msgstr "'%(value)s' trebuie să fie True sau False." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "'%(value)s' valoarea trebuie să fie True, False, sau None." + msgid "Boolean (Either True or False)" msgstr "Boolean (adevărat sau fals)" @@ -595,6 +632,9 @@ msgstr "Date binare brute" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' nu este un UUID valid." +msgid "Universally unique identifier" +msgstr "Identificator unic universal" + msgid "File" msgstr "Fișier" @@ -613,11 +653,11 @@ msgstr "Relaţie unul-la-unul" #, python-format msgid "%(from)s-%(to)s relationship" -msgstr "" +msgstr "Relație %(from)s-%(to)s" #, python-format msgid "%(from)s-%(to)s relationships" -msgstr "" +msgstr "Relații %(from)s-%(to)s" msgid "Many-to-many relationship" msgstr "Relație multe-la-multe" @@ -634,9 +674,6 @@ msgstr "Acest câmp este obligatoriu." msgid "Enter a whole number." msgstr "Introduceţi un număr întreg." -msgid "Enter a number." -msgstr "Introduceţi un număr." - msgid "Enter a valid date." msgstr "Introduceți o dată validă." @@ -649,6 +686,10 @@ msgstr "Introduceți o dată/oră validă." msgid "Enter a valid duration." msgstr "Introduceți o durată validă." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Numărul de zile trebuie să fie cuprins între {min_days} și {max_days}." + msgid "No file was submitted. Check the encoding type on the form." msgstr "Nici un fișier nu a fost trimis. Verificați tipul fișierului." @@ -663,13 +704,13 @@ 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] "" -"Verificați că numele fișierului are cel mult %(max)d caractere (are " +"Asigurați-vă că numele fișierului are cel mult %(max)d caracter (are " "%(length)d)." msgstr[1] "" -"Verificați că numele fișierului are cel mult %(max)d caractere (are " +"Asigurați-vă că numele fișierului are cel mult %(max)d caractere (are " "%(length)d)." msgstr[2] "" -"Verificați că numele fișierului are cel mult %(max)d caractere (are " +"Asigurați-vă că numele fișierului are cel mult %(max)d de caractere (are " "%(length)d)." msgid "Please either submit a file or check the clear checkbox, not both." @@ -713,14 +754,14 @@ msgid "Please submit %d or fewer forms." msgid_plural "Please submit %d or fewer forms." msgstr[0] "Trimiteți maxim %d formular." msgstr[1] "Trimiteți maxim %d formulare." -msgstr[2] "Trimiteți maxim %d formulare." +msgstr[2] "Trimiteți maxim %d de formulare." #, python-format msgid "Please submit %d or more forms." msgid_plural "Please submit %d or more forms." msgstr[0] "Trimiteți minim %d formular." msgstr[1] "Trimiteți minim %d formulare." -msgstr[2] "Trimiteți minim %d formulare." +msgstr[2] "Trimiteți minim %d de formulare." msgid "Order" msgstr "Ordine" @@ -747,9 +788,8 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Corectaţi valorile duplicate de mai jos." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "" -"Foreign key-ul inline nu se potrivește cu cheia primară a istanței părinte." +msgid "The inline value did not match the parent instance." +msgstr "Valoarea în linie nu s-a potrivit cu instanța părinte." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" @@ -757,8 +797,8 @@ msgstr "" "disponibile." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" nu este o cheie primară validă." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" nu este o valoare validă." #, python-format msgid "" @@ -768,15 +808,15 @@ msgstr "" "%(datetime)s nu poate fi interpetat in fusul orar %(current_timezone)s; este " "ambiguu sau nu există." +msgid "Clear" +msgstr "Șterge" + msgid "Currently" msgstr "În prezent" msgid "Change" msgstr "Schimbă" -msgid "Clear" -msgstr "Șterge" - msgid "Unknown" msgstr "Necunoscut" @@ -792,9 +832,9 @@ msgstr "da,nu,poate" #, python-format msgid "%(size)d byte" msgid_plural "%(size)d bytes" -msgstr[0] "%(size)d byte" -msgstr[1] "%(size)d bytes" -msgstr[2] "%(size)d bytes" +msgstr[0] "%(size)d octet" +msgstr[1] "%(size)d octeţi" +msgstr[2] "%(size)d de octeţi" #, python-format msgid "%s KB" @@ -1049,8 +1089,8 @@ msgstr "Aceasta nu este o adresă IPv6 validă." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s…" msgid "or" msgstr "sau" @@ -1064,42 +1104,42 @@ msgid "%d year" msgid_plural "%d years" msgstr[0] "%d an" msgstr[1] "%d ani" -msgstr[2] "%d ani" +msgstr[2] "%d de ani" #, python-format msgid "%d month" msgid_plural "%d months" msgstr[0] "%d lună" msgstr[1] "%d luni" -msgstr[2] "%d luni" +msgstr[2] "%d de luni" #, python-format msgid "%d week" msgid_plural "%d weeks" msgstr[0] "%d săptămână" msgstr[1] "%d săptămâni" -msgstr[2] "%d săptămâni" +msgstr[2] "%d de săptămâni" #, python-format msgid "%d day" msgid_plural "%d days" msgstr[0] "%d zi" msgstr[1] "%d zile" -msgstr[2] "%d zile" +msgstr[2] "%d de zile" #, python-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "%d oră" msgstr[1] "%d ore" -msgstr[2] "%d ore" +msgstr[2] "%d de ore" #, python-format msgid "%d minute" msgid_plural "%d minutes" -msgstr[0] "%d minută" +msgstr[0] "%d minut" msgstr[1] "%d minute" -msgstr[2] "%d minute" +msgstr[2] "%d de minute" msgid "0 minutes" msgstr "0 minute" @@ -1130,6 +1170,20 @@ msgstr "" "rugăm să le reactivați, cel puțin pentru aceasta pagină web, sau pentru " "conexiunile HTTPS, sau pentru cererile '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." +msgstr "" +"Dacă utilizați eticheta sau " +"includeți antetul 'Referrer-Policy: no-referrer', te rugăm sa îl elimini. " +"Protecția CSRF necesită antetul 'Referer' pentru a face verificarea strictă " +"a 'referer'. Dacă sunteți îngrijorat de confidențialitate, utilizați " +"alternative ca pentru linkuri către site-uri " +"terțe." + 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 " @@ -1150,32 +1204,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Mai multe informații sunt disponibile pentru DEBUG=True." -msgid "Welcome to Django" -msgstr "Bine ai venit la Django" - -msgid "It worked!" -msgstr "A mers!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Felicitări pentru prima ta pagină Django." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Desigur, încă nu ați prestat nici o activiate. În continuare, inițiați prima " -"aplicație executând python manage.py startapp [app_label]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Vedeți acest mesaj deoarece ați setat DEBUG = True în fișierul " -"de setări Django și nu ați configurat nici un URL. La treabă!" - msgid "No year specified" msgstr "Niciun an specificat" +msgid "Date out of range" +msgstr "Dată în afara intervalului" + msgid "No month specified" msgstr "Nicio lună specificată" @@ -1227,3 +1261,47 @@ msgstr "\"%(path)s\" nu există" #, python-format msgid "Index of %(directory)s" msgstr "Index pentru %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: Framework-ul web pentru perfecționiști cu termene limită." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Vezi notele de lansare pentru Django " +"%(version)s" + +msgid "The install worked successfully! Congratulations!" +msgstr "Instalarea a funcționat cu succes! Felicitări!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Vedeți această pagină deoarece DEBUG=True este în fișierul de setări și nu ați configurat niciun URL." + +msgid "Django Documentation" +msgstr "Documentația Django" + +msgid "Topics, references, & how-to's" +msgstr "Subiecte, referinţe, & cum să" + +msgid "Tutorial: A Polling App" +msgstr "Tutorial: O aplicație de votare" + +msgid "Get started with Django" +msgstr "Începeți cu Django" + +msgid "Django Community" +msgstr "Comunitatea Django" + +msgid "Connect, get help, or contribute" +msgstr "Conectați-vă, obțineți ajutor sau contribuiți" diff --git a/django/conf/locale/ro/formats.py b/django/conf/locale/ro/formats.py index 4ed143b74542..8cefeb839595 100644 --- a/django/conf/locale/ro/formats.py +++ b/django/conf/locale/ro/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j F Y' TIME_FORMAT = 'H:i' DATETIME_FORMAT = 'j F Y, H:i' @@ -12,13 +9,27 @@ MONTH_DAY_FORMAT = 'j F' SHORT_DATE_FORMAT = 'd.m.Y' SHORT_DATETIME_FORMAT = 'd.m.Y, H:i' -# FIRST_DAY_OF_WEEK = +FIRST_DAY_OF_WEEK = 1 # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior -# DATE_INPUT_FORMATS = -# TIME_INPUT_FORMATS = -# DATETIME_INPUT_FORMATS = +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior +DATE_INPUT_FORMATS = [ + '%d.%m.%Y', + '%d.%b.%Y', + '%d %B %Y', + '%A, %d %B %Y', +] +TIME_INPUT_FORMATS = [ + '%H:%M', + '%H:%M:%S', + '%H:%M:%S.%f', +] +DATETIME_INPUT_FORMATS = [ + '%d.%m.%Y, %H:%M', + '%d.%m.%Y, %H:%M:%S', + '%d.%B.%Y, %H:%M', + '%d.%B.%Y, %H:%M:%S', +] DECIMAL_SEPARATOR = ',' THOUSAND_SEPARATOR = '.' -# NUMBER_GROUPING = +NUMBER_GROUPING = 3 diff --git a/django/conf/locale/ru/LC_MESSAGES/django.mo b/django/conf/locale/ru/LC_MESSAGES/django.mo index 421ad7903dbe..f5124ee8e9ec 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 d66e50448853..3a0b6c2b864e 100644 --- a/django/conf/locale/ru/LC_MESSAGES/django.po +++ b/django/conf/locale/ru/LC_MESSAGES/django.po @@ -2,23 +2,27 @@ # # Translators: # Mingun , 2014 +# Anton Bazhanov , 2017 # Denis Darii , 2011 # Dimmus , 2011 # eigrad , 2012 -# Eugene MechanisM , 2013 +# Eugene , 2013 # eXtractor , 2015 # Igor Melnyk, 2014 +# Ivan Khomutov , 2017 # Jannis Leidel , 2011 # lilo.panic, 2016 # Mikhail Zholobov , 2013 -# Алексей Борискин , 2013-2016 -# Дмитрий Шатера , 2016 +# Nikolay Korotkiy , 2018 +# Вася Аникин , 2017 +# Алексей Борискин , 2013-2017,2019 +# Дмитрий Шатера , 2016,2018 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-07-20 09:53+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-02-18 21:13+0000\n" "Last-Translator: Алексей Борискин \n" "Language-Team: Russian (http://www.transifex.com/django/django/language/" "ru/)\n" @@ -150,6 +154,9 @@ msgstr "Верхнелужицкий" msgid "Hungarian" msgstr "Венгерский" +msgid "Armenian" +msgstr "Армянский" + msgid "Interlingua" msgstr "Интерлингва" @@ -171,6 +178,9 @@ msgstr "Японский" msgid "Georgian" msgstr "Грузинский" +msgid "Kabyle" +msgstr "Кабильский" + msgid "Kazakh" msgstr "Казахский" @@ -306,6 +316,15 @@ msgstr "Статические файлы" msgid "Syndication" msgstr "Ленты новостей" +msgid "That page number is not an integer" +msgstr "Номер страницы не является натуральным числом" + +msgid "That page number is less than 1" +msgstr "Номер страницы меньше 1" + +msgid "That page contains no results" +msgstr "Страница не содержит результатов" + msgid "Enter a valid value." msgstr "Введите правильное значение." @@ -318,6 +337,7 @@ msgstr "Введите правильное число." msgid "Enter a valid email address." msgstr "Введите правильный адрес электронной почты." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -396,6 +416,9 @@ msgstr[3] "" "Убедитесь, что это значение содержит не более %(limit_value)d символов " "(сейчас %(show_value)d)." +msgid "Enter a number." +msgstr "Введите число." + #, 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." @@ -422,6 +445,17 @@ msgstr[1] "Убедитесь, что вы ввели не более %(max)s ц msgstr[2] "Убедитесь, что вы ввели не более %(max)s цифр перед запятой." msgstr[3] "Убедитесь, что вы ввели не более %(max)s цифр перед запятой." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Формат файлов '%(extension)s' не поддерживается. Поддерживаемые форматы " +"файлов: '%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Данные содержат запрещённый символ: ноль-байт" + msgid "and" msgstr "и" @@ -471,6 +505,10 @@ msgstr "Длинное целое (8 байт)" msgid "'%(value)s' value must be either True or False." msgstr "Значение '%(value)s' должно быть True или False." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "Значение '%(value)s' должно быть True, False или None." + msgid "Boolean (Either True or False)" msgstr "Логическое (True или False)" @@ -608,6 +646,9 @@ msgstr "Необработанные двоичные данные" msgid "'%(value)s' is not a valid UUID." msgstr "Значение '%(value)s' не является верным UUID-ом." +msgid "Universally unique identifier" +msgstr "Поле для UUID, универсального уникального идентификатора" + msgid "File" msgstr "Файл" @@ -649,9 +690,6 @@ msgstr "Обязательное поле." msgid "Enter a whole number." msgstr "Введите целое число." -msgid "Enter a number." -msgstr "Введите число." - msgid "Enter a valid date." msgstr "Введите правильную дату." @@ -664,6 +702,10 @@ msgstr "Введите правильную дату и время." msgid "Enter a valid duration." msgstr "Введите правильную продолжительность." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Количество дней должно быть в диапазоне от {min_days} до {max_days}." + msgid "No file was submitted. Check the encoding type on the form." msgstr "Ни одного файла не было отправлено. Проверьте тип кодировки формы." @@ -768,16 +810,16 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Пожалуйста, измените повторяющиеся значения ниже." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "Внешний ключ не совпадает с первичным ключом родителя." +msgid "The inline value did not match the parent instance." +msgstr "Значение во вложенной форме не совпадает со значением в базовой форме." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" "Выберите корректный вариант. Вашего варианта нет среди допустимых значений." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" не является верным значением для первичного ключа." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" не является верным значением." #, python-format msgid "" @@ -788,15 +830,15 @@ msgstr "" "%(current_timezone)s; дата может быть неоднозначной или оказаться " "несуществующей." +msgid "Clear" +msgstr "Очистить" + msgid "Currently" msgstr "На данный момент" msgid "Change" msgstr "Изменить" -msgid "Clear" -msgstr "Очистить" - msgid "Unknown" msgstr "Неизвестно" @@ -1070,8 +1112,8 @@ msgstr "Значение не является корректным адресо #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s…" msgid "or" msgstr "или" @@ -1160,6 +1202,20 @@ msgstr "" "запросов, домен и порт назначения совпадают с доменом и портом текущей " "страницы." +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." +msgstr "" +"Если Вы используете HTML-тэг или добавили HTTP-заголовок 'Referrer-Policy: no-referrer', пожалуйста " +"удалите их. CSRF защите необходим заголовок 'Referer' для строгой проверки " +"адреса ссылающейся страницы. Если Вы беспокоитесь о приватности, используйте " +"альтернативы, например , для ссылок на сайты " +"третьих лиц." + 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 " @@ -1184,34 +1240,12 @@ msgstr "" "В отладочном режиме доступно больше информации. Включить отладочный режим " "можно, установив значение переменной DEBUG=True." -msgid "Welcome to Django" -msgstr "Добро пожаловать в Django" - -msgid "It worked!" -msgstr "Заработало!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Поздравляем вас с вашей первой страницей, работающей на Django." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Конечно, это только начало. Сейчас вы можете создать ваше первое приложение. " -"Чтобы сделать это, запустите команду python manage.py startapp " -"[app_label]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Вы видите это сообщение, потому что в файле ваших настроек Django есть " -"строчка DEBUG = True и вы не сконфигурировали ни одного URL. За " -"работу!" - msgid "No year specified" msgstr "Не указан год" +msgid "Date out of range" +msgstr "Дата выходит за пределы диапазона" + msgid "No month specified" msgstr "Не указан месяц" @@ -1268,3 +1302,48 @@ 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." +msgstr "" +"Вы видите данную страницу, потому что указали DEBUG=True в файле настроек и не настроили ни одного " +"обработчика URL-адресов." + +msgid "Django Documentation" +msgstr "Документация Django" + +msgid "Topics, references, & how-to's" +msgstr "Разделы, справочник, & примеры" + +msgid "Tutorial: A Polling App" +msgstr "Руководство: Приложение для голосования" + +msgid "Get started with Django" +msgstr "Начало работы с Django" + +msgid "Django Community" +msgstr "Сообщество Django" + +msgid "Connect, get help, or contribute" +msgstr "Присоединяйтесь, получайте помощь или помогайте в разработке" diff --git a/django/conf/locale/ru/formats.py b/django/conf/locale/ru/formats.py index 07b11812851f..3e7651d7552f 100644 --- a/django/conf/locale/ru/formats.py +++ b/django/conf/locale/ru/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j E Y г.' TIME_FORMAT = 'G:i' DATETIME_FORMAT = 'j E Y г. G:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d.%m.%Y', # '25.10.2006' '%d.%m.%y', # '25.10.06' diff --git a/django/conf/locale/sk/LC_MESSAGES/django.mo b/django/conf/locale/sk/LC_MESSAGES/django.mo index 2b4553c8dd25..6f5900941ab0 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 9e652091758f..33b620ef3891 100644 --- a/django/conf/locale/sk/LC_MESSAGES/django.po +++ b/django/conf/locale/sk/LC_MESSAGES/django.po @@ -3,21 +3,23 @@ # Translators: # Jannis Leidel , 2011 # Juraj Bubniak , 2012-2013 -# Marian Andre , 2013,2015 +# Marian Andre , 2013,2015,2017-2018 # Martin Kosír, 2011 +# Martin Tóth , 2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2018-05-17 11:49+0200\n" +"PO-Revision-Date: 2018-08-25 06:21+0000\n" +"Last-Translator: Marian Andre \n" "Language-Team: Slovak (http://www.transifex.com/django/django/language/sk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: sk\n" -"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" +"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n " +">= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);\n" msgid "Afrikaans" msgstr "afrikánsky" @@ -26,7 +28,7 @@ msgid "Arabic" msgstr "arabský" msgid "Asturian" -msgstr "" +msgstr "astúrsky" msgid "Azerbaijani" msgstr "azerbajdžansky" @@ -62,7 +64,7 @@ msgid "German" msgstr "nemecky" msgid "Lower Sorbian" -msgstr "" +msgstr "dolnolužická srbčina" msgid "Greek" msgstr "grécky" @@ -71,10 +73,10 @@ msgid "English" msgstr "anglicky" msgid "Australian English" -msgstr "" +msgstr "austrálskou angličtinou" msgid "British English" -msgstr "britsky" +msgstr "britskou angličtinou" msgid "Esperanto" msgstr "esperantsky" @@ -86,7 +88,7 @@ msgid "Argentinian Spanish" msgstr "argentínska španielčina" msgid "Colombian Spanish" -msgstr "" +msgstr "kolumbijská španielčina" msgid "Mexican Spanish" msgstr "mexická španielčina" @@ -101,7 +103,7 @@ msgid "Estonian" msgstr "estónsky" msgid "Basque" -msgstr "baskický" +msgstr "baskicky" msgid "Persian" msgstr "perzsky" @@ -119,7 +121,7 @@ msgid "Irish" msgstr "írsky" msgid "Scottish Gaelic" -msgstr "" +msgstr "škótska gaelčina" msgid "Galician" msgstr "galícijsky" @@ -134,7 +136,7 @@ msgid "Croatian" msgstr "chorvátsky" msgid "Upper Sorbian" -msgstr "" +msgstr "hornolužická srbčina" msgid "Hungarian" msgstr "maďarsky" @@ -146,7 +148,7 @@ msgid "Indonesian" msgstr "indonézsky" msgid "Ido" -msgstr "" +msgstr "ido" msgid "Icelandic" msgstr "islandsky" @@ -160,14 +162,17 @@ msgstr "japonsky" msgid "Georgian" msgstr "gruzínsky" +msgid "Kabyle" +msgstr "kabylsky" + msgid "Kazakh" -msgstr "kazašský" +msgstr "kazašsky" msgid "Khmer" msgstr "kmérsky" msgid "Kannada" -msgstr "kanadský" +msgstr "kannadsky" msgid "Korean" msgstr "kórejsky" @@ -191,13 +196,13 @@ msgid "Mongolian" msgstr "mongolsky" msgid "Marathi" -msgstr "" +msgstr "maráthsky" msgid "Burmese" msgstr "barmsky" msgid "Norwegian Bokmål" -msgstr "" +msgstr "nórsky (Bokmål)" msgid "Nepali" msgstr "nepálsky" @@ -221,7 +226,7 @@ msgid "Portuguese" msgstr "portugalsky" msgid "Brazilian Portuguese" -msgstr "portugalský (Brazília)" +msgstr "portugalsky (Brazília)" msgid "Romanian" msgstr "rumunsky" @@ -248,13 +253,13 @@ msgid "Swedish" msgstr "švédsky" msgid "Swahili" -msgstr "svahilský" +msgstr "svahilsky" msgid "Tamil" msgstr "tamilsky" msgid "Telugu" -msgstr "telúgsky" +msgstr "telugsky" msgid "Thai" msgstr "thajsky" @@ -266,7 +271,7 @@ msgid "Tatar" msgstr "tatársky" msgid "Udmurt" -msgstr "udmurtský" +msgstr "udmurtsky" msgid "Ukrainian" msgstr "ukrajinsky" @@ -284,16 +289,25 @@ msgid "Traditional Chinese" msgstr "čínsky (tradične)" msgid "Messages" -msgstr "" +msgstr "Správy" msgid "Site Maps" -msgstr "" +msgstr "Mapy Sídla" msgid "Static Files" -msgstr "" +msgstr "Statické Súbory" msgid "Syndication" -msgstr "" +msgstr "Syndikácia" + +msgid "That page number is not an integer" +msgstr "Číslo stránky nie je celé číslo" + +msgid "That page number is less than 1" +msgstr "Číslo stránky je menšie ako 1" + +msgid "That page contains no results" +msgstr "Stránka neobsahuje žiadne výsledky" msgid "Enter a valid value." msgstr "Zadajte platnú hodnotu." @@ -302,11 +316,12 @@ msgid "Enter a valid URL." msgstr "Zadajte platnú URL adresu." msgid "Enter a valid integer." -msgstr "" +msgstr "Zadajte platné celé číslo." msgid "Enter a valid email address." msgstr "Zadajte platnú e-mailovú adresu." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -317,6 +332,8 @@ msgid "" "Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or " "hyphens." msgstr "" +"Zadajte platný 'slug' pozostávajúci z písmen (Unicode), čísel, " +"podčiarkovníkov alebo pomlčiek." msgid "Enter a valid IPv4 address." msgstr "Zadajte platnú IPv4 adresu." @@ -358,6 +375,9 @@ msgstr[1] "" msgstr[2] "" "Uistite sa, že zadaná hodnota má najmenej %(limit_value)d znakov (má " "%(show_value)d)." +msgstr[3] "" +"Uistite sa, že zadaná hodnota má najmenej %(limit_value)d znakov (má " +"%(show_value)d)." #, python-format msgid "" @@ -375,6 +395,12 @@ msgstr[1] "" msgstr[2] "" "Uistite sa, že táto hodnota má najviac %(limit_value)d znakov (má " "%(show_value)d)." +msgstr[3] "" +"Uistite sa, že táto hodnota má najviac %(limit_value)d znakov (má " +"%(show_value)d)." + +msgid "Enter a number." +msgstr "Zadajte číslo." #, python-format msgid "Ensure that there are no more than %(max)s digit in total." @@ -382,6 +408,7 @@ msgid_plural "Ensure that there are no more than %(max)s digits in total." msgstr[0] "Uistite sa, že nie je zadaných celkovo viac ako %(max)s číslica." msgstr[1] "Uistite sa, že nie je zadaných celkovo viac ako %(max)s číslice." msgstr[2] "Uistite sa, že nie je zadaných celkovo viac ako %(max)s číslic." +msgstr[3] "Uistite sa, že nie je zadaných celkovo viac ako %(max)s číslic." #, python-format msgid "Ensure that there are no more than %(max)s decimal place." @@ -389,6 +416,7 @@ msgid_plural "Ensure that there are no more than %(max)s decimal places." msgstr[0] "Uistite sa, že nie je zadané viac ako %(max)s desatinné miesto." msgstr[1] "Uistite sa, že nie sú zadané viac ako %(max)s desatinné miesta." msgstr[2] "Uistite sa, že nie je zadaných viac ako %(max)s desatinných miest." +msgstr[3] "Uistite sa, že nie je zadaných viac ako %(max)s desatinných miest." #, python-format msgid "" @@ -404,17 +432,31 @@ msgstr[1] "" msgstr[2] "" "Uistite sa, že nie je zadaných viac ako %(max)s číslic pred desatinnou " "čiarkou." +msgstr[3] "" +"Uistite sa, že nie je zadaných viac ako %(max)s číslic pred desatinnou " +"čiarkou." + +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Prípona súboru '%(extension)s' nie je povolená. Povolené prípony sú: " +"'%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Znaky NULL nie sú povolené." msgid "and" msgstr "a" #, python-format msgid "%(model_name)s with this %(field_labels)s already exists." -msgstr "" +msgstr "%(model_name)s s týmto %(field_labels)s už existuje." #, python-format msgid "Value %(value)r is not a valid choice." -msgstr "" +msgstr "Hodnota %(value)r nie je platná možnosť." msgid "This field cannot be null." msgstr "Toto pole nemôže byť prázdne." @@ -432,6 +474,7 @@ msgstr "%(model_name)s s týmto %(field_label)s už existuje." msgid "" "%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." msgstr "" +"%(field_label)s musí byť unikátne pre %(date_field_label)s %(lookup_type)s." #, python-format msgid "Field of type: %(field_type)s" @@ -442,14 +485,18 @@ msgstr "Celé číslo" #, python-format msgid "'%(value)s' value must be an integer." -msgstr "" +msgstr "'%(value)s' musí byť celé číslo." msgid "Big (8 byte) integer" msgstr "Veľké celé číslo (8 bajtov)" #, python-format msgid "'%(value)s' value must be either True or False." -msgstr "" +msgstr "'%(value)s' value musí byť True alebo False." + +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "'%(value)s' musí byť True, False alebo None." msgid "Boolean (Either True or False)" msgstr "Logická hodnota (buď True alebo False)" @@ -465,13 +512,14 @@ msgstr "Celé čísla oddelené čiarkou" msgid "" "'%(value)s' value has an invalid date format. It must be in YYYY-MM-DD " "format." -msgstr "" +msgstr "'%(value)s' má neplatný tvar dátumu. Musí byť v tvare YYYY-MM-DD." #, python-format msgid "" "'%(value)s' value has the correct format (YYYY-MM-DD) but it is an invalid " "date." msgstr "" +"'%(value)s' je v správnom tvare (YYYY-MM-DD), ale je to neplatný dátum." msgid "Date (without time)" msgstr "Dátum (bez času)" @@ -481,19 +529,23 @@ msgid "" "'%(value)s' value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." "uuuuuu]][TZ] format." msgstr "" +"'%(value)s' má neplatný tvar. Musí byť v tvare 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' je v správnom tvare (YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]), ale je " +"to neplatný dátum/čas." msgid "Date (with time)" msgstr "Dátum (a čas)" #, python-format msgid "'%(value)s' value must be a decimal number." -msgstr "" +msgstr "'%(value)s' musí byť desatinné číslo." msgid "Decimal number" msgstr "Desatinné číslo" @@ -503,19 +555,20 @@ msgid "" "'%(value)s' value has an invalid format. It must be in [DD] [HH:[MM:]]ss[." "uuuuuu] format." msgstr "" +"'%(value)s' má neplatný tvar. Musí byť v tvare [DD] [HH:[MM]]ss[.uuuuuu]." msgid "Duration" -msgstr "" +msgstr "Doba trvania" msgid "Email address" -msgstr "E-mail adresa" +msgstr "E-mailová adresa" msgid "File path" msgstr "Cesta k súboru" #, python-format msgid "'%(value)s' value must be a float." -msgstr "" +msgstr "'%(value)s' musí byť desatinné číslo." msgid "Floating point number" msgstr "Číslo s plávajúcou desatinnou čiarkou" @@ -528,7 +581,7 @@ msgstr "IP adresa" #, python-format msgid "'%(value)s' value must be either None, True or False." -msgstr "" +msgstr "'%(value)s' musí byť buď None, True alebo False." msgid "Boolean (Either True, False or None)" msgstr "Logická hodnota (buď True, False alebo None)" @@ -553,13 +606,15 @@ msgstr "Text" msgid "" "'%(value)s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " "format." -msgstr "" +msgstr "'%(value)s' má neplatný tvar. Musí byť v tvare 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 "" +"'%(value)s' je v správnom tvare (HH:MM[:ss[.uuuuuu]]), ale je to neplatný " +"čas." msgid "Time" msgstr "Čas" @@ -568,11 +623,11 @@ msgid "URL" msgstr "URL" msgid "Raw binary data" -msgstr "Binárne dáta" +msgstr "Binárne údaje" #, python-format msgid "'%(value)s' is not a valid UUID." -msgstr "" +msgstr "'%(value)s' nie je platné UUID." msgid "File" msgstr "Súbor" @@ -582,7 +637,7 @@ msgstr "Obrázok" #, python-format msgid "%(model)s instance with %(field)s %(value)r does not exist." -msgstr "" +msgstr "Inštancia modelu %(model)s s %(field)s %(value)r neexistuje." msgid "Foreign Key (type determined by related field)" msgstr "Cudzí kľúč (typ určuje pole v relácii)" @@ -592,11 +647,11 @@ msgstr "Typ relácie: jedna k jednej" #, python-format msgid "%(from)s-%(to)s relationship" -msgstr "" +msgstr "vzťah: %(from)s-%(to)s " #, python-format msgid "%(from)s-%(to)s relationships" -msgstr "" +msgstr "vzťahy: %(from)s-%(to)s" msgid "Many-to-many relationship" msgstr "Typ relácie: M ku N" @@ -613,9 +668,6 @@ msgstr "Toto pole je povinné." msgid "Enter a whole number." msgstr "Zadajte celé číslo." -msgid "Enter a number." -msgstr "Zadajte číslo." - msgid "Enter a valid date." msgstr "Zadajte platný dátum." @@ -623,10 +675,14 @@ msgid "Enter a valid time." msgstr "Zadajte platný čas." msgid "Enter a valid date/time." -msgstr "Zadajte platný dátum a čas." +msgstr "Zadajte platný dátum/čas." msgid "Enter a valid duration." -msgstr "" +msgstr "Zadajte platnú dobu trvania." + +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Počet dní musí byť medzi {min_days} a {max_days}." msgid "No file was submitted. Check the encoding type on the form." msgstr "Súbor nebol odoslaný. Skontrolujte typ kódovania vo formulári." @@ -647,6 +703,8 @@ msgstr[1] "" "Uistite sa, že názov súboru má najviac %(max)d znaky (má %(length)d)." msgstr[2] "" "Uistite sa, že názov súboru má najviac %(max)d znakov (má %(length)d)." +msgstr[3] "" +"Uistite sa, že názov súboru má najviac %(max)d znakov (má %(length)d)." msgid "Please either submit a file or check the clear checkbox, not both." msgstr "" @@ -665,13 +723,13 @@ msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "Vyberte platnú voľbu. %(value)s nepatrí medzi dostupné možnosti." msgid "Enter a list of values." -msgstr "Vložte zoznam hodnôt." +msgstr "Zadajte zoznam hodnôt." msgid "Enter a complete value." -msgstr "" +msgstr "Zadajte úplnú hodnotu." msgid "Enter a valid UUID." -msgstr "" +msgstr "Zadajte platné UUID." #. Translators: This is the default suffix added to form field labels msgid ":" @@ -682,7 +740,7 @@ msgid "(Hidden field %(name)s) %(error)s" msgstr "(Skryté pole %(name)s) %(error)s" msgid "ManagementForm data is missing or has been tampered with" -msgstr "" +msgstr "Údaje ManagementForm chýbajú alebo boli sfalšované" #, python-format msgid "Please submit %d or fewer forms." @@ -690,13 +748,15 @@ msgid_plural "Please submit %d or fewer forms." msgstr[0] "Prosím odošlite %d alebo menej formulárov." msgstr[1] "Prosím odošlite %d alebo menej formulárov." msgstr[2] "Prosím odošlite %d alebo menej formulárov." +msgstr[3] "Prosím odošlite %d alebo menej formulárov." #, python-format msgid "Please submit %d or more forms." msgid_plural "Please submit %d or more forms." -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Prosím odošlite %d alebo viac formulárov." +msgstr[1] "Prosím odošlite %d alebo viac formulárov." +msgstr[2] "Prosím odošlite %d alebo viac formulárov." +msgstr[3] "Prosím odošlite %d alebo viac formulárov." msgid "Order" msgstr "Poradie" @@ -706,34 +766,33 @@ msgstr "Odstrániť" #, python-format msgid "Please correct the duplicate data for %(field)s." -msgstr "Prosím, opravte duplicitné dáta pre %(field)s." +msgstr "Prosím, opravte duplicitné údaje pre %(field)s." #, python-format msgid "Please correct the duplicate data for %(field)s, which must be unique." -msgstr "Dáta pre %(field)s musia byť unikátne, prosím, opravte duplikáty." +msgstr "Údaje pre %(field)s musia byť unikátne, prosím, opravte duplikáty." #, 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 "" -"Dáta pre %(field_name)s musia byť unikátne pre %(lookup)s v %(date_field)s, " +"Údaje pre %(field_name)s musia byť unikátne pre %(lookup)s v %(date_field)s, " "prosím, opravte duplikáty." msgid "Please correct the duplicate values below." msgstr "Prosím, opravte nižšie uvedené duplicitné hodnoty. " -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "" -"Vnorený cudzí kľúč sa nezhoduje s nadradenou inštanciou primárnho kľúča." +msgid "The inline value did not match the parent instance." +msgstr "Vnorená hodnota sa nezhoduje s nadradenou inštanciou." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" "Vyberte platnú možnosť. Vybraná položka nepatrí medzi dostupné možnosti." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" nie je platná hodnota pre primárny kľúč." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" nie je platná hodnota." #, python-format msgid "" @@ -743,15 +802,15 @@ msgstr "" "Hodnota %(datetime)s v časovej zóne %(current_timezone)s sa nedá " "interpretovať; môže byť nejednoznačná alebo nemusí existovať." +msgid "Clear" +msgstr "Vymazať" + msgid "Currently" msgstr "Súčasne" msgid "Change" msgstr "Zmeniť" -msgid "Clear" -msgstr "Vymazať" - msgid "Unknown" msgstr "Neznámy" @@ -770,6 +829,7 @@ msgid_plural "%(size)d bytes" msgstr[0] "%(size)d bajt" msgstr[1] "%(size)d bajty" msgstr[2] "%(size)d bajtov" +msgstr[3] "%(size)d bajtov" #, python-format msgid "%s KB" @@ -795,13 +855,13 @@ msgid "p.m." msgstr "popoludní" msgid "a.m." -msgstr "dopoludnia" +msgstr "predpoludním" msgid "PM" msgstr "popoludní" msgid "AM" -msgstr "dopoludnia" +msgstr "predpoludním" msgid "midnight" msgstr "polnoc" @@ -1020,7 +1080,7 @@ msgid "December" msgstr "december" msgid "This is not a valid IPv6 address." -msgstr "" +msgstr "Toto nieje platná IPv6 adresa." #, python-format msgctxt "String to return when truncating text" @@ -1040,6 +1100,7 @@ msgid_plural "%d years" msgstr[0] "%d rok" msgstr[1] "%d roky" msgstr[2] "%d rokov" +msgstr[3] "%d rokov" #, python-format msgid "%d month" @@ -1047,6 +1108,7 @@ msgid_plural "%d months" msgstr[0] "%d mesiac" msgstr[1] "%d mesiace" msgstr[2] "%d mesiacov" +msgstr[3] "%d mesiacov" #, python-format msgid "%d week" @@ -1054,6 +1116,7 @@ msgid_plural "%d weeks" msgstr[0] "%d týždeň" msgstr[1] "%d týždne" msgstr[2] "%d týždňov" +msgstr[3] "%d týždňov" #, python-format msgid "%d day" @@ -1061,6 +1124,7 @@ msgid_plural "%d days" msgstr[0] "%d deň" msgstr[1] "%d dni" msgstr[2] "%d dní" +msgstr[3] "%d dní" #, python-format msgid "%d hour" @@ -1068,6 +1132,7 @@ msgid_plural "%d hours" msgstr[0] "%d hodina" msgstr[1] "%d hodiny" msgstr[2] "%d hodín" +msgstr[3] "%d hodín" #, python-format msgid "%d minute" @@ -1075,15 +1140,16 @@ msgid_plural "%d minutes" msgstr[0] "%d minúta" msgstr[1] "%d minúty" msgstr[2] "%d minút" +msgstr[3] "%d minút" msgid "0 minutes" msgstr "0 minút" msgid "Forbidden" -msgstr "" +msgstr "Zakázané (Forbidden)" msgid "CSRF verification failed. Request aborted." -msgstr "" +msgstr "CSRF verifikázia zlyhala. Požiadavka bola prerušená." msgid "" "You are seeing this message because this HTTPS site requires a 'Referer " @@ -1091,49 +1157,58 @@ msgid "" "required for security reasons, to ensure that your browser is not being " "hijacked by third parties." msgstr "" +"Túto správu vidíte, pretože táto HTTPS lokalita vyžaduje, aby prehliadač " +"poslal 'Referer' hlavičku a browser takúto hlavičku v požiadavke neodoslal. " +"Hlavička je potrebná na zabezpečenie toho, že váš prehliadač nie je zneužitý " +"\"hijack\"." 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 "" +"Ak máte v prehliadači zakázané odosielanie hlavičky Referer, povoľte ho " +"znovu prosím - minimálne pre túto stránku, pre HTTPS spojenia alebo " +"požiadavky s politikou 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." +msgstr "" +"Ak používate tag alebo " +"vkladáte hlavičku 'Referrer-Policy: no-referrer', prosím odstránte ich. " +"Ochrana CSRF vyžaduje hlavičku 'Referer' na striktnú kontrolu. Ak máte obavy " +"o súkromie, použite alternatívy ako napríklad pre " +"linky na iné stránky." 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úto správu vidíte, pretože táto lokalita vyžaduje CSRF cookie pri " +"odosielaní formulárov. Toto cookie je potrebné na zabezpečenie toho, že váš " +"prehliadač nie je zneužitý - \"hijack\"." 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 "" +"Ak máte v prehliadači zakázané cookies, povoľte ich znovu prosím - minimálne " +"pre túto stránku, alebo požiadavky s politikou same-origin." msgid "More information is available with DEBUG=True." -msgstr "" - -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" +msgstr "Viac informácií bude dostupných s DEBUG=True." msgid "No year specified" msgstr "Nešpecifikovaný rok" +msgid "Date out of range" +msgstr "Dátum je mimo rozsahu" + msgid "No month specified" msgstr "Nešpecifikovaný mesiac" @@ -1188,3 +1263,48 @@ msgstr "\"%(path)s\" neexistuje" #, python-format msgid "Index of %(directory)s" msgstr "Výpis %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: Webový framework pre pedantov s termínmi" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Zobraziť poznámky k vydaniu pre Django " +"%(version)s" + +msgid "The install worked successfully! Congratulations!" +msgstr "Inštalácia prebehla úspešne! Gratulujeme!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Táto stránka sa zobrazuje pretože máte DEBUG=True v súbore s nastaveniami a nie sú nakonfigurované žiadne " +"URL." + +msgid "Django Documentation" +msgstr "Dokumentácia Django" + +msgid "Topics, references, & how-to's" +msgstr "Témy, referencie, & návody" + +msgid "Tutorial: A Polling App" +msgstr "Tutoriál: Aplikácia \"Hlasovania\"" + +msgid "Get started with Django" +msgstr "Začíname s Django" + +msgid "Django Community" +msgstr "Komunita Django" + +msgid "Connect, get help, or contribute" +msgstr "Spojte sa, získajte pomoc, alebo prispejte" diff --git a/django/conf/locale/sk/formats.py b/django/conf/locale/sk/formats.py index 04bdceb00039..fedd8b67860b 100644 --- a/django/conf/locale/sk/formats.py +++ b/django/conf/locale/sk/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j. F Y' TIME_FORMAT = 'G:i' DATETIME_FORMAT = 'j. F Y G:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d.%m.%Y', '%d.%m.%y', # '25.10.2006', '25.10.06' '%y-%m-%d', # '06-10-25' diff --git a/django/conf/locale/sl/LC_MESSAGES/django.mo b/django/conf/locale/sl/LC_MESSAGES/django.mo index 88364be12c43..b6c049b3fa41 100644 Binary files a/django/conf/locale/sl/LC_MESSAGES/django.mo and b/django/conf/locale/sl/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/sl/LC_MESSAGES/django.po b/django/conf/locale/sl/LC_MESSAGES/django.po index 231027f753cb..a63e51e97226 100644 --- a/django/conf/locale/sl/LC_MESSAGES/django.po +++ b/django/conf/locale/sl/LC_MESSAGES/django.po @@ -4,14 +4,16 @@ # iElectric , 2011-2012 # Jannis Leidel , 2011 # Jure Cuhalev , 2012-2013 -# zejn , 2013,2016 +# Marko Zabreznik , 2016 +# Primož Verdnik , 2017 +# zejn , 2013,2016-2017 # zejn , 2011-2013 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-09-27 19:12+0000\n" +"POT-Creation-Date: 2017-12-01 21:10+0100\n" +"PO-Revision-Date: 2017-12-03 15:52+0000\n" "Last-Translator: zejn \n" "Language-Team: Slovenian (http://www.transifex.com/django/django/language/" "sl/)\n" @@ -163,6 +165,9 @@ msgstr "Japonščina" msgid "Georgian" msgstr "Gruzijščina" +msgid "Kabyle" +msgstr "Kabilski jezik" + msgid "Kazakh" msgstr "Kazaščina" @@ -296,7 +301,16 @@ msgid "Static Files" msgstr "Statične datoteke" msgid "Syndication" -msgstr "" +msgstr "Sindiciranje" + +msgid "That page number is not an integer" +msgstr "Število te strani ni naravno število" + +msgid "That page number is less than 1" +msgstr "Število te strani je manj kot 1" + +msgid "That page contains no results" +msgstr "Ta stran nima zadetkov" msgid "Enter a valid value." msgstr "Vnesite veljavno vrednost." @@ -310,6 +324,7 @@ msgstr "Vnesite veljavno celo število." msgid "Enter a valid email address." msgstr "Vnesite veljaven e-poštni naslov." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -418,6 +433,17 @@ msgstr[2] "" msgstr[3] "" "Poskrbite, da skupno ne bo več kot %(max)s števk pred decimalno vejico." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Končnica datoteke '%(extension)s' ni dovoljena. Dovoljene končnice so: " +"'%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Znak null ni dovoljen." + msgid "and" msgstr "in" @@ -759,15 +785,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Prosimo odpravite podvojene vrednosti spodaj." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "Tuji ključ se ne ujema z glavnim ključem povezanega vnosa." +msgid "The inline value did not match the parent instance." +msgstr "Vrednost se ne ujema s povezanim vnosom." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Izberite veljavno možnost. Te možnosti ni med ponujenimi izbirami." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" ni veljavna vrednost za glavni ključ." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" ni veljavna vrednost." #, python-format msgid "" @@ -777,15 +803,15 @@ msgstr "" "Vrednosti %(datetime)s ni bilo možno razumeti v časovnem pasu " "%(current_timezone)s; ali je izraz dvoumen ali pa ne obstaja." +msgid "Clear" +msgstr "Počisti" + msgid "Currently" msgstr "Trenutno" msgid "Change" msgstr "Spremeni" -msgid "Clear" -msgstr "Počisti" - msgid "Unknown" msgstr "Neznano" @@ -1082,7 +1108,7 @@ msgid "%d month" msgid_plural "%d months" msgstr[0] "%d mesec" msgstr[1] "%d meseca" -msgstr[2] "%d meseci" +msgstr[2] "%d mesece" msgstr[3] "%d mesecev" #, python-format @@ -1090,7 +1116,7 @@ msgid "%d week" msgid_plural "%d weeks" msgstr[0] "%d teden" msgstr[1] "%d tedna" -msgstr[2] "%d tedni" +msgstr[2] "%d tedne" msgstr[3] "%d tednov" #, python-format @@ -1098,7 +1124,7 @@ msgid "%d day" msgid_plural "%d days" msgstr[0] "%d dan" msgstr[1] "%d dneva" -msgstr[2] "%d dnevi" +msgstr[2] "%d dni" msgstr[3] "%d dni" #, python-format @@ -1145,6 +1171,19 @@ msgstr "" "('Referer'), to ponovno omogočite, vsaj za to stran ali za HTTPS povezave " "ali za povezave iz istega vira ('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." +msgstr "" +"Če uporabljate ali " +"vključujete 'Referrer-Policy: no-referrer' zaglavje, jih prosimo odstranite. " +"CSRF zaščita zahteva zaglavje \"Referer\", da se izvaja preverjanje. Za " +"zagotovitev zasebnosti za povezave na druge strani uporabite ." + 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 " @@ -1165,32 +1204,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Več informacij je na voljo, če nastavite DEBUG=True." -msgid "Welcome to Django" -msgstr "Dobrodošli v Django" - -msgid "It worked!" -msgstr "Deluje!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Dobrodošli na vaši prvi spletni strani, zgrajeni na Django platformi." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Seveda se tu resno delo šele prične. Ustvarite prvo aplikacijo tako, da " -"poženete python manage.py startapp [ime_aplikacije]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"To sporočilo vidite, ker imate v vaših Django nastavitvah nastavljeno " -"DEBUG = True in še niste nastavili nobenega URL-ja. Na delo!" - msgid "No year specified" msgstr "Leto ni vnešeno" +msgid "Date out of range" +msgstr "Datum ni znotraj veljavnega obsega." + msgid "No month specified" msgstr "Mesec ni vnešen" @@ -1243,3 +1262,48 @@ msgstr "\"%(path)s\" ne obstaja." #, python-format msgid "Index of %(directory)s" msgstr "Vsebina mape %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: spletno ogrodje za perfekcioniste s časovnimi roki." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Oglejte si obvestila ob izdaji za Django " +"%(version)s" + +msgid "The install worked successfully! Congratulations!" +msgstr "Namestitev se je uspešno izvedla! Čestitke!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"To stran vidite, ker imate nastavljeno DEBUG=True v vaši settings.py datoteki in ker nimate nastavljenih URL-" +"jev." + +msgid "Django Documentation" +msgstr "Django Dokumentacija" + +msgid "Topics, references, & how-to's" +msgstr "Teme, referenca in vodiči" + +msgid "Tutorial: A Polling App" +msgstr "Vodič: aplikacija anketa" + +msgid "Get started with Django" +msgstr "Začnite z Djangom" + +msgid "Django Community" +msgstr "Django Skupnost" + +msgid "Connect, get help, or contribute" +msgstr "Spoznajte nove ljudi, poiščite pomoč in prispevajte " diff --git a/django/conf/locale/sl/formats.py b/django/conf/locale/sl/formats.py index a4e9973a5522..769c2ba1edea 100644 --- a/django/conf/locale/sl/formats.py +++ b/django/conf/locale/sl/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'd. F Y' TIME_FORMAT = 'H:i' DATETIME_FORMAT = 'j. F Y. H:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 0 # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d.%m.%Y', '%d.%m.%y', # '25.10.2006', '25.10.06' '%d-%m-%Y', # '25-10-2006' diff --git a/django/conf/locale/sq/LC_MESSAGES/django.mo b/django/conf/locale/sq/LC_MESSAGES/django.mo index b0f57c4c2614..ac3953dab135 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 7b9b64525fc8..41d42e30256c 100644 --- a/django/conf/locale/sq/LC_MESSAGES/django.po +++ b/django/conf/locale/sq/LC_MESSAGES/django.po @@ -2,15 +2,15 @@ # # Translators: # Besnik , 2011-2014 -# Besnik , 2015-2016 +# Besnik , 2015-2019 # Jannis Leidel , 2011 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-03-30 10:51+0000\n" +"Last-Translator: Besnik \n" "Language-Team: Albanian (http://www.transifex.com/django/django/language/" "sq/)\n" "MIME-Version: 1.0\n" @@ -62,7 +62,7 @@ msgid "German" msgstr "Gjermane" msgid "Lower Sorbian" -msgstr "" +msgstr "Sorbiane e Poshtme" msgid "Greek" msgstr "Greke" @@ -74,7 +74,7 @@ msgid "Australian English" msgstr "Angleze Australiane" msgid "British English" -msgstr "Anglishte Britanike" +msgstr "Angleze Britanike" msgid "Esperanto" msgstr "Esperanto" @@ -83,19 +83,19 @@ msgid "Spanish" msgstr "Spanjolle" msgid "Argentinian Spanish" -msgstr "Spanjishte Argjentinase" +msgstr "Spanjolle Argjentinase" msgid "Colombian Spanish" -msgstr "Spanjishte Kolombiane" +msgstr "Spanjolle Kolumbiane" msgid "Mexican Spanish" -msgstr "Spanjishte Meksikane" +msgstr "Spanjolle Meksikane" msgid "Nicaraguan Spanish" -msgstr "Spanjishte Nikaraguane" +msgstr "Spanjolle Nikaraguane" msgid "Venezuelan Spanish" -msgstr "Spanjishte Venezueliane" +msgstr "Spanjolle Venezuelane" msgid "Estonian" msgstr "Estoneze" @@ -134,11 +134,14 @@ msgid "Croatian" msgstr "Kroate" msgid "Upper Sorbian" -msgstr "" +msgstr "Sorbiane e Sipërme" msgid "Hungarian" msgstr "Hungareze" +msgid "Armenian" +msgstr "Armenisht" + msgid "Interlingua" msgstr "Interlingua" @@ -160,6 +163,9 @@ msgstr "Japoneze" msgid "Georgian" msgstr "Gjeorgjiane" +msgid "Kabyle" +msgstr "Kabilase" + msgid "Kazakh" msgstr "Kazake" @@ -179,7 +185,7 @@ msgid "Lithuanian" msgstr "Lituaneze" msgid "Latvian" -msgstr "Latviane" +msgstr "Letoneze" msgid "Macedonian" msgstr "Maqedone" @@ -197,7 +203,7 @@ msgid "Burmese" msgstr "Burmeze" msgid "Norwegian Bokmål" -msgstr "" +msgstr "Norvegjeze Bokmal" msgid "Nepali" msgstr "Nepaleze" @@ -230,7 +236,7 @@ msgid "Russian" msgstr "Ruse" msgid "Slovak" -msgstr "Slovake" +msgstr "Sllovake " msgid "Slovenian" msgstr "Slovene" @@ -257,7 +263,7 @@ msgid "Telugu" msgstr "Telugu" msgid "Thai" -msgstr "Tailandeze" +msgstr "Tajlandeze" msgid "Turkish" msgstr "Turke" @@ -295,8 +301,17 @@ msgstr "Kartela Statike" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "Ai numër faqeje s’është numër i plotë" + +msgid "That page number is less than 1" +msgstr "Ai numër faqeje është më i vogël se 1" + +msgid "That page contains no results" +msgstr "Ajo faqe s’përmban përfundime" + msgid "Enter a valid value." -msgstr "Jepni vlerë të vlefshme." +msgstr "Jepni një vlerë të vlefshme." msgid "Enter a valid URL." msgstr "Jepni një URL të vlefshme." @@ -307,10 +322,11 @@ msgstr "Jepni një numër të plotë të vlefshëm." msgid "Enter a valid email address." msgstr "Jepni një adresë email të vlefshme." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" -"Jepni një 'slug' të vlefshëm, të përbërë nga shkronja, numra, nëvija ose " +"Jepni një 'slug' të vlefshëm, të përbërë nga shkronja, numra, nënvija ose " "vija në mes." msgid "" @@ -321,30 +337,28 @@ msgstr "" "vija ndarëse Unikod." msgid "Enter a valid IPv4 address." -msgstr "Jepni një vendndodhje të vlefshme IPv4." +msgstr "Jepni një adresë IPv4 të vlefshme." msgid "Enter a valid IPv6 address." -msgstr "Jepni një adresë IPv6 të vlefshme" +msgstr "Jepni një adresë IPv6 të vlefshme." msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Jepninjë adresë IPv4 ose IPv6 të vlefshme." +msgstr "Jepni një adresë IPv4 ose IPv6 të vlefshme." msgid "Enter only digits separated by commas." msgstr "Jepni vetëm shifra të ndara nga presje." #, python-format msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." -msgstr "" -"Sigurohuni që kjo vlerë të jetë %(limit_value)s (është %(show_value)s)." +msgstr "Siguroni që kjo vlerë të jetë %(limit_value)s (është %(show_value)s)." #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." -msgstr "" -"Sigurohuni që kjo vlerë të jetë më e vogël ose baraz me %(limit_value)s." +msgstr "Siguroni që kjo vlerë të jetë më e vogël ose baras me %(limit_value)s." #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." -msgstr "Sigurohuni që kjo vlerë është më e madhe ose baraz me %(limit_value)s." +msgstr "Siguroni që kjo vlerë është më e madhe ose baras me %(limit_value)s." #, python-format msgid "" @@ -374,17 +388,20 @@ msgstr[1] "" "Sigurohuni që kjo vlerë ka të shumtën %(limit_value)d shenja (ka " "%(show_value)d)." +msgid "Enter a number." +msgstr "Jepni një numër." + #, 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] "Sigurohuni që nuk ka më tepër se %(max)s shifër gjithsej." -msgstr[1] "Sigurohuni që nuk ka më tepër se %(max)s shifra gjithsej." +msgstr[0] "Sigurohuni që s’ka më tepër se %(max)s shifër gjithsej." +msgstr[1] "Sigurohuni që s’ka më tepër se %(max)s shifra gjithsej." #, 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] "Sigurohuni që nuk ka më shumë se %(max)s vend dhjetor." -msgstr[1] "Sigurohuni që nuk ka më shumë se %(max)s vende dhjetore." +msgstr[0] "Sigurohuni që s’ka më shumë se %(max)s vend dhjetor." +msgstr[1] "Sigurohuni që s’ka më shumë se %(max)s vende dhjetore." #, python-format msgid "" @@ -392,12 +409,23 @@ msgid "" msgid_plural "" "Ensure that there are no more than %(max)s digits before the decimal point." msgstr[0] "" -"Sigurohuni që nuk ka më tepër se %(max)s shifër para presjes dhjetore." +"Sigurohuni që s’ka më tepër se %(max)s shifër para presjes dhjetore." msgstr[1] "" -"Sigurohuni që nuk ka më tepër se %(max)s shifra para presjes dhjetore." +"Sigurohuni që s’ka më tepër se %(max)s shifra para presjes dhjetore." + +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Zgjatimi '%(extension)s' për kartela nuk lejohet. Zgjatime të lejuara janë: " +"'%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Nuk lejohen shenja null." msgid "and" -msgstr " dhe " +msgstr "dhe " #, python-format msgid "%(model_name)s with this %(field_labels)s already exists." @@ -405,13 +433,13 @@ msgstr "Ka tashmë %(model_name)s me këtë %(field_labels)s." #, python-format msgid "Value %(value)r is not a valid choice." -msgstr "Vlera %(value)r nuk është një nga zgjedhjet e vlefshme." +msgstr "Vlera %(value)r s’është zgjedhje e vlefshme." msgid "This field cannot be null." -msgstr "Kjo fushë nuk mund të jetë bosh." +msgstr "Kjo fushë s’mund të përmbajë shenja null." msgid "This field cannot be blank." -msgstr "Kjo fushë nuk mund të jetë e zbrazët." +msgstr "Kjo fushë s’mund të jetë e paplotësuar." #, python-format msgid "%(model_name)s with this %(field_label)s already exists." @@ -443,6 +471,10 @@ msgstr "Numër i plotë i madh (8 bajte)" msgid "'%(value)s' value must be either True or False." msgstr "Vlera '%(value)s' duhet të jetë True ose False." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "Vlera për '%(value)s' duhet të jetë ose True, ose False, ose None." + msgid "Boolean (Either True or False)" msgstr "Buleane (Ose True, ose False)" @@ -466,7 +498,7 @@ msgid "" "'%(value)s' value has the correct format (YYYY-MM-DD) but it is an invalid " "date." msgstr "" -"Vlera '%(value)s' ka formatin e saktë (YYYY-MM-DD) por është datë e " +"Vlera '%(value)s' ka formatin e saktë (YYYY-MM-DD), por është datë e " "pavlefshme." msgid "Date (without time)" @@ -485,7 +517,7 @@ msgid "" "'%(value)s' value has the correct format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]" "[TZ]) but it is an invalid date/time." msgstr "" -"Vlera '%(value)s' ka format të saktë (YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) " +"Vlera '%(value)s' ka format të saktë (YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]), " "por është datë/kohë e pavlefshme." msgid "Date (with time)" @@ -565,7 +597,7 @@ msgid "" "invalid time." msgstr "" "Vlera '%(value)s' ka formatin e saktë (HH:MM[:ss[.uuuuuu]]) por është kohë e " -"palvefshme." +"pavlefshme." msgid "Time" msgstr "Kohë" @@ -580,6 +612,9 @@ msgstr "Të dhëna dyore të papërpunuara" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' s’është UUID i vlefshëm." +msgid "Universally unique identifier" +msgstr "Identifikues universalisht unik" + msgid "File" msgstr "Kartelë" @@ -588,7 +623,7 @@ msgstr "Figurë" #, python-format msgid "%(model)s instance with %(field)s %(value)r does not exist." -msgstr "Instanca %(model)s me %(field)s %(value)r nuk ekziston." +msgstr "Instanca %(model)s me %(field)s %(value)r s’ekziston." msgid "Foreign Key (type determined by related field)" msgstr "Kyç i Jashtëm (lloj i përcaktuar nga fusha përkatëse)" @@ -598,11 +633,11 @@ msgstr "Marrëdhënie një-për-një" #, python-format msgid "%(from)s-%(to)s relationship" -msgstr "" +msgstr "Marrëdhënie %(from)s-%(to)s" #, python-format msgid "%(from)s-%(to)s relationships" -msgstr "" +msgstr "Marrëdhënie %(from)s-%(to)s" msgid "Many-to-many relationship" msgstr "Marrëdhënie shumë-për-shumë" @@ -619,9 +654,6 @@ msgstr "Kjo fushë është e domosdoshme." msgid "Enter a whole number." msgstr "Jepni një numër të tërë." -msgid "Enter a number." -msgstr "Jepni një numër." - msgid "Enter a valid date." msgstr "Jepni një datë të vlefshme." @@ -634,15 +666,19 @@ msgstr "Jepni një datë/kohë të vlefshme." msgid "Enter a valid duration." msgstr "Jepni një kohëzgjatje të vlefshme." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Numri i ditëve duhet të jetë mes {min_days} dhe {max_days}." + msgid "No file was submitted. Check the encoding type on the form." msgstr "" -"Nuk u parashtrua ndonjë kartelë. Kontrolloni llojin e kodimit te forma." +"S’u parashtrua ndonjë kartelë. Kontrolloni llojin e kodimit te formulari." msgid "No file was submitted." -msgstr "Nuk u parashtrua kartelë." +msgstr "S’u parashtrua kartelë." msgid "The submitted file is empty." -msgstr "Kartela e parashtruar është bosh." +msgstr "Kartela e parashtruar është e zbrazët." #, python-format msgid "Ensure this filename has at most %(max)d character (it has %(length)d)." @@ -670,7 +706,7 @@ msgstr "" #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "" -"Përzgjidhni një zgjedhje të vlefshme. %(value)s nuk është nga zgjedhjet e " +"Përzgjidhni një zgjedhje të vlefshme. %(value)s s’është një nga zgjedhjet e " "mundshme." msgid "Enter a list of values." @@ -706,35 +742,34 @@ msgstr[0] "Ju lutemi, parashtroni %d ose më shumë formularë." msgstr[1] "Ju lutemi, parashtroni %d ose më shumë formularë." msgid "Order" -msgstr "Rend" +msgstr "Renditi" msgid "Delete" msgstr "Fshije" #, python-format msgid "Please correct the duplicate data for %(field)s." -msgstr "Ju lutemi, ndreqni të dhënat dyfishe për %(field)s." +msgstr "Ju lutemi, ndreqni të dhënat e përsëdytura për %(field)s." #, python-format msgid "Please correct the duplicate data for %(field)s, which must be unique." msgstr "" -"Ju lutemi, ndreqni të dhënat dyfishe për %(field)s, të cilat duhet të jenë " -"unike." +"Ju lutemi, ndreqni të dhënat e përsëdytura për %(field)s, të cilat duhet të " +"jenë unike." #, 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 "" -"Ju lutemi, ndreqni të dhënat dyfishe për %(field_name)s të cilat duhet të " -"jenë unike për %(lookup)s te %(date_field)s." +"Ju lutemi, ndreqni të dhënat e përsëdytura për %(field_name)s të cilat duhet " +"të jenë unike për %(lookup)s te %(date_field)s." msgid "Please correct the duplicate values below." -msgstr "Ju lutemi, ndreqni vlerat dyfishe më poshtë." +msgstr "Ju lutemi, ndreqni më poshtë vlerat e përsëdytura." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "" -"Kyçi i jashtëm \"inline\" nuk u përputh me kyçin parësor të instancës mëmë." +msgid "The inline value did not match the parent instance." +msgstr "Vlera e brendshme s’u përputh me instancën prind." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" @@ -742,16 +777,19 @@ msgstr "" "zgjedhjet e mundshme." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" nuk është vlerë e vlefshme për kyç parësor." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" s’është vlerë e vlefshme." #, 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 nuk u interpretua dot brenda zonë kohore %(current_timezone)s; " -"mund të jetë e dykuptimtë ose mund të mos ekzistojë." +"%(datetime)s s’u interpretua dot brenda zonës kohore %(current_timezone)s; " +"mund të jetë e dykuptimtë, ose mund të mos ekzistojë." + +msgid "Clear" +msgstr "Pastroje" msgid "Currently" msgstr "Tani" @@ -759,9 +797,6 @@ msgstr "Tani" msgid "Change" msgstr "Ndryshoje" -msgid "Clear" -msgstr "Pastroje" - msgid "Unknown" msgstr "E panjohur" @@ -816,7 +851,7 @@ msgid "midnight" msgstr "mesnatë" msgid "noon" -msgstr "meditë" +msgstr "mesditë" msgid "Monday" msgstr "E hënë" @@ -924,7 +959,7 @@ msgid "sep" msgstr "sht" msgid "oct" -msgstr "oct" +msgstr "tet" msgid "nov" msgstr "nën" @@ -1029,12 +1064,12 @@ msgid "December" msgstr "Dhjetor" msgid "This is not a valid IPv6 address." -msgstr "Kjo nuk është adresë IPv6 e vlefshme." +msgstr "Kjo s’është adresë IPv6 e vlefshme." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s…" msgid "or" msgstr "ose" @@ -1047,7 +1082,7 @@ msgstr ", " msgid "%d year" msgid_plural "%d years" msgstr[0] "%d vit" -msgstr[1] "%d vjetë" +msgstr[1] "%d vjet" #, python-format msgid "%d month" @@ -1094,9 +1129,9 @@ msgid "" "required for security reasons, to ensure that your browser is not being " "hijacked by third parties." msgstr "" -"Këtë mesazh po e shihni ngaqë ky sajt HTTPS e ka të domosdpshme dërgimin e " -"'Referer header' te shfletuesi juaj Web, por nuk u dërgua ndonjë i tillë. " -"Kjo krye është e domosdoshme për arsye sigurie, për të bërë të mundur që " +"Këtë mesazh po e shihni ngaqë ky sajt HTTPS e ka të domosdoshme dërgimin e " +"'Referer header' te shfletuesi juaj Web, por s’u dërgua ndonjë i tillë. Kjo " +"krye është e domosdoshme për arsye sigurie, për të bërë të mundur që " "shfletuesi juaj të mos komprometohet nga palë të treta." msgid "" @@ -1108,6 +1143,19 @@ msgstr "" "lutemi, riaktivizojini ato, të paktën për këtë sajt, ose për lidhjet HTTPS, " "ose për kërkesat '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." +msgstr "" +"Nëse përdorni etiketën ose " +"kryet e përfshira 'Referrer-Policy: no-referrer', ju lutemi, hiqini. " +"Mbrojtja CSRF lyp që kryet 'Referer' të kryejnë kontroll strikt referuesi. " +"Nëse shqetësoheni për privatësinë, përdorni alternativa si për lidhje te sajte palësh të treta." + 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 " @@ -1127,33 +1175,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Më tepër të dhëna mund të gjeni me DEBUG=True." -msgid "Welcome to Django" -msgstr "Mirë se vini te Django" - -msgid "It worked!" -msgstr "Funksionoi!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Urime për faqen tuaj të parë me Django." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Kuptohet, që s’keni bërë ende ndonjë punë. Në vazhdim, filloni aplikacionin " -"tuaj të parë duke xhiruar python manage.py startapp [app_label]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Po e shihni këtë mesazh ngaqë keni caktuar DEBUG = True te " -"kartela juaj e rregullimeve Django dhe s’keni formësuar ndonjë URL. Vijuni " -"punës!" - msgid "No year specified" msgstr "Nuk është caktuar vit" +msgid "Date out of range" +msgstr "Datë jashtë intervali" + msgid "No month specified" msgstr "Nuk është caktuar muaj" @@ -1182,10 +1209,10 @@ msgstr "" #, python-format msgid "No %(verbose_name)s found matching the query" -msgstr "Nuk u gjetën %(verbose_name)s me përputhje" +msgstr "S’u gjetën %(verbose_name)s me përputhje" msgid "Page is not 'last', nor can it be converted to an int." -msgstr "Faqja nuk është 'last', as mund të shndërrohet në një int." +msgstr "Faqja s’është 'last', as mund të shndërrohet në një int." #, python-format msgid "Invalid page (%(page_number)s): %(message)s" @@ -1196,12 +1223,56 @@ msgid "Empty list and '%(class_name)s.allow_empty' is False." msgstr "Listë e zbrazët dhe '%(class_name)s.allow_empty' është False." msgid "Directory indexes are not allowed here." -msgstr "Këtu nuk lejohen treguesa drejtorish." +msgstr "Këtu s’lejohen tregues drejtorish." #, python-format msgid "\"%(path)s\" does not exist" -msgstr "\"%(path)s\" nuk ekziston" +msgstr "\"%(path)s\" s’ekziston" #, python-format msgid "Index of %(directory)s" msgstr "Tregues i %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: platforma Web për perfeksionistë me afate." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Shihni shënimet për hedhjen në qarkullim të " +"Django %(version)s" + +msgid "The install worked successfully! Congratulations!" +msgstr "Instalimi funksionoi me sukses! Përgëzime!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Po e shihni këtë faqe ngaqë te kartela juaj e rregullimeve gjendet DEBUG=True dhe s’keni formësuar ndonjë URL." + +msgid "Django Documentation" +msgstr "Dokumentim i Django-s" + +msgid "Topics, references, & how-to's" +msgstr "Tema, referenca, & how-to" + +msgid "Tutorial: A Polling App" +msgstr "Përkujdesore: Një Aplikacion Për Sondazhe" + +msgid "Get started with Django" +msgstr "Si t’ia filloni me Django-n" + +msgid "Django Community" +msgstr "Bashkësia Django" + +msgid "Connect, get help, or contribute" +msgstr "Lidhuni, merrni ndihmë, ose jepni ndihmesë" diff --git a/django/conf/locale/sq/formats.py b/django/conf/locale/sq/formats.py index 0fb21a6583aa..2f0da0d40022 100644 --- a/django/conf/locale/sq/formats.py +++ b/django/conf/locale/sq/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'd F Y' TIME_FORMAT = 'g.i.A' # DATETIME_FORMAT = @@ -15,7 +12,7 @@ # FIRST_DAY_OF_WEEK = # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # DATE_INPUT_FORMATS = # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = diff --git a/django/conf/locale/sr/LC_MESSAGES/django.mo b/django/conf/locale/sr/LC_MESSAGES/django.mo index a14adaa99ab3..0a8665ef11f6 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 1ff12a283c03..81df886749cd 100644 --- a/django/conf/locale/sr/LC_MESSAGES/django.po +++ b/django/conf/locale/sr/LC_MESSAGES/django.po @@ -1,15 +1,17 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Branko Kokanovic , 2018 +# Igor Jerosimić, 2019 # Jannis Leidel , 2011 # Janos Guljas , 2011-2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-06-27 19:31+0000\n" +"Last-Translator: Igor Jerosimić\n" "Language-Team: Serbian (http://www.transifex.com/django/django/language/" "sr/)\n" "MIME-Version: 1.0\n" @@ -20,13 +22,13 @@ msgstr "" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" msgid "Afrikaans" -msgstr "" +msgstr "африкански" msgid "Arabic" msgstr "арапски" msgid "Asturian" -msgstr "" +msgstr "астуријски" msgid "Azerbaijani" msgstr "азербејџански" @@ -35,13 +37,13 @@ msgid "Bulgarian" msgstr "бугарски" msgid "Belarusian" -msgstr "" +msgstr "белоруски" msgid "Bengali" msgstr "бенгалски" msgid "Breton" -msgstr "" +msgstr "бретонски" msgid "Bosnian" msgstr "босански" @@ -62,7 +64,7 @@ msgid "German" msgstr "немачки" msgid "Lower Sorbian" -msgstr "" +msgstr "доњолужичкосрпски" msgid "Greek" msgstr "грчки" @@ -71,13 +73,13 @@ msgid "English" msgstr "енглески" msgid "Australian English" -msgstr "" +msgstr "аустралијски енглески" msgid "British English" msgstr "британски енглески" msgid "Esperanto" -msgstr "" +msgstr "есперанто" msgid "Spanish" msgstr "шпански" @@ -86,7 +88,7 @@ msgid "Argentinian Spanish" msgstr "аргентински шпански" msgid "Colombian Spanish" -msgstr "" +msgstr "колумбијски шпански" msgid "Mexican Spanish" msgstr "мексички шпански" @@ -95,7 +97,7 @@ msgid "Nicaraguan Spanish" msgstr "никарагвански шпански" msgid "Venezuelan Spanish" -msgstr "" +msgstr "венецуелански шпански" msgid "Estonian" msgstr "естонски" @@ -119,10 +121,10 @@ msgid "Irish" msgstr "ирски" msgid "Scottish Gaelic" -msgstr "" +msgstr "шкотски гелски" msgid "Galician" -msgstr "галски" +msgstr "галицијски" msgid "Hebrew" msgstr "хебрејски" @@ -134,19 +136,22 @@ msgid "Croatian" msgstr "хрватски" msgid "Upper Sorbian" -msgstr "" +msgstr "горњолужичкосрпски" msgid "Hungarian" msgstr "мађарски" +msgid "Armenian" +msgstr "јерменски" + msgid "Interlingua" -msgstr "" +msgstr "интерлингва" msgid "Indonesian" msgstr "индонежански" msgid "Ido" -msgstr "" +msgstr "идо" msgid "Icelandic" msgstr "исландски" @@ -160,11 +165,14 @@ msgstr "јапански" msgid "Georgian" msgstr "грузијски" +msgid "Kabyle" +msgstr "кабилски" + msgid "Kazakh" -msgstr "" +msgstr "казашки" msgid "Khmer" -msgstr "камбодијски" +msgstr "кмерски" msgid "Kannada" msgstr "канада" @@ -173,7 +181,7 @@ msgid "Korean" msgstr "корејски" msgid "Luxembourgish" -msgstr "" +msgstr "луксембуршки" msgid "Lithuanian" msgstr "литвански" @@ -191,16 +199,16 @@ msgid "Mongolian" msgstr "монголски" msgid "Marathi" -msgstr "" +msgstr "маратхи" msgid "Burmese" -msgstr "" +msgstr "бурмански" msgid "Norwegian Bokmål" -msgstr "" +msgstr "норвешки књижевни" msgid "Nepali" -msgstr "" +msgstr "непалски" msgid "Dutch" msgstr "холандски" @@ -209,10 +217,10 @@ msgid "Norwegian Nynorsk" msgstr "норвешки нови" msgid "Ossetic" -msgstr "" +msgstr "осетински" msgid "Punjabi" -msgstr "Панџаби" +msgstr "панџаби" msgid "Polish" msgstr "пољски" @@ -248,7 +256,7 @@ msgid "Swedish" msgstr "шведски" msgid "Swahili" -msgstr "" +msgstr "свахили" msgid "Tamil" msgstr "тамилски" @@ -263,37 +271,46 @@ msgid "Turkish" msgstr "турски" msgid "Tatar" -msgstr "" +msgstr "татарски" msgid "Udmurt" -msgstr "" +msgstr "удмуртски" msgid "Ukrainian" msgstr "украјински" msgid "Urdu" -msgstr "Урду" +msgstr "урду" msgid "Vietnamese" msgstr "вијетнамски" msgid "Simplified Chinese" -msgstr "новокинески" +msgstr "поједностављени кинески" msgid "Traditional Chinese" -msgstr "старокинески" +msgstr "традиционални кинески" msgid "Messages" -msgstr "" +msgstr "Poruke" msgid "Site Maps" -msgstr "" +msgstr "Мапе сајта" msgid "Static Files" -msgstr "" +msgstr "Статички фајлови" msgid "Syndication" -msgstr "" +msgstr "Удруживање садржаја" + +msgid "That page number is not an integer" +msgstr "Задати број стране није цео број" + +msgid "That page number is less than 1" +msgstr "Задати број стране је мањи од 1" + +msgid "That page contains no results" +msgstr "Тражена страна не садржи резултате" msgid "Enter a valid value." msgstr "Унесите исправну вредност." @@ -302,21 +319,24 @@ msgid "Enter a valid URL." msgstr "Унесите исправан URL." msgid "Enter a valid integer." -msgstr "" +msgstr "Унесите исправан цео број." msgid "Enter a valid email address." -msgstr "" +msgstr "Унесите исправну и-мејл адресу." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" -"Унесите исрпаван „слаг“, који се састоји од слова, бројки, доњих црта или " -"циртица." +"Унесите исправан 'слаг', који се састоји од слова, бројки, доњих црта или " +"цртица." msgid "" "Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or " "hyphens." msgstr "" +"Унесите исправан 'слаг', који се састоји од Уникод слова, бројки, доњих црта " +"или цртица." msgid "Enter a valid IPv4 address." msgstr "Унесите исправну IPv4 адресу." @@ -328,7 +348,7 @@ msgid "Enter a valid IPv4 or IPv6 address." msgstr "Унесите исправну IPv4 или IPv6 адресу." msgid "Enter only digits separated by commas." -msgstr "Унесите само бројке раздвојене запетама." +msgstr "Унесите само цифре раздвојене запетама." #, python-format msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." @@ -350,8 +370,14 @@ msgid_plural "" "Ensure this value has at least %(limit_value)d characters (it has " "%(show_value)d)." msgstr[0] "" +"Ово поље мора да има најмање %(limit_value)d карактер (тренутно има " +"%(show_value)d)." msgstr[1] "" +"Ово поље мора да има најмање %(limit_value)d карактера (тренутно има " +"%(show_value)d)." msgstr[2] "" +"Ово поље мора да има најмање %(limit_value)d карактера (тренутно има " +"%(show_value)d)." #, python-format msgid "" @@ -361,52 +387,72 @@ msgid_plural "" "Ensure this value has at most %(limit_value)d characters (it has " "%(show_value)d)." msgstr[0] "" +"Ово поље не сме да има више од %(limit_value)d карактера (тренутно има " +"%(show_value)d)." msgstr[1] "" +"Ово поље не сме да има више од %(limit_value)d карактера (тренутно има " +"%(show_value)d)." msgstr[2] "" +"Ово поље не сме да има више од %(limit_value)d карактера (тренутно има " +"%(show_value)d)." + +msgid "Enter a number." +msgstr "Унесите број." #, 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[0] "Укупно не може бити више од %(max)s цифре." +msgstr[1] "Укупно не може бити више од %(max)s цифре." +msgstr[2] "Укупно не може бити више од %(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] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Не може бити више од %(max)s децимале." +msgstr[1] "Не може бити више од %(max)s децимале." +msgstr[2] "Не може бити више од %(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] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Не може бити више од %(max)s цифре пре децималног зареза." +msgstr[1] "Не може бити више од %(max)s цифре пре децималног зареза." +msgstr[2] "Не може бити више од %(max)s цифара пре децималног зареза." + +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Фајл екстензија '%(extension)s' није дозвољена. Дозвољене су следеће " +"екстензије: '%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "'Null' карактери нису дозвољени." msgid "and" msgstr "и" #, python-format msgid "%(model_name)s with this %(field_labels)s already exists." -msgstr "" +msgstr "%(model_name)s са пољем %(field_labels)s већ постоји." #, python-format msgid "Value %(value)r is not a valid choice." -msgstr "" +msgstr "Вредност %(value)r није валидна." msgid "This field cannot be null." -msgstr "Ово поље не може да остане празно." +msgstr "Ово поље не може бити 'null'." msgid "This field cannot be blank." msgstr "Ово поље не може да остане празно." #, python-format msgid "%(model_name)s with this %(field_label)s already exists." -msgstr "%(model_name)s са овом вредношћу %(field_label)s већ постоји." +msgstr "%(model_name)s са пољем %(field_label)s већ постоји." #. Translators: The 'lookup_type' is one of 'date', 'year' or 'month'. #. Eg: "Title must be unique for pub_date year" @@ -414,31 +460,37 @@ msgstr "%(model_name)s са овом вредношћу %(field_label)s већ msgid "" "%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." msgstr "" +"%(field_label)s мора бити јединствен(a) за %(date_field_label)s " +"%(lookup_type)s." #, python-format msgid "Field of type: %(field_type)s" -msgstr "Поње типа: %(field_type)s" +msgstr "Поље типа: %(field_type)s" msgid "Integer" msgstr "Цео број" #, python-format msgid "'%(value)s' value must be an integer." -msgstr "" +msgstr "Вредност '%(value)s' мора бити цео број." msgid "Big (8 byte) integer" -msgstr "Велики цео број" +msgstr "Велики (8 бајтова) цео број" #, 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 "'%(value)s' вредност мора бити или True, False, или None." msgid "Boolean (Either True or False)" msgstr "Булова вредност (True или False)" #, python-format msgid "String (up to %(max_length)s)" -msgstr "Стринг (највише %(max_length)s знакова)" +msgstr "Стринг са макс. дужином %(max_length)s" msgid "Comma-separated integers" msgstr "Цели бројеви раздвојени запетама" @@ -448,12 +500,16 @@ msgid "" "'%(value)s' value has an invalid date format. It must be in YYYY-MM-DD " "format." msgstr "" +"Вредност '%(value)s' нема валидан формат датума. Мора бити у формату ГГГГ-ММ-" +"ДД ." #, python-format msgid "" "'%(value)s' value has the correct format (YYYY-MM-DD) but it is an invalid " "date." msgstr "" +"Вредност '%(value)s' има исправан формат (ГГГГ-ММ-ДД) али то није валидан " +"датум." msgid "Date (without time)" msgstr "Датум (без времена)" @@ -463,19 +519,23 @@ msgid "" "'%(value)s' value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." "uuuuuu]][TZ] format." msgstr "" +"Вредност '%(value)s' нема валидан формат. Мора бити у формату ГГГГ-ММ-ДД ЧЧ:" +"ММ[:сс[.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' има исправан формат (ГГГГ-ММ-ДД ЧЧ:ММ[:сс[.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 "Децимални број" @@ -485,9 +545,11 @@ msgid "" "'%(value)s' value has an invalid format. It must be in [DD] [HH:[MM:]]ss[." "uuuuuu] format." msgstr "" +"'%(value)s' нема валидан формат. Мора бити у формату [ДД] [ЧЧ:[ММ:]]ss[." +"uuuuuu] ." msgid "Duration" -msgstr "" +msgstr "Временски интервал" msgid "Email address" msgstr "Имејл адреса" @@ -497,20 +559,20 @@ msgstr "Путања фајла" #, python-format msgid "'%(value)s' value must be a float." -msgstr "" +msgstr "Вредност '%(value)s' мора бити 'float'." msgid "Floating point number" -msgstr "Број са покреном запетом" +msgstr "Број са покретним зарезом" msgid "IPv4 address" -msgstr "IPv4 adresa" +msgstr "IPv4 адреса" msgid "IP address" 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)" @@ -523,7 +585,7 @@ msgstr "Позитиван мали цео број" #, python-format msgid "Slug (up to %(max_length)s)" -msgstr "Слаг (не дужи од %(max_length)s)" +msgstr "Слаг са макс. дужином %(max_length)s" msgid "Small integer" msgstr "Мали цео број" @@ -536,12 +598,16 @@ msgid "" "'%(value)s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " "format." msgstr "" +"Вредност '%(value)s' нема валидан формат. Мора бити у формату ЧЧ:ММ[:сс[." +"uuuuuu]] ." #, python-format msgid "" "'%(value)s' value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " "invalid time." msgstr "" +"Вредност '%(value)s' има исправан формат (ЧЧ:ММ[:сс[.uuuuuu]]) али то није " +"валидно време." msgid "Time" msgstr "Време" @@ -550,11 +616,14 @@ msgid "URL" msgstr "URL" msgid "Raw binary data" -msgstr "" +msgstr "Сирови бинарни подаци" #, python-format msgid "'%(value)s' is not a valid UUID." -msgstr "" +msgstr "'%(value)s' није валидан UUID." + +msgid "Universally unique identifier" +msgstr "Универзално јединствени идентификатор" msgid "File" msgstr "Фајл" @@ -564,21 +633,21 @@ msgstr "Слика" #, python-format msgid "%(model)s instance with %(field)s %(value)r does not exist." -msgstr "" +msgstr "%(model)s инстанца са вредношћу %(value)r у пољу %(field)s не постоји." msgid "Foreign Key (type determined by related field)" -msgstr "Страни кључ (тип одређује референтно поље)" +msgstr "Спољни кључ (тип је одређен асоцираном колоном)" msgid "One-to-one relationship" msgstr "Релација један на један" #, python-format msgid "%(from)s-%(to)s relationship" -msgstr "" +msgstr "Релација %(from)s-%(to)s" #, python-format msgid "%(from)s-%(to)s relationships" -msgstr "" +msgstr "Релације %(from)s-%(to)s" msgid "Many-to-many relationship" msgstr "Релација више на више" @@ -587,7 +656,7 @@ msgstr "Релација више на више" #. characters will prevent the default label_suffix to be appended to the #. label msgid ":?.!" -msgstr "" +msgstr ":?.!" msgid "This field is required." msgstr "Ово поље се мора попунити." @@ -595,9 +664,6 @@ msgstr "Ово поље се мора попунити." msgid "Enter a whole number." msgstr "Унесите цео број." -msgid "Enter a number." -msgstr "Унесите број." - msgid "Enter a valid date." msgstr "Унесите исправан датум." @@ -608,24 +674,31 @@ msgid "Enter a valid date/time." msgstr "Унесите исправан датум/време." msgid "Enter a valid duration." -msgstr "" +msgstr "Унесите исправан временски интервал." + +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Број дана мора бити између {min_days} и {max_days}." msgid "No file was submitted. Check the encoding type on the form." -msgstr "Фајл није пребачен. Проверите тип енкодирања формулара." +msgstr "Фајл није пребачен. Проверите тип енкодирања на форми." msgid "No file was submitted." msgstr "Фајл није пребачен." msgid "The submitted file is empty." -msgstr "Пребачен фајл је празан." +msgstr "Пребачени фајл је празан." #, python-format 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] "" +"Име фајла не може имати више од %(max)d карактера (тренутно има %(length)d)." msgstr[1] "" +"Име фајла не може имати више од %(max)d карактера (тренутно има %(length)d)." msgstr[2] "" +"Име фајла не може имати више од %(max)d карактера (тренутно има %(length)d)." msgid "Please either submit a file or check the clear checkbox, not both." msgstr "Може се само послати фајл или избрисати, не оба." @@ -646,35 +719,35 @@ msgid "Enter a list of values." msgstr "Унесите листу вредности." msgid "Enter a complete value." -msgstr "" +msgstr "Унесите комплетну вредност." msgid "Enter a valid UUID." -msgstr "" +msgstr "Унесите исправан UUID." #. Translators: This is the default suffix added to form field labels msgid ":" -msgstr "" +msgstr ":" #, python-format msgid "(Hidden field %(name)s) %(error)s" -msgstr "" +msgstr "(Скривено поље %(name)s) %(error)s" msgid "ManagementForm data is missing or has been tampered with" -msgstr "" +msgstr "ManagementForm недостаје или је измењена на погрешан начин." #, python-format msgid "Please submit %d or fewer forms." msgid_plural "Please submit %d or fewer forms." -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Попуните и проследите највише %d форму." +msgstr[1] "Попуните и проследите највише %d форме." +msgstr[2] "Попуните и проследите највише %d форми." #, python-format msgid "Please submit %d or more forms." msgid_plural "Please submit %d or more forms." -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Попуните и проследите најмање %d форму." +msgstr[1] "Попуните и проследите највише %d форме." +msgstr[2] "Попуните и проследите највише %d форми." msgid "Order" msgstr "Редослед" @@ -684,41 +757,45 @@ msgstr "Обриши" #, python-format msgid "Please correct the duplicate data for %(field)s." -msgstr "Исправите дуплиран садржај за поља: %(field)s." +msgstr "Исправите вредност за поље %(field)s - оно мора бити јединствено." #, python-format msgid "Please correct the duplicate data for %(field)s, which must be unique." msgstr "" -"Исправите дуплиран садржај за поља: %(field)s, који мора да буде јединствен." +"Исправите вредности за поља %(field)s - њихова комбинација мора бити " +"јединствена." #, 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 "" -"Исправите дуплиран садржај за поља: %(field_name)s, који мора да буде " -"јединствен за %(lookup)s у %(date_field)s." +"Иправите вредност за поље %(field_name)s, оно мора бити јединствено за " +"%(lookup)s у %(date_field)s." msgid "Please correct the duplicate values below." msgstr "Исправите дуплиране вредности доле." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "Страни кључ се није поклопио са инстанцом родитељског кључа." +msgid "The inline value did not match the parent instance." +msgstr "Директно унета вредност не одговара инстанци родитеља." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Одабрана вредност није међу понуђенима. Одаберите једну од понуђених." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "" +msgid "\"%(pk)s\" is not a valid value." +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." +"%(datetime)s се не може се представити у временској зони " +"%(current_timezone)s - или је двосмислено или можда не постоји." + +msgid "Clear" +msgstr "Очисти" msgid "Currently" msgstr "Тренутно" @@ -726,9 +803,6 @@ msgstr "Тренутно" msgid "Change" msgstr "Измени" -msgid "Clear" -msgstr "Очисти" - msgid "Unknown" msgstr "Непознато" @@ -997,11 +1071,11 @@ msgid "December" msgstr "Децембар" msgid "This is not a valid IPv6 address." -msgstr "" +msgstr "Ово није валидна IPv6 адреса." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." +msgid "%(truncated_text)s…" msgstr "%(truncated_text)s..." msgid "or" @@ -1009,58 +1083,58 @@ msgstr "или" #. Translators: This string is used as a separator between list elements msgid ", " -msgstr "," +msgstr ", " #, python-format msgid "%d year" msgid_plural "%d years" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "%d година" +msgstr[1] "%d године" +msgstr[2] "%d година" #, python-format msgid "%d month" msgid_plural "%d months" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "%d месец" +msgstr[1] "%d месеца" +msgstr[2] "%d месеци" #, python-format msgid "%d week" msgid_plural "%d weeks" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "%d недеља" +msgstr[1] "%d недеље" +msgstr[2] "%d недеља" #, python-format msgid "%d day" msgid_plural "%d days" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "%d дан" +msgstr[1] "%d дана" +msgstr[2] "%d дана" #, python-format msgid "%d hour" msgid_plural "%d hours" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "%d час" +msgstr[1] "%d часа" +msgstr[2] "%d часова" #, python-format msgid "%d minute" msgid_plural "%d minutes" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "%d минут" +msgstr[1] "%d минута" +msgstr[2] "%d минута" msgid "0 minutes" -msgstr "" +msgstr "0 минута" msgid "Forbidden" -msgstr "" +msgstr "Забрањено" msgid "CSRF verification failed. Request aborted." -msgstr "" +msgstr "CSRF верификација није прошла. Захтев одбијен." msgid "" "You are seeing this message because this HTTPS site requires a 'Referer " @@ -1068,49 +1142,58 @@ msgid "" "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." +msgstr "" +"Ако користите таг или " +"'Referrer-Policy: no-referrer' заглавље, молимо да их уклоните. CSRF заштита " +"захтева 'Referer' заглавље да би се обавила стриктна 'referrer' провера. " +"Уколико вас брине приватност, користите алтерантиве као за линкове ка другим сајтовима." 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 "" +"Ова порука је приказана јер овај сајт захтева CSRF куки када се прослеђују " +"подаци из форми. Овај куки је потребан из сигурносних разлога, да би се " +"осигурало да ваш претраживач није под контролом трећих лица." 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 "" +"Ако је ваш претраживач конфигурисан са искљученим кукијима, молимо да их " +"укључите, барем за овај сајт, или за 'same-origin' захтеве." msgid "More information is available with DEBUG=True." -msgstr "" - -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" +msgstr "Више информација је доступно са DEBUG=True." msgid "No year specified" msgstr "Година није назначена" +msgid "Date out of range" +msgstr "Датум ван опсега" + msgid "No month specified" msgstr "Месец није назначен" @@ -1141,11 +1224,11 @@ 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 "Страница није последња, нити може бити конвертована у тип int." +msgstr "Страница није последња, нити може бити конвертована у тип 'int'." #, python-format msgid "Invalid page (%(page_number)s): %(message)s" -msgstr "" +msgstr "Неисправна страна (%(page_number)s): %(message)s" #, python-format msgid "Empty list and '%(class_name)s.allow_empty' is False." @@ -1161,3 +1244,47 @@ msgstr "„%(path)s“ не постоји" #, python-format msgid "Index of %(directory)s" msgstr "Индекс директоријума %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Ђанго: веб окружење за перфекционисте са строгим роковима." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Погледајте напомене уз издање за Ђанго " +"%(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." +msgstr "" +"Ова страна је приказана јер је DEBUG=True у вашим подешавањима и нисте конфигурисали ниједан URL." + +msgid "Django Documentation" +msgstr "Ђанго документација" + +msgid "Topics, references, & how-to's" +msgstr "Теме, референце, & како-да" + +msgid "Tutorial: A Polling App" +msgstr "Упутство: апликација за гласање" + +msgid "Get started with Django" +msgstr "Почните са Ђангом" + +msgid "Django Community" +msgstr "Ђанго заједница" + +msgid "Connect, get help, or contribute" +msgstr "Повежите се, потражите помоћ или дајте допринос" diff --git a/django/conf/locale/sr/formats.py b/django/conf/locale/sr/formats.py index 5c5e48e20e41..94994c7e792c 100644 --- a/django/conf/locale/sr/formats.py +++ b/django/conf/locale/sr/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j. F Y.' TIME_FORMAT = 'H:i' DATETIME_FORMAT = 'j. F Y. H:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d.%m.%Y.', '%d.%m.%y.', # '25.10.2006.', '25.10.06.' '%d. %m. %Y.', '%d. %m. %y.', # '25. 10. 2006.', '25. 10. 06.' diff --git a/django/conf/locale/sr_Latn/LC_MESSAGES/django.mo b/django/conf/locale/sr_Latn/LC_MESSAGES/django.mo index 609d1874b979..873edb352448 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 4166c0831cdc..16d3937e40f7 100644 --- a/django/conf/locale/sr_Latn/LC_MESSAGES/django.po +++ b/django/conf/locale/sr_Latn/LC_MESSAGES/django.po @@ -1,15 +1,16 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Igor Jerosimić, 2019 # Jannis Leidel , 2011 # Janos Guljas , 2011-2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-06-27 19:21+0000\n" +"Last-Translator: Igor Jerosimić\n" "Language-Team: Serbian (Latin) (http://www.transifex.com/django/django/" "language/sr@latin/)\n" "MIME-Version: 1.0\n" @@ -20,13 +21,13 @@ msgstr "" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" msgid "Afrikaans" -msgstr "" +msgstr "afrikanski" msgid "Arabic" msgstr "arapski" msgid "Asturian" -msgstr "" +msgstr "asturijski" msgid "Azerbaijani" msgstr "azerbejdžanski" @@ -35,13 +36,13 @@ msgid "Bulgarian" msgstr "bugarski" msgid "Belarusian" -msgstr "" +msgstr "beloruski" msgid "Bengali" msgstr "bengalski" msgid "Breton" -msgstr "" +msgstr "bretonski" msgid "Bosnian" msgstr "bosanski" @@ -71,13 +72,13 @@ msgid "English" msgstr "engleski" msgid "Australian English" -msgstr "" +msgstr "australijski engleski" msgid "British English" msgstr "britanski engleski" msgid "Esperanto" -msgstr "" +msgstr "esperanto" msgid "Spanish" msgstr "španski" @@ -86,7 +87,7 @@ msgid "Argentinian Spanish" msgstr "argentinski španski" msgid "Colombian Spanish" -msgstr "" +msgstr "kolumbijski španski" msgid "Mexican Spanish" msgstr "meksički španski" @@ -95,7 +96,7 @@ msgid "Nicaraguan Spanish" msgstr "nikaragvanski španski" msgid "Venezuelan Spanish" -msgstr "" +msgstr "venecuelanski španski" msgid "Estonian" msgstr "estonski" @@ -119,7 +120,7 @@ msgid "Irish" msgstr "irski" msgid "Scottish Gaelic" -msgstr "" +msgstr "škotski galski" msgid "Galician" msgstr "galski" @@ -139,6 +140,9 @@ msgstr "" msgid "Hungarian" msgstr "mađarski" +msgid "Armenian" +msgstr "jermenski" + msgid "Interlingua" msgstr "" @@ -146,7 +150,7 @@ msgid "Indonesian" msgstr "indonežanski" msgid "Ido" -msgstr "" +msgstr "ido" msgid "Icelandic" msgstr "islandski" @@ -160,9 +164,12 @@ msgstr "japanski" msgid "Georgian" msgstr "gruzijski" -msgid "Kazakh" +msgid "Kabyle" msgstr "" +msgid "Kazakh" +msgstr "kazaški" + msgid "Khmer" msgstr "kambodijski" @@ -173,7 +180,7 @@ msgid "Korean" msgstr "korejski" msgid "Luxembourgish" -msgstr "" +msgstr "luksemburški" msgid "Lithuanian" msgstr "litvanski" @@ -194,13 +201,13 @@ msgid "Marathi" msgstr "" msgid "Burmese" -msgstr "" +msgstr "burmanski" msgid "Norwegian Bokmål" -msgstr "" +msgstr "norveški književni" msgid "Nepali" -msgstr "" +msgstr "nepalski" msgid "Dutch" msgstr "holandski" @@ -248,7 +255,7 @@ msgid "Swedish" msgstr "švedski" msgid "Swahili" -msgstr "" +msgstr "svahili" msgid "Tamil" msgstr "tamilski" @@ -263,7 +270,7 @@ msgid "Turkish" msgstr "turski" msgid "Tatar" -msgstr "" +msgstr "tatarski" msgid "Udmurt" msgstr "" @@ -284,17 +291,26 @@ msgid "Traditional Chinese" msgstr "starokineski" msgid "Messages" -msgstr "" +msgstr "Poruke" msgid "Site Maps" -msgstr "" +msgstr "Mape sajta" msgid "Static Files" -msgstr "" +msgstr "Statičke datoteke" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "Zadati broj strane nije ceo broj" + +msgid "That page number is less than 1" +msgstr "Zadati broj strane je manji od 1" + +msgid "That page contains no results" +msgstr "Tražena strana ne sadrži rezultate" + msgid "Enter a valid value." msgstr "Unesite ispravnu vrednost." @@ -302,11 +318,12 @@ msgid "Enter a valid URL." msgstr "Unesite ispravan URL." msgid "Enter a valid integer." -msgstr "" +msgstr "Unesite ispravan ceo broj." msgid "Enter a valid email address." -msgstr "" +msgstr "Unesite ispravnu e-mail adresu." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -364,6 +381,9 @@ msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgid "Enter a number." +msgstr "Unesite broj." + #, 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." @@ -387,12 +407,21 @@ msgstr[0] "" msgstr[1] "" msgstr[2] "" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "i" #, python-format msgid "%(model_name)s with this %(field_labels)s already exists." -msgstr "" +msgstr "%(model_name)ssa poljem %(field_labels)sveć postoji." #, python-format msgid "Value %(value)r is not a valid choice." @@ -433,6 +462,10 @@ msgstr "Veliki ceo broj" msgid "'%(value)s' value must be either True or False." msgstr "" +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "" + msgid "Boolean (Either True or False)" msgstr "Bulova vrednost (True ili False)" @@ -556,6 +589,9 @@ msgstr "" msgid "'%(value)s' is not a valid UUID." msgstr "" +msgid "Universally unique identifier" +msgstr "" + msgid "File" msgstr "Fajl" @@ -587,7 +623,7 @@ msgstr "Relacija više na više" #. characters will prevent the default label_suffix to be appended to the #. label msgid ":?.!" -msgstr "" +msgstr ":?.!" msgid "This field is required." msgstr "Ovo polje se mora popuniti." @@ -595,9 +631,6 @@ msgstr "Ovo polje se mora popuniti." msgid "Enter a whole number." msgstr "Unesite ceo broj." -msgid "Enter a number." -msgstr "Unesite broj." - msgid "Enter a valid date." msgstr "Unesite ispravan datum." @@ -610,6 +643,10 @@ msgstr "Unesite ispravan datum/vreme." msgid "Enter a valid duration." msgstr "" +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "" + msgid "No file was submitted. Check the encoding type on the form." msgstr "Fajl nije prebačen. Proverite tip enkodiranja formulara." @@ -649,11 +686,11 @@ msgid "Enter a complete value." msgstr "" msgid "Enter a valid UUID." -msgstr "" +msgstr "Unesite ispravan UUID." #. Translators: This is the default suffix added to form field labels msgid ":" -msgstr "" +msgstr ":" #, python-format msgid "(Hidden field %(name)s) %(error)s" @@ -702,14 +739,14 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Ispravite duplirane vrednosti dole." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "Strani ključ se nije poklopio sa instancom roditeljskog ključa." +msgid "The inline value did not match the parent instance." +msgstr "" 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." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -720,15 +757,15 @@ msgstr "" "Vreme %(datetime)s ne može se predstaviti u vremenskoj zoni " "%(current_timezone)s." +msgid "Clear" +msgstr "Očisti" + msgid "Currently" msgstr "Trenutno" msgid "Change" msgstr "Izmeni" -msgid "Clear" -msgstr "Očisti" - msgid "Unknown" msgstr "Nepoznato" @@ -997,12 +1034,12 @@ msgid "December" msgstr "Decembar" msgid "This is not a valid IPv6 address." -msgstr "" +msgstr "Ovo nije ispravna IPv6 adresa." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "" msgid "or" msgstr "ili" @@ -1054,10 +1091,10 @@ msgstr[1] "" msgstr[2] "" msgid "0 minutes" -msgstr "" +msgstr "0 minuta" msgid "Forbidden" -msgstr "" +msgstr "Zabranjeno" msgid "CSRF verification failed. Request aborted." msgstr "" @@ -1075,6 +1112,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1089,28 +1134,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "Godina nije naznačena" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "Mesec nije naznačen" @@ -1161,3 +1190,41 @@ msgstr "„%(path)s“ ne postoji" #, python-format msgid "Index of %(directory)s" msgstr "Indeks direktorijuma %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/sr_Latn/formats.py b/django/conf/locale/sr_Latn/formats.py index 5c5e48e20e41..94994c7e792c 100644 --- a/django/conf/locale/sr_Latn/formats.py +++ b/django/conf/locale/sr_Latn/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j. F Y.' TIME_FORMAT = 'H:i' DATETIME_FORMAT = 'j. F Y. H:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d.%m.%Y.', '%d.%m.%y.', # '25.10.2006.', '25.10.06.' '%d. %m. %Y.', '%d. %m. %y.', # '25. 10. 2006.', '25. 10. 06.' diff --git a/django/conf/locale/sv/LC_MESSAGES/django.mo b/django/conf/locale/sv/LC_MESSAGES/django.mo index 48ce600c3e72..172bb8c7edf6 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 f531c9aa16fe..52c999bfad1d 100644 --- a/django/conf/locale/sv/LC_MESSAGES/django.po +++ b/django/conf/locale/sv/LC_MESSAGES/django.po @@ -8,17 +8,18 @@ # Jonathan Lindén, 2015 # Jonathan Lindén, 2014 # Mattias Hansson , 2016 -# Mattias Jansson , 2011 +# Mattias Benjaminsson , 2011 +# Petter Strandmark , 2019 # Rasmus Précenth , 2014 # Samuel Linde , 2011 -# Thomas Lundqvist , 2013,2016 +# Thomas Lundqvist, 2013,2016 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-07-07 19:03+0000\n" -"Last-Translator: Mattias Hansson \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-28 14:10+0000\n" +"Last-Translator: Petter Strandmark \n" "Language-Team: Swedish (http://www.transifex.com/django/django/language/" "sv/)\n" "MIME-Version: 1.0\n" @@ -147,6 +148,9 @@ msgstr "Högsorbiska" msgid "Hungarian" msgstr "Ungerska" +msgid "Armenian" +msgstr "Armeniska" + msgid "Interlingua" msgstr "Interlingua" @@ -168,6 +172,9 @@ msgstr "Japanska" msgid "Georgian" msgstr "Georgiska" +msgid "Kabyle" +msgstr "Kabyliska" + msgid "Kazakh" msgstr "Kazakiska" @@ -303,6 +310,15 @@ msgstr "Statiska filer" msgid "Syndication" msgstr "Syndikering" +msgid "That page number is not an integer" +msgstr "Sidnumret är inte ett heltal" + +msgid "That page number is less than 1" +msgstr "Sidnumret är mindre än 1" + +msgid "That page contains no results" +msgstr "Sidan innehåller inga resultat" + msgid "Enter a valid value." msgstr "Fyll i ett giltigt värde." @@ -315,6 +331,7 @@ msgstr "Fyll i ett giltigt heltal." msgid "Enter a valid email address." msgstr "Fyll i en giltig e-postadress." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -383,6 +400,9 @@ msgstr[1] "" "Säkerställ att detta värde har som mest %(limit_value)d tecken (den har " "%(show_value)d)." +msgid "Enter a number." +msgstr "Fyll i ett tal." + #, 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." @@ -405,6 +425,17 @@ msgstr[0] "" msgstr[1] "" "Säkerställ att det inte är mer än %(max)s siffror före decimalavskiljaren." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Filändelse %(extension)s är inte tillåten. Tillåtna ändelser är: " +"”%(allowed_extensions)s”." + +msgid "Null characters are not allowed." +msgstr "Null-tecken är inte tillåtna." + msgid "and" msgstr "och" @@ -452,6 +483,10 @@ msgstr "Stort (8 byte) heltal" msgid "'%(value)s' value must be either True or False." msgstr "Värdet '%(value)s' måste vara antingen True eller False." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "”%(value)s” värde måste vara antingen True, False eller None." + msgid "Boolean (Either True or False)" msgstr "Boolesk (antingen True eller False)" @@ -589,6 +624,9 @@ msgstr "Rå binärdata" msgid "'%(value)s' is not a valid UUID." msgstr "Värdet '%(value)s' är inget giltigt UUID." +msgid "Universally unique identifier" +msgstr "Globalt unik identifierare" + msgid "File" msgstr "Fil" @@ -628,9 +666,6 @@ msgstr "Detta fält måste fyllas i." msgid "Enter a whole number." msgstr "Fyll i ett heltal." -msgid "Enter a number." -msgstr "Fyll i ett tal." - msgid "Enter a valid date." msgstr "Fyll i ett giltigt datum." @@ -643,6 +678,10 @@ msgstr "Fyll i ett giltigt datum/tid." msgid "Enter a valid duration." msgstr "Fyll i ett giltigt tidsspann." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Antalet dagar måste vara mellan {min_days} och {max_days}." + msgid "No file was submitted. Check the encoding type on the form." msgstr "Ingen fil skickades. Kontrollera kodningstypen i formuläret." @@ -736,10 +775,8 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Vänligen korrigera duplikatvärdena nedan." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "" -"Den infogade främmande nyckeln matchade inte den överordnade instansens " -"primära nyckel." +msgid "The inline value did not match the parent instance." +msgstr "Värdet för InlineForeignKeyField motsvarade inte dess motpart." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" @@ -747,8 +784,8 @@ msgstr "" "alternativ." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" är inte ett giltigt värde för en primärnyckel." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" är inte ett giltigt värde." #, python-format msgid "" @@ -758,15 +795,15 @@ msgstr "" "%(datetime)s kunde inte tolkas i tidszonen %(current_timezone)s; det kan " "vara en ogiltig eller tvetydigt tidpunkt" +msgid "Clear" +msgstr "Rensa" + msgid "Currently" msgstr "Nuvarande" msgid "Change" msgstr "Ändra" -msgid "Clear" -msgstr "Rensa" - msgid "Unknown" msgstr "Okänt" @@ -1038,8 +1075,8 @@ msgstr "Detta är inte en giltig IPv6 adress." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s…" msgid "or" msgstr "eller" @@ -1112,6 +1149,19 @@ msgstr "" "dem, åtminstone för denna sida, eller för HTTPS-anslutningar eller för 'same-" "origin'-förfrågningar." +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." +msgstr "" +"Om du använder -taggen eller " +"har med ”Referrer-Policy: no-referrer”, tag bort dem. CSRF-skyddet kräver " +"”Referer” för att kunna göra sin strikta kontroll. Om detta oroar dig, " +"använd alternativ såsom för länkar till tredje " +"part." + 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 " @@ -1131,32 +1181,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Mer information är tillgänglig med DEBUG=True." -msgid "Welcome to Django" -msgstr "Välkommen till Django" - -msgid "It worked!" -msgstr "Det fungerade!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Grattis till din nya Django-drivna sida." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Självklart har du inte hunnit jobba något med sidan än. Starta din första " -"app genom att köra python manage.py startapp [app_label]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Du ser detta meddelande eftersom du har DEBUG = True i din " -"inställningsfil och inte har konfigurerat några URLer än. Börja jobba!" - msgid "No year specified" msgstr "Inget år angivet" +msgid "Date out of range" +msgstr "Datum är utanför intervallet" + msgid "No month specified" msgstr "Ingen månad angiven" @@ -1207,3 +1237,47 @@ msgstr "\"%(path)s\" finns inte" #, python-format msgid "Index of %(directory)s" msgstr "Innehåll i %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: webb-ramverket för perfektionister med deadlines." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Visa release notes för Django %(version)s" + +msgid "The install worked successfully! Congratulations!" +msgstr "Installationen lyckades! Grattis!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Du ser den här sidan eftersom DEBUG=True i din settings-fil och du har inte konfigurerat några URL:" +"er." + +msgid "Django Documentation" +msgstr "Djangodokumentation" + +msgid "Topics, references, & how-to's" +msgstr "Ämnen, referenser och how-to's" + +msgid "Tutorial: A Polling App" +msgstr "Tutorial: En undersöknings-app" + +msgid "Get started with Django" +msgstr "Kom igång med Django" + +msgid "Django Community" +msgstr "Djangos community" + +msgid "Connect, get help, or contribute" +msgstr "Kontakta, begär hjälp eller bidra" diff --git a/django/conf/locale/sv/formats.py b/django/conf/locale/sv/formats.py index f2de0bc16bc4..4dd2f6327aa8 100644 --- a/django/conf/locale/sv/formats.py +++ b/django/conf/locale/sv/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j F Y' TIME_FORMAT = 'H:i' DATETIME_FORMAT = 'j F Y H:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # Kept ISO formats as they are in first position DATE_INPUT_FORMATS = [ '%Y-%m-%d', # '2006-10-25' diff --git a/django/conf/locale/sw/LC_MESSAGES/django.mo b/django/conf/locale/sw/LC_MESSAGES/django.mo index 3b853849738e..0228c051f349 100644 Binary files a/django/conf/locale/sw/LC_MESSAGES/django.mo and b/django/conf/locale/sw/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/sw/LC_MESSAGES/django.po b/django/conf/locale/sw/LC_MESSAGES/django.po index c03c70a887d8..23802e6b1a78 100644 --- a/django/conf/locale/sw/LC_MESSAGES/django.po +++ b/django/conf/locale/sw/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Swahili (http://www.transifex.com/django/django/language/" "sw/)\n" @@ -294,6 +294,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "Ingiza thamani halali" @@ -306,6 +315,7 @@ msgstr "Ingiza namba halali" msgid "Enter a valid email address." msgstr "Ingiza anuani halali ya barua pepe" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "Ingiza slagi halali yenye herufi, namba, \"_\" au \"-\"" @@ -379,6 +389,15 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "na" @@ -691,14 +710,14 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Tafadhali sahihisha thamani zilizojirudia hapo chini." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "`Inline foreign key` haijafanana tukio la `primary key` mama." +msgid "The inline value did not match the parent instance." +msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Chagua chaguo halali. Chaguo hilo si moja kati ya chaguzi halali" #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -709,15 +728,15 @@ msgstr "" "Imeshindikana kufasiri %(datetime)s katika majira ya %(current_timezone)s;" "Inawezekana kuwa kuna utata au kiti hichi hakipo." +msgid "Clear" +msgstr "Safisha" + msgid "Currently" msgstr "Kwa sasa" msgid "Change" msgstr "Badili" -msgid "Clear" -msgstr "Safisha" - msgid "Unknown" msgstr "Haijulikani" @@ -1057,6 +1076,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1071,28 +1098,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Maelezo zaidi yanapatikana ikiwa DEBUG=True" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "Hakuna mwaka maalum uliotajwa" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "Hakuna mwezi maalum uliotajwa" @@ -1143,3 +1154,41 @@ msgstr "\"%(path)s\" haipo" #, python-format msgid "Index of %(directory)s" msgstr "Sahirisi ya %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/ta/LC_MESSAGES/django.mo b/django/conf/locale/ta/LC_MESSAGES/django.mo index 7e02a0d766a7..9428ecb6d719 100644 Binary files a/django/conf/locale/ta/LC_MESSAGES/django.mo and b/django/conf/locale/ta/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/ta/LC_MESSAGES/django.po b/django/conf/locale/ta/LC_MESSAGES/django.po index 2e342dc8e995..0969ca0f81e9 100644 --- a/django/conf/locale/ta/LC_MESSAGES/django.po +++ b/django/conf/locale/ta/LC_MESSAGES/django.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Tamil (http://www.transifex.com/django/django/language/ta/)\n" "MIME-Version: 1.0\n" @@ -292,6 +292,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "" @@ -304,6 +313,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -377,6 +387,15 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "மற்றும்" @@ -685,14 +704,14 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "" -msgid "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -701,15 +720,15 @@ msgid "" "may be ambiguous or it may not exist." msgstr "" +msgid "Clear" +msgstr "" + msgid "Currently" msgstr "" msgid "Change" msgstr "மாற்றுக" -msgid "Clear" -msgstr "" - msgid "Unknown" msgstr "தெரியாத" @@ -1049,6 +1068,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1063,26 +1090,10 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" +msgid "No year specified" msgstr "" -msgid "No year specified" +msgid "Date out of range" msgstr "" msgid "No month specified" @@ -1133,3 +1144,41 @@ msgstr "" #, python-format msgid "Index of %(directory)s" msgstr "" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/ta/formats.py b/django/conf/locale/ta/formats.py index 89282447096d..61810e3fa737 100644 --- a/django/conf/locale/ta/formats.py +++ b/django/conf/locale/ta/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j F, Y' TIME_FORMAT = 'g:i A' # DATETIME_FORMAT = @@ -15,7 +12,7 @@ # FIRST_DAY_OF_WEEK = # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # DATE_INPUT_FORMATS = # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = diff --git a/django/conf/locale/te/LC_MESSAGES/django.mo b/django/conf/locale/te/LC_MESSAGES/django.mo index fa9fdc43b2d5..45b2982b0977 100644 Binary files a/django/conf/locale/te/LC_MESSAGES/django.mo and b/django/conf/locale/te/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/te/LC_MESSAGES/django.po b/django/conf/locale/te/LC_MESSAGES/django.po index b8c87ce5f1f5..ff2c7a744286 100644 --- a/django/conf/locale/te/LC_MESSAGES/django.po +++ b/django/conf/locale/te/LC_MESSAGES/django.po @@ -9,8 +9,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Telugu (http://www.transifex.com/django/django/language/te/)\n" "MIME-Version: 1.0\n" @@ -295,6 +295,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "సరైన విలువని ఇవ్వండి." @@ -307,6 +316,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "దయచేసి సరైన ఈమెయిల్ చిరునామాను ప్రవేశపెట్టండి." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -382,6 +392,15 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "మరియు" @@ -688,14 +707,14 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "దయచేసి క్రింద ఉన్న నకలు విలువను సరిదిద్దుకోండి." -msgid "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -704,15 +723,15 @@ msgid "" "may be ambiguous or it may not exist." msgstr "" +msgid "Clear" +msgstr "" + msgid "Currently" msgstr "ప్రస్తుతము " msgid "Change" msgstr "మార్చు" -msgid "Clear" -msgstr "" - msgid "Unknown" msgstr "తెలియనది" @@ -1052,6 +1071,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1066,26 +1093,10 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" +msgid "No year specified" msgstr "" -msgid "No year specified" +msgid "Date out of range" msgstr "" msgid "No month specified" @@ -1136,3 +1147,41 @@ msgstr "" #, python-format msgid "Index of %(directory)s" msgstr "" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/te/formats.py b/django/conf/locale/te/formats.py index 74420d0ea175..8fb98cf72021 100644 --- a/django/conf/locale/te/formats.py +++ b/django/conf/locale/te/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j F Y' TIME_FORMAT = 'g:i A' # DATETIME_FORMAT = @@ -15,7 +12,7 @@ # FIRST_DAY_OF_WEEK = # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # DATE_INPUT_FORMATS = # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = diff --git a/django/conf/locale/th/LC_MESSAGES/django.mo b/django/conf/locale/th/LC_MESSAGES/django.mo index 2542f7a427f2..49dc53ad5549 100644 Binary files a/django/conf/locale/th/LC_MESSAGES/django.mo and b/django/conf/locale/th/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/th/LC_MESSAGES/django.po b/django/conf/locale/th/LC_MESSAGES/django.po index b9a71ede7335..174759549ca1 100644 --- a/django/conf/locale/th/LC_MESSAGES/django.po +++ b/django/conf/locale/th/LC_MESSAGES/django.po @@ -3,7 +3,8 @@ # Translators: # Abhabongse Janthong, 2015 # Jannis Leidel , 2011 -# Kowit Charoenratchatabhan , 2014 +# Kowit Charoenratchatabhan , 2014,2018 +# Naowal Siripatana , 2017 # sipp11 , 2014 # Suteepat Damrongyingsupab , 2011-2012 # Suteepat Damrongyingsupab , 2013 @@ -12,8 +13,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2018-05-17 11:49+0200\n" +"PO-Revision-Date: 2018-05-18 00:21+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Thai (http://www.transifex.com/django/django/language/th/)\n" "MIME-Version: 1.0\n" @@ -163,6 +164,9 @@ msgstr "ญี่ปุ่น" msgid "Georgian" msgstr "จอร์เจีย" +msgid "Kabyle" +msgstr "" + msgid "Kazakh" msgstr "คาซัค" @@ -287,7 +291,7 @@ msgid "Traditional Chinese" msgstr "จีนตัวเต็ม" msgid "Messages" -msgstr "" +msgstr "ข้อความ" msgid "Site Maps" msgstr "" @@ -298,6 +302,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "หมายเลขหน้าดังกล่าวไม่ใช่จำนวนเต็ม" + +msgid "That page number is less than 1" +msgstr "หมายเลขหน้าดังกล่าวมีค่าน้อยกว่า 1" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "กรุณาใส่ค่าที่ถูกต้อง" @@ -310,6 +323,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "ป้อนที่อยู่อีเมลที่ถูกต้อง" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "ใส่ 'slug' ประกอปด้วย ตัวหนังสือ ตัวเลข เครื่องหมายขีดล่าง หรือ เครื่องหมายขีด" @@ -361,6 +375,9 @@ msgid_plural "" "%(show_value)d)." msgstr[0] "" +msgid "Enter a number." +msgstr "กรอกหมายเลข" + #, 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." @@ -378,6 +395,15 @@ msgid_plural "" "Ensure that there are no more than %(max)s digits before the decimal point." msgstr[0] "" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "และ" @@ -415,13 +441,17 @@ msgstr "จำนวนเต็ม" #, python-format msgid "'%(value)s' value must be an integer." -msgstr "" +msgstr "ค่าของ %(value)s ต้องเป็น integer" msgid "Big (8 byte) integer" msgstr "จำนวนเต็ม (8 byte)" #, python-format msgid "'%(value)s' value must be either True or False." +msgstr "ค่าของ %(value)s ต้องเป็น True หรือ False อย่างใดอย่างหนึ่ง" + +#, python-format +msgid "'%(value)s' value must be either True, False, or None." msgstr "" msgid "Boolean (Either True or False)" @@ -478,7 +508,7 @@ msgid "" msgstr "" msgid "Duration" -msgstr "" +msgstr "ช่วงเวลา" msgid "Email address" msgstr "อีเมล" @@ -587,9 +617,6 @@ msgstr "ฟิลด์นี้จำเป็น" msgid "Enter a whole number." msgstr "กรอกหมายเลข" -msgid "Enter a number." -msgstr "กรอกหมายเลข" - msgid "Enter a valid date." msgstr "กรุณาใส่วัน" @@ -600,6 +627,10 @@ msgid "Enter a valid date/time." msgstr "กรุณาใส่วันเวลา" msgid "Enter a valid duration." +msgstr "ใส่ระยะเวลาที่ถูกต้อง" + +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." msgstr "" msgid "No file was submitted. Check the encoding type on the form." @@ -636,7 +667,7 @@ msgid "Enter a complete value." msgstr "" msgid "Enter a valid UUID." -msgstr "" +msgstr "ใส่ UUID ที่ถูกต้อง" #. Translators: This is the default suffix added to form field labels msgid ":" @@ -684,14 +715,14 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "โปรดแก้ไขค่าที่ซ้ำซ้อนด้านล่าง" -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "Foreign key ไม่สัมพันธ์กับ parent primary key" +msgid "The inline value did not match the parent instance." +msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "เลือกตัวเลือกที่ถูกต้อง. ตัวเลือกนั้นไม่สามารถเลือกได้." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -702,15 +733,15 @@ msgstr "" "%(datetime)s ไม่สามารถแปลงให้อยู่ใน %(current_timezone)s time zone ได้ เนื่องจาก " "time zone ไม่ชัดเจน หรือไม่มีอยู่จริง" +msgid "Clear" +msgstr "ล้าง" + msgid "Currently" msgstr "ปัจจุบัน" msgid "Change" msgstr "เปลี่ยนแปลง" -msgid "Clear" -msgstr "ล้าง" - msgid "Unknown" msgstr "ไม่รู้" @@ -1025,7 +1056,7 @@ msgid "0 minutes" msgstr "0 นาที" msgid "Forbidden" -msgstr "" +msgstr "หวงห้าม" msgid "CSRF verification failed. Request aborted." msgstr "" @@ -1043,6 +1074,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1057,28 +1096,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "ไม่ระบุปี" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "ไม่ระบุเดือน" @@ -1129,3 +1152,41 @@ msgstr "\"%(path)s\" ไม่มีอยู่" #, python-format msgid "Index of %(directory)s" msgstr "ดัชนีของ %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "เริ่มต้นกับ Django" + +msgid "Django Community" +msgstr "ชุมชน Django" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/th/formats.py b/django/conf/locale/th/formats.py index 1b0e2d4f917b..d7394eb69c31 100644 --- a/django/conf/locale/th/formats.py +++ b/django/conf/locale/th/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j F Y' TIME_FORMAT = 'G:i' DATETIME_FORMAT = 'j F Y, G:i' @@ -12,13 +9,25 @@ MONTH_DAY_FORMAT = 'j F' SHORT_DATE_FORMAT = 'j M Y' SHORT_DATETIME_FORMAT = 'j M Y, G:i' -# FIRST_DAY_OF_WEEK = +FIRST_DAY_OF_WEEK = 0 # Sunday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior -# DATE_INPUT_FORMATS = -# TIME_INPUT_FORMATS = -# DATETIME_INPUT_FORMATS = +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior +DATE_INPUT_FORMATS = [ + '%d/%m/%Y', # 25/10/2006 + '%d %b %Y', # 25 ต.ค. 2006 + '%d %B %Y', # 25 ตุลาคม 2006 +] +TIME_INPUT_FORMATS = [ + '%H:%M:%S', # 14:30:59 + '%H:%M:%S.%f', # 14:30:59.000200 + '%H:%M', # 14:30 +] +DATETIME_INPUT_FORMATS = [ + '%d/%m/%Y %H:%M:%S', # 25/10/2006 14:30:59 + '%d/%m/%Y %H:%M:%S.%f', # 25/10/2006 14:30:59.000200 + '%d/%m/%Y %H:%M', # 25/10/2006 14:30 +] DECIMAL_SEPARATOR = '.' THOUSAND_SEPARATOR = ',' -# NUMBER_GROUPING = +NUMBER_GROUPING = 3 diff --git a/django/conf/locale/tr/LC_MESSAGES/django.mo b/django/conf/locale/tr/LC_MESSAGES/django.mo index cfaf17b1f3d0..4755178e86e6 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 7584e4942550..5959bd6bd4a9 100644 --- a/django/conf/locale/tr/LC_MESSAGES/django.po +++ b/django/conf/locale/tr/LC_MESSAGES/django.po @@ -2,9 +2,9 @@ # # Translators: # Ahmet Emre Aladağ , 2013 -# BouRock, 2015-2016 +# BouRock, 2015-2019 # BouRock, 2014-2015 -# Caner Başaran , 2013 +# Caner Başaran , 2013 # Cihad GÜNDOĞDU , 2012 # Cihad GÜNDOĞDU , 2013-2014 # Gökmen Görgen , 2013 @@ -16,8 +16,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 09:50+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 16:08+0000\n" "Last-Translator: BouRock\n" "Language-Team: Turkish (http://www.transifex.com/django/django/language/" "tr/)\n" @@ -147,6 +147,9 @@ msgstr "Yukarı Sorb dili" msgid "Hungarian" msgstr "Macarca" +msgid "Armenian" +msgstr "Ermenice" + msgid "Interlingua" msgstr "Interlingua" @@ -168,6 +171,9 @@ msgstr "Japonca" msgid "Georgian" msgstr "Gürcüce" +msgid "Kabyle" +msgstr "Kabiliye dili" + msgid "Kazakh" msgstr "Kazakça" @@ -303,6 +309,15 @@ msgstr "Sabit Dosyalar" msgid "Syndication" msgstr "Dağıtım" +msgid "That page number is not an integer" +msgstr "Bu sayfa numarası bir tamsayı değil" + +msgid "That page number is less than 1" +msgstr "Bu sayfa numarası 1'den az" + +msgid "That page contains no results" +msgstr "Bu sayfa hiç sonuç içermiyor" + msgid "Enter a valid value." msgstr "Geçerli bir değer girin." @@ -315,6 +330,7 @@ msgstr "Geçerli bir tamsayı girin." msgid "Enter a valid email address." msgstr "Geçerli bir e-posta adresi girin." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -381,6 +397,9 @@ msgstr[1] "" "Bu değerin en fazla %(limit_value)d karaktere sahip olduğuna emin olun (şu " "an %(show_value)d)." +msgid "Enter a number." +msgstr "Bir sayı girin." + #, 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." @@ -403,6 +422,17 @@ msgstr[0] "" msgstr[1] "" "Ondalık noktasından önce %(max)s rakamdan daha fazla olmadığından emin olun." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"'%(extension)s' dosya uzantısına izin verilmiyor. İzin verilen uzantılar: " +"'%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Boş karakterlere izin verilmiyor." + msgid "and" msgstr "ve" @@ -451,6 +481,10 @@ msgstr "Büyük (8 bayt) tamsayı" msgid "'%(value)s' value must be either True or False." msgstr "'%(value)s' değeri ya True ya da False olmak zorundadır." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "'%(value)s' değeri ya True, False ya da None olmak zorundadır." + msgid "Boolean (Either True or False)" msgstr "Boolean (Ya True ya da False)" @@ -588,6 +622,9 @@ msgstr "Ham ikili veri" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' geçerli bir UUID değil." +msgid "Universally unique identifier" +msgstr "Evrensel benzersiz tanımlayıcı" + msgid "File" msgstr "Dosya" @@ -627,9 +664,6 @@ msgstr "Bu alan zorunludur." msgid "Enter a whole number." msgstr "Tam bir sayı girin." -msgid "Enter a number." -msgstr "Bir sayı girin." - msgid "Enter a valid date." msgstr "Geçerli bir tarih girin." @@ -642,6 +676,10 @@ msgstr "Geçerli bir tarih/saat girin." msgid "Enter a valid duration." msgstr "Geçerli bir süre girin." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Gün sayıları {min_days} ve {max_days} arasında olmak zorundadır." + msgid "No file was submitted. Check the encoding type on the form." msgstr "Hiç dosya gönderilmedi. Formdaki kodlama türünü kontrol edin." @@ -736,16 +774,16 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Lütfen aşağıdaki kopya değerleri düzeltin." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "Satıriçi dış anahtar ana örnek birincil anahtarı ile eşleşmedi." +msgid "The inline value did not match the parent instance." +msgstr "Satıriçi değer ana örnek ile eşleşmedi." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" "Geçerli bir seçenek seçin. Bu seçenek, mevcut seçeneklerden biri değil." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" birincil anahtar için geçerli bir değer değil." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" geçerli bir değer değil." #, python-format msgid "" @@ -755,15 +793,15 @@ msgstr "" " %(datetime)s, %(current_timezone)s saat dilimi olarak yorumlanamadı; bu " "belirsiz olabilir ya da mevcut olmayabilir." +msgid "Clear" +msgstr "Temizle" + msgid "Currently" msgstr "Şu anki" msgid "Change" msgstr "Değiştir" -msgid "Clear" -msgstr "Temizle" - msgid "Unknown" msgstr "Bilinmiyor" @@ -1035,8 +1073,8 @@ msgstr "Bu, geçerli bir IPv6 adresi değil." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s…" msgid "or" msgstr "ya da" @@ -1096,9 +1134,9 @@ msgid "" "required for security reasons, to ensure that your browser is not being " "hijacked by third parties." msgstr "" -"Bu iletiyi görüyorsunuz çünkü bu HTTPS sitesi Web tarayıcınız tarafından " -"gönderilen 'Göndereni başlığı'nı gerektirir, ancak hiçbir şey gönderilmedi. " -"Bu başlık güvenlik nedenleri için gerekir, tarayıcınızın üçüncü parti " +"Bu iletiyi görüyorsunuz çünkü bu HTTPS sitesi, Web tarayıcınız tarafından " +"gönderilen 'Referer üstbilgisi'ni gerektirir, ancak hiçbir şey gönderilmedi. " +"Bu üstbilgi güvenlik nedenleri için gerekir, tarayıcınızın üçüncü taraf " "uygulamalar tarafından ele geçirilmediğinden emin olun." msgid "" @@ -1106,10 +1144,24 @@ msgid "" "enable them, at least for this site, or for HTTPS connections, or for 'same-" "origin' requests." msgstr "" -"Eğer tarayıcınızı 'Göndereni' başlıklarını etkisizleştirmek için " +"Eğer tarayıcınızı 'Referer' üstbilgilerini etkisizleştirmek için " "yapılandırdıysanız, lütfen bunları, en azından bu site ya da HTTPS " "bağlantıları veya 'aynı-kaynakta' olan istekler için yeniden etkinleştirin." +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." +msgstr "" +"Eğer etiketi " +"kullanıyorsanız ya da 'Referrer-Policy: no-referrer' üstbilgisini dahil " +"ediyorsanız, lütfen bunları kaldırın. CSRF koruması, katı göndereni denetimi " +"yapmak için 'Referer' üstbilgisi gerektirir. Gizlilik konusunda endişeniz " +"varsa, üçüncü taraf sitelere bağlantılar için " +"gibi alternatifler kullanın." + 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 " @@ -1117,7 +1169,7 @@ msgid "" msgstr "" "Bu iletiyi görüyorsunuz çünkü bu site, formları gönderdiğinizde bir CSRF " "tanımlama bilgisini gerektirir. Bu tanımlama bilgisi güvenlik nedenleri için " -"gerekir, tarayıcınızın üçüncü parti uygulamalar tarafından ele " +"gerekir, tarayıcınızın üçüncü taraf uygulamalar tarafından ele " "geçirilmediğinden emin olun." msgid "" @@ -1131,33 +1183,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Daha fazla bilgi DEBUG=True ayarı ile mevcut olur." -msgid "Welcome to Django" -msgstr "Django'ya Hoş Geldiniz" - -msgid "It worked!" -msgstr "İşe yaradı!" - -msgid "Congratulations on your first Django-powered page." -msgstr "İlk Django-destekli sayfanız için tebrikler." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Tabii ki, aslında henüz herhangi bir çalışma yapmadınız. Sonrasında, " -"python manage.py startapp [app_label] komutunu çalıştırarak ilk " -"uygulamanızı başlatın." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Bu iletiyi görüyorsunuz çünkü Django ayarları dosyanızda DEBUG = True ifadesi var ve herhangi bir URL'yi yapılandırmadınız. Işe koyulun!" - msgid "No year specified" msgstr "Yıl bilgisi belirtilmedi" +msgid "Date out of range" +msgstr "Tarih aralık dışında" + msgid "No month specified" msgstr "Ay bilgisi belirtilmedi" @@ -1208,3 +1239,48 @@ msgstr "\"%(path)s\" mevcut değil" #, python-format msgid "Index of %(directory)s" msgstr "%(directory)s indeksi" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django: bitiş tarihleri olan mükemmelliyetçiler için Web yapısı." + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"Django %(version)s sürümü için yayım notlarını göster" + +msgid "The install worked successfully! Congratulations!" +msgstr "Yükleme başarılı olarak çalıştı! Tebrikler!" + +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" +"Bu sayfayı görüyorsunuz çünkü DEBUG=True parametresi ayarlar dosyanızın içindedir ve herhangi bir " +"URL yapılandırmadınız." + +msgid "Django Documentation" +msgstr "Django Belgeleri" + +msgid "Topics, references, & how-to's" +msgstr "Konular, kaynaklar, ve nasıl yapılırlar" + +msgid "Tutorial: A Polling App" +msgstr "Eğitim: Anket Uygulaması" + +msgid "Get started with Django" +msgstr "Django ile başlayın" + +msgid "Django Community" +msgstr "Django Topluluğu" + +msgid "Connect, get help, or contribute" +msgstr "Bağlanın, yardım alın, ya da katkıda bulunun" diff --git a/django/conf/locale/tr/formats.py b/django/conf/locale/tr/formats.py index c765f77489a7..23012db0bb65 100644 --- a/django/conf/locale/tr/formats.py +++ b/django/conf/locale/tr/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'd F Y' TIME_FORMAT = 'H:i' DATETIME_FORMAT = 'd F Y H:i' @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # Pazartesi # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d/%m/%Y', '%d/%m/%y', # '25/10/2006', '25/10/06' '%y-%m-%d', # '06-10-25' diff --git a/django/conf/locale/tt/LC_MESSAGES/django.mo b/django/conf/locale/tt/LC_MESSAGES/django.mo index 0cefcea88b5a..e7030829280a 100644 Binary files a/django/conf/locale/tt/LC_MESSAGES/django.mo and b/django/conf/locale/tt/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/tt/LC_MESSAGES/django.po b/django/conf/locale/tt/LC_MESSAGES/django.po index 2d83046075e8..fe28eeeb07ea 100644 --- a/django/conf/locale/tt/LC_MESSAGES/django.po +++ b/django/conf/locale/tt/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Tatar (http://www.transifex.com/django/django/language/tt/)\n" "MIME-Version: 1.0\n" @@ -293,6 +293,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "Дөрес кыйммәтне кертегез." @@ -305,6 +314,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "Дөрес эл. почта адресны кертегез." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -378,6 +388,15 @@ msgid_plural "" "Ensure that there are no more than %(max)s digits before the decimal point." msgstr[0] "" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "һәм" @@ -688,8 +707,8 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Зинһар, астагы кабатлана торган кыйммәтләрне төзәтегез." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "Тыш ачкыч атаның баш ачкычы белән туры килмиләр." +msgid "The inline value did not match the parent instance." +msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" @@ -697,7 +716,7 @@ msgstr "" "юк." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -706,15 +725,15 @@ msgid "" "may be ambiguous or it may not exist." msgstr "" +msgid "Clear" +msgstr "Бушайтырга" + msgid "Currently" msgstr "Хәзерге вакытта" msgid "Change" msgstr "Үзгәртергә" -msgid "Clear" -msgstr "Бушайтырга" - msgid "Unknown" msgstr "Билгесез" @@ -1047,6 +1066,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1061,28 +1088,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "Ел билгеләнмәгән" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "Ай билгеләнмәгән" @@ -1133,3 +1144,41 @@ msgstr "" #, python-format msgid "Index of %(directory)s" msgstr "" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/udm/LC_MESSAGES/django.mo b/django/conf/locale/udm/LC_MESSAGES/django.mo index e675479eb0db..1138116db121 100644 Binary files a/django/conf/locale/udm/LC_MESSAGES/django.mo and b/django/conf/locale/udm/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/udm/LC_MESSAGES/django.po b/django/conf/locale/udm/LC_MESSAGES/django.po index 155be24281ac..67b600d98eca 100644 --- a/django/conf/locale/udm/LC_MESSAGES/django.po +++ b/django/conf/locale/udm/LC_MESSAGES/django.po @@ -5,8 +5,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Udmurt (http://www.transifex.com/django/django/language/" "udm/)\n" @@ -292,6 +292,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "Тазэ шонер гожтэ." @@ -304,6 +313,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "Электорн почта адресэз шонер гожтэ" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -373,6 +383,15 @@ msgid_plural "" "Ensure that there are no more than %(max)s digits before the decimal point." msgstr[0] "" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "но" @@ -677,14 +696,14 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "" -msgid "The inline foreign key did not match the parent instance primary key." +msgid "The inline value did not match the parent instance." msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -695,15 +714,15 @@ msgstr "" "%(datetime)s couldn't be interpreted in time zone %(current_timezone)s; it " "may be ambiguous or it may not exist." +msgid "Clear" +msgstr "Буш кароно" + msgid "Currently" msgstr "Али" msgid "Change" msgstr "Тупатъяно" -msgid "Clear" -msgstr "Буш кароно" - msgid "Unknown" msgstr "Тодымтэ" @@ -1036,6 +1055,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1050,26 +1077,10 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" +msgid "No year specified" msgstr "" -msgid "No year specified" +msgid "Date out of range" msgstr "" msgid "No month specified" @@ -1120,3 +1131,41 @@ msgstr "\"%(path)s\" ӧвӧл" #, python-format msgid "Index of %(directory)s" msgstr "%(directory)s папкалэн пушторсэз" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/uk/LC_MESSAGES/django.mo b/django/conf/locale/uk/LC_MESSAGES/django.mo index 499a89be9b10..f38667cd81d7 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 67e36e1d674a..ad863851e39e 100644 --- a/django/conf/locale/uk/LC_MESSAGES/django.po +++ b/django/conf/locale/uk/LC_MESSAGES/django.po @@ -3,32 +3,36 @@ # Translators: # Oleksandr Chernihov , 2014 # Boryslav Larin , 2011 -# Denis Podlesniy , 2016 -# Igor Melnyk, 2014-2015 +# Денис Подлесный , 2016 +# Igor Melnyk, 2014-2015,2017 # Jannis Leidel , 2011 # Kirill Gagarski , 2014 # Max V. Stotsky , 2014 # Mikhail Kolesnik , 2015 # Mykola Zamkovoi , 2014 -# Oleksandr Bolotov , 2013-2014 +# Alex Bolotov , 2013-2014 # Roman Kozlovskyi , 2012 # Sergiy Kuzmenko , 2011 -# Zoriana Zaiats, 2016 +# tarasyyyk , 2018 +# tarasyyyk , 2019 +# Zoriana Zaiats, 2016-2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-07-07 22:24+0000\n" -"Last-Translator: Denis Podlesniy \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-02-21 16:29+0000\n" +"Last-Translator: tarasyyyk \n" "Language-Team: Ukrainian (http://www.transifex.com/django/django/language/" "uk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: uk\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=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != " +"11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % " +"100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || " +"(n % 100 >=11 && n % 100 <=14 )) ? 2: 3);\n" msgid "Afrikaans" msgstr "Африканська" @@ -150,6 +154,9 @@ msgstr "Верхньолужицька" msgid "Hungarian" msgstr "Угорська" +msgid "Armenian" +msgstr "Вірменська" + msgid "Interlingua" msgstr "Інтерлінгва" @@ -171,6 +178,9 @@ msgstr "Японська" msgid "Georgian" msgstr "Грузинська" +msgid "Kabyle" +msgstr "Кабіли" + msgid "Kazakh" msgstr "Казахська" @@ -306,6 +316,15 @@ msgstr "Статичні файли" msgid "Syndication" msgstr "Об'єднання" +msgid "That page number is not an integer" +msgstr "Номер сторінки не є цілим числом" + +msgid "That page number is less than 1" +msgstr "Номер сторінки менше 1" + +msgid "That page contains no results" +msgstr "Сторінка не містить результатів" + msgid "Enter a valid value." msgstr "Введіть коректне значення." @@ -318,6 +337,7 @@ msgstr "Введіть коректне ціле число." msgid "Enter a valid email address." msgstr "Введіть коректну email адресу." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -373,6 +393,9 @@ msgstr[1] "" msgstr[2] "" "Переконайтеся, що це значення містить не менш ніж %(limit_value)d символів " "(зараз %(show_value)d)." +msgstr[3] "" +"Переконайтеся, що це значення містить не менш ніж %(limit_value)d символів " +"(зараз %(show_value)d)." #, python-format msgid "" @@ -390,6 +413,12 @@ msgstr[1] "" msgstr[2] "" "Переконайтеся, що це значення містить не більше ніж %(limit_value)d символів " "(зараз %(show_value)d)." +msgstr[3] "" +"Переконайтеся, що це значення містить не більше ніж %(limit_value)d символів " +"(зараз %(show_value)d)." + +msgid "Enter a number." +msgstr "Введіть число." #, python-format msgid "Ensure that there are no more than %(max)s digit in total." @@ -397,6 +426,7 @@ msgid_plural "Ensure that there are no more than %(max)s digits in total." msgstr[0] "Переконайтеся, що загалом тут не більше ніж %(max)s цифра." msgstr[1] "Переконайтеся, що загалом тут не більше ніж %(max)s цифер." msgstr[2] "Переконайтеся, що загалом тут не більше ніж %(max)s цифер." +msgstr[3] "Переконайтеся, що загалом тут не більше ніж %(max)s цифер." #, python-format msgid "Ensure that there are no more than %(max)s decimal place." @@ -407,6 +437,8 @@ msgstr[1] "" "Переконайтеся, що тут не більше ніж %(max)s цифри після десяткової коми." msgstr[2] "" "Переконайтеся, що тут не більше ніж %(max)s цифер після десяткової коми." +msgstr[3] "" +"Переконайтеся, що тут не більше ніж %(max)s цифер після десяткової коми." #, python-format msgid "" @@ -419,6 +451,19 @@ msgstr[1] "" "Переконайтеся, що тут не більше ніж %(max)s цифри до десяткової коми." msgstr[2] "" "Переконайтеся, що тут не більше ніж %(max)s цифер до десяткової коми." +msgstr[3] "" +"Переконайтеся, що тут не більше ніж %(max)s цифер до десяткової коми." + +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"Розширення файлу '%(extension)s' не дозволено. Дозволені розширення: ' " +"%(allowed_extensions)s'." + +msgid "Null characters are not allowed." +msgstr "Символи Null не дозволені." msgid "and" msgstr "та" @@ -468,6 +513,10 @@ msgstr "Велике (8 байтів) ціле число" msgid "'%(value)s' value must be either True or False." msgstr "Значення '%(value)s' повинне бути True або False." +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "Значення '%(value)s' повинне бути True, False, або None." + msgid "Boolean (Either True or False)" msgstr "Булеве значення (True або False)" @@ -602,6 +651,9 @@ msgstr "Необроблені двійкові дані" msgid "'%(value)s' is not a valid UUID." msgstr "'%(value)s' невірне значення UUID." +msgid "Universally unique identifier" +msgstr "Універсальний унікальний ідентифікатор" + msgid "File" msgstr "Файл" @@ -641,9 +693,6 @@ msgstr "Це поле обов'язкове." msgid "Enter a whole number." msgstr "Введіть ціле число." -msgid "Enter a number." -msgstr "Введіть число." - msgid "Enter a valid date." msgstr "Введіть коректну дату." @@ -656,6 +705,10 @@ msgstr "Введіть коректну дату/час." msgid "Enter a valid duration." msgstr "Введіть коректну тривалість." +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "Кількість днів повинна бути від {min_days} до {max_days}." + msgid "No file was submitted. Check the encoding type on the form." msgstr "Файл не надіслано. Перевірте тип кодування форми." @@ -678,6 +731,9 @@ msgstr[1] "" msgstr[2] "" "Переконайтеся, що це ім'я файлу містить не більше ніж з %(max)d символів " "(зараз %(length)d)." +msgstr[3] "" +"Переконайтеся, що це ім'я файлу містить не більше ніж з %(max)d символів " +"(зараз %(length)d)." msgid "Please either submit a file or check the clear checkbox, not both." msgstr "" @@ -721,6 +777,7 @@ msgid_plural "Please submit %d or fewer forms." msgstr[0] "Будь ласка, відправте %d або менше форм." msgstr[1] "Будь ласка, відправте %d або менше форм." msgstr[2] "Будь ласка, відправте %d або менше форм." +msgstr[3] "Будь ласка, відправте %d або менше форм." #, python-format msgid "Please submit %d or more forms." @@ -728,6 +785,7 @@ msgid_plural "Please submit %d or more forms." msgstr[0] "Будь ласка, відправте як мінімум %d форму." msgstr[1] "Будь ласка, відправте як мінімум %d форми." msgstr[2] "Будь ласка, відправте як мінімум %d форм." +msgstr[3] "Будь ласка, відправте як мінімум %d форм." msgid "Order" msgstr "Послідовність" @@ -756,17 +814,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Будь ласка, виправте повторювані значення нижче." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "" -"Зв'язаний зовнішній ключ не відповідає первісному ключу батьківського " -"екземпляру." +msgid "The inline value did not match the parent instance." +msgstr "Зв'язане значення не відповідає батьківському екземпляру." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Зробить коректний вибір. Такого варіанту нема серед доступних." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" не є допустимим значенням для первинного ключа." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" не є допустимим значенням." #, python-format msgid "" @@ -776,15 +832,15 @@ msgstr "" "%(datetime)s не може бути інтерпретована в часовому поясі " "%(current_timezone)s; дата може бути неодзначною або виявитись неіснуючою." +msgid "Clear" +msgstr "Очистити" + msgid "Currently" msgstr "Наразі" msgid "Change" msgstr "Змінити" -msgid "Clear" -msgstr "Очистити" - msgid "Unknown" msgstr "Невідомо" @@ -803,6 +859,7 @@ msgid_plural "%(size)d bytes" msgstr[0] "%(size)d байт" msgstr[1] "%(size)d байти" msgstr[2] "%(size)d байтів" +msgstr[3] "%(size)d байтів" #, python-format msgid "%s KB" @@ -1057,8 +1114,8 @@ msgstr "Це не є правильною адресою IPv6." #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s…" msgid "or" msgstr "або" @@ -1073,6 +1130,7 @@ msgid_plural "%d years" msgstr[0] "%d рік" msgstr[1] "%d роки" msgstr[2] "%d років" +msgstr[3] "%d років" #, python-format msgid "%d month" @@ -1080,6 +1138,7 @@ msgid_plural "%d months" msgstr[0] "%d місяць" msgstr[1] "%d місяці" msgstr[2] "%d місяців" +msgstr[3] "%d місяців" #, python-format msgid "%d week" @@ -1087,6 +1146,7 @@ msgid_plural "%d weeks" msgstr[0] "%d тиждень" msgstr[1] "%d тижні" msgstr[2] "%d тижнів" +msgstr[3] "%d тижнів" #, python-format msgid "%d day" @@ -1094,6 +1154,7 @@ msgid_plural "%d days" msgstr[0] "%d день" msgstr[1] "%d дня" msgstr[2] "%d днів" +msgstr[3] "%d днів" #, python-format msgid "%d hour" @@ -1101,6 +1162,7 @@ msgid_plural "%d hours" msgstr[0] "%d година" msgstr[1] "%d години" msgstr[2] "%d годин" +msgstr[3] "%d годин" #, python-format msgid "%d minute" @@ -1108,6 +1170,7 @@ msgid_plural "%d minutes" msgstr[0] "%d хвилина" msgstr[1] "%d хвилини" msgstr[2] "%d хвилин" +msgstr[3] "%d хвилин" msgid "0 minutes" msgstr "0 хвилин" @@ -1139,6 +1202,20 @@ msgstr "" "принаймні для даного сайту, або для всіх HTTPS-з'єднань, або для подібних " "запитів." +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." +msgstr "" +"Якщо ви використовуєте тег " +"або включаєте в запит заголовок 'Referrer-Policy: no-referrer', тоді, будь " +"ласка, видаліть їх. CSRF-захист потребує заголовок 'Referer', щоб виконати " +"перевірку. Якщо ви занепокоєні стосовно приватності, використовуйте " +"альтернативи, наприклад, для посилань на сайти третіх сторін використайте " +"тег ." + 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 " @@ -1159,33 +1236,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "Більше інформації можна отримати з DEBUG=True." -msgid "Welcome to Django" -msgstr "Ласкаво просимо до Django" - -msgid "It worked!" -msgstr "Воно працює!" - -msgid "Congratulations on your first Django-powered page." -msgstr "Вітаємо Вас на першій сторінці яка створена за допомогою Django." - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"Звичайно, Ви ще, насправді, нічого не зробили. Наступним кроком, розпочніть " -"створення Вашого першого додатку, виконавши python manage.py startapp " -"[app_label]." - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"Ви бачите це повідомлення тому, що у Вас DEBUG = True у вашому " -"файлі налаштувань Django і ви не налаштували жодного URL. Вперед до роботи!" - msgid "No year specified" msgstr "Рік не вказано" +msgid "Date out of range" +msgstr "Дата поза діапазоном" + msgid "No month specified" msgstr "Місяць не вказано" @@ -1236,3 +1292,47 @@ 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 "" +"Нотатки релізу for Django %(version)s" + +msgid "The install worked successfully! Congratulations!" +msgstr "Вітаємо, команда install завершилась успішно!" + +#, 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 у вашому файлі конфігурації і ви не " +"налаштували жодного URL." + +msgid "Django Documentation" +msgstr "Документація Django" + +msgid "Topics, references, & how-to's" +msgstr "Статті, довідки та інструкції" + +msgid "Tutorial: A Polling App" +msgstr "Посібник: програма голосування" + +msgid "Get started with Django" +msgstr "Початок роботи з Django" + +msgid "Django Community" +msgstr "Спільнота Django" + +msgid "Connect, get help, or contribute" +msgstr "Отримати допомогу, чи допомогти" diff --git a/django/conf/locale/uk/formats.py b/django/conf/locale/uk/formats.py index 938a97d42a97..63e4b97bf3a4 100644 --- a/django/conf/locale/uk/formats.py +++ b/django/conf/locale/uk/formats.py @@ -1,11 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # - -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'd E Y р.' TIME_FORMAT = 'H:i' DATETIME_FORMAT = 'd E Y р. H:i' @@ -16,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%d.%m.%Y', # '25.10.2006' '%d %B %Y', # '25 October 2006' diff --git a/django/conf/locale/ur/LC_MESSAGES/django.mo b/django/conf/locale/ur/LC_MESSAGES/django.mo index e9ab9b538a9b..ad4750619c74 100644 Binary files a/django/conf/locale/ur/LC_MESSAGES/django.mo and b/django/conf/locale/ur/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/ur/LC_MESSAGES/django.po b/django/conf/locale/ur/LC_MESSAGES/django.po index 439ad3b4a3eb..dd4b6ed93983 100644 --- a/django/conf/locale/ur/LC_MESSAGES/django.po +++ b/django/conf/locale/ur/LC_MESSAGES/django.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Urdu (http://www.transifex.com/django/django/language/ur/)\n" "MIME-Version: 1.0\n" @@ -292,6 +292,15 @@ msgstr "" msgid "Syndication" msgstr "" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "درست قیمت (ویلیو) درج کریں۔" @@ -304,6 +313,7 @@ msgstr "" msgid "Enter a valid email address." msgstr "" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "درست 'slug' درج کریں جو حروف، نمبروں، انڈرسکور یا ھائفنز پر مشتمل ھو۔" @@ -383,6 +393,15 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "اور" @@ -694,14 +713,14 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "براہ کرم نیچے دوہری قیمتیں (ویلیوز) درست کریں۔" -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "ان لائن بیرونی کلید (FK) آبائی پرائمری کلید (PK) سے نھیں ملتی۔" +msgid "The inline value did not match the parent instance." +msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "درست انتخاب منتخب کریں۔ یہ انتخاب دستیاب انتخابات میں سے کوئی نہیں ھے۔" #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." +msgid "\"%(pk)s\" is not a valid value." msgstr "" #, python-format @@ -710,15 +729,15 @@ msgid "" "may be ambiguous or it may not exist." msgstr "" +msgid "Clear" +msgstr "صاف کریں" + msgid "Currently" msgstr "فی الحال" msgid "Change" msgstr "تبدیل کریں" -msgid "Clear" -msgstr "صاف کریں" - msgid "Unknown" msgstr "نامعلوم" @@ -1058,6 +1077,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1072,26 +1099,10 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" +msgid "No year specified" msgstr "" -msgid "No year specified" +msgid "Date out of range" msgstr "" msgid "No month specified" @@ -1142,3 +1153,41 @@ msgstr "" #, python-format msgid "Index of %(directory)s" msgstr "" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/vi/LC_MESSAGES/django.mo b/django/conf/locale/vi/LC_MESSAGES/django.mo index cb074260b807..def7a4d4eba1 100644 Binary files a/django/conf/locale/vi/LC_MESSAGES/django.mo and b/django/conf/locale/vi/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/vi/LC_MESSAGES/django.po b/django/conf/locale/vi/LC_MESSAGES/django.po index f214678a7e0e..05a985f3a684 100644 --- a/django/conf/locale/vi/LC_MESSAGES/django.po +++ b/django/conf/locale/vi/LC_MESSAGES/django.po @@ -12,8 +12,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-06-30 08:31+0000\n" +"POT-Creation-Date: 2017-11-15 16:15+0100\n" +"PO-Revision-Date: 2017-11-16 01:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Vietnamese (http://www.transifex.com/django/django/language/" "vi/)\n" @@ -299,6 +299,15 @@ msgstr "Tập tin tĩnh" msgid "Syndication" msgstr "Syndication" +msgid "That page number is not an integer" +msgstr "" + +msgid "That page number is less than 1" +msgstr "" + +msgid "That page contains no results" +msgstr "" + msgid "Enter a valid value." msgstr "Nhập một giá trị hợp lệ." @@ -311,6 +320,7 @@ msgstr "Nhập một số nguyên hợp lệ." msgid "Enter a valid email address." msgstr "Nhập địa chỉ email." +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "Nhập một 'slug' hợp lệ gồm chữ cái, số, gạch dưới và gạch nối." @@ -383,6 +393,15 @@ msgstr[0] "" "Hãy chắc chắn rằng không có nhiều hơn %(max)s chữ số trước dấu phẩy thập " "phân." +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +msgid "Null characters are not allowed." +msgstr "" + msgid "and" msgstr "và" @@ -695,8 +714,8 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Hãy sửa các giá trị trùng lặp dưới đây." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "Khóa ngoại không tương ứng với khóa chính của đối tượng cha." +msgid "The inline value did not match the parent instance." +msgstr "" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" @@ -704,8 +723,8 @@ msgstr "" "chọn khả thi." #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" không phải là giá trị hợp lệ cho khóa chính." +msgid "\"%(pk)s\" is not a valid value." +msgstr "" #, python-format msgid "" @@ -715,15 +734,15 @@ msgstr "" "%(datetime)s không thích hợp với khu vực thời gian %(current_timezone)s; " "phần này có thể còn mơ hồ chưa rõ nghĩa hoặc không hề tồn tại." +msgid "Clear" +msgstr "Xóa" + msgid "Currently" msgstr "Hiện nay" msgid "Change" msgstr "Thay đổi" -msgid "Clear" -msgstr "Xóa" - msgid "Unknown" msgstr "Chưa xác định" @@ -1056,6 +1075,14 @@ msgid "" "origin' requests." msgstr "" +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." +msgstr "" + 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 " @@ -1070,28 +1097,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "" -msgid "Welcome to Django" -msgstr "" - -msgid "It worked!" -msgstr "" - -msgid "Congratulations on your first Django-powered page." -msgstr "" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" - msgid "No year specified" msgstr "Không có năm xác định" +msgid "Date out of range" +msgstr "" + msgid "No month specified" msgstr "Không có tháng xác định" @@ -1143,3 +1154,41 @@ msgstr "\"%(path)s\" không tồn tại" #, python-format msgid "Index of %(directory)s" msgstr "Index của %(directory)s" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +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." +msgstr "" + +msgid "Django Documentation" +msgstr "" + +msgid "Topics, references, & how-to's" +msgstr "" + +msgid "Tutorial: A Polling App" +msgstr "" + +msgid "Get started with Django" +msgstr "" + +msgid "Django Community" +msgstr "" + +msgid "Connect, get help, or contribute" +msgstr "" diff --git a/django/conf/locale/vi/formats.py b/django/conf/locale/vi/formats.py index ee87e2f3261a..495b6f7993d1 100644 --- a/django/conf/locale/vi/formats.py +++ b/django/conf/locale/vi/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = r'\N\gà\y d \t\há\n\g n \nă\m Y' TIME_FORMAT = 'H:i' DATETIME_FORMAT = r'H:i \N\gà\y d \t\há\n\g n \nă\m Y' @@ -15,7 +12,7 @@ # FIRST_DAY_OF_WEEK = # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior # DATE_INPUT_FORMATS = # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = diff --git a/django/conf/locale/zh_Hans/LC_MESSAGES/django.mo b/django/conf/locale/zh_Hans/LC_MESSAGES/django.mo index b6e8c399aee3..75cbeae418ca 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 73b40c3558fd..c160cb9b07b7 100644 --- a/django/conf/locale/zh_Hans/LC_MESSAGES/django.po +++ b/django/conf/locale/zh_Hans/LC_MESSAGES/django.po @@ -1,28 +1,37 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Bai HuanCheng , 2017-2018 # Daniel Duan , 2013 +# jamin M , 2019 # Jannis Leidel , 2011 # Kevin Sze , 2012 -# Lele Long , 2011,2015 -# Liping Wang , 2016 +# Lele Long , 2011,2015,2017 +# Le Yang , 2018 +# Liping Wang , 2016-2017 # mozillazg , 2016 -# Ronald White , 2014 +# Ronald White , 2014 # pylemon , 2013 +# Ray Wang , 2017 # slene , 2011 # Sun Liwen , 2014 +# Suntravel Chris , 2019 # Liping Wang , 2016 +# Wentao Han , 2018 # Xiang Yu , 2014 -# Yin Jifeng , 2013 -# Ziang Song , 2011-2012 +# Jeff Yin , 2013 +# Zhengyang Wang , 2017 +# ced773123cfad7b4e8b79ca80f736af9, 2011-2012 +# Ziya Tang , 2018 +# 付峥 , 2018 # Kevin Sze , 2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-09-20 02:26+0000\n" -"Last-Translator: Liping Wang \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-05-26 11:53+0000\n" +"Last-Translator: jamin M \n" "Language-Team: Chinese (China) (http://www.transifex.com/django/django/" "language/zh_CN/)\n" "MIME-Version: 1.0\n" @@ -32,7 +41,7 @@ msgstr "" "Plural-Forms: nplurals=1; plural=0;\n" msgid "Afrikaans" -msgstr "南非语" +msgstr "南非荷兰语" msgid "Arabic" msgstr "阿拉伯语" @@ -151,6 +160,9 @@ msgstr "上索布" msgid "Hungarian" msgstr "匈牙利语" +msgid "Armenian" +msgstr "亚美尼亚语" + msgid "Interlingua" msgstr "国际语" @@ -172,6 +184,9 @@ msgstr "日语" msgid "Georgian" msgstr "格鲁吉亚语" +msgid "Kabyle" +msgstr "卡拜尔语" + msgid "Kazakh" msgstr "哈萨克语" @@ -307,6 +322,15 @@ msgstr "静态文件" msgid "Syndication" msgstr "联合" +msgid "That page number is not an integer" +msgstr "页码不是整数" + +msgid "That page number is less than 1" +msgstr "页码小于 1" + +msgid "That page contains no results" +msgstr "本页结果为空" + msgid "Enter a valid value." msgstr "输入一个有效的值。" @@ -319,6 +343,7 @@ msgstr "输入一个有效的整数。" msgid "Enter a valid email address." msgstr "输入一个有效的 Email 地址。" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "输入一个有效的 'slug',由字母、数字、下划线或横线组成。" @@ -372,6 +397,9 @@ msgid_plural "" msgstr[0] "" "确保该变量包含不超过 %(limit_value)d 字符 (目前字符数 %(show_value)d)。" +msgid "Enter a number." +msgstr "输入一个数字。" + #, 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." @@ -389,6 +417,17 @@ msgid_plural "" "Ensure that there are no more than %(max)s digits before the decimal point." msgstr[0] "确认小数点前不超过 %(max)s 位。" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"文件后缀名 '%(extension)s' 不被允许.。可用的文件后缀" +"名:'%(allowed_extensions)s'。" + +msgid "Null characters are not allowed." +msgstr "不允许是用空字符串。" + msgid "and" msgstr "和" @@ -435,10 +474,14 @@ msgstr "大整数(8字节)" #, python-format msgid "'%(value)s' value must be either True or False." -msgstr "’%(value)s‘ 必须为 True 或者 False。" +msgstr "’%(value)s‘ 的值必须为 True 或者 False。" + +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "'%(value)s' 的值必须是 True , False 或 None 之一。" msgid "Boolean (Either True or False)" -msgstr "布尔值(真或假)" +msgstr "布尔值( True或False )" #, python-format msgid "String (up to %(max_length)s)" @@ -460,7 +503,7 @@ msgid "" msgstr "’%(value)s‘ 值的格式正确(YYYY-MM-DD),但是日期无效。" msgid "Date (without time)" -msgstr "日期(无时间)" +msgstr "日期(不带时分)" #, python-format msgid "" @@ -479,7 +522,7 @@ msgstr "" "效。" msgid "Date (with time)" -msgstr "日期(带时间)" +msgstr "日期(带时分)" #, python-format msgid "'%(value)s' value must be a decimal number." @@ -521,7 +564,7 @@ msgid "'%(value)s' value must be either None, True or False." msgstr "’%(value)s‘ 必须为None,True或者False。" msgid "Boolean (Either True, False or None)" -msgstr "布尔值(真、假或无)" +msgstr "布尔值(True、False或None)" msgid "Positive integer" msgstr "正整数" @@ -564,6 +607,9 @@ msgstr "原始二进制数据" msgid "'%(value)s' is not a valid UUID." msgstr "‘%(value)s’不是有效UUID。" +msgid "Universally unique identifier" +msgstr "通用唯一识别码" + msgid "File" msgstr "文件" @@ -603,9 +649,6 @@ msgstr "这个字段是必填项。" msgid "Enter a whole number." msgstr "输入整数。" -msgid "Enter a number." -msgstr "输入一个数字。" - msgid "Enter a valid date." msgstr "输入一个有效的日期。" @@ -618,6 +661,10 @@ msgstr "输入一个有效的日期/时间。" msgid "Enter a valid duration." msgstr "请输入有效的时长。" +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "天数应该在 {min_days} 和 {max_days} 之间。" + msgid "No file was submitted. Check the encoding type on the form." msgstr "未提交文件。请检查表单的编码类型。" @@ -700,15 +747,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "请修正重复的数据." -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "内联外键与父实例的主键不匹配。" +msgid "The inline value did not match the parent instance." +msgstr "内联值与父实例不匹配。" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "选择一个有效的选项: 该选择不在可用的选项中。" #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" 不是一个合法的主键值." +msgid "\"%(pk)s\" is not a valid value." +msgstr "\"%(pk)s\" 不是一个有效值." #, python-format msgid "" @@ -718,15 +765,15 @@ msgstr "" "%(datetime)s 不能在时区 %(current_timezone)s正确解读; 可能时间有歧义或者不存" "在." +msgid "Clear" +msgstr "清除" + msgid "Currently" msgstr "目前" msgid "Change" msgstr "修改" -msgid "Clear" -msgstr "清除" - msgid "Unknown" msgstr "未知" @@ -997,8 +1044,8 @@ msgstr "该值不是合法的IPv6地址。" #, python-format msgctxt "String to return when truncating text" -msgid "%(truncated_text)s..." -msgstr "%(truncated_text)s..." +msgid "%(truncated_text)s…" +msgstr "%(truncated_text)s" msgid "or" msgstr "或" @@ -1044,7 +1091,7 @@ msgid "Forbidden" msgstr "禁止访问" msgid "CSRF verification failed. Request aborted." -msgstr "CSRF验证失败. 相应中断." +msgstr "CSRF验证失败. 请求被中断." msgid "" "You are seeing this message because this HTTPS site requires a 'Referer " @@ -1052,16 +1099,29 @@ msgid "" "required for security reasons, to ensure that your browser is not being " "hijacked by third parties." msgstr "" -"您看到此消息是由于HTTPS站点需要浏览器发送 ‘Referer HTTP头‘,但是目前没有被发" -"送。出于安全考虑,浏览器必须发送该HTTP头,以确保您的浏览器没有被第三方劫持。" +"您看到此消息是由于HTTPS站点需要您的浏览器发送的 'Referer header'信息,但是该" +"信息并未被发送。该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)启用发送该HTTP头。" +"如果您已经设置您的浏览器禁用 '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." +msgstr "" +"如果您正在使用 tag 或 包含" +"了“Referrer-Policy: no-referrer”的头部, 请移除它们。CSRF保护机制要" +"求“Referer”头部进行严格的referer检查.如果您关心隐私问题,请使用其它替代方式," +"如 到第三方站点的链接。" msgid "" "You are seeing this message because this site requires a CSRF cookie when " @@ -1075,38 +1135,18 @@ 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 "" -"如果您已经设置浏览器禁用cookies,请重新启用,至少针对这个站点,全部HTTPS请" -"求,或者同源请求(same-origin)启用cookies。" +"如果您已经设置您的浏览器禁用cookies,请重新启用它们,至少要针对这个站点,或者" +"针对 'same-origin' 请求来启用。" msgid "More information is available with DEBUG=True." -msgstr "更多信息请设置选项DEBUG=True。" - -msgid "Welcome to Django" -msgstr "欢迎认识Django" - -msgid "It worked!" -msgstr "正常工作了!" - -msgid "Congratulations on your first Django-powered page." -msgstr "祝贺你的第一个由Django驱动的页面。" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"当然,您还没有真正开始工作。接下来,请执行 python manage.py startapp " -"[app_label] 来创建您的第一个应用。" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"您看到此消息是由于Django的配置文件设置了 DEBUG = True,您还没有" -"配置任何路由URL。开始工作吧。" +msgstr "更多可用信息请设置选项DEBUG=True。" msgid "No year specified" msgstr "没有指定年" +msgid "Date out of range" +msgstr "日期超出范围。" + msgid "No month specified" msgstr "没有指定月" @@ -1118,7 +1158,7 @@ msgstr "没有指定周" #, python-format msgid "No %(verbose_name_plural)s available" -msgstr "%(verbose_name_plural)s 不存在" +msgstr "%(verbose_name_plural)s 可用" #, python-format msgid "" @@ -1137,7 +1177,7 @@ 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 "page 不等于 'last',或者它不能被转为数字。" +msgstr "页码既不为'last',也不能被转为数字。" #, python-format msgid "Invalid page (%(page_number)s): %(message)s" @@ -1157,3 +1197,48 @@ msgstr "\"%(path)s\" 不存在" #, python-format msgid "Index of %(directory)s" msgstr "%(directory)s的索引" + +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "Django:按时交付完美主义者的 Web 框架" + +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" +"查看 Django %(version)s 的 release notes " + +msgid "The install worked successfully! Congratulations!" +msgstr "" +"安装成功!\n" +"祝贺!" + +#, 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 并且您还没有配置任何URLs。" + +msgid "Django Documentation" +msgstr "Django 文档" + +msgid "Topics, references, & how-to's" +msgstr "主题,参考和指南" + +msgid "Tutorial: A Polling App" +msgstr "教程:投票应用" + +msgid "Get started with Django" +msgstr "开始使用 Django" + +msgid "Django Community" +msgstr "Django 社区" + +msgid "Connect, get help, or contribute" +msgstr "联系,获取帮助,贡献代码" diff --git a/django/conf/locale/zh_Hans/formats.py b/django/conf/locale/zh_Hans/formats.py index b6bac2f90e29..018b9b17f449 100644 --- a/django/conf/locale/zh_Hans/formats.py +++ b/django/conf/locale/zh_Hans/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'Y年n月j日' # 2016年9月5日 TIME_FORMAT = 'H:i' # 20:45 DATETIME_FORMAT = 'Y年n月j日 H:i' # 2016年9月5日 20:45 @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # 星期一 (Monday) # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%Y/%m/%d', # '2016/09/05' '%Y-%m-%d', # '2016-09-05' diff --git a/django/conf/locale/zh_Hant/LC_MESSAGES/django.mo b/django/conf/locale/zh_Hant/LC_MESSAGES/django.mo index 9cb4303ff3e3..1e310fa37095 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 e9097b5e7322..f5b70c9468b5 100644 --- a/django/conf/locale/zh_Hant/LC_MESSAGES/django.po +++ b/django/conf/locale/zh_Hant/LC_MESSAGES/django.po @@ -8,15 +8,15 @@ # mail6543210 , 2013 # ming hsien tzang , 2011 # tcc , 2011 -# Tzu-ping Chung , 2016 +# Tzu-ping Chung , 2016-2017 # Yeh-Yung , 2013 # Yeh-Yung , 2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-29 20:57+0200\n" -"PO-Revision-Date: 2016-08-30 09:04+0000\n" +"POT-Creation-Date: 2017-12-01 21:10+0100\n" +"PO-Revision-Date: 2017-12-03 05:47+0000\n" "Last-Translator: Tzu-ping Chung \n" "Language-Team: Chinese (Taiwan) (http://www.transifex.com/django/django/" "language/zh_TW/)\n" @@ -167,6 +167,9 @@ msgstr "日語" msgid "Georgian" msgstr "喬治亞語" +msgid "Kabyle" +msgstr "卡拜爾語" + msgid "Kazakh" msgstr "哈薩克語" @@ -302,6 +305,15 @@ msgstr "靜態文件" msgid "Syndication" msgstr "聯播" +msgid "That page number is not an integer" +msgstr "該頁碼並非整數" + +msgid "That page number is less than 1" +msgstr "該頁碼小於 1" + +msgid "That page contains no results" +msgstr "該頁未包含任何內容" + msgid "Enter a valid value." msgstr "請輸入有效的值。" @@ -314,6 +326,7 @@ msgstr "請輸入有效的整數。" msgid "Enter a valid email address." msgstr "請輸入有效的電子郵件地址。" +#. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "請輸入一個有效的「slug」,由字母、數字、底線與連字號組成。" @@ -384,6 +397,16 @@ msgid_plural "" "Ensure that there are no more than %(max)s digits before the decimal point." msgstr[0] "請確認小數點前不多於 %(max)s 位。" +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" +"副檔名「%(extension)s」不被允許。允許的副檔名包含:%(allowed_extensions)s。" + +msgid "Null characters are not allowed." +msgstr "不允許空(null)字元。" + msgid "and" msgstr "和" @@ -690,15 +713,15 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "請修正下方重覆的數值" -msgid "The inline foreign key did not match the parent instance primary key." -msgstr "內含的外鍵無法連接到對應的上層實體主鍵。" +msgid "The inline value did not match the parent instance." +msgstr "內含的外鍵無法連接到對應的上層實體。" msgid "Select a valid choice. That choice is not one of the available choices." msgstr "選擇有效的選項: 此選擇不在可用的選項中。" #, python-format -msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" 不是一個主鍵的有效資料。" +msgid "\"%(pk)s\" is not a valid value." +msgstr "「%(pk)s」並非有效的值。" #, python-format msgid "" @@ -708,15 +731,15 @@ msgstr "" "%(datetime)s 無法被轉換成 %(current_timezone)s 時區格式; 可能是不符格式或不存" "在。" +msgid "Clear" +msgstr "清除" + msgid "Currently" msgstr "目前" msgid "Change" msgstr "變更" -msgid "Clear" -msgstr "清除" - msgid "Unknown" msgstr "未知" @@ -1053,6 +1076,18 @@ 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." +msgstr "" +"若你使用 標籤,或在頭欄位包" +"含「Referrer-Policy: no-referrer」,請將其移除。CSRF 保護需要使用「Referer」" +"頭欄位進行嚴格參照檢查。若你擔心隱私問題,請在指向第三方網站的連結使用 語法。" + 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 " @@ -1071,32 +1106,12 @@ msgstr "" msgid "More information is available with DEBUG=True." msgstr "設定 DEBUG=True 以獲得更多資訊。" -msgid "Welcome to Django" -msgstr "歡迎使用 Django" - -msgid "It worked!" -msgstr "設定成功!" - -msgid "Congratulations on your first Django-powered page." -msgstr "觀迎來到你的第一個 Django 頁面。" - -msgid "" -"Of course, you haven't actually done any work yet. Next, start your first " -"app by running python manage.py startapp [app_label]." -msgstr "" -"當然,你什麼都還沒做。馬上用 python manage.py startapp [app_label] 建立你的第一個 app 吧。" - -msgid "" -"You're seeing this message because you have DEBUG = True in " -"your Django settings file and you haven't configured any URLs. Get to work!" -msgstr "" -"你看到這個訊息,是因為你在 Django 設定檔中包含 DEBUG = True,且" -"尚未配置任何網址。開始工作吧!" - msgid "No year specified" msgstr "不指定年份" +msgid "Date out of range" +msgstr "日期超過範圍" + msgid "No month specified" msgstr "不指定月份" @@ -1147,3 +1162,46 @@ 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." +msgstr "" +"你看到這個訊息,是因為你在 Django 設定檔中包含 DEBUG = True,且尚未配置任何網址。開始工作吧!" + +msgid "Django Documentation" +msgstr "Django 文件" + +msgid "Topics, references, & how-to's" +msgstr "主題、參考、教學" + +msgid "Tutorial: A Polling App" +msgstr "教學:投票應用" + +msgid "Get started with Django" +msgstr "初學 Django" + +msgid "Django Community" +msgstr "Django 社群" + +msgid "Connect, get help, or contribute" +msgstr "聯繫、求助、貢獻" diff --git a/django/conf/locale/zh_Hant/formats.py b/django/conf/locale/zh_Hant/formats.py index b6bac2f90e29..018b9b17f449 100644 --- a/django/conf/locale/zh_Hant/formats.py +++ b/django/conf/locale/zh_Hant/formats.py @@ -1,10 +1,7 @@ -# -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # -from __future__ import unicode_literals - # The *_FORMAT strings use the Django date format syntax, -# see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'Y年n月j日' # 2016年9月5日 TIME_FORMAT = 'H:i' # 20:45 DATETIME_FORMAT = 'Y年n月j日 H:i' # 2016年9月5日 20:45 @@ -15,7 +12,7 @@ FIRST_DAY_OF_WEEK = 1 # 星期一 (Monday) # The *_INPUT_FORMATS strings use the Python strftime format syntax, -# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior +# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior DATE_INPUT_FORMATS = [ '%Y/%m/%d', # '2016/09/05' '%Y-%m-%d', # '2016-09-05' diff --git a/django/conf/project_template/manage.py-tpl b/django/conf/project_template/manage.py-tpl index 41309844e019..9525fd7ac703 100755 --- a/django/conf/project_template/manage.py-tpl +++ b/django/conf/project_template/manage.py-tpl @@ -1,22 +1,21 @@ #!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" import os import sys -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings") + +def main(): + os.environ.setdefault('DJANGO_SETTINGS_MODULE', '{{ project_name }}.settings') try: from django.core.management import execute_from_command_line - except ImportError: - # The above import may fail for some other reason. Ensure that the - # issue is really that Django is missing to avoid masking other - # exceptions on Python 2. - try: - import django - except ImportError: - raise ImportError( - "Couldn't import Django. Are you sure it's installed and " - "available on your PYTHONPATH environment variable? Did you " - "forget to activate a virtual environment?" - ) - raise + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/django/conf/project_template/project_name/urls.py-tpl b/django/conf/project_template/project_name/urls.py-tpl index 30ddffb8769c..e23d6a92babb 100644 --- a/django/conf/project_template/project_name/urls.py-tpl +++ b/django/conf/project_template/project_name/urls.py-tpl @@ -5,17 +5,17 @@ The `urlpatterns` list routes URLs to views. For more information please see: Examples: Function views 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') + 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf - 1. Import the include() function: from django.conf.urls import url, include - 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ -from django.conf.urls import url from django.contrib import admin +from django.urls import path urlpatterns = [ - url(r'^admin/', admin.site.urls), + path('admin/', admin.site.urls), ] diff --git a/django/conf/project_template/project_name/wsgi.py-tpl b/django/conf/project_template/project_name/wsgi.py-tpl index 0d68b9564591..1ee28d0e8cf4 100644 --- a/django/conf/project_template/project_name/wsgi.py-tpl +++ b/django/conf/project_template/project_name/wsgi.py-tpl @@ -11,6 +11,6 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings") +os.environ.setdefault('DJANGO_SETTINGS_MODULE', '{{ project_name }}.settings') application = get_wsgi_application() diff --git a/django/conf/urls/__init__.py b/django/conf/urls/__init__.py index 03879bccd4ca..7bda34516bdf 100644 --- a/django/conf/urls/__init__.py +++ b/django/conf/urls/__init__.py @@ -1,85 +1,13 @@ -import warnings -from importlib import import_module - -from django.core.exceptions import ImproperlyConfigured -from django.urls import ( - LocaleRegexURLResolver, RegexURLPattern, RegexURLResolver, -) -from django.utils import six -from django.utils.deprecation import RemovedInDjango20Warning +from django.urls import include, re_path +from django.views import defaults __all__ = ['handler400', 'handler403', 'handler404', 'handler500', 'include', 'url'] -handler400 = 'django.views.defaults.bad_request' -handler403 = 'django.views.defaults.permission_denied' -handler404 = 'django.views.defaults.page_not_found' -handler500 = 'django.views.defaults.server_error' - - -def include(arg, namespace=None, app_name=None): - if app_name and not namespace: - raise ValueError('Must specify a namespace if specifying app_name.') - if app_name: - warnings.warn( - 'The app_name argument to django.conf.urls.include() is deprecated. ' - 'Set the app_name in the included URLconf instead.', - RemovedInDjango20Warning, stacklevel=2 - ) - - if isinstance(arg, tuple): - # callable returning a namespace hint - try: - urlconf_module, app_name = arg - except ValueError: - if namespace: - raise ImproperlyConfigured( - 'Cannot override the namespace for a dynamic module that provides a namespace' - ) - warnings.warn( - 'Passing a 3-tuple to django.conf.urls.include() is deprecated. ' - 'Pass a 2-tuple containing the list of patterns and app_name, ' - 'and provide the namespace argument to include() instead.', - RemovedInDjango20Warning, stacklevel=2 - ) - urlconf_module, app_name, namespace = arg - else: - # No namespace hint - use manually provided namespace - urlconf_module = arg - - if isinstance(urlconf_module, six.string_types): - urlconf_module = import_module(urlconf_module) - patterns = getattr(urlconf_module, 'urlpatterns', urlconf_module) - app_name = getattr(urlconf_module, 'app_name', app_name) - if namespace and not app_name: - warnings.warn( - 'Specifying a namespace in django.conf.urls.include() without ' - 'providing an app_name is deprecated. Set the app_name attribute ' - 'in the included module, or pass a 2-tuple containing the list of ' - 'patterns and app_name instead.', - RemovedInDjango20Warning, stacklevel=2 - ) - - namespace = namespace or app_name - - # Make sure we can iterate through the patterns (without this, some - # testcases will break). - if isinstance(patterns, (list, tuple)): - for url_pattern in patterns: - # Test if the LocaleRegexURLResolver is used within the include; - # this should throw an error since this is not allowed! - if isinstance(url_pattern, LocaleRegexURLResolver): - raise ImproperlyConfigured( - 'Using i18n_patterns in an included URLconf is not allowed.') - - return (urlconf_module, app_name, namespace) +handler400 = defaults.bad_request +handler403 = defaults.permission_denied +handler404 = defaults.page_not_found +handler500 = defaults.server_error def url(regex, view, kwargs=None, name=None): - if isinstance(view, (list, tuple)): - # For include(...) processing. - urlconf_module, app_name, namespace = view - return RegexURLResolver(regex, urlconf_module, kwargs, app_name=app_name, namespace=namespace) - elif callable(view): - return RegexURLPattern(regex, view, kwargs, name) - else: - raise TypeError('view must be a callable or a list/tuple in the case of include().') + return re_path(regex, view, kwargs, name) diff --git a/django/conf/urls/i18n.py b/django/conf/urls/i18n.py index 14f4c6971b0c..256c247491ed 100644 --- a/django/conf/urls/i18n.py +++ b/django/conf/urls/i18n.py @@ -1,37 +1,39 @@ +import functools + from django.conf import settings -from django.conf.urls import url -from django.urls import LocaleRegexURLResolver, get_resolver -from django.utils import lru_cache +from django.urls import LocalePrefixPattern, URLResolver, get_resolver, path from django.views.i18n import set_language -def i18n_patterns(*urls, **kwargs): +def i18n_patterns(*urls, prefix_default_language=True): """ - Adds the language code prefix to every URL pattern within this - function. This may only be used in the root URLconf, not in an included - URLconf. + Add the language code prefix to every URL pattern within this function. + This may only be used in the root URLconf, not in an included URLconf. """ if not settings.USE_I18N: return list(urls) - prefix_default_language = kwargs.pop('prefix_default_language', True) - assert not kwargs, 'Unexpected kwargs for i18n_patterns(): %s' % kwargs - return [LocaleRegexURLResolver(list(urls), prefix_default_language=prefix_default_language)] + return [ + URLResolver( + LocalePrefixPattern(prefix_default_language=prefix_default_language), + list(urls), + ) + ] -@lru_cache.lru_cache(maxsize=None) +@functools.lru_cache(maxsize=None) def is_language_prefix_patterns_used(urlconf): """ Return a tuple of two booleans: ( - `True` if LocaleRegexURLResolver` is used in the `urlconf`, + `True` if i18n_patterns() (LocalePrefixPattern) is used in the URLconf, `True` if the default language should be prefixed ) """ for url_pattern in get_resolver(urlconf).url_patterns: - if isinstance(url_pattern, LocaleRegexURLResolver): - return True, url_pattern.prefix_default_language + if isinstance(url_pattern.pattern, LocalePrefixPattern): + return True, url_pattern.pattern.prefix_default_language return False, False urlpatterns = [ - url(r'^setlang/$', set_language, name='set_language'), + path('setlang/', set_language, name='set_language'), ] diff --git a/django/conf/urls/static.py b/django/conf/urls/static.py index e8f8a9e80488..fa83645b9dd8 100644 --- a/django/conf/urls/static.py +++ b/django/conf/urls/static.py @@ -1,14 +1,15 @@ import re +from urllib.parse import urlsplit from django.conf import settings -from django.conf.urls import url from django.core.exceptions import ImproperlyConfigured +from django.urls import re_path from django.views.static import serve def static(prefix, view=serve, **kwargs): """ - Helper function to return a URL pattern for serving files in debug mode. + Return a URL pattern for serving files in debug mode. from django.conf import settings from django.conf.urls.static import static @@ -17,11 +18,11 @@ def static(prefix, view=serve, **kwargs): # ... the rest of your URLconf goes here ... ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) """ - # No-op if not in debug mode or an non-local prefix - if not settings.DEBUG or (prefix and '://' in prefix): - return [] - elif not prefix: + if not prefix: raise ImproperlyConfigured("Empty static prefix not permitted") + elif not settings.DEBUG or urlsplit(prefix).netloc: + # No-op if not in debug mode or a non-local prefix. + return [] return [ - url(r'^%s(?P.*)$' % re.escape(prefix.lstrip('/')), view, kwargs=kwargs), + re_path(r'^%s(?P.*)$' % re.escape(prefix.lstrip('/')), view, kwargs=kwargs), ] diff --git a/django/contrib/admin/actions.py b/django/contrib/admin/actions.py index f07f3549cbda..1e1c3bd384f3 100644 --- a/django/contrib/admin/actions.py +++ b/django/contrib/admin/actions.py @@ -4,12 +4,10 @@ from django.contrib import messages from django.contrib.admin import helpers -from django.contrib.admin.utils import get_deleted_objects, model_ngettext +from django.contrib.admin.utils import model_ngettext from django.core.exceptions import PermissionDenied -from django.db import router from django.template.response import TemplateResponse -from django.utils.encoding import force_text -from django.utils.translation import ugettext as _, ugettext_lazy +from django.utils.translation import gettext as _, gettext_lazy def delete_selected(modeladmin, request, queryset): @@ -17,7 +15,7 @@ def delete_selected(modeladmin, request, queryset): Default action which deletes the selected objects. This action first displays a confirmation page which shows all the - deleteable objects, or, if the user has no permission one of the related + deletable objects, or, if the user has no permission one of the related childs (foreignkeys), a "permission denied" message. Next, it deletes all selected objects and redirects back to the change list. @@ -25,57 +23,47 @@ def delete_selected(modeladmin, request, queryset): opts = modeladmin.model._meta app_label = opts.app_label - # Check that the user has delete permission for the actual model - if not modeladmin.has_delete_permission(request): - raise PermissionDenied - - using = router.db_for_write(modeladmin.model) - # Populate deletable_objects, a data structure of all related objects that # will also be deleted. - deletable_objects, model_count, perms_needed, protected = get_deleted_objects( - queryset, opts, request.user, modeladmin.admin_site, using) + deletable_objects, model_count, perms_needed, protected = modeladmin.get_deleted_objects(queryset, request) # The user has already confirmed the deletion. - # Do the deletion and return a None to display the change list view again. + # Do the deletion and return None to display the change list view again. if request.POST.get('post') and not protected: if perms_needed: raise PermissionDenied n = queryset.count() if n: for obj in queryset: - obj_display = force_text(obj) + obj_display = str(obj) modeladmin.log_deletion(request, obj, obj_display) - queryset.delete() + modeladmin.delete_queryset(request, queryset) modeladmin.message_user(request, _("Successfully deleted %(count)d %(items)s.") % { "count": n, "items": model_ngettext(modeladmin.opts, n) }, messages.SUCCESS) # Return None to display the change list page again. return None - if len(queryset) == 1: - objects_name = force_text(opts.verbose_name) - else: - objects_name = force_text(opts.verbose_name_plural) + objects_name = model_ngettext(queryset) if perms_needed or protected: title = _("Cannot delete %(name)s") % {"name": objects_name} else: title = _("Are you sure?") - context = dict( - modeladmin.admin_site.each_context(request), - title=title, - objects_name=objects_name, - deletable_objects=[deletable_objects], - model_count=dict(model_count).items(), - queryset=queryset, - perms_lacking=perms_needed, - protected=protected, - opts=opts, - action_checkbox_name=helpers.ACTION_CHECKBOX_NAME, - media=modeladmin.media, - ) + context = { + **modeladmin.admin_site.each_context(request), + 'title': title, + 'objects_name': str(objects_name), + 'deletable_objects': [deletable_objects], + 'model_count': dict(model_count).items(), + 'queryset': queryset, + 'perms_lacking': perms_needed, + 'protected': protected, + 'opts': opts, + 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME, + 'media': modeladmin.media, + } request.current_app = modeladmin.admin_site.name @@ -87,4 +75,5 @@ def delete_selected(modeladmin, request, queryset): ], context) -delete_selected.short_description = ugettext_lazy("Delete selected %(verbose_name_plural)s") +delete_selected.allowed_permissions = ('delete',) +delete_selected.short_description = gettext_lazy("Delete selected %(verbose_name_plural)s") diff --git a/django/contrib/admin/apps.py b/django/contrib/admin/apps.py index 194ec9f89d16..36c157683ddf 100644 --- a/django/contrib/admin/apps.py +++ b/django/contrib/admin/apps.py @@ -1,12 +1,13 @@ from django.apps import AppConfig from django.contrib.admin.checks import check_admin_app, check_dependencies from django.core import checks -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class SimpleAdminConfig(AppConfig): """Simple AppConfig which does not do automatic discovery.""" + default_site = 'django.contrib.admin.sites.AdminSite' name = 'django.contrib.admin' verbose_name = _("Administration") @@ -19,5 +20,5 @@ class AdminConfig(SimpleAdminConfig): """The default AppConfig for admin which does autodiscovery.""" def ready(self): - super(AdminConfig, self).ready() + super().ready() self.module.autodiscover() diff --git a/django/contrib/admin/bin/compress.py b/django/contrib/admin/bin/compress.py index dbef84d75916..69660284eb75 100644 --- a/django/contrib/admin/bin/compress.py +++ b/django/contrib/admin/bin/compress.py @@ -1,17 +1,17 @@ #!/usr/bin/env python import argparse -import os import subprocess import sys +from pathlib import Path try: import closure except ImportError: closure_compiler = None else: - closure_compiler = os.path.join(os.path.dirname(closure.__file__), 'closure.jar') + closure_compiler = closure.get_jar_filename() -js_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'static', 'admin', 'js') +js_path = Path(__file__).parent.parent / 'static' / 'admin' / 'js' def main(): @@ -28,8 +28,8 @@ def main(): parser.add_argument("-q", "--quiet", action="store_false", dest="verbose") options = parser.parse_args() - compiler = closure_compiler if closure_compiler else os.path.expanduser(options.compiler) - if not os.path.exists(compiler): + compiler = Path(closure_compiler or options.compiler).expanduser() + if not compiler.exists(): sys.exit( "Google Closure compiler jar file %s not found. Please use the -c " "option to specify the path." % compiler @@ -39,18 +39,16 @@ def main(): if options.verbose: sys.stdout.write("No filenames given; defaulting to admin scripts\n") files = [ - os.path.join(js_path, f) for f in - ["actions.js", "collapse.js", "inlines.js", "prepopulate.js"] + js_path / f + for f in ["actions.js", "collapse.js", "inlines.js", "prepopulate.js"] ] else: - files = options.file - - for file_name in files: - if not file_name.endswith(".js"): - file_name = file_name + ".js" - to_compress = os.path.expanduser(file_name) - if os.path.exists(to_compress): - to_compress_min = "%s.min.js" % "".join(file_name.rsplit(".js")) + files = [Path(f) for f in options.file] + + for file_path in files: + to_compress = file_path.expanduser() + if to_compress.exists(): + to_compress_min = to_compress.with_suffix('.min.js') cmd = "java -jar %s --js %s --js_output_file %s" % (compiler, to_compress, to_compress_min) if options.verbose: sys.stdout.write("Running: %s\n" % cmd) diff --git a/django/contrib/admin/checks.py b/django/contrib/admin/checks.py index 286c1169a489..aa549943cb71 100644 --- a/django/contrib/admin/checks.py +++ b/django/contrib/admin/checks.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - +import warnings from itertools import chain from django.apps import apps @@ -12,10 +10,43 @@ from django.core.exceptions import FieldDoesNotExist from django.db import models from django.db.models.constants import LOOKUP_SEP +from django.db.models.expressions import Combinable, F, OrderBy from django.forms.models import ( BaseModelForm, BaseModelFormSet, _get_foreign_key, ) -from django.template.engine import Engine +from django.template import engines +from django.template.backends.django import DjangoTemplates +from django.utils.deprecation import RemovedInDjango30Warning +from django.utils.inspect import get_func_args +from django.utils.module_loading import import_string + + +def _issubclass(cls, classinfo): + """ + issubclass() variant that doesn't raise an exception if cls isn't a + class. + """ + try: + return issubclass(cls, classinfo) + except TypeError: + return False + + +def _contains_subclass(class_path, candidate_paths): + """ + Return whether or not a dotted class path (or a subclass of that class) is + found in a list of candidate paths. + """ + cls = import_string(class_path) + for path in candidate_paths: + try: + candidate_cls = import_string(path) + except ImportError: + # ImportErrors are raised elsewhere. + continue + if _issubclass(candidate_cls, cls): + return True + return False def check_admin_app(app_configs, **kwargs): @@ -30,58 +61,147 @@ def check_dependencies(**kwargs): """ Check that the admin's dependencies are correctly installed. """ + if not apps.is_installed('django.contrib.admin'): + return [] errors = [] - # contrib.contenttypes must be installed. - if not apps.is_installed('django.contrib.contenttypes'): - missing_app = checks.Error( - "'django.contrib.contenttypes' must be in INSTALLED_APPS in order " - "to use the admin application.", - id="admin.E401", - ) - errors.append(missing_app) - # The auth context processor must be installed if using the default - # authentication backend. - try: - default_template_engine = Engine.get_default() - except Exception: - # Skip this non-critical check: - # 1. if the user has a non-trivial TEMPLATES setting and Django - # can't find a default template engine - # 2. if anything goes wrong while loading template engines, in - # order to avoid raising an exception from a confusing location - # Catching ImproperlyConfigured suffices for 1. but 2. requires - # catching all exceptions. - pass + app_dependencies = ( + ('django.contrib.contenttypes', 401), + ('django.contrib.auth', 405), + ('django.contrib.messages', 406), + ) + for app_name, error_code in app_dependencies: + if not apps.is_installed(app_name): + errors.append(checks.Error( + "'%s' must be in INSTALLED_APPS in order to use the admin " + "application." % app_name, + id='admin.E%d' % error_code, + )) + for engine in engines.all(): + if isinstance(engine, DjangoTemplates): + django_templates_instance = engine.engine + break + else: + django_templates_instance = None + if not django_templates_instance: + errors.append(checks.Error( + "A 'django.template.backends.django.DjangoTemplates' instance " + "must be configured in TEMPLATES in order to use the admin " + "application.", + id='admin.E403', + )) else: if ('django.contrib.auth.context_processors.auth' - not in default_template_engine.context_processors and - 'django.contrib.auth.backends.ModelBackend' in settings.AUTHENTICATION_BACKENDS): - missing_template = checks.Error( - "'django.contrib.auth.context_processors.auth' must be in " - "TEMPLATES in order to use the admin application.", - id="admin.E402" - ) - errors.append(missing_template) + not in django_templates_instance.context_processors and + _contains_subclass('django.contrib.auth.backends.ModelBackend', settings.AUTHENTICATION_BACKENDS)): + errors.append(checks.Error( + "'django.contrib.auth.context_processors.auth' must be " + "enabled in DjangoTemplates (TEMPLATES) if using the default " + "auth backend in order to use the admin application.", + id='admin.E402', + )) + if ('django.contrib.messages.context_processors.messages' + not in django_templates_instance.context_processors): + errors.append(checks.Error( + "'django.contrib.messages.context_processors.messages' must " + "be enabled in DjangoTemplates (TEMPLATES) in order to use " + "the admin application.", + id='admin.E404', + )) + + if not _contains_subclass('django.contrib.auth.middleware.AuthenticationMiddleware', settings.MIDDLEWARE): + errors.append(checks.Error( + "'django.contrib.auth.middleware.AuthenticationMiddleware' must " + "be in MIDDLEWARE in order to use the admin application.", + id='admin.E408', + )) + if not _contains_subclass('django.contrib.messages.middleware.MessageMiddleware', settings.MIDDLEWARE): + errors.append(checks.Error( + "'django.contrib.messages.middleware.MessageMiddleware' must " + "be in MIDDLEWARE in order to use the admin application.", + id='admin.E409', + )) + if not _contains_subclass('django.contrib.sessions.middleware.SessionMiddleware', settings.MIDDLEWARE): + errors.append(checks.Error( + "'django.contrib.sessions.middleware.SessionMiddleware' must " + "be in MIDDLEWARE in order to use the admin application.", + id='admin.E410', + )) return errors -class BaseModelAdminChecks(object): +class BaseModelAdminChecks: def check(self, admin_obj, **kwargs): - errors = [] - errors.extend(self._check_raw_id_fields(admin_obj)) - errors.extend(self._check_fields(admin_obj)) - errors.extend(self._check_fieldsets(admin_obj)) - errors.extend(self._check_exclude(admin_obj)) - errors.extend(self._check_form(admin_obj)) - errors.extend(self._check_filter_vertical(admin_obj)) - errors.extend(self._check_filter_horizontal(admin_obj)) - errors.extend(self._check_radio_fields(admin_obj)) - errors.extend(self._check_prepopulated_fields(admin_obj)) - errors.extend(self._check_view_on_site_url(admin_obj)) - errors.extend(self._check_ordering(admin_obj)) - errors.extend(self._check_readonly_fields(admin_obj)) - return errors + return [ + *self._check_autocomplete_fields(admin_obj), + *self._check_raw_id_fields(admin_obj), + *self._check_fields(admin_obj), + *self._check_fieldsets(admin_obj), + *self._check_exclude(admin_obj), + *self._check_form(admin_obj), + *self._check_filter_vertical(admin_obj), + *self._check_filter_horizontal(admin_obj), + *self._check_radio_fields(admin_obj), + *self._check_prepopulated_fields(admin_obj), + *self._check_view_on_site_url(admin_obj), + *self._check_ordering(admin_obj), + *self._check_readonly_fields(admin_obj), + ] + + def _check_autocomplete_fields(self, obj): + """ + Check that `autocomplete_fields` is a list or tuple of model fields. + """ + if not isinstance(obj.autocomplete_fields, (list, tuple)): + return must_be('a list or tuple', option='autocomplete_fields', obj=obj, id='admin.E036') + else: + return list(chain.from_iterable([ + self._check_autocomplete_fields_item(obj, field_name, 'autocomplete_fields[%d]' % index) + for index, field_name in enumerate(obj.autocomplete_fields) + ])) + + def _check_autocomplete_fields_item(self, obj, field_name, label): + """ + Check that an item in `autocomplete_fields` is a ForeignKey or a + ManyToManyField and that the item has a related ModelAdmin with + search_fields defined. + """ + try: + field = obj.model._meta.get_field(field_name) + except FieldDoesNotExist: + return refer_to_missing_field(field=field_name, option=label, obj=obj, id='admin.E037') + else: + if not field.many_to_many and not isinstance(field, models.ForeignKey): + return must_be( + 'a foreign key or a many-to-many field', + option=label, obj=obj, id='admin.E038' + ) + related_admin = obj.admin_site._registry.get(field.remote_field.model) + if related_admin is None: + return [ + checks.Error( + 'An admin for model "%s" has to be registered ' + 'to be referenced by %s.autocomplete_fields.' % ( + field.remote_field.model.__name__, + type(obj).__name__, + ), + obj=obj.__class__, + id='admin.E039', + ) + ] + elif not related_admin.search_fields: + return [ + checks.Error( + '%s must define "search_fields", because it\'s ' + 'referenced by %s.autocomplete_fields.' % ( + related_admin.__class__.__name__, + type(obj).__name__, + ), + obj=obj.__class__, + id='admin.E040', + ) + ] + return [] def _check_raw_id_fields(self, obj): """ Check that `raw_id_fields` only contains field names that are listed @@ -90,25 +210,23 @@ def _check_raw_id_fields(self, obj): if not isinstance(obj.raw_id_fields, (list, tuple)): return must_be('a list or tuple', option='raw_id_fields', obj=obj, id='admin.E001') else: - return list(chain(*[ - self._check_raw_id_fields_item(obj, obj.model, field_name, 'raw_id_fields[%d]' % index) + return list(chain.from_iterable( + self._check_raw_id_fields_item(obj, field_name, 'raw_id_fields[%d]' % index) for index, field_name in enumerate(obj.raw_id_fields) - ])) + )) - def _check_raw_id_fields_item(self, obj, model, field_name, label): + def _check_raw_id_fields_item(self, obj, field_name, label): """ Check an item of `raw_id_fields`, i.e. check that field named `field_name` exists in model `model` and is a ForeignKey or a ManyToManyField. """ try: - field = model._meta.get_field(field_name) + field = obj.model._meta.get_field(field_name) except FieldDoesNotExist: - return refer_to_missing_field(field=field_name, option=label, - model=model, obj=obj, id='admin.E002') + return refer_to_missing_field(field=field_name, option=label, obj=obj, id='admin.E002') else: if not field.many_to_many and not isinstance(field, models.ForeignKey): - return must_be('a foreign key or a many-to-many field', - option=label, obj=obj, id='admin.E003') + return must_be('a foreign key or a many-to-many field', option=label, obj=obj, id='admin.E003') else: return [] @@ -139,10 +257,10 @@ def _check_fields(self, obj): ) ] - return list(chain(*[ - self._check_field_spec(obj, obj.model, field_name, 'fields') + return list(chain.from_iterable( + self._check_field_spec(obj, field_name, 'fields') for field_name in obj.fields - ])) + )) def _check_fieldsets(self, obj): """ Check that fieldsets is properly formatted and doesn't contain @@ -153,12 +271,13 @@ def _check_fieldsets(self, obj): elif not isinstance(obj.fieldsets, (list, tuple)): return must_be('a list or tuple', option='fieldsets', obj=obj, id='admin.E007') else: - return list(chain(*[ - self._check_fieldsets_item(obj, obj.model, fieldset, 'fieldsets[%d]' % index) + seen_fields = [] + return list(chain.from_iterable( + self._check_fieldsets_item(obj, fieldset, 'fieldsets[%d]' % index, seen_fields) for index, fieldset in enumerate(obj.fieldsets) - ])) + )) - def _check_fieldsets_item(self, obj, model, fieldset, label): + def _check_fieldsets_item(self, obj, fieldset, label, seen_fields): """ Check an item of `fieldsets`, i.e. check that this is a pair of a set name and a dictionary containing "fields" key. """ @@ -179,8 +298,8 @@ def _check_fieldsets_item(self, obj, model, fieldset, label): elif not isinstance(fieldset[1]['fields'], (list, tuple)): return must_be('a list or tuple', option="%s[1]['fields']" % label, obj=obj, id='admin.E008') - fields = flatten(fieldset[1]['fields']) - if len(fields) != len(set(fields)): + seen_fields.extend(flatten(fieldset[1]['fields'])) + if len(seen_fields) != len(set(seen_fields)): return [ checks.Error( "There are duplicate field(s) in '%s[1]'." % label, @@ -188,25 +307,25 @@ def _check_fieldsets_item(self, obj, model, fieldset, label): id='admin.E012', ) ] - return list(chain(*[ - self._check_field_spec(obj, model, fieldset_fields, '%s[1]["fields"]' % label) + return list(chain.from_iterable( + self._check_field_spec(obj, fieldset_fields, '%s[1]["fields"]' % label) for fieldset_fields in fieldset[1]['fields'] - ])) + )) - def _check_field_spec(self, obj, model, fields, label): + def _check_field_spec(self, obj, fields, label): """ `fields` should be an item of `fields` or an item of fieldset[1]['fields'] for any `fieldset` in `fieldsets`. It should be a field name or a tuple of field names. """ if isinstance(fields, tuple): - return list(chain(*[ - self._check_field_spec_item(obj, model, field_name, "%s[%d]" % (label, index)) + return list(chain.from_iterable( + self._check_field_spec_item(obj, field_name, "%s[%d]" % (label, index)) for index, field_name in enumerate(fields) - ])) + )) else: - return self._check_field_spec_item(obj, model, fields, label) + return self._check_field_spec_item(obj, fields, label) - def _check_field_spec_item(self, obj, model, field_name, label): + def _check_field_spec_item(self, obj, field_name, label): if field_name in obj.readonly_fields: # Stuff can be put in fields that isn't actually a model field if # it's in readonly_fields, readonly_fields will handle the @@ -214,7 +333,7 @@ def _check_field_spec_item(self, obj, model, field_name, label): return [] else: try: - field = model._meta.get_field(field_name) + field = obj.model._meta.get_field(field_name) except FieldDoesNotExist: # If we can't find a field on the model that matches, it could # be an extra field on the form. @@ -254,8 +373,7 @@ def _check_exclude(self, obj): def _check_form(self, obj): """ Check that form subclasses BaseModelForm. """ - - if hasattr(obj, 'form') and not issubclass(obj.form, BaseModelForm): + if not _issubclass(obj.form, BaseModelForm): return must_inherit_from(parent='BaseModelForm', option='form', obj=obj, id='admin.E016') else: @@ -263,39 +381,32 @@ def _check_form(self, obj): def _check_filter_vertical(self, obj): """ Check that filter_vertical is a sequence of field names. """ - - if not hasattr(obj, 'filter_vertical'): - return [] - elif not isinstance(obj.filter_vertical, (list, tuple)): + if not isinstance(obj.filter_vertical, (list, tuple)): return must_be('a list or tuple', option='filter_vertical', obj=obj, id='admin.E017') else: - return list(chain(*[ - self._check_filter_item(obj, obj.model, field_name, "filter_vertical[%d]" % index) + return list(chain.from_iterable( + self._check_filter_item(obj, field_name, "filter_vertical[%d]" % index) for index, field_name in enumerate(obj.filter_vertical) - ])) + )) def _check_filter_horizontal(self, obj): """ Check that filter_horizontal is a sequence of field names. """ - - if not hasattr(obj, 'filter_horizontal'): - return [] - elif not isinstance(obj.filter_horizontal, (list, tuple)): + if not isinstance(obj.filter_horizontal, (list, tuple)): return must_be('a list or tuple', option='filter_horizontal', obj=obj, id='admin.E018') else: - return list(chain(*[ - self._check_filter_item(obj, obj.model, field_name, "filter_horizontal[%d]" % index) + return list(chain.from_iterable( + self._check_filter_item(obj, field_name, "filter_horizontal[%d]" % index) for index, field_name in enumerate(obj.filter_horizontal) - ])) + )) - def _check_filter_item(self, obj, model, field_name, label): + def _check_filter_item(self, obj, field_name, label): """ Check one item of `filter_vertical` or `filter_horizontal`, i.e. check that given field exists and is a ManyToManyField. """ try: - field = model._meta.get_field(field_name) + field = obj.model._meta.get_field(field_name) except FieldDoesNotExist: - return refer_to_missing_field(field=field_name, option=label, - model=model, obj=obj, id='admin.E019') + return refer_to_missing_field(field=field_name, option=label, obj=obj, id='admin.E019') else: if not field.many_to_many: return must_be('a many-to-many field', option=label, obj=obj, id='admin.E020') @@ -304,27 +415,23 @@ def _check_filter_item(self, obj, model, field_name, label): def _check_radio_fields(self, obj): """ Check that `radio_fields` is a dictionary. """ - - if not hasattr(obj, 'radio_fields'): - return [] - elif not isinstance(obj.radio_fields, dict): + if not isinstance(obj.radio_fields, dict): return must_be('a dictionary', option='radio_fields', obj=obj, id='admin.E021') else: - return list(chain(*[ - self._check_radio_fields_key(obj, obj.model, field_name, 'radio_fields') + + return list(chain.from_iterable( + self._check_radio_fields_key(obj, field_name, 'radio_fields') + self._check_radio_fields_value(obj, val, 'radio_fields["%s"]' % field_name) for field_name, val in obj.radio_fields.items() - ])) + )) - def _check_radio_fields_key(self, obj, model, field_name, label): + def _check_radio_fields_key(self, obj, field_name, label): """ Check that a key of `radio_fields` dictionary is name of existing field and that the field is a ForeignKey or has `choices` defined. """ try: - field = model._meta.get_field(field_name) + field = obj.model._meta.get_field(field_name) except FieldDoesNotExist: - return refer_to_missing_field(field=field_name, option=label, - model=model, obj=obj, id='admin.E022') + return refer_to_missing_field(field=field_name, option=label, obj=obj, id='admin.E022') else: if not (isinstance(field, models.ForeignKey) or field.choices): return [ @@ -357,45 +464,38 @@ def _check_radio_fields_value(self, obj, val, label): return [] def _check_view_on_site_url(self, obj): - if hasattr(obj, 'view_on_site'): - if not callable(obj.view_on_site) and not isinstance(obj.view_on_site, bool): - return [ - checks.Error( - "The value of 'view_on_site' must be a callable or a boolean value.", - obj=obj.__class__, - id='admin.E025', - ) - ] - else: - return [] + if not callable(obj.view_on_site) and not isinstance(obj.view_on_site, bool): + return [ + checks.Error( + "The value of 'view_on_site' must be a callable or a boolean value.", + obj=obj.__class__, + id='admin.E025', + ) + ] else: return [] def _check_prepopulated_fields(self, obj): """ Check that `prepopulated_fields` is a dictionary containing allowed field types. """ - - if not hasattr(obj, 'prepopulated_fields'): - return [] - elif not isinstance(obj.prepopulated_fields, dict): + if not isinstance(obj.prepopulated_fields, dict): return must_be('a dictionary', option='prepopulated_fields', obj=obj, id='admin.E026') else: - return list(chain(*[ - self._check_prepopulated_fields_key(obj, obj.model, field_name, 'prepopulated_fields') + - self._check_prepopulated_fields_value(obj, obj.model, val, 'prepopulated_fields["%s"]' % field_name) + return list(chain.from_iterable( + self._check_prepopulated_fields_key(obj, field_name, 'prepopulated_fields') + + self._check_prepopulated_fields_value(obj, val, 'prepopulated_fields["%s"]' % field_name) for field_name, val in obj.prepopulated_fields.items() - ])) + )) - def _check_prepopulated_fields_key(self, obj, model, field_name, label): + def _check_prepopulated_fields_key(self, obj, field_name, label): """ Check a key of `prepopulated_fields` dictionary, i.e. check that it is a name of existing field and the field is one of the allowed types. """ try: - field = model._meta.get_field(field_name) + field = obj.model._meta.get_field(field_name) except FieldDoesNotExist: - return refer_to_missing_field(field=field_name, option=label, - model=model, obj=obj, id='admin.E027') + return refer_to_missing_field(field=field_name, option=label, obj=obj, id='admin.E027') else: if isinstance(field, (models.DateTimeField, models.ForeignKey, models.ManyToManyField)): return [ @@ -409,26 +509,26 @@ def _check_prepopulated_fields_key(self, obj, model, field_name, label): else: return [] - def _check_prepopulated_fields_value(self, obj, model, val, label): + def _check_prepopulated_fields_value(self, obj, val, label): """ Check a value of `prepopulated_fields` dictionary, i.e. it's an iterable of existing fields. """ if not isinstance(val, (list, tuple)): return must_be('a list or tuple', option=label, obj=obj, id='admin.E029') else: - return list(chain(*[ - self._check_prepopulated_fields_value_item(obj, model, subfield_name, "%s[%r]" % (label, index)) + return list(chain.from_iterable( + self._check_prepopulated_fields_value_item(obj, subfield_name, "%s[%r]" % (label, index)) for index, subfield_name in enumerate(val) - ])) + )) - def _check_prepopulated_fields_value_item(self, obj, model, field_name, label): + def _check_prepopulated_fields_value_item(self, obj, field_name, label): """ For `prepopulated_fields` equal to {"slug": ("title",)}, `field_name` is "title". """ try: - model._meta.get_field(field_name) + obj.model._meta.get_field(field_name) except FieldDoesNotExist: - return refer_to_missing_field(field=field_name, option=label, model=model, obj=obj, id='admin.E030') + return refer_to_missing_field(field=field_name, option=label, obj=obj, id='admin.E030') else: return [] @@ -441,14 +541,20 @@ def _check_ordering(self, obj): elif not isinstance(obj.ordering, (list, tuple)): return must_be('a list or tuple', option='ordering', obj=obj, id='admin.E031') else: - return list(chain(*[ - self._check_ordering_item(obj, obj.model, field_name, 'ordering[%d]' % index) + return list(chain.from_iterable( + self._check_ordering_item(obj, field_name, 'ordering[%d]' % index) for index, field_name in enumerate(obj.ordering) - ])) + )) - def _check_ordering_item(self, obj, model, field_name, label): + def _check_ordering_item(self, obj, field_name, label): """ Check that `ordering` refers to existing fields. """ - + if isinstance(field_name, (Combinable, OrderBy)): + if not isinstance(field_name, OrderBy): + field_name = field_name.asc() + if isinstance(field_name.expression, F): + field_name = field_name.expression.name + else: + return [] if field_name == '?' and len(obj.ordering) != 1: return [ checks.Error( @@ -468,11 +574,12 @@ def _check_ordering_item(self, obj, model, field_name, label): else: if field_name.startswith('-'): field_name = field_name[1:] - + if field_name == 'pk': + return [] try: - model._meta.get_field(field_name) + obj.model._meta.get_field(field_name) except FieldDoesNotExist: - return refer_to_missing_field(field=field_name, option=label, model=model, obj=obj, id='admin.E033') + return refer_to_missing_field(field=field_name, option=label, obj=obj, id='admin.E033') else: return [] @@ -484,26 +591,26 @@ def _check_readonly_fields(self, obj): elif not isinstance(obj.readonly_fields, (list, tuple)): return must_be('a list or tuple', option='readonly_fields', obj=obj, id='admin.E034') else: - return list(chain(*[ - self._check_readonly_fields_item(obj, obj.model, field_name, "readonly_fields[%d]" % index) + return list(chain.from_iterable( + self._check_readonly_fields_item(obj, field_name, "readonly_fields[%d]" % index) for index, field_name in enumerate(obj.readonly_fields) - ])) + )) - def _check_readonly_fields_item(self, obj, model, field_name, label): + def _check_readonly_fields_item(self, obj, field_name, label): if callable(field_name): return [] elif hasattr(obj, field_name): return [] - elif hasattr(model, field_name): + elif hasattr(obj.model, field_name): return [] else: try: - model._meta.get_field(field_name) + obj.model._meta.get_field(field_name) except FieldDoesNotExist: return [ checks.Error( "The value of '%s' is not a callable, an attribute of '%s', or an attribute of '%s.%s'." % ( - label, obj.__class__.__name__, model._meta.app_label, model._meta.object_name + label, obj.__class__.__name__, obj.model._meta.app_label, obj.model._meta.object_name ), obj=obj.__class__, id='admin.E035', @@ -516,20 +623,23 @@ def _check_readonly_fields_item(self, obj, model, field_name, label): class ModelAdminChecks(BaseModelAdminChecks): def check(self, admin_obj, **kwargs): - errors = super(ModelAdminChecks, self).check(admin_obj) - errors.extend(self._check_save_as(admin_obj)) - errors.extend(self._check_save_on_top(admin_obj)) - errors.extend(self._check_inlines(admin_obj)) - errors.extend(self._check_list_display(admin_obj)) - errors.extend(self._check_list_display_links(admin_obj)) - errors.extend(self._check_list_filter(admin_obj)) - errors.extend(self._check_list_select_related(admin_obj)) - errors.extend(self._check_list_per_page(admin_obj)) - errors.extend(self._check_list_max_show_all(admin_obj)) - errors.extend(self._check_list_editable(admin_obj)) - errors.extend(self._check_search_fields(admin_obj)) - errors.extend(self._check_date_hierarchy(admin_obj)) - return errors + return [ + *super().check(admin_obj), + *self._check_save_as(admin_obj), + *self._check_save_on_top(admin_obj), + *self._check_inlines(admin_obj), + *self._check_list_display(admin_obj), + *self._check_list_display_links(admin_obj), + *self._check_list_filter(admin_obj), + *self._check_list_select_related(admin_obj), + *self._check_list_per_page(admin_obj), + *self._check_list_max_show_all(admin_obj), + *self._check_list_editable(admin_obj), + *self._check_search_fields(admin_obj), + *self._check_date_hierarchy(admin_obj), + *self._check_action_permission_methods(admin_obj), + *self._check_actions_uniqueness(admin_obj), + ] def _check_save_as(self, obj): """ Check save_as is a boolean. """ @@ -555,18 +665,27 @@ def _check_inlines(self, obj): if not isinstance(obj.inlines, (list, tuple)): return must_be('a list or tuple', option='inlines', obj=obj, id='admin.E103') else: - return list(chain(*[ - self._check_inlines_item(obj, obj.model, item, "inlines[%d]" % index) + return list(chain.from_iterable( + self._check_inlines_item(obj, item, "inlines[%d]" % index) for index, item in enumerate(obj.inlines) - ])) + )) - def _check_inlines_item(self, obj, model, inline, label): + def _check_inlines_item(self, obj, inline, label): """ Check one inline model admin. """ - inline_label = '.'.join([inline.__module__, inline.__name__]) + try: + inline_label = inline.__module__ + '.' + inline.__name__ + except AttributeError: + return [ + checks.Error( + "'%s' must inherit from 'InlineModelAdmin'." % obj, + obj=obj.__class__, + id='admin.E104', + ) + ] from django.contrib.admin.options import InlineModelAdmin - if not issubclass(inline, InlineModelAdmin): + if not _issubclass(inline, InlineModelAdmin): return [ checks.Error( "'%s' must inherit from 'InlineModelAdmin'." % inline_label, @@ -582,10 +701,10 @@ def _check_inlines_item(self, obj, model, inline, label): id='admin.E105', ) ] - elif not issubclass(inline.model, models.Model): + elif not _issubclass(inline.model, models.Model): return must_be('a Model', option='%s.model' % inline_label, obj=obj, id='admin.E106') else: - return inline(model, obj.admin_site).check() + return inline(obj.model, obj.admin_site).check() def _check_list_display(self, obj): """ Check that list_display only contains fields or usable attributes. @@ -594,65 +713,43 @@ def _check_list_display(self, obj): if not isinstance(obj.list_display, (list, tuple)): return must_be('a list or tuple', option='list_display', obj=obj, id='admin.E107') else: - return list(chain(*[ - self._check_list_display_item(obj, obj.model, item, "list_display[%d]" % index) + return list(chain.from_iterable( + self._check_list_display_item(obj, item, "list_display[%d]" % index) for index, item in enumerate(obj.list_display) - ])) + )) - def _check_list_display_item(self, obj, model, item, label): + def _check_list_display_item(self, obj, item, label): if callable(item): return [] elif hasattr(obj, item): return [] - elif hasattr(model, item): - # getattr(model, item) could be an X_RelatedObjectsDescriptor + elif hasattr(obj.model, item): try: - field = model._meta.get_field(item) + field = obj.model._meta.get_field(item) except FieldDoesNotExist: - try: - field = getattr(model, item) - except AttributeError: - field = None - - if field is None: - return [ - checks.Error( - "The value of '%s' refers to '%s', which is not a " - "callable, an attribute of '%s', or an attribute or method on '%s.%s'." % ( - label, item, obj.__class__.__name__, model._meta.app_label, model._meta.object_name - ), - obj=obj.__class__, - id='admin.E108', - ) - ] - elif isinstance(field, models.ManyToManyField): - return [ - checks.Error( - "The value of '%s' must not be a ManyToManyField." % label, - obj=obj.__class__, - id='admin.E109', - ) - ] - else: return [] - else: - try: - model._meta.get_field(item) - except FieldDoesNotExist: - return [ - # This is a deliberate repeat of E108; there's more than one path - # required to test this condition. - checks.Error( - "The value of '%s' refers to '%s', which is not a callable, " - "an attribute of '%s', or an attribute or method on '%s.%s'." % ( - label, item, obj.__class__.__name__, model._meta.app_label, model._meta.object_name - ), - obj=obj.__class__, - id='admin.E108', - ) - ] else: + if isinstance(field, models.ManyToManyField): + return [ + checks.Error( + "The value of '%s' must not be a ManyToManyField." % label, + obj=obj.__class__, + id='admin.E109', + ) + ] return [] + else: + return [ + checks.Error( + "The value of '%s' refers to '%s', which is not a callable, " + "an attribute of '%s', or an attribute or method on '%s.%s'." % ( + label, item, obj.__class__.__name__, + obj.model._meta.app_label, obj.model._meta.object_name, + ), + obj=obj.__class__, + id='admin.E108', + ) + ] def _check_list_display_links(self, obj): """ Check that list_display_links is a unique subset of list_display. @@ -664,13 +761,11 @@ def _check_list_display_links(self, obj): elif not isinstance(obj.list_display_links, (list, tuple)): return must_be('a list, a tuple, or None', option='list_display_links', obj=obj, id='admin.E110') # Check only if ModelAdmin.get_list_display() isn't overridden. - elif obj.get_list_display.__code__ is ModelAdmin.get_list_display.__code__: - # Use obj.get_list_display.__func__ is ModelAdmin.get_list_display - # when dropping PY2. - return list(chain(*[ + elif obj.get_list_display.__func__ is ModelAdmin.get_list_display: + return list(chain.from_iterable( self._check_list_display_links_item(obj, field_name, "list_display_links[%d]" % index) for index, field_name in enumerate(obj.list_display_links) - ])) + )) return [] def _check_list_display_links_item(self, obj, field_name, label): @@ -691,12 +786,12 @@ def _check_list_filter(self, obj): if not isinstance(obj.list_filter, (list, tuple)): return must_be('a list or tuple', option='list_filter', obj=obj, id='admin.E112') else: - return list(chain(*[ - self._check_list_filter_item(obj, obj.model, item, "list_filter[%d]" % index) + return list(chain.from_iterable( + self._check_list_filter_item(obj, item, "list_filter[%d]" % index) for index, item in enumerate(obj.list_filter) - ])) + )) - def _check_list_filter_item(self, obj, model, item, label): + def _check_list_filter_item(self, obj, item, label): """ Check one item of `list_filter`, i.e. check if it is one of three options: 1. 'field' -- a basic field filter, possibly w/ relationships (e.g. @@ -709,7 +804,7 @@ def _check_list_filter_item(self, obj, model, item, label): if callable(item) and not isinstance(item, models.Field): # If item is option 3, it should be a ListFilter... - if not issubclass(item, ListFilter): + if not _issubclass(item, ListFilter): return must_inherit_from(parent='ListFilter', option=label, obj=obj, id='admin.E113') # ... but not a FieldListFilter. @@ -726,7 +821,7 @@ def _check_list_filter_item(self, obj, model, item, label): elif isinstance(item, (tuple, list)): # item is option #2 field, list_filter_class = item - if not issubclass(list_filter_class, FieldListFilter): + if not _issubclass(list_filter_class, FieldListFilter): return must_inherit_from(parent='FieldListFilter', option='%s[1]' % label, obj=obj, id='admin.E115') else: return [] @@ -736,7 +831,7 @@ def _check_list_filter_item(self, obj, model, item, label): # Validate the field string try: - get_fields_from_path(model, field) + get_fields_from_path(obj.model, field) except (NotRelationField, FieldDoesNotExist): return [ checks.Error( @@ -779,16 +874,16 @@ def _check_list_editable(self, obj): if not isinstance(obj.list_editable, (list, tuple)): return must_be('a list or tuple', option='list_editable', obj=obj, id='admin.E120') else: - return list(chain(*[ - self._check_list_editable_item(obj, obj.model, item, "list_editable[%d]" % index) + return list(chain.from_iterable( + self._check_list_editable_item(obj, item, "list_editable[%d]" % index) for index, item in enumerate(obj.list_editable) - ])) + )) - def _check_list_editable_item(self, obj, model, field_name, label): + def _check_list_editable_item(self, obj, field_name, label): try: - field = model._meta.get_field(field_name) + field = obj.model._meta.get_field(field_name) except FieldDoesNotExist: - return refer_to_missing_field(field=field_name, option=label, model=model, obj=obj, id='admin.E121') + return refer_to_missing_field(field=field_name, option=label, obj=obj, id='admin.E121') else: if field_name not in obj.list_display: return [ @@ -865,24 +960,64 @@ def _check_date_hierarchy(self, obj): else: return [] + def _check_action_permission_methods(self, obj): + """ + Actions with an allowed_permission attribute require the ModelAdmin to + implement a has__permission() method for each permission. + """ + actions = obj._get_base_actions() + errors = [] + for func, name, _ in actions: + if not hasattr(func, 'allowed_permissions'): + continue + for permission in func.allowed_permissions: + method_name = 'has_%s_permission' % permission + if not hasattr(obj, method_name): + errors.append( + checks.Error( + '%s must define a %s() method for the %s action.' % ( + obj.__class__.__name__, + method_name, + func.__name__, + ), + obj=obj.__class__, + id='admin.E129', + ) + ) + return errors + + def _check_actions_uniqueness(self, obj): + """Check that every action has a unique __name__.""" + names = [name for _, name, _ in obj._get_base_actions()] + if len(names) != len(set(names)): + return [checks.Error( + '__name__ attributes of actions defined in %s must be ' + 'unique.' % obj.__class__, + obj=obj.__class__, + id='admin.E130', + )] + return [] + class InlineModelAdminChecks(BaseModelAdminChecks): def check(self, inline_obj, **kwargs): - errors = super(InlineModelAdminChecks, self).check(inline_obj) + self._check_has_add_permission(inline_obj) parent_model = inline_obj.parent_model - errors.extend(self._check_relation(inline_obj, parent_model)) - errors.extend(self._check_exclude_of_parent_model(inline_obj, parent_model)) - errors.extend(self._check_extra(inline_obj)) - errors.extend(self._check_max_num(inline_obj)) - errors.extend(self._check_min_num(inline_obj)) - errors.extend(self._check_formset(inline_obj)) - return errors + return [ + *super().check(inline_obj), + *self._check_relation(inline_obj, parent_model), + *self._check_exclude_of_parent_model(inline_obj, parent_model), + *self._check_extra(inline_obj), + *self._check_max_num(inline_obj), + *self._check_min_num(inline_obj), + *self._check_formset(inline_obj), + ] def _check_exclude_of_parent_model(self, obj, parent_model): # Do not perform more specific checks if the base checks result in an # error. - errors = super(InlineModelAdminChecks, self)._check_exclude(obj) + errors = super()._check_exclude(obj) if errors: return [] @@ -947,11 +1082,25 @@ def _check_min_num(self, obj): def _check_formset(self, obj): """ Check formset is a subclass of BaseModelFormSet. """ - if not issubclass(obj.formset, BaseModelFormSet): + if not _issubclass(obj.formset, BaseModelFormSet): return must_inherit_from(parent='BaseModelFormSet', option='formset', obj=obj, id='admin.E206') else: return [] + def _check_has_add_permission(self, obj): + cls = obj.__class__ + try: + func = cls.has_add_permission + except AttributeError: + pass + else: + args = get_func_args(func) + if 'obj' not in args: + warnings.warn( + "Update %s.has_add_permission() to accept a positional " + "`obj` argument." % cls.__name__, RemovedInDjango30Warning + ) + def must_be(type, option, obj, id): return [ @@ -973,11 +1122,11 @@ def must_inherit_from(parent, option, obj, id): ] -def refer_to_missing_field(field, option, model, obj, id): +def refer_to_missing_field(field, option, obj, id): return [ checks.Error( "The value of '%s' refers to '%s', which is not an attribute of '%s.%s'." % ( - option, field, model._meta.app_label, model._meta.object_name + option, field, obj.model._meta.app_label, obj.model._meta.object_name ), obj=obj.__class__, id=id, diff --git a/django/contrib/admin/decorators.py b/django/contrib/admin/decorators.py index 8b30139ea3da..0c2e35c2b213 100644 --- a/django/contrib/admin/decorators.py +++ b/django/contrib/admin/decorators.py @@ -1,23 +1,22 @@ -def register(*models, **kwargs): +def register(*models, site=None): """ - Registers the given model(s) classes and wrapped ModelAdmin class with + Register the given model(s) classes and wrapped ModelAdmin class with admin site: @register(Author) class AuthorAdmin(admin.ModelAdmin): pass - A kwarg of `site` can be passed as the admin site, otherwise the default - admin site will be used. + The `site` kwarg is an admin site to use instead of the default admin site. """ from django.contrib.admin import ModelAdmin - from django.contrib.admin.sites import site, AdminSite + from django.contrib.admin.sites import site as default_site, AdminSite def _model_admin_wrapper(admin_class): if not models: raise ValueError('At least one model must be passed to register.') - admin_site = kwargs.pop('site', site) + admin_site = site or default_site if not isinstance(admin_site, AdminSite): raise ValueError('site must subclass AdminSite') diff --git a/django/contrib/admin/filters.py b/django/contrib/admin/filters.py index 87839c313064..d65e01d5e2fe 100644 --- a/django/contrib/admin/filters.py +++ b/django/contrib/admin/filters.py @@ -14,11 +14,10 @@ from django.core.exceptions import ImproperlyConfigured, ValidationError from django.db import models from django.utils import timezone -from django.utils.encoding import force_text -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ -class ListFilter(object): +class ListFilter: title = None # Human-readable title to appear in the right sidebar. template = 'admin/filter.html' @@ -28,18 +27,19 @@ def __init__(self, request, params, model, model_admin): self.used_parameters = {} if self.title is None: raise ImproperlyConfigured( - "The list filter '%s' does not specify " - "a 'title'." % self.__class__.__name__) + "The list filter '%s' does not specify a 'title'." + % self.__class__.__name__ + ) def has_output(self): """ - Returns True if some choices would be output for this filter. + Return True if some choices would be output for this filter. """ raise NotImplementedError('subclasses of ListFilter must provide a has_output() method') def choices(self, changelist): """ - Returns choices ready to be output in the template. + Return choices ready to be output in the template. `changelist` is the ChangeList to be displayed. """ @@ -47,13 +47,13 @@ def choices(self, changelist): def queryset(self, request, queryset): """ - Returns the filtered queryset. + Return the filtered queryset. """ raise NotImplementedError('subclasses of ListFilter must provide a queryset() method') def expected_parameters(self): """ - Returns the list of parameter names that are expected from the + Return the list of parameter names that are expected from the request's query string and that will be used by this filter. """ raise NotImplementedError('subclasses of ListFilter must provide an expected_parameters() method') @@ -64,12 +64,12 @@ class SimpleListFilter(ListFilter): parameter_name = None def __init__(self, request, params, model, model_admin): - super(SimpleListFilter, self).__init__( - request, params, model, model_admin) + super().__init__(request, params, model, model_admin) if self.parameter_name is None: raise ImproperlyConfigured( - "The list filter '%s' does not specify " - "a 'parameter_name'." % self.__class__.__name__) + "The list filter '%s' does not specify a 'parameter_name'." + % self.__class__.__name__ + ) if self.parameter_name in params: value = params.pop(self.parameter_name) self.used_parameters[self.parameter_name] = value @@ -83,9 +83,9 @@ def has_output(self): def value(self): """ - Returns the value (in string format) provided in the request's - query string for this filter, if any. If the value wasn't provided then - returns None. + Return the value (in string format) provided in the request's + query string for this filter, if any, or None if the value wasn't + provided. """ return self.used_parameters.get(self.parameter_name) @@ -95,7 +95,8 @@ def lookups(self, request, model_admin): """ raise NotImplementedError( 'The SimpleListFilter.lookups() method must be overridden to ' - 'return a list of tuples (value, verbose value)') + 'return a list of tuples (value, verbose value).' + ) def expected_parameters(self): return [self.parameter_name] @@ -103,13 +104,13 @@ def expected_parameters(self): def choices(self, changelist): yield { 'selected': self.value() is None, - 'query_string': changelist.get_query_string({}, [self.parameter_name]), + 'query_string': changelist.get_query_string(remove=[self.parameter_name]), 'display': _('All'), } for lookup, title in self.lookup_choices: yield { - 'selected': self.value() == force_text(lookup), - 'query_string': changelist.get_query_string({self.parameter_name: lookup}, []), + 'selected': self.value() == str(lookup), + 'query_string': changelist.get_query_string({self.parameter_name: lookup}), 'display': title, } @@ -122,8 +123,7 @@ def __init__(self, field, request, params, model, model_admin, field_path): self.field = field self.field_path = field_path self.title = getattr(field, 'verbose_name', field_path) - super(FieldListFilter, self).__init__( - request, params, model, model_admin) + super().__init__(request, params, model, model_admin) for p in self.expected_parameters(): if p in params: value = params.pop(p) @@ -135,7 +135,9 @@ def has_output(self): def queryset(self, request, queryset): try: return queryset.filter(**self.used_parameters) - except ValidationError as e: + except (ValueError, ValidationError) as e: + # Fields may raise a ValueError or ValidationError when converting + # the parameters to the correct type. raise IncorrectLookupParameters(e) @classmethod @@ -153,9 +155,8 @@ def register(cls, test, list_filter_class, take_priority=False): @classmethod def create(cls, field, request, params, model, model_admin, field_path): for test, list_filter_class in cls._field_list_filters: - if not test(field): - continue - return list_filter_class(field, request, params, model, model_admin, field_path=field_path) + if test(field): + return list_filter_class(field, request, params, model, model_admin, field_path=field_path) class RelatedFieldListFilter(FieldListFilter): @@ -163,10 +164,9 @@ def __init__(self, field, request, params, model, model_admin, field_path): other_model = get_model_from_relation(field) self.lookup_kwarg = '%s__%s__exact' % (field_path, field.target_field.name) self.lookup_kwarg_isnull = '%s__isnull' % field_path - self.lookup_val = request.GET.get(self.lookup_kwarg) - self.lookup_val_isnull = request.GET.get(self.lookup_kwarg_isnull) - super(RelatedFieldListFilter, self).__init__( - field, request, params, model, model_admin, field_path) + self.lookup_val = params.get(self.lookup_kwarg) + self.lookup_val_isnull = params.get(self.lookup_kwarg_isnull) + super().__init__(field, request, params, model, model_admin, field_path) self.lookup_choices = self.field_choices(field, request, model_admin) if hasattr(field, 'verbose_name'): self.lookup_title = field.verbose_name @@ -194,31 +194,28 @@ def expected_parameters(self): return [self.lookup_kwarg, self.lookup_kwarg_isnull] def field_choices(self, field, request, model_admin): - return field.get_choices(include_blank=False) + ordering = () + related_admin = model_admin.admin_site._registry.get(field.remote_field.model) + if related_admin is not None: + ordering = related_admin.get_ordering(request) + return field.get_choices(include_blank=False, ordering=ordering) def choices(self, changelist): yield { 'selected': self.lookup_val is None and not self.lookup_val_isnull, - 'query_string': changelist.get_query_string( - {}, - [self.lookup_kwarg, self.lookup_kwarg_isnull] - ), + 'query_string': changelist.get_query_string(remove=[self.lookup_kwarg, self.lookup_kwarg_isnull]), 'display': _('All'), } for pk_val, val in self.lookup_choices: yield { - 'selected': self.lookup_val == force_text(pk_val), - 'query_string': changelist.get_query_string({ - self.lookup_kwarg: pk_val, - }, [self.lookup_kwarg_isnull]), + 'selected': self.lookup_val == str(pk_val), + 'query_string': changelist.get_query_string({self.lookup_kwarg: pk_val}, [self.lookup_kwarg_isnull]), 'display': val, } if self.include_empty_choice: yield { 'selected': bool(self.lookup_val_isnull), - 'query_string': changelist.get_query_string({ - self.lookup_kwarg_isnull: 'True', - }, [self.lookup_kwarg]), + 'query_string': changelist.get_query_string({self.lookup_kwarg_isnull: 'True'}, [self.lookup_kwarg]), 'display': self.empty_value_display, } @@ -230,9 +227,9 @@ class BooleanFieldListFilter(FieldListFilter): def __init__(self, field, request, params, model, model_admin, field_path): self.lookup_kwarg = '%s__exact' % field_path self.lookup_kwarg2 = '%s__isnull' % field_path - self.lookup_val = request.GET.get(self.lookup_kwarg) - self.lookup_val2 = request.GET.get(self.lookup_kwarg2) - super(BooleanFieldListFilter, self).__init__(field, request, params, model, model_admin, field_path) + self.lookup_val = params.get(self.lookup_kwarg) + self.lookup_val2 = params.get(self.lookup_kwarg2) + super().__init__(field, request, params, model, model_admin, field_path) if (self.used_parameters and self.lookup_kwarg in self.used_parameters and self.used_parameters[self.lookup_kwarg] in ('1', '0')): self.used_parameters[self.lookup_kwarg] = bool(int(self.used_parameters[self.lookup_kwarg])) @@ -247,35 +244,27 @@ def choices(self, changelist): ('0', _('No'))): yield { 'selected': self.lookup_val == lookup and not self.lookup_val2, - 'query_string': changelist.get_query_string({ - self.lookup_kwarg: lookup, - }, [self.lookup_kwarg2]), + 'query_string': changelist.get_query_string({self.lookup_kwarg: lookup}, [self.lookup_kwarg2]), 'display': title, } - if isinstance(self.field, models.NullBooleanField): + if self.field.null: yield { 'selected': self.lookup_val2 == 'True', - 'query_string': changelist.get_query_string({ - self.lookup_kwarg2: 'True', - }, [self.lookup_kwarg]), + 'query_string': changelist.get_query_string({self.lookup_kwarg2: 'True'}, [self.lookup_kwarg]), 'display': _('Unknown'), } -FieldListFilter.register( - lambda f: isinstance(f, (models.BooleanField, models.NullBooleanField)), - BooleanFieldListFilter -) +FieldListFilter.register(lambda f: isinstance(f, models.BooleanField), BooleanFieldListFilter) class ChoicesFieldListFilter(FieldListFilter): def __init__(self, field, request, params, model, model_admin, field_path): self.lookup_kwarg = '%s__exact' % field_path self.lookup_kwarg_isnull = '%s__isnull' % field_path - self.lookup_val = request.GET.get(self.lookup_kwarg) - self.lookup_val_isnull = request.GET.get(self.lookup_kwarg_isnull) - super(ChoicesFieldListFilter, self).__init__( - field, request, params, model, model_admin, field_path) + self.lookup_val = params.get(self.lookup_kwarg) + self.lookup_val_isnull = params.get(self.lookup_kwarg_isnull) + super().__init__(field, request, params, model, model_admin, field_path) def expected_parameters(self): return [self.lookup_kwarg, self.lookup_kwarg_isnull] @@ -283,9 +272,7 @@ def expected_parameters(self): def choices(self, changelist): yield { 'selected': self.lookup_val is None, - 'query_string': changelist.get_query_string( - {}, [self.lookup_kwarg, self.lookup_kwarg_isnull] - ), + 'query_string': changelist.get_query_string(remove=[self.lookup_kwarg, self.lookup_kwarg_isnull]), 'display': _('All') } none_title = '' @@ -294,18 +281,14 @@ def choices(self, changelist): none_title = title continue yield { - 'selected': force_text(lookup) == self.lookup_val, - 'query_string': changelist.get_query_string( - {self.lookup_kwarg: lookup}, [self.lookup_kwarg_isnull] - ), + 'selected': str(lookup) == self.lookup_val, + 'query_string': changelist.get_query_string({self.lookup_kwarg: lookup}, [self.lookup_kwarg_isnull]), 'display': title, } if none_title: yield { 'selected': bool(self.lookup_val_isnull), - 'query_string': changelist.get_query_string({ - self.lookup_kwarg_isnull: 'True', - }, [self.lookup_kwarg]), + 'query_string': changelist.get_query_string({self.lookup_kwarg_isnull: 'True'}, [self.lookup_kwarg]), 'display': none_title, } @@ -362,8 +345,7 @@ def __init__(self, field, request, params, model, model_admin, field_path): (_('No date'), {self.field_generic + 'isnull': 'True'}), (_('Has date'), {self.field_generic + 'isnull': 'False'}), ) - super(DateFieldListFilter, self).__init__( - field, request, params, model, model_admin, field_path) + super().__init__(field, request, params, model, model_admin, field_path) def expected_parameters(self): params = [self.lookup_kwarg_since, self.lookup_kwarg_until] @@ -391,8 +373,8 @@ class AllValuesFieldListFilter(FieldListFilter): def __init__(self, field, request, params, model, model_admin, field_path): self.lookup_kwarg = field_path self.lookup_kwarg_isnull = '%s__isnull' % field_path - self.lookup_val = request.GET.get(self.lookup_kwarg) - self.lookup_val_isnull = request.GET.get(self.lookup_kwarg_isnull) + self.lookup_val = params.get(self.lookup_kwarg) + self.lookup_val_isnull = params.get(self.lookup_kwarg_isnull) self.empty_value_display = model_admin.get_empty_value_display() parent_model, reverse_path = reverse_field_path(model, field_path) # Obey parent ModelAdmin queryset when deciding which options to show @@ -400,12 +382,8 @@ def __init__(self, field, request, params, model, model_admin, field_path): queryset = model_admin.get_queryset(request) else: queryset = parent_model._default_manager.all() - self.lookup_choices = (queryset - .distinct() - .order_by(field.name) - .values_list(field.name, flat=True)) - super(AllValuesFieldListFilter, self).__init__( - field, request, params, model, model_admin, field_path) + self.lookup_choices = queryset.distinct().order_by(field.name).values_list(field.name, flat=True) + super().__init__(field, request, params, model, model_admin, field_path) def expected_parameters(self): return [self.lookup_kwarg, self.lookup_kwarg_isnull] @@ -413,7 +391,7 @@ def expected_parameters(self): def choices(self, changelist): yield { 'selected': self.lookup_val is None and self.lookup_val_isnull is None, - 'query_string': changelist.get_query_string({}, [self.lookup_kwarg, self.lookup_kwarg_isnull]), + 'query_string': changelist.get_query_string(remove=[self.lookup_kwarg, self.lookup_kwarg_isnull]), 'display': _('All'), } include_none = False @@ -421,20 +399,16 @@ def choices(self, changelist): if val is None: include_none = True continue - val = force_text(val) + val = str(val) yield { 'selected': self.lookup_val == val, - 'query_string': changelist.get_query_string({ - self.lookup_kwarg: val, - }, [self.lookup_kwarg_isnull]), + 'query_string': changelist.get_query_string({self.lookup_kwarg: val}, [self.lookup_kwarg_isnull]), 'display': val, } if include_none: yield { 'selected': bool(self.lookup_val_isnull), - 'query_string': changelist.get_query_string({ - self.lookup_kwarg_isnull: 'True', - }, [self.lookup_kwarg]), + 'query_string': changelist.get_query_string({self.lookup_kwarg_isnull: 'True'}, [self.lookup_kwarg]), 'display': self.empty_value_display, } diff --git a/django/contrib/admin/forms.py b/django/contrib/admin/forms.py index ed71d63e4c18..e973c6197296 100644 --- a/django/contrib/admin/forms.py +++ b/django/contrib/admin/forms.py @@ -1,8 +1,6 @@ -from __future__ import unicode_literals - from django import forms from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class AdminAuthenticationForm(AuthenticationForm): @@ -10,6 +8,7 @@ class AdminAuthenticationForm(AuthenticationForm): A custom authentication form used in the admin app. """ error_messages = { + **AuthenticationForm.error_messages, 'invalid_login': _( "Please enter the correct %(username)s and password for a staff " "account. Note that both fields may be case-sensitive." @@ -18,7 +17,8 @@ class AdminAuthenticationForm(AuthenticationForm): required_css_class = 'required' def confirm_login_allowed(self, user): - if not user.is_active or not user.is_staff: + super().confirm_login_allowed(user) + if not user.is_staff: raise forms.ValidationError( self.error_messages['invalid_login'], code='invalid_login', diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py index cbe03031a97c..0c0b3a4e345d 100644 --- a/django/contrib/admin/helpers.py +++ b/django/contrib/admin/helpers.py @@ -1,7 +1,4 @@ -from __future__ import unicode_literals - import json -import warnings from django import forms from django.conf import settings @@ -13,12 +10,9 @@ from django.db.models.fields.related import ManyToManyRel from django.forms.utils import flatatt from django.template.defaultfilters import capfirst, linebreaksbr -from django.utils import six -from django.utils.deprecation import RemovedInDjango20Warning -from django.utils.encoding import force_text from django.utils.html import conditional_escape, format_html from django.utils.safestring import mark_safe -from django.utils.translation import ugettext, ugettext_lazy as _ +from django.utils.translation import gettext, gettext_lazy as _ ACTION_CHECKBOX_NAME = '_selected_action' @@ -36,7 +30,7 @@ class ActionForm(forms.Form): checkbox = forms.CheckboxInput({'class': 'action-select'}, lambda value: False) -class AdminForm(object): +class AdminForm: def __init__(self, form, fieldsets, prepopulated_fields, readonly_fields=None, model_admin=None): self.form, self.fieldsets = form, fieldsets self.prepopulated_fields = [{ @@ -73,7 +67,7 @@ def media(self): return media -class Fieldset(object): +class Fieldset: def __init__(self, form, name=None, readonly_fields=(), fields=(), classes=(), description=None, model_admin=None): self.form = form @@ -87,12 +81,7 @@ def __init__(self, form, name=None, readonly_fields=(), fields=(), classes=(), def media(self): if 'collapse' in self.classes: extra = '' if settings.DEBUG else '.min' - js = [ - 'vendor/jquery/jquery%s.js' % extra, - 'jquery.init.js', - 'collapse%s.js' % extra, - ] - return forms.Media(js=['admin/js/%s' % url for url in js]) + return forms.Media(js=['admin/js/collapse%s.js' % extra]) return forms.Media() def __iter__(self): @@ -100,10 +89,10 @@ def __iter__(self): yield Fieldline(self.form, field, self.readonly_fields, model_admin=self.model_admin) -class Fieldline(object): +class Fieldline: def __init__(self, form, field, readonly_fields=None, model_admin=None): self.form = form # A django.forms.Form instance - if not hasattr(field, "__iter__") or isinstance(field, six.text_type): + if not hasattr(field, "__iter__") or isinstance(field, str): self.fields = [field] else: self.fields = field @@ -131,7 +120,7 @@ def errors(self): ) -class AdminField(object): +class AdminField: def __init__(self, form, field, is_first): self.field = form[field] # A django.forms.BoundField instance self.is_first = is_first # Whether this field is first on the line @@ -140,7 +129,7 @@ def __init__(self, form, field, is_first): def label_tag(self): classes = [] - contents = conditional_escape(force_text(self.field.label)) + contents = conditional_escape(self.field.label) if self.is_checkbox: classes.append('vCheckboxLabel') @@ -160,7 +149,7 @@ def errors(self): return mark_safe(self.field.errors.as_ul()) -class AdminReadonlyField(object): +class AdminReadonlyField: def __init__(self, form, field, is_first, model_admin=None): # Make self.field look a little bit like a field. This means that # {{ field.name }} must be a useful class name to identify the field. @@ -173,7 +162,7 @@ def __init__(self, form, field, is_first, model_admin=None): if form._meta.labels and class_name in form._meta.labels: label = form._meta.labels[class_name] else: - label = label_for_field(field, form._meta.model, model_admin) + label = label_for_field(field, form._meta.model, model_admin, form=form) if form._meta.help_texts and class_name in form._meta.help_texts: help_text = form._meta.help_texts[class_name] @@ -198,9 +187,7 @@ def label_tag(self): if not self.is_first: attrs["class"] = "inline" label = self.field['label'] - return format_html('{}:', - flatatt(attrs), - capfirst(force_text(label))) + return format_html('{}:', flatatt(attrs), capfirst(label)) def contents(self): from django.contrib.admin.templatetags.admin_list import _boolean_icon @@ -210,40 +197,37 @@ def contents(self): except (AttributeError, ValueError, ObjectDoesNotExist): result_repr = self.empty_value_display else: + if field in self.form.fields: + widget = self.form[field].field.widget + # This isn't elegant but suffices for contrib.auth's + # ReadOnlyPasswordHashWidget. + if getattr(widget, 'read_only', False): + return widget.render(field, value) if f is None: - boolean = getattr(attr, "boolean", False) - if boolean: + if getattr(attr, 'boolean', False): result_repr = _boolean_icon(value) else: if hasattr(value, "__html__"): result_repr = value else: - result_repr = force_text(value) - if getattr(attr, "allow_tags", False): - warnings.warn( - "Deprecated allow_tags attribute used on %s. " - "Use django.utils.html.format_html(), format_html_join(), " - "or django.utils.safestring.mark_safe() instead." % attr, - RemovedInDjango20Warning - ) - result_repr = mark_safe(value) - else: - result_repr = linebreaksbr(result_repr) + result_repr = linebreaksbr(value) else: if isinstance(f.remote_field, ManyToManyRel) and value is not None: - result_repr = ", ".join(map(six.text_type, value.all())) + result_repr = ", ".join(map(str, value.all())) else: result_repr = display_for_field(value, f, self.empty_value_display) result_repr = linebreaksbr(result_repr) return conditional_escape(result_repr) -class InlineAdminFormSet(object): +class InlineAdminFormSet: """ A wrapper around an inline formset for use in the admin system. """ def __init__(self, inline, formset, fieldsets, prepopulated_fields=None, - readonly_fields=None, model_admin=None): + readonly_fields=None, model_admin=None, has_add_permission=True, + has_change_permission=True, has_delete_permission=True, + has_view_permission=True): self.opts = inline self.formset = formset self.fieldsets = fieldsets @@ -255,13 +239,22 @@ def __init__(self, inline, formset, fieldsets, prepopulated_fields=None, prepopulated_fields = {} self.prepopulated_fields = prepopulated_fields self.classes = ' '.join(inline.classes) if inline.classes else '' + self.has_add_permission = has_add_permission + self.has_change_permission = has_change_permission + self.has_delete_permission = has_delete_permission + self.has_view_permission = has_view_permission def __iter__(self): + if self.has_change_permission: + readonly_fields_for_editing = self.readonly_fields + else: + readonly_fields_for_editing = self.readonly_fields + flatten_fieldsets(self.fieldsets) + for form, original in zip(self.formset.initial_forms, self.formset.get_queryset()): view_on_site_url = self.opts.get_view_on_site_url(original) yield InlineAdminForm( self.formset, form, self.fieldsets, self.prepopulated_fields, - original, self.readonly_fields, model_admin=self.opts, + original, readonly_fields_for_editing, model_admin=self.opts, view_on_site_url=view_on_site_url, ) for form in self.formset.extra_forms: @@ -269,30 +262,36 @@ def __iter__(self): self.formset, form, self.fieldsets, self.prepopulated_fields, None, self.readonly_fields, model_admin=self.opts, ) - yield InlineAdminForm( - self.formset, self.formset.empty_form, - self.fieldsets, self.prepopulated_fields, None, - self.readonly_fields, model_admin=self.opts, - ) + if self.has_add_permission: + yield InlineAdminForm( + self.formset, self.formset.empty_form, + self.fieldsets, self.prepopulated_fields, None, + self.readonly_fields, model_admin=self.opts, + ) def fields(self): fk = getattr(self.formset, "fk", None) + empty_form = self.formset.empty_form + meta_labels = empty_form._meta.labels or {} + meta_help_texts = empty_form._meta.help_texts or {} for i, field_name in enumerate(flatten_fieldsets(self.fieldsets)): if fk and fk.name == field_name: continue - if field_name in self.readonly_fields: + if not self.has_change_permission or field_name in self.readonly_fields: yield { - 'label': label_for_field(field_name, self.opts.model, self.opts), + 'name': field_name, + 'label': meta_labels.get(field_name) or label_for_field(field_name, self.opts.model, self.opts), 'widget': {'is_hidden': False}, 'required': False, - 'help_text': help_text_for_field(field_name, self.opts.model), + 'help_text': meta_help_texts.get(field_name) or help_text_for_field(field_name, self.opts.model), } else: - form_field = self.formset.empty_form.fields[field_name] + form_field = empty_form.fields[field_name] label = form_field.label if label is None: label = label_for_field(field_name, self.opts.model, self.opts) yield { + 'name': field_name, 'label': label, 'widget': form_field.widget, 'required': form_field.required, @@ -305,10 +304,10 @@ def inline_formset_data(self): 'name': '#%s' % self.formset.prefix, 'options': { 'prefix': self.formset.prefix, - 'addText': ugettext('Add another %(verbose_name)s') % { + 'addText': gettext('Add another %(verbose_name)s') % { 'verbose_name': capfirst(verbose_name), }, - 'deleteText': ugettext('Remove'), + 'deleteText': gettext('Remove'), } }) @@ -339,7 +338,7 @@ def __init__(self, formset, form, fieldsets, prepopulated_fields, original, self.original = original self.show_url = original and view_on_site_url is not None self.absolute_url = view_on_site_url - super(InlineAdminForm, self).__init__(form, fieldsets, prepopulated_fields, readonly_fields, model_admin) + super().__init__(form, fieldsets, prepopulated_fields, readonly_fields, model_admin) def __iter__(self): for name, options in self.fieldsets: @@ -349,15 +348,15 @@ def __iter__(self): ) def needs_explicit_pk_field(self): - # Auto fields are editable (oddly), so need to check for auto or non-editable pk - if self.form._meta.model._meta.auto_field or not self.form._meta.model._meta.pk.editable: - return True - # Also search any parents for an auto field. (The pk info is propagated to child - # models so that does not need to be checked in parents.) - for parent in self.form._meta.model._meta.get_parent_list(): - if parent._meta.auto_field: - return True - return False + return ( + # Auto fields are editable, so check for auto or non-editable pk. + self.form._meta.model._meta.auto_field or not self.form._meta.model._meta.pk.editable or + # Also search any parents for an auto field. (The pk info is + # propagated to child models so that does not need to be checked + # in parents.) + any(parent._meta.auto_field or not parent._meta.model._meta.pk.editable + for parent in self.form._meta.model._meta.get_parent_list()) + ) def pk_field(self): return AdminField(self.form, self.formset._pk_field.name, False) @@ -381,22 +380,19 @@ def ordering_field(self): class InlineFieldset(Fieldset): def __init__(self, formset, *args, **kwargs): self.formset = formset - super(InlineFieldset, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def __iter__(self): fk = getattr(self.formset, "fk", None) for field in self.fields: - if fk and fk.name == field: - continue - yield Fieldline(self.form, field, self.readonly_fields, model_admin=self.model_admin) + if not fk or fk.name != field: + yield Fieldline(self.form, field, self.readonly_fields, model_admin=self.model_admin) class AdminErrorList(forms.utils.ErrorList): - """ - Stores all errors for the form/formsets in an add/change stage view. - """ + """Store errors for the form/formsets in an add/change view.""" def __init__(self, form, inline_formsets): - super(AdminErrorList, self).__init__() + super().__init__() if form.is_bound: self.extend(form.errors.values()) diff --git a/django/contrib/admin/locale/af/LC_MESSAGES/django.mo b/django/contrib/admin/locale/af/LC_MESSAGES/django.mo index 260d562a9a1b..0e5afe06b21e 100644 Binary files a/django/contrib/admin/locale/af/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/af/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/af/LC_MESSAGES/django.po b/django/contrib/admin/locale/af/LC_MESSAGES/django.po index c2fec9dd8c69..1843123da5d9 100644 --- a/django/contrib/admin/locale/af/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/af/LC_MESSAGES/django.po @@ -1,15 +1,18 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Christopher Penkin , 2012 +# Christopher Penkin, 2012 +# Christopher Penkin, 2012 +# F Wolff , 2019 +# Pi Delport , 2012 # Pi Delport , 2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-06-03 14:32+0000\n" +"Last-Translator: F Wolff \n" "Language-Team: Afrikaans (http://www.transifex.com/django/django/language/" "af/)\n" "MIME-Version: 1.0\n" @@ -27,23 +30,23 @@ msgid "Cannot delete %(name)s" msgstr "Kan %(name)s nie skrap nie" msgid "Are you sure?" -msgstr "Is jy seker?" +msgstr "Is u seker?" #, python-format msgid "Delete selected %(verbose_name_plural)s" msgstr "Skrap gekose %(verbose_name_plural)s" msgid "Administration" -msgstr "" +msgstr "Administrasie" msgid "All" -msgstr "Alles" +msgstr "Almal" msgid "Yes" msgstr "Ja" msgid "No" -msgstr "Geen" +msgstr "Nee" msgid "Unknown" msgstr "Onbekend" @@ -64,150 +67,171 @@ msgid "This year" msgstr "Hierdie jaar" msgid "No date" -msgstr "" +msgstr "Geen datum" msgid "Has date" -msgstr "" +msgstr "Het datum" #, python-format msgid "" "Please enter the correct %(username)s and password for a staff account. Note " "that both fields may be case-sensitive." msgstr "" +"Gee die korrekte %(username)s en wagwoord vir ’n personeelrekening. Let op " +"dat altwee velde dalk hooflettersensitief is." msgid "Action:" msgstr "Aksie:" #, python-format msgid "Add another %(verbose_name)s" -msgstr "Voeg nog 'n %(verbose_name)s by" +msgstr "Voeg nog ’n %(verbose_name)s by" msgid "Remove" msgstr "Verwyder" +msgid "Addition" +msgstr "Byvoeging" + +msgid "Change" +msgstr "" + +msgid "Deletion" +msgstr "Verwydering" + msgid "action time" -msgstr "aksie tyd" +msgstr "aksietyd" msgid "user" -msgstr "" +msgstr "gebruiker" msgid "content type" -msgstr "" +msgstr "inhoudtipe" msgid "object id" -msgstr "objek id" +msgstr "objek-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 "objek repr" +msgstr "objek-repr" msgid "action flag" -msgstr "aksie vlag" +msgstr "aksievlag" msgid "change message" -msgstr "verandering boodskap" +msgstr "veranderingboodskap" msgid "log entry" -msgstr "" +msgstr "log-inskrywing" msgid "log entries" -msgstr "" +msgstr "log-inskrywingings" #, python-format msgid "Added \"%(object)s\"." -msgstr "Het \"%(object)s\" bygevoeg." +msgstr "Het “%(object)s” bygevoeg." #, python-format msgid "Changed \"%(object)s\" - %(changes)s" -msgstr "Het \"%(object)s\" verander - %(changes)s" +msgstr "Het “%(object)s” verander — %(changes)s" #, python-format msgid "Deleted \"%(object)s.\"" -msgstr "Het \"%(object)s\" geskrap." +msgstr "Het “%(object)s” verwyder." msgid "LogEntry Object" -msgstr "" +msgstr "LogEntry-objek" #, python-brace-format msgid "Added {name} \"{object}\"." -msgstr "" +msgstr "Het {name} “{object}” bygevoeg." msgid "Added." -msgstr "" +msgstr "Bygevoeg." msgid "and" msgstr "en" #, python-brace-format msgid "Changed {fields} for {name} \"{object}\"." -msgstr "" +msgstr "Het {fields} vir {name} “{object}” gewysig." #, python-brace-format msgid "Changed {fields}." -msgstr "" +msgstr "Het {fields} verander." #, python-brace-format msgid "Deleted {name} \"{object}\"." -msgstr "" +msgstr "Het {name} “{object}” geskrap." msgid "No fields changed." -msgstr "Geen velde verander nie." +msgstr "Geen velde het verander nie." msgid "None" -msgstr "None" +msgstr "Geen" msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." -msgstr "" +msgstr "Hou “Ctrl” in (of “⌘” op ’n Mac) om meer as een te kies." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" +msgid "The {name} \"{obj}\" was added successfully." +msgstr "Die {name} “{obj}” is suksesvol bygevoeg." + +msgid "You may edit it again below." +msgstr "Dit kan weer hieronder gewysig word." #, python-brace-format msgid "" "The {name} \"{obj}\" was added successfully. You may add another {name} " "below." msgstr "" +"Die {name} “{obj}” is suksesvol bygevoeg. Nog ’n {name} kan onder bygevoeg " +"word." #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" +"Die {name} “{obj}” is suksesvol gewysig. Dit kan weer hieronder gewysig word." #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." +"The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" +"Die {name} “{obj}” is suksesvol bygevoeg. Dit kan weer hieronder gewysig " +"word." #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " "below." msgstr "" +"Die {name} “{obj}” is suksesvol gewysig. Nog ’n {name} kan onder bygevoeg " +"word." #, python-brace-format msgid "The {name} \"{obj}\" was changed successfully." -msgstr "" +msgstr "Die {name} “{obj}” is suksesvol gewysig." msgid "" "Items must be selected in order to perform actions on them. No items have " "been changed." msgstr "" "Items moet gekies word om aksies op hulle uit te voer. Geen items is " -"verander." +"verander nie." msgid "No action selected." msgstr "Geen aksie gekies nie." #, python-format msgid "The %(name)s \"%(obj)s\" was deleted successfully." -msgstr "Die %(name)s \"%(obj)s\" was suksesvol geskrap." +msgstr "Die %(name)s “%(obj)s” is suksesvol geskrap." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(name)s voorwerp met primêre sleutel %(key)r bestaan ​​nie." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "Die %(name)s met ID “%(key)s” bestaan nie. Miskien is dit geskrap?" #, python-format msgid "Add %s" @@ -215,16 +239,20 @@ msgstr "Voeg %s by" #, python-format msgid "Change %s" -msgstr "Verander %s" +msgstr "Wysig %s" + +#, python-format +msgid "View %s" +msgstr "Beskou %s" msgid "Database error" -msgstr "Databasis fout" +msgstr "Databasisfout" #, 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 was suksesvol verander." -msgstr[1] "%(count)s %(name)s was suksesvol verander." +msgstr[0] "%(count)s %(name)s is suksesvol verander." +msgstr[1] "%(count)s %(name)s is suksesvol verander." #, python-format msgid "%(total_count)s selected" @@ -244,38 +272,40 @@ msgstr "Verander geskiedenis: %s" #. suitable to be an item in a list. #, python-format msgid "%(class_name)s %(instance)s" -msgstr "" +msgstr "%(class_name)s %(instance)s" #, python-format msgid "" "Deleting %(class_name)s %(instance)s would require deleting the following " "protected related objects: %(related_objects)s" msgstr "" +"Om %(class_name)s %(instance)s te skrap sal vereis dat die volgende " +"beskermde verwante objekte geskrap word: %(related_objects)s" msgid "Django site admin" -msgstr "Django werf admin" +msgstr "Django-werfadmin" msgid "Django administration" -msgstr "Django administrasie" +msgstr "Django-administrasie" msgid "Site administration" -msgstr "Werf administrasie" +msgstr "Werfadministrasie" msgid "Log in" -msgstr "Teken in" +msgstr "Meld aan" #, python-format msgid "%(app)s administration" -msgstr "" +msgstr "%(app)s-administrasie" msgid "Page not found" msgstr "Bladsy nie gevind nie" msgid "We're sorry, but the requested page could not be found." -msgstr "Ons is jammer, maar die aangevraagde bladsy kon nie gevind word nie." +msgstr "Jammer, maar die aangevraagde bladsy kon nie gevind word nie." msgid "Home" -msgstr "Tuisblad" +msgstr "Tuis" msgid "Server error" msgstr "Bedienerfout" @@ -290,9 +320,12 @@ 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 "" +"’n Fout het voorgekom. Dit is per e-pos gerapporteer aan die " +"werfadministrateurs en behoort binnekort reggestel te word. Dankie vir u " +"geduld." msgid "Run the selected action" -msgstr "Hardloop die gekose aksie" +msgstr "Voer die gekose aksie uit" msgid "Go" msgstr "Gaan" @@ -311,36 +344,36 @@ msgid "" "First, enter a username and password. Then, you'll be able to edit more user " "options." msgstr "" -"Vul eers 'n gebruikersnaam en wagwoord in. Dan sal jy in staat wees om meer " -"gebruikersopsies te wysig." +"Vul eers ’n gebruikersnaam en wagwoord in. Daarna kan mens meer " +"gebruikersopsies wysig." msgid "Enter a username and password." -msgstr "Vul 'n gebruikersnaam en wagwoord in." +msgstr "Vul ’n gebruikersnaam en wagwoord in." msgid "Change password" msgstr "Verander wagwoord" msgid "Please correct the error below." -msgstr "Korrigeer asseblief die foute hieronder." +msgstr "Maak die onderstaande fout asb. reg." msgid "Please correct the errors below." -msgstr "" +msgstr "Maak die onderstaande foute asb. reg." #, python-format msgid "Enter a new password for the user %(username)s." -msgstr "Vul 'n nuwe wagwoord vir gebruiker %(username)s in." +msgstr "Vul ’n nuwe wagwoord vir gebruiker %(username)s in." msgid "Welcome," msgstr "Welkom," msgid "View site" -msgstr "" +msgstr "Besoek werf" msgid "Documentation" msgstr "Dokumentasie" msgid "Log out" -msgstr "Teken uit" +msgstr "Meld af" #, python-format msgid "Add %(name)s" @@ -353,14 +386,14 @@ msgid "View on site" msgstr "Bekyk op werf" msgid "Filter" -msgstr "Filter" +msgstr "Filtreer" msgid "Remove from sorting" -msgstr "Verwyder van sortering" +msgstr "Verwyder uit sortering" #, python-format msgid "Sorting priority: %(priority_number)s" -msgstr "Sortering prioriteit: %(priority_number)s" +msgstr "Sorteerprioriteit: %(priority_number)s" msgid "Toggle sorting" msgstr "Wissel sortering" @@ -374,29 +407,34 @@ msgid "" "related objects, but your account doesn't have permission to delete the " "following types of objects:" msgstr "" +"Om die %(object_name)s %(escaped_object)s te skrap sou verwante objekte " +"skrap, maar jou rekening het nie toestemming om die volgende tipes objekte " +"te skrap nie:" #, python-format msgid "" "Deleting the %(object_name)s '%(escaped_object)s' would require deleting the " "following protected related objects:" msgstr "" -"Om die %(object_name)s '%(escaped_object)s' te skrap sou vereis dat die " -"volgende beskermde verwante objekte geskrap word:" +"Om die %(object_name)s “%(escaped_object)s” te skrap vereis dat die volgende " +"beskermde verwante objekte geskrap word:" #, python-format msgid "" "Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " "All of the following related items will be deleted:" msgstr "" +"Wil u definitief die %(object_name)s “%(escaped_object)s” skrap? Al die " +"volgende verwante items sal geskrap word:" msgid "Objects" -msgstr "" +msgstr "Objekte" msgid "Yes, I'm sure" msgstr "Ja, ek is seker" msgid "No, take me back" -msgstr "" +msgstr "Nee, ek wil teruggaan" msgid "Delete multiple objects" msgstr "Skrap meerdere objekte" @@ -407,7 +445,7 @@ msgid "" "objects, but your account doesn't have permission to delete the following " "types of objects:" msgstr "" -"Om die gekose %(objects_name)s te skrap sou verwante objekte skrap, maar jou " +"Om die gekose %(objects_name)s te skrap sou verwante objekte skrap, maar u " "rekening het nie toestemming om die volgende tipes objekte te skrap nie:" #, python-format @@ -415,7 +453,7 @@ msgid "" "Deleting the selected %(objects_name)s would require deleting the following " "protected related objects:" msgstr "" -"Om die gekose %(objects_name)s te skrap veries dat die volgende beskermde " +"Om die gekose %(objects_name)s te skrap vereis dat die volgende beskermde " "verwante objekte geskrap word:" #, python-format @@ -423,55 +461,60 @@ 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 "" -"Is jy seker jy wil die gekose %(objects_name)s skrap? Al die volgende " -"objekte en hul verwante items sal geskrap word:" +"Wil u definitief die gekose %(objects_name)s skrap? Al die volgende objekte " +"en hul verwante items sal geskrap word:" -msgid "Change" -msgstr "Verander" +msgid "View" +msgstr "Bekyk" msgid "Delete?" msgstr "Skrap?" #, python-format msgid " By %(filter_title)s " -msgstr "Deur %(filter_title)s" +msgstr " Volgens %(filter_title)s " msgid "Summary" -msgstr "" +msgstr "Opsomming" #, python-format msgid "Models in the %(name)s application" -msgstr "" +msgstr "Modelle in die %(name)s-toepassing" msgid "Add" msgstr "Voeg by" -msgid "You don't have permission to edit anything." -msgstr "Jy het nie toestemming om enigiets te wysig nie." +msgid "You don't have permission to view or edit anything." +msgstr "U het nie toestemming om enigiets te sien of te wysig nie." msgid "Recent actions" -msgstr "" +msgstr "Onlangse aksies" msgid "My actions" -msgstr "" +msgstr "My aksies" msgid "None available" msgstr "Niks beskikbaar nie" msgid "Unknown content" -msgstr "Onbekend inhoud" +msgstr "Onbekende inhoud" 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 "" +"Iets is verkeerd met die databasisinstallasie. Maak seker dat die gepaste " +"databasistabelle geskep is, en maak seker dat die databasis leesbaar is deur " +"die gepaste gebruiker." #, 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 "" +"U is aangemeld as %(username)s, maar het nie toegang tot hierdie bladsy nie. " +"Wil u met ’n ander rekening aanmeld?" msgid "Forgotten your password or username?" msgstr "Wagwoord of gebruikersnaam vergeet?" @@ -489,29 +532,17 @@ msgid "" "This object doesn't have a change history. It probably wasn't added via this " "admin site." msgstr "" -"Hierdie item het nie 'n veranderingsgeskiedenis nie. Dit was waarskynlik nie " -"deur middel van hierdie admin werf bygevoeg nie." +"Hierdie item het nie ’n veranderingsgeskiedenis nie. Dit is waarskynlik nie " +"deur middel van hierdie adminwerf bygevoeg nie." msgid "Show all" -msgstr "Wys alle" +msgstr "Wys almal" msgid "Save" msgstr "Stoor" -msgid "Popup closing..." -msgstr "" - -#, python-format -msgid "Change selected %(model)s" -msgstr "" - -#, python-format -msgid "Add another %(model)s" -msgstr "" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "" +msgid "Popup closing…" +msgstr "Opspringer sluit tans…" msgid "Search" msgstr "Soek" @@ -530,48 +561,67 @@ msgid "Save as new" msgstr "Stoor as nuwe" msgid "Save and add another" -msgstr "Stoor en voeg 'n ander by" +msgstr "Stoor en voeg ’n ander by" msgid "Save and continue editing" msgstr "Stoor en wysig verder" +msgid "Save and view" +msgstr "Stoor en bekyk" + +msgid "Close" +msgstr "Sluit" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Wysig gekose %(model)s" + +#, python-format +msgid "Add another %(model)s" +msgstr "Voeg nog ’n %(model)s by" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Skrap gekose %(model)s" + msgid "Thanks for spending some quality time with the Web site today." msgstr "" +"Dankie vir die kwaliteittyd wat u met die webwerf deurgebring het vandag." msgid "Log in again" -msgstr "Teken weer in" +msgstr "Meld weer aan" msgid "Password change" -msgstr "Wagwoord verandering" +msgstr "Wagwoordverandering" msgid "Your password was changed." -msgstr "Jou wagwoord was verander." +msgstr "Die wagwoord is verander." 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 "" -"Tik jou ou wagwoord, ter wille van sekuriteit's, en dan 'n nuwe wagwoord " -"twee keer so dat ons kan seker wees dat jy dit korrek ingetik het." +"Tik die ou wagwoord ter wille van sekuriteit, en dan die nuwe wagwoord twee " +"keer so dat ons kan seker wees dat dit korrek ingetik is." msgid "Change my password" msgstr "Verander my wagwoord" msgid "Password reset" -msgstr "Wagwoord herstel" +msgstr "Wagwoordherstel" msgid "Your password has been set. You may go ahead and log in now." -msgstr "Jou wagwoord is gestel. Jy kan nou voort gaan en aanteken." +msgstr "Jou wagwoord is gestel. Jy kan nou voortgaan en aanmeld." msgid "Password reset confirmation" -msgstr "Wagwoord herstel bevestiging" +msgstr "Bevestig wagwoordherstel" msgid "" "Please enter your new password twice so we can verify you typed it in " "correctly." msgstr "" -"Tik jou nuwe wagwoord twee keer in so ons kan seker wees dat jy dit korrek " -"ingetik het." +"Tik die nuwe wagwoord twee keer in so ons kan seker wees dat dit korrek " +"ingetik is." msgid "New password:" msgstr "Nuwe wagwoord:" @@ -583,25 +633,34 @@ msgid "" "The password reset link was invalid, possibly because it has already been " "used. Please request a new password reset." msgstr "" +"Die skakel vir wagwoordherstel was ongeldig, dalk omdat dit reeds gebruik " +"is. Vra gerus ’n nuwe een aan." 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 "" +"Instruksies vir die instel van u wagwoord is per e-pos gestuur as ’n " +"rekening bestaan met die e-posadres wat u gegee het. Die e-pos behoort " +"binnekort daar te wees." 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 "" +"Indien u nie ’n e-pos ontvang nie, maak seker dat die getikte adres die een " +"is waarmee u geregistreer het, en kontroleer ook u gemorspos." #, python-format msgid "" "You're receiving this email because you requested a password reset for your " "user account at %(site_name)s." msgstr "" +"U ontvang hierdie e-pos omdat u ’n wagwoordherstel vir u rekening by " +"%(site_name)s aangevra het." msgid "Please go to the following page and choose a new password:" -msgstr "Gaan asseblief na die volgende bladsy en kies 'n nuwe wagwoord:" +msgstr "Gaan asseblief na die volgende bladsy en kies ’n nuwe wagwoord:" msgid "Your username, in case you've forgotten:" msgstr "Jou gebruikersnaam, in geval jy vergeet het:" @@ -617,9 +676,11 @@ msgid "" "Forgotten your password? Enter your email address below, and we'll email " "instructions for setting a new one." msgstr "" +"Wagwoord vergeet? Tik u e-posadres hieronder in, en ons pos instruksies vir " +"die instel van ’n nuwe een." msgid "Email address:" -msgstr "" +msgstr "E-posadres:" msgid "Reset my password" msgstr "Herstel my wagwoord" @@ -635,6 +696,10 @@ msgstr "Kies %s" msgid "Select %s to change" msgstr "Kies %s om te verander" +#, python-format +msgid "Select %s to view" +msgstr "Kies %s om te bekyk" + msgid "Date:" msgstr "Datum:" @@ -645,7 +710,7 @@ msgid "Lookup" msgstr "Soek" msgid "Currently:" -msgstr "" +msgstr "Tans:" msgid "Change:" -msgstr "" +msgstr "Wysig:" diff --git a/django/contrib/admin/locale/af/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/af/LC_MESSAGES/djangojs.mo index 31bcfe0466d6..896cad2d697e 100644 Binary files a/django/contrib/admin/locale/af/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/af/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/af/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/af/LC_MESSAGES/djangojs.po index c9dd753ca066..816ef6e7f0a8 100644 --- a/django/contrib/admin/locale/af/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/af/LC_MESSAGES/djangojs.po @@ -1,14 +1,16 @@ # This file is distributed under the same license as the Django package. # # Translators: +# F Wolff , 2019 +# Pi Delport , 2013 # Pi Delport , 2013 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:10+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2019-01-04 18:43+0000\n" +"Last-Translator: F Wolff \n" "Language-Team: Afrikaans (http://www.transifex.com/django/django/language/" "af/)\n" "MIME-Version: 1.0\n" @@ -26,20 +28,22 @@ 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 "" +"Hierdie is die lys beskikbare %s. Kies gerus deur hulle in die boksie " +"hieronder te merk en dan die “Kies”-knoppie tussen die boksies te klik." #, javascript-format msgid "Type into this box to filter down the list of available %s." -msgstr "" +msgstr "Tik in hierdie blokkie om die lys beskikbare %s te filtreer." msgid "Filter" -msgstr "Filter" +msgstr "Filteer" msgid "Choose all" -msgstr "Kies alle" +msgstr "Kies almal" #, javascript-format msgid "Click to choose all %s at once." -msgstr "" +msgstr "Klik om al die %s gelyktydig te kies." msgid "Choose" msgstr "Kies" @@ -56,68 +60,78 @@ 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 "" +"Hierdie is die lys gekose %s. Verwyder gerus deur hulle in die boksie " +"hieronder te merk en dan die “Verwyder”-knoppie tussen die boksies te klik." msgid "Remove all" -msgstr "Verwyder alle" +msgstr "Verwyder almal" #, javascript-format msgid "Click to remove all chosen %s at once." -msgstr "" +msgstr "Klik om al die %s gelyktydig te verwyder." msgid "%(sel)s of %(cnt)s selected" msgid_plural "%(sel)s of %(cnt)s selected" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%(sel)s van %(cnt)s gekies" +msgstr[1] "%(sel)s van %(cnt)s gekies" msgid "" "You have unsaved changes on individual editable fields. If you run an " "action, your unsaved changes will be lost." msgstr "" +"Daar is ongestoorde veranderinge op individuele redigeerbare velde. Deur nou " +"’n aksie uit te voer, sal ongestoorde veranderinge verlore gaan." 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 "" +"U het ’n aksie gekies, maar nog nie die veranderinge aan individuele velde " +"gestoor nie. Klik asb. OK om te stoor. Dit sal nodig wees om weer die aksie " +"uit te voer." 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 "" - -#, javascript-format -msgid "Note: You are %s hour ahead of server time." -msgid_plural "Note: You are %s hours ahead of server time." -msgstr[0] "" -msgstr[1] "" - -#, javascript-format -msgid "Note: You are %s hour behind server time." -msgid_plural "Note: You are %s hours behind server time." -msgstr[0] "" -msgstr[1] "" +"U het ’n aksie gekies en het nie enige veranderinge aan individuele velde " +"aangebring nie. U soek waarskynlik na die Gaan-knoppie eerder as die Stoor-" +"knoppie." msgid "Now" msgstr "Nou" -msgid "Choose a Time" -msgstr "" - -msgid "Choose a time" -msgstr "Kies 'n tyd" - msgid "Midnight" msgstr "Middernag" msgid "6 a.m." -msgstr "6 v.m." +msgstr "06:00" msgid "Noon" msgstr "Middag" msgid "6 p.m." -msgstr "" +msgstr "18:00" + +#, javascript-format +msgid "Note: You are %s hour ahead of server time." +msgid_plural "Note: You are %s hours ahead of server time." +msgstr[0] "Let wel: U is %s uur voor die bedienertyd." +msgstr[1] "Let wel: U is %s ure voor die bedienertyd." + +#, javascript-format +msgid "Note: You are %s hour behind server time." +msgid_plural "Note: You are %s hours behind server time." +msgstr[0] "Let wel: U is %s uur agter die bedienertyd." +msgstr[1] "Let wel: U is %s ure agter die bedienertyd." + +msgid "Choose a Time" +msgstr "Kies ’n tyd" + +msgid "Choose a time" +msgstr "Kies ‘n tyd" msgid "Cancel" msgstr "Kanselleer" @@ -126,7 +140,7 @@ msgid "Today" msgstr "Vandag" msgid "Choose a Date" -msgstr "" +msgstr "Kies ’n datum" msgid "Yesterday" msgstr "Gister" @@ -135,68 +149,68 @@ msgid "Tomorrow" msgstr "Môre" msgid "January" -msgstr "" +msgstr "Januarie" msgid "February" -msgstr "" +msgstr "Februarie" msgid "March" -msgstr "" +msgstr "Maart" msgid "April" -msgstr "" +msgstr "April" msgid "May" -msgstr "" +msgstr "Mei" msgid "June" -msgstr "" +msgstr "Junie" msgid "July" -msgstr "" +msgstr "Julie" msgid "August" -msgstr "" +msgstr "Augustus" msgid "September" -msgstr "" +msgstr "September" msgid "October" -msgstr "" +msgstr "Oktober" msgid "November" -msgstr "" +msgstr "November" msgid "December" -msgstr "" +msgstr "Desember" msgctxt "one letter Sunday" msgid "S" -msgstr "" +msgstr "S" msgctxt "one letter Monday" msgid "M" -msgstr "" +msgstr "M" msgctxt "one letter Tuesday" msgid "T" -msgstr "" +msgstr "D" msgctxt "one letter Wednesday" msgid "W" -msgstr "" +msgstr "W" msgctxt "one letter Thursday" msgid "T" -msgstr "" +msgstr "D" msgctxt "one letter Friday" msgid "F" -msgstr "" +msgstr "V" msgctxt "one letter Saturday" msgid "S" -msgstr "" +msgstr "S" msgid "Show" msgstr "Wys" diff --git a/django/contrib/admin/locale/am/LC_MESSAGES/django.mo b/django/contrib/admin/locale/am/LC_MESSAGES/django.mo index 7f6c89989145..37fd72aa7a85 100644 Binary files a/django/contrib/admin/locale/am/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/am/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/am/LC_MESSAGES/django.po b/django/contrib/admin/locale/am/LC_MESSAGES/django.po index f9dce72409f1..b42fc41ec753 100644 --- a/django/contrib/admin/locale/am/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/am/LC_MESSAGES/django.po @@ -5,8 +5,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-19 17:44+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Amharic (http://www.transifex.com/django/django/language/" "am/)\n" @@ -202,7 +202,7 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" በተሳካ ሁኔታ ተወግድዋል:: " #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" msgstr "" #, python-format diff --git a/django/contrib/admin/locale/ar/LC_MESSAGES/django.mo b/django/contrib/admin/locale/ar/LC_MESSAGES/django.mo index fb29ecf58e2f..51148aa86e7a 100644 Binary files a/django/contrib/admin/locale/ar/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/ar/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/ar/LC_MESSAGES/django.po b/django/contrib/admin/locale/ar/LC_MESSAGES/django.po index 4750f67e7bc1..80f4ce3947a5 100644 --- a/django/contrib/admin/locale/ar/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/ar/LC_MESSAGES/django.po @@ -1,7 +1,7 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Bashar Al-Abdulhadi, 2015-2016 +# Bashar Al-Abdulhadi, 2015-2016,2018 # Bashar Al-Abdulhadi, 2014 # Eyad Toma , 2013 # Jannis Leidel , 2011 @@ -9,9 +9,9 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-09-21 12:54+0000\n" -"Last-Translator: Bashar Al-Abdulhadi\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 00:36+0000\n" +"Last-Translator: Ramiro Morales\n" "Language-Team: Arabic (http://www.transifex.com/django/django/language/ar/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -89,6 +89,15 @@ msgstr "إضافة سجل %(verbose_name)s آخر" msgid "Remove" msgstr "أزل" +msgid "Addition" +msgstr "إضافة" + +msgid "Change" +msgstr "عدّل" + +msgid "Deletion" +msgstr "حذف" + msgid "action time" msgstr "وقت الإجراء" @@ -102,7 +111,7 @@ msgid "object id" msgstr "معرف العنصر" #. 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 "ممثل العنصر" @@ -168,8 +177,10 @@ msgstr "" "أكثر من أختيار واحد." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "" + +msgid "You may edit it again below." msgstr "" #, python-brace-format @@ -179,12 +190,13 @@ msgid "" msgstr "" #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." +"The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" #, python-brace-format @@ -210,8 +222,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "تم حذف %(name)s \"%(obj)s\" بنجاح." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "العنصر %(name)s الذي به الحقل الأساسي %(key)r غير موجود." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" @@ -221,6 +233,10 @@ msgstr "أضف %s" msgid "Change %s" msgstr "عدّل %s" +#, python-format +msgid "View %s" +msgstr "" + msgid "Database error" msgstr "خطـأ في قاعدة البيانات" @@ -337,7 +353,7 @@ msgid "Change password" msgstr "غيّر كلمة المرور" msgid "Please correct the error below." -msgstr "الرجاء تصحيح الخطأ أدناه." +msgstr "" msgid "Please correct the errors below." msgstr "الرجاء تصحيح الأخطاء أدناه." @@ -446,8 +462,8 @@ msgstr "" "أأنت متأكد أنك تريد حذف عناصر %(objects_name)s المحددة؟ جميع العناصر التالية " "والعناصر المرتبطة بها سيتم حذفها:" -msgid "Change" -msgstr "عدّل" +msgid "View" +msgstr "" msgid "Delete?" msgstr "احذفه؟" @@ -466,8 +482,8 @@ msgstr "النماذج في تطبيق %(name)s" msgid "Add" msgstr "أضف" -msgid "You don't have permission to edit anything." -msgstr "ليست لديك الصلاحية لتعديل أي شيء." +msgid "You don't have permission to view or edit anything." +msgstr "" msgid "Recent actions" msgstr "آخر الإجراءات" @@ -522,20 +538,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 "ابحث" @@ -563,6 +567,24 @@ msgstr "احفظ وأضف آخر" msgid "Save and continue editing" 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 المختارة" + msgid "Thanks for spending some quality time with the Web site today." msgstr "شكراً لك على قضائك بعض الوقت مع الموقع اليوم." @@ -671,6 +693,10 @@ msgstr "اختر %s" msgid "Select %s to change" msgstr "اختر %s لتغييره" +#, python-format +msgid "Select %s to view" +msgstr "" + msgid "Date:" msgstr "التاريخ:" diff --git a/django/contrib/admin/locale/ar/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/ar/LC_MESSAGES/djangojs.mo index 9ce85c7144c2..b6c0a802d58c 100644 Binary files a/django/contrib/admin/locale/ar/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/ar/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/ar/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/ar/LC_MESSAGES/djangojs.po index 5fb5384c7b3b..577951ff36d9 100644 --- a/django/contrib/admin/locale/ar/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/ar/LC_MESSAGES/djangojs.po @@ -9,7 +9,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Arabic (http://www.transifex.com/django/django/language/ar/)\n" "MIME-Version: 1.0\n" diff --git a/django/contrib/admin/locale/ast/LC_MESSAGES/django.mo b/django/contrib/admin/locale/ast/LC_MESSAGES/django.mo index 72315f42430b..e35811bbb20c 100644 Binary files a/django/contrib/admin/locale/ast/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/ast/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/ast/LC_MESSAGES/django.po b/django/contrib/admin/locale/ast/LC_MESSAGES/django.po index 1409ab9737f2..437b080ac8fd 100644 --- a/django/contrib/admin/locale/ast/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/ast/LC_MESSAGES/django.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-23 19:51+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Asturian (http://www.transifex.com/django/django/language/" "ast/)\n" @@ -205,7 +205,7 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "" #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" msgstr "" #, python-format diff --git a/django/contrib/admin/locale/ast/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/ast/LC_MESSAGES/djangojs.mo index 5e2791f1e90d..7b7e49b7a39d 100644 Binary files a/django/contrib/admin/locale/ast/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/ast/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/ast/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/ast/LC_MESSAGES/djangojs.po index 36970237020c..53705c7038fb 100644 --- a/django/contrib/admin/locale/ast/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/ast/LC_MESSAGES/djangojs.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" +"PO-Revision-Date: 2017-09-20 02:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Asturian (http://www.transifex.com/django/django/language/" "ast/)\n" diff --git a/django/contrib/admin/locale/az/LC_MESSAGES/django.mo b/django/contrib/admin/locale/az/LC_MESSAGES/django.mo index 6b4f92dbfa86..13228817dee2 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 eacbab6cee45..1bedd485256e 100644 --- a/django/contrib/admin/locale/az/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/az/LC_MESSAGES/django.po @@ -1,15 +1,17 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Emin Mastizada , 2018 # Emin Mastizada , 2016 # Konul Allahverdiyeva , 2016 +# Zulfugar Ismayilzadeh , 2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-08-31 10:37+0000\n" -"Last-Translator: Konul Allahverdiyeva \n" +"POT-Creation-Date: 2018-05-21 14:16-0300\n" +"PO-Revision-Date: 2018-09-09 12:44+0000\n" +"Last-Translator: Emin Mastizada \n" "Language-Team: Azerbaijani (http://www.transifex.com/django/django/language/" "az/)\n" "MIME-Version: 1.0\n" @@ -87,6 +89,15 @@ msgstr "Daha bir %(verbose_name)s əlavə et" msgid "Remove" msgstr "Yığışdır" +msgid "Addition" +msgstr "Əlavə" + +msgid "Change" +msgstr "Dəyiş" + +msgid "Deletion" +msgstr "Silmə" + msgid "action time" msgstr "əməliyyat vaxtı" @@ -166,11 +177,11 @@ msgstr "" "basılı tutun." #, 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." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "{name} \"{obj}\" uğurla əlavə edildi." + +msgid "You may edit it again below." +msgstr "Bunu aşağıda təkrar redaktə edə bilərsiz." #, python-brace-format msgid "" @@ -180,16 +191,19 @@ msgstr "" "{name} \"{obj}\" uğurla əlavə edildi. Aşağıdan başqa bir {name} əlavə edə " "bilərsiz." -#, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "{name} \"{obj}\" uğurla əlavə edildi." - #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may edit it again below." 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} " @@ -217,8 +231,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" uğurla silindi." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(key)r əsas açarı ilə %(name)s mövcud deyil." +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. Çox güman ki, silinib?" #, python-format msgid "Add %s" @@ -228,6 +242,10 @@ msgstr "%s əlavə et" msgid "Change %s" msgstr "%s dəyiş" +#, python-format +msgid "View %s" +msgstr "%s gör" + msgid "Database error" msgstr "Bazada xəta" @@ -336,9 +354,7 @@ msgid "Change password" msgstr "Parolu dəyiş" msgid "Please correct the error below." -msgstr "" -"one: Aşağıdakı səhvi düzəltməyi xahiş edirik.\n" -"other: Aşağıdakı səhvləri düzəltməyi xahiş edirik." +msgstr "Lütfən aşağıdakı xətanı düzəldin." msgid "Please correct the errors below." msgstr "Lütfən aşağıdakı səhvləri düzəldin." @@ -449,8 +465,8 @@ msgstr "" "Seçdiyiniz %(objects_name)s obyektini silməkdə əminsiniz? Aşağıdakı bütün " "obyektlər və ona bağlı digər obyektlər də silinəcək:" -msgid "Change" -msgstr "Dəyiş" +msgid "View" +msgstr "Gör" msgid "Delete?" msgstr "Silək?" @@ -469,8 +485,8 @@ msgstr "%(name)s proqramındakı modellər" msgid "Add" msgstr "Əlavə et" -msgid "You don't have permission to edit anything." -msgstr "Üzrlər, amma sizin nəyisə dəyişməyə səlahiyyətiniz çatmır." +msgid "You don't have permission to view or edit anything." +msgstr "Heç nəyi görmə və ya redaktə etmə icazəniz yoxdur." msgid "Recent actions" msgstr "Son əməliyyatlar" @@ -532,6 +548,10 @@ msgstr "Qəfl pəncərə qapatılır..." msgid "Change selected %(model)s" msgstr "Seçilmiş %(model)s dəyişdir" +#, python-format +msgid "View selected %(model)s" +msgstr "Seçilən %(model)s gör" + #, python-format msgid "Add another %(model)s" msgstr "Başqa %(model)s əlavə et" @@ -562,6 +582,12 @@ msgstr "Yadda saxla və yenisini əlavə et" msgid "Save and continue editing" msgstr "Yadda saxla və redaktəyə davam et" +msgid "Save and view" +msgstr "Saxla və gör" + +msgid "Close" +msgstr "Qapat" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Sayt ilə səmərəli vaxt keçirdiyiniz üçün təşəkkür." @@ -670,6 +696,10 @@ msgstr "%s seç" msgid "Select %s to change" msgstr "%s dəyişmək üçün seç" +#, python-format +msgid "Select %s to view" +msgstr "Görmək üçün %s seçin" + msgid "Date:" msgstr "Tarix:" diff --git a/django/contrib/admin/locale/az/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/az/LC_MESSAGES/djangojs.mo index 040a0b260954..32272d65ba40 100644 Binary files a/django/contrib/admin/locale/az/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/az/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/az/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/az/LC_MESSAGES/djangojs.po index 5a04dc087352..266153591f52 100644 --- a/django/contrib/admin/locale/az/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/az/LC_MESSAGES/djangojs.po @@ -9,7 +9,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-09-16 10:02+0000\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Emin Mastizada \n" "Language-Team: Azerbaijani (http://www.transifex.com/django/django/language/" "az/)\n" diff --git a/django/contrib/admin/locale/be/LC_MESSAGES/django.mo b/django/contrib/admin/locale/be/LC_MESSAGES/django.mo index 8fea7f81b5af..8610030da8e8 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 bdb247cb89a9..5ba518469f76 100644 --- a/django/contrib/admin/locale/be/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/be/LC_MESSAGES/django.po @@ -2,13 +2,13 @@ # # Translators: # Viktar Palstsiuk , 2015 -# znotdead , 2016 +# znotdead , 2016-2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-09-15 10:32+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: znotdead \n" "Language-Team: Belarusian (http://www.transifex.com/django/django/language/" "be/)\n" @@ -212,8 +212,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "Сьцерлі %(name)s «%(obj)s»." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Аб’ект %(name)s з галоўным ключом %(key)r не існуе." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s з ID \"%(key)s\" не існуе. Магчыма гэта было выдалена." #, python-format msgid "Add %s" diff --git a/django/contrib/admin/locale/be/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/be/LC_MESSAGES/djangojs.mo index 665f60f67b57..4808902d6159 100644 Binary files a/django/contrib/admin/locale/be/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/be/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/be/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/be/LC_MESSAGES/djangojs.po index aafe889f6cb1..edbc103e6318 100644 --- a/django/contrib/admin/locale/be/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/be/LC_MESSAGES/djangojs.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-09-15 03:59+0000\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: znotdead \n" "Language-Team: Belarusian (http://www.transifex.com/django/django/language/" "be/)\n" diff --git a/django/contrib/admin/locale/bg/LC_MESSAGES/django.mo b/django/contrib/admin/locale/bg/LC_MESSAGES/django.mo index 1c5885597367..143965723d9f 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 11ce2fe2c10b..73bce75b06d2 100644 --- a/django/contrib/admin/locale/bg/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/bg/LC_MESSAGES/django.po @@ -6,13 +6,13 @@ # Jannis Leidel , 2011 # Lyuboslav Petrov , 2014 # Todor Lubenov , 2014-2015 -# Venelin Stoykov , 2015-2016 +# Venelin Stoykov , 2015-2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-06-30 17:44+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-11-17 08:33+0000\n" "Last-Translator: Venelin Stoykov \n" "Language-Team: Bulgarian (http://www.transifex.com/django/django/language/" "bg/)\n" @@ -221,8 +221,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "Обектът %(name)s \"%(obj)s\" бе успешно изтрит. " #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(name)s обект с първичен ключ %(key)r не съществува." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s с ИД \"%(key)s\" несъществува. Може би е изтрито?" #, python-format msgid "Add %s" @@ -527,7 +527,7 @@ msgid "Save" msgstr "Запис" msgid "Popup closing..." -msgstr "" +msgstr "Затваряне на изкачащ прозорец..." #, python-format msgid "Change selected %(model)s" diff --git a/django/contrib/admin/locale/bg/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/bg/LC_MESSAGES/djangojs.mo index bd4d7a82b0b8..4940bb9f4e71 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 f1089f616713..ded64ac3d036 100644 --- a/django/contrib/admin/locale/bg/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/bg/LC_MESSAGES/djangojs.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 11:57+0000\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Venelin Stoykov \n" "Language-Team: Bulgarian (http://www.transifex.com/django/django/language/" "bg/)\n" diff --git a/django/contrib/admin/locale/bn/LC_MESSAGES/django.mo b/django/contrib/admin/locale/bn/LC_MESSAGES/django.mo index 4b816f50eea1..ab1d7ee1b792 100644 Binary files a/django/contrib/admin/locale/bn/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/bn/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/bn/LC_MESSAGES/django.po b/django/contrib/admin/locale/bn/LC_MESSAGES/django.po index 051e02e8632c..e36fb616799b 100644 --- a/django/contrib/admin/locale/bn/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/bn/LC_MESSAGES/django.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Bengali (http://www.transifex.com/django/django/language/" "bn/)\n" @@ -205,8 +205,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" সফলতার সাথে মুছে ফেলা হয়েছে।" #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(key)r প্রাইমারি কি সম্বলিত %(name)s অবজেক্ট এর অস্তিত্ব নেই।" +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" diff --git a/django/contrib/admin/locale/bn/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/bn/LC_MESSAGES/djangojs.mo index b922c1f15a09..b3f7f973e1a3 100644 Binary files a/django/contrib/admin/locale/bn/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/bn/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/bn/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/bn/LC_MESSAGES/djangojs.po index 0a62e37f47b1..139d81c2abed 100644 --- a/django/contrib/admin/locale/bn/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/bn/LC_MESSAGES/djangojs.po @@ -9,7 +9,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Bengali (http://www.transifex.com/django/django/language/" "bn/)\n" diff --git a/django/contrib/admin/locale/br/LC_MESSAGES/django.mo b/django/contrib/admin/locale/br/LC_MESSAGES/django.mo index 52d25a6f39ae..296f113a522f 100644 Binary files a/django/contrib/admin/locale/br/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/br/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/br/LC_MESSAGES/django.po b/django/contrib/admin/locale/br/LC_MESSAGES/django.po index bbd53e8f6994..cbdc3593aa49 100644 --- a/django/contrib/admin/locale/br/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/br/LC_MESSAGES/django.po @@ -2,19 +2,24 @@ # # Translators: # Fulup , 2012 +# Irriep Nala Novram , 2018 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 00:36+0000\n" +"Last-Translator: Ramiro Morales\n" "Language-Team: Breton (http://www.transifex.com/django/django/language/br/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: br\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"Plural-Forms: nplurals=5; plural=((n%10 == 1) && (n%100 != 11) && (n%100 !" +"=71) && (n%100 !=91) ? 0 :(n%10 == 2) && (n%100 != 12) && (n%100 !=72) && (n" +"%100 !=92) ? 1 :(n%10 ==3 || n%10==4 || n%10==9) && (n%100 < 10 || n% 100 > " +"19) && (n%100 < 70 || n%100 > 79) && (n%100 < 90 || n%100 > 99) ? 2 :(n != 0 " +"&& n % 1000000 == 0) ? 3 : 4);\n" #, python-format msgid "Successfully deleted %(count)d %(items)s." @@ -25,14 +30,14 @@ msgid "Cannot delete %(name)s" msgstr "" msgid "Are you sure?" -msgstr "Ha sur oc'h ?" +msgstr "Ha sur oc'h?" #, python-format msgid "Delete selected %(verbose_name_plural)s" -msgstr "" +msgstr "Dilemel %(verbose_name_plural)s diuzet" msgid "Administration" -msgstr "" +msgstr "Melestradurezh" msgid "All" msgstr "An holl" @@ -62,10 +67,10 @@ msgid "This year" msgstr "Ar bloaz-mañ" msgid "No date" -msgstr "" +msgstr "Deiziad ebet" msgid "Has date" -msgstr "" +msgstr "D'an deiziad" #, python-format msgid "" @@ -74,37 +79,46 @@ msgid "" msgstr "" msgid "Action:" -msgstr "Ober :" +msgstr "Ober:" #, python-format msgid "Add another %(verbose_name)s" -msgstr "" +msgstr "Ouzhpennañ %(verbose_name)s all" msgid "Remove" msgstr "Lemel kuit" +msgid "Addition" +msgstr "Sammañ" + +msgid "Change" +msgstr "Cheñch" + +msgid "Deletion" +msgstr "Diverkadur" + msgid "action time" msgstr "eur an ober" msgid "user" -msgstr "" +msgstr "implijer" msgid "content type" -msgstr "" +msgstr "doare endalc'had" msgid "object id" -msgstr "" +msgstr "id an objed" #. 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 "" msgid "action flag" -msgstr "" +msgstr "ober banniel" msgid "change message" -msgstr "Kemennadenn gemmañ" +msgstr "Kemennadenn cheñchamant" msgid "log entry" msgstr "" @@ -114,43 +128,43 @@ msgstr "" #, python-format msgid "Added \"%(object)s\"." -msgstr "" +msgstr "Ouzhpennet \"%(object)s\"." #, python-format msgid "Changed \"%(object)s\" - %(changes)s" -msgstr "" +msgstr "Cheñchet \"%(object)s\" - %(changes)s" #, python-format msgid "Deleted \"%(object)s.\"" -msgstr "" +msgstr "Dilamet \"%(object)s.\"" msgid "LogEntry Object" -msgstr "Traezenn eus ar marilh" +msgstr "" #, python-brace-format msgid "Added {name} \"{object}\"." -msgstr "" +msgstr "Ouzhpennet {name} \"{object}\"." msgid "Added." -msgstr "" +msgstr "Ouzhpennet." msgid "and" msgstr "ha" #, python-brace-format msgid "Changed {fields} for {name} \"{object}\"." -msgstr "" +msgstr "Cheñchet {fields} evit {name} \"{object}\"." #, python-brace-format msgid "Changed {fields}." -msgstr "" +msgstr "Cheñchet {fields}." #, python-brace-format msgid "Deleted {name} \"{object}\"." -msgstr "" +msgstr "Dilamet {name} \"{object}\"." msgid "No fields changed." -msgstr "N'eus bet kemmet maezienn ebet." +msgstr "Maezienn ebet cheñchet." msgid "None" msgstr "Hini ebet" @@ -160,10 +174,12 @@ msgid "" msgstr "" #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." +msgid "The {name} \"{obj}\" was added successfully." msgstr "" +msgid "You may edit it again below." +msgstr "Rankout a rit ec'h aozañ adarre dindan." + #, python-brace-format msgid "" "The {name} \"{obj}\" was added successfully. You may add another {name} " @@ -171,12 +187,13 @@ msgid "" msgstr "" #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." +"The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" #, python-brace-format @@ -195,14 +212,14 @@ msgid "" msgstr "" msgid "No action selected." -msgstr "" +msgstr "Ober ebet diuzet." #, python-format msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "" #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" msgstr "" #, python-format @@ -211,36 +228,46 @@ msgstr "Ouzhpennañ %s" #, python-format msgid "Change %s" -msgstr "Kemmañ %s" +msgstr "Cheñch %s" + +#, python-format +msgid "View %s" +msgstr "Gwelet %s" msgid "Database error" -msgstr "Fazi en diaz roadennoù" +msgstr "Fazi diaz-roadennoù" #, python-format msgid "%(count)s %(name)s was changed successfully." msgid_plural "%(count)s %(name)s were changed successfully." -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%(count)s %(name)s a zo bet cheñchet mat." +msgstr[1] "%(count)s %(name)s a zo bet cheñchet mat. " +msgstr[2] "%(count)s %(name)s a zo bet cheñchet mat. " +msgstr[3] "%(count)s %(name)s a zo bet cheñchet mat." +msgstr[4] "%(count)s %(name)s a zo bet cheñchet mat." #, python-format msgid "%(total_count)s selected" msgid_plural "All %(total_count)s selected" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%(total_count)s diuzet" +msgstr[1] "%(total_count)s diuzet" +msgstr[2] "%(total_count)s diuzet" +msgstr[3] "%(total_count)s diuzet" +msgstr[4] "Pep %(total_count)s diuzet" #, python-format msgid "0 of %(cnt)s selected" -msgstr "" +msgstr "0 diwar %(cnt)s diuzet" #, python-format msgid "Change history: %s" -msgstr "Istor ar c'hemmoù : %s" +msgstr "Istor ar cheñchadurioù: %s" #. Translators: Model verbose name and instance representation, #. suitable to be an item in a list. #, python-format msgid "%(class_name)s %(instance)s" -msgstr "" +msgstr "%(class_name)s %(instance)s" #, python-format msgid "" @@ -412,8 +439,8 @@ msgid "" "following objects and their related items will be deleted:" msgstr "" -msgid "Change" -msgstr "Kemmañ" +msgid "View" +msgstr "" msgid "Delete?" msgstr "Diverkañ ?" @@ -432,7 +459,7 @@ msgstr "" msgid "Add" msgstr "Ouzhpennañ" -msgid "You don't have permission to edit anything." +msgid "You don't have permission to view or edit anything." msgstr "" msgid "Recent actions" @@ -482,19 +509,7 @@ msgstr "Diskouez pep tra" msgid "Save" msgstr "Enrollañ" -msgid "Popup closing..." -msgstr "" - -#, python-format -msgid "Change selected %(model)s" -msgstr "" - -#, python-format -msgid "Add another %(model)s" -msgstr "" - -#, python-format -msgid "Delete selected %(model)s" +msgid "Popup closing…" msgstr "" msgid "Search" @@ -505,6 +520,9 @@ msgid "%(counter)s result" msgid_plural "%(counter)s results" msgstr[0] "" msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" #, python-format msgid "%(full_result_count)s total" @@ -519,6 +537,24 @@ msgstr "Enrollañ hag ouzhpennañ unan all" msgid "Save and continue editing" msgstr "Enrollañ ha derc'hel da gemmañ" +msgid "Save and view" +msgstr "" + +msgid "Close" +msgstr "" + +#, python-format +msgid "Change selected %(model)s" +msgstr "" + +#, python-format +msgid "Add another %(model)s" +msgstr "" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "" + msgid "Thanks for spending some quality time with the Web site today." msgstr "" @@ -615,6 +651,10 @@ msgstr "Diuzañ %s" msgid "Select %s to change" msgstr "" +#, python-format +msgid "Select %s to view" +msgstr "" + msgid "Date:" msgstr "Deiziad :" diff --git a/django/contrib/admin/locale/br/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/br/LC_MESSAGES/djangojs.mo index 5ca4fff16ec9..58664d0728fe 100644 Binary files a/django/contrib/admin/locale/br/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/br/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/br/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/br/LC_MESSAGES/djangojs.po index 35ed85acec51..3f8195616816 100644 --- a/django/contrib/admin/locale/br/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/br/LC_MESSAGES/djangojs.po @@ -6,15 +6,19 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Breton (http://www.transifex.com/django/django/language/br/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: br\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"Plural-Forms: nplurals=5; plural=((n%10 == 1) && (n%100 != 11) && (n%100 !" +"=71) && (n%100 !=91) ? 0 :(n%10 == 2) && (n%100 != 12) && (n%100 !=72) && (n" +"%100 !=92) ? 1 :(n%10 ==3 || n%10==4 || n%10==9) && (n%100 < 10 || n% 100 > " +"19) && (n%100 < 70 || n%100 > 79) && (n%100 < 90 || n%100 > 99) ? 2 :(n != 0 " +"&& n % 1000000 == 0) ? 3 : 4);\n" #, javascript-format msgid "Available %s" @@ -67,6 +71,9 @@ msgid "%(sel)s of %(cnt)s selected" msgid_plural "%(sel)s of %(cnt)s selected" msgstr[0] "" msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" msgid "" "You have unsaved changes on individual editable fields. If you run an " @@ -85,20 +92,38 @@ msgid "" "button." msgstr "" +msgid "Now" +msgstr "Bremañ" + +msgid "Midnight" +msgstr "Hanternoz" + +msgid "6 a.m." +msgstr "6e00" + +msgid "Noon" +msgstr "Kreisteiz" + +msgid "6 p.m." +msgstr "" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." msgstr[0] "" msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" #, javascript-format msgid "Note: You are %s hour behind server time." msgid_plural "Note: You are %s hours behind server time." msgstr[0] "" msgstr[1] "" - -msgid "Now" -msgstr "Bremañ" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" msgid "Choose a Time" msgstr "" @@ -106,18 +131,6 @@ msgstr "" msgid "Choose a time" msgstr "Dibab un eur" -msgid "Midnight" -msgstr "Hanternoz" - -msgid "6 a.m." -msgstr "6e00" - -msgid "Noon" -msgstr "Kreisteiz" - -msgid "6 p.m." -msgstr "" - msgid "Cancel" msgstr "Nullañ" diff --git a/django/contrib/admin/locale/bs/LC_MESSAGES/django.mo b/django/contrib/admin/locale/bs/LC_MESSAGES/django.mo index 41b22c6fb58d..f920c9bbcaae 100644 Binary files a/django/contrib/admin/locale/bs/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/bs/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/bs/LC_MESSAGES/django.po b/django/contrib/admin/locale/bs/LC_MESSAGES/django.po index 2499f9e9c874..1d7eb6e6446e 100644 --- a/django/contrib/admin/locale/bs/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/bs/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Bosnian (http://www.transifex.com/django/django/language/" "bs/)\n" @@ -207,8 +207,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "Objekat „%(obj)s“ klase %(name)s obrisan je uspješno." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Objekat klase %(name)s sa primarnim ključem %(key)r ne postoji." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" diff --git a/django/contrib/admin/locale/bs/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/bs/LC_MESSAGES/djangojs.mo index e8f94c48614b..0a373ec447c7 100644 Binary files a/django/contrib/admin/locale/bs/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/bs/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/bs/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/bs/LC_MESSAGES/djangojs.po index 9c099c7d2b28..4866fd39e570 100644 --- a/django/contrib/admin/locale/bs/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/bs/LC_MESSAGES/djangojs.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:10+0000\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Bosnian (http://www.transifex.com/django/django/language/" "bs/)\n" diff --git a/django/contrib/admin/locale/ca/LC_MESSAGES/django.mo b/django/contrib/admin/locale/ca/LC_MESSAGES/django.mo index 236615f9684c..aeb5008a2643 100644 Binary files a/django/contrib/admin/locale/ca/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/ca/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/ca/LC_MESSAGES/django.po b/django/contrib/admin/locale/ca/LC_MESSAGES/django.po index b51d0888cb48..4c33f36be443 100644 --- a/django/contrib/admin/locale/ca/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/ca/LC_MESSAGES/django.po @@ -1,18 +1,20 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Antoni Aloy , 2014-2015 +# Antoni Aloy , 2014-2015,2017 # Carles Barrobés , 2011-2012,2014 # duub qnnp, 2015 +# GerardoGa , 2018 +# Gil Obradors Via , 2019 # Jannis Leidel , 2011 # Roger Pons , 2015 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-28 20:40+0000\n" +"Last-Translator: Gil Obradors Via \n" "Language-Team: Catalan (http://www.transifex.com/django/django/language/" "ca/)\n" "MIME-Version: 1.0\n" @@ -67,10 +69,10 @@ msgid "This year" msgstr "Aquest any" msgid "No date" -msgstr "" +msgstr "Sense data" msgid "Has date" -msgstr "" +msgstr "Té data" #, python-format msgid "" @@ -90,6 +92,15 @@ msgstr "Afegir un/a altre/a %(verbose_name)s." msgid "Remove" msgstr "Eliminar" +msgid "Addition" +msgstr "Afegeix" + +msgid "Change" +msgstr "Modificar" + +msgid "Deletion" +msgstr "Supressió" + msgid "action time" msgstr "moment de l'acció" @@ -103,7 +114,7 @@ msgid "object id" msgstr "id de l'objecte" #. 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' de l'objecte" @@ -136,7 +147,7 @@ msgstr "Objecte entrada del registre" #, python-brace-format msgid "Added {name} \"{object}\"." -msgstr "" +msgstr "Afegit {name} \"{object}\"." msgid "Added." msgstr "Afegit." @@ -146,15 +157,15 @@ msgstr "i" #, python-brace-format msgid "Changed {fields} for {name} \"{object}\"." -msgstr "" +msgstr "Canviat {fields} a {name} \"{object}\"." #, python-brace-format msgid "Changed {fields}." -msgstr "" +msgstr "Canviats {fields}." #, python-brace-format msgid "Deleted {name} \"{object}\"." -msgstr "" +msgstr "Eliminat {name} \"{object}\"." msgid "No fields changed." msgstr "Cap camp modificat." @@ -167,34 +178,45 @@ msgid "" msgstr "Premi \"Control\" o \"Command\" a un Mac per seleccionar-ne més d'un." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" +msgid "The {name} \"{obj}\" was added successfully." +msgstr "El {name} \"{obj}\" fou afegit amb èxit." + +msgid "You may edit it again below." +msgstr "Hauria d'editar de nou a sota." #, python-brace-format msgid "" "The {name} \"{obj}\" was added successfully. You may add another {name} " "below." msgstr "" +"El {name} \"{obj}\" s'ha afegit amb èxit. Pots afegir un altre {name} a " +"sota." #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" +"El {name} \"{obj}\" fou canviat amb èxit. Pots editar-ho un altra vegada a " +"sota." #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." +"The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" +"El {name} \"{obj}\" s'ha afegit amb èxit. Pots editar-lo altra vegada a " +"sota." #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " "below." msgstr "" +"El {name} \"{obj}\" fou canviat amb èxit. Pots afegir un altre {name} a " +"sota." #, python-brace-format msgid "The {name} \"{obj}\" was changed successfully." -msgstr "" +msgstr "El {name} \"{obj}\" fou canviat amb èxit." msgid "" "Items must be selected in order to perform actions on them. No items have " @@ -204,15 +226,15 @@ msgstr "" "seleccionat cap element." msgid "No action selected." -msgstr "no heu seleccionat cap acció" +msgstr "No heu seleccionat cap acció." #, python-format msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "El/la %(name)s \"%(obj)s\" s'ha eliminat amb èxit." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "No existeix cap objecte %(name)s amb la clau primària %(key)r." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s amb ID \"%(key)s\" no existeix. Potser va ser eliminat?" #, python-format msgid "Add %s" @@ -222,6 +244,10 @@ msgstr "Afegir %s" msgid "Change %s" msgstr "Modificar %s" +#, python-format +msgid "View %s" +msgstr "Visualitza %s" + msgid "Database error" msgstr "Error de base de dades" @@ -331,7 +357,7 @@ msgid "Change password" msgstr "Canviar contrasenya" msgid "Please correct the error below." -msgstr "Si us plau, corregiu els errors mostrats a sota." +msgstr "Si us plau, corregeix l'error de sota" msgid "Please correct the errors below." msgstr "Si us plau, corregiu els errors mostrats a sota." @@ -442,8 +468,8 @@ msgstr "" "N'esteu segur de voler esborrar els %(objects_name)s seleccionats? " "S'esborraran tots els objects següents i els seus elements relacionats:" -msgid "Change" -msgstr "Modificar" +msgid "View" +msgstr "Visualitza" msgid "Delete?" msgstr "Eliminar?" @@ -462,14 +488,14 @@ msgstr "Models en l'aplicació %(name)s" msgid "Add" msgstr "Afegir" -msgid "You don't have permission to edit anything." -msgstr "No teniu permís per editar res." +msgid "You don't have permission to view or edit anything." +msgstr "No teniu permisos per veure o editar" msgid "Recent actions" -msgstr "" +msgstr "Accions recents" msgid "My actions" -msgstr "" +msgstr "Les meves accions" msgid "None available" msgstr "Cap disponible" @@ -519,20 +545,8 @@ msgstr "Mostrar tots" msgid "Save" msgstr "Desar" -msgid "Popup closing..." -msgstr "Tancant el contingut emergent..." - -#, python-format -msgid "Change selected %(model)s" -msgstr "Canviea el %(model)s seleccionat" - -#, python-format -msgid "Add another %(model)s" -msgstr "Afegeix un altre %(model)s" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Esborra el %(model)s seleccionat" +msgid "Popup closing…" +msgstr "Tancant finestra emergent..." msgid "Search" msgstr "Cerca" @@ -556,6 +570,24 @@ msgstr "Desar i afegir-ne un de nou" msgid "Save and continue editing" msgstr "Desar i continuar editant" +msgid "Save and view" +msgstr "Desa i visualitza" + +msgid "Close" +msgstr "Tanca" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Canviea el %(model)s seleccionat" + +#, python-format +msgid "Add another %(model)s" +msgstr "Afegeix un altre %(model)s" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Esborra el %(model)s seleccionat" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Gràcies per passar una estona de qualitat al web durant el dia d'avui." @@ -668,6 +700,10 @@ msgstr "Seleccioneu %s" msgid "Select %s to change" msgstr "Seleccioneu %s per modificar" +#, python-format +msgid "Select %s to view" +msgstr "Selecciona %sper a veure" + msgid "Date:" msgstr "Data:" diff --git a/django/contrib/admin/locale/ca/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/ca/LC_MESSAGES/djangojs.mo index e9a9352ec376..d08f6d69b739 100644 Binary files a/django/contrib/admin/locale/ca/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/ca/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/ca/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/ca/LC_MESSAGES/djangojs.po index 8e1e6612992e..156a2508d460 100644 --- a/django/contrib/admin/locale/ca/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/ca/LC_MESSAGES/djangojs.po @@ -1,6 +1,7 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Antoni Aloy , 2017 # Carles Barrobés , 2011-2012,2014 # Jannis Leidel , 2011 # Roger Pons , 2015 @@ -9,8 +10,8 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" -"Last-Translator: Jannis Leidel \n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" +"Last-Translator: Antoni Aloy \n" "Language-Team: Catalan (http://www.transifex.com/django/django/language/" "ca/)\n" "MIME-Version: 1.0\n" @@ -150,68 +151,68 @@ msgid "Tomorrow" msgstr "Demà" msgid "January" -msgstr "" +msgstr "Gener" msgid "February" -msgstr "" +msgstr "Febrer" msgid "March" -msgstr "" +msgstr "Març" msgid "April" -msgstr "" +msgstr "Abril" msgid "May" -msgstr "" +msgstr "Maig" msgid "June" -msgstr "" +msgstr "Juny" msgid "July" -msgstr "" +msgstr "Juliol" msgid "August" -msgstr "" +msgstr "Agost" msgid "September" -msgstr "" +msgstr "Setembre" msgid "October" -msgstr "" +msgstr "Octubre" msgid "November" -msgstr "" +msgstr "Novembre" msgid "December" -msgstr "" +msgstr "Desembre" msgctxt "one letter Sunday" msgid "S" -msgstr "" +msgstr "D" msgctxt "one letter Monday" msgid "M" -msgstr "" +msgstr "L" msgctxt "one letter Tuesday" msgid "T" -msgstr "" +msgstr "M" msgctxt "one letter Wednesday" msgid "W" -msgstr "" +msgstr "X" msgctxt "one letter Thursday" msgid "T" -msgstr "" +msgstr "J" msgctxt "one letter Friday" msgid "F" -msgstr "" +msgstr "V" msgctxt "one letter Saturday" msgid "S" -msgstr "" +msgstr "S" msgid "Show" msgstr "Mostrar" diff --git a/django/contrib/admin/locale/cs/LC_MESSAGES/django.mo b/django/contrib/admin/locale/cs/LC_MESSAGES/django.mo index ea58660a5428..b4fb95066911 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 ffe3d6a5ef5d..45418edebb6c 100644 --- a/django/contrib/admin/locale/cs/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/cs/LC_MESSAGES/django.po @@ -5,21 +5,22 @@ # Jirka Vejrazka , 2011 # Tomáš Ehrlich , 2015 # Vláďa Macek , 2013-2014 -# Vláďa Macek , 2015-2016 +# Vláďa Macek , 2015-2019 # yedpodtrzitko , 2016 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-09-16 22:14+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-02-19 07:56+0000\n" "Last-Translator: Vláďa Macek \n" "Language-Team: Czech (http://www.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" "Language: cs\n" -"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" +"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n " +"<= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" #, python-format msgid "Successfully deleted %(count)d %(items)s." @@ -90,6 +91,15 @@ msgstr "Přidat %(verbose_name)s" msgid "Remove" msgstr "Odebrat" +msgid "Addition" +msgstr "Přidání" + +msgid "Change" +msgstr "Změnit" + +msgid "Deletion" +msgstr "Odstranění" + msgid "action time" msgstr "čas operace" @@ -103,7 +113,7 @@ msgid "object id" msgstr "id položky" #. 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 "reprez. položky" @@ -169,11 +179,11 @@ msgstr "" "\"Command\" na Macu)." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" -"Položka typu {name} \"{obj}\" byla úspěšně přidána. Níže ji můžete dále " -"upravovat." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "Položka typu {name} \"{obj}\" byla úspěšně přidána." + +msgid "You may edit it again below." +msgstr "Níže můžete údaje znovu upravovat." #, python-brace-format msgid "" @@ -183,10 +193,6 @@ msgstr "" "Položka typu {name} \"{obj}\" byla úspěšně přidána. Níže můžete přidat další " "položku {name}." -#, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "Položka typu {name} \"{obj}\" byla úspěšně přidána." - #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may edit it again below." @@ -194,6 +200,13 @@ 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 typu {name} \"{obj}\" 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} " @@ -221,8 +234,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "Položka \"%(obj)s\" typu %(name)s byla úspěšně odstraněna." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Položka \"%(name)s\" s primárním klíčem \"%(key)r\" neexistuje." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "Objekt %(name)s s klíčem \"%(key)s\" neexistuje. Možná byl odstraněn." #, python-format msgid "Add %s" @@ -232,6 +245,10 @@ msgstr "%s: přidat" msgid "Change %s" msgstr "%s: změnit" +#, python-format +msgid "View %s" +msgstr "Zobrazit %s" + msgid "Database error" msgstr "Chyba databáze" @@ -241,6 +258,7 @@ msgid_plural "%(count)s %(name)s were changed successfully." msgstr[0] "Položka %(name)s byla úspěšně změněna." msgstr[1] "%(count)s položky %(name)s byly úspěšně změněny." msgstr[2] "%(count)s položek %(name)s bylo úspěšně změněno." +msgstr[3] "%(count)s položek %(name)s bylo úspěšně změněno." #, python-format msgid "%(total_count)s selected" @@ -248,6 +266,7 @@ msgid_plural "All %(total_count)s selected" msgstr[0] "%(total_count)s položka vybrána." msgstr[1] "Všechny %(total_count)s položky vybrány." msgstr[2] "Vybráno všech %(total_count)s položek." +msgstr[3] "Vybráno všech %(total_count)s položek." #, python-format msgid "0 of %(cnt)s selected" @@ -342,7 +361,7 @@ msgid "Change password" msgstr "Změnit heslo" msgid "Please correct the error below." -msgstr "Opravte níže uvedené chyby." +msgstr "Opravte níže uvedenou chybu." msgid "Please correct the errors below." msgstr "Opravte níže uvedené chyby." @@ -453,8 +472,8 @@ msgstr "" "Opravdu má být odstraněny vybrané položky typu %(objects_name)s? Všechny " "vybrané a s nimi související položky budou odstraněny:" -msgid "Change" -msgstr "Změnit" +msgid "View" +msgstr "Zobrazit" msgid "Delete?" msgstr "Odstranit?" @@ -473,8 +492,8 @@ msgstr "Modely v aplikaci %(name)s" msgid "Add" msgstr "Přidat" -msgid "You don't have permission to edit anything." -msgstr "Nemáte oprávnění nic měnit." +msgid "You don't have permission to view or edit anything." +msgstr "Nemáte oprávnění k zobrazení ani úpravám." msgid "Recent actions" msgstr "Nedávné akce" @@ -530,21 +549,9 @@ msgstr "Zobrazit vše" msgid "Save" msgstr "Uložit" -msgid "Popup closing..." +msgid "Popup closing…" msgstr "Vyskakovací okno se zavírá..." -#, python-format -msgid "Change selected %(model)s" -msgstr "Změnit vybrané položky typu %(model)s" - -#, python-format -msgid "Add another %(model)s" -msgstr "Přidat další %(model)s" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Odstranit vybrané položky typu %(model)s" - msgid "Search" msgstr "Hledat" @@ -554,6 +561,7 @@ msgid_plural "%(counter)s results" msgstr[0] "%(counter)s výsledek" msgstr[1] "%(counter)s výsledky" msgstr[2] "%(counter)s výsledků" +msgstr[3] "%(counter)s výsledků" #, python-format msgid "%(full_result_count)s total" @@ -568,6 +576,24 @@ msgstr "Uložit a přidat další položku" msgid "Save and continue editing" msgstr "Uložit a pokračovat v úpravách" +msgid "Save and view" +msgstr "Uložit a zobrazit" + +msgid "Close" +msgstr "Zavřít" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Změnit vybrané položky typu %(model)s" + +#, python-format +msgid "Add another %(model)s" +msgstr "Přidat další %(model)s" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Odstranit vybrané položky typu %(model)s" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Děkujeme za čas strávený s tímto webem." @@ -677,6 +703,10 @@ msgstr "%s: vybrat" msgid "Select %s to change" msgstr "Vyberte položku %s ke změně" +#, python-format +msgid "Select %s to view" +msgstr "Vyberte položku %s k zobrazení" + msgid "Date:" msgstr "Datum:" diff --git a/django/contrib/admin/locale/cs/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/cs/LC_MESSAGES/djangojs.mo index e286304aa111..d9595162f55e 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 c5eba2dfe160..7785061dc26f 100644 --- a/django/contrib/admin/locale/cs/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/cs/LC_MESSAGES/djangojs.po @@ -9,15 +9,16 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-09-16 22:17+0000\n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Vláďa Macek \n" "Language-Team: Czech (http://www.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" "Language: cs\n" -"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" +"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n " +"<= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" #, javascript-format msgid "Available %s" @@ -77,6 +78,7 @@ msgid_plural "%(sel)s of %(cnt)s selected" msgstr[0] "Vybrána je %(sel)s položka z celkem %(cnt)s." msgstr[1] "Vybrány jsou %(sel)s položky z celkem %(cnt)s." msgstr[2] "Vybraných je %(sel)s položek z celkem %(cnt)s." +msgstr[3] "Vybraných je %(sel)s položek z celkem %(cnt)s." msgid "" "You have unsaved changes on individual editable fields. If you run an " @@ -101,12 +103,28 @@ msgstr "" "Byla vybrána operace a jednotlivá pole nejsou změněná. Patrně hledáte " "tlačítko Provést spíše než Uložit." +msgid "Now" +msgstr "Nyní" + +msgid "Midnight" +msgstr "Půlnoc" + +msgid "6 a.m." +msgstr "6h ráno" + +msgid "Noon" +msgstr "Poledne" + +msgid "6 p.m." +msgstr "6h večer" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." msgstr[0] "Poznámka: Váš čas o %s hodinu předstihuje čas na serveru." msgstr[1] "Poznámka: Váš čas o %s hodiny předstihuje čas na serveru." msgstr[2] "Poznámka: Váš čas o %s hodin předstihuje čas na serveru." +msgstr[3] "Poznámka: Váš čas o %s hodin předstihuje čas na serveru." #, javascript-format msgid "Note: You are %s hour behind server time." @@ -114,9 +132,7 @@ msgid_plural "Note: You are %s hours behind server time." msgstr[0] "Poznámka: Váš čas se o %s hodinu zpožďuje za časem na serveru." msgstr[1] "Poznámka: Váš čas se o %s hodiny zpožďuje za časem na serveru." msgstr[2] "Poznámka: Váš čas se o %s hodin zpožďuje za časem na serveru." - -msgid "Now" -msgstr "Nyní" +msgstr[3] "Poznámka: Váš čas se o %s hodin zpožďuje za časem na serveru." msgid "Choose a Time" msgstr "Vyberte čas" @@ -124,18 +140,6 @@ msgstr "Vyberte čas" msgid "Choose a time" msgstr "Vyberte čas" -msgid "Midnight" -msgstr "Půlnoc" - -msgid "6 a.m." -msgstr "6h ráno" - -msgid "Noon" -msgstr "Poledne" - -msgid "6 p.m." -msgstr "6h večer" - msgid "Cancel" msgstr "Storno" diff --git a/django/contrib/admin/locale/cy/LC_MESSAGES/django.mo b/django/contrib/admin/locale/cy/LC_MESSAGES/django.mo index 37a0624c4b8d..e20f6a4a95f2 100644 Binary files a/django/contrib/admin/locale/cy/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/cy/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/cy/LC_MESSAGES/django.po b/django/contrib/admin/locale/cy/LC_MESSAGES/django.po index a1dd0fca2401..82e82f78c3e8 100644 --- a/django/contrib/admin/locale/cy/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/cy/LC_MESSAGES/django.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-23 18:54+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Welsh (http://www.transifex.com/django/django/language/cy/)\n" "MIME-Version: 1.0\n" @@ -208,8 +208,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "Dilëwyd %(name)s \"%(obj)s\" yn llwyddiannus." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Nid ydy gwrthrych %(name)s gyda'r prif allwedd %(key)r yn bodoli." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" diff --git a/django/contrib/admin/locale/cy/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/cy/LC_MESSAGES/djangojs.mo index e7a56c56d3ff..ee9a9ca28592 100644 Binary files a/django/contrib/admin/locale/cy/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/cy/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/cy/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/cy/LC_MESSAGES/djangojs.po index f3ffadaca748..fa7ad2ac03fd 100644 --- a/django/contrib/admin/locale/cy/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/cy/LC_MESSAGES/djangojs.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" +"PO-Revision-Date: 2017-09-23 18:54+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Welsh (http://www.transifex.com/django/django/language/cy/)\n" "MIME-Version: 1.0\n" diff --git a/django/contrib/admin/locale/da/LC_MESSAGES/django.mo b/django/contrib/admin/locale/da/LC_MESSAGES/django.mo index cba678daaa69..48aae7b1aac7 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 1ac6e9dc9792..2f9fbfc4b951 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 Wognsen , 2013,2015-2016 +# Erik Wognsen , 2013,2015-2019 # Finn Gruwier Larsen, 2011 # Jannis Leidel , 2011 # valberg , 2014-2015 @@ -11,8 +11,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:51+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 07:26+0000\n" "Last-Translator: Erik Wognsen \n" "Language-Team: Danish (http://www.transifex.com/django/django/language/da/)\n" "MIME-Version: 1.0\n" @@ -90,6 +90,15 @@ msgstr "Tilføj endnu en %(verbose_name)s" msgid "Remove" msgstr "Fjern" +msgid "Addition" +msgstr "Tilføjelse" + +msgid "Change" +msgstr "Ret" + +msgid "Deletion" +msgstr "Sletning" + msgid "action time" msgstr "handlingstid" @@ -103,7 +112,7 @@ msgid "object id" msgstr "objekt-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 "objekt repr" @@ -168,25 +177,29 @@ msgstr "" "Hold \"Ctrl\" (eller \"Æbletasten\" på Mac) nede for at vælge mere end en." #, 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." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "{name} \"{obj}\" blev tilføjet." + +msgid "You may edit it again below." +msgstr "Du kan redigere den/det igen herunder." #, python-brace-format msgid "" "The {name} \"{obj}\" was added successfully. You may add another {name} " "below." -msgstr "{name} \"{obj}\" blev tilføjet. Du kan endnu en/et {name} herunder." - -#, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "{name} \"{obj}\" blev tilføjet." +msgstr "" +"{name} \"{obj}\" blev tilføjet. Du kan tilføje endnu en/et {name} herunder." #, python-brace-format 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} " @@ -213,8 +226,9 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" blev slettet." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Der findes ikke et %(name)s-objekt med primærnøgle %(key)r." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" +"%(name)s med ID \"%(key)s\" findes ikke. Måske er objektet blevet slettet?" #, python-format msgid "Add %s" @@ -224,8 +238,12 @@ msgstr "Tilføj %s" msgid "Change %s" msgstr "Ret %s" +#, python-format +msgid "View %s" +msgstr "Vis %s" + msgid "Database error" -msgstr "databasefejl" +msgstr "Databasefejl" #, python-format msgid "%(count)s %(name)s was changed successfully." @@ -443,8 +461,8 @@ msgstr "" "Er du sikker på du vil slette de valgte %(objects_name)s? Alle de følgende " "objekter og deres relaterede emner vil blive slettet:" -msgid "Change" -msgstr "Ret" +msgid "View" +msgstr "Vis" msgid "Delete?" msgstr "Slet?" @@ -463,8 +481,8 @@ msgstr "Modeller i applikationen %(name)s" msgid "Add" msgstr "Tilføj" -msgid "You don't have permission to edit anything." -msgstr "Du har ikke rettigheder til at foretage ændringer." +msgid "You don't have permission to view or edit anything." +msgstr "Du har ikke rettigheder til at se eller redigere noget." msgid "Recent actions" msgstr "Seneste handlinger" @@ -520,20 +538,8 @@ msgstr "Vis alle" msgid "Save" msgstr "Gem" -msgid "Popup closing..." -msgstr "Popup lukker..." - -#, python-format -msgid "Change selected %(model)s" -msgstr "Redigér valgte %(model)s" - -#, python-format -msgid "Add another %(model)s" -msgstr "Tilføj endnu en %(model)s" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Slet valgte %(model)s" +msgid "Popup closing…" +msgstr "Popup lukker…" msgid "Search" msgstr "Søg" @@ -557,6 +563,24 @@ msgstr "Gem og tilføj endnu en" msgid "Save and continue editing" msgstr "Gem og fortsæt med at redigere" +msgid "Save and view" +msgstr "Gem og vis" + +msgid "Close" +msgstr "Luk" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Redigér valgte %(model)s" + +#, python-format +msgid "Add another %(model)s" +msgstr "Tilføj endnu en %(model)s" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Slet valgte %(model)s" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Tak for den kvalitetstid du brugte på websitet i dag." @@ -668,6 +692,10 @@ msgstr "Vælg %s" msgid "Select %s to change" msgstr "Vælg %s, der skal ændres" +#, python-format +msgid "Select %s to view" +msgstr "Vælg %s, der skal vises" + msgid "Date:" msgstr "Dato:" diff --git a/django/contrib/admin/locale/da/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/da/LC_MESSAGES/djangojs.mo index b4ada4fb8b6f..9270ea4f05cd 100644 Binary files a/django/contrib/admin/locale/da/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/da/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/da/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/da/LC_MESSAGES/djangojs.po index 13c373a5ce1b..17e0d5ae0665 100644 --- a/django/contrib/admin/locale/da/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/da/LC_MESSAGES/djangojs.po @@ -5,14 +5,15 @@ # Erik Wognsen , 2012,2015-2016 # Finn Gruwier Larsen, 2011 # Jannis Leidel , 2011 +# Mathias Rav , 2017 # valberg , 2014 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:16+0000\n" -"Last-Translator: Erik Wognsen \n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" +"Last-Translator: Mathias Rav \n" "Language-Team: Danish (http://www.transifex.com/django/django/language/da/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -101,27 +102,9 @@ msgstr "" "Du har valgt en handling, og du har ikke udført nogen ændringer på felter. " "Det, du søger er formentlig Udfør-knappen i stedet for Gem-knappen." -#, javascript-format -msgid "Note: You are %s hour ahead of server time." -msgid_plural "Note: You are %s hours ahead of server time." -msgstr[0] "Obs: Du er %s time forud i forhold servertiden." -msgstr[1] "Obs: Du er %s timer forud i forhold servertiden." - -#, javascript-format -msgid "Note: You are %s hour behind server time." -msgid_plural "Note: You are %s hours behind server time." -msgstr[0] "Obs: Du er %s time bagud i forhold servertiden." -msgstr[1] "Obs: Du er %s timer forud i forhold servertiden." - msgid "Now" msgstr "Nu" -msgid "Choose a Time" -msgstr "Vælg et Tidspunkt" - -msgid "Choose a time" -msgstr "Vælg et tidspunkt" - msgid "Midnight" msgstr "Midnat" @@ -134,6 +117,24 @@ msgstr "Middag" msgid "6 p.m." msgstr "Klokken 18" +#, javascript-format +msgid "Note: You are %s hour ahead of server time." +msgid_plural "Note: You are %s hours ahead of server time." +msgstr[0] "Obs: Du er %s time forud i forhold til servertiden." +msgstr[1] "Obs: Du er %s timer forud i forhold til servertiden." + +#, javascript-format +msgid "Note: You are %s hour behind server time." +msgid_plural "Note: You are %s hours behind server time." +msgstr[0] "Obs: Du er %s time bagud i forhold til servertiden." +msgstr[1] "Obs: Du er %s timer bagud i forhold til servertiden." + +msgid "Choose a Time" +msgstr "Vælg et Tidspunkt" + +msgid "Choose a time" +msgstr "Vælg et tidspunkt" + msgid "Cancel" msgstr "Annuller" diff --git a/django/contrib/admin/locale/de/LC_MESSAGES/django.mo b/django/contrib/admin/locale/de/LC_MESSAGES/django.mo index 4a940a13e98a..21391ce9e603 100644 Binary files a/django/contrib/admin/locale/de/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/de/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/de/LC_MESSAGES/django.po b/django/contrib/admin/locale/de/LC_MESSAGES/django.po index 91b0c134bdce..afbd3e3aed4a 100644 --- a/django/contrib/admin/locale/de/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/de/LC_MESSAGES/django.po @@ -4,17 +4,17 @@ # André Hagenbruch, 2012 # Florian Apolloner , 2011 # Dimitris Glezos , 2012 -# Jannis, 2013 -# Jannis Leidel , 2013-2016 -# Jannis, 2016 -# Markus Holtermann , 2013,2015 +# Jannis Vajen, 2013 +# Jannis Leidel , 2013-2018 +# Jannis Vajen, 2016 +# Markus Holtermann , 2013,2015 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-07-26 11:53+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 00:36+0000\n" +"Last-Translator: Ramiro Morales\n" "Language-Team: German (http://www.transifex.com/django/django/language/de/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -78,8 +78,8 @@ msgid "" "Please enter the correct %(username)s and password for a staff account. Note " "that both fields may be case-sensitive." msgstr "" -"Bitte einen gültigen %(username)s und ein Passwort für einen Staff-Account " -"eingeben. Beide Felder berücksichtigen die Groß-/Kleinschreibung." +"Bitte %(username)s und Passwort für einen Staff-Account eingeben. Beide " +"Felder berücksichtigen die Groß-/Kleinschreibung." msgid "Action:" msgstr "Aktion:" @@ -91,6 +91,15 @@ msgstr "%(verbose_name)s hinzufügen" msgid "Remove" msgstr "Entfernen" +msgid "Addition" +msgstr "Hinzugefügt" + +msgid "Change" +msgstr "Ändern" + +msgid "Deletion" +msgstr "Gelöscht" + msgid "action time" msgstr "Zeitpunkt der Aktion" @@ -104,7 +113,7 @@ msgid "object id" msgstr "Objekt-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 "Objekt Darst." @@ -170,10 +179,11 @@ msgstr "" "mehrere Einträge auszuwählen." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" -"{name} „{obj}“ wurde erfolgreich hinzugefügt und kann unten geändert werden." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "{name} „{obj}“ wurde erfolgreich hinzugefügt." + +msgid "You may edit it again below." +msgstr "Es kann unten erneut geändert werden." #, python-brace-format msgid "" @@ -183,10 +193,6 @@ msgstr "" "{name} „{obj}“ wurde erfolgreich hinzugefügt und kann nun unten um ein " "Weiteres ergänzt werden." -#, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "{name} „{obj}“ wurde erfolgreich hinzugefügt." - #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may edit it again below." @@ -194,6 +200,12 @@ msgstr "" "{name} „{obj}“ wurde erfolgreich geändert und kann unten erneut geändert " "werden." +#, python-brace-format +msgid "" +"The {name} \"{obj}\" was added successfully. You may edit it again below." +msgstr "" +"{name} „{obj}“ wurde erfolgreich hinzugefügt und kann unten geändert werden." + #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " @@ -221,9 +233,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" wurde erfolgreich gelöscht." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "" -"Das %(name)s-Objekt mit dem Primärschlüssel %(key)r ist nicht vorhanden." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s mit ID \"%(key)s\" existiert nicht. Eventuell gelöscht?" #, python-format msgid "Add %s" @@ -233,6 +244,10 @@ msgstr "%s hinzufügen" msgid "Change %s" msgstr "%s ändern" +#, python-format +msgid "View %s" +msgstr "%s ansehen" + msgid "Database error" msgstr "Datenbankfehler" @@ -342,7 +357,7 @@ msgid "Change password" msgstr "Passwort ändern" msgid "Please correct the error below." -msgstr "Bitte die aufgeführten Fehler korrigieren." +msgstr "Bitte den unten aufgeführten Fehler korrigieren." msgid "Please correct the errors below." msgstr "Bitte die unten aufgeführten Fehler korrigieren." @@ -455,8 +470,8 @@ msgstr "" "Sind Sie sicher, dass Sie die ausgewählten %(objects_name)s löschen wollen? " "Alle folgenden Objekte und ihre verwandten Objekte werden gelöscht:" -msgid "Change" -msgstr "Ändern" +msgid "View" +msgstr "Ansehen" msgid "Delete?" msgstr "Löschen?" @@ -475,8 +490,10 @@ msgstr "Modelle der %(name)s-Anwendung" msgid "Add" msgstr "Hinzufügen" -msgid "You don't have permission to edit anything." -msgstr "Sie haben keine Berechtigung, irgendetwas zu ändern." +msgid "You don't have permission to view or edit anything." +msgstr "" +"Ihr Benutzerkonto besitzt nicht die nötigen Rechte, um etwas anzusehen oder " +"zu ändern." msgid "Recent actions" msgstr "Neueste Aktionen" @@ -532,20 +549,8 @@ msgstr "Zeige alle" msgid "Save" msgstr "Sichern" -msgid "Popup closing..." -msgstr "Popup wird geschlossen..." - -#, python-format -msgid "Change selected %(model)s" -msgstr "Ausgewählte %(model)s ändern" - -#, python-format -msgid "Add another %(model)s" -msgstr "%(model)s hinzufügen" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Ausgewählte %(model)s löschen" +msgid "Popup closing…" +msgstr "" msgid "Search" msgstr "Suchen" @@ -569,6 +574,24 @@ msgstr "Sichern und neu hinzufügen" msgid "Save and continue editing" msgstr "Sichern und weiter bearbeiten" +msgid "Save and view" +msgstr "Sichern und ansehen" + +msgid "Close" +msgstr "Schließen" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Ausgewählte %(model)s ändern" + +#, python-format +msgid "Add another %(model)s" +msgstr "%(model)s hinzufügen" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Ausgewählte %(model)s löschen" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Vielen Dank, dass Sie hier ein paar nette Minuten verbracht haben." @@ -681,6 +704,10 @@ msgstr "%s auswählen" msgid "Select %s to change" msgstr "%s zur Änderung auswählen" +#, python-format +msgid "Select %s to view" +msgstr "%s zum Ansehen auswählen" + msgid "Date:" msgstr "Datum:" diff --git a/django/contrib/admin/locale/de/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/de/LC_MESSAGES/djangojs.mo index 31682665b791..d30346a4b338 100644 Binary files a/django/contrib/admin/locale/de/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/de/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/de/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/de/LC_MESSAGES/djangojs.po index c19eda114892..ba4f0cb2232b 100644 --- a/django/contrib/admin/locale/de/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/de/LC_MESSAGES/djangojs.po @@ -9,7 +9,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-07-26 11:31+0000\n" +"PO-Revision-Date: 2017-09-23 18:54+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: German (http://www.transifex.com/django/django/language/de/)\n" "MIME-Version: 1.0\n" diff --git a/django/contrib/admin/locale/dsb/LC_MESSAGES/django.mo b/django/contrib/admin/locale/dsb/LC_MESSAGES/django.mo index 2c9f6a0c3f46..e4a2a95501d3 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 125c4f22ba8f..af7598aa14eb 100644 --- a/django/contrib/admin/locale/dsb/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/dsb/LC_MESSAGES/django.po @@ -1,13 +1,13 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Michael Wolf , 2016 +# Michael Wolf , 2016-2019 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-06-12 16:37+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-02-19 10:13+0000\n" "Last-Translator: Michael Wolf \n" "Language-Team: Lower Sorbian (http://www.transifex.com/django/django/" "language/dsb/)\n" @@ -87,6 +87,15 @@ msgstr "Dalšne %(verbose_name)s pśidaś" msgid "Remove" msgstr "Wótpóraś" +msgid "Addition" +msgstr "Pśidanje" + +msgid "Change" +msgstr "Změniś" + +msgid "Deletion" +msgstr "Wulašowanje" + msgid "action time" msgstr "akciski cas" @@ -100,7 +109,7 @@ msgid "object id" msgstr "objektowy 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 "objektowa reprezentacija" @@ -164,11 +173,11 @@ msgid "" msgstr "´Źaržćo „ctrl“ abo „cmd“ na Mac tłocony, aby wusej jadnogo wubrał." #, 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ś." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "{name} \"{obj}\" jo se wuspěšnje pśidał." + +msgid "You may edit it again below." +msgstr "Móźośo dołojce znowego wobźěłaś." #, python-brace-format msgid "" @@ -177,10 +186,6 @@ msgid "" msgstr "" "{name} \"{obj}\" jo se wuspěšnje pśidał. Móžośo dołojce dalšne {name} pśidaś." -#, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "{name} \"{obj}\" jo se wuspěšnje pśidał." - #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may edit it again below." @@ -188,6 +193,13 @@ 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} " @@ -214,8 +226,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" jo se wuspěšnje wulašował." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Objekt %(name)s z primarnym klucom %(key)r njeeksistěrujo." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s z ID \" %(key)s\" njeeksistěrujo. Jo se snaź wulašowało?" #, python-format msgid "Add %s" @@ -225,6 +237,10 @@ msgstr "%s pśidaś" msgid "Change %s" msgstr "%s změniś" +#, python-format +msgid "View %s" +msgstr "%s pokazaś" + msgid "Database error" msgstr "Zmólka datoweje banki" @@ -337,7 +353,7 @@ msgid "Change password" msgstr "Gronidło změniś" msgid "Please correct the error below." -msgstr "Pšosym skorigěrujśo slědujucu zmólku." +msgstr "Pšosym korigěrujśo slědujucu zmólku." msgid "Please correct the errors below." msgstr "Pšosym skorigěrujśo slědujuce zmólki." @@ -446,8 +462,8 @@ msgstr "" "Cośo napšawdu wubrany %(objects_name)s lašowaś? Wšykne slědujuce objekty a " "jich pśisłušne zapiski se wulašuju:" -msgid "Change" -msgstr "Změniś" +msgid "View" +msgstr "Pokazaś" msgid "Delete?" msgstr "Lašowaś?" @@ -466,8 +482,8 @@ msgstr "Modele w nałoženju %(name)s" msgid "Add" msgstr "Pśidaś" -msgid "You don't have permission to edit anything." -msgstr "Njejsćo pšawo něco wobźěłowaś." +msgid "You don't have permission to view or edit anything." +msgstr "Njamaśo pšawo něco pokazaś abo wobźěłaś" msgid "Recent actions" msgstr "Nejnowše akcije" @@ -523,20 +539,8 @@ msgstr "Wšykne pokazaś" msgid "Save" msgstr "Składowaś" -msgid "Popup closing..." -msgstr "Wuskokujuce wokno se zacynja..." - -#, python-format -msgid "Change selected %(model)s" -msgstr "Wubrane %(model)s změniś" - -#, python-format -msgid "Add another %(model)s" -msgstr "Dalšny %(model)s pśidaś" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Wubrane %(model)s lašowaś" +msgid "Popup closing…" +msgstr "Wuskokujuce wokno se zacynja…" msgid "Search" msgstr "Pytaś" @@ -562,6 +566,24 @@ msgstr "Składowaś a dalšny pśidaś" msgid "Save and continue editing" msgstr "Składowaś a dalej wobźěłowaś" +msgid "Save and view" +msgstr "Składowaś a pokazaś" + +msgid "Close" +msgstr "Zacyniś" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Wubrane %(model)s změniś" + +#, python-format +msgid "Add another %(model)s" +msgstr "Dalšny %(model)s pśidaś" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Wubrane %(model)s lašowaś" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Źěkujomy se, až sćo źinsa wěsty cas na websedle pśebywał." @@ -673,6 +695,10 @@ msgstr "%s wubraś" msgid "Select %s to change" msgstr "%s wubraś, aby se změniło" +#, python-format +msgid "Select %s to view" +msgstr "%s wubraś, kótaryž ma se pokazaś" + msgid "Date:" msgstr "Datum:" diff --git a/django/contrib/admin/locale/dsb/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/dsb/LC_MESSAGES/djangojs.mo index 7735d9f3608d..185749c4a913 100644 Binary files a/django/contrib/admin/locale/dsb/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/dsb/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/dsb/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/dsb/LC_MESSAGES/djangojs.po index 6da4c57ca3b3..3dbda729bd68 100644 --- a/django/contrib/admin/locale/dsb/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/dsb/LC_MESSAGES/djangojs.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-06-12 13:24+0000\n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-23 00:02+0000\n" "Last-Translator: Michael Wolf \n" "Language-Team: Lower Sorbian (http://www.transifex.com/django/django/" "language/dsb/)\n" @@ -102,6 +102,21 @@ msgstr "" "Sćo akciju wubrał, ale njejsćo jadnotliwe póla změnił. Nejskerjej pytaśo " "skerjej za tłocaškom Start ako za tłocaškom Składowaś." +msgid "Now" +msgstr "Něnto" + +msgid "Midnight" +msgstr "Połnoc" + +msgid "6 a.m." +msgstr "6:00 góź. dopołdnja" + +msgid "Noon" +msgstr "Połdnjo" + +msgid "6 p.m." +msgstr "6:00 wótpołdnja" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." @@ -118,27 +133,12 @@ msgstr[1] "Glědajśo: Waš cas jo wó %s góźinje za serwerowym casom." msgstr[2] "Glědajśo: Waš cas jo wó %s góźiny za serwerowym casom." msgstr[3] "Glědajśo: Waš cas jo wó %s góźin za serwerowym casom." -msgid "Now" -msgstr "Něnto" - msgid "Choose a Time" msgstr "Wubjeŕśo cas" msgid "Choose a time" msgstr "Wubjeŕśo cas" -msgid "Midnight" -msgstr "Połnoc" - -msgid "6 a.m." -msgstr "6:00 góź. dopołdnja" - -msgid "Noon" -msgstr "Połdnjo" - -msgid "6 p.m." -msgstr "6:00 wótpołdnja" - msgid "Cancel" msgstr "Pśetergnuś" diff --git a/django/contrib/admin/locale/el/LC_MESSAGES/django.mo b/django/contrib/admin/locale/el/LC_MESSAGES/django.mo index 30f6ac519a7c..0ae1e1650972 100644 Binary files a/django/contrib/admin/locale/el/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/el/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/el/LC_MESSAGES/django.po b/django/contrib/admin/locale/el/LC_MESSAGES/django.po index 5e32ed20934f..1574e80751a5 100644 --- a/django/contrib/admin/locale/el/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/el/LC_MESSAGES/django.po @@ -4,16 +4,17 @@ # Dimitris Glezos , 2011 # Giannis Meletakis , 2015 # Jannis Leidel , 2011 +# Nick Mavrakis , 2017-2018 # Nick Mavrakis , 2016 # Pãnoș , 2014 -# Pãnoș , 2016 +# Pãnoș , 2016,2019 # Yorgos Pagles , 2011-2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-07-20 19:46+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-25 19:38+0000\n" "Last-Translator: Pãnoș \n" "Language-Team: Greek (http://www.transifex.com/django/django/language/el/)\n" "MIME-Version: 1.0\n" @@ -92,6 +93,15 @@ msgstr "Προσθήκη και άλλου %(verbose_name)s" msgid "Remove" msgstr "Αφαίρεση" +msgid "Addition" +msgstr "Προσθήκη" + +msgid "Change" +msgstr "Αλλαγή" + +msgid "Deletion" +msgstr "Διαγραφή" + msgid "action time" msgstr "ώρα ενέργειας" @@ -105,7 +115,7 @@ msgid "object id" msgstr "ταυτότητα αντικειμένου" #. 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 "αναπαράσταση αντικειμένου" @@ -171,11 +181,11 @@ msgstr "" "επιλέξετε παραπάνω από ένα." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" -"Το {name} \"{obj}\" προστέθηκε με επιτυχία. Μπορείτε να το επεξεργαστείτε " -"πάλι παρακάτω." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "Το {name} \"{obj}\" αποθηκεύτηκε με επιτυχία." + +msgid "You may edit it again below." +msgstr "Μπορείτε να το επεξεργαστείτε ξανά παρακάτω." #, python-brace-format msgid "" @@ -185,10 +195,6 @@ msgstr "" "Το {name} \"{obj}\" προστέθηκε με επιτυχία. Μπορείτε να προσθέσετε και άλλο " "{name} παρακάτω." -#, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "Το {name} \"{obj}\" αποθηκεύτηκε με επιτυχία." - #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may edit it again below." @@ -196,6 +202,13 @@ 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} " @@ -224,8 +237,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "Το %(name)s \"%(obj)s\" διαγράφηκε με επιτυχία." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr " Το αντικείμενο %(name)s με πρωτεύον κλειδί %(key)r δεν βρέθηκε." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s με το ID \"%(key)s\" δεν υπάρχει. Μήπως διαγράφηκε;" #, python-format msgid "Add %s" @@ -235,6 +248,10 @@ msgstr "Προσθήκη %s" msgid "Change %s" msgstr "Αλλαγή του %s" +#, python-format +msgid "View %s" +msgstr "Προβολή %s" + msgid "Database error" msgstr "Σφάλμα βάσεως δεδομένων" @@ -460,8 +477,8 @@ msgstr "" "Αν προχωρήσετε με την διαγραφή όλα τα παρακάτω συσχετισμένα αντικείμενα θα " "διαγραφούν επίσης:" -msgid "Change" -msgstr "Αλλαγή" +msgid "View" +msgstr "Προβολή" msgid "Delete?" msgstr "Διαγραφή;" @@ -480,8 +497,8 @@ msgstr "Μοντέλα στην εφαρμογή %(name)s" msgid "Add" msgstr "Προσθήκη" -msgid "You don't have permission to edit anything." -msgstr "Δεν έχετε δικαίωμα να επεξεργαστείτε τίποτα." +msgid "You don't have permission to view or edit anything." +msgstr "Δεν έχετε δικαίωμα να δείτε ή να επεξεργαστείτε τίποτα." msgid "Recent actions" msgstr "Πρόσφατες ενέργειες" @@ -537,21 +554,9 @@ msgstr "Εμφάνιση όλων" msgid "Save" msgstr "Αποθήκευση" -msgid "Popup closing..." +msgid "Popup closing…" msgstr "Κλείσιμο popup..." -#, 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 "Search" msgstr "Αναζήτηση" @@ -574,6 +579,24 @@ msgstr "Αποθήκευση και προσθήκη καινούριου" msgid "Save and continue editing" 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" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Ευχαριστούμε που διαθέσατε κάποιο ποιοτικό χρόνο στον ιστότοπο σήμερα." @@ -695,6 +718,10 @@ msgstr "Επιλέξτε %s" msgid "Select %s to change" msgstr "Επιλέξτε %s προς αλλαγή" +#, python-format +msgid "Select %s to view" +msgstr "Επιλέξτε %s για προβολή" + msgid "Date:" msgstr "Ημ/νία:" diff --git a/django/contrib/admin/locale/el/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/el/LC_MESSAGES/djangojs.mo index 26dd3c205419..5da3e88499ed 100644 Binary files a/django/contrib/admin/locale/el/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/el/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/el/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/el/LC_MESSAGES/djangojs.po index 4061f7da981a..223eccb88816 100644 --- a/django/contrib/admin/locale/el/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/el/LC_MESSAGES/djangojs.po @@ -13,7 +13,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-07-14 10:20+0000\n" +"PO-Revision-Date: 2017-09-23 19:47+0000\n" "Last-Translator: Nick Mavrakis \n" "Language-Team: Greek (http://www.transifex.com/django/django/language/el/)\n" "MIME-Version: 1.0\n" diff --git a/django/contrib/admin/locale/en/LC_MESSAGES/django.po b/django/contrib/admin/locale/en/LC_MESSAGES/django.po index 172c8224c3d6..99e47a85ff10 100644 --- a/django/contrib/admin/locale/en/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/en/LC_MESSAGES/django.po @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: Django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -12,321 +12,346 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: contrib/admin/actions.py:50 +#: contrib/admin/actions.py:41 #, python-format msgid "Successfully deleted %(count)d %(items)s." msgstr "" -#: contrib/admin/actions.py:62 contrib/admin/options.py:1707 +#: contrib/admin/actions.py:50 contrib/admin/options.py:1862 #, python-format msgid "Cannot delete %(name)s" msgstr "" -#: contrib/admin/actions.py:64 contrib/admin/options.py:1709 +#: contrib/admin/actions.py:52 contrib/admin/options.py:1864 msgid "Are you sure?" msgstr "" -#: contrib/admin/actions.py:89 +#: contrib/admin/actions.py:79 #, python-format msgid "Delete selected %(verbose_name_plural)s" msgstr "" -#: contrib/admin/apps.py:11 +#: contrib/admin/apps.py:12 msgid "Administration" msgstr "" -#: contrib/admin/filters.py:107 contrib/admin/filters.py:205 -#: contrib/admin/filters.py:241 contrib/admin/filters.py:278 -#: contrib/admin/filters.py:384 +#: contrib/admin/filters.py:108 contrib/admin/filters.py:207 +#: contrib/admin/filters.py:242 contrib/admin/filters.py:276 +#: contrib/admin/filters.py:395 msgid "All" msgstr "" -#: contrib/admin/filters.py:242 +#: contrib/admin/filters.py:243 msgid "Yes" msgstr "" -#: contrib/admin/filters.py:243 +#: contrib/admin/filters.py:244 msgid "No" msgstr "" -#: contrib/admin/filters.py:257 +#: contrib/admin/filters.py:254 msgid "Unknown" msgstr "" -#: contrib/admin/filters.py:316 +#: contrib/admin/filters.py:324 msgid "Any date" msgstr "" -#: contrib/admin/filters.py:317 +#: contrib/admin/filters.py:325 msgid "Today" msgstr "" -#: contrib/admin/filters.py:321 +#: contrib/admin/filters.py:329 msgid "Past 7 days" msgstr "" -#: contrib/admin/filters.py:325 +#: contrib/admin/filters.py:333 msgid "This month" msgstr "" -#: contrib/admin/filters.py:329 +#: contrib/admin/filters.py:337 msgid "This year" msgstr "" -#: contrib/admin/filters.py:359 +#: contrib/admin/filters.py:345 msgid "No date" msgstr "" -#: contrib/admin/filters.py:360 +#: contrib/admin/filters.py:346 msgid "Has date" msgstr "" -#: contrib/admin/forms.py:14 +#: contrib/admin/forms.py:13 #, python-format msgid "" "Please enter the correct %(username)s and password for a staff account. Note " "that both fields may be case-sensitive." msgstr "" -#: contrib/admin/helpers.py:27 +#: contrib/admin/helpers.py:21 msgid "Action:" msgstr "" -#: contrib/admin/helpers.py:286 +#: contrib/admin/helpers.py:307 #, python-format msgid "Add another %(verbose_name)s" msgstr "" -#: contrib/admin/helpers.py:289 +#: contrib/admin/helpers.py:310 msgid "Remove" msgstr "" -#: contrib/admin/models.py:39 +#: contrib/admin/models.py:17 +msgid "Addition" +msgstr "" + +#: contrib/admin/models.py:18 +#: contrib/admin/templates/admin/edit_inline/stacked.html:12 +#: contrib/admin/templates/admin/edit_inline/tabular.html:34 +#: contrib/admin/templates/admin/index.html:40 +#: contrib/admin/templates/admin/widgets/related_widget_wrapper.html:11 +msgid "Change" +msgstr "" + +#: contrib/admin/models.py:19 +msgid "Deletion" +msgstr "" + +#: contrib/admin/models.py:41 msgid "action time" msgstr "" -#: contrib/admin/models.py:46 +#: contrib/admin/models.py:48 msgid "user" msgstr "" -#: contrib/admin/models.py:51 +#: contrib/admin/models.py:53 msgid "content type" msgstr "" -#: contrib/admin/models.py:54 +#: contrib/admin/models.py:56 msgid "object id" msgstr "" -#. Translators: 'repr' means representation (https://docs.python.org/3/library/functions.html#repr) -#: contrib/admin/models.py:56 +#. Translators: 'repr' means representation (https://docs.python.org/library/functions.html#repr) +#: contrib/admin/models.py:58 msgid "object repr" msgstr "" -#: contrib/admin/models.py:57 +#: contrib/admin/models.py:59 msgid "action flag" msgstr "" -#: contrib/admin/models.py:59 +#: contrib/admin/models.py:61 msgid "change message" msgstr "" -#: contrib/admin/models.py:64 +#: contrib/admin/models.py:66 msgid "log entry" msgstr "" -#: contrib/admin/models.py:65 +#: contrib/admin/models.py:67 msgid "log entries" msgstr "" -#: contrib/admin/models.py:74 +#: contrib/admin/models.py:76 #, python-format msgid "Added \"%(object)s\"." msgstr "" -#: contrib/admin/models.py:76 +#: contrib/admin/models.py:78 #, python-format msgid "Changed \"%(object)s\" - %(changes)s" msgstr "" -#: contrib/admin/models.py:81 +#: contrib/admin/models.py:83 #, python-format msgid "Deleted \"%(object)s.\"" msgstr "" -#: contrib/admin/models.py:83 +#: contrib/admin/models.py:85 msgid "LogEntry Object" msgstr "" -#: contrib/admin/models.py:109 +#: contrib/admin/models.py:111 #, python-brace-format msgid "Added {name} \"{object}\"." msgstr "" -#: contrib/admin/models.py:111 +#: contrib/admin/models.py:113 msgid "Added." msgstr "" -#: contrib/admin/models.py:115 contrib/admin/options.py:1917 +#: contrib/admin/models.py:117 contrib/admin/options.py:2093 msgid "and" msgstr "" -#: contrib/admin/models.py:119 +#: contrib/admin/models.py:121 #, python-brace-format msgid "Changed {fields} for {name} \"{object}\"." msgstr "" -#: contrib/admin/models.py:123 +#: contrib/admin/models.py:125 #, python-brace-format msgid "Changed {fields}." msgstr "" -#: contrib/admin/models.py:127 +#: contrib/admin/models.py:129 #, python-brace-format msgid "Deleted {name} \"{object}\"." msgstr "" -#: contrib/admin/models.py:130 +#: contrib/admin/models.py:132 msgid "No fields changed." msgstr "" -#: contrib/admin/options.py:196 contrib/admin/options.py:225 +#: contrib/admin/options.py:204 contrib/admin/options.py:236 msgid "None" msgstr "" -#: contrib/admin/options.py:261 +#: contrib/admin/options.py:274 msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "" -#: contrib/admin/options.py:1115 contrib/admin/options.py:1186 +#: contrib/admin/options.py:1212 contrib/admin/options.py:1236 #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "" + +#: contrib/admin/options.py:1214 +msgid "You may edit it again below." msgstr "" -#: contrib/admin/options.py:1129 +#: contrib/admin/options.py:1226 #, python-brace-format msgid "" "The {name} \"{obj}\" was added successfully. You may add another {name} " "below." msgstr "" -#: contrib/admin/options.py:1139 +#: contrib/admin/options.py:1276 #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" -#: contrib/admin/options.py:1176 +#: contrib/admin/options.py:1286 #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." +"The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" -#: contrib/admin/options.py:1199 +#: contrib/admin/options.py:1299 #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " "below." msgstr "" -#: contrib/admin/options.py:1211 +#: contrib/admin/options.py:1311 #, python-brace-format msgid "The {name} \"{obj}\" was changed successfully." msgstr "" -#: contrib/admin/options.py:1296 contrib/admin/options.py:1564 +#: contrib/admin/options.py:1388 contrib/admin/options.py:1704 msgid "" "Items must be selected in order to perform actions on them. No items have " "been changed." msgstr "" -#: contrib/admin/options.py:1315 +#: contrib/admin/options.py:1407 msgid "No action selected." msgstr "" -#: contrib/admin/options.py:1336 +#: contrib/admin/options.py:1432 #, python-format msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "" -#: contrib/admin/options.py:1424 contrib/admin/options.py:1682 -#: contrib/admin/options.py:1738 +#: contrib/admin/options.py:1511 #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" msgstr "" -#: contrib/admin/options.py:1475 +#: contrib/admin/options.py:1599 #, python-format msgid "Add %s" msgstr "" -#: contrib/admin/options.py:1475 +#: contrib/admin/options.py:1601 #, python-format msgid "Change %s" msgstr "" -#: contrib/admin/options.py:1543 +#: contrib/admin/options.py:1603 +#, python-format +msgid "View %s" +msgstr "" + +#: contrib/admin/options.py:1682 msgid "Database error" msgstr "" -#: contrib/admin/options.py:1606 +#: contrib/admin/options.py:1751 #, python-format msgid "%(count)s %(name)s was changed successfully." msgid_plural "%(count)s %(name)s were changed successfully." msgstr[0] "" msgstr[1] "" -#: contrib/admin/options.py:1633 +#: contrib/admin/options.py:1782 #, python-format msgid "%(total_count)s selected" msgid_plural "All %(total_count)s selected" msgstr[0] "" msgstr[1] "" -#: contrib/admin/options.py:1639 +#: contrib/admin/options.py:1790 #, python-format msgid "0 of %(cnt)s selected" msgstr "" -#: contrib/admin/options.py:1755 +#: contrib/admin/options.py:1907 #, python-format msgid "Change history: %s" msgstr "" #. Translators: Model verbose name and instance representation, #. suitable to be an item in a list. -#: contrib/admin/options.py:1911 +#: contrib/admin/options.py:2086 #, python-format msgid "%(class_name)s %(instance)s" msgstr "" -#: contrib/admin/options.py:1918 +#: contrib/admin/options.py:2095 #, python-format msgid "" "Deleting %(class_name)s %(instance)s would require deleting the following " "protected related objects: %(related_objects)s" msgstr "" -#: contrib/admin/sites.py:40 contrib/admin/templates/admin/base_site.html:3 +#: contrib/admin/sites.py:41 contrib/admin/templates/admin/base_site.html:3 msgid "Django site admin" msgstr "" -#: contrib/admin/sites.py:43 contrib/admin/templates/admin/base_site.html:6 +#: contrib/admin/sites.py:44 contrib/admin/templates/admin/base_site.html:6 msgid "Django administration" msgstr "" -#: contrib/admin/sites.py:46 +#: contrib/admin/sites.py:47 msgid "Site administration" msgstr "" -#: contrib/admin/sites.py:398 contrib/admin/templates/admin/login.html.py:61 +#: contrib/admin/sites.py:384 contrib/admin/templates/admin/login.html:61 #: contrib/admin/templates/registration/password_reset_complete.html:18 -#: contrib/admin/tests.py:131 +#: contrib/admin/tests.py:123 msgid "Log in" msgstr "" -#: contrib/admin/sites.py:525 +#: contrib/admin/sites.py:513 #, python-format msgid "%(app)s administration" msgstr "" @@ -343,20 +368,20 @@ msgstr "" #: contrib/admin/templates/admin/500.html:6 #: contrib/admin/templates/admin/app_index.html:9 #: contrib/admin/templates/admin/auth/user/change_password.html:13 -#: contrib/admin/templates/admin/base.html:56 +#: contrib/admin/templates/admin/base.html:61 #: contrib/admin/templates/admin/change_form.html:18 #: contrib/admin/templates/admin/change_list.html:31 -#: contrib/admin/templates/admin/delete_confirmation.html:13 -#: contrib/admin/templates/admin/delete_selected_confirmation.html:13 +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#: contrib/admin/templates/admin/delete_selected_confirmation.html:14 #: contrib/admin/templates/admin/invalid_setup.html:6 #: contrib/admin/templates/admin/object_history.html:6 #: contrib/admin/templates/registration/logged_out.html:4 #: contrib/admin/templates/registration/password_change_done.html:6 #: contrib/admin/templates/registration/password_change_form.html:7 #: contrib/admin/templates/registration/password_reset_complete.html:6 -#: contrib/admin/templates/registration/password_reset_confirm.html:6 +#: contrib/admin/templates/registration/password_reset_confirm.html:7 #: contrib/admin/templates/registration/password_reset_done.html:6 -#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:7 msgid "Home" msgstr "" @@ -378,24 +403,24 @@ msgid "" "email and should be fixed shortly. Thanks for your patience." msgstr "" -#: contrib/admin/templates/admin/actions.html:4 +#: contrib/admin/templates/admin/actions.html:8 msgid "Run the selected action" msgstr "" -#: contrib/admin/templates/admin/actions.html:4 +#: contrib/admin/templates/admin/actions.html:8 msgid "Go" msgstr "" -#: contrib/admin/templates/admin/actions.html:10 +#: contrib/admin/templates/admin/actions.html:16 msgid "Click here to select the objects across all pages" msgstr "" -#: contrib/admin/templates/admin/actions.html:10 +#: contrib/admin/templates/admin/actions.html:16 #, python-format msgid "Select all %(total_count)s %(module_name)s" msgstr "" -#: contrib/admin/templates/admin/actions.html:12 +#: contrib/admin/templates/admin/actions.html:18 msgid "Clear selection" msgstr "" @@ -410,72 +435,72 @@ msgid "Enter a username and password." msgstr "" #: contrib/admin/templates/admin/auth/user/change_password.html:17 -#: contrib/admin/templates/admin/auth/user/change_password.html:54 -#: contrib/admin/templates/admin/base.html:44 +#: contrib/admin/templates/admin/auth/user/change_password.html:55 +#: contrib/admin/templates/admin/base.html:49 #: contrib/admin/templates/registration/password_change_done.html:3 #: contrib/admin/templates/registration/password_change_form.html:4 msgid "Change password" msgstr "" -#: contrib/admin/templates/admin/auth/user/change_password.html:27 -#: contrib/admin/templates/admin/change_form.html:47 -#: contrib/admin/templates/admin/change_list.html:58 +#: contrib/admin/templates/admin/auth/user/change_password.html:28 +#: contrib/admin/templates/admin/change_form.html:43 +#: contrib/admin/templates/admin/change_list.html:51 #: contrib/admin/templates/admin/login.html:21 #: contrib/admin/templates/registration/password_change_form.html:21 msgid "Please correct the error below." msgstr "" -#: contrib/admin/templates/admin/auth/user/change_password.html:27 -#: contrib/admin/templates/admin/change_form.html:47 -#: contrib/admin/templates/admin/change_list.html:58 +#: contrib/admin/templates/admin/auth/user/change_password.html:28 +#: contrib/admin/templates/admin/change_form.html:43 +#: contrib/admin/templates/admin/change_list.html:51 #: contrib/admin/templates/admin/login.html:21 #: contrib/admin/templates/registration/password_change_form.html:21 msgid "Please correct the errors below." msgstr "" -#: contrib/admin/templates/admin/auth/user/change_password.html:31 +#: contrib/admin/templates/admin/auth/user/change_password.html:32 #, python-format msgid "Enter a new password for the user %(username)s." msgstr "" -#: contrib/admin/templates/admin/base.html:30 +#: contrib/admin/templates/admin/base.html:35 msgid "Welcome," msgstr "" -#: contrib/admin/templates/admin/base.html:35 +#: contrib/admin/templates/admin/base.html:40 msgid "View site" msgstr "" -#: contrib/admin/templates/admin/base.html:40 +#: contrib/admin/templates/admin/base.html:45 #: contrib/admin/templates/registration/password_change_done.html:3 #: contrib/admin/templates/registration/password_change_form.html:4 msgid "Documentation" msgstr "" -#: contrib/admin/templates/admin/base.html:46 +#: contrib/admin/templates/admin/base.html:51 #: contrib/admin/templates/registration/password_change_done.html:3 #: contrib/admin/templates/registration/password_change_form.html:4 msgid "Log out" msgstr "" #: contrib/admin/templates/admin/change_form.html:21 -#: contrib/admin/templates/admin/change_list.html:49 +#: contrib/admin/templates/admin/change_list_object_tools.html:8 #, python-format msgid "Add %(name)s" msgstr "" -#: contrib/admin/templates/admin/change_form.html:33 +#: contrib/admin/templates/admin/change_form_object_tools.html:5 #: contrib/admin/templates/admin/object_history.html:10 msgid "History" msgstr "" -#: contrib/admin/templates/admin/change_form.html:35 +#: contrib/admin/templates/admin/change_form_object_tools.html:7 #: contrib/admin/templates/admin/edit_inline/stacked.html:14 #: contrib/admin/templates/admin/edit_inline/tabular.html:36 msgid "View on site" msgstr "" -#: contrib/admin/templates/admin/change_list.html:69 +#: contrib/admin/templates/admin/change_list.html:62 msgid "Filter" msgstr "" @@ -492,13 +517,13 @@ msgstr "" msgid "Toggle sorting" msgstr "" -#: contrib/admin/templates/admin/delete_confirmation.html:17 -#: contrib/admin/templates/admin/related_widget_wrapper.html:23 -#: contrib/admin/templates/admin/submit_line.html:6 +#: contrib/admin/templates/admin/delete_confirmation.html:18 +#: contrib/admin/templates/admin/submit_line.html:7 +#: contrib/admin/templates/admin/widgets/related_widget_wrapper.html:25 msgid "Delete" msgstr "" -#: contrib/admin/templates/admin/delete_confirmation.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:24 #, python-format msgid "" "Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " @@ -506,40 +531,40 @@ msgid "" "following types of objects:" msgstr "" -#: contrib/admin/templates/admin/delete_confirmation.html:30 +#: contrib/admin/templates/admin/delete_confirmation.html:31 #, python-format msgid "" "Deleting the %(object_name)s '%(escaped_object)s' would require deleting the " "following protected related objects:" msgstr "" -#: contrib/admin/templates/admin/delete_confirmation.html:37 +#: contrib/admin/templates/admin/delete_confirmation.html:38 #, python-format msgid "" "Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " "All of the following related items will be deleted:" msgstr "" -#: contrib/admin/templates/admin/delete_confirmation.html:39 -#: contrib/admin/templates/admin/delete_selected_confirmation.html:38 +#: contrib/admin/templates/admin/delete_confirmation.html:40 +#: contrib/admin/templates/admin/delete_selected_confirmation.html:39 msgid "Objects" msgstr "" -#: contrib/admin/templates/admin/delete_confirmation.html:46 -#: contrib/admin/templates/admin/delete_selected_confirmation.html:49 +#: contrib/admin/templates/admin/delete_confirmation.html:47 +#: contrib/admin/templates/admin/delete_selected_confirmation.html:50 msgid "Yes, I'm sure" msgstr "" -#: contrib/admin/templates/admin/delete_confirmation.html:47 -#: contrib/admin/templates/admin/delete_selected_confirmation.html:50 +#: contrib/admin/templates/admin/delete_confirmation.html:48 +#: contrib/admin/templates/admin/delete_selected_confirmation.html:51 msgid "No, take me back" msgstr "" -#: contrib/admin/templates/admin/delete_selected_confirmation.html:16 +#: contrib/admin/templates/admin/delete_selected_confirmation.html:17 msgid "Delete multiple objects" msgstr "" -#: contrib/admin/templates/admin/delete_selected_confirmation.html:22 +#: contrib/admin/templates/admin/delete_selected_confirmation.html:23 #, python-format msgid "" "Deleting the selected %(objects_name)s would result in deleting related " @@ -547,14 +572,14 @@ msgid "" "types of objects:" msgstr "" -#: contrib/admin/templates/admin/delete_selected_confirmation.html:29 +#: contrib/admin/templates/admin/delete_selected_confirmation.html:30 #, python-format msgid "" "Deleting the selected %(objects_name)s would require deleting the following " "protected related objects:" msgstr "" -#: contrib/admin/templates/admin/delete_selected_confirmation.html:36 +#: contrib/admin/templates/admin/delete_selected_confirmation.html:37 #, python-format msgid "" "Are you sure you want to delete the selected %(objects_name)s? All of the " @@ -563,9 +588,8 @@ msgstr "" #: contrib/admin/templates/admin/edit_inline/stacked.html:12 #: contrib/admin/templates/admin/edit_inline/tabular.html:34 -#: contrib/admin/templates/admin/index.html:37 -#: contrib/admin/templates/admin/related_widget_wrapper.html:9 -msgid "Change" +#: contrib/admin/templates/admin/index.html:38 +msgid "View" msgstr "" #: contrib/admin/templates/admin/edit_inline/tabular.html:20 @@ -587,27 +611,27 @@ msgid "Models in the %(name)s application" msgstr "" #: contrib/admin/templates/admin/index.html:31 -#: contrib/admin/templates/admin/related_widget_wrapper.html:16 +#: contrib/admin/templates/admin/widgets/related_widget_wrapper.html:18 msgid "Add" msgstr "" -#: contrib/admin/templates/admin/index.html:47 -msgid "You don't have permission to edit anything." +#: contrib/admin/templates/admin/index.html:51 +msgid "You don't have permission to view or edit anything." msgstr "" -#: contrib/admin/templates/admin/index.html:55 +#: contrib/admin/templates/admin/index.html:59 msgid "Recent actions" msgstr "" -#: contrib/admin/templates/admin/index.html:56 +#: contrib/admin/templates/admin/index.html:60 msgid "My actions" msgstr "" -#: contrib/admin/templates/admin/index.html:60 +#: contrib/admin/templates/admin/index.html:64 msgid "None available" msgstr "" -#: contrib/admin/templates/admin/index.html:74 +#: contrib/admin/templates/admin/index.html:78 msgid "Unknown content" msgstr "" @@ -653,27 +677,12 @@ msgid "Show all" msgstr "" #: contrib/admin/templates/admin/pagination.html:11 -#: contrib/admin/templates/admin/submit_line.html:3 +#: contrib/admin/templates/admin/submit_line.html:4 msgid "Save" msgstr "" #: contrib/admin/templates/admin/popup_response.html:3 -msgid "Popup closing..." -msgstr "" - -#: contrib/admin/templates/admin/related_widget_wrapper.html:8 -#, python-format -msgid "Change selected %(model)s" -msgstr "" - -#: contrib/admin/templates/admin/related_widget_wrapper.html:15 -#, python-format -msgid "Add another %(model)s" -msgstr "" - -#: contrib/admin/templates/admin/related_widget_wrapper.html:22 -#, python-format -msgid "Delete selected %(model)s" +msgid "Popup closing…" msgstr "" #: contrib/admin/templates/admin/search_form.html:7 @@ -692,18 +701,41 @@ msgstr[1] "" msgid "%(full_result_count)s total" msgstr "" -#: contrib/admin/templates/admin/submit_line.html:8 +#: contrib/admin/templates/admin/submit_line.html:9 msgid "Save as new" msgstr "" -#: contrib/admin/templates/admin/submit_line.html:9 +#: contrib/admin/templates/admin/submit_line.html:10 msgid "Save and add another" msgstr "" -#: contrib/admin/templates/admin/submit_line.html:10 +#: contrib/admin/templates/admin/submit_line.html:11 msgid "Save and continue editing" msgstr "" +#: contrib/admin/templates/admin/submit_line.html:11 +msgid "Save and view" +msgstr "" + +#: contrib/admin/templates/admin/submit_line.html:12 +msgid "Close" +msgstr "" + +#: contrib/admin/templates/admin/widgets/related_widget_wrapper.html:10 +#, python-format +msgid "Change selected %(model)s" +msgstr "" + +#: contrib/admin/templates/admin/widgets/related_widget_wrapper.html:17 +#, python-format +msgid "Add another %(model)s" +msgstr "" + +#: contrib/admin/templates/admin/widgets/related_widget_wrapper.html:24 +#, python-format +msgid "Delete selected %(model)s" +msgstr "" + #: contrib/admin/templates/registration/logged_out.html:8 msgid "Thanks for spending some quality time with the Web site today." msgstr "" @@ -728,13 +760,13 @@ msgid "" msgstr "" #: contrib/admin/templates/registration/password_change_form.html:54 -#: contrib/admin/templates/registration/password_reset_confirm.html:24 +#: contrib/admin/templates/registration/password_reset_confirm.html:32 msgid "Change my password" msgstr "" #: contrib/admin/templates/registration/password_reset_complete.html:7 #: contrib/admin/templates/registration/password_reset_done.html:7 -#: contrib/admin/templates/registration/password_reset_form.html:7 +#: contrib/admin/templates/registration/password_reset_form.html:8 msgid "Password reset" msgstr "" @@ -742,25 +774,25 @@ msgstr "" msgid "Your password has been set. You may go ahead and log in now." msgstr "" -#: contrib/admin/templates/registration/password_reset_confirm.html:7 +#: contrib/admin/templates/registration/password_reset_confirm.html:8 msgid "Password reset confirmation" msgstr "" -#: contrib/admin/templates/registration/password_reset_confirm.html:17 +#: contrib/admin/templates/registration/password_reset_confirm.html:18 msgid "" "Please enter your new password twice so we can verify you typed it in " "correctly." msgstr "" -#: contrib/admin/templates/registration/password_reset_confirm.html:21 +#: contrib/admin/templates/registration/password_reset_confirm.html:24 msgid "New password:" msgstr "" -#: contrib/admin/templates/registration/password_reset_confirm.html:23 +#: contrib/admin/templates/registration/password_reset_confirm.html:29 msgid "Confirm password:" msgstr "" -#: contrib/admin/templates/registration/password_reset_confirm.html:29 +#: contrib/admin/templates/registration/password_reset_confirm.html:38 msgid "" "The password reset link was invalid, possibly because it has already been " "used. Please request a new password reset." @@ -802,50 +834,55 @@ msgstr "" msgid "The %(site_name)s team" msgstr "" -#: contrib/admin/templates/registration/password_reset_form.html:15 +#: contrib/admin/templates/registration/password_reset_form.html:16 msgid "" "Forgotten your password? Enter your email address below, and we'll email " "instructions for setting a new one." msgstr "" -#: contrib/admin/templates/registration/password_reset_form.html:19 +#: contrib/admin/templates/registration/password_reset_form.html:22 msgid "Email address:" msgstr "" -#: contrib/admin/templates/registration/password_reset_form.html:19 +#: contrib/admin/templates/registration/password_reset_form.html:25 msgid "Reset my password" msgstr "" -#: contrib/admin/templatetags/admin_list.py:387 +#: contrib/admin/templatetags/admin_list.py:409 msgid "All dates" msgstr "" -#: contrib/admin/views/main.py:81 +#: contrib/admin/views/main.py:84 #, python-format msgid "Select %s" msgstr "" -#: contrib/admin/views/main.py:83 +#: contrib/admin/views/main.py:86 #, python-format msgid "Select %s to change" msgstr "" -#: contrib/admin/widgets.py:92 +#: contrib/admin/views/main.py:88 +#, python-format +msgid "Select %s to view" +msgstr "" + +#: contrib/admin/widgets.py:91 msgid "Date:" msgstr "" -#: contrib/admin/widgets.py:93 +#: contrib/admin/widgets.py:92 msgid "Time:" msgstr "" -#: contrib/admin/widgets.py:175 +#: contrib/admin/widgets.py:154 msgid "Lookup" msgstr "" -#: contrib/admin/widgets.py:363 +#: contrib/admin/widgets.py:338 msgid "Currently:" msgstr "" -#: contrib/admin/widgets.py:364 +#: contrib/admin/widgets.py:339 msgid "Change:" msgstr "" diff --git a/django/contrib/admin/locale/en/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/en/LC_MESSAGES/djangojs.po index 0e51c8402239..2b335c932556 100644 --- a/django/contrib/admin/locale/en/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/en/LC_MESSAGES/djangojs.po @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: Django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -72,21 +72,21 @@ msgstr "" msgid "Click to remove all chosen %s at once." msgstr "" -#: contrib/admin/static/admin/js/actions.js:47 +#: contrib/admin/static/admin/js/actions.js:48 #: contrib/admin/static/admin/js/actions.min.js:2 msgid "%(sel)s of %(cnt)s selected" msgid_plural "%(sel)s of %(cnt)s selected" msgstr[0] "" msgstr[1] "" -#: contrib/admin/static/admin/js/actions.js:116 -#: contrib/admin/static/admin/js/actions.min.js:4 +#: contrib/admin/static/admin/js/actions.js:117 +#: contrib/admin/static/admin/js/actions.min.js:5 msgid "" "You have unsaved changes on individual editable fields. If you run an " "action, your unsaved changes will be lost." msgstr "" -#: contrib/admin/static/admin/js/actions.js:128 +#: contrib/admin/static/admin/js/actions.js:129 #: contrib/admin/static/admin/js/actions.min.js:5 msgid "" "You have selected an action, but you haven't saved your changes to " @@ -94,76 +94,76 @@ msgid "" "action." msgstr "" -#: contrib/admin/static/admin/js/actions.js:130 -#: contrib/admin/static/admin/js/actions.min.js:5 +#: contrib/admin/static/admin/js/actions.js:131 +#: contrib/admin/static/admin/js/actions.min.js:6 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 "" -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:74 +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:13 +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:118 +msgid "Now" +msgstr "" + +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:14 +msgid "Midnight" +msgstr "" + +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:15 +msgid "6 a.m." +msgstr "" + +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:16 +msgid "Noon" +msgstr "" + +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:17 +msgid "6 p.m." +msgstr "" + +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:83 #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." msgstr[0] "" msgstr[1] "" -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:82 +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:91 #, javascript-format msgid "Note: You are %s hour behind server time." msgid_plural "Note: You are %s hours behind server time." msgstr[0] "" msgstr[1] "" -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:109 -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:149 -msgid "Now" -msgstr "" - -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:116 +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:136 msgid "Choose a Time" msgstr "" -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:146 +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:166 msgid "Choose a time" msgstr "" -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:150 -msgid "Midnight" -msgstr "" - -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:151 -msgid "6 a.m." -msgstr "" - -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:152 -msgid "Noon" -msgstr "" - -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:153 -msgid "6 p.m." -msgstr "" - -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:157 -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:281 +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:183 +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:343 msgid "Cancel" msgstr "" -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:217 -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:274 +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:248 +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:328 msgid "Today" msgstr "" -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:224 +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:265 msgid "Choose a Date" msgstr "" -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:272 +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:322 msgid "Yesterday" msgstr "" -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:276 +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:334 msgid "Tomorrow" msgstr "" @@ -252,11 +252,12 @@ msgstr "" #: contrib/admin/static/admin/js/collapse.js:10 #: contrib/admin/static/admin/js/collapse.js:21 -#: contrib/admin/static/admin/js/collapse.min.js:1 +#: contrib/admin/static/admin/js/collapse.min.js:4 +#: contrib/admin/static/admin/js/collapse.min.js:5 msgid "Show" msgstr "" #: contrib/admin/static/admin/js/collapse.js:18 -#: contrib/admin/static/admin/js/collapse.min.js:1 +#: contrib/admin/static/admin/js/collapse.min.js:4 msgid "Hide" msgstr "" diff --git a/django/contrib/admin/locale/en_AU/LC_MESSAGES/django.mo b/django/contrib/admin/locale/en_AU/LC_MESSAGES/django.mo index f8e7bc51ff0e..a19397e2ffb8 100644 Binary files a/django/contrib/admin/locale/en_AU/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/en_AU/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/en_AU/LC_MESSAGES/django.po b/django/contrib/admin/locale/en_AU/LC_MESSAGES/django.po index 3f7f90b120be..111eb3817724 100644 --- a/django/contrib/admin/locale/en_AU/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/en_AU/LC_MESSAGES/django.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-19 21:09+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: English (Australia) (http://www.transifex.com/django/django/" "language/en_AU/)\n" @@ -207,8 +207,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "" #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(name)s object with primary key %(key)r does not exist." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" diff --git a/django/contrib/admin/locale/en_AU/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/en_AU/LC_MESSAGES/djangojs.mo index 9a62a8a13382..775077fa0e93 100644 Binary files a/django/contrib/admin/locale/en_AU/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/en_AU/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/en_AU/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/en_AU/LC_MESSAGES/djangojs.po index 1c183d636968..fe991ffac808 100644 --- a/django/contrib/admin/locale/en_AU/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/en_AU/LC_MESSAGES/djangojs.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:10+0000\n" +"PO-Revision-Date: 2017-09-19 21:09+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: English (Australia) (http://www.transifex.com/django/django/" "language/en_AU/)\n" diff --git a/django/contrib/admin/locale/en_GB/LC_MESSAGES/django.mo b/django/contrib/admin/locale/en_GB/LC_MESSAGES/django.mo index 8f76ffe36c9d..b20f7bd18c6a 100644 Binary files a/django/contrib/admin/locale/en_GB/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/en_GB/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/en_GB/LC_MESSAGES/django.po b/django/contrib/admin/locale/en_GB/LC_MESSAGES/django.po index 3898b86fe502..167a0dbadcc7 100644 --- a/django/contrib/admin/locale/en_GB/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/en_GB/LC_MESSAGES/django.po @@ -1,15 +1,16 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Adam Forster , 2019 # jon_atkinson , 2011-2012 # Ross Poulton , 2011-2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-04-05 10:37+0000\n" +"Last-Translator: Adam Forster \n" "Language-Team: English (United Kingdom) (http://www.transifex.com/django/" "django/language/en_GB/)\n" "MIME-Version: 1.0\n" @@ -34,7 +35,7 @@ msgid "Delete selected %(verbose_name_plural)s" msgstr "Delete selected %(verbose_name_plural)s" msgid "Administration" -msgstr "" +msgstr "Administration" msgid "All" msgstr "All" @@ -64,16 +65,18 @@ msgid "This year" msgstr "This year" msgid "No date" -msgstr "" +msgstr "No date" msgid "Has date" -msgstr "" +msgstr "Has date" #, python-format msgid "" "Please enter the correct %(username)s and password for a staff account. Note " "that both fields may be case-sensitive." msgstr "" +"Please enter the correct %(username)s and password for a staff account. Note " +"that both fields may be case-sensitive." msgid "Action:" msgstr "Action:" @@ -85,20 +88,29 @@ msgstr "Add another %(verbose_name)s" msgid "Remove" msgstr "Remove" +msgid "Addition" +msgstr "Addition" + +msgid "Change" +msgstr "Change" + +msgid "Deletion" +msgstr "Deletion" + msgid "action time" msgstr "action time" msgid "user" -msgstr "" +msgstr "user" msgid "content type" -msgstr "" +msgstr "content type" msgid "object id" msgstr "object 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 "object repr" @@ -131,10 +143,10 @@ msgstr "LogEntry Object" #, python-brace-format msgid "Added {name} \"{object}\"." -msgstr "" +msgstr "Added {name} \"{object}\"." msgid "Added." -msgstr "" +msgstr "Added." msgid "and" msgstr "and" @@ -162,8 +174,10 @@ msgid "" msgstr "" #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "" + +msgid "You may edit it again below." msgstr "" #, python-brace-format @@ -173,12 +187,13 @@ msgid "" msgstr "" #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." +"The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" #, python-brace-format @@ -206,8 +221,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "The %(name)s \"%(obj)s\" was deleted successfully." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(name)s object with primary key %(key)r does not exist." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" @@ -217,6 +232,10 @@ msgstr "Add %s" msgid "Change %s" msgstr "Change %s" +#, python-format +msgid "View %s" +msgstr "" + msgid "Database error" msgstr "Database error" @@ -321,7 +340,7 @@ msgid "Change password" msgstr "Change password" msgid "Please correct the error below." -msgstr "Please correct the errors below." +msgstr "" msgid "Please correct the errors below." msgstr "" @@ -432,8 +451,8 @@ msgstr "" "Are you sure you want to delete the selected %(objects_name)s? All of the " "following objects and their related items will be deleted:" -msgid "Change" -msgstr "Change" +msgid "View" +msgstr "" msgid "Delete?" msgstr "Delete?" @@ -452,8 +471,8 @@ msgstr "" msgid "Add" msgstr "Add" -msgid "You don't have permission to edit anything." -msgstr "You don't have permission to edit anything." +msgid "You don't have permission to view or edit anything." +msgstr "" msgid "Recent actions" msgstr "" @@ -507,19 +526,7 @@ msgstr "Show all" msgid "Save" msgstr "Save" -msgid "Popup closing..." -msgstr "" - -#, python-format -msgid "Change selected %(model)s" -msgstr "" - -#, python-format -msgid "Add another %(model)s" -msgstr "" - -#, python-format -msgid "Delete selected %(model)s" +msgid "Popup closing…" msgstr "" msgid "Search" @@ -544,6 +551,24 @@ msgstr "Save and add another" msgid "Save and continue editing" msgstr "Save and continue editing" +msgid "Save and view" +msgstr "" + +msgid "Close" +msgstr "" + +#, python-format +msgid "Change selected %(model)s" +msgstr "" + +#, python-format +msgid "Add another %(model)s" +msgstr "" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Thanks for spending some quality time with the Web site today." @@ -646,6 +671,10 @@ msgstr "Select %s" msgid "Select %s to change" msgstr "Select %s to change" +#, python-format +msgid "Select %s to view" +msgstr "" + msgid "Date:" msgstr "Date:" diff --git a/django/contrib/admin/locale/en_GB/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/en_GB/LC_MESSAGES/djangojs.mo index 96f7f244503b..0967a3893dd5 100644 Binary files a/django/contrib/admin/locale/en_GB/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/en_GB/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/en_GB/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/en_GB/LC_MESSAGES/djangojs.po index 582c56354fb2..03cf67991d44 100644 --- a/django/contrib/admin/locale/en_GB/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/en_GB/LC_MESSAGES/djangojs.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: English (United Kingdom) (http://www.transifex.com/django/" "django/language/en_GB/)\n" diff --git a/django/contrib/admin/locale/eo/LC_MESSAGES/django.mo b/django/contrib/admin/locale/eo/LC_MESSAGES/django.mo index 5aff0679201e..b61dbe6af2a3 100644 Binary files a/django/contrib/admin/locale/eo/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/eo/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/eo/LC_MESSAGES/django.po b/django/contrib/admin/locale/eo/LC_MESSAGES/django.po index f425de19d281..ffab5e1e580d 100644 --- a/django/contrib/admin/locale/eo/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/eo/LC_MESSAGES/django.po @@ -2,16 +2,18 @@ # # Translators: # Baptiste Darthenay , 2012-2013 -# Baptiste Darthenay , 2013-2016 +# Baptiste Darthenay , 2013-2019 +# Claude Paroz , 2016 # Dinu Gherman , 2011 # kristjan , 2012 +# Nikolay Korotkiy , 2017 # Adamo Mesha , 2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-09-28 11:25+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 12:48+0000\n" "Last-Translator: Baptiste Darthenay \n" "Language-Team: Esperanto (http://www.transifex.com/django/django/language/" "eo/)\n" @@ -90,6 +92,15 @@ msgstr "Aldoni alian %(verbose_name)sn" msgid "Remove" msgstr "Forigu" +msgid "Addition" +msgstr "Aldono" + +msgid "Change" +msgstr "Ŝanĝi" + +msgid "Deletion" +msgstr "Forviŝo" + msgid "action time" msgstr "aga tempo" @@ -103,7 +114,7 @@ msgid "object id" msgstr "objekta identigaĵo" #. 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 "objekta prezento" @@ -168,11 +179,11 @@ msgstr "" "Premadu la stirklavon, aŭ Komando-klavon ĉe Mac, por elekti pli ol unu." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" -"La {name} \"{obj}\" estis aldonita sukcese. Vi rajtas ĝin redakti denove " -"sube." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "La {name} \"{obj}\" estis aldonita sukcese." + +msgid "You may edit it again below." +msgstr "Eblas redakti ĝin sube." #, python-brace-format msgid "" @@ -182,16 +193,19 @@ msgstr "" "La {name} \"{obj}\" estis sukcese aldonita. Vi povas sube aldoni alian {name}" "n." -#, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "La {name} \"{obj}\" estis aldonita sukcese." - #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" "La {name} \"{obj}\" estis sukcese ŝanĝita. Vi povas sube redakti ĝin denove." +#, python-brace-format +msgid "" +"The {name} \"{obj}\" was added successfully. You may edit it again below." +msgstr "" +"La {name} \"{obj}\" estis aldonita sukcese. Vi rajtas ĝin redakti denove " +"sube." + #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " @@ -219,8 +233,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "La %(name)s \"%(obj)s\" estis forigita sukcese." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(name)s objekto kun ĉefŝlosilo %(key)r ne ekzistas." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s kun ID \"%(key)s\" ne ekzistas. Eble tio estis forigita?" #, python-format msgid "Add %s" @@ -230,6 +244,10 @@ msgstr "Aldoni %sn" msgid "Change %s" msgstr "Ŝanĝi %s" +#, python-format +msgid "View %s" +msgstr "Vidi %sn" + msgid "Database error" msgstr "Datumbaza eraro" @@ -338,7 +356,7 @@ msgid "Change password" msgstr "Ŝanĝi pasvorton" msgid "Please correct the error below." -msgstr "Bonvolu ĝustigi la erarojn sube." +msgstr "Bonvolu ĝustigi la eraron sube." msgid "Please correct the errors below." msgstr "Bonvolu ĝustigi la erarojn sube." @@ -448,8 +466,8 @@ msgstr "" "Ĉu vi certas, ke vi volas forigi la elektitajn %(objects_name)s? Ĉiuj el la " "sekvaj objektoj kaj iliaj rilataj eroj estos forigita:" -msgid "Change" -msgstr "Ŝanĝi" +msgid "View" +msgstr "Vidi" msgid "Delete?" msgstr "Forviŝi?" @@ -468,8 +486,8 @@ msgstr "Modeloj en la %(name)s aplikaĵo" msgid "Add" msgstr "Aldoni" -msgid "You don't have permission to edit anything." -msgstr "Vi ne havas permeson por redakti ĉion ajn." +msgid "You don't have permission to view or edit anything." +msgstr "Vi havas nenian permeson por vidi aŭ redakti." msgid "Recent actions" msgstr "Lastaj agoj" @@ -525,20 +543,8 @@ msgstr "Montri ĉion" msgid "Save" msgstr "Konservi" -msgid "Popup closing..." -msgstr "Ŝprucfenestro fermante…" - -#, python-format -msgid "Change selected %(model)s" -msgstr "Redaktu elektitan %(model)sn" - -#, python-format -msgid "Add another %(model)s" -msgstr "Aldoni alian %(model)sn" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Forigi elektitan %(model)sn" +msgid "Popup closing…" +msgstr "Ŝprucfenesto fermiĝas…" msgid "Search" msgstr "Serĉu" @@ -562,6 +568,24 @@ msgstr "Konservi kaj aldoni alian" msgid "Save and continue editing" msgstr "Konservi kaj daŭre redakti" +msgid "Save and view" +msgstr "Konservi kaj vidi" + +msgid "Close" +msgstr "Fermi" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Redaktu elektitan %(model)sn" + +#, python-format +msgid "Add another %(model)s" +msgstr "Aldoni alian %(model)sn" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Forigi elektitan %(model)sn" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Dankon pro pasigo de kvalita tempon kun la retejo hodiaŭ." @@ -617,15 +641,16 @@ 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 "" -"Ni retpoŝte sendis al vi instrukciojn por agordi la pasvorton, se konto " -"ekzistas, al la retpoŝto kiun vi sendis. Vi baldaŭ devus ĝin ricevi." +"Ni retpoŝte sendis al vi instrukciojn por agordi la pasvorton, se la " +"koncerna konto ekzistas, al la retpoŝta adreso kiun vi sendis. Vi baldaŭ " +"devus ĝin ricevi." 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 "" -"Se vi ne ricevas retpoŝton, bonvolu certigi vin eniris la adreson kun kiu vi " -"registris, kaj kontroli vian spaman dosierujon." +"Se vi ne ricevas retpoŝton, bonvolu certigi ke vi metis la adreson per kiu " +"vi registris, kaj kontroli vian spaman dosierujon." #, python-format msgid "" @@ -672,6 +697,10 @@ msgstr "Elekti %sn" msgid "Select %s to change" msgstr "Elekti %sn por ŝanĝi" +#, python-format +msgid "Select %s to view" +msgstr "Elektu %sn por vidi" + msgid "Date:" msgstr "Dato:" diff --git a/django/contrib/admin/locale/eo/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/eo/LC_MESSAGES/djangojs.mo index 9b66d830519e..9b6aa8f21ec0 100644 Binary files a/django/contrib/admin/locale/eo/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/eo/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/eo/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/eo/LC_MESSAGES/djangojs.po index 9b13d0cd6660..f101319a4c42 100644 --- a/django/contrib/admin/locale/eo/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/eo/LC_MESSAGES/djangojs.po @@ -9,8 +9,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-09-28 11:14+0000\n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Baptiste Darthenay \n" "Language-Team: Esperanto (http://www.transifex.com/django/django/language/" "eo/)\n" @@ -101,6 +101,21 @@ msgstr "" "Vi elektas agon, kaj vi ne faris ajnajn ŝanĝojn ĉe unuopaj kampoj. Vi " "verŝajne serĉas la Iru-butonon prefere ol la Ŝirmu-butono." +msgid "Now" +msgstr "Nun" + +msgid "Midnight" +msgstr "Noktomezo" + +msgid "6 a.m." +msgstr "6 a.t.m." + +msgid "Noon" +msgstr "Tagmezo" + +msgid "6 p.m." +msgstr "6 ptm" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." @@ -113,27 +128,12 @@ msgid_plural "Note: You are %s hours behind server time." msgstr[0] "Noto: Vi estas %s horo post la servila horo." msgstr[1] "Noto: Vi estas %s horoj post la servila horo." -msgid "Now" -msgstr "Nun" - msgid "Choose a Time" msgstr "Elektu horon" msgid "Choose a time" msgstr "Elektu tempon" -msgid "Midnight" -msgstr "Noktomezo" - -msgid "6 a.m." -msgstr "6 a.t.m." - -msgid "Noon" -msgstr "Tagmezo" - -msgid "6 p.m." -msgstr "6 ptm" - msgid "Cancel" msgstr "Malmendu" diff --git a/django/contrib/admin/locale/es/LC_MESSAGES/django.mo b/django/contrib/admin/locale/es/LC_MESSAGES/django.mo index 85f3320bd8c9..f4c502ec187e 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 c8e764a28599..949e20400fc8 100644 --- a/django/contrib/admin/locale/es/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/es/LC_MESSAGES/django.po @@ -7,21 +7,24 @@ # Ernesto Avilés Vázquez , 2015-2016 # franchukelly , 2011 # guillem , 2012 +# Ignacio José Lizarán Rus , 2019 # Igor Támara , 2013 # Jannis Leidel , 2011 -# Jorge Puente-Sarrín , 2014-2015 -# Yusuf (Josè) Luis , 2016 +# Jorge Puente Sarrín , 2014-2015 +# José Luis , 2016 # Josue Naaman Nistal Guerra , 2014 +# Luigy, 2019 # Marc Garcia , 2011 +# Miguel Angel Tribaldos , 2017 # Pablo, 2015 # Veronicabh , 2015 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-08-03 15:38+0000\n" -"Last-Translator: Ernesto Avilés Vázquez \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-02-19 08:44+0000\n" +"Last-Translator: Ignacio José Lizarán Rus \n" "Language-Team: Spanish (http://www.transifex.com/django/django/language/" "es/)\n" "MIME-Version: 1.0\n" @@ -99,6 +102,15 @@ msgstr "Agregar %(verbose_name)s adicional." msgid "Remove" msgstr "Eliminar" +msgid "Addition" +msgstr "Añadido" + +msgid "Change" +msgstr "Modificar" + +msgid "Deletion" +msgstr "Borrado" + msgid "action time" msgstr "hora de la acción" @@ -112,7 +124,7 @@ msgid "object id" msgstr "id del objeto" #. 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 del objeto" @@ -174,15 +186,15 @@ msgstr "Ninguno" msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "" -"Mantenga presionado \"Control\" o \"Command\" en un Mac, para seleccionar " +"Mantenga presionado \"Control\", o \"Command\" en un Mac, para seleccionar " "más de una opción." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" -"Se añadió con éxito el {name} \"{obj}\". Puede editarlo otra vez a " -"continuación." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "Se añadió con éxito el {name} \"{obj}\"." + +msgid "You may edit it again below." +msgstr "Puede volverlo a editar otra vez a continuación." #, python-brace-format msgid "" @@ -193,23 +205,30 @@ msgstr "" "continuación." #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "Se añadió con éxito el {name} \"{obj}\"." +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." +msgstr "" +"Se modificó con éxito el {name} \"{obj}\". Puede editarlo otra vez a " +"continuación." #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." +"The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" +"Se añadió con éxito el {name} \"{obj}\". Puede editarlo otra vez a " +"continuación." #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " "below." msgstr "" +"Se modificó con éxito el {name} \"{obj}\". Puede añadir otro {name} a " +"continuación." #, python-brace-format msgid "The {name} \"{obj}\" was changed successfully." -msgstr "" +msgstr "Se modificó con éxito el {name} \"{obj}\"." msgid "" "Items must be selected in order to perform actions on them. No items have " @@ -226,8 +245,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "Se eliminó con éxito el %(name)s \"%(obj)s\"." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "No existe ningún objeto %(name)s con la clave primaria %(key)r." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s con ID \"%(key)s\" no existe. ¿Fue quizá eliminado?" #, python-format msgid "Add %s" @@ -237,6 +256,10 @@ msgstr "Añadir %s" msgid "Change %s" msgstr "Modificar %s" +#, python-format +msgid "View %s" +msgstr "Vistas %s" + msgid "Database error" msgstr "Error en la base de datos" @@ -346,7 +369,7 @@ msgid "Change password" msgstr "Cambiar contraseña" msgid "Please correct the error below." -msgstr "Por favor, corrija los siguientes errores." +msgstr "Por Favor corrija el siguiente error." msgid "Please correct the errors below." msgstr "Por favor, corrija los siguientes errores." @@ -459,8 +482,8 @@ msgstr "" "¿Está usted seguro que quiere eliminar el %(objects_name)s seleccionado? " "Todos los siguientes objetos y sus elementos relacionados serán borrados:" -msgid "Change" -msgstr "Modificar" +msgid "View" +msgstr "Vista" msgid "Delete?" msgstr "¿Eliminar?" @@ -479,8 +502,8 @@ msgstr "Modelos en la aplicación %(name)s" msgid "Add" msgstr "Añadir" -msgid "You don't have permission to edit anything." -msgstr "No tiene permiso para editar nada." +msgid "You don't have permission to view or edit anything." +msgstr "No tiene permisos para ver o editar nada" msgid "Recent actions" msgstr "Acciones recientes" @@ -536,21 +559,9 @@ msgstr "Mostrar todo" msgid "Save" msgstr "Grabar" -msgid "Popup closing..." +msgid "Popup closing…" msgstr "Cerrando ventana emergente..." -#, python-format -msgid "Change selected %(model)s" -msgstr "Cambiar %(model)s seleccionado" - -#, python-format -msgid "Add another %(model)s" -msgstr "Añadir otro %(model)s" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Eliminar %(model)s seleccionada/o" - msgid "Search" msgstr "Buscar" @@ -573,6 +584,24 @@ msgstr "Grabar y añadir otro" msgid "Save and continue editing" msgstr "Grabar y continuar editando" +msgid "Save and view" +msgstr "Guardar y ver" + +msgid "Close" +msgstr "Cerrar" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Cambiar %(model)s seleccionado" + +#, python-format +msgid "Add another %(model)s" +msgstr "Añadir otro %(model)s" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Eliminar %(model)s seleccionada/o" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Gracias por el tiempo que ha dedicado hoy al sitio web." @@ -689,6 +718,10 @@ msgstr "Escoja %s" msgid "Select %s to change" msgstr "Escoja %s a modificar" +#, python-format +msgid "Select %s to view" +msgstr "Seleccione %s para ver" + msgid "Date:" msgstr "Fecha:" diff --git a/django/contrib/admin/locale/es/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/es/LC_MESSAGES/djangojs.mo index e143ae5bfe89..a953b628cfc2 100644 Binary files a/django/contrib/admin/locale/es/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/es/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/es/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/es/LC_MESSAGES/djangojs.po index 20a382bda4f3..6ac954a2d3fb 100644 --- a/django/contrib/admin/locale/es/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/es/LC_MESSAGES/djangojs.po @@ -12,7 +12,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-08-03 15:39+0000\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Ernesto Avilés Vázquez \n" "Language-Team: Spanish (http://www.transifex.com/django/django/language/" "es/)\n" 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 a761c7f6b6e9..27fc83a28281 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 af34ad1d1fae..1a2d9267787d 100644 --- a/django/contrib/admin/locale/es_AR/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/es_AR/LC_MESSAGES/django.po @@ -3,13 +3,13 @@ # Translators: # Jannis Leidel , 2011 # Leonardo José Guzmán , 2013 -# Ramiro Morales, 2013-2016 +# Ramiro Morales, 2013-2019 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-08-02 03:26+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-03-20 14:06+0000\n" "Last-Translator: Ramiro Morales\n" "Language-Team: Spanish (Argentina) (http://www.transifex.com/django/django/" "language/es_AR/)\n" @@ -89,6 +89,15 @@ msgstr "Agregar otro/a %(verbose_name)s" msgid "Remove" msgstr "Eliminar" +msgid "Addition" +msgstr "Agregado" + +msgid "Change" +msgstr "Modificar" + +msgid "Deletion" +msgstr "Borrado" + msgid "action time" msgstr "hora de la acción" @@ -102,7 +111,7 @@ msgid "object id" msgstr "id de objeto" #. 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 de objeto" @@ -149,7 +158,7 @@ msgstr "Se modifican {fields} en {name} \"{object}\"." #, python-brace-format msgid "Changed {fields}." -msgstr "Se modifican {fields}." +msgstr "Modificación de {fields}." #, python-brace-format msgid "Deleted {name} \"{object}\"." @@ -168,41 +177,47 @@ msgstr "" "más de uno." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" +msgid "The {name} \"{obj}\" was added successfully." +msgstr "Se agregó con éxito {name} \"{obj}\"." + +msgid "You may edit it again below." +msgstr "Puede modificarlo/a nuevamente mas abajo." #, python-brace-format msgid "" "The {name} \"{obj}\" was added successfully. You may add another {name} " "below." msgstr "" +"Se agregó con éxito {name} \"{obj}\". Puede agregar otro/a {name} abajo." #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" +"Se modificó con éxito {name} \"{obj}\". Puede modificarlo/a nuevamente abajo." #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." -msgstr "" +"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} " "below." msgstr "" +"Se modificó con éxito {name} \"{obj}\". Puede agregar otro {name} abajo." #, python-brace-format msgid "The {name} \"{obj}\" was changed successfully." -msgstr "" +msgstr "Se modificó con éxito {name} \"{obj}\"." msgid "" "Items must be selected in order to perform actions on them. No items have " "been changed." msgstr "" -"Deben existir items seleccionados para poder realizar acciones sobre los " -"mismos. No se modificó ningún item." +"Deben existir ítems seleccionados para poder realizar acciones sobre los " +"mismos. No se modificó ningún ítem." msgid "No action selected." msgstr "No se ha seleccionado ninguna acción." @@ -212,8 +227,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "Se eliminó con éxito %(name)s \"%(obj)s\"." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "No existe un objeto %(name)s con una clave primaria %(key)r." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "No existe %(name)s con ID \"%(key)s\". ¿Quizá fue eliminado/a?" #, python-format msgid "Add %s" @@ -223,6 +238,10 @@ msgstr "Agregar %s" msgid "Change %s" msgstr "Modificar %s" +#, python-format +msgid "View %s" +msgstr "Ver %s" + msgid "Database error" msgstr "Error de base de datos" @@ -332,7 +351,7 @@ msgid "Change password" msgstr "Cambiar contraseña" msgid "Please correct the error below." -msgstr "Por favor, corrija los siguientes errores." +msgstr "Por favor, corrija el error detallado mas abajo." msgid "Please correct the errors below." msgstr "Por favor corrija los errores detallados abajo." @@ -444,11 +463,11 @@ msgid "" "following objects and their related items will be deleted:" msgstr "" "¿Está seguro de que desea eliminar el/los objetos %(objects_name)s?. Todos " -"los siguientes objetos e items relacionados a los mismos también serán " +"los siguientes objetos e ítems relacionados a los mismos también serán " "eliminados:" -msgid "Change" -msgstr "Modificar" +msgid "View" +msgstr "Ver" msgid "Delete?" msgstr "¿Eliminar?" @@ -467,8 +486,8 @@ msgstr "Modelos en la aplicación %(name)s" msgid "Add" msgstr "Agregar" -msgid "You don't have permission to edit anything." -msgstr "No tiene permiso para editar nada." +msgid "You don't have permission to view or edit anything." +msgstr "No tiene permiso para ver o modificar nada." msgid "Recent actions" msgstr "Acciones recientes" @@ -524,20 +543,8 @@ msgstr "Mostrar todos/as" msgid "Save" msgstr "Guardar" -msgid "Popup closing..." -msgstr "Cerrando ventana emergente..." - -#, python-format -msgid "Change selected %(model)s" -msgstr "Modificar %(model)s seleccionados/as" - -#, python-format -msgid "Add another %(model)s" -msgstr "Agregar otro/a %(model)s" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Eliminar %(model)s seleccionados/as" +msgid "Popup closing…" +msgstr "Cerrando ventana amergente…" msgid "Search" msgstr "Buscar" @@ -561,6 +568,24 @@ msgstr "Guardar y agregar otro" msgid "Save and continue editing" msgstr "Guardar y continuar editando" +msgid "Save and view" +msgstr "Guardar y ver" + +msgid "Close" +msgstr "Cerrar" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Modificar %(model)s seleccionados/as" + +#, python-format +msgid "Add another %(model)s" +msgstr "Agregar otro/a %(model)s" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Eliminar %(model)s seleccionados/as" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Gracias por el tiempo que ha dedicado al sitio web hoy." @@ -676,6 +701,10 @@ msgstr "Seleccione %s" msgid "Select %s to change" msgstr "Seleccione %s a modificar" +#, python-format +msgid "Select %s to view" +msgstr "Seleccione %s que desea ver" + msgid "Date:" msgstr "Fecha:" diff --git a/django/contrib/admin/locale/es_AR/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/es_AR/LC_MESSAGES/djangojs.mo index c444b984cadc..daf4fb3e01a6 100644 Binary files a/django/contrib/admin/locale/es_AR/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/es_AR/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/es_AR/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/es_AR/LC_MESSAGES/djangojs.po index 373c12f75d66..ed9155d51b98 100644 --- a/django/contrib/admin/locale/es_AR/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/es_AR/LC_MESSAGES/djangojs.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-07-30 23:55+0000\n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-23 18:54+0000\n" "Last-Translator: Ramiro Morales\n" "Language-Team: Spanish (Argentina) (http://www.transifex.com/django/django/" "language/es_AR/)\n" @@ -101,6 +101,21 @@ msgstr "" "campos individuales. Es probable que lo que necesite usar en realidad sea el " "botón Ejecutar y no el botón Guardar." +msgid "Now" +msgstr "Ahora" + +msgid "Midnight" +msgstr "Medianoche" + +msgid "6 a.m." +msgstr "6 AM" + +msgid "Noon" +msgstr "Mediodía" + +msgid "6 p.m." +msgstr "6 PM" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." @@ -121,27 +136,12 @@ msgstr[1] "" "Nota: Ud. se encuentra en una zona horaria que está %s horas atrasada " "respecto a la del servidor." -msgid "Now" -msgstr "Ahora" - msgid "Choose a Time" msgstr "Seleccione una Hora" msgid "Choose a time" msgstr "Elija una hora" -msgid "Midnight" -msgstr "Medianoche" - -msgid "6 a.m." -msgstr "6 AM" - -msgid "Noon" -msgstr "Mediodía" - -msgid "6 p.m." -msgstr "6 PM" - msgid "Cancel" msgstr "Cancelar" diff --git a/django/contrib/admin/locale/es_CO/LC_MESSAGES/django.mo b/django/contrib/admin/locale/es_CO/LC_MESSAGES/django.mo index 4e3187df41c2..f806074309e2 100644 Binary files a/django/contrib/admin/locale/es_CO/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/es_CO/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/es_CO/LC_MESSAGES/django.po b/django/contrib/admin/locale/es_CO/LC_MESSAGES/django.po index 5e03c9b83365..5831fbfa8f57 100644 --- a/django/contrib/admin/locale/es_CO/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/es_CO/LC_MESSAGES/django.po @@ -17,8 +17,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-19 19:11+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Spanish (Colombia) (http://www.transifex.com/django/django/" "language/es_CO/)\n" @@ -220,8 +220,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "Se eliminó con éxito el %(name)s \"%(obj)s\"." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "No existe ningún objeto %(name)s con la clave primaria %(key)r." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" diff --git a/django/contrib/admin/locale/es_CO/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/es_CO/LC_MESSAGES/djangojs.mo index b398451ac807..3d428a045b0f 100644 Binary files a/django/contrib/admin/locale/es_CO/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/es_CO/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/es_CO/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/es_CO/LC_MESSAGES/djangojs.po index d227169cc215..4bcc1cccc29c 100644 --- a/django/contrib/admin/locale/es_CO/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/es_CO/LC_MESSAGES/djangojs.po @@ -11,7 +11,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" +"PO-Revision-Date: 2017-09-20 03:01+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Spanish (Colombia) (http://www.transifex.com/django/django/" "language/es_CO/)\n" diff --git a/django/contrib/admin/locale/es_MX/LC_MESSAGES/django.mo b/django/contrib/admin/locale/es_MX/LC_MESSAGES/django.mo index fededd89a1b4..43e182482730 100644 Binary files a/django/contrib/admin/locale/es_MX/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/es_MX/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/es_MX/LC_MESSAGES/django.po b/django/contrib/admin/locale/es_MX/LC_MESSAGES/django.po index 5eb172d8c77b..1a424f203506 100644 --- a/django/contrib/admin/locale/es_MX/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/es_MX/LC_MESSAGES/django.po @@ -1,14 +1,14 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Abraham Estrada , 2011-2013 +# Abraham Estrada, 2011-2013 # Alex Dzul , 2015 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Spanish (Mexico) (http://www.transifex.com/django/django/" "language/es_MX/)\n" @@ -211,8 +211,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "Se eliminó con éxito %(name)s \"%(obj)s\"." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "No existe un objeto %(name)s con una clave primaria %(key)r." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" diff --git a/django/contrib/admin/locale/es_MX/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/es_MX/LC_MESSAGES/djangojs.mo index 2c81a1c098f2..fbd765aecdb5 100644 Binary files a/django/contrib/admin/locale/es_MX/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/es_MX/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/es_MX/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/es_MX/LC_MESSAGES/djangojs.po index 1ca2989fe27c..76af2f30e01e 100644 --- a/django/contrib/admin/locale/es_MX/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/es_MX/LC_MESSAGES/djangojs.po @@ -1,13 +1,13 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Abraham Estrada , 2011-2012 +# Abraham Estrada, 2011-2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Spanish (Mexico) (http://www.transifex.com/django/django/" "language/es_MX/)\n" diff --git a/django/contrib/admin/locale/es_VE/LC_MESSAGES/django.mo b/django/contrib/admin/locale/es_VE/LC_MESSAGES/django.mo index 07076f36e6d5..ab04e3f34e64 100644 Binary files a/django/contrib/admin/locale/es_VE/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/es_VE/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/es_VE/LC_MESSAGES/django.po b/django/contrib/admin/locale/es_VE/LC_MESSAGES/django.po index 2bd79e01e908..c9e1509bdffe 100644 --- a/django/contrib/admin/locale/es_VE/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/es_VE/LC_MESSAGES/django.po @@ -1,15 +1,17 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Eduardo , 2017 # Hotellook, 2014 # Leonardo J. Caballero G. , 2016 +# Yoel Acevedo, 2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-19 19:11+0000\n" +"Last-Translator: Eduardo \n" "Language-Team: Spanish (Venezuela) (http://www.transifex.com/django/django/" "language/es_VE/)\n" "MIME-Version: 1.0\n" @@ -64,10 +66,10 @@ msgid "This year" msgstr "Este año" msgid "No date" -msgstr "" +msgstr "Sin fecha" msgid "Has date" -msgstr "" +msgstr "Tiene fecha" #, python-format msgid "" @@ -133,7 +135,7 @@ msgstr "Objeto LogEntry" #, python-brace-format msgid "Added {name} \"{object}\"." -msgstr "" +msgstr "Agregado {name} \"{object}\"." msgid "Added." msgstr "Añadido." @@ -143,15 +145,15 @@ msgstr "y" #, python-brace-format msgid "Changed {fields} for {name} \"{object}\"." -msgstr "" +msgstr "Modificado {fields} por {name} \"{object}\"." #, python-brace-format msgid "Changed {fields}." -msgstr "" +msgstr "Modificado {fields}." #, python-brace-format msgid "Deleted {name} \"{object}\"." -msgstr "" +msgstr "Eliminado {name} \"{object}\"." msgid "No fields changed." msgstr "No ha cambiado ningún campo." @@ -169,31 +171,39 @@ msgstr "" msgid "" "The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" +"El {name} \"{obj}\" fue agregado satisfactoriamente. Puede editarlo " +"nuevamente a continuación. " #, python-brace-format msgid "" "The {name} \"{obj}\" was added successfully. You may add another {name} " "below." msgstr "" +"El {name} \"{obj}\" fue agregado satisfactoriamente. Puede agregar otro " +"{name} a continuación. " #, python-brace-format msgid "The {name} \"{obj}\" was added successfully." -msgstr "" +msgstr "El {name} \"{obj}\" fue cambiado satisfactoriamente." #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" +"El {name} \"{obj}\" fue cambiado satisfactoriamente. Puede editarlo " +"nuevamente a continuación. " #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " "below." msgstr "" +"El {name} \"{obj}\" fue cambiado satisfactoriamente. Puede agregar otro " +"{name} a continuación." #, python-brace-format msgid "The {name} \"{obj}\" was changed successfully." -msgstr "" +msgstr "El {name} \"{obj}\" fue cambiado satisfactoriamente." msgid "" "Items must be selected in order to perform actions on them. No items have " @@ -210,8 +220,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "Se eliminó con éxito el %(name)s \"%(obj)s\"." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "No existe ningún objeto %(name)s con la clave primaria %(key)r." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s con ID \"%(key)s\" no existe. ¿Tal vez fue eliminada?" #, python-format msgid "Add %s" @@ -466,10 +476,10 @@ msgid "You don't have permission to edit anything." msgstr "No tiene permiso para editar nada." msgid "Recent actions" -msgstr "" +msgstr "Acciones recientes" msgid "My actions" -msgstr "" +msgstr "Mis acciones" msgid "None available" msgstr "Ninguno disponible" diff --git a/django/contrib/admin/locale/es_VE/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/es_VE/LC_MESSAGES/djangojs.mo index 543e40a2e329..6cc051982971 100644 Binary files a/django/contrib/admin/locale/es_VE/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/es_VE/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/es_VE/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/es_VE/LC_MESSAGES/djangojs.po index 65aa92260a4c..1ab4dcd2a5db 100644 --- a/django/contrib/admin/locale/es_VE/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/es_VE/LC_MESSAGES/djangojs.po @@ -1,6 +1,7 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Eduardo , 2017 # FIRST AUTHOR , 2012 # Hotellook, 2014 # Leonardo J. Caballero G. , 2016 @@ -9,8 +10,8 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" -"Last-Translator: Jannis Leidel \n" +"PO-Revision-Date: 2017-09-20 03:01+0000\n" +"Last-Translator: Eduardo \n" "Language-Team: Spanish (Venezuela) (http://www.transifex.com/django/django/" "language/es_VE/)\n" "MIME-Version: 1.0\n" @@ -151,68 +152,68 @@ msgid "Tomorrow" msgstr "Mañana" msgid "January" -msgstr "" +msgstr "Enero" msgid "February" -msgstr "" +msgstr "Febrero" msgid "March" -msgstr "" +msgstr "Marzo" msgid "April" -msgstr "" +msgstr "Abril" msgid "May" -msgstr "" +msgstr "Mayo" msgid "June" -msgstr "" +msgstr "Junio" msgid "July" -msgstr "" +msgstr "Julio" msgid "August" -msgstr "" +msgstr "Agosto" msgid "September" -msgstr "" +msgstr "Septiembre" msgid "October" -msgstr "" +msgstr "Octubre" msgid "November" -msgstr "" +msgstr "Noviembre" msgid "December" -msgstr "" +msgstr "Diciembre" msgctxt "one letter Sunday" msgid "S" -msgstr "" +msgstr "D" msgctxt "one letter Monday" msgid "M" -msgstr "" +msgstr "L" msgctxt "one letter Tuesday" msgid "T" -msgstr "" +msgstr "M" msgctxt "one letter Wednesday" msgid "W" -msgstr "" +msgstr "M" msgctxt "one letter Thursday" msgid "T" -msgstr "" +msgstr "J" msgctxt "one letter Friday" msgid "F" -msgstr "" +msgstr "V" msgctxt "one letter Saturday" msgid "S" -msgstr "" +msgstr "S" msgid "Show" msgstr "Mostrar" diff --git a/django/contrib/admin/locale/et/LC_MESSAGES/django.mo b/django/contrib/admin/locale/et/LC_MESSAGES/django.mo index 3e2be98cbcac..3af4426f1bb4 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 b6c888a8c5c3..a9674165d12a 100644 --- a/django/contrib/admin/locale/et/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/et/LC_MESSAGES/django.po @@ -5,15 +5,15 @@ # Jannis Leidel , 2011 # Janno Liivak , 2013-2015 # Martin Pajuste , 2015 -# Martin Pajuste , 2016 +# Martin Pajuste , 2016,2019 # Marti Raudsepp , 2016 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-07-18 21:25+0000\n" -"Last-Translator: Marti Raudsepp \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 16:25+0000\n" +"Last-Translator: Martin Pajuste \n" "Language-Team: Estonian (http://www.transifex.com/django/django/language/" "et/)\n" "MIME-Version: 1.0\n" @@ -91,6 +91,15 @@ msgstr "Lisa veel üks %(verbose_name)s" msgid "Remove" msgstr "Eemalda" +msgid "Addition" +msgstr "" + +msgid "Change" +msgstr "Muuda" + +msgid "Deletion" +msgstr "" + msgid "action time" msgstr "toimingu aeg" @@ -104,7 +113,7 @@ msgid "object id" msgstr "objekti 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 "objekti esitus" @@ -168,9 +177,11 @@ msgid "" msgstr "Et valida mitu, hoidke all \"Control\"-nuppu (Maci puhul \"Command\")." #, 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." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "{name} \"{obj}\" lisamine õnnestus." + +msgid "You may edit it again below." +msgstr "" #, python-brace-format msgid "" @@ -178,15 +189,16 @@ msgid "" "below." msgstr "{name} \"{obj}\" lisamine õnnestus. Allpool saate lisada uue {name}." -#, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "{name} \"{obj}\" lisamine õnnestus." - #, python-brace-format 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} " @@ -212,8 +224,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" kustutati." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(name)s objekt primaarvõtmega %(key)r ei eksisteeri." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" @@ -223,6 +235,10 @@ msgstr "Lisa %s" msgid "Change %s" msgstr "Muuda %s" +#, python-format +msgid "View %s" +msgstr "" + msgid "Database error" msgstr "Andmebaasi viga" @@ -331,7 +347,7 @@ msgid "Change password" msgstr "Muuda salasõna" msgid "Please correct the error below." -msgstr "Palun parandage allolevad vead" +msgstr "Palun parandage allolev viga." msgid "Please correct the errors below." msgstr "Palun parandage allolevad vead." @@ -442,8 +458,8 @@ msgstr "" "Kas oled kindel, et soovid kustutada valitud %(objects_name)s? Kõik " "järgnevad objektid ja seotud objektid kustutatakse:" -msgid "Change" -msgstr "Muuda" +msgid "View" +msgstr "" msgid "Delete?" msgstr "Kustutan?" @@ -462,8 +478,8 @@ msgstr "Rakenduse %(name)s moodulid" msgid "Add" msgstr "Lisa" -msgid "You don't have permission to edit anything." -msgstr "Teil ei ole õigust midagi muuta." +msgid "You don't have permission to view or edit anything." +msgstr "" msgid "Recent actions" msgstr "Hiljutised toimingud" @@ -519,20 +535,8 @@ msgstr "Näita kõiki" msgid "Save" msgstr "Salvesta" -msgid "Popup closing..." -msgstr "Hüpikaken sulgub..." - -#, python-format -msgid "Change selected %(model)s" -msgstr "Muuda valitud %(model)s" - -#, python-format -msgid "Add another %(model)s" -msgstr "Lisa veel üks %(model)s" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Kustuta valitud %(model)s" +msgid "Popup closing…" +msgstr "" msgid "Search" msgstr "Otsing" @@ -556,6 +560,24 @@ msgstr "Salvesta ja lisa uus" msgid "Save and continue editing" msgstr "Salvesta ja jätka muutmist" +msgid "Save and view" +msgstr "" + +msgid "Close" +msgstr "" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Muuda valitud %(model)s" + +#, python-format +msgid "Add another %(model)s" +msgstr "Lisa veel üks %(model)s" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Kustuta valitud %(model)s" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Tänan, et veetsite aega meie lehel." @@ -667,6 +689,10 @@ msgstr "Vali %s" msgid "Select %s to change" msgstr "Vali %s mida muuta" +#, python-format +msgid "Select %s to view" +msgstr "" + msgid "Date:" msgstr "Kuupäev:" diff --git a/django/contrib/admin/locale/et/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/et/LC_MESSAGES/djangojs.mo index 10fc758a27a9..9b3fafbc139e 100644 Binary files a/django/contrib/admin/locale/et/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/et/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/et/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/et/LC_MESSAGES/djangojs.po index 51313dd544fa..ae6713fb751e 100644 --- a/django/contrib/admin/locale/et/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/et/LC_MESSAGES/djangojs.po @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 11:01+0000\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Martin Pajuste \n" "Language-Team: Estonian (http://www.transifex.com/django/django/language/" "et/)\n" diff --git a/django/contrib/admin/locale/eu/LC_MESSAGES/django.mo b/django/contrib/admin/locale/eu/LC_MESSAGES/django.mo index 61058f57a8df..e3c840f91661 100644 Binary files a/django/contrib/admin/locale/eu/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/eu/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/eu/LC_MESSAGES/django.po b/django/contrib/admin/locale/eu/LC_MESSAGES/django.po index b2b213c60d9d..9176368484fd 100644 --- a/django/contrib/admin/locale/eu/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/eu/LC_MESSAGES/django.po @@ -1,17 +1,19 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Aitzol Naberan , 2013 +# Aitzol Naberan , 2013,2016 +# Eneko Illarramendi , 2017-2019 # Jannis Leidel , 2011 -# julen , 2012-2013 -# julen , 2013 +# julen, 2012-2013 +# julen, 2013 +# Urtzi Odriozola , 2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-22 09:57+0000\n" +"Last-Translator: Eneko Illarramendi \n" "Language-Team: Basque (http://www.transifex.com/django/django/language/eu/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -28,14 +30,14 @@ msgid "Cannot delete %(name)s" msgstr "Ezin da %(name)s ezabatu" msgid "Are you sure?" -msgstr "Ziur zaude?" +msgstr "Ziur al zaude?" #, python-format msgid "Delete selected %(verbose_name_plural)s" msgstr "Ezabatu aukeratutako %(verbose_name_plural)s" msgid "Administration" -msgstr "" +msgstr "Kudeaketa" msgid "All" msgstr "Dena" @@ -65,10 +67,10 @@ msgid "This year" msgstr "Urte hau" msgid "No date" -msgstr "" +msgstr "Datarik ez" msgid "Has date" -msgstr "" +msgstr "Data dauka" #, python-format msgid "" @@ -88,22 +90,31 @@ msgstr "Gehitu beste %(verbose_name)s bat" msgid "Remove" msgstr "Kendu" +msgid "Addition" +msgstr "Gehitzea" + +msgid "Change" +msgstr "Aldatu" + +msgid "Deletion" +msgstr "Ezabatzea" + msgid "action time" msgstr "Ekintza hordua" msgid "user" -msgstr "" +msgstr "erabiltzailea" msgid "content type" -msgstr "" +msgstr "eduki mota" msgid "object id" -msgstr "Objetuaren id-a" +msgstr "objetuaren id-a" #. 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 "Objeturaren aurkezpena" +msgstr "objeturaren adierazpena" msgid "action flag" msgstr "Ekintza botoia" @@ -130,29 +141,29 @@ msgid "Deleted \"%(object)s.\"" msgstr "\"%(object)s\" ezabatuta." msgid "LogEntry Object" -msgstr "LogEntry objektua" +msgstr "LogEntry objetua" #, python-brace-format msgid "Added {name} \"{object}\"." -msgstr "" +msgstr "{name} \"{object}\" gehitu." msgid "Added." -msgstr "" +msgstr "Gehituta" msgid "and" msgstr "eta" #, python-brace-format msgid "Changed {fields} for {name} \"{object}\"." -msgstr "" +msgstr "{fields}-(e)tik {name} \"{object}\" aldatatuta." #, python-brace-format msgid "Changed {fields}." -msgstr "" +msgstr "{fields} aldatuta." #, python-brace-format msgid "Deleted {name} \"{object}\"." -msgstr "" +msgstr "{name} \"{object}\" ezabatuta." msgid "No fields changed." msgstr "Ez da eremurik aldatu." @@ -163,36 +174,45 @@ msgstr "Bat ere ez" msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "" +"Bat baino gehiago hautatzeko, sakatu \"Kontrol\" tekla edo \"Command\" Mac " +"batean." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" +msgid "The {name} \"{obj}\" was added successfully." +msgstr "{name} \"{obj}\" ondo gehitu da." + +msgid "You may edit it again below." +msgstr "Aldaketa gehiago egin ditzazkezu jarraian." #, python-brace-format msgid "" "The {name} \"{obj}\" was added successfully. You may add another {name} " "below." msgstr "" +"{name} \"{obj}\" ondo gehitu da. Beste {name} bat gehitu dezakezu jarraian." #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" +"{name} \"{obj}\" ondo aldatu da. Aldaketa gehiago egin ditzazkezu jarraian." #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." +"The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" +"{name} \"{obj}\" ondo gehitu da. Aldaketa gehiago egin ditzazkezu jarraian." #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " "below." msgstr "" +"{name} \"{obj}\" ondo aldatu da. Beste {name} bat gehitu dezakezu jarraian." #, python-brace-format msgid "The {name} \"{obj}\" was changed successfully." -msgstr "" +msgstr "{name} \"{obj}\" ondo aldatu da." msgid "" "Items must be selected in order to perform actions on them. No items have " @@ -209,8 +229,9 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" ondo ezabatu da." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Ez dago %(key)r gakodun %(name)s objekturik." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" +"\"%(key)s\" ID dun %(name)s ez dira existitzen. Agian ezabatua izan da?" #, python-format msgid "Add %s" @@ -220,8 +241,12 @@ msgstr "Gehitu %s" msgid "Change %s" msgstr "Aldatu %s" +#, python-format +msgid "View %s" +msgstr "%s ikusi" + msgid "Database error" -msgstr "Datu-basearen errorea" +msgstr "Errorea datu-basean" #, python-format msgid "%(count)s %(name)s was changed successfully." @@ -272,7 +297,7 @@ msgstr "Sartu" #, python-format msgid "%(app)s administration" -msgstr "" +msgstr "%(app)s kudeaketa" msgid "Page not found" msgstr "Ez da orririk aurkitu" @@ -297,10 +322,10 @@ msgid "" "email and should be fixed shortly. Thanks for your patience." msgstr "" "Errore bat gertatu da. Errorea guneko kudeatzaileari jakinarazi zaio email " -"bidez eta laister egon beharko luke konponduta. Barkatu eragozpenak." +"bidez eta laster egon beharko luke konponduta. Barkatu eragozpenak." msgid "Run the selected action" -msgstr "Burutu hautatutako ekintza" +msgstr "Burutu aukeratutako ekintza" msgid "Go" msgstr "Joan" @@ -323,16 +348,16 @@ msgstr "" "gehiago aldatu ahal izango dituzu." msgid "Enter a username and password." -msgstr "Idatzi erabiltzaile-izen eta pasahitza." +msgstr "Sartu erabiltzaile izen eta pasahitz bat." msgid "Change password" msgstr "Aldatu pasahitza" msgid "Please correct the error below." -msgstr "Zuzendu azpiko erroreak." +msgstr "Mesedez zuzendu erroreak behean." msgid "Please correct the errors below." -msgstr "" +msgstr "Mesedez zuzendu erroreak behean." #, python-format msgid "Enter a new password for the user %(username)s." @@ -343,7 +368,7 @@ msgid "Welcome," msgstr "Ongi etorri," msgid "View site" -msgstr "" +msgstr "Webgunea ikusi" msgid "Documentation" msgstr "Dokumentazioa" @@ -359,7 +384,7 @@ msgid "History" msgstr "Historia" msgid "View on site" -msgstr "Ikusi gunean" +msgstr "Webgunean ikusi" msgid "Filter" msgstr "Iragazkia" @@ -403,13 +428,13 @@ msgstr "" "Erlazionaturik dauden hurrengo elementuak ere ezabatuko dira:" msgid "Objects" -msgstr "" +msgstr "Objetuak" msgid "Yes, I'm sure" msgstr "Bai, ziur nago" msgid "No, take me back" -msgstr "" +msgstr "Ez, itzuli atzera" msgid "Delete multiple objects" msgstr "Ezabatu hainbat objektu" @@ -420,7 +445,7 @@ msgid "" "objects, but your account doesn't have permission to delete the following " "types of objects:" msgstr "" -"Hautatutako %(objects_name)s ezabatzeak erlazionatutako objektuak ezabatzea " +"Aukeratutako %(objects_name)s ezabatzeak erlazionatutako objektuak ezabatzea " "eskatzen du baina zure kontuak ez dauka baimen nahikorik objektu mota hauek " "ezabatzeko: " @@ -429,7 +454,7 @@ msgid "" "Deleting the selected %(objects_name)s would require deleting the following " "protected related objects:" msgstr "" -"Hautatutako %(objects_name)s ezabatzeak erlazionatutako objektu babestu " +"Aukeratutako %(objects_name)s ezabatzeak erlazionatutako objektu babestu " "hauek ezabatzea eskatzen du:" #, python-format @@ -437,11 +462,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 "" -"Ziur zaude hautatutako %(objects_name)s ezabatu nahi duzula? Objektu guzti " +"Ziur zaude aukeratutako %(objects_name)s ezabatu nahi duzula? Objektu guzti " "hauek eta erlazionatutako elementu guztiak ezabatuko dira:" -msgid "Change" -msgstr "Aldatu" +msgid "View" +msgstr "Ikusi" msgid "Delete?" msgstr "Ezabatu?" @@ -451,7 +476,7 @@ msgid " By %(filter_title)s " msgstr "Irizpidea: %(filter_title)s" msgid "Summary" -msgstr "" +msgstr "Laburpena" #, python-format msgid "Models in the %(name)s application" @@ -460,14 +485,14 @@ msgstr "%(name)s aplikazioaren modeloak" msgid "Add" msgstr "Gehitu" -msgid "You don't have permission to edit anything." -msgstr "Ez daukazu ezer aldatzeko baimenik." +msgid "You don't have permission to view or edit anything." +msgstr "Ez duzu ezer ikusi edo ezabatzeko baimenik." msgid "Recent actions" -msgstr "" +msgstr "Azken ekintzak" msgid "My actions" -msgstr "" +msgstr "Nire ekintzak" msgid "None available" msgstr "Ez dago ezer" @@ -480,14 +505,16 @@ msgid "" "database tables have been created, and make sure the database is readable by " "the appropriate user." msgstr "" -"Zerbait gaizki dago zure datu-basearekin. Ziurtatu datu-baseko taulak sortu " -"direla eta erabiltzaile egokiak irakurtzeko baimena duela." +"Zerbait gaizki dago zure datu-basearen instalazioan. Ziurtatu datu-baseko " +"taulak sortu direla eta dagokion erabiltzaileak irakurtzeko baimena duela." #, 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 "" +"%(username)s bezala autentikatu zara, baina ez daukazu orrialde honetara " +"sarbidea. Nahi al duzu kontu ezberdin batez sartu?" msgid "Forgotten your password or username?" msgstr "Pasahitza edo erabiltzaile-izena ahaztu duzu?" @@ -514,20 +541,8 @@ msgstr "Erakutsi dena" msgid "Save" msgstr "Gorde" -msgid "Popup closing..." -msgstr "" - -#, python-format -msgid "Change selected %(model)s" -msgstr "" - -#, python-format -msgid "Add another %(model)s" -msgstr "" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "" +msgid "Popup closing…" +msgstr "Popup leihoa ixten..." msgid "Search" msgstr "Bilatu" @@ -546,10 +561,28 @@ msgid "Save as new" msgstr "Gorde berri gisa" msgid "Save and add another" -msgstr "Gorde eta gehitu beste bat" +msgstr "Gorde eta beste bat gehitu" msgid "Save and continue editing" -msgstr "Gorde eta jarraitu editatzen" +msgstr "Gorde eta editatzen jarraitu" + +msgid "Save and view" +msgstr "Gorde eta ikusi" + +msgid "Close" +msgstr "Itxi" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Aldatu aukeratutako %(model)s" + +#, python-format +msgid "Add another %(model)s" +msgstr "Gehitu beste %(model)s" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Ezabatu aukeratutako %(model)s" msgid "Thanks for spending some quality time with the Web site today." msgstr "Eskerrik asko webguneari zure probetxuzko denbora eskaintzeagatik." @@ -571,7 +604,7 @@ msgstr "" "bi aldiz, akatsik egiten ez duzula ziurta dezagun." msgid "Change my password" -msgstr "Aldatu nire pasahitza" +msgstr "Nire pasahitza aldatu" msgid "Password reset" msgstr "Berrezarri pasahitza" @@ -604,6 +637,9 @@ 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 "" +"Zure pasahitza ezartzeko jarraibideak bidali dizkizugu email bidez, sartu " +"duzun helbide elektronikoa kontu bati lotuta badago. Laster jaso beharko " +"zenituzke." msgid "" "If you don't receive an email, please make sure you've entered the address " @@ -618,7 +654,7 @@ msgid "" "user account at %(site_name)s." msgstr "" "Mezu hau %(site_name)s webgunean pasahitza berrezartzea eskatu duzulako jaso " -"duzu" +"duzu." msgid "Please go to the following page and choose a new password:" msgstr "Zoaz hurrengo orrira eta aukeratu pasahitz berria:" @@ -651,11 +687,15 @@ msgstr "Data guztiak" #, python-format msgid "Select %s" -msgstr "Hautatu %s" +msgstr "Aukeratu %s" #, python-format msgid "Select %s to change" -msgstr "Hautatu %s aldatzeko" +msgstr "Aukeratu %s aldatzeko" + +#, python-format +msgid "Select %s to view" +msgstr "Aukeratu %s ikusteko" msgid "Date:" msgstr "Data:" diff --git a/django/contrib/admin/locale/eu/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/eu/LC_MESSAGES/djangojs.mo index 26b18703cb4a..6b9adaa92c9b 100644 Binary files a/django/contrib/admin/locale/eu/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/eu/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/eu/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/eu/LC_MESSAGES/djangojs.po index 052ecc14b0f7..40d86fae8350 100644 --- a/django/contrib/admin/locale/eu/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/eu/LC_MESSAGES/djangojs.po @@ -2,6 +2,7 @@ # # Translators: # Aitzol Naberan , 2011 +# Eneko Illarramendi , 2017 # Jannis Leidel , 2011 # julen , 2012-2013 msgid "" @@ -9,8 +10,8 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" -"Last-Translator: Jannis Leidel \n" +"PO-Revision-Date: 2017-09-23 18:54+0000\n" +"Last-Translator: Eneko Illarramendi \n" "Language-Team: Basque (http://www.transifex.com/django/django/language/eu/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -20,7 +21,7 @@ msgstr "" #, javascript-format msgid "Available %s" -msgstr "%s Erabilgarri" +msgstr "%s erabilgarri" #, javascript-format msgid "" @@ -53,7 +54,7 @@ msgstr "Kendu" #, javascript-format msgid "Chosen %s" -msgstr "%s Aukeratuak" +msgstr "%s aukeratuak" #, javascript-format msgid "" @@ -101,20 +102,20 @@ msgstr "" #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Oharra: zerbitzariaren denborarekiko ordu %s aurrerago zaude" +msgstr[1] "Oharra: zerbitzariaren denborarekiko %s ordu aurrerago zaude" #, javascript-format msgid "Note: You are %s hour behind server time." msgid_plural "Note: You are %s hours behind server time." -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Oharra: zerbitzariaren denborarekiko ordu %s atzerago zaude. " +msgstr[1] "Oharra: zerbitzariaren denborarekiko %s ordu atzerago zaude. " msgid "Now" msgstr "Orain" msgid "Choose a Time" -msgstr "" +msgstr "Aukeratu ordu bat" msgid "Choose a time" msgstr "Aukeratu ordu bat" @@ -129,7 +130,7 @@ msgid "Noon" msgstr "Eguerdia" msgid "6 p.m." -msgstr "" +msgstr "6 p.m." msgid "Cancel" msgstr "Atzera" @@ -138,7 +139,7 @@ msgid "Today" msgstr "Gaur" msgid "Choose a Date" -msgstr "" +msgstr "Aukeratu data bat" msgid "Yesterday" msgstr "Atzo" @@ -147,68 +148,68 @@ msgid "Tomorrow" msgstr "Bihar" msgid "January" -msgstr "" +msgstr "Urtarrila" msgid "February" -msgstr "" +msgstr "Otsaila" msgid "March" -msgstr "" +msgstr "Martxoa" msgid "April" -msgstr "" +msgstr "Apirila" msgid "May" -msgstr "" +msgstr "Maiatza" msgid "June" -msgstr "" +msgstr "Ekaina" msgid "July" -msgstr "" +msgstr "Uztaila" msgid "August" -msgstr "" +msgstr "Abuztua" msgid "September" -msgstr "" +msgstr "Iraila" msgid "October" -msgstr "" +msgstr "Urria" msgid "November" -msgstr "" +msgstr "Azaroa" msgid "December" -msgstr "" +msgstr "Abendua" msgctxt "one letter Sunday" msgid "S" -msgstr "" +msgstr "I" msgctxt "one letter Monday" msgid "M" -msgstr "" +msgstr "A" msgctxt "one letter Tuesday" msgid "T" -msgstr "" +msgstr "A" msgctxt "one letter Wednesday" msgid "W" -msgstr "" +msgstr "A" msgctxt "one letter Thursday" msgid "T" -msgstr "" +msgstr "O" msgctxt "one letter Friday" msgid "F" -msgstr "" +msgstr "O" msgctxt "one letter Saturday" msgid "S" -msgstr "" +msgstr "L" msgid "Show" msgstr "Erakutsi" diff --git a/django/contrib/admin/locale/fa/LC_MESSAGES/django.mo b/django/contrib/admin/locale/fa/LC_MESSAGES/django.mo index cde37cac9795..85b2dbf67648 100644 Binary files a/django/contrib/admin/locale/fa/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/fa/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/fa/LC_MESSAGES/django.po b/django/contrib/admin/locale/fa/LC_MESSAGES/django.po index 38973aa14cbb..2d64609052d7 100644 --- a/django/contrib/admin/locale/fa/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/fa/LC_MESSAGES/django.po @@ -5,14 +5,16 @@ # Ali Vakilzade , 2015 # Arash Fazeli , 2012 # Jannis Leidel , 2011 +# MJafar Mashhadi , 2018 +# Mohammad Hossein Mojtahedi , 2017,2019 # Pouya Abbassi, 2016 # Reza Mohammadi , 2013-2014 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-08-16 13:50+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-23 12:01+0000\n" "Last-Translator: Mohammad Hossein Mojtahedi \n" "Language-Team: Persian (http://www.transifex.com/django/django/language/" "fa/)\n" @@ -20,7 +22,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: fa\n" -"Plural-Forms: nplurals=1; plural=0;\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" #, python-format msgid "Successfully deleted %(count)d %(items)s." @@ -91,6 +93,15 @@ msgstr "افزودن یک %(verbose_name)s دیگر" msgid "Remove" msgstr "حذف" +msgid "Addition" +msgstr "افزودن" + +msgid "Change" +msgstr "تغییر" + +msgid "Deletion" +msgstr "کاستن" + msgid "action time" msgstr "زمان اقدام" @@ -104,7 +115,7 @@ msgid "object id" msgstr "شناسهٔ شیء" #. 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 "صورت شیء" @@ -170,11 +181,11 @@ msgstr "" "دارید." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" -" {name} \"{obj}\" به موفقیت اضافه شد. شما میتوانید در قسمت پایین، آنرا " -"ویرایش کنید." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "{name} \"{obj}\" با موفقیت اضافه شد." + +msgid "You may edit it again below." +msgstr "می‌توانید مجدداً ویرایش کنید." #, python-brace-format msgid "" @@ -184,10 +195,6 @@ msgstr "" "{name} \"{obj}\" با موفقیت اضافه شد. شما میتوانید {name} دیگری در قسمت پایین " "اضافه کنید." -#, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "{name} \"{obj}\" با موفقیت اضافه شد." - #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may edit it again below." @@ -195,6 +202,13 @@ 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} " @@ -222,8 +236,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s·\"%(obj)s\" با موفقیت حذف شد." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "ایتم%(name)s با کلید اصلی %(key)r وجود ندارد." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s با کلید «%(key)s» وجود ندارد. ممکن است حذف شده باشد." #, python-format msgid "Add %s" @@ -233,6 +247,10 @@ msgstr "اضافه کردن %s" msgid "Change %s" msgstr "تغییر %s" +#, python-format +msgid "View %s" +msgstr "مشاهده %s" + msgid "Database error" msgstr "خطا در بانک اطلاعاتی" @@ -240,11 +258,13 @@ msgstr "خطا در بانک اطلاعاتی" msgid "%(count)s %(name)s was changed successfully." msgid_plural "%(count)s %(name)s were changed successfully." msgstr[0] "%(count)s %(name)s با موفقیت تغییر کرد." +msgstr[1] "%(count)s %(name)s با موفقیت تغییر کرد." #, python-format msgid "%(total_count)s selected" msgid_plural "All %(total_count)s selected" msgstr[0] "همه موارد %(total_count)s انتخاب شده" +msgstr[1] "همه موارد %(total_count)s انتخاب شده" #, python-format msgid "0 of %(cnt)s selected" @@ -448,8 +468,8 @@ msgstr "" "آیا در خصوص حذف %(objects_name)s انتخاب شده اطمینان دارید؟ تمام موجودیت‌های " "ذیل به همراه موارد مرتبط با آنها حذف خواهند شد:" -msgid "Change" -msgstr "تغییر" +msgid "View" +msgstr "مشاهده" msgid "Delete?" msgstr "حذف؟" @@ -468,8 +488,8 @@ msgstr "مدلها در برنامه %(name)s " msgid "Add" msgstr "اضافه کردن" -msgid "You don't have permission to edit anything." -msgstr "شما اجازهٔ ویرایش چیزی را ندارید." +msgid "You don't have permission to view or edit anything." +msgstr "شما اجازهٔ مشاهده یا ویرایش چیزی را ندارید." msgid "Recent actions" msgstr "فعالیتهای اخیر" @@ -525,21 +545,9 @@ msgstr "نمایش همه" msgid "Save" msgstr "ذخیره" -msgid "Popup closing..." +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 "Search" msgstr "جستجو" @@ -547,6 +555,7 @@ msgstr "جستجو" msgid "%(counter)s result" msgid_plural "%(counter)s results" msgstr[0] "%(counter)s نتیجه" +msgstr[1] "%(counter)s نتیجه" #, python-format msgid "%(full_result_count)s total" @@ -561,6 +570,24 @@ msgstr "ذخیره و ایجاد یکی دیگر" msgid "Save and continue editing" 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 انتخاب شده" + msgid "Thanks for spending some quality time with the Web site today." msgstr "متشکر از اینکه مدتی از وقت خود را به ما اختصاص دادید." @@ -671,6 +698,10 @@ msgstr "%s انتخاب کنید" msgid "Select %s to change" msgstr "%s را برای تغییر انتخاب کنید" +#, python-format +msgid "Select %s to view" +msgstr "%s را برای مشاهده انتخاب کنید" + msgid "Date:" msgstr "تاریخ:" diff --git a/django/contrib/admin/locale/fa/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/fa/LC_MESSAGES/djangojs.mo index 89b4ed0f130c..7c6fa113bca6 100644 Binary files a/django/contrib/admin/locale/fa/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/fa/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/fa/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/fa/LC_MESSAGES/djangojs.po index 52fc9a9de656..5f8db3b153d4 100644 --- a/django/contrib/admin/locale/fa/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/fa/LC_MESSAGES/djangojs.po @@ -12,8 +12,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-08-16 13:54+0000\n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-23 18:54+0000\n" "Last-Translator: Mohammad Hossein Mojtahedi \n" "Language-Team: Persian (http://www.transifex.com/django/django/language/" "fa/)\n" @@ -21,7 +21,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: fa\n" -"Plural-Forms: nplurals=1; plural=0;\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" #, javascript-format msgid "Available %s" @@ -77,6 +77,7 @@ msgstr "برای حذف یکجای همهٔ %sی انتخاب شده کلیک ک msgid "%(sel)s of %(cnt)s selected" msgid_plural "%(sel)s of %(cnt)s selected" msgstr[0] " %(sel)s از %(cnt)s انتخاب شده‌اند" +msgstr[1] " %(sel)s از %(cnt)s انتخاب شده‌اند" msgid "" "You have unsaved changes on individual editable fields. If you run an " @@ -102,18 +103,32 @@ msgstr "" "شما عملی را انجام داده اید، ولی تغییری انجام نداده اید. احتمالا دنبال کلید " "Go به جای Save میگردید." +msgid "Now" +msgstr "اکنون" + +msgid "Midnight" +msgstr "نیمه‌شب" + +msgid "6 a.m." +msgstr "۶ صبح" + +msgid "Noon" +msgstr "ظهر" + +msgid "6 p.m." +msgstr "۶ بعدازظهر" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." msgstr[0] "توجه: شما %s ساعت از زمان سرور جلو هستید." +msgstr[1] "توجه: شما %s ساعت از زمان سرور جلو هستید." #, javascript-format msgid "Note: You are %s hour behind server time." msgid_plural "Note: You are %s hours behind server time." msgstr[0] "توجه: شما %s ساعت از زمان سرور عقب هستید." - -msgid "Now" -msgstr "اکنون" +msgstr[1] "توجه: شما %s ساعت از زمان سرور عقب هستید." msgid "Choose a Time" msgstr "یک زمان انتخاب کنید" @@ -121,18 +136,6 @@ msgstr "یک زمان انتخاب کنید" msgid "Choose a time" msgstr "یک زمان انتخاب کنید" -msgid "Midnight" -msgstr "نیمه‌شب" - -msgid "6 a.m." -msgstr "۶ صبح" - -msgid "Noon" -msgstr "ظهر" - -msgid "6 p.m." -msgstr "۶ بعدازظهر" - msgid "Cancel" msgstr "انصراف" diff --git a/django/contrib/admin/locale/fi/LC_MESSAGES/django.mo b/django/contrib/admin/locale/fi/LC_MESSAGES/django.mo index 25dbb3498df1..b65ffeff53f9 100644 Binary files a/django/contrib/admin/locale/fi/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/fi/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/fi/LC_MESSAGES/django.po b/django/contrib/admin/locale/fi/LC_MESSAGES/django.po index 0982f0a45af6..a2c0a3806e10 100644 --- a/django/contrib/admin/locale/fi/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/fi/LC_MESSAGES/django.po @@ -1,17 +1,18 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Aarni Koskela, 2015 +# Aarni Koskela, 2015,2017 # Antti Kaihola , 2011 # Jannis Leidel , 2011 # Klaus Dahlén , 2012 +# Nikolay Korotkiy , 2018 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 00:36+0000\n" +"Last-Translator: Ramiro Morales\n" "Language-Team: Finnish (http://www.transifex.com/django/django/language/" "fi/)\n" "MIME-Version: 1.0\n" @@ -66,10 +67,10 @@ msgid "This year" msgstr "Tänä vuonna" msgid "No date" -msgstr "" +msgstr "Ei päivämäärää" msgid "Has date" -msgstr "" +msgstr "On päivämäärä" #, python-format msgid "" @@ -89,6 +90,15 @@ msgstr "Lisää toinen %(verbose_name)s" msgid "Remove" msgstr "Poista" +msgid "Addition" +msgstr "Lisäys" + +msgid "Change" +msgstr "Muokkaa" + +msgid "Deletion" +msgstr "Poisto" + msgid "action time" msgstr "tapahtumahetki" @@ -102,7 +112,7 @@ msgid "object id" msgstr "kohteen tunniste" #. 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 "kohteen tiedot" @@ -135,7 +145,7 @@ msgstr "Lokimerkintätietue" #, python-brace-format msgid "Added {name} \"{object}\"." -msgstr "" +msgstr "Lisätty {name} \"{object}\"." msgid "Added." msgstr "Lisätty." @@ -145,15 +155,15 @@ msgstr "ja" #, python-brace-format msgid "Changed {fields} for {name} \"{object}\"." -msgstr "" +msgstr "Muutettu {fields} {name}-kohteelle \"{object}\"." #, python-brace-format msgid "Changed {fields}." -msgstr "" +msgstr "Muutettu {fields}." #, python-brace-format msgid "Deleted {name} \"{object}\"." -msgstr "" +msgstr "Poistettu {name} \"{object}\"." msgid "No fields changed." msgstr "Ei muutoksia kenttiin." @@ -168,34 +178,37 @@ msgstr "" "vaihtoehtoja." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "{name} \"{obj}\" on lisätty." + +msgid "You may edit it again below." msgstr "" #, python-brace-format msgid "" "The {name} \"{obj}\" was added successfully. You may add another {name} " "below." -msgstr "" +msgstr "{name} \"{obj}\" on lisätty. Voit lisätä toisen {name} alla." #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "" +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." +msgstr "{name} \"{obj}\" on muokattu. Voit muokata sitä edelleen alla." #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." -msgstr "" +"The {name} \"{obj}\" was added successfully. You may edit it again below." +msgstr "{name} \"{obj}\" on lisätty. Voit muokata sitä uudelleen alla." #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " "below." -msgstr "" +msgstr "{name} \"{obj}\" on muokattu. Voit lisätä toisen alla." #, python-brace-format msgid "The {name} \"{obj}\" was changed successfully." -msgstr "" +msgstr "{name} \"{obj}\" on muokattu." msgid "" "Items must be selected in order to perform actions on them. No items have " @@ -212,8 +225,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" on poistettu." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(name)s perusavaimella %(key)r ei ole olemassa." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s tunnisteella %(key)s puuttuu. Se on voitu poistaa." #, python-format msgid "Add %s" @@ -223,6 +236,10 @@ msgstr "Lisää %s" msgid "Change %s" msgstr "Muokkaa %s" +#, python-format +msgid "View %s" +msgstr "" + msgid "Database error" msgstr "Tietokantavirhe" @@ -331,7 +348,7 @@ msgid "Change password" msgstr "Vaihda salasana" msgid "Please correct the error below." -msgstr "Korjaa allaolevat virheet." +msgstr "" msgid "Please correct the errors below." msgstr "Korjaa allaolevat virheet." @@ -442,8 +459,8 @@ msgstr "" "Haluatki varmasti poistaa valitut %(objects_name)s? Samalla poistetaan " "kaikki alla mainitut ja niihin liittyvät kohteet:" -msgid "Change" -msgstr "Muokkaa" +msgid "View" +msgstr "" msgid "Delete?" msgstr "Poista?" @@ -462,14 +479,14 @@ msgstr "%(name)s -applikaation mallit" msgid "Add" msgstr "Lisää" -msgid "You don't have permission to edit anything." -msgstr "Sinulla ei ole oikeutta muokata mitään." +msgid "You don't have permission to view or edit anything." +msgstr "" msgid "Recent actions" -msgstr "" +msgstr "Viimeisimmät tapahtumat" msgid "My actions" -msgstr "" +msgstr "Omat tapahtumat" msgid "None available" msgstr "Ei yhtään" @@ -518,20 +535,8 @@ msgstr "Näytä kaikki" msgid "Save" msgstr "Tallenna ja poistu" -msgid "Popup closing..." -msgstr "Ponnahdusikkuna sulkeutuu..." - -#, python-format -msgid "Change selected %(model)s" -msgstr "Muuta valittuja %(model)s" - -#, python-format -msgid "Add another %(model)s" -msgstr "Lisää toinen %(model)s" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Poista valitut %(model)s" +msgid "Popup closing…" +msgstr "" msgid "Search" msgstr "Haku" @@ -555,6 +560,24 @@ msgstr "Tallenna ja lisää toinen" msgid "Save and continue editing" msgstr "Tallenna välillä ja jatka muokkaamista" +msgid "Save and view" +msgstr "" + +msgid "Close" +msgstr "Sulje" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Muuta valittuja %(model)s" + +#, python-format +msgid "Add another %(model)s" +msgstr "Lisää toinen %(model)s" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Poista valitut %(model)s" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Kiitos sivuillamme viettämästäsi ajasta." @@ -665,6 +688,10 @@ msgstr "Valitse %s" msgid "Select %s to change" msgstr "Valitse muokattava %s" +#, python-format +msgid "Select %s to view" +msgstr "" + msgid "Date:" msgstr "Pvm:" diff --git a/django/contrib/admin/locale/fi/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/fi/LC_MESSAGES/djangojs.mo index e68cede0d6a0..10d6422a4d22 100644 Binary files a/django/contrib/admin/locale/fi/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/fi/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/fi/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/fi/LC_MESSAGES/djangojs.po index 764e877a5308..bf775c864486 100644 --- a/django/contrib/admin/locale/fi/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/fi/LC_MESSAGES/djangojs.po @@ -1,7 +1,7 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Aarni Koskela, 2015 +# Aarni Koskela, 2015,2017 # Antti Kaihola , 2011 # Jannis Leidel , 2011 msgid "" @@ -9,8 +9,8 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" -"Last-Translator: Jannis Leidel \n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" +"Last-Translator: Aarni Koskela\n" "Language-Team: Finnish (http://www.transifex.com/django/django/language/" "fi/)\n" "MIME-Version: 1.0\n" @@ -150,68 +150,68 @@ msgid "Tomorrow" msgstr "Huomenna" msgid "January" -msgstr "" +msgstr "tammikuu" msgid "February" -msgstr "" +msgstr "helmikuu" msgid "March" -msgstr "" +msgstr "maaliskuu" msgid "April" -msgstr "" +msgstr "huhtikuu" msgid "May" -msgstr "" +msgstr "toukokuu" msgid "June" -msgstr "" +msgstr "kesäkuu" msgid "July" -msgstr "" +msgstr "heinäkuu" msgid "August" -msgstr "" +msgstr "elokuu" msgid "September" -msgstr "" +msgstr "syyskuu" msgid "October" -msgstr "" +msgstr "lokakuu" msgid "November" -msgstr "" +msgstr "marraskuu" msgid "December" -msgstr "" +msgstr "joulukuu" msgctxt "one letter Sunday" msgid "S" -msgstr "" +msgstr "Su" msgctxt "one letter Monday" msgid "M" -msgstr "" +msgstr "Ma" msgctxt "one letter Tuesday" msgid "T" -msgstr "" +msgstr "Ti" msgctxt "one letter Wednesday" msgid "W" -msgstr "" +msgstr "Ke" msgctxt "one letter Thursday" msgid "T" -msgstr "" +msgstr "To" msgctxt "one letter Friday" msgid "F" -msgstr "" +msgstr "Pe" msgctxt "one letter Saturday" msgid "S" -msgstr "" +msgstr "La" msgid "Show" msgstr "Näytä" diff --git a/django/contrib/admin/locale/fr/LC_MESSAGES/django.mo b/django/contrib/admin/locale/fr/LC_MESSAGES/django.mo index db79af91dd8c..213fd357d1b3 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 b752694beecf..4483ced6768f 100644 --- a/django/contrib/admin/locale/fr/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/fr/LC_MESSAGES/django.po @@ -1,15 +1,15 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Claude Paroz , 2013-2016 +# Claude Paroz , 2013-2019 # Claude Paroz , 2011,2013 # Jannis Leidel , 2011 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 16:32+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 17:28+0000\n" "Last-Translator: Claude Paroz \n" "Language-Team: French (http://www.transifex.com/django/django/language/fr/)\n" "MIME-Version: 1.0\n" @@ -88,6 +88,15 @@ msgstr "Ajouter un objet %(verbose_name)s supplémentaire" msgid "Remove" msgstr "Supprimer" +msgid "Addition" +msgstr "Ajout" + +msgid "Change" +msgstr "Modifier" + +msgid "Deletion" +msgstr "Suppression" + msgid "action time" msgstr "heure de l'action" @@ -101,7 +110,7 @@ msgid "object id" msgstr "id de l'objet" #. 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ésentation de l'objet" @@ -119,22 +128,22 @@ msgstr "entrées d'historique" #, python-format msgid "Added \"%(object)s\"." -msgstr "%(object)s ajouté(e)s." +msgstr "Ajout de « %(object)s »." #, python-format msgid "Changed \"%(object)s\" - %(changes)s" -msgstr "%(object)s modifié(e)s - %(changes)s" +msgstr "Modification de « %(object)s » - %(changes)s" #, python-format msgid "Deleted \"%(object)s.\"" -msgstr "%(object)s supprimé(e)s" +msgstr "Suppression de « %(object)s »." msgid "LogEntry Object" msgstr "Objet de journal" #, python-brace-format msgid "Added {name} \"{object}\"." -msgstr "{name} « {object} » ajouté." +msgstr "Ajout de {name} « {object} »." msgid "Added." msgstr "Ajout." @@ -144,15 +153,15 @@ msgstr "et" #, python-brace-format msgid "Changed {fields} for {name} \"{object}\"." -msgstr "{fields} modifié(s) pour l'objet {name} « {object} »." +msgstr "Modification de {fields} pour l'objet {name} « {object} »." #, python-brace-format msgid "Changed {fields}." -msgstr "{fields} modifié(s)." +msgstr "Modification de {fields}." #, python-brace-format msgid "Deleted {name} \"{object}\"." -msgstr "{name} « {object} » supprimé." +msgstr "Suppression de {name} « {object} »." msgid "No fields changed." msgstr "Aucun champ modifié." @@ -167,11 +176,11 @@ msgstr "" "en sélectionner plusieurs." #, 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 continuer " -"l'édition ci-dessous." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "L'objet {name} « {obj} » a été ajouté avec succès." + +msgid "You may edit it again below." +msgstr "Vous pouvez l'éditer à nouveau ci-dessous." #, python-brace-format msgid "" @@ -181,10 +190,6 @@ msgstr "" "L'objet {name} « {obj} » a été ajouté avec succès. Vous pouvez ajouter un " "autre objet « {name} » ci-dessous." -#, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "L'objet {name} « {obj} » a été ajouté avec succès." - #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may edit it again below." @@ -192,6 +197,13 @@ msgstr "" "L'objet {name} « {obj} » a été modifié avec succès. Vous pouvez continuer " "l'édition 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 continuer " +"l'édition ci-dessous." + #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " @@ -219,8 +231,10 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "L'objet %(name)s « %(obj)s » a été supprimé avec succès." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "L'objet %(name)s avec la clef primaire %(key)r n'existe pas." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" +"%(name)s avec l'identifiant « %(key)s » n'existe pas. Peut-être a-t-il été " +"supprimé ?" #, python-format msgid "Add %s" @@ -230,6 +244,10 @@ msgstr "Ajout %s" msgid "Change %s" msgstr "Modification de %s" +#, python-format +msgid "View %s" +msgstr "Afficher %s" + msgid "Database error" msgstr "Erreur de base de données" @@ -339,7 +357,7 @@ msgid "Change password" msgstr "Modifier le mot de passe" msgid "Please correct the error below." -msgstr "Corrigez les erreurs suivantes." +msgstr "Corrigez l'erreur ci-dessous." msgid "Please correct the errors below." msgstr "Corrigez les erreurs ci-dessous." @@ -453,8 +471,8 @@ msgstr "" "Voulez-vous vraiment supprimer les objets %(objects_name)s sélectionnés ? " "Tous les objets suivants et les éléments liés seront supprimés :" -msgid "Change" -msgstr "Modifier" +msgid "View" +msgstr "Afficher" msgid "Delete?" msgstr "Supprimer ?" @@ -473,8 +491,8 @@ msgstr "Modèles de l'application %(name)s" msgid "Add" msgstr "Ajouter" -msgid "You don't have permission to edit anything." -msgstr "Vous n'avez pas la permission de modifier quoi que ce soit." +msgid "You don't have permission to view or edit anything." +msgstr "Vous n'avez pas la permission de voir ou de modifier quoi que ce soit." msgid "Recent actions" msgstr "Actions récentes" @@ -531,21 +549,9 @@ msgstr "Tout afficher" msgid "Save" msgstr "Enregistrer" -msgid "Popup closing..." +msgid "Popup closing…" msgstr "Fenêtre en cours de fermeture…" -#, python-format -msgid "Change selected %(model)s" -msgstr "Modifier l'objet %(model)s sélectionné" - -#, python-format -msgid "Add another %(model)s" -msgstr "Ajouter un autre objet %(model)s" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Supprimer l'objet %(model)s sélectionné" - msgid "Search" msgstr "Rechercher" @@ -568,6 +574,24 @@ msgstr "Enregistrer et ajouter un nouveau" msgid "Save and continue editing" msgstr "Enregistrer et continuer les modifications" +msgid "Save and view" +msgstr "Enregistrer et afficher" + +msgid "Close" +msgstr "Fermer" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Modifier l'objet %(model)s sélectionné" + +#, python-format +msgid "Add another %(model)s" +msgstr "Ajouter un autre objet %(model)s" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Supprimer l'objet %(model)s sélectionné" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Merci pour le temps que vous avez accordé à ce site aujourd'hui." @@ -684,6 +708,10 @@ msgstr "Sélectionnez %s" msgid "Select %s to change" msgstr "Sélectionnez l'objet %s à changer" +#, python-format +msgid "Select %s to view" +msgstr "Sélectionnez l'objet %s à afficher" + msgid "Date:" msgstr "Date :" diff --git a/django/contrib/admin/locale/fr/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/fr/LC_MESSAGES/djangojs.mo index 86d7d6de6aa0..919247d9914d 100644 Binary files a/django/contrib/admin/locale/fr/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/fr/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/fr/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/fr/LC_MESSAGES/djangojs.po index fc5c83ef3c77..4b17b0ccf5cc 100644 --- a/django/contrib/admin/locale/fr/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/fr/LC_MESSAGES/djangojs.po @@ -1,15 +1,15 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Claude Paroz , 2014-2016 +# Claude Paroz , 2014-2017 # Claude Paroz , 2011-2012 # Jannis Leidel , 2011 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 18:51+0000\n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-10-21 13:28+0000\n" "Last-Translator: Claude Paroz \n" "Language-Team: French (http://www.transifex.com/django/django/language/fr/)\n" "MIME-Version: 1.0\n" @@ -99,7 +99,22 @@ msgid "" msgstr "" "Vous avez sélectionné une action, et vous n'avez fait aucune modification " "sur des champs. Vous cherchez probablement le bouton Envoyer et non le " -"bouton Sauvegarder." +"bouton Enregistrer." + +msgid "Now" +msgstr "Maintenant" + +msgid "Midnight" +msgstr "Minuit" + +msgid "6 a.m." +msgstr "6:00" + +msgid "Noon" +msgstr "Midi" + +msgid "6 p.m." +msgstr "18:00" #, javascript-format msgid "Note: You are %s hour ahead of server time." @@ -113,27 +128,12 @@ msgid_plural "Note: You are %s hours behind server time." msgstr[0] "Note : votre heure précède l'heure du serveur de %s heure." msgstr[1] "Note : votre heure précède l'heure du serveur de %s heures." -msgid "Now" -msgstr "Maintenant" - msgid "Choose a Time" msgstr "Choisir une heure" msgid "Choose a time" msgstr "Choisir une heure" -msgid "Midnight" -msgstr "Minuit" - -msgid "6 a.m." -msgstr "6:00" - -msgid "Noon" -msgstr "Midi" - -msgid "6 p.m." -msgstr "18:00" - msgid "Cancel" msgstr "Annuler" diff --git a/django/contrib/admin/locale/ga/LC_MESSAGES/django.mo b/django/contrib/admin/locale/ga/LC_MESSAGES/django.mo index 4439db06bc9e..8c029af57b53 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 66d6049697d6..252e50d06556 100644 --- a/django/contrib/admin/locale/ga/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/ga/LC_MESSAGES/django.po @@ -2,14 +2,15 @@ # # Translators: # Jannis Leidel , 2011 +# Luke Blaney , 2019 # Michael Thornhill , 2011-2012,2015 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \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" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -64,10 +65,10 @@ msgid "This year" msgstr "An blian seo" msgid "No date" -msgstr "" +msgstr "Gan dáta" msgid "Has date" -msgstr "" +msgstr "Le dáta" #, python-format msgid "" @@ -87,11 +88,20 @@ msgstr "Cuir eile %(verbose_name)s" msgid "Remove" msgstr "Tóg amach" +msgid "Addition" +msgstr "" + +msgid "Change" +msgstr "Athraigh" + +msgid "Deletion" +msgstr "Scriosadh" + msgid "action time" msgstr "am aicsean" msgid "user" -msgstr "" +msgstr "úsáideoir" msgid "content type" msgstr "" @@ -100,7 +110,7 @@ msgid "object id" msgstr "id oibiacht" #. 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 oibiacht" @@ -133,25 +143,25 @@ msgstr "Oibiacht LogEntry" #, python-brace-format msgid "Added {name} \"{object}\"." -msgstr "" +msgstr "{name} curtha leis \"{object}\"." msgid "Added." -msgstr "" +msgstr "Curtha leis." msgid "and" msgstr "agus" #, python-brace-format msgid "Changed {fields} for {name} \"{object}\"." -msgstr "" +msgstr "{fields} athrithe don {name} \"{object}\"." #, python-brace-format msgid "Changed {fields}." -msgstr "" +msgstr "{fields} athrithe." #, python-brace-format msgid "Deleted {name} \"{object}\"." -msgstr "" +msgstr "{name} scrioste: \"{object}\"." msgid "No fields changed." msgstr "Dada réimse aithraithe" @@ -166,9 +176,11 @@ msgstr "" "amháin a roghnú." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" +msgid "The {name} \"{obj}\" was added successfully." +msgstr "Bhí {name} \"{obj}\" curtha leis go rathúil" + +msgid "You may edit it again below." +msgstr "Thig leat é a athrú arís faoi seo." #, python-brace-format msgid "" @@ -177,12 +189,15 @@ msgid "" msgstr "" #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." +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 changed successfully. You may edit it again below." +"The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" #, python-brace-format @@ -190,10 +205,12 @@ 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 "" +msgstr "D'athraigh {name} \"{obj}\" go rathúil." msgid "" "Items must be selected in order to perform actions on them. No items have " @@ -210,8 +227,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "Bhí %(name)s \"%(obj)s\" scrioste go rathúil." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Níl réad le hainm %(name)s agus eochair %(key)r ann." +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 é?" #, python-format msgid "Add %s" @@ -221,6 +238,10 @@ msgstr "Cuir %s le" msgid "Change %s" msgstr "Aithrigh %s" +#, python-format +msgid "View %s" +msgstr "Amharc ar %s" + msgid "Database error" msgstr "Botún bunachar sonraí" @@ -336,7 +357,7 @@ msgid "Change password" msgstr "Athraigh focal faire" msgid "Please correct the error below." -msgstr "Ceartaigh na botúin thíos le do thoil" +msgstr "Ceartaigh an botún thíos le do thoil." msgid "Please correct the errors below." msgstr "Le do thoil cheartú earráidí thíos." @@ -448,8 +469,8 @@ 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 "Change" -msgstr "Athraigh" +msgid "View" +msgstr "Amharc ar" msgid "Delete?" msgstr "Cealaigh?" @@ -468,8 +489,8 @@ msgstr "Samhlacha ins an %(name)s iarratais" msgid "Add" msgstr "Cuir le" -msgid "You don't have permission to edit anything." -msgstr "Níl cead agat aon rud a cuir in eagar." +msgid "You don't have permission to view or edit anything." +msgstr "" msgid "Recent actions" msgstr "" @@ -523,21 +544,9 @@ msgstr "Taispéan gach rud" msgid "Save" msgstr "Sábháil" -msgid "Popup closing..." +msgid "Popup closing…" msgstr "" -#, python-format -msgid "Change selected %(model)s" -msgstr "Athraigh roghnaithe %(model)s" - -#, python-format -msgid "Add another %(model)s" -msgstr "Cuir le %(model)s" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Scrios roghnaithe %(model)s" - msgid "Search" msgstr "Cuardach" @@ -563,6 +572,24 @@ msgstr "Sabháil agus cuir le ceann eile" msgid "Save and continue editing" msgstr "Sábhail agus lean ag cuir in eagar" +msgid "Save and view" +msgstr "Sabháil agus amharc ar" + +msgid "Close" +msgstr "Druid" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Athraigh roghnaithe %(model)s" + +#, python-format +msgid "Add another %(model)s" +msgstr "Cuir le %(model)s" + +#, python-format +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ú." @@ -668,6 +695,10 @@ msgstr "Roghnaigh %s" msgid "Select %s to change" msgstr "Roghnaigh %s a athrú" +#, python-format +msgid "Select %s to view" +msgstr "" + 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 3b8e7e82cc79..ee000e278fc1 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 8b5f62b738b9..ce0a412d1072 100644 --- a/django/contrib/admin/locale/ga/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/ga/LC_MESSAGES/djangojs.po @@ -2,14 +2,15 @@ # # Translators: # Jannis Leidel , 2011 +# Luke Blaney , 2019 # Michael Thornhill , 2011-2012,2015 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" -"Last-Translator: Jannis Leidel \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" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -103,6 +104,21 @@ 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." +msgid "Now" +msgstr "Anois" + +msgid "Midnight" +msgstr "Meán oíche" + +msgid "6 a.m." +msgstr "6 a.m." + +msgid "Noon" +msgstr "Nóin" + +msgid "6 p.m." +msgstr "6in" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." @@ -126,27 +142,12 @@ msgstr[3] "" msgstr[4] "" "Tabhair faoi deara: Tá tú %s uair a chloig taobh thiar am an friothálaí." -msgid "Now" -msgstr "Anois" - msgid "Choose a Time" -msgstr "" +msgstr "Roghnaigh Am" msgid "Choose a time" msgstr "Roghnaigh am" -msgid "Midnight" -msgstr "Meán oíche" - -msgid "6 a.m." -msgstr "6 a.m." - -msgid "Noon" -msgstr "Nóin" - -msgid "6 p.m." -msgstr "" - msgid "Cancel" msgstr "Cealaigh" @@ -154,7 +155,7 @@ msgid "Today" msgstr "Inniu" msgid "Choose a Date" -msgstr "" +msgstr "Roghnaigh Dáta" msgid "Yesterday" msgstr "Inné" @@ -163,68 +164,68 @@ msgid "Tomorrow" msgstr "Amárach" msgid "January" -msgstr "" +msgstr "Eanáir" msgid "February" -msgstr "" +msgstr "Feabhra" msgid "March" -msgstr "" +msgstr "Márta" msgid "April" -msgstr "" +msgstr "Aibreán" msgid "May" -msgstr "" +msgstr "Bealtaine" msgid "June" -msgstr "" +msgstr "Meitheamh" msgid "July" -msgstr "" +msgstr "Iúil" msgid "August" -msgstr "" +msgstr "Lúnasa" msgid "September" -msgstr "" +msgstr "Meán Fómhair" msgid "October" -msgstr "" +msgstr "Deireadh Fómhair" msgid "November" -msgstr "" +msgstr "Samhain" msgid "December" -msgstr "" +msgstr "Nollaig" msgctxt "one letter Sunday" msgid "S" -msgstr "" +msgstr "D" msgctxt "one letter Monday" msgid "M" -msgstr "" +msgstr "L" msgctxt "one letter Tuesday" msgid "T" -msgstr "" +msgstr "M" msgctxt "one letter Wednesday" msgid "W" -msgstr "" +msgstr "C" msgctxt "one letter Thursday" msgid "T" -msgstr "" +msgstr "D" msgctxt "one letter Friday" msgid "F" -msgstr "" +msgstr "A" msgctxt "one letter Saturday" msgid "S" -msgstr "" +msgstr "S" msgid "Show" msgstr "Taispeán" diff --git a/django/contrib/admin/locale/gd/LC_MESSAGES/django.mo b/django/contrib/admin/locale/gd/LC_MESSAGES/django.mo index fce0d02955c8..ad734b846271 100644 Binary files a/django/contrib/admin/locale/gd/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/gd/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/gd/LC_MESSAGES/django.po b/django/contrib/admin/locale/gd/LC_MESSAGES/django.po index 9fcf0d6485b3..ef8f4bc789a6 100644 --- a/django/contrib/admin/locale/gd/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/gd/LC_MESSAGES/django.po @@ -1,15 +1,15 @@ # This file is distributed under the same license as the Django package. # # Translators: -# GunChleoc, 2015-2016 +# GunChleoc, 2015-2017 # GunChleoc, 2015 # GunChleoc, 2015 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:24+0000\n" +"POT-Creation-Date: 2018-05-21 14:16-0300\n" +"PO-Revision-Date: 2018-05-29 09:32+0000\n" "Last-Translator: GunChleoc\n" "Language-Team: Gaelic, Scottish (http://www.transifex.com/django/django/" "language/gd/)\n" @@ -90,6 +90,15 @@ msgstr "Cuir %(verbose_name)s eile ris" msgid "Remove" msgstr "Thoir air falbh" +msgid "Addition" +msgstr "Cur ris" + +msgid "Change" +msgstr "Atharraich" + +msgid "Deletion" +msgstr "Sguabadh às" + msgid "action time" msgstr "àm a’ ghnìomha" @@ -167,11 +176,11 @@ msgid "" msgstr "Cum sìos “Control” no “Command” air Mac gus iomadh nì a thaghadh." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" -"Chaidh {name} “{obj}” a chur ris gu soirbheachail. ’S urrainn dhut a " -"dheasachadh a-rithist gu h-ìosal." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "Chaidh {name} “{obj}” a chur ris gu soirbheachail." + +msgid "You may edit it again below." +msgstr "’S urrainn dhut a dheasachadh a-rithist gu h-ìosal." #, python-brace-format msgid "" @@ -181,10 +190,6 @@ msgstr "" "Chaidh {name} “%{obj}” a chur ris gu soirbheachail. ’S urrainn dhut {name} " "eile a chur ris gu h-ìosal." -#, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "Chaidh {name} “{obj}” a chur ris gu soirbheachail." - #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may edit it again below." @@ -192,6 +197,13 @@ msgstr "" "Chaidh {name} “{obj}” atharrachadh gu soirbheachail. ’S urrainn dhut a " "dheasachadh a-rithist gu h-ìosal." +#, python-brace-format +msgid "" +"The {name} \"{obj}\" was added successfully. You may edit it again below." +msgstr "" +"Chaidh {name} “{obj}” a chur ris gu soirbheachail. ’S urrainn dhut a " +"dheasachadh a-rithist gu h-ìosal." + #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " @@ -219,8 +231,10 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "Chaidh %(name)s “%(obj)s” a sguabadh às gu soirbheachail." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Chan eil oibseact %(name)s air a bheil prìomh-iuchair %(key)r ann." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" +"Chan eil %(name)s leis an ID \"%(key)s\" ann. 'S dòcha gun deach a sguabadh " +"às?" #, python-format msgid "Add %s" @@ -230,6 +244,10 @@ msgstr "Cuir %s ris" msgid "Change %s" msgstr "Atharraich %s" +#, python-format +msgid "View %s" +msgstr "Seall %s" + msgid "Database error" msgstr "Mearachd an stòir-dhàta" @@ -459,8 +477,8 @@ msgstr "" "sguabadh às? Thèid a h-uile oibseact seo ’s na nithean dàimheach aca a " "sguabadh às:" -msgid "Change" -msgstr "Atharraich" +msgid "View" +msgstr "Seall" msgid "Delete?" msgstr "A bheil thu airson a sguabadh às?" @@ -479,8 +497,8 @@ msgstr "Modailean ann an aplacaid %(name)s" msgid "Add" msgstr "Cuir ris" -msgid "You don't have permission to edit anything." -msgstr "Chan eil cead agad gus dad a dheasachadh." +msgid "You don't have permission to view or edit anything." +msgstr "Chan eil cead agad gus dad a shealltainn no a dheasachadh." msgid "Recent actions" msgstr "Gnìomhan o chionn goirid" @@ -544,6 +562,10 @@ msgstr "Tha a’ phriob-uinneag ’ga dùnadh…" msgid "Change selected %(model)s" msgstr "Atharraich a’ %(model)s a thagh thu" +#, python-format +msgid "View selected %(model)s" +msgstr "Seall %(model)s a thagh thu" + #, python-format msgid "Add another %(model)s" msgstr "Cuir %(model)s eile ris" @@ -576,6 +598,12 @@ msgstr "Sàbhail is cuir fear eile ris" msgid "Save and continue editing" msgstr "Sàbhail is deasaich a-rithist" +msgid "Save and view" +msgstr "Sàbhail is seall" + +msgid "Close" +msgstr "Dùin" + msgid "Thanks for spending some quality time with the Web site today." msgstr "" "Mòran taing gun do chuir thu seachad deagh-àm air an làrach-lìn an-diugh." @@ -695,6 +723,10 @@ msgstr "Tagh %s" msgid "Select %s to change" msgstr "Tagh %s gus atharrachadh" +#, python-format +msgid "Select %s to view" +msgstr "Tagh %s gus a shealltainn" + msgid "Date:" msgstr "Ceann-là:" diff --git a/django/contrib/admin/locale/gd/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/gd/LC_MESSAGES/djangojs.mo index e81b144fd94f..e7c0103c2285 100644 Binary files a/django/contrib/admin/locale/gd/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/gd/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/gd/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/gd/LC_MESSAGES/djangojs.po index 1a623f31c028..f198aa452e31 100644 --- a/django/contrib/admin/locale/gd/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/gd/LC_MESSAGES/djangojs.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:22+0000\n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-22 17:29+0000\n" "Last-Translator: GunChleoc\n" "Language-Team: Gaelic, Scottish (http://www.transifex.com/django/django/" "language/gd/)\n" @@ -106,6 +106,21 @@ msgstr "" "’S dòcha gu bheil thu airson am putan “Siuthad” a chleachdadh seach am putan " "“Sàbhail”." +msgid "Now" +msgstr "An-dràsta" + +msgid "Midnight" +msgstr "Meadhan-oidhche" + +msgid "6 a.m." +msgstr "6m" + +msgid "Noon" +msgstr "Meadhan-latha" + +msgid "6 p.m." +msgstr "6f" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." @@ -130,27 +145,12 @@ msgstr[2] "" msgstr[3] "" "An aire: Tha thu %s uair a thìde air dheireadh àm an fhrithealaiche." -msgid "Now" -msgstr "An-dràsta" - msgid "Choose a Time" msgstr "Tagh àm" msgid "Choose a time" msgstr "Tagh àm" -msgid "Midnight" -msgstr "Meadhan-oidhche" - -msgid "6 a.m." -msgstr "6m" - -msgid "Noon" -msgstr "Meadhan-latha" - -msgid "6 p.m." -msgstr "6f" - msgid "Cancel" msgstr "Sguir dheth" diff --git a/django/contrib/admin/locale/gl/LC_MESSAGES/django.mo b/django/contrib/admin/locale/gl/LC_MESSAGES/django.mo index 604d336be00a..7cf4d84c7880 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 4b7a9b8bad0d..47f1115c9c9f 100644 --- a/django/contrib/admin/locale/gl/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/gl/LC_MESSAGES/django.po @@ -3,17 +3,18 @@ # Translators: # fasouto , 2011-2012 # fonso , 2011,2013 +# fasouto , 2017 # Jannis Leidel , 2011 # Leandro Regueiro , 2013 -# Oscar Carballal , 2011-2012 +# Oscar Carballal , 2011-2012 # Pablo, 2015 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-23 18:54+0000\n" +"Last-Translator: fasouto \n" "Language-Team: Galician (http://www.transifex.com/django/django/language/" "gl/)\n" "MIME-Version: 1.0\n" @@ -68,10 +69,10 @@ msgid "This year" msgstr "Este ano" msgid "No date" -msgstr "" +msgstr "Sen data" msgid "Has date" -msgstr "" +msgstr "Ten data" #, python-format msgid "" @@ -95,7 +96,7 @@ msgid "action time" msgstr "hora da acción" msgid "user" -msgstr "" +msgstr "usuario" msgid "content type" msgstr "" @@ -140,7 +141,7 @@ msgid "Added {name} \"{object}\"." msgstr "" msgid "Added." -msgstr "" +msgstr "Engadido" msgid "and" msgstr "e" @@ -212,8 +213,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "Eliminouse correctamente o/a %(name)s \"%(obj)s\"." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "O obxecto %(name)s con primary key %(key)r non existe." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" @@ -272,7 +273,7 @@ msgstr "Iniciar sesión" #, python-format msgid "%(app)s administration" -msgstr "" +msgstr "administración de %(app)s " msgid "Page not found" msgstr "Páxina non atopada" @@ -343,7 +344,7 @@ msgid "Welcome," msgstr "Benvido," msgid "View site" -msgstr "" +msgstr "Ver sitio" msgid "Documentation" msgstr "Documentación" @@ -466,10 +467,10 @@ msgid "You don't have permission to edit anything." msgstr "Non ten permiso para editar nada." msgid "Recent actions" -msgstr "" +msgstr "Accións recentes" msgid "My actions" -msgstr "" +msgstr "As miñas accións" msgid "None available" msgstr "Ningunha dispoñíbel" diff --git a/django/contrib/admin/locale/gl/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/gl/LC_MESSAGES/djangojs.mo index 0b095b93ac3a..fefbe0d1af3b 100644 Binary files a/django/contrib/admin/locale/gl/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/gl/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/gl/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/gl/LC_MESSAGES/djangojs.po index ee1d501c9213..2df9fa03df57 100644 --- a/django/contrib/admin/locale/gl/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/gl/LC_MESSAGES/djangojs.po @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" +"PO-Revision-Date: 2017-09-23 18:54+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Galician (http://www.transifex.com/django/django/language/" "gl/)\n" diff --git a/django/contrib/admin/locale/he/LC_MESSAGES/django.mo b/django/contrib/admin/locale/he/LC_MESSAGES/django.mo index 4c6980a42cc3..6803b9cf7592 100644 Binary files a/django/contrib/admin/locale/he/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/he/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/he/LC_MESSAGES/django.po b/django/contrib/admin/locale/he/LC_MESSAGES/django.po index cf53a0d514c4..e5315484b917 100644 --- a/django/contrib/admin/locale/he/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/he/LC_MESSAGES/django.po @@ -3,20 +3,21 @@ # Translators: # Alex Gaynor , 2011 # Jannis Leidel , 2011 -# Meir Kriheli , 2011-2015 +# Meir Kriheli , 2011-2015,2017,2019 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-03-19 16:40+0000\n" +"Last-Translator: Meir Kriheli \n" "Language-Team: Hebrew (http://www.transifex.com/django/django/language/he/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: he\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % " +"1 == 0) ? 1: (n % 10 == 0 && n % 1 == 0 && n > 10) ? 2 : 3;\n" #, python-format msgid "Successfully deleted %(count)d %(items)s." @@ -64,10 +65,10 @@ msgid "This year" msgstr "השנה" msgid "No date" -msgstr "" +msgstr "ללא תאריך" msgid "Has date" -msgstr "" +msgstr "עם תאריך" #, python-format msgid "" @@ -87,6 +88,15 @@ msgstr "הוספת %(verbose_name)s" msgid "Remove" msgstr "להסיר" +msgid "Addition" +msgstr "הוספה" + +msgid "Change" +msgstr "שינוי" + +msgid "Deletion" +msgstr "מחיקה" + msgid "action time" msgstr "זמן פעולה" @@ -100,7 +110,7 @@ msgid "object id" msgstr "מזהה אובייקט" #. 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 "ייצוג אובייקט" @@ -133,7 +143,7 @@ msgstr "אובייקט LogEntry" #, python-brace-format msgid "Added {name} \"{object}\"." -msgstr "" +msgstr "בוצעה הוספת {name} \"{object}\"." msgid "Added." msgstr "נוסף." @@ -143,15 +153,15 @@ msgstr "ו" #, python-brace-format msgid "Changed {fields} for {name} \"{object}\"." -msgstr "" +msgstr "בוצע שינוי {fields} עבור {name} \"{object}\"." #, python-brace-format msgid "Changed {fields}." -msgstr "" +msgstr " {fields} שונו." #, python-brace-format msgid "Deleted {name} \"{object}\"." -msgstr "" +msgstr "בוצעה מחיקת {name} \"{object}\"." msgid "No fields changed." msgstr "אף שדה לא השתנה." @@ -165,34 +175,37 @@ msgstr "" "יש להחזיק את \"Control\", או \"Command\" על מק, לחוץ כדי לבחור יותר מאחד." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" +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 add another {name} " "below." -msgstr "" +msgstr "הוספת {name} \"{obj}\" בוצעה בהצלחה. ניתן להוסיף עוד {name} מתחת.." #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "" +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 "" +"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} " "below." -msgstr "" +msgstr "עדכון {name} \"{obj}\" בוצע בהצלחה. ניתן להוסיף עוד {name} מתחת." #, python-brace-format msgid "The {name} \"{obj}\" was changed successfully." -msgstr "" +msgstr "שינוי {name} \"{obj}\" בוצע בהצלחה." msgid "" "Items must be selected in order to perform actions on them. No items have " @@ -207,8 +220,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "מחיקת %(name)s \"%(obj)s\" בוצעה בהצלחה." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "הפריט %(name)s עם המפתח הראשי %(key)r אינו קיים." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s עם ID \"%(key)s\" לא במצאי. אולי זה נמחק?" #, python-format msgid "Add %s" @@ -218,6 +231,10 @@ msgstr "הוספת %s" msgid "Change %s" msgstr "שינוי %s" +#, python-format +msgid "View %s" +msgstr "צפיה ב%s" + msgid "Database error" msgstr "שגיאת בסיס נתונים" @@ -226,12 +243,16 @@ msgid "%(count)s %(name)s was changed successfully." msgid_plural "%(count)s %(name)s were changed successfully." msgstr[0] "שינוי %(count)s %(name)s בוצע בהצלחה." msgstr[1] "שינוי %(count)s %(name)s בוצע בהצלחה." +msgstr[2] "שינוי %(count)s %(name)s בוצע בהצלחה." +msgstr[3] "שינוי %(count)s %(name)s בוצע בהצלחה." #, python-format msgid "%(total_count)s selected" msgid_plural "All %(total_count)s selected" msgstr[0] "%(total_count)s נבחר" msgstr[1] "כל ה־%(total_count)s נבחרו" +msgstr[2] "כל ה־%(total_count)s נבחרו" +msgstr[3] "כל ה־%(total_count)s נבחרו" #, python-format msgid "0 of %(cnt)s selected" @@ -325,7 +346,7 @@ msgid "Change password" msgstr "שינוי סיסמה" msgid "Please correct the error below." -msgstr "נא לתקן את השגיאות המופיעות מתחת." +msgstr "נא לתקן את השגיאה מתחת." msgid "Please correct the errors below." msgstr "נא לתקן את השגיאות מתחת." @@ -434,8 +455,8 @@ msgstr "" "האם אתה בטוח שאתה רוצה למחוק את ה%(objects_name)s הנבחר? כל האובייקטים הבאים " "ופריטים הקשורים להם יימחקו:" -msgid "Change" -msgstr "שינוי" +msgid "View" +msgstr "צפיה" msgid "Delete?" msgstr "מחיקה ?" @@ -454,14 +475,14 @@ msgstr "מודלים ביישום %(name)s" msgid "Add" msgstr "הוספה" -msgid "You don't have permission to edit anything." -msgstr "אין לך הרשאות לעריכה." +msgid "You don't have permission to view or edit anything." +msgstr "אין לך הרשאות לצפיה או עריכה." msgid "Recent actions" -msgstr "" +msgstr "פעולות אחרונות" msgid "My actions" -msgstr "" +msgstr "הפעולות שלי" msgid "None available" msgstr "לא נמצאו" @@ -509,21 +530,9 @@ msgstr "הצג הכל" msgid "Save" msgstr "שמירה" -msgid "Popup closing..." +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 "Search" msgstr "חיפוש" @@ -532,6 +541,8 @@ msgid "%(counter)s result" msgid_plural "%(counter)s results" msgstr[0] "תוצאה %(counter)s" msgstr[1] "%(counter)s תוצאות" +msgstr[2] "%(counter)s תוצאות" +msgstr[3] "%(counter)s תוצאות" #, python-format msgid "%(full_result_count)s total" @@ -546,6 +557,24 @@ msgstr "שמירה והוספת אחר" msgid "Save and continue editing" 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 הנבחר." + msgid "Thanks for spending some quality time with the Web site today." msgstr "תודה על בילוי זמן איכות עם האתר." @@ -654,6 +683,10 @@ msgstr "בחירת %s" msgid "Select %s to change" msgstr "בחירת %s לשינוי" +#, python-format +msgid "Select %s to view" +msgstr "בחירת %s לצפיה" + msgid "Date:" msgstr "תאריך:" diff --git a/django/contrib/admin/locale/he/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/he/LC_MESSAGES/djangojs.mo index 4611b961a39e..fe37ec5a8339 100644 Binary files a/django/contrib/admin/locale/he/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/he/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/he/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/he/LC_MESSAGES/djangojs.po index 176a070f2d7e..43eee285656e 100644 --- a/django/contrib/admin/locale/he/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/he/LC_MESSAGES/djangojs.po @@ -3,24 +3,25 @@ # Translators: # Alex Gaynor , 2012 # Jannis Leidel , 2011 -# Meir Kriheli , 2011-2012,2014-2015 +# Meir Kriheli , 2011-2012,2014-2015,2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" +"Last-Translator: Meir Kriheli \n" "Language-Team: Hebrew (http://www.transifex.com/django/django/language/he/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: he\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % " +"1 == 0) ? 1: (n % 10 == 0 && n % 1 == 0 && n > 10) ? 2 : 3;\n" #, javascript-format msgid "Available %s" -msgstr "%s זמינות" +msgstr "אפשרויות %s זמינות" #, javascript-format msgid "" @@ -73,6 +74,8 @@ msgid "%(sel)s of %(cnt)s selected" msgid_plural "%(sel)s of %(cnt)s selected" msgstr[0] "%(sel)s מ %(cnt)s נבחרות" msgstr[1] "%(sel)s מ %(cnt)s נבחרות" +msgstr[2] "%(sel)s מ %(cnt)s נבחרות" +msgstr[3] "%(sel)s מ %(cnt)s נבחרות" msgid "" "You have unsaved changes on individual editable fields. If you run an " @@ -97,20 +100,36 @@ msgstr "" "בחרת פעולה, ולא עשיתה שינויימ על שדות. אתה כנראה מחפש את הכפתור ללכת במקום " "הכפתור לשמור." +msgid "Now" +msgstr "כעת" + +msgid "Midnight" +msgstr "חצות" + +msgid "6 a.m." +msgstr "6 בבוקר" + +msgid "Noon" +msgstr "12 בצהריים" + +msgid "6 p.m." +msgstr "6 אחר הצהריים" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." msgstr[0] "הערה: את/ה %s שעה לפני זמן השרת." msgstr[1] "הערה: את/ה %s שעות לפני זמן השרת." +msgstr[2] "הערה: את/ה %s שעות לפני זמן השרת." +msgstr[3] "הערה: את/ה %s שעות לפני זמן השרת." #, javascript-format msgid "Note: You are %s hour behind server time." msgid_plural "Note: You are %s hours behind server time." msgstr[0] "הערה: את/ה %s שעה אחרי זמן השרת." msgstr[1] "הערה: את/ה %s שעות אחרי זמן השרת." - -msgid "Now" -msgstr "כעת" +msgstr[2] "הערה: את/ה %s שעות אחרי זמן השרת." +msgstr[3] "הערה: את/ה %s שעות אחרי זמן השרת." msgid "Choose a Time" msgstr "בחירת שעה" @@ -118,18 +137,6 @@ msgstr "בחירת שעה" msgid "Choose a time" msgstr "בחירת שעה" -msgid "Midnight" -msgstr "חצות" - -msgid "6 a.m." -msgstr "6 בבוקר" - -msgid "Noon" -msgstr "12 בצהריים" - -msgid "6 p.m." -msgstr "6 אחר הצהריים" - msgid "Cancel" msgstr "ביטול" @@ -146,68 +153,68 @@ msgid "Tomorrow" msgstr "מחר" msgid "January" -msgstr "" +msgstr "ינואר" msgid "February" -msgstr "" +msgstr "פברואר" msgid "March" -msgstr "" +msgstr "מרץ" msgid "April" -msgstr "" +msgstr "אפריל" msgid "May" -msgstr "" +msgstr "מאי" msgid "June" -msgstr "" +msgstr "יוני" msgid "July" -msgstr "" +msgstr "יולי" msgid "August" -msgstr "" +msgstr "אוגוסט" msgid "September" -msgstr "" +msgstr "ספטמבר" msgid "October" -msgstr "" +msgstr "אוקטובר" msgid "November" -msgstr "" +msgstr "נובמבר" msgid "December" -msgstr "" +msgstr "דצמבר" msgctxt "one letter Sunday" msgid "S" -msgstr "" +msgstr "ר" msgctxt "one letter Monday" msgid "M" -msgstr "" +msgstr "ש" msgctxt "one letter Tuesday" msgid "T" -msgstr "" +msgstr "ש" msgctxt "one letter Wednesday" msgid "W" -msgstr "" +msgstr "ר" msgctxt "one letter Thursday" msgid "T" -msgstr "" +msgstr "ח" msgctxt "one letter Friday" msgid "F" -msgstr "" +msgstr "ש" msgctxt "one letter Saturday" msgid "S" -msgstr "" +msgstr "ש" msgid "Show" msgstr "הצג" diff --git a/django/contrib/admin/locale/hi/LC_MESSAGES/django.mo b/django/contrib/admin/locale/hi/LC_MESSAGES/django.mo index a552950739eb..b8c97bb4df2e 100644 Binary files a/django/contrib/admin/locale/hi/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/hi/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/hi/LC_MESSAGES/django.po b/django/contrib/admin/locale/hi/LC_MESSAGES/django.po index ec3be504cae8..8ed2fb96b8ab 100644 --- a/django/contrib/admin/locale/hi/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/hi/LC_MESSAGES/django.po @@ -10,8 +10,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-19 16:40+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Hindi (http://www.transifex.com/django/django/language/hi/)\n" "MIME-Version: 1.0\n" @@ -208,8 +208,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" को कामयाबी से निकाला गया है" #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(name)s नामक कोई वस्तू जिस की प्राथमिक कुंजी %(key)r हो, अस्तित्व में नहीं हैं |" +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" diff --git a/django/contrib/admin/locale/hi/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/hi/LC_MESSAGES/djangojs.mo index 15c98855b347..bb755ad12f28 100644 Binary files a/django/contrib/admin/locale/hi/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/hi/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/hi/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/hi/LC_MESSAGES/djangojs.po index 5369d62b28d4..78b49e7d8931 100644 --- a/django/contrib/admin/locale/hi/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/hi/LC_MESSAGES/djangojs.po @@ -9,7 +9,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Hindi (http://www.transifex.com/django/django/language/hi/)\n" "MIME-Version: 1.0\n" diff --git a/django/contrib/admin/locale/hr/LC_MESSAGES/django.mo b/django/contrib/admin/locale/hr/LC_MESSAGES/django.mo index 6f7248994054..eb87cd149b88 100644 Binary files a/django/contrib/admin/locale/hr/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/hr/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/hr/LC_MESSAGES/django.po b/django/contrib/admin/locale/hr/LC_MESSAGES/django.po index 5c322eac1286..b9192865160a 100644 --- a/django/contrib/admin/locale/hr/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/hr/LC_MESSAGES/django.po @@ -3,17 +3,20 @@ # Translators: # aljosa , 2011,2013 # Bojan Mihelač , 2012 +# Filip Cuk , 2016 +# Goran Zugelj , 2018 # Jannis Leidel , 2011 -# Mislav Cimperšak , 2013,2015 +# Mislav Cimperšak , 2013,2015-2016 # Ylodi , 2015 +# Vedran Linić , 2019 # Ylodi , 2011 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-02-19 06:44+0000\n" +"Last-Translator: Vedran Linić \n" "Language-Team: Croatian (http://www.transifex.com/django/django/language/" "hr/)\n" "MIME-Version: 1.0\n" @@ -69,10 +72,10 @@ msgid "This year" msgstr "Ova godina" msgid "No date" -msgstr "" +msgstr "Nema datuma" msgid "Has date" -msgstr "" +msgstr "Ima datum" #, python-format msgid "" @@ -92,6 +95,15 @@ msgstr "Dodaj još jedan %(verbose_name)s" msgid "Remove" msgstr "Ukloni" +msgid "Addition" +msgstr "" + +msgid "Change" +msgstr "Promijeni" + +msgid "Deletion" +msgstr "" + msgid "action time" msgstr "vrijeme akcije" @@ -105,7 +117,7 @@ msgid "object id" msgstr "id objekta" #. 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 objekta" @@ -171,8 +183,10 @@ msgstr "" "objekta. " #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "" + +msgid "You may edit it again below." msgstr "" #, python-brace-format @@ -182,12 +196,13 @@ msgid "" msgstr "" #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." +"The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" #, python-brace-format @@ -215,8 +230,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" uspješno izbrisan." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Unos %(name)s sa primarnim ključem %(key)r ne postoji." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" @@ -226,6 +241,10 @@ msgstr "Novi unos (%s)" msgid "Change %s" msgstr "Promijeni %s" +#, python-format +msgid "View %s" +msgstr "" + msgid "Database error" msgstr "Pogreška u bazi" @@ -336,7 +355,7 @@ msgid "Change password" msgstr "Promijeni lozinku" msgid "Please correct the error below." -msgstr "Molimo ispravite navedene greške." +msgstr "" msgid "Please correct the errors below." msgstr "Molimo ispravite navedene greške." @@ -446,8 +465,8 @@ msgstr "" "Jeste li sigurni da želite izbrisati odabrane %(objects_name)s ? Svi " "sljedeći objekti i povezane stavke će biti izbrisani:" -msgid "Change" -msgstr "Promijeni" +msgid "View" +msgstr "Prikaz" msgid "Delete?" msgstr "Izbriši?" @@ -466,14 +485,14 @@ msgstr "Modeli u aplikaciji %(name)s" msgid "Add" msgstr "Novi unos" -msgid "You don't have permission to edit anything." -msgstr "Nemate privilegije za promjenu podataka." +msgid "You don't have permission to view or edit anything." +msgstr "Nemate dozvole za pregled ili izmjenu." msgid "Recent actions" -msgstr "" +msgstr "Nedavne promjene" msgid "My actions" -msgstr "" +msgstr "Moje promjene" msgid "None available" msgstr "Nije dostupno" @@ -522,20 +541,8 @@ msgstr "Prikaži sve" msgid "Save" msgstr "Spremi" -msgid "Popup closing..." -msgstr "Zatvaranje popup-a..." - -#, python-format -msgid "Change selected %(model)s" -msgstr "Promijeni označene %(model)s" - -#, python-format -msgid "Add another %(model)s" -msgstr "Dodaj još jedan %(model)s" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Obriši odabrane %(model)s" +msgid "Popup closing…" +msgstr "" msgid "Search" msgstr "Traži" @@ -560,6 +567,24 @@ msgstr "Spremi i unesi novi unos" msgid "Save and continue editing" msgstr "Spremi i nastavi uređivati" +msgid "Save and view" +msgstr "" + +msgid "Close" +msgstr "Zatvori" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Promijeni označene %(model)s" + +#, python-format +msgid "Add another %(model)s" +msgstr "Dodaj još jedan %(model)s" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Obriši odabrane %(model)s" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Hvala što ste proveli malo kvalitetnog vremena na stranicama danas." @@ -671,6 +696,10 @@ msgstr "Odaberi %s" msgid "Select %s to change" msgstr "Odaberi za promjenu - %s" +#, python-format +msgid "Select %s to view" +msgstr "" + msgid "Date:" msgstr "Datum:" diff --git a/django/contrib/admin/locale/hr/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/hr/LC_MESSAGES/djangojs.mo index 9ceabe8024de..e8231f69af4f 100644 Binary files a/django/contrib/admin/locale/hr/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/hr/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/hr/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/hr/LC_MESSAGES/djangojs.po index 0cabe3ec37a6..0878d8ab13f2 100644 --- a/django/contrib/admin/locale/hr/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/hr/LC_MESSAGES/djangojs.po @@ -10,8 +10,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Croatian (http://www.transifex.com/django/django/language/" "hr/)\n" @@ -102,6 +102,21 @@ msgstr "" "Odabrali ste akciju, a niste napravili nikakve izmjene na pojedinim poljima. " "Vjerojatno tražite gumb Idi umjesto gumb Spremi." +msgid "Now" +msgstr "Sada" + +msgid "Midnight" +msgstr "Ponoć" + +msgid "6 a.m." +msgstr "6 ujutro" + +msgid "Noon" +msgstr "Podne" + +msgid "6 p.m." +msgstr "6 popodne" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." @@ -116,27 +131,12 @@ msgstr[0] "" msgstr[1] "" msgstr[2] "" -msgid "Now" -msgstr "Sada" - msgid "Choose a Time" msgstr "Izaberite vrijeme" msgid "Choose a time" msgstr "Izaberite vrijeme" -msgid "Midnight" -msgstr "Ponoć" - -msgid "6 a.m." -msgstr "6 ujutro" - -msgid "Noon" -msgstr "Podne" - -msgid "6 p.m." -msgstr "6 popodne" - msgid "Cancel" msgstr "Odustani" diff --git a/django/contrib/admin/locale/hsb/LC_MESSAGES/django.mo b/django/contrib/admin/locale/hsb/LC_MESSAGES/django.mo index ddf673706263..c578603918ab 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 7044813f8472..2056c8285f8d 100644 --- a/django/contrib/admin/locale/hsb/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/hsb/LC_MESSAGES/django.po @@ -1,13 +1,13 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Michael Wolf , 2016 +# Michael Wolf , 2016-2019 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-06-12 16:37+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-03-04 13:47+0000\n" "Last-Translator: Michael Wolf \n" "Language-Team: Upper Sorbian (http://www.transifex.com/django/django/" "language/hsb/)\n" @@ -87,6 +87,15 @@ msgstr "Přidajće nowe %(verbose_name)s" msgid "Remove" msgstr "Wotstronić" +msgid "Addition" +msgstr "Přidaće" + +msgid "Change" +msgstr "Změnić" + +msgid "Deletion" +msgstr "Zhašenje" + msgid "action time" msgstr "akciski čas" @@ -100,7 +109,7 @@ msgid "object id" msgstr "objektowy 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 "objektowa reprezentacija" @@ -164,9 +173,11 @@ msgid "" msgstr "Dźeržće „ctrl“ abo „cmd“ na Mac stłóčeny, zo byšće přez jedyn wubrał." #, 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ć." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "{name} „{obj}“ je so wuspěšnje přidał." + +msgid "You may edit it again below." +msgstr "Móžeće deleka unowa wobdźěłać." #, python-brace-format msgid "" @@ -175,15 +186,16 @@ msgid "" msgstr "" "{name} „{obj}“ je so wuspěšnje přidał. Móžeće deleka dalši {name} přidać." -#, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "{name} „{obj}“ je so wuspěšnje přidał." - #, python-brace-format 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} " @@ -210,8 +222,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" je so wuspěšnje zhašał." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Objekt %(name)s z primarnym klučom %(key)r njeeksistuje." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s z ID \" %(key)s\" njeeksistuje. Je so snano zhašało?" #, python-format msgid "Add %s" @@ -221,6 +233,10 @@ msgstr "%s přidać" msgid "Change %s" msgstr "%s změnić" +#, python-format +msgid "View %s" +msgstr "%s pokazać" + msgid "Database error" msgstr "Zmylk datoweje banki" @@ -442,8 +458,8 @@ msgstr "" "Chceće woprawdźe wubrane %(objects_name)s zhašeć? Wšě slědowace objekty a " "jich přisłušne zapiski so zhašeja:" -msgid "Change" -msgstr "Změnić" +msgid "View" +msgstr "Pokazać" msgid "Delete?" msgstr "Zhašeć?" @@ -462,8 +478,8 @@ msgstr "Modele w nałoženju %(name)s" msgid "Add" msgstr "Přidać" -msgid "You don't have permission to edit anything." -msgstr "Nimaće prawo něšto wobdźěłować." +msgid "You don't have permission to view or edit anything." +msgstr "Nimaće prawo něšto pokazać abo wobdźěłać." msgid "Recent actions" msgstr "Najnowše akcije" @@ -519,20 +535,8 @@ msgstr "Wšě pokazać" msgid "Save" msgstr "Składować" -msgid "Popup closing..." -msgstr "Wuskakowace wokno so začinja..." - -#, python-format -msgid "Change selected %(model)s" -msgstr "Wubrane %(model)s změnić" - -#, python-format -msgid "Add another %(model)s" -msgstr "Druhi %(model)s přidać" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Wubrane %(model)s zhašeć" +msgid "Popup closing…" +msgstr "Wuskakowace wokno so začinja…" msgid "Search" msgstr "Pytać" @@ -558,6 +562,24 @@ msgstr "Skłaodwac a druhi přidać" msgid "Save and continue editing" msgstr "Składować a dale wobdźěłować" +msgid "Save and view" +msgstr "Składować a pokazać" + +msgid "Close" +msgstr "Začinić" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Wubrane %(model)s změnić" + +#, python-format +msgid "Add another %(model)s" +msgstr "Druhi %(model)s přidać" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Wubrane %(model)s zhašeć" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Wulki dźak, zo sće dźensa rjane chwile z websydłom přebywali." @@ -668,6 +690,10 @@ msgstr "%s wubrać" msgid "Select %s to change" msgstr "%s wubrać, zo by so změniło" +#, python-format +msgid "Select %s to view" +msgstr "%s wubrać, kotryž ma so pokazać" + msgid "Date:" msgstr "Datum:" diff --git a/django/contrib/admin/locale/hsb/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/hsb/LC_MESSAGES/djangojs.mo index b40557382524..48ff13aed2bb 100644 Binary files a/django/contrib/admin/locale/hsb/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/hsb/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/hsb/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/hsb/LC_MESSAGES/djangojs.po index 5c39a3719cd2..e33aed632acc 100644 --- a/django/contrib/admin/locale/hsb/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/hsb/LC_MESSAGES/djangojs.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-06-12 13:17+0000\n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-23 00:02+0000\n" "Last-Translator: Michael Wolf \n" "Language-Team: Upper Sorbian (http://www.transifex.com/django/django/" "language/hsb/)\n" @@ -103,6 +103,21 @@ msgstr "" "Sće akciju wubrał, a njejsće žane změny na jednotliwych polach přewjedł. " "Pytajće najskerje za tłóčatkom „Pósłać“ město tłóčatka „Składować“." +msgid "Now" +msgstr "Nětko" + +msgid "Midnight" +msgstr "Połnóc" + +msgid "6 a.m." +msgstr "6:00 hodź. dopołdnja" + +msgid "Noon" +msgstr "připołdnjo" + +msgid "6 p.m." +msgstr "6 hodź. popołdnju" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." @@ -119,27 +134,12 @@ msgstr[1] "Kedźbu: Waš čas je wo %s hodźinje za serwerowym časom." msgstr[2] "Kedźbu: Waš čas je wo %s hodźiny za serwerowym časom." msgstr[3] "Kedźbu: Waš čas je wo %s hodźin za serwerowym časom." -msgid "Now" -msgstr "Nětko" - msgid "Choose a Time" msgstr "Wubjerće čas" msgid "Choose a time" msgstr "Wubjerće čas" -msgid "Midnight" -msgstr "Połnóc" - -msgid "6 a.m." -msgstr "6:00 hodź. dopołdnja" - -msgid "Noon" -msgstr "připołdnjo" - -msgid "6 p.m." -msgstr "6 hodź. popołdnju" - msgid "Cancel" msgstr "Přetorhnyć" diff --git a/django/contrib/admin/locale/hu/LC_MESSAGES/django.mo b/django/contrib/admin/locale/hu/LC_MESSAGES/django.mo index eb9c49b21fe9..b94369f95be5 100644 Binary files a/django/contrib/admin/locale/hu/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/hu/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/hu/LC_MESSAGES/django.po b/django/contrib/admin/locale/hu/LC_MESSAGES/django.po index 9059c454e245..3bff0cd53ab9 100644 --- a/django/contrib/admin/locale/hu/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/hu/LC_MESSAGES/django.po @@ -2,9 +2,11 @@ # # Translators: # Ádám Krizsány , 2015 -# András Veres-Szentkirályi, 2016 +# Akos Zsolt Hochrein , 2018 +# András Veres-Szentkirályi, 2016,2018-2019 # Jannis Leidel , 2011 -# János Péter Ronkay , 2014 +# János R (Hangya), 2017 +# János R (Hangya), 2014 # Kristóf Gruber <>, 2012 # slink , 2011 # Szilveszter Farkas , 2011 @@ -12,8 +14,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-07-19 07:39+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-04-17 07:35+0000\n" "Last-Translator: András Veres-Szentkirályi\n" "Language-Team: Hungarian (http://www.transifex.com/django/django/language/" "hu/)\n" @@ -92,6 +94,15 @@ msgstr "Újabb %(verbose_name)s hozzáadása" msgid "Remove" msgstr "Törlés" +msgid "Addition" +msgstr "Hozzáadás" + +msgid "Change" +msgstr "Módosítás" + +msgid "Deletion" +msgstr "Törlés" + msgid "action time" msgstr "művelet időpontja" @@ -105,7 +116,7 @@ msgid "object id" msgstr "objektum 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 "objektum repr" @@ -171,9 +182,11 @@ msgstr "" "kiválasztásához." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "\"{obj}\" {name} sikeresen létrehozva. Alább ismét szerkesztheti." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "\"{obj}\" {name} sikeresen létrehozva." + +msgid "You may edit it again below." +msgstr "Alább ismét szerkesztheti." #, python-brace-format msgid "" @@ -182,15 +195,16 @@ msgid "" msgstr "" "\"{obj}\" {name} sikeresen létrehozva. Alább újabb {name} hozható létre." -#, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "\"{obj}\" {name} sikeresen létrehozva." - #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "\"{obj}\" {name} sikeresen módosítva. Alább ismét szerkesztheti." +#, python-brace-format +msgid "" +"The {name} \"{obj}\" was added successfully. You may edit it again below." +msgstr "\"{obj}\" {name} sikeresen létrehozva. Alább ismét szerkesztheti." + #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " @@ -217,8 +231,9 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "\"%(obj)s\" %(name)s sikeresen törölve." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(name)s objektum %(key)r elsődleges kulccsal nem létezik." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" +"Nem létezik %(name)s ezzel az azonosítóval: \"%(key)s\". Netán törölve lett?" #, python-format msgid "Add %s" @@ -228,6 +243,10 @@ msgstr "Új %s" msgid "Change %s" msgstr "%s módosítása" +#, python-format +msgid "View %s" +msgstr "%s megtekintése" + msgid "Database error" msgstr "Adatbázishiba" @@ -336,7 +355,7 @@ msgid "Change password" msgstr "Jelszó megváltoztatása" msgid "Please correct the error below." -msgstr "Kérem, javítsa az alábbi hibákat." +msgstr "Kérem javítsa a hibát alább." msgid "Please correct the errors below." msgstr "Kérem javítsa ki a lenti hibákat." @@ -448,8 +467,8 @@ msgstr "" "Biztosan törölni akarja a kiválasztott %(objects_name)s objektumokat? Minden " "alábbi objektum, és a hozzájuk kapcsolódóak is törlésre kerülnek:" -msgid "Change" -msgstr "Módosítás" +msgid "View" +msgstr "Megtekintés" msgid "Delete?" msgstr "Törli?" @@ -468,8 +487,8 @@ msgstr "%(name)s alkalmazásban elérhető modellek." msgid "Add" msgstr "Új" -msgid "You don't have permission to edit anything." -msgstr "Nincs joga szerkeszteni." +msgid "You don't have permission to view or edit anything." +msgstr "Nincs jogosultsága megkinteni vagy módosítani akármit." msgid "Recent actions" msgstr "Legutóbbi műveletek" @@ -523,20 +542,8 @@ msgstr "Mutassa mindet" msgid "Save" msgstr "Mentés" -msgid "Popup closing..." -msgstr "A popup bezáródik..." - -#, python-format -msgid "Change selected %(model)s" -msgstr "Kiválasztott %(model)s szerkesztése" - -#, python-format -msgid "Add another %(model)s" -msgstr "Újabb %(model)s hozzáadása" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Kiválasztott %(model)s törlése" +msgid "Popup closing…" +msgstr "A popup bezáródik…" msgid "Search" msgstr "Keresés" @@ -560,6 +567,24 @@ msgstr "Mentés és másik hozzáadása" msgid "Save and continue editing" msgstr "Mentés és a szerkesztés folytatása" +msgid "Save and view" +msgstr "Mentés és megtekintés" + +msgid "Close" +msgstr "Bezárás" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Kiválasztott %(model)s szerkesztése" + +#, python-format +msgid "Add another %(model)s" +msgstr "Újabb %(model)s hozzáadása" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Kiválasztott %(model)s törlése" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Köszönjük hogy egy kis időt eltöltött ma a honlapunkon." @@ -670,6 +695,10 @@ msgstr "%s kiválasztása" msgid "Select %s to change" msgstr "Válasszon ki egyet a módosításhoz (%s)" +#, python-format +msgid "Select %s to view" +msgstr "Válasszon ki egyet a megtekintéshez (%s)" + msgid "Date:" msgstr "Dátum:" diff --git a/django/contrib/admin/locale/hu/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/hu/LC_MESSAGES/djangojs.mo index f2bf53a0ca40..fd76d35a639d 100644 Binary files a/django/contrib/admin/locale/hu/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/hu/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/hu/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/hu/LC_MESSAGES/djangojs.po index 958bcdfee82a..5642e4069e9b 100644 --- a/django/contrib/admin/locale/hu/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/hu/LC_MESSAGES/djangojs.po @@ -4,16 +4,16 @@ # András Veres-Szentkirályi, 2016 # Attila Nagy <>, 2012 # Jannis Leidel , 2011 -# János Péter Ronkay , 2011 +# János R (Hangya), 2011 # Máté Őry , 2012 # Szilveszter Farkas , 2011 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-07-19 07:42+0000\n" -"Last-Translator: András Veres-Szentkirályi\n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-23 18:54+0000\n" +"Last-Translator: János R (Hangya)\n" "Language-Team: Hungarian (http://www.transifex.com/django/django/language/" "hu/)\n" "MIME-Version: 1.0\n" @@ -102,6 +102,21 @@ msgstr "" "Kiválasztott egy műveletet, és nem módosított egyetlen mezőt sem. " "Feltehetően a Mehet gombot keresi a Mentés helyett." +msgid "Now" +msgstr "Most" + +msgid "Midnight" +msgstr "Éjfél" + +msgid "6 a.m." +msgstr "Reggel 6 óra" + +msgid "Noon" +msgstr "Dél" + +msgid "6 p.m." +msgstr "Este 6 óra" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." @@ -114,27 +129,12 @@ msgid_plural "Note: You are %s hours behind server time." msgstr[0] "Megjegyzés: %s órával a szerveridő mögött jársz" msgstr[1] "Megjegyzés: %s órával a szerveridő mögött jársz" -msgid "Now" -msgstr "Most" - msgid "Choose a Time" msgstr "Válassza ki az időt" msgid "Choose a time" msgstr "Válassza ki az időt" -msgid "Midnight" -msgstr "Éjfél" - -msgid "6 a.m." -msgstr "Reggel 6 óra" - -msgid "Noon" -msgstr "Dél" - -msgid "6 p.m." -msgstr "Este 6 óra" - msgid "Cancel" msgstr "Mégsem" diff --git a/django/contrib/admin/locale/hy/LC_MESSAGES/django.mo b/django/contrib/admin/locale/hy/LC_MESSAGES/django.mo new file mode 100644 index 000000000000..1627b2d57c47 Binary files /dev/null and b/django/contrib/admin/locale/hy/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/hy/LC_MESSAGES/django.po b/django/contrib/admin/locale/hy/LC_MESSAGES/django.po new file mode 100644 index 000000000000..b39e1a7212ea --- /dev/null +++ b/django/contrib/admin/locale/hy/LC_MESSAGES/django.po @@ -0,0 +1,708 @@ +# This file is distributed under the same license as the Django package. +# +# Translators: +# Սմբատ Պետրոսյան , 2014 +msgid "" +msgstr "" +"Project-Id-Version: django\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-05-21 14:16-0300\n" +"PO-Revision-Date: 2018-11-01 20:23+0000\n" +"Last-Translator: Ruben Harutyunov \n" +"Language-Team: Armenian (http://www.transifex.com/django/django/language/" +"hy/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: hy\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#, python-format +msgid "Successfully deleted %(count)d %(items)s." +msgstr "Հաջողությամբ հեռացվել է %(count)d %(items)s։" + +#, python-format +msgid "Cannot delete %(name)s" +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 "Ադմինիստրավորում" + +msgid "All" +msgstr "Բոլորը" + +msgid "Yes" +msgstr "Այո" + +msgid "No" +msgstr "Ոչ" + +msgid "Unknown" +msgstr "Անհայտ" + +msgid "Any date" +msgstr "Ցանկացած ամսաթիվ" + +msgid "Today" +msgstr "Այսօր" + +msgid "Past 7 days" +msgstr "Անցած 7 օրերին" + +msgid "This month" +msgstr "Այս ամիս" + +msgid "This year" +msgstr "Այս տարի" + +msgid "No date" +msgstr "" + +msgid "Has date" +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 և գաղտնաբառ։" + +msgid "Action:" +msgstr "Գործողություն" + +#, python-format +msgid "Add another %(verbose_name)s" +msgstr "Ավելացնել այլ %(verbose_name)s" + +msgid "Remove" +msgstr "Հեռացնել" + +msgid "Addition" +msgstr "" + +msgid "Change" +msgstr "Փոփոխել" + +msgid "Deletion" +msgstr "" + +msgid "action time" +msgstr "գործողության ժամանակ" + +msgid "user" +msgstr "օգտագործող" + +msgid "content type" +msgstr "կոնտենտի տիպ" + +msgid "object id" +msgstr "օբյեկտի id" + +#. Translators: 'repr' means representation +#. (https://docs.python.org/3/library/functions.html#repr) +msgid "object repr" +msgstr "օբյեկտի repr" + +msgid "action flag" +msgstr "գործողության դրոշ" + +msgid "change message" +msgstr "փոփոխել հաղորդագրությունը" + +msgid "log entry" +msgstr "log գրառում" + +msgid "log entries" +msgstr "log գրառումներ" + +#, python-format +msgid "Added \"%(object)s\"." +msgstr "%(object)s֊ը ավելացվեց " + +#, python-format +msgid "Changed \"%(object)s\" - %(changes)s" +msgstr "%(object)s֊ը փոփոխվեց ֊ %(changes)s" + +#, python-format +msgid "Deleted \"%(object)s.\"" +msgstr "%(object)s-ը հեռացվեց" + +msgid "LogEntry Object" +msgstr "LogEntry օբյեկտ" + +#, python-brace-format +msgid "Added {name} \"{object}\"." +msgstr "" + +msgid "Added." +msgstr "Ավելացվեց։" + +msgid "and" +msgstr "և" + +#, python-brace-format +msgid "Changed {fields} for {name} \"{object}\"." +msgstr "" + +#, python-brace-format +msgid "Changed {fields}." +msgstr "" + +#, python-brace-format +msgid "Deleted {name} \"{object}\"." +msgstr "" + +msgid "No fields changed." +msgstr "Ոչ մի դաշտ չփոփոխվեց։" + +msgid "None" +msgstr "Ոչինչ" + +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "" +"Սեղմեք \"Control\", կամ \"Command\" Mac֊ի մրա, մեկից ավելին ընտրելու համար։" + +#, python-brace-format +msgid "The {name} \"{obj}\" was added successfully." +msgstr "" + +msgid "You may edit it again below." +msgstr "" + +#, python-brace-format +msgid "" +"The {name} \"{obj}\" was added successfully. You may add another {name} " +"below." +msgstr "" + +#, python-brace-format +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} " +"below." +msgstr "" + +#, python-brace-format +msgid "The {name} \"{obj}\" was changed successfully." +msgstr "" + +msgid "" +"Items must be selected in order to perform actions on them. No items have " +"been changed." +msgstr "" +"Օբյեկտների հետ գործողություն կատարելու համար նրանք պետք է ընտրվեն․ Ոչ մի " +"օբյեկտ չի փոփոխվել։" + +msgid "No action selected." +msgstr "Գործողությունը ընտրված չէ։" + +#, python-format +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?" +msgstr "" + +#, python-format +msgid "Add %s" +msgstr "Ավելացնել %s" + +#, python-format +msgid "Change %s" +msgstr "Փոփոխել %s" + +#, python-format +msgid "View %s" +msgstr "" + +msgid "Database error" +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[1] "%(count)s %(name)s հաջողությամբ փոփոխվեցին։" + +#, python-format +msgid "%(total_count)s selected" +msgid_plural "All %(total_count)s selected" +msgstr[0] "Ընտրված են %(total_count)s" +msgstr[1] "Բոլոր %(total_count)s֊ը ընտրված են " + +#, python-format +msgid "0 of %(cnt)s selected" +msgstr "%(cnt)s֊ից 0֊ն ընտրված է" + +#, python-format +msgid "Change history: %s" +msgstr "Փոփոխությունների պատմություն %s" + +#. Translators: Model verbose name and instance representation, +#. suitable to be an item in a list. +#, python-format +msgid "%(class_name)s %(instance)s" +msgstr "%(instance)s %(class_name)s" + +#, python-format +msgid "" +"Deleting %(class_name)s %(instance)s would require deleting the following " +"protected related objects: %(related_objects)s" +msgstr "" +"%(instance)s %(class_name)s֊ը հեռացնելու համար անհրաժեշտ է հեռացնել նրա հետ " +"կապված պաշտպանված օբյեկտները՝ %(related_objects)s" + +msgid "Django site admin" +msgstr "Django կայքի ադմինիստրավորման էջ" + +msgid "Django administration" +msgstr "Django ադմինիստրավորում" + +msgid "Site administration" +msgstr "Կայքի ադմինիստրավորում" + +msgid "Log in" +msgstr "Մուտք" + +#, python-format +msgid "%(app)s administration" +msgstr "%(app)s ադմինիստրավորում" + +msgid "Page not found" +msgstr "Էջը գտնված չէ" + +msgid "We're sorry, but the requested page could not be found." +msgstr "Ներողություն ենք հայցում, բայց հարցվող Էջը գտնված չէ" + +msgid "Home" +msgstr "Գլխավոր" + +msgid "Server error" +msgstr "Սերվերի սխալ" + +msgid "Server error (500)" +msgstr "Սերվերի սխալ (500)" + +msgid "Server Error (500)" +msgstr "Սերվերի սխալ (500)" + +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 "" +"Առաջացել է սխալ։ Ադմինիստրատորները տեղեկացվել են դրա մասին էլեկտրոնային " +"փոստի միջոցով և այն կուղղվի կարճ ժամանակահատվածի ընդացքում․ Շնորհակալ ենք " +"ձեր համբերության համար։" + +msgid "Run the selected action" +msgstr "Կատարել ընտրված գործողությունը" + +msgid "Go" +msgstr "Կատարել" + +msgid "Click here to select the objects across all pages" +msgstr "Սեղմեք այստեղ բոլոր էջերից օբյեկտներ ընտրելու համար" + +#, python-format +msgid "Select all %(total_count)s %(module_name)s" +msgstr "Ընտրել բոլոր %(total_count)s %(module_name)s" + +msgid "Clear selection" +msgstr "Չեղարկել ընտրությունը" + +msgid "" +"First, enter a username and password. Then, you'll be able to edit more user " +"options." +msgstr "" +"Սկզբում մուտքագրեք օգտագործողի անունը և գաղտնաբառը․ Հետո դուք " +"հնարավորություն կունենաք խմբագրել ավելին։" + +msgid "Enter a username and password." +msgstr "Մուտքագրեք օգտագործողի անունը և գաղտնաբառը։" + +msgid "Change password" +msgstr "Փոխել գաղտնաբառը" + +msgid "Please correct the error below." +msgstr "Ուղղեք ստորև նշված սխալը։" + +msgid "Please correct the errors below." +msgstr "Ուղղեք ստորև նշված սխալները․" + +#, python-format +msgid "Enter a new password for the user %(username)s." +msgstr "" +"Մուտքագրեք նոր գաղտնաբառ %(username)s օգտագործողի համար։" + +msgid "Welcome," +msgstr "Բարի գալուստ, " + +msgid "View site" +msgstr "Դիտել կայքը" + +msgid "Documentation" +msgstr "Դոկումենտացիա" + +msgid "Log out" +msgstr "Դուրս գալ" + +#, python-format +msgid "Add %(name)s" +msgstr "Ավելացնել %(name)s" + +msgid "History" +msgstr "Պատմություն" + +msgid "View on site" +msgstr "Դիտել կայքում" + +msgid "Filter" +msgstr "Ֆիլտրել" + +msgid "Remove from sorting" +msgstr "Հեռացնել դասակարգումից" + +#, python-format +msgid "Sorting priority: %(priority_number)s" +msgstr "Դասակարգման առաջնություն՝ %(priority_number)s" + +msgid "Toggle sorting" +msgstr "Toggle sorting" + +msgid "Delete" +msgstr "Հեռացնել" + +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " +"related objects, but your account doesn't have permission to delete the " +"following types of objects:" +msgstr "" +"%(object_name)s '%(escaped_object)s'֊ի հեռացումը կարող է հանգեցնել նրա հետ " +"կապված օբյեկտների հեռացմանը, բայց դուք չունեք իրավունք հեռացնել այդ տիպի " +"օբյեկտներ․" + +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would require deleting the " +"following protected related objects:" +msgstr "" +"%(object_name)s '%(escaped_object)s'֊ը հեռացնելու համար կարող է անհրաժեշտ " +"լինել հեռացնել նրա հետ կապված պաշտպանված օբյեկտները։" + +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " +"All of the following related items will be deleted:" +msgstr "" +"Համոզված ե՞ք, որ ուզում եք հեռացնել %(object_name)s \"%(escaped_object)s\"֊" +"ը։ նրա հետ կապված այս բոլոր օբյեկտները կհեռացվեն․" + +msgid "Objects" +msgstr "Օբյեկտներ" + +msgid "Yes, I'm sure" +msgstr "Այո, ես համոզված եմ" + +msgid "No, take me back" +msgstr "Ոչ, տարեք ենձ ետ" + +msgid "Delete multiple objects" +msgstr "Հեռացնել մի քանի օբյեկտ" + +#, python-format +msgid "" +"Deleting the selected %(objects_name)s would result in deleting related " +"objects, but your account doesn't have permission to delete the following " +"types of objects:" +msgstr "" +"%(objects_name)s֊ների հեռացումը կարող է հանգեցնել նրա հետ կապված օբյեկտների " +"հեռացմանը, բայց դուք չունեք իրավունք հեռացնել այդ տիպի օբյեկտներ․" + +#, python-format +msgid "" +"Deleting the selected %(objects_name)s would require deleting the following " +"protected related objects:" +msgstr "" +"%(objects_name)s֊ը հեռացնելու համար կարող է անհրաժեշտ լինել հեռացնել նրա հետ " +"կապված պաշտպանված օբյեկտները։" + +#, python-format +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 "" +"Համոզված ե՞ք, որ ուզում եք հեռացնել նշված %(objects_name)s֊ները։ Այս բոլոր " +"օբյեկտները, ինչպես նաև նրանց հետ կապված օբյեկտները կհեռացվեն․" + +msgid "View" +msgstr "" + +msgid "Delete?" +msgstr "Հեռացնե՞լ" + +#, python-format +msgid " By %(filter_title)s " +msgstr "%(filter_title)s " + +msgid "Summary" +msgstr "Ամփոփում" + +#, python-format +msgid "Models in the %(name)s application" +msgstr " %(name)s հավելվածի մոդել" + +msgid "Add" +msgstr "Ավելացնել" + +msgid "You don't have permission to view or edit anything." +msgstr "" + +msgid "Recent actions" +msgstr "" + +msgid "My actions" +msgstr "" + +msgid "None available" +msgstr "Ոչինք չկա" + +msgid "Unknown content" +msgstr "Անհայտ կոնտենտ" + +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 "" +"Ինչ֊որ բան այն չէ ձեր տվյալների բազայի հետ։ Համոզվեք, որ համապատասխան " +"աղյուսակները ստեղծվել են և համոզվեք, որ համապատասխան օգտագործողը կարող է " +"կարդալ բազան։" + +#, 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 "" +"Դուք մուտք եք գործել որպես %(username)s, բայց իրավունք չունեք դիտելու այս " +"էջը։ Ցանկանում ե՞ք մուտք գործել որպես այլ օգտագործող" + +msgid "Forgotten your password or username?" +msgstr "Մոռացել ե՞ք օգտագործողի անունը կամ գաղտնաբառը" + +msgid "Date/time" +msgstr "Ամսաթիվ/Ժամանակ" + +msgid "User" +msgstr "Օգտագործող" + +msgid "Action" +msgstr "Գործողություն" + +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" +"Այս օբյեկտը չունի փոփոխման պատմություն։ Այն հավանաբար ավելացված չէ " +"ադմինիստրավորման էջից։" + +msgid "Show all" +msgstr "Ցույց տալ բոլորը" + +msgid "Save" +msgstr "Պահպանել" + +msgid "Popup closing..." +msgstr "Ելնող պատուհանը փակվում է" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Փոփոխել ընտրված %(model)s տիպի օբյեկտը" + +#, python-format +msgid "View selected %(model)s" +msgstr "" + +#, python-format +msgid "Add another %(model)s" +msgstr "Ավելացնել այլ %(model)s տիպի օբյեկտ" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Հեռացնել ընտրված %(model)s տիպի օբյեկտը" + +msgid "Search" +msgstr "Փնտրել" + +#, python-format +msgid "%(counter)s result" +msgid_plural "%(counter)s results" +msgstr[0] "%(counter)s արդյունք" +msgstr[1] "%(counter)s արդյունքներ" + +#, python-format +msgid "%(full_result_count)s total" +msgstr "%(full_result_count)s ընդհանուր" + +msgid "Save as new" +msgstr "Պահպանել որպես նոր" + +msgid "Save and add another" +msgstr "Պահպանել և ավելացնել նորը" + +msgid "Save and continue editing" +msgstr "Պահպանել և շարունակել խմբագրել" + +msgid "Save and view" +msgstr "" + +msgid "Close" +msgstr "" + +msgid "Thanks for spending some quality time with the Web site today." +msgstr "Շնորհակալություն մեր կայքում ինչ֊որ ժամանակ ծախսելու համար։" + +msgid "Log in again" +msgstr "Մուտք գործել նորից" + +msgid "Password change" +msgstr "Փոխել գաղտնաբառը" + +msgid "Your password was changed." +msgstr "Ձեր գաղտնաբառը փոխվել է" + +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 "" +"Մուտքագրեք ձեր հին գաղտնաբառը։ Անվտանգության նկատառումներով մուտքագրեք ձեր " +"նոր գաղտնաբառը երկու անգամ, որպեսզի մենք համոզված լինենք, որ այն ճիշտ է " +"հավաքված։" + +msgid "Change my password" +msgstr "Փոխել իմ գաղտնաբառը" + +msgid "Password reset" +msgstr "Գաղտնաբառի փոփոխում" + +msgid "Your password has been set. You may go ahead and log in now." +msgstr "Ձեր գաղտնաբառը պահպանված է․ Կարող եք մուտք գործել։" + +msgid "Password reset confirmation" +msgstr "Գաղտնաբառի փոփոխման հաստատում" + +msgid "" +"Please enter your new password twice so we can verify you typed it in " +"correctly." +msgstr "" +"Մուտքագրեք ձեր նոր գաղտնաբառը երկու անգամ, որպեսզի մենք համոզված լինենք, որ " +"այն ճիշտ է հավաքված։" + +msgid "New password:" +msgstr "Նոր գաղտնաբառ․" + +msgid "Confirm password:" +msgstr "Նոր գաղտնաբառը նորից․" + +msgid "" +"The password reset link was invalid, possibly because it has already been " +"used. Please request a new password reset." +msgstr "" +"Գաղտնաբառի փոփոխման հղում է սխալ է, հավանաբար այն արդեն օգտագործվել է․ Դուք " +"կարող եք ստանալ նոր հղում։" + +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 "" +"Մենք ուղարկեցինք ձեր էլեկտրոնային փոստի հասցեին գաղտնաբառը փոփոխելու " +"հրահանգներ․ Դուք շուտով կստանաք դրանք։" + +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 "" +"Եթե դուք չեք ստացել էլեկտրոնային նամակ, համոզվեք, որ հավաքել եք այն հասցեն, " +"որով գրանցվել եք և ստուգեք ձեր սպամի թղթապանակը։" + +#, python-format +msgid "" +"You're receiving this email because you requested a password reset for your " +"user account at %(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 "Thanks for using our site!" +msgstr "Շնորհակալություն մեր կայքից օգտվելու համար։" + +#, python-format +msgid "The %(site_name)s team" +msgstr "%(site_name)s կայքի թիմ" + +msgid "" +"Forgotten your password? Enter your email address below, and we'll email " +"instructions for setting a new one." +msgstr "" +"Մոռացել ե՞ք ձեր գաղտնաբառը Մուտքագրեք ձեր էլեկտրոնային փոստի հասցեն և մենք " +"կուղարկենք ձեզ հրահանգներ նորը ստանալու համար։" + +msgid "Email address:" +msgstr "Email հասցե․" + +msgid "Reset my password" +msgstr "Փոխել գաղտնաբառը" + +msgid "All dates" +msgstr "Բոլոր ամսաթվերը" + +#, python-format +msgid "Select %s" +msgstr "Ընտրեք %s" + +#, python-format +msgid "Select %s to change" +msgstr "Ընտրեք %s փոխելու համար" + +#, python-format +msgid "Select %s to view" +msgstr "" + +msgid "Date:" +msgstr "Ամսաթիվ․" + +msgid "Time:" +msgstr "Ժամանակ․" + +msgid "Lookup" +msgstr "Որոնում" + +msgid "Currently:" +msgstr "Հիմա․" + +msgid "Change:" +msgstr "Փոփոխել" diff --git a/django/contrib/admin/locale/hy/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/hy/LC_MESSAGES/djangojs.mo new file mode 100644 index 000000000000..b9a8fa2cff77 Binary files /dev/null and b/django/contrib/admin/locale/hy/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/hy/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/hy/LC_MESSAGES/djangojs.po new file mode 100644 index 000000000000..e209f5428cf8 --- /dev/null +++ b/django/contrib/admin/locale/hy/LC_MESSAGES/djangojs.po @@ -0,0 +1,219 @@ +# This file is distributed under the same license as the Django package. +# +# Translators: +# Ruben Harutyunov , 2018 +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-01-15 10:40+0100\n" +"Last-Translator: Ruben Harutyunov \n" +"Language-Team: Armenian (http://www.transifex.com/django/django/language/" +"hy/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: hy\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#, javascript-format +msgid "Available %s" +msgstr "Հասանելի %s" + +#, 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 "" +"Սա հասանելի %s ցուցակ է։ Դուք կարող եք ընտրել նրանցից որոշները ընտրելով " +"դրանք ստորև գտնվող վանդակում և սեղմելով երկու վանդակների միջև գտնվող \"Ընտրել" +"\" սլաքը։" + +#, javascript-format +msgid "Type into this box to filter down the list of available %s." +msgstr "Մուտքագրեք այս դաշտում հասանելի %s ցուցակը ֆիլտրելու համար։" + +msgid "Filter" +msgstr "Ֆիլտրել" + +msgid "Choose all" +msgstr "Ընտրել բոլորը" + +#, javascript-format +msgid "Click to choose all %s at once." +msgstr "Սեղմեք բոլոր %sը ընտրելու համար։" + +msgid "Choose" +msgstr "Ընտրել" + +msgid "Remove" +msgstr "Հեռացնել" + +#, javascript-format +msgid "Chosen %s" +msgstr "Ընտրված %s" + +#, 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 "" +"Սա հասանելի %sի ցուցակ է։ Դուք կարող եք հեռացնել նրանցից որոշները ընտրելով " +"դրանք ստորև գտնվող վանդակում և սեղմելով երկու վանդակների միջև գտնվող " +"\"Հեռացնել\" սլաքը։" + +msgid "Remove all" +msgstr "Հեռացնել բոլորը" + +#, javascript-format +msgid "Click to remove all chosen %s at once." +msgstr "Սեղմեք բոլոր %sը հեռացնելու համար։" + +msgid "%(sel)s of %(cnt)s selected" +msgid_plural "%(sel)s of %(cnt)s selected" +msgstr[0] "Ընտրված է %(cnt)s-ից %(sel)s-ը" +msgstr[1] "Ընտրված է %(cnt)s-ից %(sel)s-ը" + +msgid "" +"You have unsaved changes on individual editable fields. If you run an " +"action, your unsaved changes will be lost." +msgstr "" +"Դուք ունեք չպահպանված անհատական խմբագրելի դաշտեր։ Եթե դուք կատարեք " +"գործողությունը, ձեր չպահպանված փոփոխությունները կկորեն։" + +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 "" +"Դուք ընտրել եք գործողություն, բայց դեռ չեք պահպանել անհատական խմբագրելի " +"դաշտերի փոփոխությունները Սեղմեք OK պահպանելու համար։ Անհրաժեշտ կլինի " +"վերագործարկել գործողությունը" + +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 "" +"Դուք ընտրել եք գործողություն, բայց դեռ չեք կատարել որևէ անհատական խմբագրելի " +"դաշտերի փոփոխություն Ձեզ հավանաբար պետք է Կատարել կոճակը, Պահպանել կոճակի " +"փոխարեն" + +msgid "Now" +msgstr "Հիմա" + +msgid "Midnight" +msgstr "Կեսգիշեր" + +msgid "6 a.m." +msgstr "6 a.m." + +msgid "Noon" +msgstr "Կեսօր" + +msgid "6 p.m." +msgstr "6 p.m." + +#, javascript-format +msgid "Note: You are %s hour ahead of server time." +msgid_plural "Note: You are %s hours ahead of server time." +msgstr[0] "Ձեր ժամը առաջ է սերվերի ժամանակից %s ժամով" +msgstr[1] "Ձեր ժամը առաջ է սերվերի ժամանակից %s ժամով" + +#, javascript-format +msgid "Note: You are %s hour behind server time." +msgid_plural "Note: You are %s hours behind server time." +msgstr[0] "Ձեր ժամը հետ է սերվերի ժամանակից %s ժամով" +msgstr[1] "Ձեր ժամը հետ է սերվերի ժամանակից %s ժամով" + +msgid "Choose a Time" +msgstr "Ընտրեք ժամանակ" + +msgid "Choose a time" +msgstr "Ընտրեք ժամանակ" + +msgid "Cancel" +msgstr "Չեղարկել" + +msgid "Today" +msgstr "Այսօր" + +msgid "Choose a Date" +msgstr "Ընտրեք ամսաթիվ" + +msgid "Yesterday" +msgstr "Երեկ" + +msgid "Tomorrow" +msgstr "Վաղը" + +msgid "January" +msgstr "Հունվար" + +msgid "February" +msgstr "Փետրվար" + +msgid "March" +msgstr "Մարտ" + +msgid "April" +msgstr "Ապրիլ" + +msgid "May" +msgstr "Մայիս" + +msgid "June" +msgstr "Հունիս" + +msgid "July" +msgstr "Հուլիս" + +msgid "August" +msgstr "Օգոստոս" + +msgid "September" +msgstr "Սեպտեմբեր" + +msgid "October" +msgstr "Հոկտեմբեր" + +msgid "November" +msgstr "Նոյեմբեր" + +msgid "December" +msgstr "Դեկտեմբեր" + +msgctxt "one letter Sunday" +msgid "S" +msgstr "Կ" + +msgctxt "one letter Monday" +msgid "M" +msgstr "Ե" + +msgctxt "one letter Tuesday" +msgid "T" +msgstr "Ե" + +msgctxt "one letter Wednesday" +msgid "W" +msgstr "Չ" + +msgctxt "one letter Thursday" +msgid "T" +msgstr "Հ" + +msgctxt "one letter Friday" +msgid "F" +msgstr "ՈՒ" + +msgctxt "one letter Saturday" +msgid "S" +msgstr "Շ" + +msgid "Show" +msgstr "Ցույց տալ" + +msgid "Hide" +msgstr "Թաքցնել" diff --git a/django/contrib/admin/locale/ia/LC_MESSAGES/django.mo b/django/contrib/admin/locale/ia/LC_MESSAGES/django.mo index dc604af40399..06ddd422dc15 100644 Binary files a/django/contrib/admin/locale/ia/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/ia/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/ia/LC_MESSAGES/django.po b/django/contrib/admin/locale/ia/LC_MESSAGES/django.po index 497a70b091c8..f7986c9b3dfc 100644 --- a/django/contrib/admin/locale/ia/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/ia/LC_MESSAGES/django.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Interlingua (http://www.transifex.com/django/django/language/" "ia/)\n" @@ -205,8 +205,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "Le %(name)s \"%(obj)s\" ha essite delite con successo." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Le objecto %(name)s con le clave primari %(key)r non existe." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" diff --git a/django/contrib/admin/locale/ia/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/ia/LC_MESSAGES/djangojs.mo index c440389daed3..4c9eccce331d 100644 Binary files a/django/contrib/admin/locale/ia/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/ia/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/ia/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/ia/LC_MESSAGES/djangojs.po index 7e295660daa4..82850978131e 100644 --- a/django/contrib/admin/locale/ia/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/ia/LC_MESSAGES/djangojs.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Interlingua (http://www.transifex.com/django/django/language/" "ia/)\n" diff --git a/django/contrib/admin/locale/id/LC_MESSAGES/django.mo b/django/contrib/admin/locale/id/LC_MESSAGES/django.mo index e877e4b39e8f..26df0693bf9d 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 83d39c696384..f4b29b20029c 100644 --- a/django/contrib/admin/locale/id/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/id/LC_MESSAGES/django.po @@ -2,19 +2,19 @@ # # Translators: # Claude Paroz , 2014 -# Fery Setiawan , 2015-2016 +# Fery Setiawan , 2015-2019 # Jannis Leidel , 2011 # M Asep Indrayana , 2015 # oon arfiandwi , 2016 # rodin , 2011-2013 -# rodin , 2013-2015 +# rodin , 2013-2017 # Sutrisno Efendi , 2015 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-08-24 04:56+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-26 23:57+0000\n" "Last-Translator: Fery Setiawan \n" "Language-Team: Indonesian (http://www.transifex.com/django/django/language/" "id/)\n" @@ -93,6 +93,15 @@ msgstr "Tambahkan %(verbose_name)s lagi" msgid "Remove" msgstr "Hapus" +msgid "Addition" +msgstr "Tambahan" + +msgid "Change" +msgstr "Ubah" + +msgid "Deletion" +msgstr "Penghapusan" + msgid "action time" msgstr "waktu aksi" @@ -106,7 +115,7 @@ msgid "object id" msgstr "id objek" #. 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 "representasi objek" @@ -171,11 +180,11 @@ msgstr "" "Tekan \"Control\", atau \"Command\" pada Mac, untuk memilih lebih dari satu." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" -"{name} \"{obj}\" telah berhasil ditambahkan. Anda dapat menyuntingnya " -"kembali dibawah." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "{name} \"{obj}\" telah berhasil ditambahkan." + +msgid "You may edit it again below." +msgstr "Anda dapat menyunting itu kembali dibawah." #, python-brace-format msgid "" @@ -183,30 +192,33 @@ msgid "" "below." msgstr "" "{name} \"{obj}\" telah berhasil ditambahkan. Anda dapat menambahkan {name} " -"lain dibawah." +"lain di bawah." #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "{name} \"{obj}\" telah berhasil ditambahkan." +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." +msgstr "" +" {name} \"{obj}\" telah berhasil diubah. Anda dapat mengeditnya kembali di " +"bawah." #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." +"The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" -" {name} \"{obj}\" telah berhasil dirubah. Anda dapat menyuntingnya kembali " -"dibawah." +"{name} \"{obj}\" telah berhasil ditambahkan. Anda dapat mengeditnya kembali " +"di bawah." #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " "below." msgstr "" -"{name} \"{obj}\" telah berhasil dirubah. Anda dapat menambahkan {name} lain " -"dibawah." +"{name} \"{obj}\" telah berhasil diubah. Anda dapat menambahkan {name} lain " +"di bawah." #, python-brace-format msgid "The {name} \"{obj}\" was changed successfully." -msgstr "{name} \"{obj}\" telah berhasil dirubah." +msgstr "{name} \"{obj}\" telah berhasil diubah." msgid "" "Items must be selected in order to perform actions on them. No items have " @@ -222,8 +234,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" berhasil dihapus." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Objek %(name)s dengan kunci utama %(key)r tidak ada." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s dengan ID \"%(key)s\" tidak ada. Mungkin telah dihapus?" #, python-format msgid "Add %s" @@ -233,6 +245,10 @@ msgstr "Tambahkan %s" msgid "Change %s" msgstr "Ubah %s" +#, python-format +msgid "View %s" +msgstr "Melihat %s" + msgid "Database error" msgstr "Galat basis data" @@ -339,7 +355,7 @@ msgid "Change password" msgstr "Ganti sandi" msgid "Please correct the error below." -msgstr "Perbaiki galat di bawah ini." +msgstr "Harap perbaiki kesalahan dibawah." msgid "Please correct the errors below." msgstr "Perbaiki galat di bawah ini." @@ -450,8 +466,8 @@ msgstr "" "Yakin akan menghapus %(objects_name)s terpilih? Semua objek berikut beserta " "objek terkait juga akan dihapus:" -msgid "Change" -msgstr "Ubah" +msgid "View" +msgstr "Tampilan" msgid "Delete?" msgstr "Hapus?" @@ -470,11 +486,11 @@ msgstr "Model pada aplikasi %(name)s" msgid "Add" msgstr "Tambah" -msgid "You don't have permission to edit anything." -msgstr "Anda tidak memiliki izin untuk mengubah apapun." +msgid "You don't have permission to view or edit anything." +msgstr "Anda tidak mempunyai perizinan untuk melihat atau menyunting apapun." msgid "Recent actions" -msgstr "Tindakah terbaru" +msgstr "Tindakan terbaru" msgid "My actions" msgstr "Tindakan saya" @@ -498,8 +514,8 @@ 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 "" -"Anda dikenali sebagai %(username)s, tapi tidak diperbolehkan untuk mengakses " -"halaman ini. Anda ingin mencoba mengakses menggunakan akun yang lain?" +"Anda diautentikasi sebagai %(username)s, tapi tidak diperbolehkan untuk " +"mengakses halaman ini. Ingin mencoba mengakses menggunakan akun yang lain?" msgid "Forgotten your password or username?" msgstr "Lupa nama pengguna atau sandi?" @@ -526,21 +542,9 @@ msgstr "Tampilkan semua" msgid "Save" msgstr "Simpan" -msgid "Popup closing..." +msgid "Popup closing…" msgstr "Menutup popup..." -#, python-format -msgid "Change selected %(model)s" -msgstr "Ubah %(model)s yang dipilih" - -#, python-format -msgid "Add another %(model)s" -msgstr "Tambahkan %(model)s yang lain" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Hapus %(model)s yang dipilih" - msgid "Search" msgstr "Cari" @@ -562,6 +566,24 @@ msgstr "Simpan dan tambahkan lagi" msgid "Save and continue editing" msgstr "Simpan dan terus mengedit" +msgid "Save and view" +msgstr "Simpan dan tampilkan" + +msgid "Close" +msgstr "Tutup" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Ubah %(model)s yang dipilih" + +#, python-format +msgid "Add another %(model)s" +msgstr "Tambahkan %(model)s yang lain" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Hapus %(model)s yang dipilih" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Terima kasih telah menggunakan situs ini hari ini." @@ -618,8 +640,8 @@ 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 "" -"Kami mengirimi anda instruksi untuk pengubahan kata sandi, jika ada akun " -"dengan alamat email yang sesuai. Anda seharusnya menerimanya sesaat lagi." +"Kami mengirimi Anda petunjuk untuk mengubah kata sandi. Jika ada akun dengan " +"alamat email yang sesuai. Anda seharusnya menerimanya sesaat lagi." msgid "" "If you don't receive an email, please make sure you've entered the address " @@ -673,6 +695,10 @@ msgstr "Pilih %s" msgid "Select %s to change" msgstr "Pilih %s untuk diubah" +#, python-format +msgid "Select %s to view" +msgstr "Pilih %s untuk melihat" + msgid "Date:" msgstr "Tanggal:" diff --git a/django/contrib/admin/locale/id/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/id/LC_MESSAGES/djangojs.mo index 0bf816907ad1..6b7bff39c635 100644 Binary files a/django/contrib/admin/locale/id/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/id/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/id/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/id/LC_MESSAGES/djangojs.po index ac18cd79c23e..aa096df9e02d 100644 --- a/django/contrib/admin/locale/id/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/id/LC_MESSAGES/djangojs.po @@ -4,14 +4,14 @@ # Fery Setiawan , 2015-2016 # Jannis Leidel , 2011 # rodin , 2011-2012 -# rodin , 2014 +# rodin , 2014,2016 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-08-25 02:21+0000\n" -"Last-Translator: Fery Setiawan \n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-23 18:54+0000\n" +"Last-Translator: rodin \n" "Language-Team: Indonesian (http://www.transifex.com/django/django/language/" "id/)\n" "MIME-Version: 1.0\n" @@ -101,6 +101,21 @@ msgstr "" "Anda telah memilih sebuah aksi, tetapi belum mengubah bidang apapun. " "Kemungkinan Anda mencari tombol Buka dan bukan tombol Simpan." +msgid "Now" +msgstr "Sekarang" + +msgid "Midnight" +msgstr "Tengah malam" + +msgid "6 a.m." +msgstr "6 pagi" + +msgid "Noon" +msgstr "Siang" + +msgid "6 p.m." +msgstr "18.00" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." @@ -111,27 +126,12 @@ msgid "Note: You are %s hour behind server time." msgid_plural "Note: You are %s hours behind server time." msgstr[0] "Catatan: Waktu Anda lebih lambat %s jam dibandingkan waktu server." -msgid "Now" -msgstr "Sekarang" - msgid "Choose a Time" msgstr "Pilih Waktu" msgid "Choose a time" msgstr "Pilih waktu" -msgid "Midnight" -msgstr "Tengah malam" - -msgid "6 a.m." -msgstr "6 pagi" - -msgid "Noon" -msgstr "Siang" - -msgid "6 p.m." -msgstr "6 p.m" - msgid "Cancel" msgstr "Batal" @@ -185,27 +185,27 @@ msgstr "Desember" msgctxt "one letter Sunday" msgid "S" -msgstr "S" +msgstr "M" msgctxt "one letter Monday" msgid "M" -msgstr "M" +msgstr "S" msgctxt "one letter Tuesday" msgid "T" -msgstr "T" +msgstr "S" msgctxt "one letter Wednesday" msgid "W" -msgstr "W" +msgstr "R" msgctxt "one letter Thursday" msgid "T" -msgstr "T" +msgstr "K" msgctxt "one letter Friday" msgid "F" -msgstr "F" +msgstr "J" msgctxt "one letter Saturday" msgid "S" diff --git a/django/contrib/admin/locale/io/LC_MESSAGES/django.mo b/django/contrib/admin/locale/io/LC_MESSAGES/django.mo index e070a3131fb0..abe5bb50d40d 100644 Binary files a/django/contrib/admin/locale/io/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/io/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/io/LC_MESSAGES/django.po b/django/contrib/admin/locale/io/LC_MESSAGES/django.po index 0bc2b716805e..ddf09c2f05be 100644 --- a/django/contrib/admin/locale/io/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/io/LC_MESSAGES/django.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-20 01:58+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Ido (http://www.transifex.com/django/django/language/io/)\n" "MIME-Version: 1.0\n" @@ -206,8 +206,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "La %(name)s \"%(obj)s\" eliminesis sucesoze." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "La %(name)s objekto kun precipua klefo %(key)r ne existas." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" diff --git a/django/contrib/admin/locale/is/LC_MESSAGES/django.mo b/django/contrib/admin/locale/is/LC_MESSAGES/django.mo index d16b87406470..1e029ac8fd60 100644 Binary files a/django/contrib/admin/locale/is/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/is/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/is/LC_MESSAGES/django.po b/django/contrib/admin/locale/is/LC_MESSAGES/django.po index b2d3c00de34d..c6bbad91589c 100644 --- a/django/contrib/admin/locale/is/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/is/LC_MESSAGES/django.po @@ -1,16 +1,18 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Dagur Ammendrup , 2019 # Hafsteinn Einarsson , 2011-2012 # Jannis Leidel , 2011 # Kári Tristan Helgason , 2013 +# Thordur Sigurdsson , 2016-2019 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-06-05 12:48+0000\n" +"Last-Translator: Dagur Ammendrup \n" "Language-Team: Icelandic (http://www.transifex.com/django/django/language/" "is/)\n" "MIME-Version: 1.0\n" @@ -35,7 +37,7 @@ msgid "Delete selected %(verbose_name_plural)s" msgstr "Eyða völdum %(verbose_name_plural)s" msgid "Administration" -msgstr "" +msgstr "Vefstjórn" msgid "All" msgstr "Allt" @@ -65,10 +67,10 @@ msgid "This year" msgstr "Þetta ár" msgid "No date" -msgstr "" +msgstr "Engin dagsetning" msgid "Has date" -msgstr "" +msgstr "Hefur dagsetningu" #, python-format msgid "" @@ -88,20 +90,29 @@ msgstr "Bæta við öðrum %(verbose_name)s" msgid "Remove" msgstr "Fjarlægja" +msgid "Addition" +msgstr "Viðbót" + +msgid "Change" +msgstr "Breyta" + +msgid "Deletion" +msgstr "Eyðing" + msgid "action time" msgstr "tími aðgerðar" msgid "user" -msgstr "" +msgstr "notandi" msgid "content type" -msgstr "" +msgstr "efnistag" msgid "object id" msgstr "kenni hlutar" #. 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 "framsetning hlutar" @@ -119,40 +130,40 @@ msgstr "kladdafærslur" #, python-format msgid "Added \"%(object)s\"." -msgstr "\"%(object)s\" bætt við." +msgstr "„%(object)s“ bætt við." #, python-format msgid "Changed \"%(object)s\" - %(changes)s" -msgstr "Breytti \"%(object)s\" - %(changes)s" +msgstr "Breytti „%(object)s“ - %(changes)s" #, python-format msgid "Deleted \"%(object)s.\"" -msgstr "Eyddi \"%(object)s.\"" +msgstr "Eyddi „%(object)s.“" msgid "LogEntry Object" msgstr "LogEntry hlutur" #, python-brace-format msgid "Added {name} \"{object}\"." -msgstr "" +msgstr "Bætti við {name} „{object}“." msgid "Added." -msgstr "" +msgstr "Bætti við." msgid "and" msgstr "og" #, python-brace-format msgid "Changed {fields} for {name} \"{object}\"." -msgstr "" +msgstr "Breytti {fields} fyrir {name} „{object}“." #, python-brace-format msgid "Changed {fields}." -msgstr "" +msgstr "Breytti {fields}." #, python-brace-format msgid "Deleted {name} \"{object}\"." -msgstr "" +msgstr "Eyddi {name} „{object}“." msgid "No fields changed." msgstr "Engum reitum breytt." @@ -163,36 +174,43 @@ msgstr "Ekkert" msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "" +"Haltu inni „Control“, eða „Command“ á Mac til þess að velja fleira en eitt." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" +msgid "The {name} \"{obj}\" was added successfully." +msgstr "{name} „{obj}“ var bætt við." + +msgid "You may edit it again below." +msgstr "Þú mátt breyta þessu aftur hér að neðan." #, python-brace-format msgid "" "The {name} \"{obj}\" was added successfully. You may add another {name} " "below." msgstr "" +"{name} „{obj}“ hefur verið breytt. Þú getur bætt við öðru {name} að neðan." #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "" +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." +msgstr "{name} „{obj}“ hefur verið breytt. Þú getur breytt því aftur að neðan." #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." +"The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" +"{name} „{obj}“ hefur verið bætt við. Þú getur breytt því aftur að neðan." #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " "below." msgstr "" +"{name} \"{obj}\" hefur verið breytt. Þú getur bætt við öðru {name} að neðan." #, python-brace-format msgid "The {name} \"{obj}\" was changed successfully." -msgstr "" +msgstr "{name} „{obj}“ hefur verið breytt." msgid "" "Items must be selected in order to perform actions on them. No items have " @@ -209,8 +227,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s „%(obj)s“ var eytt." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(name)s hlutur með lykilinn %(key)r er ekki til." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s með ID \"%(key)s\" er ekki til. Var því mögulega eytt?" #, python-format msgid "Add %s" @@ -220,6 +238,10 @@ msgstr "Bæta við %s" msgid "Change %s" msgstr "Breyta %s" +#, python-format +msgid "View %s" +msgstr "Skoða %s" + msgid "Database error" msgstr "Gagnagrunnsvilla" @@ -254,6 +276,8 @@ msgid "" "Deleting %(class_name)s %(instance)s would require deleting the following " "protected related objects: %(related_objects)s" msgstr "" +"Að eyða %(class_name)s %(instance)s þyrfti að eyða eftirfarandi tengdum " +"hlutum: %(related_objects)s" msgid "Django site admin" msgstr "Django vefstjóri" @@ -269,7 +293,7 @@ msgstr "Skrá inn" #, python-format msgid "%(app)s administration" -msgstr "" +msgstr "%(app)s vefstjórn" msgid "Page not found" msgstr "Síða fannst ekki" @@ -293,6 +317,8 @@ 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 "" +"Villa kom upp. Hún hefur verið tilkynnt til vefstjóra með tölvupósti og ætti " +"að lagast fljótlega. Þökkum þolinmæðina." msgid "Run the selected action" msgstr "Keyra valda aðgerð" @@ -324,10 +350,10 @@ msgid "Change password" msgstr "Breyta lykilorði" msgid "Please correct the error below." -msgstr "Vinsamlegast leiðréttu villurnar hér að neðan:" +msgstr "Vinsamlegast lagfærðu villuna fyrir neðan." msgid "Please correct the errors below." -msgstr "" +msgstr "Vinsamlegast leiðréttu villurnar hér að neðan." #, python-format msgid "Enter a new password for the user %(username)s." @@ -337,7 +363,7 @@ msgid "Welcome," msgstr "Velkomin(n)," msgid "View site" -msgstr "" +msgstr "Skoða vef" msgid "Documentation" msgstr "Skjölun" @@ -385,7 +411,7 @@ msgid "" "Deleting the %(object_name)s '%(escaped_object)s' would require deleting the " "following protected related objects:" msgstr "" -"Að eyða %(object_name)s ' %(escaped_object)s ' þyrfti að eyða eftirfarandi " +"Að eyða %(object_name)s „%(escaped_object)s“ þyrfti að eyða eftirfarandi " "tengdum hlutum:" #, python-format @@ -397,13 +423,13 @@ msgstr "" "eftirfarandi verður eytt:" msgid "Objects" -msgstr "" +msgstr "Hlutir" msgid "Yes, I'm sure" msgstr "Já ég er viss." msgid "No, take me back" -msgstr "" +msgstr "Nei, fara til baka" msgid "Delete multiple objects" msgstr "Eyða mörgum hlutum." @@ -433,8 +459,8 @@ msgstr "" "Ertu viss um að þú viljir eyða völdum %(objects_name)s? Öllum eftirtöldum " "hlutum og skyldum hlutum verður eytt:" -msgid "Change" -msgstr "Breyta" +msgid "View" +msgstr "Skoða" msgid "Delete?" msgstr "Eyða?" @@ -444,23 +470,23 @@ msgid " By %(filter_title)s " msgstr " Eftir %(filter_title)s " msgid "Summary" -msgstr "" +msgstr "Samantekt" #, python-format msgid "Models in the %(name)s application" -msgstr "" +msgstr "Módel í appinu %(name)s" msgid "Add" msgstr "Bæta við" -msgid "You don't have permission to edit anything." -msgstr "Þú hefur ekki réttindi til að breyta neinu" +msgid "You don't have permission to view or edit anything." +msgstr "Þú hefur ekki réttindi til að skoða eða breyta neinu." msgid "Recent actions" -msgstr "" +msgstr "Nýlegar aðgerðir" msgid "My actions" -msgstr "" +msgstr "Mínar aðgerðir" msgid "None available" msgstr "Engin fáanleg" @@ -473,7 +499,7 @@ msgid "" "database tables have been created, and make sure the database is readable by " "the appropriate user." msgstr "" -"Eitthvað er að gagnagrunnsuppsetningu. Gakktu úr skuggum um að allar töflur " +"Eitthvað er að gagnagrunnsuppsetningu. Gakktu úr skugga um að allar töflur " "séu til staðar og að notandinn hafi aðgang að grunninum." #, python-format @@ -481,6 +507,8 @@ 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 "" +"Þú ert skráður inn sem %(username)s, en ert ekki með réttindi að þessari " +"síðu. Viltu skrá þig inn sem annar notandi?" msgid "Forgotten your password or username?" msgstr "Gleymt notandanafn eða lykilorð?" @@ -507,20 +535,8 @@ msgstr "Sýna allt" msgid "Save" msgstr "Vista" -msgid "Popup closing..." -msgstr "" - -#, python-format -msgid "Change selected %(model)s" -msgstr "" - -#, python-format -msgid "Add another %(model)s" -msgstr "" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "" +msgid "Popup closing…" +msgstr "Sprettigluggi lokast..." msgid "Search" msgstr "Leita" @@ -544,6 +560,24 @@ msgstr "Vista og búa til nýtt" msgid "Save and continue editing" msgstr "Vista og halda áfram að breyta" +msgid "Save and view" +msgstr "Vista og skoða" + +msgid "Close" +msgstr "Loka" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Breyta völdu %(model)s" + +#, python-format +msgid "Add another %(model)s" +msgstr "Bæta við %(model)s" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Eyða völdu %(model)s" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Takk fyrir að verja tíma í vefsíðuna í dag." @@ -600,17 +634,25 @@ 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 "" +"Við höfum sent þér tölvupóst með leiðbeiningum til að endurstilla lykilorðið " +"þitt, sé aðgangur til með netfanginu sem þú slóst inn. Þú ættir að fá " +"leiðbeiningarnar fljótlega. " 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 "" +"Ef þú færð ekki tölvupóstinn, gakktu úr skugga um að netfangið sem þú slóst " +"inn sé það sama og þú notaðir til að stofna aðganginn og að það hafi ekki " +"lent í spamsíu." #, python-format msgid "" "You're receiving this email because you requested a password reset for your " "user account at %(site_name)s." msgstr "" +"Þú ert að fá þennan tölvupóst því þú baðst um endurstillingu á lykilorði " +"fyrir aðganginn þinn á %(site_name)s." msgid "Please go to the following page and choose a new password:" msgstr "Vinsamlegast farðu á eftirfarandi síðu og veldu nýtt lykilorð:" @@ -629,9 +671,11 @@ msgid "" "Forgotten your password? Enter your email address below, and we'll email " "instructions for setting a new one." msgstr "" +"Hefurðu gleymt lykilorðinu þínu? Sláðu inn netfangið þitt hér að neðan og " +"við sendum þér tölvupóst með leiðbeiningum til að setja nýtt lykilorð. " msgid "Email address:" -msgstr "" +msgstr "Netfang:" msgid "Reset my password" msgstr "Endursstilla lykilorðið mitt" @@ -647,6 +691,10 @@ msgstr "Veldu %s" msgid "Select %s to change" msgstr "Veldu %s til að breyta" +#, python-format +msgid "Select %s to view" +msgstr "Veldu %s til að skoða" + msgid "Date:" msgstr "Dagsetning:" @@ -657,7 +705,7 @@ msgid "Lookup" msgstr "Fletta upp" msgid "Currently:" -msgstr "" +msgstr "Eins og er:" msgid "Change:" -msgstr "" +msgstr "Breyta:" diff --git a/django/contrib/admin/locale/is/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/is/LC_MESSAGES/djangojs.mo index a20f3e27475b..3f47b7b22ad2 100644 Binary files a/django/contrib/admin/locale/is/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/is/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/is/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/is/LC_MESSAGES/djangojs.po index 4960bd3daaab..847c39cea442 100644 --- a/django/contrib/admin/locale/is/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/is/LC_MESSAGES/djangojs.po @@ -4,13 +4,15 @@ # gudbergur , 2012 # Hafsteinn Einarsson , 2011-2012 # Jannis Leidel , 2011 +# Matt R, 2018 +# Thordur Sigurdsson , 2016-2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2018-05-18 14:09+0000\n" +"Last-Translator: Matt R\n" "Language-Team: Icelandic (http://www.transifex.com/django/django/language/" "is/)\n" "MIME-Version: 1.0\n" @@ -98,27 +100,9 @@ msgstr "" "Þú hefur valið aðgerð en hefur ekki gert breytingar á reitum. Þú ert líklega " "að leita að 'Fara' hnappnum frekar en 'Vista' hnappnum." -#, javascript-format -msgid "Note: You are %s hour ahead of server time." -msgid_plural "Note: You are %s hours ahead of server time." -msgstr[0] "" -msgstr[1] "" - -#, javascript-format -msgid "Note: You are %s hour behind server time." -msgid_plural "Note: You are %s hours behind server time." -msgstr[0] "" -msgstr[1] "" - msgid "Now" msgstr "Núna" -msgid "Choose a Time" -msgstr "" - -msgid "Choose a time" -msgstr "Veldu tíma" - msgid "Midnight" msgstr "Miðnætti" @@ -129,7 +113,25 @@ msgid "Noon" msgstr "Hádegi" msgid "6 p.m." -msgstr "" +msgstr "6 e.h." + +#, javascript-format +msgid "Note: You are %s hour ahead of server time." +msgid_plural "Note: You are %s hours ahead of server time." +msgstr[0] "Athugaðu að þú ert %s klukkustund á undan tíma vefþjóns." +msgstr[1] "Athugaðu að þú ert %s klukkustundum á undan tíma vefþjóns." + +#, javascript-format +msgid "Note: You are %s hour behind server time." +msgid_plural "Note: You are %s hours behind server time." +msgstr[0] "Athugaðu að þú ert %s klukkustund á eftir tíma vefþjóns." +msgstr[1] "Athugaðu að þú ert %s klukkustundum á eftir tíma vefþjóns." + +msgid "Choose a Time" +msgstr "Veldu tíma" + +msgid "Choose a time" +msgstr "Veldu tíma" msgid "Cancel" msgstr "Hætta við" @@ -138,7 +140,7 @@ msgid "Today" msgstr "Í dag" msgid "Choose a Date" -msgstr "" +msgstr "Veldu dagsetningu" msgid "Yesterday" msgstr "Í gær" @@ -147,68 +149,68 @@ msgid "Tomorrow" msgstr "Á morgun" msgid "January" -msgstr "" +msgstr "janúar" msgid "February" -msgstr "" +msgstr "febrúar" msgid "March" -msgstr "" +msgstr "mars" msgid "April" -msgstr "" +msgstr "apríl" msgid "May" -msgstr "" +msgstr "maí" msgid "June" -msgstr "" +msgstr "júní" msgid "July" -msgstr "" +msgstr "júlí" msgid "August" -msgstr "" +msgstr "ágúst" msgid "September" -msgstr "" +msgstr "september" msgid "October" -msgstr "" +msgstr "október" msgid "November" -msgstr "" +msgstr "nóvember" msgid "December" -msgstr "" +msgstr "desember" msgctxt "one letter Sunday" msgid "S" -msgstr "" +msgstr "S" msgctxt "one letter Monday" msgid "M" -msgstr "" +msgstr "M" msgctxt "one letter Tuesday" msgid "T" -msgstr "" +msgstr "Þ" msgctxt "one letter Wednesday" msgid "W" -msgstr "" +msgstr "M" msgctxt "one letter Thursday" msgid "T" -msgstr "" +msgstr "F" msgctxt "one letter Friday" msgid "F" -msgstr "" +msgstr "F" msgctxt "one letter Saturday" msgid "S" -msgstr "" +msgstr "L" msgid "Show" msgstr "Sýna" diff --git a/django/contrib/admin/locale/it/LC_MESSAGES/django.mo b/django/contrib/admin/locale/it/LC_MESSAGES/django.mo index 5251920844e8..72b2ffa20f79 100644 Binary files a/django/contrib/admin/locale/it/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/it/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/it/LC_MESSAGES/django.po b/django/contrib/admin/locale/it/LC_MESSAGES/django.po index 3a94c42c581a..d47979e0a0c2 100644 --- a/django/contrib/admin/locale/it/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/it/LC_MESSAGES/django.po @@ -1,11 +1,14 @@ # This file is distributed under the same license as the Django package. # # Translators: +# AndreiCR , 2017 +# Carlo Miron , 2018-2019 # Denis Darii , 2011 # Flavio Curella , 2013 # Jannis Leidel , 2011 # Luciano De Falco Alfano, 2016 # Marco Bonetti, 2014 +# Mirco Grillo , 2018 # Nicola Larosa , 2013 # palmux , 2014-2015 # Mattia Procopio , 2015 @@ -14,9 +17,9 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-06-18 09:39+0000\n" -"Last-Translator: palmux \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-02-19 10:24+0000\n" +"Last-Translator: Carlo Miron \n" "Language-Team: Italian (http://www.transifex.com/django/django/language/" "it/)\n" "MIME-Version: 1.0\n" @@ -94,6 +97,15 @@ msgstr "Aggiungi un altro %(verbose_name)s." msgid "Remove" msgstr "Elimina" +msgid "Addition" +msgstr "Aggiunta " + +msgid "Change" +msgstr "Modifica" + +msgid "Deletion" +msgstr "Eliminazione" + msgid "action time" msgstr "momento dell'azione" @@ -107,7 +119,7 @@ msgid "object id" msgstr "id dell'oggetto" #. 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 "rappr. dell'oggetto" @@ -172,11 +184,11 @@ msgstr "" "Tieni premuto \"Control\", o \"Command\" su Mac, per selezionarne più di uno." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" -"Il {name} \"{obj}\" è stato aggiunto con successo. Puoi modificarlo " -"nuovamente qui sotto." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "Il {name} \"{obj}\" è stato aggiunto con successo." + +msgid "You may edit it again below." +msgstr "Puoi modificarlo di nuovo qui sotto." #, python-brace-format msgid "" @@ -186,10 +198,6 @@ msgstr "" "Il {name} \"{obj}\" è stato aggiunto con successo. Puoi aggiungere un altro " "{name} qui sotto." -#, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "Il {name} \"{obj}\" è stato aggiunto con successo." - #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may edit it again below." @@ -197,6 +205,13 @@ msgstr "" "Il {name} \"{obj}\" è stato modificato con successo. Puoi modificarlo " "nuovamente qui sotto." +#, python-brace-format +msgid "" +"The {name} \"{obj}\" was added successfully. You may edit it again below." +msgstr "" +"Il {name} \"{obj}\" è stato aggiunto con successo. Puoi modificarlo " +"nuovamente qui sotto." + #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " @@ -224,8 +239,9 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" cancellato correttamente." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "L'oggetto %(name)s con chiave primaria %(key)r non esiste." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" +"%(name)s con ID \"%(key)s\" non esiste. Probabilmente sarà stato cancellato?" #, python-format msgid "Add %s" @@ -235,6 +251,10 @@ msgstr "Aggiungi %s" msgid "Change %s" msgstr "Modifica %s" +#, python-format +msgid "View %s" +msgstr "Vista %s" + msgid "Database error" msgstr "Errore del database" @@ -344,7 +364,7 @@ msgid "Change password" msgstr "Modifica password" msgid "Please correct the error below." -msgstr "Correggi l'errore qui sotto." +msgstr "Per favore, correggi l'errore sottostante" msgid "Please correct the errors below." msgstr "Correggi gli errori qui sotto." @@ -456,8 +476,8 @@ msgstr "" "Confermi la cancellazione dell'elemento %(objects_name)s selezionato? " "Saranno rimossi tutti i seguenti oggetti e le loro voci correlate:" -msgid "Change" -msgstr "Modifica" +msgid "View" +msgstr "Vista" msgid "Delete?" msgstr "Cancellare?" @@ -476,8 +496,8 @@ msgstr "Modelli nell'applicazione %(name)s" msgid "Add" msgstr "Aggiungi" -msgid "You don't have permission to edit anything." -msgstr "Non hai i privilegi per modificare nulla." +msgid "You don't have permission to view or edit anything." +msgstr "Non hai i permessi per visualizzare o modificare nulla" msgid "Recent actions" msgstr "Azioni recenti" @@ -533,21 +553,9 @@ msgstr "Mostra tutto" msgid "Save" msgstr "Salva" -msgid "Popup closing..." +msgid "Popup closing…" msgstr "Chiusura popup..." -#, python-format -msgid "Change selected %(model)s" -msgstr "Modifica la selezione %(model)s" - -#, python-format -msgid "Add another %(model)s" -msgstr "Aggiungi un altro %(model)s" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Elimina la selezione %(model)s" - msgid "Search" msgstr "Cerca" @@ -570,6 +578,24 @@ msgstr "Salva e aggiungi un altro" msgid "Save and continue editing" msgstr "Salva e continua le modifiche" +msgid "Save and view" +msgstr "Salva e visualizza" + +msgid "Close" +msgstr "Chiudi" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Modifica la selezione %(model)s" + +#, python-format +msgid "Add another %(model)s" +msgstr "Aggiungi un altro %(model)s" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Elimina la selezione %(model)s" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Grazie per aver speso il tuo tempo prezioso su questo sito oggi." @@ -681,6 +707,10 @@ msgstr "Scegli %s" msgid "Select %s to change" msgstr "Scegli %s da modificare" +#, python-format +msgid "Select %s to view" +msgstr "Seleziona %s per visualizzarlo" + msgid "Date:" msgstr "Data:" diff --git a/django/contrib/admin/locale/it/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/it/LC_MESSAGES/djangojs.mo index e940e7011407..85f5ce8e858a 100644 Binary files a/django/contrib/admin/locale/it/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/it/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/it/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/it/LC_MESSAGES/djangojs.po index b73154a56085..baa69c6b88d9 100644 --- a/django/contrib/admin/locale/it/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/it/LC_MESSAGES/djangojs.po @@ -12,8 +12,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-06-18 09:36+0000\n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-23 18:54+0000\n" "Last-Translator: palmux \n" "Language-Team: Italian (http://www.transifex.com/django/django/language/" "it/)\n" @@ -104,6 +104,21 @@ msgstr "" "Hai selezionato un'azione, e non hai ancora apportato alcuna modifica a " "campi singoli. Probabilmente stai cercando il pulsante Go, invece di Save." +msgid "Now" +msgstr "Adesso" + +msgid "Midnight" +msgstr "Mezzanotte" + +msgid "6 a.m." +msgstr "6 del mattino" + +msgid "Noon" +msgstr "Mezzogiorno" + +msgid "6 p.m." +msgstr "6 del pomeriggio" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." @@ -116,27 +131,12 @@ msgid_plural "Note: You are %s hours behind server time." msgstr[0] "Nota: Sei %s ora in ritardo rispetto al server." msgstr[1] "Nota: Sei %s ore in ritardo rispetto al server." -msgid "Now" -msgstr "Adesso" - msgid "Choose a Time" msgstr "Scegli un orario" msgid "Choose a time" msgstr "Scegli un orario" -msgid "Midnight" -msgstr "Mezzanotte" - -msgid "6 a.m." -msgstr "6 del mattino" - -msgid "Noon" -msgstr "Mezzogiorno" - -msgid "6 p.m." -msgstr "6 del pomeriggio" - msgid "Cancel" msgstr "Annulla" diff --git a/django/contrib/admin/locale/ja/LC_MESSAGES/django.mo b/django/contrib/admin/locale/ja/LC_MESSAGES/django.mo index 5a108106ce9e..2a968da0922b 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 c38fd0f4ea91..afa002d1a49c 100644 --- a/django/contrib/admin/locale/ja/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/ja/LC_MESSAGES/django.po @@ -1,17 +1,19 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Claude Paroz , 2016 # Jannis Leidel , 2011 -# Shinya Okano , 2012-2016 +# Shinichi Katsumata , 2019 +# Shinya Okano , 2012-2018 # Tetsuya Morimoto , 2011 # 上田慶祐 , 2015 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-26 16:20+0000\n" -"Last-Translator: Shinya Okano \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-19 07:00+0000\n" +"Last-Translator: Shinichi Katsumata \n" "Language-Team: Japanese (http://www.transifex.com/django/django/language/" "ja/)\n" "MIME-Version: 1.0\n" @@ -89,6 +91,15 @@ msgstr "%(verbose_name)s の追加" msgid "Remove" msgstr "削除" +msgid "Addition" +msgstr "追加" + +msgid "Change" +msgstr "変更" + +msgid "Deletion" +msgstr "削除" + msgid "action time" msgstr "操作時刻" @@ -102,7 +113,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 "オブジェクトの文字列表現" @@ -168,9 +179,11 @@ msgstr "" "Command キーを使ってください" #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "{name} \"{obj}\" を追加しました。続けて編集できます。" +msgid "The {name} \"{obj}\" was added successfully." +msgstr "{name} \"{obj}\" を追加しました。" + +msgid "You may edit it again below." +msgstr "以下で再度編集できます。" #, python-brace-format msgid "" @@ -178,15 +191,16 @@ msgid "" "below." msgstr "{name} \"{obj}\" を追加しました。 別の {name} を以下から追加できます。" -#, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -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 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} " @@ -211,8 +225,9 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" を削除しました。" #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "主キーが %(key)r である %(name)s オブジェクトは存在しません。" +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" +"ID \"%(key)s\" の%(name)sは見つかりませんでした。削除された可能性があります。" #, python-format msgid "Add %s" @@ -222,6 +237,10 @@ msgstr "%s を追加" msgid "Change %s" msgstr "%s を変更" +#, python-format +msgid "View %s" +msgstr "%sを表示" + msgid "Database error" msgstr "データベースエラー" @@ -439,8 +458,8 @@ msgstr "" "本当に選択した %(objects_name)s を削除しますか? 以下の全てのオブジェクトと関" "連する要素が削除されます:" -msgid "Change" -msgstr "変更" +msgid "View" +msgstr "表示" msgid "Delete?" msgstr "削除しますか?" @@ -459,8 +478,8 @@ msgstr "%(name)s アプリケーション内のモデル" msgid "Add" msgstr "追加" -msgid "You don't have permission to edit anything." -msgstr "変更のためのパーミッションがありません。" +msgid "You don't have permission to view or edit anything." +msgstr "表示または変更のためのパーミッションがありません。" msgid "Recent actions" msgstr "最近行った操作" @@ -515,21 +534,9 @@ msgstr "全件表示" msgid "Save" msgstr "保存" -msgid "Popup closing..." +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 "Search" msgstr "検索" @@ -551,6 +558,24 @@ msgstr "保存してもう一つ追加" msgid "Save and continue editing" 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 を削除" + msgid "Thanks for spending some quality time with the Web site today." msgstr "ご利用ありがとうございました。" @@ -659,6 +684,10 @@ msgstr "%s を選択" msgid "Select %s to change" msgstr "変更する %s を選択" +#, python-format +msgid "Select %s to view" +msgstr "表示する%sを選択" + msgid "Date:" msgstr "日付:" diff --git a/django/contrib/admin/locale/ja/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/ja/LC_MESSAGES/djangojs.mo index f0c2147af440..24824f82dc96 100644 Binary files a/django/contrib/admin/locale/ja/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/ja/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/ja/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/ja/LC_MESSAGES/djangojs.po index c44c6399b877..3768547cd480 100644 --- a/django/contrib/admin/locale/ja/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/ja/LC_MESSAGES/djangojs.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-26 17:05+0000\n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Shinya Okano \n" "Language-Team: Japanese (http://www.transifex.com/django/django/language/" "ja/)\n" @@ -96,6 +96,21 @@ msgstr "" "操作を選択しましたが、フィールドに変更はありませんでした。もしかして保存ボタ" "ンではなくて実行ボタンをお探しですか。" +msgid "Now" +msgstr "現在" + +msgid "Midnight" +msgstr "0時" + +msgid "6 a.m." +msgstr "午前 6 時" + +msgid "Noon" +msgstr "12時" + +msgid "6 p.m." +msgstr "午後 6 時" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." @@ -106,27 +121,12 @@ msgid "Note: You are %s hour behind server time." msgid_plural "Note: You are %s hours behind server time." msgstr[0] "ノート: あなたの環境はサーバー時間より、%s時間遅れています。" -msgid "Now" -msgstr "現在" - msgid "Choose a Time" msgstr "時間を選択" msgid "Choose a time" msgstr "時間を選択" -msgid "Midnight" -msgstr "0時" - -msgid "6 a.m." -msgstr "午前 6 時" - -msgid "Noon" -msgstr "12時" - -msgid "6 p.m." -msgstr "午後 6 時" - msgid "Cancel" msgstr "キャンセル" diff --git a/django/contrib/admin/locale/ka/LC_MESSAGES/django.mo b/django/contrib/admin/locale/ka/LC_MESSAGES/django.mo index b7366133a34e..ed45180dd722 100644 Binary files a/django/contrib/admin/locale/ka/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/ka/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/ka/LC_MESSAGES/django.po b/django/contrib/admin/locale/ka/LC_MESSAGES/django.po index 3c8a7e91c536..75aee9c582a5 100644 --- a/django/contrib/admin/locale/ka/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/ka/LC_MESSAGES/django.po @@ -2,22 +2,22 @@ # # Translators: # André Bouatchidzé , 2013-2015 -# avsd05 , 2011 +# David A. , 2011 # Jannis Leidel , 2011 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 00:36+0000\n" +"Last-Translator: Ramiro Morales\n" "Language-Team: Georgian (http://www.transifex.com/django/django/language/" "ka/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ka\n" -"Plural-Forms: nplurals=1; plural=0;\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" #, python-format msgid "Successfully deleted %(count)d %(items)s." @@ -88,6 +88,15 @@ msgstr "კიდევ ერთი %(verbose_name)s-ის დამატე msgid "Remove" msgstr "წაშლა" +msgid "Addition" +msgstr "" + +msgid "Change" +msgstr "შეცვლა" + +msgid "Deletion" +msgstr "" + msgid "action time" msgstr "მოქმედების დრო" @@ -101,7 +110,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 "ობიექტის წარმ." @@ -165,8 +174,10 @@ msgid "" msgstr "" #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "" + +msgid "You may edit it again below." msgstr "" #, python-brace-format @@ -176,12 +187,13 @@ msgid "" msgstr "" #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." +"The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" #, python-brace-format @@ -209,8 +221,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" წარმატებით წაიშალა." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(name)s-ის ობიექტი პირველადი გასაღებით %(key)r არ არსებობს." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" @@ -220,6 +232,10 @@ msgstr "დავამატოთ %s" msgid "Change %s" msgstr "შევცვალოთ %s" +#, python-format +msgid "View %s" +msgstr "" + msgid "Database error" msgstr "მონაცემთა ბაზის შეცდომა" @@ -227,11 +243,13 @@ msgstr "მონაცემთა ბაზის შეცდომა" msgid "%(count)s %(name)s was changed successfully." msgid_plural "%(count)s %(name)s were changed successfully." msgstr[0] "%(count)s %(name)s წარმატებით შეიცვალა." +msgstr[1] "%(count)s %(name)s წარმატებით შეიცვალა." #, python-format msgid "%(total_count)s selected" msgid_plural "All %(total_count)s selected" msgstr[0] "%(total_count)s-ია არჩეული" +msgstr[1] "%(total_count)s-ია არჩეული" #, python-format msgid "0 of %(cnt)s selected" @@ -324,7 +342,7 @@ msgid "Change password" msgstr "პაროლის შეცვლა" msgid "Please correct the error below." -msgstr "გთხოვთ, გაასწოროთ შეცდომები." +msgstr "" msgid "Please correct the errors below." msgstr "გთხოვთ, შეასწოროთ ქვემოთმოყვანილი შეცდომები." @@ -435,8 +453,8 @@ msgstr "" "დარწმუნებული ხართ, რომ გსურთ %(objects_name)s ობიექტის წაშლა? ყველა შემდეგი " "ობიექტი, და მათზე დამოკიდებული ჩანაწერები წაშლილი იქნება:" -msgid "Change" -msgstr "შეცვლა" +msgid "View" +msgstr "" msgid "Delete?" msgstr "წავშალოთ?" @@ -455,8 +473,8 @@ msgstr "მოდელები %(name)s აპლიკაციაში" msgid "Add" msgstr "დამატება" -msgid "You don't have permission to edit anything." -msgstr "თქვენ არა გაქვთ რედაქტირების უფლება." +msgid "You don't have permission to view or edit anything." +msgstr "" msgid "Recent actions" msgstr "" @@ -510,21 +528,9 @@ msgstr "ვაჩვენოთ ყველა" msgid "Save" msgstr "შევინახოთ" -msgid "Popup closing..." +msgid "Popup closing…" msgstr "" -#, python-format -msgid "Change selected %(model)s" -msgstr "მონიშნული %(model)s-ის შეცვლა" - -#, python-format -msgid "Add another %(model)s" -msgstr "" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "მონიშნული %(model)s-ის წაშლა" - msgid "Search" msgstr "ძებნა" @@ -532,6 +538,7 @@ msgstr "ძებნა" msgid "%(counter)s result" msgid_plural "%(counter)s results" msgstr[0] "%(counter)s შედეგი" +msgstr[1] "%(counter)s შედეგი" #, python-format msgid "%(full_result_count)s total" @@ -546,6 +553,24 @@ msgstr "შევინახოთ და დავამატოთ ახა msgid "Save and continue editing" 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 "" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "მონიშნული %(model)s-ის წაშლა" + msgid "Thanks for spending some quality time with the Web site today." msgstr "გმადლობთ, რომ დღეს ამ საიტთან მუშაობას დაუთმეთ დრო." @@ -654,6 +679,10 @@ msgstr "ავირჩიოთ %s" msgid "Select %s to change" msgstr "აირჩიეთ %s შესაცვლელად" +#, python-format +msgid "Select %s to view" +msgstr "" + msgid "Date:" msgstr "თარიღი;" diff --git a/django/contrib/admin/locale/ka/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/ka/LC_MESSAGES/djangojs.mo index 4ee30d20a8e3..a66299c892fe 100644 Binary files a/django/contrib/admin/locale/ka/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/ka/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/ka/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/ka/LC_MESSAGES/djangojs.po index 443836c25727..65ee60f06052 100644 --- a/django/contrib/admin/locale/ka/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/ka/LC_MESSAGES/djangojs.po @@ -2,14 +2,14 @@ # # Translators: # André Bouatchidzé , 2013,2015 -# avsd05 , 2011 +# David A. , 2011 # Jannis Leidel , 2011 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Georgian (http://www.transifex.com/django/django/language/" "ka/)\n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ka\n" -"Plural-Forms: nplurals=1; plural=0;\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" #, javascript-format msgid "Available %s" @@ -74,6 +74,7 @@ msgstr "დააწკაპუნეთ ყველა არჩეული msgid "%(sel)s of %(cnt)s selected" msgid_plural "%(sel)s of %(cnt)s selected" msgstr[0] "%(cnt)s-დან არჩეულია %(sel)s" +msgstr[1] "%(cnt)s-დან არჩეულია %(sel)s" msgid "" "You have unsaved changes on individual editable fields. If you run an " @@ -98,18 +99,32 @@ msgstr "" "აგირჩევიათ მოქმედება, მაგრამ ცალკეულ ველებში ცვლილებები არ გაგიკეთებიათ! " "სავარაუდოდ, ეძებთ ღილაკს \"Go\", და არა \"შენახვა\"" +msgid "Now" +msgstr "ახლა" + +msgid "Midnight" +msgstr "შუაღამე" + +msgid "6 a.m." +msgstr "დილის 6 სთ" + +msgid "Noon" +msgstr "შუადღე" + +msgid "6 p.m." +msgstr "" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." msgstr[0] "შენიშვნა: თქვენ ხართ %s საათით წინ სერვერის დროზე." +msgstr[1] "შენიშვნა: თქვენ ხართ %s საათით წინ სერვერის დროზე." #, javascript-format msgid "Note: You are %s hour behind server time." msgid_plural "Note: You are %s hours behind server time." msgstr[0] "შენიშვნა: თქვენ ხართ %s საათით უკან სერვერის დროზე." - -msgid "Now" -msgstr "ახლა" +msgstr[1] "შენიშვნა: თქვენ ხართ %s საათით უკან სერვერის დროზე." msgid "Choose a Time" msgstr "" @@ -117,18 +132,6 @@ msgstr "" msgid "Choose a time" msgstr "ავირჩიოთ დრო" -msgid "Midnight" -msgstr "შუაღამე" - -msgid "6 a.m." -msgstr "დილის 6 სთ" - -msgid "Noon" -msgstr "შუადღე" - -msgid "6 p.m." -msgstr "" - msgid "Cancel" msgstr "უარი" diff --git a/django/contrib/admin/locale/kab/LC_MESSAGES/django.mo b/django/contrib/admin/locale/kab/LC_MESSAGES/django.mo new file mode 100644 index 000000000000..d095721bce64 Binary files /dev/null and b/django/contrib/admin/locale/kab/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/kab/LC_MESSAGES/django.po b/django/contrib/admin/locale/kab/LC_MESSAGES/django.po new file mode 100644 index 000000000000..b3d89582e878 --- /dev/null +++ b/django/contrib/admin/locale/kab/LC_MESSAGES/django.po @@ -0,0 +1,631 @@ +# This file is distributed under the same license as the Django package. +# +# Translators: +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-10-06 11:59+0000\n" +"Last-Translator: Muḥend Belqasem \n" +"Language-Team: Kabyle (http://www.transifex.com/django/django/language/" +"kab/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: kab\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#, python-format +msgid "Successfully deleted %(count)d %(items)s." +msgstr "" + +#, python-format +msgid "Cannot delete %(name)s" +msgstr "" + +msgid "Are you sure?" +msgstr "Tebɣiḍ?" + +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "" + +msgid "Administration" +msgstr "Tadbelt" + +msgid "All" +msgstr "Akkw" + +msgid "Yes" +msgstr "Ih" + +msgid "No" +msgstr "Uhu" + +msgid "Unknown" +msgstr "Arussin" + +msgid "Any date" +msgstr "Yal azemz" + +msgid "Today" +msgstr "Ass-a" + +msgid "Past 7 days" +msgstr "Di 7 n wussan ineggura" + +msgid "This month" +msgstr "Aggur-agi" + +msgid "This year" +msgstr "Aseggass-agi" + +msgid "No date" +msgstr "Ulac azemz" + +msgid "Has date" +msgstr "Ɣur-s azemz" + +#, python-format +msgid "" +"Please enter the correct %(username)s and password for a staff account. Note " +"that both fields may be case-sensitive." +msgstr "" + +msgid "Action:" +msgstr "Tigawt:" + +#, python-format +msgid "Add another %(verbose_name)s" +msgstr "" + +msgid "Remove" +msgstr "Kkes" + +msgid "action time" +msgstr "akud n tigawt" + +msgid "user" +msgstr "aseqdac" + +msgid "content type" +msgstr "anaw n ugbur" + +msgid "object id" +msgstr "asulay n tɣawsa" + +#. Translators: 'repr' means representation +#. (https://docs.python.org/3/library/functions.html#repr) +msgid "object repr" +msgstr "" + +msgid "action flag" +msgstr "anay n tigawt" + +msgid "change message" +msgstr "" + +msgid "log entry" +msgstr "anekcum n uɣmis" + +msgid "log entries" +msgstr "inekcam n uɣmis" + +#, python-format +msgid "Added \"%(object)s\"." +msgstr "" + +#, python-format +msgid "Changed \"%(object)s\" - %(changes)s" +msgstr "" + +#, python-format +msgid "Deleted \"%(object)s.\"" +msgstr "" + +msgid "LogEntry Object" +msgstr "" + +#, python-brace-format +msgid "Added {name} \"{object}\"." +msgstr "" + +msgid "Added." +msgstr "yettwarna." + +msgid "and" +msgstr "akked" + +#, python-brace-format +msgid "Changed {fields} for {name} \"{object}\"." +msgstr "" + +#, python-brace-format +msgid "Changed {fields}." +msgstr "" + +#, python-brace-format +msgid "Deleted {name} \"{object}\"." +msgstr "" + +msgid "No fields changed." +msgstr "" + +msgid "None" +msgstr "Ula yiwen" + +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +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 added successfully. You may add another {name} " +"below." +msgstr "" + +#, python-brace-format +msgid "The {name} \"{obj}\" was added successfully." +msgstr "" + +#, python-brace-format +msgid "" +"The {name} \"{obj}\" was changed 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 "" + +#, python-brace-format +msgid "The {name} \"{obj}\" was changed successfully." +msgstr "" + +msgid "" +"Items must be selected in order to perform actions on them. No items have " +"been changed." +msgstr "" + +msgid "No action selected." +msgstr "" + +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "" + +#, python-format +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" + +#, python-format +msgid "Add %s" +msgstr "Rnu %s" + +#, python-format +msgid "Change %s" +msgstr "" + +msgid "Database error" +msgstr "Agul n database" + +#, python-format +msgid "%(count)s %(name)s was changed successfully." +msgid_plural "%(count)s %(name)s were changed successfully." +msgstr[0] "" +msgstr[1] "" + +#, python-format +msgid "%(total_count)s selected" +msgid_plural "All %(total_count)s selected" +msgstr[0] "" +msgstr[1] "" + +#, python-format +msgid "0 of %(cnt)s selected" +msgstr "" + +#, python-format +msgid "Change history: %s" +msgstr "" + +#. Translators: Model verbose name and instance representation, +#. suitable to be an item in a list. +#, python-format +msgid "%(class_name)s %(instance)s" +msgstr "" + +#, python-format +msgid "" +"Deleting %(class_name)s %(instance)s would require deleting the following " +"protected related objects: %(related_objects)s" +msgstr "" + +msgid "Django site admin" +msgstr "" + +msgid "Django administration" +msgstr "" + +msgid "Site administration" +msgstr "Asmel n tedbelt" + +msgid "Log in" +msgstr "Kcem" + +#, python-format +msgid "%(app)s administration" +msgstr "" + +msgid "Page not found" +msgstr "Asebtar ulac-it" + +msgid "We're sorry, but the requested page could not be found." +msgstr "Ad nesḥissef imi asebter i d-sutreḍ ulac-it." + +msgid "Home" +msgstr "Agejdan" + +msgid "Server error" +msgstr "Tuccḍa n uqeddac" + +msgid "Server error (500)" +msgstr "" + +msgid "Server Error (500)" +msgstr "" + +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 "" + +msgid "Run the selected action" +msgstr "" + +msgid "Go" +msgstr "Ẓer" + +msgid "Click here to select the objects across all pages" +msgstr "" + +#, python-format +msgid "Select all %(total_count)s %(module_name)s" +msgstr "" + +msgid "Clear selection" +msgstr "" + +msgid "" +"First, enter a username and password. Then, you'll be able to edit more user " +"options." +msgstr "" + +msgid "Enter a username and password." +msgstr "" + +msgid "Change password" +msgstr "Beddel awal n tbaḍnit" + +msgid "Please correct the error below." +msgstr "" + +msgid "Please correct the errors below." +msgstr "" + +#, python-format +msgid "Enter a new password for the user %(username)s." +msgstr "" + +msgid "Welcome," +msgstr "Anṣuf," + +msgid "View site" +msgstr "Wali asmel" + +msgid "Documentation" +msgstr "Tasemlit" + +msgid "Log out" +msgstr "Asenser" + +#, python-format +msgid "Add %(name)s" +msgstr "" + +msgid "History" +msgstr "Amazray" + +msgid "View on site" +msgstr "Wali deg usmel" + +msgid "Filter" +msgstr "Tastayt" + +msgid "Remove from sorting" +msgstr "" + +#, python-format +msgid "Sorting priority: %(priority_number)s" +msgstr "" + +msgid "Toggle sorting" +msgstr "" + +msgid "Delete" +msgstr "Mḥu" + +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " +"related objects, but your account doesn't have permission to delete the " +"following types of objects:" +msgstr "" + +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would require deleting the " +"following protected related objects:" +msgstr "" + +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " +"All of the following related items will be deleted:" +msgstr "" + +msgid "Objects" +msgstr "Tiɣawsiwin" + +msgid "Yes, I'm sure" +msgstr "" + +msgid "No, take me back" +msgstr "" + +msgid "Delete multiple objects" +msgstr "" + +#, python-format +msgid "" +"Deleting the selected %(objects_name)s would result in deleting related " +"objects, but your account doesn't have permission to delete the following " +"types of objects:" +msgstr "" + +#, python-format +msgid "" +"Deleting the selected %(objects_name)s would require deleting the following " +"protected related objects:" +msgstr "" + +#, python-format +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 "" + +msgid "Change" +msgstr "Beddel" + +msgid "Delete?" +msgstr "Kkes?" + +#, python-format +msgid " By %(filter_title)s " +msgstr "" + +msgid "Summary" +msgstr "Agzul" + +#, python-format +msgid "Models in the %(name)s application" +msgstr "" + +msgid "Add" +msgstr "Rnu" + +msgid "You don't have permission to edit anything." +msgstr "" + +msgid "Recent actions" +msgstr "" + +msgid "My actions" +msgstr "Tigawin-iw" + +msgid "None available" +msgstr "" + +msgid "Unknown content" +msgstr "" + +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 "" + +#, 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 "" + +msgid "Forgotten your password or username?" +msgstr "" + +msgid "Date/time" +msgstr "Azemz/asrag" + +msgid "User" +msgstr "Amseqdac" + +msgid "Action" +msgstr "Tigawt" + +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" + +msgid "Show all" +msgstr "Sken akk" + +msgid "Save" +msgstr "Sekles" + +msgid "Popup closing..." +msgstr "" + +#, python-format +msgid "Change selected %(model)s" +msgstr "" + +#, python-format +msgid "Add another %(model)s" +msgstr "" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "" + +msgid "Search" +msgstr "Anadi" + +#, python-format +msgid "%(counter)s result" +msgid_plural "%(counter)s results" +msgstr[0] "" +msgstr[1] "" + +#, python-format +msgid "%(full_result_count)s total" +msgstr "" + +msgid "Save as new" +msgstr "Sekles d amaynut:" + +msgid "Save and add another" +msgstr "" + +msgid "Save and continue editing" +msgstr "" + +msgid "Thanks for spending some quality time with the Web site today." +msgstr "" + +msgid "Log in again" +msgstr "" + +msgid "Password change" +msgstr "Abeddel n wawal uffir" + +msgid "Your password was changed." +msgstr "" + +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 "" + +msgid "Change my password" +msgstr "" + +msgid "Password reset" +msgstr "Awennez n wawal uffir" + +msgid "Your password has been set. You may go ahead and log in now." +msgstr "" + +msgid "Password reset confirmation" +msgstr "Asentem n uwennez n wawal uffir" + +msgid "" +"Please enter your new password twice so we can verify you typed it in " +"correctly." +msgstr "" + +msgid "New password:" +msgstr "Awal n tbaḍnit amaynut:" + +msgid "Confirm password:" +msgstr "Sentem awal uffir" + +msgid "" +"The password reset link was invalid, possibly because it has already been " +"used. Please request a new password reset." +msgstr "" + +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 "" + +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 "" + +#, python-format +msgid "" +"You're receiving this email because you requested a password reset for your " +"user account at %(site_name)s." +msgstr "" + +msgid "Please go to the following page and choose a new password:" +msgstr "" + +msgid "Your username, in case you've forgotten:" +msgstr "" + +msgid "Thanks for using our site!" +msgstr "" + +#, python-format +msgid "The %(site_name)s team" +msgstr "" + +msgid "" +"Forgotten your password? Enter your email address below, and we'll email " +"instructions for setting a new one." +msgstr "" + +msgid "Email address:" +msgstr "Tansa e-mail :" + +msgid "Reset my password" +msgstr "Wennez awal-iw uffir" + +msgid "All dates" +msgstr "Izemzen merra" + +#, python-format +msgid "Select %s" +msgstr "Fren %s" + +#, python-format +msgid "Select %s to change" +msgstr "" + +msgid "Date:" +msgstr "Azemz:" + +msgid "Time:" +msgstr "Akud:" + +msgid "Lookup" +msgstr "Anadi" + +msgid "Currently:" +msgstr "Tura:" + +msgid "Change:" +msgstr "Beddel:" diff --git a/django/contrib/admin/locale/kab/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/kab/LC_MESSAGES/djangojs.mo new file mode 100644 index 000000000000..755849a2d60e Binary files /dev/null and b/django/contrib/admin/locale/kab/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/kab/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/kab/LC_MESSAGES/djangojs.po new file mode 100644 index 000000000000..57f70c99ea32 --- /dev/null +++ b/django/contrib/admin/locale/kab/LC_MESSAGES/djangojs.po @@ -0,0 +1,204 @@ +# This file is distributed under the same license as the Django package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-05-17 23:12+0200\n" +"PO-Revision-Date: 2017-10-06 08:10+0000\n" +"Last-Translator: Muḥend Belqasem \n" +"Language-Team: Kabyle (http://www.transifex.com/django/django/language/" +"kab/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: kab\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#, javascript-format +msgid "Available %s" +msgstr "Yella %s" + +#, 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 "" + +#, javascript-format +msgid "Type into this box to filter down the list of available %s." +msgstr "" + +msgid "Filter" +msgstr "Tastayt" + +msgid "Choose all" +msgstr "Fren akk" + +#, javascript-format +msgid "Click to choose all %s at once." +msgstr "" + +msgid "Choose" +msgstr "Fren" + +msgid "Remove" +msgstr "kkes" + +#, javascript-format +msgid "Chosen %s" +msgstr "Ifren %s" + +#, 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 "" + +msgid "Remove all" +msgstr "Kkes akk" + +#, javascript-format +msgid "Click to remove all chosen %s at once." +msgstr "" + +msgid "%(sel)s of %(cnt)s selected" +msgid_plural "%(sel)s of %(cnt)s selected" +msgstr[0] "%(sel)s si %(cnt)s yettwafren" +msgstr[1] "%(sel)s si %(cnt)s ttwafernen" + +msgid "" +"You have unsaved changes on individual editable fields. If you run an " +"action, your unsaved changes will be lost." +msgstr "" + +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 "" + +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 "" + +#, javascript-format +msgid "Note: You are %s hour ahead of server time." +msgid_plural "Note: You are %s hours ahead of server time." +msgstr[0] "" +msgstr[1] "" + +#, javascript-format +msgid "Note: You are %s hour behind server time." +msgid_plural "Note: You are %s hours behind server time." +msgstr[0] "" +msgstr[1] "" + +msgid "Now" +msgstr "Tura" + +msgid "Choose a Time" +msgstr "Fren akud:" + +msgid "Choose a time" +msgstr "Fren akud" + +msgid "Midnight" +msgstr "Ttnaṣfa n yiḍ" + +msgid "6 a.m." +msgstr "6 f.t." + +msgid "Noon" +msgstr "Ttnaṣfa n uzal" + +msgid "6 p.m." +msgstr "6 m.d." + +msgid "Cancel" +msgstr "Sefsex" + +msgid "Today" +msgstr "Ass-a" + +msgid "Choose a Date" +msgstr "Fren azemz" + +msgid "Yesterday" +msgstr "Iḍelli" + +msgid "Tomorrow" +msgstr "Azekka" + +msgid "January" +msgstr "Yennayer" + +msgid "February" +msgstr "Fuṛaṛ" + +msgid "March" +msgstr "Meɣres" + +msgid "April" +msgstr "Yebrir" + +msgid "May" +msgstr "Mayyu" + +msgid "June" +msgstr "Yunyu" + +msgid "July" +msgstr "Yulyu" + +msgid "August" +msgstr "Ɣuct" + +msgid "September" +msgstr "Ctamber" + +msgid "October" +msgstr "Tuber" + +msgid "November" +msgstr "Wamber" + +msgid "December" +msgstr "Dujamber" + +msgctxt "one letter Sunday" +msgid "S" +msgstr "" + +msgctxt "one letter Monday" +msgid "M" +msgstr "" + +msgctxt "one letter Tuesday" +msgid "T" +msgstr "" + +msgctxt "one letter Wednesday" +msgid "W" +msgstr "" + +msgctxt "one letter Thursday" +msgid "T" +msgstr "" + +msgctxt "one letter Friday" +msgid "F" +msgstr "" + +msgctxt "one letter Saturday" +msgid "S" +msgstr "" + +msgid "Show" +msgstr "Sken" + +msgid "Hide" +msgstr "Ffer" diff --git a/django/contrib/admin/locale/kk/LC_MESSAGES/django.mo b/django/contrib/admin/locale/kk/LC_MESSAGES/django.mo index 150d81a1ed6b..abc3c54e8bdd 100644 Binary files a/django/contrib/admin/locale/kk/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/kk/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/kk/LC_MESSAGES/django.po b/django/contrib/admin/locale/kk/LC_MESSAGES/django.po index 5f7d9c5d938e..6d9625afd82e 100644 --- a/django/contrib/admin/locale/kk/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/kk/LC_MESSAGES/django.po @@ -2,21 +2,22 @@ # # Translators: # Baurzhan Muftakhidinov , 2015 +# Leo Trubach , 2017 # Nurlan Rakhimzhanov , 2011 # yun_man_ger , 2011 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 00:36+0000\n" +"Last-Translator: Ramiro Morales\n" "Language-Team: Kazakh (http://www.transifex.com/django/django/language/kk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: kk\n" -"Plural-Forms: nplurals=1; plural=0;\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" #, python-format msgid "Successfully deleted %(count)d %(items)s." @@ -64,10 +65,10 @@ msgid "This year" msgstr "Осы жыл" msgid "No date" -msgstr "" +msgstr "Күні жоқ" msgid "Has date" -msgstr "" +msgstr "Күні бар" #, python-format msgid "" @@ -85,6 +86,15 @@ msgstr "Тағы басқа %(verbose_name)s кос" msgid "Remove" msgstr "Өшіру" +msgid "Addition" +msgstr "" + +msgid "Change" +msgstr "Өзгетру" + +msgid "Deletion" +msgstr "" + msgid "action time" msgstr "әрекет уақыты" @@ -98,7 +108,7 @@ msgid "object id" msgstr "объекттің id-i" #. 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-i" @@ -162,8 +172,10 @@ msgid "" msgstr "" #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "" + +msgid "You may edit it again below." msgstr "" #, python-brace-format @@ -173,12 +185,13 @@ msgid "" msgstr "" #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." +"The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" #, python-brace-format @@ -205,8 +218,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" сәтті өшірілді." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Бірінші кілті %(key)r бар %(name)s объекті жоқ." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" @@ -216,6 +229,10 @@ msgstr "%s қосу" msgid "Change %s" msgstr "%s өзгету" +#, python-format +msgid "View %s" +msgstr "" + msgid "Database error" msgstr "Мәліметтер базасының қатесі" @@ -226,6 +243,10 @@ msgstr[0] "" "one: %(count)s %(name)s өзгертілді.\n" "\n" "other: %(count)s %(name)s таңдалғандарының барі өзгертілді." +msgstr[1] "" +"one: %(count)s %(name)s өзгертілді.\n" +"\n" +"other: %(count)s %(name)s таңдалғандарының барі өзгертілді." #, python-format msgid "%(total_count)s selected" @@ -234,6 +255,10 @@ msgstr[0] "" "one: %(total_count)s таңдалды\n" "\n" "other: Барлығы %(total_count)s таңдалды" +msgstr[1] "" +"one: %(total_count)s таңдалды\n" +"\n" +"other: Барлығы %(total_count)s таңдалды" #, python-format msgid "0 of %(cnt)s selected" @@ -325,8 +350,6 @@ msgstr "Құпия сөзді өзгерту" msgid "Please correct the error below." msgstr "" -"one: Астындағы қатені дұрыстаңыз.\n" -"other: Астындағы қателерді дұрыстаңыз." msgid "Please correct the errors below." msgstr "" @@ -436,8 +459,8 @@ msgstr "" "Таңдаған %(objects_name)s объектіңізді өшіруге сенімдісіз бе? Себебі, " "таңдағын объектіліріңіз және онымен байланыстағы барлық элементтер жойылады:" -msgid "Change" -msgstr "Өзгетру" +msgid "View" +msgstr "" msgid "Delete?" msgstr "Өшіру?" @@ -456,8 +479,8 @@ msgstr "" msgid "Add" msgstr "Қосу" -msgid "You don't have permission to edit anything." -msgstr "Бірденке түзетуге рұқсатыңыз жоқ." +msgid "You don't have permission to view or edit anything." +msgstr "" msgid "Recent actions" msgstr "" @@ -509,19 +532,7 @@ msgstr "Барлығын көрсету" msgid "Save" msgstr "Сақтау" -msgid "Popup closing..." -msgstr "" - -#, python-format -msgid "Change selected %(model)s" -msgstr "" - -#, python-format -msgid "Add another %(model)s" -msgstr "" - -#, python-format -msgid "Delete selected %(model)s" +msgid "Popup closing…" msgstr "" msgid "Search" @@ -531,6 +542,7 @@ msgstr "Іздеу" msgid "%(counter)s result" msgid_plural "%(counter)s results" msgstr[0] "%(counter)s нәтиже" +msgstr[1] "%(counter)s нәтиже" #, python-format msgid "%(full_result_count)s total" @@ -545,6 +557,24 @@ msgstr "Сақта және жаңасын қос" msgid "Save and continue editing" msgstr "Сақта және өзгертуді жалғастыр" +msgid "Save and view" +msgstr "" + +msgid "Close" +msgstr "" + +#, python-format +msgid "Change selected %(model)s" +msgstr "" + +#, python-format +msgid "Add another %(model)s" +msgstr "" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Бүгін Веб-торапқа уақыт бөлгеніңіз үшін рахмет." @@ -645,6 +675,10 @@ msgstr "%s таңда" msgid "Select %s to change" msgstr "%s өзгерту үщін таңда" +#, python-format +msgid "Select %s to view" +msgstr "" + msgid "Date:" msgstr "Күнтізбелік күн:" diff --git a/django/contrib/admin/locale/kk/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/kk/LC_MESSAGES/djangojs.mo index 5b5d3426fef6..0b65151380cf 100644 Binary files a/django/contrib/admin/locale/kk/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/kk/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/kk/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/kk/LC_MESSAGES/djangojs.po index 7d8c3399aed3..9c51f35b87b6 100644 --- a/django/contrib/admin/locale/kk/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/kk/LC_MESSAGES/djangojs.po @@ -6,15 +6,15 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:10+0000\n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Kazakh (http://www.transifex.com/django/django/language/kk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: kk\n" -"Plural-Forms: nplurals=1; plural=0;\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" #, javascript-format msgid "Available %s" @@ -66,6 +66,7 @@ msgstr "" msgid "%(sel)s of %(cnt)s selected" msgid_plural "%(sel)s of %(cnt)s selected" msgstr[0] "%(cnt)s-ң %(sel)s-ы(і) таңдалды" +msgstr[1] "%(cnt)s-ң %(sel)s-ы(і) таңдалды" msgid "" "You have unsaved changes on individual editable fields. If you run an " @@ -90,18 +91,32 @@ msgstr "" "Сіз Сақтау батырмасына қарағанда, Go(Алға) батырмасын іздеп отырған " "боларсыз, себебі ешқандай өзгеріс жасамай, әрекет жасадыңыз." +msgid "Now" +msgstr "Қазір" + +msgid "Midnight" +msgstr "Түн жарым" + +msgid "6 a.m." +msgstr "06" + +msgid "Noon" +msgstr "Талтүс" + +msgid "6 p.m." +msgstr "" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." msgstr[0] "" +msgstr[1] "" #, javascript-format msgid "Note: You are %s hour behind server time." msgid_plural "Note: You are %s hours behind server time." msgstr[0] "" - -msgid "Now" -msgstr "Қазір" +msgstr[1] "" msgid "Choose a Time" msgstr "" @@ -109,18 +124,6 @@ msgstr "" msgid "Choose a time" msgstr "Уақытты таңда" -msgid "Midnight" -msgstr "Түн жарым" - -msgid "6 a.m." -msgstr "06" - -msgid "Noon" -msgstr "Талтүс" - -msgid "6 p.m." -msgstr "" - msgid "Cancel" msgstr "Болдырмау" diff --git a/django/contrib/admin/locale/km/LC_MESSAGES/django.mo b/django/contrib/admin/locale/km/LC_MESSAGES/django.mo index eab3c37f2412..a50821c26325 100644 Binary files a/django/contrib/admin/locale/km/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/km/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/km/LC_MESSAGES/django.po b/django/contrib/admin/locale/km/LC_MESSAGES/django.po index e7432b84a7ba..8b16d1fcbfea 100644 --- a/django/contrib/admin/locale/km/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/km/LC_MESSAGES/django.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Khmer (http://www.transifex.com/django/django/language/km/)\n" "MIME-Version: 1.0\n" @@ -202,7 +202,7 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "ឈ្មោះកម្មវិធី %(name)s \"%(obj)s\" ត្រូវបានលប់ដោយជោគជ័យ។" #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" msgstr "" #, python-format diff --git a/django/contrib/admin/locale/km/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/km/LC_MESSAGES/djangojs.mo index 3b7dcea236eb..c0b94c12cc3f 100644 Binary files a/django/contrib/admin/locale/km/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/km/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/km/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/km/LC_MESSAGES/djangojs.po index 042706aae0b7..fbe0ae159794 100644 --- a/django/contrib/admin/locale/km/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/km/LC_MESSAGES/djangojs.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:10+0000\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Khmer (http://www.transifex.com/django/django/language/km/)\n" "MIME-Version: 1.0\n" diff --git a/django/contrib/admin/locale/kn/LC_MESSAGES/django.mo b/django/contrib/admin/locale/kn/LC_MESSAGES/django.mo index 6257a6762d39..3740da20869e 100644 Binary files a/django/contrib/admin/locale/kn/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/kn/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/kn/LC_MESSAGES/django.po b/django/contrib/admin/locale/kn/LC_MESSAGES/django.po index e1166a936b92..3ae96cfa6791 100644 --- a/django/contrib/admin/locale/kn/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/kn/LC_MESSAGES/django.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Kannada (http://www.transifex.com/django/django/language/" "kn/)\n" @@ -203,7 +203,7 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" ಯಶಸ್ವಿಯಾಗಿ ಅಳಿಸಲಾಯಿತು." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" msgstr "" #, python-format diff --git a/django/contrib/admin/locale/kn/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/kn/LC_MESSAGES/djangojs.mo index 66c266234ccf..988728ce948e 100644 Binary files a/django/contrib/admin/locale/kn/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/kn/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/kn/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/kn/LC_MESSAGES/djangojs.po index e76cd498dda1..90363b7a2cf9 100644 --- a/django/contrib/admin/locale/kn/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/kn/LC_MESSAGES/djangojs.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:10+0000\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Kannada (http://www.transifex.com/django/django/language/" "kn/)\n" diff --git a/django/contrib/admin/locale/ko/LC_MESSAGES/django.mo b/django/contrib/admin/locale/ko/LC_MESSAGES/django.mo index 311a2ce07a56..f214f3922513 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 e5fe7d44ced5..ef78ed22d3f9 100644 --- a/django/contrib/admin/locale/ko/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/ko/LC_MESSAGES/django.po @@ -2,19 +2,23 @@ # # Translators: # Jiyoon, Ha , 2016 +# Gihun Ham , 2018 +# Hang Park , 2019 # Hoseok Lee , 2016 -# Ian Y. Choi , 2015 +# Ian Y. Choi , 2015,2019 # Jaehong Kim , 2011 # Jannis Leidel , 2011 -# Jeong Seongtae , 2014,2016 +# Le Tartuffe , 2014,2016 +# Noh Seho , 2018 +# Seacbyul Lee , 2017 # Taesik Yoon , 2015 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-09-16 06:13+0000\n" -"Last-Translator: Jiyoon, Ha \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-06-29 03:14+0000\n" +"Last-Translator: Ian Y. Choi \n" "Language-Team: Korean (http://www.transifex.com/django/django/language/ko/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -91,6 +95,15 @@ msgstr "%(verbose_name)s 더 추가하기" msgid "Remove" msgstr "삭제하기" +msgid "Addition" +msgstr "추가" + +msgid "Change" +msgstr "변경" + +msgid "Deletion" +msgstr "삭제" + msgid "action time" msgstr "액션 타임" @@ -104,7 +117,7 @@ msgid "object id" msgstr "오브젝트 아이디" #. 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 "오브젝트 표현" @@ -168,11 +181,11 @@ msgid "" msgstr "하나 이상을 선택하려면 \"Control\" 키, Mac은 \"Command\"키를 누르세요." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" -"{name} \"{obj}\"가 성공적으로 추가되었습니다. 아래에서 다시 수정할 수 있습니" -"다." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "{name} \"{obj}\"가 성공적으로 추가되었습니다." + +msgid "You may edit it again below." +msgstr "아래 내용을 수정해야 합니다." #, python-brace-format msgid "" @@ -183,12 +196,15 @@ msgstr "" "수 있습니다." #, 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." +"The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" "{name} \"{obj}\"가 성공적으로 추가되었습니다. 아래에서 다시 수정할 수 있습니" "다." @@ -220,8 +236,10 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\"이/가 삭제되었습니다." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Primary key %(key)r에 대한 오브젝트 %(name)s이/가 존재하지 않습니다." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" +"ID \"%(key)s\" 을/를 지닌 %(name)s 이/가 존재하지 않습니다. 이전에 삭제된 값" +"이 아닌지 확인해주세요." #, python-format msgid "Add %s" @@ -231,6 +249,10 @@ msgstr "%s 추가" msgid "Change %s" msgstr "%s 변경" +#, python-format +msgid "View %s" +msgstr "뷰 %s" + msgid "Database error" msgstr "데이터베이스 오류" @@ -337,7 +359,7 @@ msgid "Change password" msgstr "비밀번호 변경" msgid "Please correct the error below." -msgstr "아래의 오류를 수정하십시오." +msgstr "아래 오류를 해결해주세요." msgid "Please correct the errors below." msgstr "아래의 오류들을 수정하십시오." @@ -447,8 +469,8 @@ msgstr "" "선택한 %(objects_name)s를 정말 삭제하시겠습니까? 다음의 오브젝트와 연관 아이" "템들이 모두 삭제됩니다:" -msgid "Change" -msgstr "변경" +msgid "View" +msgstr "보기" msgid "Delete?" msgstr "삭제" @@ -462,19 +484,19 @@ msgstr "개요" #, python-format msgid "Models in the %(name)s application" -msgstr "%(name)s 애플리케이션의 " +msgstr "%(name)s 애플리케이션의 모델" msgid "Add" msgstr "추가" -msgid "You don't have permission to edit anything." -msgstr "수정할 권한이 없습니다." +msgid "You don't have permission to view or edit anything." +msgstr "조회하거나 수정할 수 있는 권한이 없습니다." msgid "Recent actions" -msgstr "최근 액션들" +msgstr "최근 활동" msgid "My actions" -msgstr "내 액션들" +msgstr "나의 활동" msgid "None available" msgstr "이용할 수 없습니다." @@ -495,11 +517,11 @@ 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 "Date/time" msgstr "날짜/시간" @@ -523,20 +545,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 "검색" @@ -559,6 +569,24 @@ msgstr "저장 및 다른 이름으로 추가" msgid "Save and continue editing" 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 제거" + msgid "Thanks for spending some quality time with the Web site today." msgstr "사이트를 이용해 주셔서 고맙습니다." @@ -669,6 +697,10 @@ msgstr "%s 선택" msgid "Select %s to change" msgstr "변경할 %s 선택" +#, python-format +msgid "Select %s to view" +msgstr "보기위한 1%s 를(을) 선택" + msgid "Date:" msgstr "날짜:" diff --git a/django/contrib/admin/locale/ko/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/ko/LC_MESSAGES/djangojs.mo index f6f5c38e6206..8ef689d23183 100644 Binary files a/django/contrib/admin/locale/ko/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/ko/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/ko/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/ko/LC_MESSAGES/djangojs.po index 90b632828286..6d52c03b99f3 100644 --- a/django/contrib/admin/locale/ko/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/ko/LC_MESSAGES/djangojs.po @@ -5,14 +5,14 @@ # Hoseok Lee , 2016 # Jaehong Kim , 2011 # Jannis Leidel , 2011 -# Jeong Seongtae , 2014 +# Le Tartuffe , 2014 # minsung kang, 2015 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-06-28 06:05+0000\n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Hoseok Lee \n" "Language-Team: Korean (http://www.transifex.com/django/django/language/ko/)\n" "MIME-Version: 1.0\n" @@ -99,6 +99,21 @@ msgstr "" "개별 필드에 아무런 변경이 없는 상태로 액션을 선택했습니다. 저장 버튼이 아니" "라 진행 버튼을 찾아보세요." +msgid "Now" +msgstr "현재" + +msgid "Midnight" +msgstr "자정" + +msgid "6 a.m." +msgstr "오전 6시" + +msgid "Noon" +msgstr "정오" + +msgid "6 p.m." +msgstr "오후 6시" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." @@ -109,27 +124,12 @@ msgid "Note: You are %s hour behind server time." msgid_plural "Note: You are %s hours behind server time." msgstr[0] "Note: 서버 시간보다 %s 시간 늦은 시간입니다." -msgid "Now" -msgstr "현재" - msgid "Choose a Time" msgstr "시간 선택" msgid "Choose a time" msgstr "시간 선택" -msgid "Midnight" -msgstr "자정" - -msgid "6 a.m." -msgstr "오전 6시" - -msgid "Noon" -msgstr "정오" - -msgid "6 p.m." -msgstr "오후 6시" - msgid "Cancel" msgstr "취소" diff --git a/django/contrib/admin/locale/lb/LC_MESSAGES/django.mo b/django/contrib/admin/locale/lb/LC_MESSAGES/django.mo index 7b95bf9ded16..f989aedbeabc 100644 Binary files a/django/contrib/admin/locale/lb/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/lb/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/lb/LC_MESSAGES/django.po b/django/contrib/admin/locale/lb/LC_MESSAGES/django.po index d1820e6abdb5..5e2e7945830c 100644 --- a/django/contrib/admin/locale/lb/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/lb/LC_MESSAGES/django.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-19 16:40+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Luxembourgish (http://www.transifex.com/django/django/" "language/lb/)\n" @@ -203,7 +203,7 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "" #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" msgstr "" #, python-format diff --git a/django/contrib/admin/locale/lt/LC_MESSAGES/django.mo b/django/contrib/admin/locale/lt/LC_MESSAGES/django.mo index ca8428212e58..b225f663d4ec 100644 Binary files a/django/contrib/admin/locale/lt/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/lt/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/lt/LC_MESSAGES/django.po b/django/contrib/admin/locale/lt/LC_MESSAGES/django.po index 3c866116a666..0c93418a630f 100644 --- a/django/contrib/admin/locale/lt/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/lt/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ # Translators: # Jannis Leidel , 2011 # lauris , 2011 -# Matas Dailyda , 2015-2016 +# Matas Dailyda , 2015-2019 # Nikolajus Krauklis , 2013 # Simonas Kazlauskas , 2012-2013 # sirex , 2011 @@ -11,8 +11,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-23 12:04+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 10:32+0000\n" "Last-Translator: Matas Dailyda \n" "Language-Team: Lithuanian (http://www.transifex.com/django/django/language/" "lt/)\n" @@ -20,8 +20,9 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: lt\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n" -"%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=4; plural=(n % 10 == 1 && (n % 100 > 19 || n % 100 < " +"11) ? 0 : (n % 10 >= 2 && n % 10 <=9) && (n % 100 > 19 || n % 100 < 11) ? " +"1 : n % 1 != 0 ? 2: 3);\n" #, python-format msgid "Successfully deleted %(count)d %(items)s." @@ -92,6 +93,15 @@ msgstr "Pridėti dar viena %(verbose_name)s" msgid "Remove" msgstr "Pašalinti" +msgid "Addition" +msgstr "Pridėjimas" + +msgid "Change" +msgstr "Pakeisti" + +msgid "Deletion" +msgstr "Pašalinimas" + msgid "action time" msgstr "veiksmo laikas" @@ -105,7 +115,7 @@ msgid "object id" msgstr "objekto 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 "objekto repr" @@ -171,10 +181,11 @@ msgstr "" "daugiau nei vieną." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" -"{name} \"{obj}\" buvo sėkmingai pridėtas. Galite jį vėl redaguoti žemiau." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "{name} \"{obj}\" buvo sėkmingai pridėtas." + +msgid "You may edit it again below." +msgstr "Galite tai dar kartą redaguoti žemiau." #, python-brace-format msgid "" @@ -183,15 +194,17 @@ msgid "" msgstr "" "{name} \"{obj}\" buvo sėkmingai pridėtas. Galite pridėti kitą {name} žemiau." -#, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "{name} \"{obj}\" buvo sėkmingai pridėtas." - #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "{name} \"{obj}\" buvo sėkmingai pakeistas. Galite jį koreguoti žemiau." +#, python-brace-format +msgid "" +"The {name} \"{obj}\" was added successfully. You may edit it again below." +msgstr "" +"{name} \"{obj}\" buvo sėkmingai pridėtas. Galite jį vėl redaguoti žemiau." + #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " @@ -218,8 +231,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" sėkmingai ištrintas." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Įrašas %(name)s su pirminiu raktu %(key)r neegzistuoja." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s su ID \"%(key)s\" neegzistuoja. Gal tai buvo ištrinta?" #, python-format msgid "Add %s" @@ -229,6 +242,10 @@ msgstr "Pridėti %s" msgid "Change %s" msgstr "Pakeisti %s" +#, python-format +msgid "View %s" +msgstr "Peržiūrėti %s" + msgid "Database error" msgstr "Duomenų bazės klaida" @@ -238,6 +255,7 @@ msgid_plural "%(count)s %(name)s were changed successfully." msgstr[0] "%(count)s %(name)s sėkmingai pakeistas." msgstr[1] "%(count)s %(name)s sėkmingai pakeisti." msgstr[2] "%(count)s %(name)s " +msgstr[3] "%(count)s %(name)s " #, python-format msgid "%(total_count)s selected" @@ -245,6 +263,7 @@ msgid_plural "All %(total_count)s selected" msgstr[0] "%(total_count)s pasirinktas" msgstr[1] "%(total_count)s pasirinkti" msgstr[2] "Visi %(total_count)s pasirinkti" +msgstr[3] "Visi %(total_count)s pasirinkti" #, python-format msgid "0 of %(cnt)s selected" @@ -339,7 +358,7 @@ msgid "Change password" msgstr "Keisti slaptažodį" msgid "Please correct the error below." -msgstr "Ištaisykite žemiau esancias klaidas." +msgstr "Prašome ištaisyti žemiau esančią klaidą." msgid "Please correct the errors below." msgstr "Ištaisykite žemiau esančias klaidas." @@ -448,8 +467,8 @@ msgstr "" "Ar esate tikri, kad norite ištrinti pasirinktus %(objects_name)s? Sekantys " "pasirinkti bei susiję objektai bus ištrinti:" -msgid "Change" -msgstr "Pakeisti" +msgid "View" +msgstr "Peržiūrėti" msgid "Delete?" msgstr "Ištrinti?" @@ -468,8 +487,8 @@ msgstr "%(name)s aplikacijos modeliai" msgid "Add" msgstr "Pridėti" -msgid "You don't have permission to edit anything." -msgstr "Neturite teisių ką nors keistis." +msgid "You don't have permission to view or edit anything." +msgstr "Jūs neturite teisių peržiūrai ir redagavimui." msgid "Recent actions" msgstr "Paskutiniai veiksmai" @@ -525,20 +544,8 @@ msgstr "Rodyti visus" msgid "Save" msgstr "Išsaugoti" -msgid "Popup closing..." -msgstr "Langas užsidaro..." - -#, python-format -msgid "Change selected %(model)s" -msgstr "Keisti pasirinktus %(model)s" - -#, python-format -msgid "Add another %(model)s" -msgstr "Pridėti dar vieną %(model)s" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Pašalinti pasirinktus %(model)s" +msgid "Popup closing…" +msgstr "Iškylantysis langas užsidaro..." msgid "Search" msgstr "Ieškoti" @@ -549,6 +556,7 @@ msgid_plural "%(counter)s results" msgstr[0] "%(counter)s rezultatas" msgstr[1] "%(counter)s rezultatai" msgstr[2] "%(counter)s rezultatai" +msgstr[3] "%(counter)s rezultatai" #, python-format msgid "%(full_result_count)s total" @@ -563,6 +571,24 @@ msgstr "Išsaugoti ir pridėti naują" msgid "Save and continue editing" msgstr "Išsaugoti ir tęsti redagavimą" +msgid "Save and view" +msgstr "Išsaugoti ir peržiūrėti" + +msgid "Close" +msgstr "Uždaryti" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Keisti pasirinktus %(model)s" + +#, python-format +msgid "Add another %(model)s" +msgstr "Pridėti dar vieną %(model)s" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Pašalinti pasirinktus %(model)s" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Dėkui už šiandien tinklalapyje turiningai praleistą laiką." @@ -674,6 +700,10 @@ msgstr "Pasirinkti %s" msgid "Select %s to change" msgstr "Pasirinkite %s kurį norite keisti" +#, python-format +msgid "Select %s to view" +msgstr "Pasirinkti %s peržiūrai" + msgid "Date:" msgstr "Data:" diff --git a/django/contrib/admin/locale/lt/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/lt/LC_MESSAGES/djangojs.mo index d8578bc69679..77922d36b363 100644 Binary files a/django/contrib/admin/locale/lt/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/lt/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/lt/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/lt/LC_MESSAGES/djangojs.po index 674ad2c3bacd..a922bd63ed25 100644 --- a/django/contrib/admin/locale/lt/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/lt/LC_MESSAGES/djangojs.po @@ -10,8 +10,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-23 12:08+0000\n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Matas Dailyda \n" "Language-Team: Lithuanian (http://www.transifex.com/django/django/language/" "lt/)\n" @@ -19,8 +19,9 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: lt\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n" -"%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=4; plural=(n % 10 == 1 && (n % 100 > 19 || n % 100 < " +"11) ? 0 : (n % 10 >= 2 && n % 10 <=9) && (n % 100 > 19 || n % 100 < 11) ? " +"1 : n % 1 != 0 ? 2: 3);\n" #, javascript-format msgid "Available %s" @@ -80,6 +81,7 @@ msgid_plural "%(sel)s of %(cnt)s selected" msgstr[0] "pasirinktas %(sel)s iš %(cnt)s" msgstr[1] "pasirinkti %(sel)s iš %(cnt)s" msgstr[2] "pasirinkti %(sel)s iš %(cnt)s" +msgstr[3] "pasirinkti %(sel)s iš %(cnt)s" msgid "" "You have unsaved changes on individual editable fields. If you run an " @@ -103,6 +105,21 @@ msgstr "" "Pasirinkote veiksmą, bet neesate pakeitę laukų reikšmių. Jūs greičiausiai " "ieškote mygtuko Vykdyti, o ne mygtuko Saugoti." +msgid "Now" +msgstr "Dabar" + +msgid "Midnight" +msgstr "Vidurnaktis" + +msgid "6 a.m." +msgstr "6 a.m." + +msgid "Noon" +msgstr "Vidurdienis" + +msgid "6 p.m." +msgstr "18:00" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." @@ -112,6 +129,8 @@ msgstr[1] "" "Pastaba: Jūsų laikrodis rodo %s valandomis daugiau nei serverio laikrodis." msgstr[2] "" "Pastaba: Jūsų laikrodis rodo %s valandų daugiau nei serverio laikrodis." +msgstr[3] "" +"Pastaba: Jūsų laikrodis rodo %s valandų daugiau nei serverio laikrodis." #, javascript-format msgid "Note: You are %s hour behind server time." @@ -122,9 +141,8 @@ msgstr[1] "" "Pastaba: Jūsų laikrodis rodo %s valandomis mažiau nei serverio laikrodis." msgstr[2] "" "Pastaba: Jūsų laikrodis rodo %s valandų mažiau nei serverio laikrodis." - -msgid "Now" -msgstr "Dabar" +msgstr[3] "" +"Pastaba: Jūsų laikrodis rodo %s valandų mažiau nei serverio laikrodis." msgid "Choose a Time" msgstr "Pasirinkite laiką" @@ -132,18 +150,6 @@ msgstr "Pasirinkite laiką" msgid "Choose a time" msgstr "Pasirinkite laiką" -msgid "Midnight" -msgstr "Vidurnaktis" - -msgid "6 a.m." -msgstr "6 a.m." - -msgid "Noon" -msgstr "Vidurdienis" - -msgid "6 p.m." -msgstr "18:00" - msgid "Cancel" msgstr "Atšaukti" diff --git a/django/contrib/admin/locale/lv/LC_MESSAGES/django.mo b/django/contrib/admin/locale/lv/LC_MESSAGES/django.mo index c409be7c78ff..d68a14a7f9bd 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 91d0f54336dc..6535d1bebe6a 100644 --- a/django/contrib/admin/locale/lv/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/lv/LC_MESSAGES/django.po @@ -2,14 +2,19 @@ # # Translators: # edgars , 2011 +# NullIsNot0 , 2017 +# NullIsNot0 , 2018 # Jannis Leidel , 2011 +# Māris Nartišs , 2016 +# NullIsNot0 , 2019 +# peterisb , 2016 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-02-18 16:58+0000\n" +"Last-Translator: NullIsNot0 \n" "Language-Team: Latvian (http://www.transifex.com/django/django/language/" "lv/)\n" "MIME-Version: 1.0\n" @@ -25,7 +30,7 @@ msgstr "Veiksmīgi izdzēsti %(count)d %(items)s." #, python-format msgid "Cannot delete %(name)s" -msgstr "" +msgstr "Nevar izdzēst %(name)s" msgid "Are you sure?" msgstr "Vai esat pārliecināts?" @@ -35,7 +40,7 @@ msgid "Delete selected %(verbose_name_plural)s" msgstr "Izdzēst izvēlēto %(verbose_name_plural)s" msgid "Administration" -msgstr "" +msgstr "Administrācija" msgid "All" msgstr "Visi" @@ -65,16 +70,18 @@ msgid "This year" msgstr "Šogad" msgid "No date" -msgstr "" +msgstr "Nav datums" msgid "Has date" -msgstr "" +msgstr "Ir datums" #, python-format msgid "" "Please enter the correct %(username)s and password for a staff account. Note " "that both fields may be case-sensitive." msgstr "" +"Lūdzu ievadi korektu %(username)s un paroli personāla kontam. Ņem vērā, ka " +"abi ievades lauki ir reģistr jūtīgi." msgid "Action:" msgstr "Darbība:" @@ -86,20 +93,29 @@ msgstr "Pievienot vēl %(verbose_name)s" msgid "Remove" msgstr "Dzēst" +msgid "Addition" +msgstr "Pievienošana" + +msgid "Change" +msgstr "Izmainīt" + +msgid "Deletion" +msgstr "Dzēšana" + msgid "action time" msgstr "darbības laiks" msgid "user" -msgstr "" +msgstr "lietotājs" msgid "content type" -msgstr "" +msgstr "satura tips" msgid "object id" msgstr "objekta 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 "objekta attēlojums" @@ -117,40 +133,40 @@ msgstr "žurnāla ieraksti" #, python-format msgid "Added \"%(object)s\"." -msgstr "" +msgstr "Pievienots \"%(object)s\"." #, python-format msgid "Changed \"%(object)s\" - %(changes)s" -msgstr "" +msgstr "Mainīts \"%(object)s\" - %(changes)s" #, python-format msgid "Deleted \"%(object)s.\"" -msgstr "" +msgstr "Dzēsts \"%(object)s.\"" msgid "LogEntry Object" -msgstr "" +msgstr "LogEntry Objekts" #, python-brace-format msgid "Added {name} \"{object}\"." -msgstr "" +msgstr "Pievienots {name} \"{object}\"." msgid "Added." -msgstr "" +msgstr "Pievienots." msgid "and" msgstr "un" #, python-brace-format msgid "Changed {fields} for {name} \"{object}\"." -msgstr "" +msgstr "Mainīti {fields} priekš {name} \"{object}\"." #, python-brace-format msgid "Changed {fields}." -msgstr "" +msgstr "Mainīts {fields}." #, python-brace-format msgid "Deleted {name} \"{object}\"." -msgstr "" +msgstr "Dzēsts {name} \"{object}\"." msgid "No fields changed." msgstr "Lauki nav izmainīti" @@ -161,36 +177,46 @@ msgstr "nekas" msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "" +"Turi nospiestu \"Control\" taustiņu vai \"Command\" uz Mac datora, lai " +"izvēlētos vairāk par vienu." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" +msgid "The {name} \"{obj}\" was added successfully." +msgstr "{name} \"{obj}\" tika veiksmīgi pievienots." + +msgid "You may edit it again below." +msgstr "Jūs varat to atkal labot zemāk. " #, python-brace-format msgid "" "The {name} \"{obj}\" was added successfully. You may add another {name} " "below." msgstr "" +"{name} \"{obj}\" tika veiksmīgi pievienots. Zemāk var pievienot vēl citu " +"{name}." #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" +"{name} \"{obj}\" tika veiksmīgi mainīts. Zemāk var turpināt veikt izmaiņas." #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." +"The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" +"{name} \"{obj}\" tika veiksmīgi pievienots. Zemāk var turpināt veikt " +"izmaiņas." #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " "below." -msgstr "" +msgstr "{name} \"{obj}\" vieksmīgi mainīts. Zemāk variet vēl pievienot {name}." #, python-brace-format msgid "The {name} \"{obj}\" was changed successfully." -msgstr "" +msgstr "{name} \"{obj}\" tika veiksmīgi mainīts." msgid "" "Items must be selected in order to perform actions on them. No items have " @@ -205,8 +231,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" sekmīgi izdzēsts." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(name)s objekts ar primāro atslēgu %(key)r neeksistē." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s ar ID \"%(key)s\" neeksistē. Varbūt tas ir dzēsts?" #, python-format msgid "Add %s" @@ -216,6 +242,10 @@ msgstr "Pievienot %s" msgid "Change %s" msgstr "Labot %s" +#, python-format +msgid "View %s" +msgstr "Apskatīt %s" + msgid "Database error" msgstr "Datubāzes kļūda" @@ -245,13 +275,15 @@ msgstr "Izmaiņu vēsture: %s" #. suitable to be an item in a list. #, python-format msgid "%(class_name)s %(instance)s" -msgstr "" +msgstr "%(class_name)s %(instance)s" #, python-format msgid "" "Deleting %(class_name)s %(instance)s would require deleting the following " "protected related objects: %(related_objects)s" msgstr "" +"%(class_name)s %(instance)s dzēšanai ir nepieciešams izdzēst sekojošus " +"aizsargātus saistītos objektus: %(related_objects)s" msgid "Django site admin" msgstr "Django administrācijas lapa" @@ -267,7 +299,7 @@ msgstr "Pieslēgties" #, python-format msgid "%(app)s administration" -msgstr "" +msgstr "%(app)s administrācija" msgid "Page not found" msgstr "Lapa nav atrasta" @@ -291,6 +323,8 @@ 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 "" +"Notika kļūda. Lapas administratoriem ir nosūtīts e-pasts un kļūda tuvākajā " +"laikā tiks novērsta. Paldies par pacietību." msgid "Run the selected action" msgstr "Izpildīt izvēlēto darbību" @@ -316,16 +350,16 @@ msgstr "" "lietotāja uzstādījumus." msgid "Enter a username and password." -msgstr "" +msgstr "Ievadi lietotājvārdu un paroli." msgid "Change password" msgstr "Paroles maiņa" msgid "Please correct the error below." -msgstr "Lūdzu, izlabojiet kļūdas zemāk." +msgstr "Lūdzu izlabojiet zemāk redzamo kļūdu." msgid "Please correct the errors below." -msgstr "" +msgstr "Lūdzu labo kļūdas zemāk." #, python-format msgid "Enter a new password for the user %(username)s." @@ -335,7 +369,7 @@ msgid "Welcome," msgstr "Sveicināti," msgid "View site" -msgstr "" +msgstr "Apskatīt lapu" msgid "Documentation" msgstr "Dokumentācija" @@ -357,14 +391,14 @@ msgid "Filter" msgstr "Filtrs" msgid "Remove from sorting" -msgstr "" +msgstr "Izņemt no kārtošanas" #, python-format msgid "Sorting priority: %(priority_number)s" -msgstr "" +msgstr "Kārtošanas prioritāte: %(priority_number)s" msgid "Toggle sorting" -msgstr "" +msgstr "Pārslēgt kārtošanu" msgid "Delete" msgstr "Dzēst" @@ -383,6 +417,8 @@ msgid "" "Deleting the %(object_name)s '%(escaped_object)s' would require deleting the " "following protected related objects:" msgstr "" +"%(object_name)s '%(escaped_object)s' dzēšanai ir nepieciešams izdzēst " +"sekojošus aizsargātus saistītos objektus:" #, python-format msgid "" @@ -393,13 +429,13 @@ msgstr "" "\"? Tiks dzēsti arī sekojoši saistītie objekti:" msgid "Objects" -msgstr "" +msgstr "Objekti" msgid "Yes, I'm sure" msgstr "Jā, esmu pārliecināts" msgid "No, take me back" -msgstr "" +msgstr "Nē, ved mani atpakaļ" msgid "Delete multiple objects" msgstr "Dzēst vairākus objektus" @@ -410,21 +446,27 @@ msgid "" "objects, but your account doesn't have permission to delete the following " "types of objects:" msgstr "" +"Izdzēšot izvēlēto %(objects_name)s, tiks dzēsti visi saistītie objekti, bet " +"jums nav tiesību dzēst sekojošus objektu tipus:" #, python-format msgid "" "Deleting the selected %(objects_name)s would require deleting the following " "protected related objects:" msgstr "" +"Izvēlēto %(objects_name)s objektu dzēšanai ir nepieciešams izdzēst sekojošus " +"aizsargātus saistītos objektus:" #, python-format 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 "" +"Vai esat pārliecināts, ka vēlaties dzēst izvēlētos %(objects_name)s " +"objektus? Visi sekojošie objekti un tiem piesaistītie objekti tiks izdzēsti:" -msgid "Change" -msgstr "Izmainīt" +msgid "View" +msgstr "Apskatīt" msgid "Delete?" msgstr "Dzēst?" @@ -434,23 +476,23 @@ msgid " By %(filter_title)s " msgstr " Pēc %(filter_title)s " msgid "Summary" -msgstr "" +msgstr "Kopsavilkums" #, python-format msgid "Models in the %(name)s application" -msgstr "" +msgstr "Modeļi %(name)s lietotnē" msgid "Add" msgstr "Pievienot" -msgid "You don't have permission to edit anything." -msgstr "Jums nav tiesības neko labot." +msgid "You don't have permission to view or edit anything." +msgstr "Jums nav tiesību neko apskatīt vai labot." msgid "Recent actions" -msgstr "" +msgstr "Nesenās darbības" msgid "My actions" -msgstr "" +msgstr "Manas darbības" msgid "None available" msgstr "Nav pieejams" @@ -471,9 +513,11 @@ 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 "" +"Jūs esat autentificējies kā %(username)s, bet jums nav tiesību piekļūt šai " +"lapai. Vai vēlaties pieteikties citā kontā?" msgid "Forgotten your password or username?" -msgstr "" +msgstr "Aizmirsi paroli vai lietotājvārdu?" msgid "Date/time" msgstr "Datums/laiks" @@ -497,20 +541,8 @@ msgstr "Rādīt visu" msgid "Save" msgstr "Saglabāt" -msgid "Popup closing..." -msgstr "" - -#, python-format -msgid "Change selected %(model)s" -msgstr "" - -#, python-format -msgid "Add another %(model)s" -msgstr "" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "" +msgid "Popup closing…" +msgstr "Logs aizveras..." msgid "Search" msgstr "Meklēt" @@ -518,9 +550,9 @@ msgstr "Meklēt" #, python-format msgid "%(counter)s result" msgid_plural "%(counter)s results" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "%(counter)s rezultāti" +msgstr[1] "%(counter)s rezultāts" +msgstr[2] "%(counter)s rezultāti" #, python-format msgid "%(full_result_count)s total" @@ -535,6 +567,24 @@ msgstr "Saglabāt un pievienot vēl vienu" msgid "Save and continue editing" msgstr "Saglabāt un turpināt labošanu" +msgid "Save and view" +msgstr "Saglabāt un apskatīt" + +msgid "Close" +msgstr "Aizvērt" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Mainīt izvēlēto %(model)s" + +#, python-format +msgid "Add another %(model)s" +msgstr "Pievienot citu %(model)s" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Dzēst izvēlēto %(model)s" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Paldies par pavadīto laiku mājas lapā." @@ -590,17 +640,24 @@ 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 "" +"Mēs nosūtījām jums e-pasta ziņojumu ar jūsu paroles iestatīšanas " +"instrukciju, ja jums ir konts ar ievadīto e-pasta adresi. Jums tos tūlīt " +"vajadzētu saņemt." 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 "" +"Ja nesaņemat e-pastu, lūdzu, pārliecinieties, vai esat ievadījis reģistrēto " +"adresi un pārbaudiet savu mēstuļu mapi." #, python-format msgid "" "You're receiving this email because you requested a password reset for your " "user account at %(site_name)s." msgstr "" +"Jūs saņemat šo e-pasta ziņojumu, jo pieprasījāt atiestatīt lietotāja konta " +"paroli vietnē %(site_name)s." msgid "Please go to the following page and choose a new password:" msgstr "Lūdzu apmeklējiet sekojošo lapu un ievadiet jaunu paroli:" @@ -619,9 +676,11 @@ msgid "" "Forgotten your password? Enter your email address below, and we'll email " "instructions for setting a new one." msgstr "" +"Aizmirsāt savu paroli? Ievadiet savu e-pasta adresi un jums tiks nosūtīta " +"informācija par jaunas paroles iestatīšanu." msgid "Email address:" -msgstr "" +msgstr "E-pasta adrese:" msgid "Reset my password" msgstr "Paroles pārstatīšana" @@ -637,6 +696,10 @@ msgstr "Izvēlēties %s" msgid "Select %s to change" msgstr "Izvēlēties %s, lai izmainītu" +#, python-format +msgid "Select %s to view" +msgstr "Izvēlēties %s, lai apskatītu" + msgid "Date:" msgstr "Datums:" @@ -647,7 +710,7 @@ msgid "Lookup" msgstr "Pārlūkot" msgid "Currently:" -msgstr "" +msgstr "Valūta:" msgid "Change:" -msgstr "" +msgstr "Izmaiņa:" diff --git a/django/contrib/admin/locale/lv/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/lv/LC_MESSAGES/djangojs.mo index 339949e7707e..61e6e33e7e9d 100644 Binary files a/django/contrib/admin/locale/lv/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/lv/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/lv/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/lv/LC_MESSAGES/djangojs.po index 63026820e847..4f1b55fe6a8f 100644 --- a/django/contrib/admin/locale/lv/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/lv/LC_MESSAGES/djangojs.po @@ -1,14 +1,16 @@ # This file is distributed under the same license as the Django package. # # Translators: +# NullIsNot0 , 2017 # Jannis Leidel , 2011 +# peterisb , 2016 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-11-18 08:13+0000\n" +"Last-Translator: NullIsNot0 \n" "Language-Team: Latvian (http://www.transifex.com/django/django/language/" "lv/)\n" "MIME-Version: 1.0\n" @@ -27,10 +29,14 @@ 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 "" +"Šis ir saraksts ar pieejamajiem %s. Tev ir jāizvēlas atbilstošās vērtības " +"atzīmējot izvēlēs zemāk esošajā sarakstā un pēc tam spiežot pogu \"Izvēlēties" +"\", lai pārvietotu starp izvēļu sarakstiem." #, javascript-format msgid "Type into this box to filter down the list of available %s." msgstr "" +"Raksti šajā logā, lai filtrētu zemāk esošo sarakstu ar pieejamajiem %s." msgid "Filter" msgstr "Filtrs" @@ -40,10 +46,10 @@ msgstr "Izvēlēties visu" #, javascript-format msgid "Click to choose all %s at once." -msgstr "" +msgstr "Izvēlies, lai pievienotu visas %s izvēles vienā reizē." msgid "Choose" -msgstr "" +msgstr "Izvēlies" msgid "Remove" msgstr "Izņemt" @@ -57,13 +63,16 @@ 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 "" +"Šis ir saraksts ar izvēlētajiem %s. Tev ir jāizvēlas atbilstošās vērtības " +"atzīmējot izvēlēs zemāk esošajā sarakstā un pēc tam spiežot pogu \"Izņemt\", " +"lai izņemtu no izvēlēto ierakstu saraksta." msgid "Remove all" -msgstr "" +msgstr "Izņemt visu" #, javascript-format msgid "Click to remove all chosen %s at once." -msgstr "" +msgstr "Izvēlies, lai izņemtu visas %s izvēles vienā reizē." msgid "%(sel)s of %(cnt)s selected" msgid_plural "%(sel)s of %(cnt)s selected" @@ -94,29 +103,9 @@ msgstr "" "Jūs esat izvēlējies veikt darbību un neesat izmainījis nevienu lauku. Jūs " "droši vien meklējat pogu 'Aiziet' nevis 'Saglabāt'." -#, javascript-format -msgid "Note: You are %s hour ahead of server time." -msgid_plural "Note: You are %s hours ahead of server time." -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" - -#, javascript-format -msgid "Note: You are %s hour behind server time." -msgid_plural "Note: You are %s hours behind server time." -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" - msgid "Now" msgstr "Tagad" -msgid "Choose a Time" -msgstr "" - -msgid "Choose a time" -msgstr "Izvēlieties laiku" - msgid "Midnight" msgstr "Pusnakts" @@ -127,7 +116,27 @@ msgid "Noon" msgstr "Pusdienas laiks" msgid "6 p.m." -msgstr "" +msgstr "6:00" + +#, javascript-format +msgid "Note: You are %s hour ahead of server time." +msgid_plural "Note: You are %s hours ahead of server time." +msgstr[0] "Piezīme: Tavs laiks ir %s stundas pirms servera laika." +msgstr[1] "Piezīme: Tavs laiks ir %s stundu pirms servera laika." +msgstr[2] "Piezīme: Tavs laiks ir %s stundas pirms servera laika." + +#, javascript-format +msgid "Note: You are %s hour behind server time." +msgid_plural "Note: You are %s hours behind server time." +msgstr[0] "Piezīme: Tavs laiks ir %s stundas pēc servera laika." +msgstr[1] "Piezīme: Tavs laiks ir %s stundu pēc servera laika." +msgstr[2] "Piezīme: Tavs laiks ir %s stundas pēc servera laika." + +msgid "Choose a Time" +msgstr "Izvēlies laiku" + +msgid "Choose a time" +msgstr "Izvēlieties laiku" msgid "Cancel" msgstr "Atcelt" @@ -136,7 +145,7 @@ msgid "Today" msgstr "Šodien" msgid "Choose a Date" -msgstr "" +msgstr "Izvēlies datumu" msgid "Yesterday" msgstr "Vakar" @@ -145,68 +154,68 @@ msgid "Tomorrow" msgstr "Rīt" msgid "January" -msgstr "" +msgstr "janvāris" msgid "February" -msgstr "" +msgstr "februāris" msgid "March" -msgstr "" +msgstr "marts" msgid "April" -msgstr "" +msgstr "aprīlis" msgid "May" -msgstr "" +msgstr "maijs" msgid "June" -msgstr "" +msgstr "jūnijs" msgid "July" -msgstr "" +msgstr "jūlijs" msgid "August" -msgstr "" +msgstr "augusts" msgid "September" -msgstr "" +msgstr "septembris" msgid "October" -msgstr "" +msgstr "oktobris" msgid "November" -msgstr "" +msgstr "novembris" msgid "December" -msgstr "" +msgstr "decembris" msgctxt "one letter Sunday" msgid "S" -msgstr "" +msgstr "Sv" msgctxt "one letter Monday" msgid "M" -msgstr "" +msgstr "Pr" msgctxt "one letter Tuesday" msgid "T" -msgstr "" +msgstr "O" msgctxt "one letter Wednesday" msgid "W" -msgstr "" +msgstr "T" msgctxt "one letter Thursday" msgid "T" -msgstr "" +msgstr "C" msgctxt "one letter Friday" msgid "F" -msgstr "" +msgstr "Pk" msgctxt "one letter Saturday" msgid "S" -msgstr "" +msgstr "Se" msgid "Show" msgstr "Parādīt" diff --git a/django/contrib/admin/locale/mk/LC_MESSAGES/django.mo b/django/contrib/admin/locale/mk/LC_MESSAGES/django.mo index 46a67468ac4b..293f3e6fd062 100644 Binary files a/django/contrib/admin/locale/mk/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/mk/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/mk/LC_MESSAGES/django.po b/django/contrib/admin/locale/mk/LC_MESSAGES/django.po index fbc8d7af24e7..99c875f11284 100644 --- a/django/contrib/admin/locale/mk/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/mk/LC_MESSAGES/django.po @@ -3,15 +3,15 @@ # Translators: # dekomote , 2015 # Jannis Leidel , 2011 -# Vasil Vangelovski , 2016 +# Vasil Vangelovski , 2016-2017 # Vasil Vangelovski , 2013-2015 # Vasil Vangelovski , 2011-2013 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-06-15 11:14+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-23 18:54+0000\n" "Last-Translator: Vasil Vangelovski \n" "Language-Team: Macedonian (http://www.transifex.com/django/django/language/" "mk/)\n" @@ -172,31 +172,39 @@ msgstr "" 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." msgstr "" +"Ставката {name} \"{obj}\" беше успешно додадена. Можете да додадете нов " +"{name} подолу." #, python-brace-format msgid "The {name} \"{obj}\" was added successfully." -msgstr "" +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} " "below." msgstr "" +"Ставката {name} \"{obj}\" беше успешно додадена. Можете да додадете нов " +"{name} подолу." #, python-brace-format msgid "The {name} \"{obj}\" was changed successfully." -msgstr "" +msgstr " {name} \"{obj}\" беше успешно изменета." msgid "" "Items must be selected in order to perform actions on them. No items have " @@ -213,8 +221,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "Ставаката %(name)s \"%(obj)s\" беше успешно избришана." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Објект %(name)s со примарен клуч %(key)r не постои." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s со клуч \"%(key)s\" не постои. Можеби е избришан?" #, python-format msgid "Add %s" diff --git a/django/contrib/admin/locale/mk/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/mk/LC_MESSAGES/djangojs.mo index ee5f4be92107..5b11c786c3af 100644 Binary files a/django/contrib/admin/locale/mk/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/mk/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/mk/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/mk/LC_MESSAGES/djangojs.po index 88da0254bc03..04e9dcbbe11f 100644 --- a/django/contrib/admin/locale/mk/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/mk/LC_MESSAGES/djangojs.po @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-06-15 11:07+0000\n" +"PO-Revision-Date: 2017-09-23 18:54+0000\n" "Last-Translator: Vasil Vangelovski \n" "Language-Team: Macedonian (http://www.transifex.com/django/django/language/" "mk/)\n" diff --git a/django/contrib/admin/locale/ml/LC_MESSAGES/django.mo b/django/contrib/admin/locale/ml/LC_MESSAGES/django.mo index 2ce228676855..dd39d0a9b36d 100644 Binary files a/django/contrib/admin/locale/ml/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/ml/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/ml/LC_MESSAGES/django.po b/django/contrib/admin/locale/ml/LC_MESSAGES/django.po index bf6a2e824a8e..776202c6f726 100644 --- a/django/contrib/admin/locale/ml/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/ml/LC_MESSAGES/django.po @@ -2,16 +2,19 @@ # # Translators: # Aby Thomas , 2014 +# Hrishikesh , 2019 # Jannis Leidel , 2011 +# JOMON THOMAS LOBO , 2019 # Junaid , 2012 +# MUHAMMED RAMEEZ , 2019 # Rajeesh Nair , 2011-2013 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-03-26 07:56+0000\n" +"Last-Translator: JOMON THOMAS LOBO \n" "Language-Team: Malayalam (http://www.transifex.com/django/django/language/" "ml/)\n" "MIME-Version: 1.0\n" @@ -22,7 +25,7 @@ msgstr "" #, python-format msgid "Successfully deleted %(count)d %(items)s." -msgstr "%(count)d %(items)s നീക്കം ചെയ്തു." +msgstr "%(count)d %(items)sവിജയകയരമായി നീക്കം ചെയ്തു." #, python-format msgid "Cannot delete %(name)s" @@ -36,10 +39,10 @@ msgid "Delete selected %(verbose_name_plural)s" msgstr "തെരഞ്ഞെടുത്ത %(verbose_name_plural)s നീക്കം ചെയ്യുക." msgid "Administration" -msgstr "ഭരണം" +msgstr "കാര്യനിർവഹണം" msgid "All" -msgstr "എല്ലാം" +msgstr "മുഴുവനും" msgid "Yes" msgstr "അതെ" @@ -48,16 +51,16 @@ msgid "No" msgstr "അല്ല" msgid "Unknown" -msgstr "അജ്ഞാതം" +msgstr "അറിയില്ല" msgid "Any date" -msgstr "ഏതെങ്കിലും തീയതി" +msgstr "ഏതെങ്കിലും തീയ്യതി" msgid "Today" msgstr "ഇന്ന്" msgid "Past 7 days" -msgstr "കഴിഞ്ഞ ഏഴു ദിവസം" +msgstr "കഴിഞ്ഞ 7 ദിവസങ്ങൾ" msgid "This month" msgstr "ഈ മാസം" @@ -66,46 +69,54 @@ msgid "This year" msgstr "ഈ വര്‍ഷം" msgid "No date" -msgstr "" +msgstr "തിയ്യതിയില്ല " msgid "Has date" -msgstr "" +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 -ഉം പാസ്‌വേഡും നല്കുക. രണ്ടു " -"കള്ളികളിലും അക്ഷരങ്ങള്‍ (ഇംഗ്ലീഷിലെ) വലിയക്ഷരമോ ചെറിയക്ഷരമോ എന്നത് പ്രധാനമാണെന്നത് " -"ശ്രദ്ധിയ്ക്കുക." +"ദയവായി സ്റ്റാഫ് അക്കൗണ്ടിനുവേണ്ടിയുള്ള ശരിയായ %(username)s പാസ്‌വേഡ് എന്നിവ നൽകുക. രണ്ടു " +"കള്ളികളിലും അക്ഷരങ്ങള്‍ വലിയക്ഷരമോ ചെറിയക്ഷരമോ എന്നത് പ്രധാനമാണെന്നത് ശ്രദ്ധിയ്ക്കുക." msgid "Action:" msgstr "ആക്ഷന്‍" #, python-format msgid "Add another %(verbose_name)s" -msgstr "%(verbose_name)s ഒന്നു കൂടി ചേര്‍ക്കുക" +msgstr "മറ്റൊരു %(verbose_name)s കൂടി ചേര്‍ക്കുക" msgid "Remove" -msgstr "നീക്കം ചെയ്യുക" +msgstr "കളയുക" + +msgid "Addition" +msgstr "ചേർക്കുക" + +msgid "Change" +msgstr "മാറ്റുക" + +msgid "Deletion" +msgstr "കളയുക" msgid "action time" -msgstr "ആക്ഷന്‍ സമയം" +msgstr "നടന്ന സമയം" msgid "user" -msgstr "" +msgstr "ഉപയോക്താവ്" msgid "content type" -msgstr "" +msgstr "കണ്ടന്റ് ടൈപ്പ്" msgid "object id" -msgstr "ഒബ്ജെക്ട് ഐഡി" +msgstr "ഒബ്ജക്റ്റിന്റെ ഐഡി" #. 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 "ഒബ്ജെക്ട് സൂചന" +msgstr "ഒബ്ജെക്ട് റെപ്രസന്റേഷൻ" msgid "action flag" msgstr "ആക്ഷന്‍ ഫ്ളാഗ്" @@ -114,10 +125,10 @@ msgid "change message" msgstr "സന്ദേശം മാറ്റുക" msgid "log entry" -msgstr "ലോഗ് എന്ട്രി" +msgstr "ലോഗ് എൻട്രി" msgid "log entries" -msgstr "ലോഗ് എന്ട്രികള്‍" +msgstr "ലോഗ് എൻട്രികള്‍" #, python-format msgid "Added \"%(object)s\"." @@ -132,14 +143,14 @@ msgid "Deleted \"%(object)s.\"" msgstr "\"%(object)s\" നീക്കം ചെയ്തു." msgid "LogEntry Object" -msgstr "ലോഗ്‌എന്‍ട്രി വസ്തു" +msgstr "ലോഗ്‌എന്‍ട്രി ഒബ്ജെക്റ്റ്" #, python-brace-format msgid "Added {name} \"{object}\"." -msgstr "" +msgstr " {name} \"{object}\" ചേർത്തിരിക്കുന്നു ." msgid "Added." -msgstr "" +msgstr "ചേര്‍ത്തു." msgid "and" msgstr "ഉം" @@ -154,7 +165,7 @@ msgstr "" #, python-brace-format msgid "Deleted {name} \"{object}\"." -msgstr "" +msgstr " {name} \"{object}\". ഡിലീറ്റ് ചെയ്തു " msgid "No fields changed." msgstr "ഒരു മാറ്റവുമില്ല." @@ -167,23 +178,28 @@ msgid "" msgstr "" #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" +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 add another {name} " "below." msgstr "" +" {name} \"{obj}\" വിജയകരമായി ചേർത്തിരിക്കുന്നു . നിങ്ങൾക്ക് പുതിയ ഒരു {name} താഴെ " +"ചേർക്കാവുന്നതാണ് " #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." +"The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" #, python-brace-format @@ -202,15 +218,15 @@ msgid "" msgstr "ആക്ഷന്‍ നടപ്പിലാക്കേണ്ട വകകള്‍ തെരഞ്ഞെടുക്കണം. ഒന്നും മാറ്റിയിട്ടില്ല." msgid "No action selected." -msgstr "ആക്ഷനൊന്നും തെരഞ്ഞെടുത്തില്ല." +msgstr "ആക്ഷനൊന്നും തെരഞ്ഞെടുത്തിട്ടില്ല." #, python-format msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" നീക്കം ചെയ്തു." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(key)r എന്ന പ്രാഥമിക കീ ഉള്ള %(name)s വസ്തു ഒന്നും നിലവിലില്ല." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" @@ -220,8 +236,12 @@ msgstr "%s ചേര്‍ക്കുക" msgid "Change %s" msgstr "%s മാറ്റാം" +#, python-format +msgid "View %s" +msgstr "%s കാണുക" + msgid "Database error" -msgstr "ഡേറ്റാബേസ് തകരാറാണ്." +msgstr "ഡേറ്റാബേസ് എറർ." #, python-format msgid "%(count)s %(name)s was changed successfully." @@ -233,11 +253,11 @@ msgstr[1] "%(count)s %(name)s ല്‍ മാറ്റം വരുത്തി msgid "%(total_count)s selected" msgid_plural "All %(total_count)s selected" msgstr[0] "%(total_count)s തെരഞ്ഞെടുത്തു." -msgstr[1] "%(total_count)sഉം തെരഞ്ഞെടുത്തു." +msgstr[1] "%(total_count)sമൊത്തമായി തെരഞ്ഞെടുത്തു." #, python-format msgid "0 of %(cnt)s selected" -msgstr "%(cnt)s ല്‍ ഒന്നും തെരഞ്ഞെടുത്തില്ല." +msgstr "%(cnt)s ല്‍ 0 തിരഞ്ഞെടുത്തിരിക്കുന്നു" #, python-format msgid "Change history: %s" @@ -261,20 +281,20 @@ msgid "Django site admin" msgstr "ജാംഗോ സൈറ്റ് അഡ്മിന്‍" msgid "Django administration" -msgstr "ജാംഗോ ഭരണം" +msgstr "ജാംഗോ കാര്യനിർവഹണം" msgid "Site administration" -msgstr "സൈറ്റ് ഭരണം" +msgstr "സൈറ്റ് കാര്യനിർവഹണം" msgid "Log in" -msgstr "ലോഗ്-ഇന്‍" +msgstr "ലോഗിൻ" #, python-format msgid "%(app)s administration" -msgstr "%(app)s ഭരണം" +msgstr "%(app)s കാര്യനിർവഹണം" msgid "Page not found" -msgstr "പേജ് കണ്ടില്ല" +msgstr "പേജ് കണ്ടെത്താനായില്ല" msgid "We're sorry, but the requested page could not be found." msgstr "ക്ഷമിക്കണം, ആവശ്യപ്പെട്ട പേജ് കണ്ടെത്താന്‍ കഴിഞ്ഞില്ല." @@ -283,29 +303,29 @@ msgid "Home" msgstr "പൂമുഖം" msgid "Server error" -msgstr "സെര്‍വര്‍ തകരാറാണ്" +msgstr "സെര്‍വറിൽ എന്തോ പ്രശ്നം" msgid "Server error (500)" -msgstr "സെര്‍വര്‍ തകരാറാണ് (500)" +msgstr "സെര്‍വറിൽ എന്തോ പ്രശ്നം (500)" msgid "Server Error (500)" -msgstr "സെര്‍വര്‍ തകരാറാണ് (500)" +msgstr "സെര്‍വറിൽ എന്തോ പ്രശ്നം (500)" 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 "" -"എന്തോ തകരാറ് സംഭവിച്ചു. ബന്ധപ്പെട്ട സൈറ്റ് ഭരണകർത്താക്കളെ ഈമെയിൽ മുഖാന്തരം അറിയിച്ചിട്ടുണ്ട്. " -"ഷമയൊടെ കത്തിരിക്കുനതിന് നന്ദി." +"എന്തോ പ്രശ്നം സംഭവിച്ചിരിക്കുന്നു. സൈറ്റിന്റെ കാര്യനിർവാഹകരെ ഈമെയിൽ മുഖാന്തരം വിവരം " +"അറിയിച്ചിട്ടുണ്ട്. ക്ഷമയോടെ കത്തിരിക്കുനതിന് നന്ദി." msgid "Run the selected action" msgstr "തെരഞ്ഞെടുത്ത ആക്ഷന്‍ നടപ്പിലാക്കുക" msgid "Go" -msgstr "Go" +msgstr "തുടരുക" msgid "Click here to select the objects across all pages" -msgstr "എല്ലാ പേജിലേയും വസ്തുക്കള്‍ തെരഞ്ഞെടുക്കാന്‍ ഇവിടെ ക്ലിക് ചെയ്യുക." +msgstr "എല്ലാ പേജിലേയും ഒബ്ജക്റ്റുകൾ തെരഞ്ഞെടുക്കാന്‍ ഇവിടെ ക്ലിക് ചെയ്യുക." #, python-format msgid "Select all %(total_count)s %(module_name)s" @@ -326,7 +346,7 @@ msgid "Change password" msgstr "പാസ് വേര്‍ഡ് മാറ്റുക." msgid "Please correct the error below." -msgstr "ദയവായി താഴെയുള്ള തെറ്റുകള്‍ പരിഹരിക്കുക." +msgstr "താഴെ പറയുന്ന തെറ്റുകൾ തിരുത്തുക " msgid "Please correct the errors below." msgstr "ദയവായി താഴെയുള്ള തെറ്റുകള്‍ പരിഹരിക്കുക." @@ -339,7 +359,7 @@ msgid "Welcome," msgstr "സ്വാഗതം, " msgid "View site" -msgstr "" +msgstr "സൈറ്റ് കാണുക " msgid "Documentation" msgstr "സഹായക്കുറിപ്പുകള്‍" @@ -400,13 +420,13 @@ msgstr "" "താഴെപ്പറയുന്ന വസ്തുക്കളെല്ലാം നീക്കം ചെയ്യുന്നതാണ്:" msgid "Objects" -msgstr "" +msgstr "വസ്തുക്കൾ" msgid "Yes, I'm sure" msgstr "അതെ, തീര്‍ച്ചയാണ്" msgid "No, take me back" -msgstr "" +msgstr "ഇല്ല, എന്നെ തിരിച്ചെടുക്കൂ" msgid "Delete multiple objects" msgstr "ഒന്നിലേറെ വസ്തുക്കള്‍ നീക്കം ചെയ്യുക" @@ -436,8 +456,8 @@ msgstr "" "തിരഞ്ഞെടുക്കപ്പെട്ട %(objects_name)s നീക്കം ചെയ്യണമെന്നു ഉറപ്പാണോ ? തിരഞ്ഞെടുക്കപ്പെട്ടതും " "അതിനോട് ബന്ധപ്പെട്ടതും ആയ എല്ലാ താഴെപ്പറയുന്ന വസ്തുക്കളും നീക്കം ചെയ്യുന്നതാണ്:" -msgid "Change" -msgstr "മാറ്റുക" +msgid "View" +msgstr "കാണുക" msgid "Delete?" msgstr "ഡിലീറ്റ് ചെയ്യട്ടെ?" @@ -447,7 +467,7 @@ msgid " By %(filter_title)s " msgstr "%(filter_title)s ആൽ" msgid "Summary" -msgstr "" +msgstr "ചുരുക്കം" #, python-format msgid "Models in the %(name)s application" @@ -456,14 +476,14 @@ msgstr "%(name)s മാതൃകയിലുള്ള" msgid "Add" msgstr "ചേര്‍ക്കുക" -msgid "You don't have permission to edit anything." -msgstr "ഒന്നിലും മാറ്റം വരുത്താനുള്ള അനുമതി ഇല്ല." +msgid "You don't have permission to view or edit anything." +msgstr "നിങ്ങൾക്ക് ഒന്നും കാണാനോ എഡിറ്റുചെയ്യാനോ അനുമതിയില്ല" msgid "Recent actions" msgstr "" msgid "My actions" -msgstr "" +msgstr "എന്റെ പ്രവർത്തനം" msgid "None available" msgstr "ഒന്നും ലഭ്യമല്ല" @@ -484,6 +504,8 @@ 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, നു ഈ പേജ് കാണാൻ അനുവാദം ഇല്ല . താങ്കൾ " +"മറ്റൊരു അക്കൗണ്ടിൽ ലോഗിൻ ചെയ്യാന് ആഗ്രഹിക്കുന്നുവോ ?" msgid "Forgotten your password or username?" msgstr "രഹസ്യവാക്കോ ഉപയോക്തൃനാമമോ മറന്നുപോയോ?" @@ -492,17 +514,17 @@ msgid "Date/time" msgstr "തീയതി/സമയം" msgid "User" -msgstr "യൂസര്‍" +msgstr "ഉപയോക്താവ്" msgid "Action" -msgstr "ആക്ഷന്‍" +msgstr "പ്രവർത്തി" msgid "" "This object doesn't have a change history. It probably wasn't added via this " "admin site." msgstr "" -"ഈ വസ്തുവിന്റെ മാറ്റങ്ങളുടെ ചരിത്രം ലഭ്യമല്ല. ഒരുപക്ഷെ ഇത് അഡ്മിന്‍ സൈറ്റ് വഴി " -"ചേര്‍ത്തതായിരിക്കില്ല." +"ഈ വസ്തുവിന്റെ മാറ്റങ്ങളുടെ ചരിത്രം ലഭ്യമല്ല. ഒരുപക്ഷെ ഇത് അഡ്മിന്‍ സൈറ്റ് വഴി ചേര്‍" +"ത്തതായിരിക്കില്ല." msgid "Show all" msgstr "എല്ലാം കാണട്ടെ" @@ -510,20 +532,8 @@ msgstr "എല്ലാം കാണട്ടെ" msgid "Save" msgstr "സേവ് ചെയ്യണം" -msgid "Popup closing..." -msgstr "" - -#, python-format -msgid "Change selected %(model)s" -msgstr "" - -#, python-format -msgid "Add another %(model)s" -msgstr "" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "" +msgid "Popup closing…" +msgstr "പോപ്പ് അപ്പ് അടക്കുക " msgid "Search" msgstr "പരതുക" @@ -547,6 +557,24 @@ msgstr "സേവ് ചെയ്ത ശേഷം വേറെ ചേര്‍ msgid "Save and continue editing" msgstr "സേവ് ചെയ്ത ശേഷം മാറ്റം വരുത്താം" +msgid "Save and view" +msgstr "സേവ് ചെയ്‌തതിന്‌ ശേഷം കാണുക " + +msgid "Close" +msgstr "അടയ്ക്കുക" + +#, python-format +msgid "Change selected %(model)s" +msgstr "" + +#, python-format +msgid "Add another %(model)s" +msgstr "" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "തിരഞ്ഞെടുത്തത് ഇല്ലാതാക്കുക%(model)s" + msgid "Thanks for spending some quality time with the Web site today." msgstr "ഈ വെബ് സൈറ്റില്‍ കുറെ നല്ല സമയം ചെലവഴിച്ചതിനു നന്ദി." @@ -655,8 +683,12 @@ msgstr "%s തെരഞ്ഞെടുക്കൂ" msgid "Select %s to change" msgstr "മാറ്റാനുള്ള %s തെരഞ്ഞെടുക്കൂ" +#, python-format +msgid "Select %s to view" +msgstr "%s കാണാൻ തിരഞ്ഞെടുക്കുക" + msgid "Date:" -msgstr "തീയതി:" +msgstr "തിയ്യതി:" msgid "Time:" msgstr "സമയം:" @@ -665,7 +697,7 @@ msgid "Lookup" msgstr "തിരയുക" msgid "Currently:" -msgstr "പ്രചാരത്തിൽ:" +msgstr "നിലവിൽ:" msgid "Change:" -msgstr "മാറ്റം" +msgstr "മാറ്റം:" diff --git a/django/contrib/admin/locale/ml/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/ml/LC_MESSAGES/djangojs.mo index cff047a6ee07..60bef7df7f00 100644 Binary files a/django/contrib/admin/locale/ml/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/ml/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/ml/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/ml/LC_MESSAGES/djangojs.po index 0e7cb159f13f..803362f8c6d6 100644 --- a/django/contrib/admin/locale/ml/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/ml/LC_MESSAGES/djangojs.po @@ -3,14 +3,15 @@ # Translators: # Aby Thomas , 2014 # Jannis Leidel , 2011 +# MUHAMMED RAMEEZ , 2019 # Rajeesh Nair , 2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2019-03-09 08:56+0000\n" +"Last-Translator: MUHAMMED RAMEEZ \n" "Language-Team: Malayalam (http://www.transifex.com/django/django/language/" "ml/)\n" "MIME-Version: 1.0\n" @@ -98,6 +99,21 @@ msgstr "" "നിങ്ങള്‍ ഒരു ആക്ഷന്‍ തെരഞ്ഞെടുത്തിട്ടുണ്ട്. കളങ്ങളില്‍ സേവ് ചെയ്യാത്ത മാറ്റങ്ങള്‍ ഇല്ല. നിങ്ങള്‍സേവ് ബട്ടണ്‍ " "തന്നെയാണോ അതോ ഗോ ബട്ടണാണോ ഉദ്ദേശിച്ചത്." +msgid "Now" +msgstr "ഇപ്പോള്‍" + +msgid "Midnight" +msgstr "അര്‍ധരാത്രി" + +msgid "6 a.m." +msgstr "6 a.m." + +msgid "Noon" +msgstr "ഉച്ച" + +msgid "6 p.m." +msgstr "6 p.m" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." @@ -110,27 +126,12 @@ msgid_plural "Note: You are %s hours behind server time." msgstr[0] "ഒർക്കുക: സെർവർ സമയത്തിനെക്കാളും നിങ്ങൾ %s സമയം പിന്നിലാണ്." msgstr[1] "ഒർക്കുക: സെർവർ സമയത്തിനെക്കാളും നിങ്ങൾ %s സമയം പിന്നിലാണ്." -msgid "Now" -msgstr "ഇപ്പോള്‍" - msgid "Choose a Time" -msgstr "" +msgstr "സമയം തിരഞ്ഞെടുക്കുക" msgid "Choose a time" msgstr "സമയം തെരഞ്ഞെടുക്കൂ" -msgid "Midnight" -msgstr "അര്‍ധരാത്രി" - -msgid "6 a.m." -msgstr "6 a.m." - -msgid "Noon" -msgstr "ഉച്ച" - -msgid "6 p.m." -msgstr "" - msgid "Cancel" msgstr "റദ്ദാക്കൂ" @@ -138,7 +139,7 @@ msgid "Today" msgstr "ഇന്ന്" msgid "Choose a Date" -msgstr "" +msgstr "ഒരു തീയതി തിരഞ്ഞെടുക്കുക" msgid "Yesterday" msgstr "ഇന്നലെ" @@ -147,68 +148,68 @@ msgid "Tomorrow" msgstr "നാളെ" msgid "January" -msgstr "" +msgstr "ജനുവരി" msgid "February" -msgstr "" +msgstr "ഫെബ്രുവരി" msgid "March" -msgstr "" +msgstr "മാർച്ച്" msgid "April" -msgstr "" +msgstr "ഏപ്രിൽ" msgid "May" -msgstr "" +msgstr "മെയ്" msgid "June" -msgstr "" +msgstr "ജൂൺ" msgid "July" -msgstr "" +msgstr "ജൂലൈ" msgid "August" -msgstr "" +msgstr "ആഗസ്റ്റ്" msgid "September" -msgstr "" +msgstr "സെപ്റ്റംബർ" msgid "October" -msgstr "" +msgstr "ഒക്ടോബർ" msgid "November" -msgstr "" +msgstr "നവംബർ" msgid "December" -msgstr "" +msgstr "ഡിസംബര്" msgctxt "one letter Sunday" msgid "S" -msgstr "" +msgstr "ഞ്ഞ‍" msgctxt "one letter Monday" msgid "M" -msgstr "" +msgstr "തി" msgctxt "one letter Tuesday" msgid "T" -msgstr "" +msgstr "ചൊ" msgctxt "one letter Wednesday" msgid "W" -msgstr "" +msgstr "ബു" msgctxt "one letter Thursday" msgid "T" -msgstr "" +msgstr "വ്യാ" msgctxt "one letter Friday" msgid "F" -msgstr "" +msgstr "വെ" msgctxt "one letter Saturday" msgid "S" -msgstr "" +msgstr "ശ" msgid "Show" msgstr "കാണട്ടെ" diff --git a/django/contrib/admin/locale/mn/LC_MESSAGES/django.mo b/django/contrib/admin/locale/mn/LC_MESSAGES/django.mo index 9b2441c366d1..57a9d75e6e81 100644 Binary files a/django/contrib/admin/locale/mn/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/mn/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/mn/LC_MESSAGES/django.po b/django/contrib/admin/locale/mn/LC_MESSAGES/django.po index c4f0fcffed9d..813710351637 100644 --- a/django/contrib/admin/locale/mn/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/mn/LC_MESSAGES/django.po @@ -4,16 +4,16 @@ # Ankhbayar , 2013 # Jannis Leidel , 2011 # jargalan , 2011 -# Zorig , 2016 -# Анхбаяр Анхаа , 2013-2015 -# Баясгалан Цэвлээ , 2011 +# Zorig, 2016 +# Анхбаяр Анхаа , 2013-2016,2018-2019 +# Баясгалан Цэвлээ , 2011,2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-02-13 09:17+0000\n" +"Last-Translator: Анхбаяр Анхаа \n" "Language-Team: Mongolian (http://www.transifex.com/django/django/language/" "mn/)\n" "MIME-Version: 1.0\n" @@ -68,10 +68,10 @@ msgid "This year" msgstr "Энэ жил" msgid "No date" -msgstr "" +msgstr "Огноогүй" msgid "Has date" -msgstr "" +msgstr "Огноотой" #, python-format msgid "" @@ -91,6 +91,15 @@ msgstr "Өөр %(verbose_name)s нэмэх " msgid "Remove" msgstr "Хасах" +msgid "Addition" +msgstr "Нэмэгдсэн" + +msgid "Change" +msgstr "Өөрчлөх" + +msgid "Deletion" +msgstr "Устгагдсан" + msgid "action time" msgstr "үйлдлийн хугацаа" @@ -104,7 +113,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 "обектийн хамаарал" @@ -137,7 +146,7 @@ msgstr "Лог бүртгэлийн обект" #, python-brace-format msgid "Added {name} \"{object}\"." -msgstr "" +msgstr "Нэмэгдсэн {name} \"{object}\"." msgid "Added." msgstr "Нэмэгдсэн." @@ -147,15 +156,15 @@ msgstr "ба" #, python-brace-format msgid "Changed {fields} for {name} \"{object}\"." -msgstr "" +msgstr "{name} \"{object}\"-ны {fields} өөрчилөгдсөн." #, python-brace-format msgid "Changed {fields}." -msgstr "" +msgstr "Өөрчлөгдсөн {fields}." #, python-brace-format msgid "Deleted {name} \"{object}\"." -msgstr "" +msgstr "Устгасан {name} \"{object}\"." msgid "No fields changed." msgstr "Өөрчилсөн талбар алга байна." @@ -170,34 +179,41 @@ msgstr "" "байгаад сонгоно." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" +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 add another {name} " "below." msgstr "" +"{name} \"{obj}\" амжилттай нэмэгдлээ. Доорх хэсгээс {name} өөрийн нэмэх " +"боломжтой." #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "" +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 "" +"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} " "below." msgstr "" +"{name} \"{obj}\" амжилттай өөрчилөгдлөө. Доорх хэсгээс {name} өөрийн нэмэх " +"боломжтой." #, python-brace-format msgid "The {name} \"{obj}\" was changed successfully." -msgstr "" +msgstr "{name} \"{obj}\" амжилттай засагдлаа." msgid "" "Items must be selected in order to perform actions on them. No items have " @@ -213,8 +229,9 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr " %(name)s \"%(obj)s\" амжилттай устгагдлаа." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(name)s обектийн үндсэн түлхүүр %(key)r олдохгүй байна." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" +"\"%(key)s\" дугаартай %(name)s байхгүй байна. Устсан байсан юм болов уу?" #, python-format msgid "Add %s" @@ -224,6 +241,10 @@ msgstr "%s-ийг нэмэх" msgid "Change %s" msgstr "%s-ийг өөрчлөх" +#, python-format +msgid "View %s" +msgstr "%s харах " + msgid "Database error" msgstr "Өгөгдлийн сангийн алдаа" @@ -332,7 +353,7 @@ msgid "Change password" msgstr "Нууц үг өөрчлөх" msgid "Please correct the error below." -msgstr "Доорх алдаануудыг засна уу." +msgstr "Доорх алдааг засна уу" msgid "Please correct the errors below." msgstr "Доор гарсан алдаануудыг засна уу." @@ -441,8 +462,8 @@ msgstr "" "Та %(objects_name)s ийг устгах гэж байна итгэлтэй байна? Дараах обектууд " "болон холбоотой зүйлс хамт устагдах болно:" -msgid "Change" -msgstr "Өөрчлөх" +msgid "View" +msgstr "Харах" msgid "Delete?" msgstr "Устгах уу?" @@ -461,14 +482,14 @@ msgstr "%(name)s хэрэглүүр дэх моделууд." msgid "Add" msgstr "Нэмэх" -msgid "You don't have permission to edit anything." -msgstr "Та ямар нэг зүйл засварлах зөвшөөрөлгүй байна." +msgid "You don't have permission to view or edit anything." +msgstr "Танд харах болон засах эрх алга." msgid "Recent actions" -msgstr "" +msgstr "Сүүлд хийсэн үйлдлүүд" msgid "My actions" -msgstr "" +msgstr "Миний үйлдлүүд" msgid "None available" msgstr "Үйлдэл алга" @@ -518,20 +539,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 "Хайлт" @@ -555,6 +564,24 @@ msgstr "Хадгалаад өөрийг нэмэх" msgid "Save and continue editing" 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 устгах" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Манай вэб сайтыг ашигласанд баярлалаа." @@ -665,6 +692,10 @@ msgstr "%s-г сонго" msgid "Select %s to change" msgstr "Өөрчлөх %s-г сонгоно уу" +#, python-format +msgid "Select %s to view" +msgstr "Харахын тулд %s сонгоно уу" + msgid "Date:" msgstr "Огноо:" diff --git a/django/contrib/admin/locale/mn/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/mn/LC_MESSAGES/djangojs.mo index 5c0cc09f351c..9f58362d57db 100644 Binary files a/django/contrib/admin/locale/mn/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/mn/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/mn/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/mn/LC_MESSAGES/djangojs.po index 5d4e10768745..5fda29750299 100644 --- a/django/contrib/admin/locale/mn/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/mn/LC_MESSAGES/djangojs.po @@ -2,16 +2,16 @@ # # Translators: # Tsolmon , 2012 -# Zorig , 2014 -# Анхбаяр Анхаа , 2011-2012,2015 +# Zorig, 2014,2018 +# Анхбаяр Анхаа , 2011-2012,2015,2019 # Ганзориг БП , 2011 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2019-02-13 09:19+0000\n" +"Last-Translator: Анхбаяр Анхаа \n" "Language-Team: Mongolian (http://www.transifex.com/django/django/language/" "mn/)\n" "MIME-Version: 1.0\n" @@ -99,6 +99,21 @@ msgstr "" "Та 1 үйлдлийг сонгосон байна бас та ямарваа өөрчлөлт оруулсангүй. Та Save " "товчлуур биш Go товчлуурыг хайж байгаа бололтой." +msgid "Now" +msgstr "Одоо" + +msgid "Midnight" +msgstr "Шөнө дунд" + +msgid "6 a.m." +msgstr "06 цаг" + +msgid "Noon" +msgstr "Үд дунд" + +msgid "6 p.m." +msgstr "18 цаг" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." @@ -111,27 +126,12 @@ msgid_plural "Note: You are %s hours behind server time." msgstr[0] "Та серверийн цагаас %s цагаар хоцорч байна" msgstr[1] "Та серверийн цагаас %s цагаар хоцорч байна" -msgid "Now" -msgstr "Одоо" - msgid "Choose a Time" msgstr "Цаг сонгох" msgid "Choose a time" msgstr "Цаг сонгох" -msgid "Midnight" -msgstr "Шөнө дунд" - -msgid "6 a.m." -msgstr "6 цаг" - -msgid "Noon" -msgstr "Үд дунд" - -msgid "6 p.m." -msgstr "Оройн 6 цаг" - msgid "Cancel" msgstr "Болих" @@ -148,68 +148,68 @@ msgid "Tomorrow" msgstr "Маргааш" msgid "January" -msgstr "" +msgstr "1-р сар" msgid "February" -msgstr "" +msgstr "2-р сар" msgid "March" -msgstr "" +msgstr "3-р сар" msgid "April" -msgstr "" +msgstr "4-р сар" msgid "May" -msgstr "" +msgstr "5-р сар" msgid "June" -msgstr "" +msgstr "6-р сар" msgid "July" -msgstr "" +msgstr "7-р сар" msgid "August" -msgstr "" +msgstr "8-р сар" msgid "September" -msgstr "" +msgstr "9-р сар" msgid "October" -msgstr "" +msgstr "10-р сар" msgid "November" -msgstr "" +msgstr "11-р сар" msgid "December" -msgstr "" +msgstr "12-р сар" msgctxt "one letter Sunday" msgid "S" -msgstr "" +msgstr "Н" msgctxt "one letter Monday" msgid "M" -msgstr "" +msgstr "Д" msgctxt "one letter Tuesday" msgid "T" -msgstr "" +msgstr "М" msgctxt "one letter Wednesday" msgid "W" -msgstr "" +msgstr "Л" msgctxt "one letter Thursday" msgid "T" -msgstr "" +msgstr "П" msgctxt "one letter Friday" msgid "F" -msgstr "" +msgstr "Ба" msgctxt "one letter Saturday" msgid "S" -msgstr "" +msgstr "Бя" msgid "Show" msgstr "Үзэх" diff --git a/django/contrib/admin/locale/my/LC_MESSAGES/django.mo b/django/contrib/admin/locale/my/LC_MESSAGES/django.mo index 74c644fbdbc6..c22fe6cd049b 100644 Binary files a/django/contrib/admin/locale/my/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/my/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/my/LC_MESSAGES/django.po b/django/contrib/admin/locale/my/LC_MESSAGES/django.po index f893381c2f7e..34054dedf535 100644 --- a/django/contrib/admin/locale/my/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/my/LC_MESSAGES/django.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-19 16:40+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Burmese (http://www.transifex.com/django/django/language/" "my/)\n" @@ -203,7 +203,7 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "" #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" msgstr "" #, python-format diff --git a/django/contrib/admin/locale/my/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/my/LC_MESSAGES/djangojs.mo index 23444d55ae58..000b8bcb2dd3 100644 Binary files a/django/contrib/admin/locale/my/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/my/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/my/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/my/LC_MESSAGES/djangojs.po index aba3b6008367..06b49fc3debe 100644 --- a/django/contrib/admin/locale/my/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/my/LC_MESSAGES/djangojs.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Burmese (http://www.transifex.com/django/django/language/" "my/)\n" diff --git a/django/contrib/admin/locale/nb/LC_MESSAGES/django.mo b/django/contrib/admin/locale/nb/LC_MESSAGES/django.mo index 776b94844f1d..1f7329445173 100644 Binary files a/django/contrib/admin/locale/nb/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/nb/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/nb/LC_MESSAGES/django.po b/django/contrib/admin/locale/nb/LC_MESSAGES/django.po index 513c9ec96cee..c457c3de2565 100644 --- a/django/contrib/admin/locale/nb/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/nb/LC_MESSAGES/django.po @@ -3,18 +3,19 @@ # Translators: # Jannis Leidel , 2011 # jensadne , 2013-2014 -# Jon , 2015-2016 -# Jon , 2013 -# Jon , 2011,2013 +# Jon , 2015-2016 +# Jon , 2017-2019 +# Jon , 2013 +# Jon , 2011,2013 # Sigurd Gartmann , 2012 # Tommy Strand , 2013 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-07-27 09:07+0000\n" -"Last-Translator: Jon \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-05-06 13:01+0000\n" +"Last-Translator: Jon \n" "Language-Team: Norwegian Bokmål (http://www.transifex.com/django/django/" "language/nb/)\n" "MIME-Version: 1.0\n" @@ -93,6 +94,15 @@ msgstr "Legg til ny %(verbose_name)s" msgid "Remove" msgstr "Fjern" +msgid "Addition" +msgstr "Tillegg" + +msgid "Change" +msgstr "Endre" + +msgid "Deletion" +msgstr "Sletting" + msgid "action time" msgstr "tid for handling" @@ -106,7 +116,7 @@ msgid "object id" msgstr "objekt-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 "objekt-repr" @@ -171,9 +181,11 @@ msgstr "" "Hold nede «Control», eller «Command» på en Mac, for å velge mer enn en." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "{name} \"{obj}\" ble lagt til. Du kan redigere videre nedenfor." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "{name} \"{obj}\" ble lagt til." + +msgid "You may edit it again below." +msgstr "Du kan endre det igjen nedenfor." #, python-brace-format msgid "" @@ -181,15 +193,16 @@ msgid "" "below." msgstr "{name} \"{obj}\" ble lagt til. Du kan legge til en ny {name} nedenfor." -#, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "{name} \"{obj}\" ble lagt til." - #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "{name} \"{obj}\" ble endret. Du kan redigere videre nedenfor." +#, python-brace-format +msgid "" +"The {name} \"{obj}\" was added successfully. You may edit it again below." +msgstr "{name} \"{obj}\" ble lagt til. Du kan redigere videre nedenfor." + #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " @@ -215,8 +228,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s «%(obj)s» ble slettet." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(name)s-objekt med primærnøkkelen %(key)r finnes ikke." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s med ID \"%(key)s\" eksisterer ikke. Kanskje det ble slettet?" #, python-format msgid "Add %s" @@ -226,6 +239,10 @@ msgstr "Legg til ny %s" msgid "Change %s" msgstr "Endre %s" +#, python-format +msgid "View %s" +msgstr "Se %s" + msgid "Database error" msgstr "Databasefeil" @@ -334,7 +351,7 @@ msgid "Change password" msgstr "Endre passord" msgid "Please correct the error below." -msgstr "Vennligst korriger feilene under." +msgstr "Vennligst korriger feilen under." msgid "Please correct the errors below." msgstr "Vennligst korriger feilene under." @@ -445,8 +462,8 @@ msgstr "" "Er du sikker på vil slette det valgte %(objects_name)s? De følgende " "objektene og deres relaterte objekter vil bli slettet:" -msgid "Change" -msgstr "Endre" +msgid "View" +msgstr "Se" msgid "Delete?" msgstr "Slette?" @@ -465,8 +482,8 @@ msgstr "Modeller i %(name)s-applikasjonen" msgid "Add" msgstr "Legg til" -msgid "You don't have permission to edit anything." -msgstr "Du har ikke rettigheter til å redigere noe." +msgid "You don't have permission to view or edit anything." +msgstr "Du har ikke tillatelse til å vise eller endre noe." msgid "Recent actions" msgstr "Siste handlinger" @@ -521,21 +538,9 @@ msgstr "Vis alle" msgid "Save" msgstr "Lagre" -msgid "Popup closing..." +msgid "Popup closing…" msgstr "Lukker popup..." -#, python-format -msgid "Change selected %(model)s" -msgstr "Endre valgt %(model)s" - -#, python-format -msgid "Add another %(model)s" -msgstr "Legg til ny %(model)s" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Slett valgte %(model)s" - msgid "Search" msgstr "Søk" @@ -558,6 +563,24 @@ msgstr "Lagre og legg til ny" msgid "Save and continue editing" msgstr "Lagre og fortsett å redigere" +msgid "Save and view" +msgstr "Lagre og se" + +msgid "Close" +msgstr "Lukk" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Endre valgt %(model)s" + +#, python-format +msgid "Add another %(model)s" +msgstr "Legg til ny %(model)s" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Slett valgte %(model)s" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Takk for i dag." @@ -668,6 +691,10 @@ msgstr "Velg %s" msgid "Select %s to change" msgstr "Velg %s du ønsker å endre" +#, python-format +msgid "Select %s to view" +msgstr "Velg %s å se" + msgid "Date:" msgstr "Dato:" diff --git a/django/contrib/admin/locale/nb/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/nb/LC_MESSAGES/djangojs.mo index 27de9228121f..5f34eb3aa735 100644 Binary files a/django/contrib/admin/locale/nb/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/nb/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/nb/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/nb/LC_MESSAGES/djangojs.po index 177c98870831..7588b488d573 100644 --- a/django/contrib/admin/locale/nb/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/nb/LC_MESSAGES/djangojs.po @@ -3,16 +3,16 @@ # Translators: # Eirik Krogstad , 2014 # Jannis Leidel , 2011 -# Jon , 2015-2016 -# Jon , 2014 -# Jon , 2011-2012 +# Jon , 2015-2016 +# Jon , 2014 +# Jon , 2011-2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-07-27 09:11+0000\n" -"Last-Translator: Jon \n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" +"Last-Translator: Jon \n" "Language-Team: Norwegian Bokmål (http://www.transifex.com/django/django/" "language/nb/)\n" "MIME-Version: 1.0\n" @@ -101,6 +101,21 @@ msgstr "" "Du har valgt en handling, og har ikke gjort noen endringer i individuelle " "felter. Du ser mest sannsynlig etter Gå-knappen, ikke Lagre-knappen." +msgid "Now" +msgstr "Nå" + +msgid "Midnight" +msgstr "Midnatt" + +msgid "6 a.m." +msgstr "06:00" + +msgid "Noon" +msgstr "12:00" + +msgid "6 p.m." +msgstr "18:00" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." @@ -113,27 +128,12 @@ msgid_plural "Note: You are %s hours behind server time." msgstr[0] "Merk: Du er %s time bak server-tid." msgstr[1] "Merk: Du er %s timer bak server-tid." -msgid "Now" -msgstr "Nå" - msgid "Choose a Time" msgstr "Velg et klokkeslett" msgid "Choose a time" msgstr "Velg et klokkeslett" -msgid "Midnight" -msgstr "Midnatt" - -msgid "6 a.m." -msgstr "06:00" - -msgid "Noon" -msgstr "12:00" - -msgid "6 p.m." -msgstr "18:00" - msgid "Cancel" msgstr "Avbryt" diff --git a/django/contrib/admin/locale/ne/LC_MESSAGES/django.mo b/django/contrib/admin/locale/ne/LC_MESSAGES/django.mo index f8ae4cb45191..8423b8e20568 100644 Binary files a/django/contrib/admin/locale/ne/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/ne/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/ne/LC_MESSAGES/django.po b/django/contrib/admin/locale/ne/LC_MESSAGES/django.po index 44bc7dc67aa2..c7e4294a492e 100644 --- a/django/contrib/admin/locale/ne/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/ne/LC_MESSAGES/django.po @@ -6,9 +6,9 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-10-07 02:46+0000\n" +"Last-Translator: Sagar Chalise \n" "Language-Team: Nepali (http://www.transifex.com/django/django/language/ne/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -204,8 +204,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" सफलतापूर्वक मेटियो । " #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "प्राइमरी की %(key)r भएको %(name)s अब्जेक्ट" +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" diff --git a/django/contrib/admin/locale/ne/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/ne/LC_MESSAGES/djangojs.mo index 0b77822a7508..820885722a24 100644 Binary files a/django/contrib/admin/locale/ne/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/ne/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/ne/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/ne/LC_MESSAGES/djangojs.po index 41abbf7b183e..d55bd9fb547e 100644 --- a/django/contrib/admin/locale/ne/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/ne/LC_MESSAGES/djangojs.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" -"Last-Translator: Jannis Leidel \n" +"PO-Revision-Date: 2017-10-07 02:46+0000\n" +"Last-Translator: Sagar Chalise \n" "Language-Team: Nepali (http://www.transifex.com/django/django/language/ne/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" diff --git a/django/contrib/admin/locale/nl/LC_MESSAGES/django.mo b/django/contrib/admin/locale/nl/LC_MESSAGES/django.mo index c7d9e6e120dd..b4b63c1929a1 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 7881667f56a8..ecd7dfacfc5f 100644 --- a/django/contrib/admin/locale/nl/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/nl/LC_MESSAGES/django.po @@ -2,20 +2,23 @@ # # Translators: # Bas Peschier , 2013 +# Claude Paroz , 2017 +# Evelijn Saaltink , 2016 # Harro van der Klauw , 2012 # Ilja Maas , 2015 # Jannis Leidel , 2011 # Jeffrey Gelens , 2011-2012 # dokterbob , 2015 -# Sander Steffann , 2014-2015 +# Sander Steffann , 2014-2015 # Tino de Bruijn , 2011 +# Tonnes , 2017,2019 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-02-19 10:34+0000\n" +"Last-Translator: Tonnes \n" "Language-Team: Dutch (http://www.transifex.com/django/django/language/nl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -25,7 +28,7 @@ msgstr "" #, python-format msgid "Successfully deleted %(count)d %(items)s." -msgstr "%(count)d %(items)s succesvol verwijderd." +msgstr "%(count)d %(items)s met succes verwijderd." #, python-format msgid "Cannot delete %(name)s" @@ -36,7 +39,7 @@ msgstr "Weet u het zeker?" #, python-format msgid "Delete selected %(verbose_name_plural)s" -msgstr "Verwijder geselecteerde %(verbose_name_plural)s" +msgstr "Geselecteerde %(verbose_name_plural)s verwijderen" msgid "Administration" msgstr "Beheer" @@ -69,10 +72,10 @@ msgid "This year" msgstr "Dit jaar" msgid "No date" -msgstr "" +msgstr "Geen datum" msgid "Has date" -msgstr "" +msgstr "Heeft datum" #, python-format msgid "" @@ -87,11 +90,20 @@ msgstr "Actie:" #, python-format msgid "Add another %(verbose_name)s" -msgstr "Voeg nog een %(verbose_name)s toe" +msgstr "Nog een %(verbose_name)s toevoegen" msgid "Remove" msgstr "Verwijderen" +msgid "Addition" +msgstr "Toevoeging" + +msgid "Change" +msgstr "Wijzigen" + +msgid "Deletion" +msgstr "Verwijdering" + msgid "action time" msgstr "actietijd" @@ -105,7 +117,7 @@ msgid "object id" msgstr "object-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 "object-repr" @@ -113,32 +125,32 @@ msgid "action flag" msgstr "actievlag" msgid "change message" -msgstr "wijzig bericht" +msgstr "wijzigingsbericht" msgid "log entry" -msgstr "logregistratie" +msgstr "logboekvermelding" msgid "log entries" -msgstr "logregistraties" +msgstr "logboekvermeldingen" #, python-format msgid "Added \"%(object)s\"." -msgstr "Toegevoegd \"%(object)s\"." +msgstr "'%(object)s' toegevoegd." #, python-format msgid "Changed \"%(object)s\" - %(changes)s" -msgstr "Gewijzigd \"%(object)s\" - %(changes)s" +msgstr "'%(object)s' gewijzigd - %(changes)s" #, python-format msgid "Deleted \"%(object)s.\"" -msgstr "Verwijderd \"%(object)s.\"" +msgstr "'%(object)s' verwijderd." msgid "LogEntry Object" -msgstr "LogEntry Object" +msgstr "LogEntry-object" #, python-brace-format msgid "Added {name} \"{object}\"." -msgstr "" +msgstr "{name} '{object}' toegevoegd." msgid "Added." msgstr "Toegevoegd." @@ -148,15 +160,15 @@ msgstr "en" #, python-brace-format msgid "Changed {fields} for {name} \"{object}\"." -msgstr "" +msgstr "{fields} voor {name} '{object}' gewijzigd." #, python-brace-format msgid "Changed {fields}." -msgstr "" +msgstr "{fields} gewijzigd." #, python-brace-format msgid "Deleted {name} \"{object}\"." -msgstr "" +msgstr "{name} '{object}' verwijderd." msgid "No fields changed." msgstr "Geen velden gewijzigd." @@ -167,64 +179,79 @@ msgstr "Geen" msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "" -"Houdt \"Control\", of \"Command\" op een Mac, ingedrukt om meerdere te " +"Houd 'Control', of 'Command' op een Mac, ingedrukt om meerdere items te " "selecteren." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" +msgid "The {name} \"{obj}\" was added successfully." +msgstr "De {name} '{obj}' is met succes toegevoegd." + +msgid "You may edit it again below." +msgstr "U kunt deze hieronder weer bewerken." #, python-brace-format msgid "" "The {name} \"{obj}\" was added successfully. You may add another {name} " "below." msgstr "" +"De {name} '{obj}' is met succes toegevoegd. U kunt hieronder nog een {name} " +"toevoegen." #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" +"De {name} '{obj}' is met succes gewijzigd. U kunt deze hieronder nogmaals " +"bewerken." #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." +"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} " "below." msgstr "" +"De {name} '{obj}' is met succes gewijzigd. U kunt hieronder nog een {name} " +"toevoegen." #, python-brace-format msgid "The {name} \"{obj}\" was changed successfully." -msgstr "" +msgstr "De {name} '{obj}' is met succes gewijzigd." msgid "" "Items must be selected in order to perform actions on them. No items have " "been changed." msgstr "" -"Er moeten items worden geselecteerd om acties op uit te voeren. Geen items " -"zijn veranderd." +"Er moeten items worden geselecteerd om acties op uit te voeren. Er zijn geen " +"items gewijzigd." msgid "No action selected." msgstr "Geen actie geselecteerd." #, python-format msgid "The %(name)s \"%(obj)s\" was deleted successfully." -msgstr "\"%(obj)s\" van type %(name)s is verwijderd." +msgstr "De %(name)s '%(obj)s' is met succes verwijderd." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(name)s object met primaire sleutel %(key)r bestaat niet." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s met ID '%(key)s' bestaat niet. Misschien is deze verwijderd?" #, python-format msgid "Add %s" -msgstr "Toevoegen %s" +msgstr "%s toevoegen" #, python-format msgid "Change %s" -msgstr "Wijzig %s" +msgstr "%s wijzigen" + +#, python-format +msgid "View %s" +msgstr "%s weergeven" msgid "Database error" msgstr "Databasefout" @@ -232,8 +259,8 @@ msgstr "Databasefout" #, 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 is succesvol gewijzigd." -msgstr[1] "%(count)s %(name)s zijn succesvol gewijzigd." +msgstr[0] "%(count)s %(name)s is met succes gewijzigd." +msgstr[1] "%(count)s %(name)s zijn met succes gewijzigd." #, python-format msgid "%(total_count)s selected" @@ -264,26 +291,26 @@ msgstr "" "de volgende beschermde gerelateerde objecten: %(related_objects)s" msgid "Django site admin" -msgstr "Django sitebeheer" +msgstr "Django-websitebeheer" msgid "Django administration" -msgstr "Djangobeheer" +msgstr "Django-beheer" msgid "Site administration" -msgstr "Sitebeheer" +msgstr "Websitebeheer" msgid "Log in" -msgstr "Inloggen" +msgstr "Aanmelden" #, python-format msgid "%(app)s administration" -msgstr "%(app)s beheer" +msgstr "%(app)s-beheer" msgid "Page not found" msgstr "Pagina niet gevonden" msgid "We're sorry, but the requested page could not be found." -msgstr "Onze excuses, maar de gevraagde pagina bestaat niet." +msgstr "Het spijt ons, maar de opgevraagde pagina kon niet worden gevonden." msgid "Home" msgstr "Voorpagina" @@ -301,25 +328,25 @@ 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 "" -"Er heeft zich een fout voorgedaan. De fout is via email gemeld aan de " -"website administrators en zou snel verholpen moeten zijn. Bedankt voor uw " +"Er heeft zich een fout voorgedaan. Dit is via e-mail bij de " +"websitebeheerders gemeld en zou snel verholpen moeten zijn. Bedankt voor uw " "geduld." msgid "Run the selected action" -msgstr "Voer de geselecteerde actie uit" +msgstr "De geselecteerde actie uitvoeren" msgid "Go" -msgstr "Voer Uit" +msgstr "Uitvoeren" msgid "Click here to select the objects across all pages" msgstr "Klik hier om alle objecten op alle pagina's te selecteren" #, python-format msgid "Select all %(total_count)s %(module_name)s" -msgstr "Selecteer alle %(total_count)s %(module_name)s" +msgstr "Alle %(total_count)s %(module_name)s selecteren" msgid "Clear selection" -msgstr "Leeg selectie" +msgstr "Selectie wissen" msgid "" "First, enter a username and password. Then, you'll be able to edit more user " @@ -335,21 +362,21 @@ msgid "Change password" msgstr "Wachtwoord wijzigen" msgid "Please correct the error below." -msgstr "Herstel de fouten hieronder." +msgstr "Corrigeer de fout hieronder." msgid "Please correct the errors below." -msgstr "Herstel de fouten hieronder." +msgstr "Corrigeer de fouten hieronder." #, python-format msgid "Enter a new password for the user %(username)s." msgstr "" -"Geef een nieuw wachtwoord voor gebruiker %(username)s." +"Voer een nieuw wachtwoord in voor de gebruiker %(username)s." msgid "Welcome," msgstr "Welkom," msgid "View site" -msgstr "Bekijk site" +msgstr "Website bekijken" msgid "Documentation" msgstr "Documentatie" @@ -365,17 +392,17 @@ msgid "History" msgstr "Geschiedenis" msgid "View on site" -msgstr "Toon op site" +msgstr "Weergeven op website" msgid "Filter" msgstr "Filter" msgid "Remove from sorting" -msgstr "Verwijder uit de sortering" +msgstr "Verwijderen uit sortering" #, python-format msgid "Sorting priority: %(priority_number)s" -msgstr "Sorteer prioriteit: %(priority_number)s" +msgstr "Sorteerprioriteit: %(priority_number)s" msgid "Toggle sorting" msgstr "Sortering aan/uit" @@ -390,8 +417,8 @@ msgid "" "following types of objects:" msgstr "" "Het verwijderen van %(object_name)s '%(escaped_object)s' zal ook " -"gerelateerde objecten verwijderen. Echter u heeft geen rechten om de " -"volgende typen objecten te verwijderen:" +"gerelateerde objecten verwijderen. U hebt echter geen rechten om de volgende " +"typen objecten te verwijderen:" #, python-format msgid "" @@ -406,8 +433,8 @@ msgid "" "Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " "All of the following related items will be deleted:" msgstr "" -"Weet u zeker dat u %(object_name)s \"%(escaped_object)s\" wilt verwijderen? " -"Alle volgende objecten worden verwijderd:" +"Weet u zeker dat u %(object_name)s '%(escaped_object)s' wilt verwijderen? " +"Alle volgende gerelateerde objecten worden verwijderd:" msgid "Objects" msgstr "Objecten" @@ -416,10 +443,10 @@ msgid "Yes, I'm sure" msgstr "Ja, ik weet het zeker" msgid "No, take me back" -msgstr "Nee, ga terug" +msgstr "Nee, teruggaan" msgid "Delete multiple objects" -msgstr "Verwijder meerdere objecten" +msgstr "Meerdere objecten verwijderen" #, python-format msgid "" @@ -447,8 +474,8 @@ msgstr "" "Weet u zeker dat u de geselecteerde %(objects_name)s wilt verwijderen? Alle " "volgende objecten en hun aanverwante items zullen worden verwijderd:" -msgid "Change" -msgstr "Wijzigen" +msgid "View" +msgstr "Weergeven" msgid "Delete?" msgstr "Verwijderen?" @@ -467,14 +494,14 @@ msgstr "Modellen in de %(name)s applicatie" msgid "Add" msgstr "Toevoegen" -msgid "You don't have permission to edit anything." -msgstr "U heeft geen rechten om iets te wijzigen." +msgid "You don't have permission to view or edit anything." +msgstr "U hebt geen rechten om iets te bekijken of te verwijderen." msgid "Recent actions" -msgstr "" +msgstr "Recente acties" msgid "My actions" -msgstr "" +msgstr "Mijn acties" msgid "None available" msgstr "Geen beschikbaar" @@ -496,7 +523,7 @@ msgid "" "page. Would you like to login to a different account?" msgstr "" "U bent geverifieerd als %(username)s, maar niet bevoegd om deze pagina te " -"bekijken. Wilt u inloggen met een ander account?" +"bekijken. Wilt u zich aanmelden bij een andere account?" msgid "Forgotten your password or username?" msgstr "Wachtwoord of gebruikersnaam vergeten?" @@ -523,23 +550,11 @@ msgstr "Alles tonen" msgid "Save" msgstr "Opslaan" -msgid "Popup closing..." -msgstr "Popup wordt gesloten..." - -#, python-format -msgid "Change selected %(model)s" -msgstr "Wijzig geselecteerde %(model)s" - -#, python-format -msgid "Add another %(model)s" -msgstr "Voeg nog een %(model)s toe" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Verwijder geselecteerde %(model)s" +msgid "Popup closing…" +msgstr "Pop-up sluiten…" msgid "Search" -msgstr "Zoek" +msgstr "Zoeken" #, python-format msgid "%(counter)s result" @@ -560,11 +575,29 @@ msgstr "Opslaan en nieuwe toevoegen" msgid "Save and continue editing" msgstr "Opslaan en opnieuw bewerken" +msgid "Save and view" +msgstr "Opslaan en weergeven" + +msgid "Close" +msgstr "Sluiten" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Geselecteerde %(model)s wijzigen" + +#, python-format +msgid "Add another %(model)s" +msgstr "Nog een %(model)s toevoegen" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Geselecteerde %(model)s verwijderen" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Bedankt voor de aanwezigheid op de site vandaag." msgid "Log in again" -msgstr "Log opnieuw in" +msgstr "Opnieuw aanmelden" msgid "Password change" msgstr "Wachtwoordwijziging" @@ -580,13 +613,13 @@ msgstr "" "invoeren, zodat we kunnen controleren of er geen typefouten zijn gemaakt." msgid "Change my password" -msgstr "Wijzig mijn wachtwoord" +msgstr "Mijn wachtwoord wijzigen" msgid "Password reset" msgstr "Wachtwoord hersteld" msgid "Your password has been set. You may go ahead and log in now." -msgstr "Uw wachtwoord is ingesteld. U kunt nu verder gaan en inloggen." +msgstr "Uw wachtwoord is ingesteld. U kunt nu verdergaan en zich aanmelden." msgid "Password reset confirmation" msgstr "Bevestiging wachtwoord herstellen" @@ -615,50 +648,50 @@ 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 "" -"We hebben u instructies toegestuurd om uw wachtwoord in te stellen, als er " -"een account bestond met het door u opgegeven emailadres. U zou deze binnen " -"korte tijd moeten ontvangen." +"We hebben u instructies gestuurd voor het instellen van uw wachtwoord, als " +"er een account bestaat met het door u ingevoerde e-mailadres. U zou deze " +"straks moeten ontvangen." 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 "" "Als u geen e-mail ontvangt, controleer dan of u het e-mailadres hebt " -"opgegeven waar u zich mee geregistreerd heeft en controleer uw spam-map." +"ingevoerd waarmee u zich hebt geregistreerd en controleer uw spam-map." #, python-format msgid "" "You're receiving this email because you requested a password reset for your " "user account at %(site_name)s." msgstr "" -"U ontvangt deze email omdat u heeft verzocht het wachtwoord te resetten voor " -"uw account op %(site_name)s." +"U ontvangt deze e-mail, omdat u een aanvraag voor opnieuw instellen van het " +"wachtwoord voor uw account op %(site_name)s hebt gedaan." msgid "Please go to the following page and choose a new password:" -msgstr "Gaat u naar de volgende pagina en kies een nieuw wachtwoord:" +msgstr "Ga naar de volgende pagina en kies een nieuw wachtwoord:" msgid "Your username, in case you've forgotten:" msgstr "Uw gebruikersnaam, mocht u deze vergeten zijn:" msgid "Thanks for using our site!" -msgstr "Bedankt voor het gebruik van onze site!" +msgstr "Bedankt voor het gebruik van onze website!" #, python-format msgid "The %(site_name)s team" -msgstr "Het %(site_name)s team" +msgstr "Het %(site_name)s-team" msgid "" "Forgotten your password? Enter your email address below, and we'll email " "instructions for setting a new one." msgstr "" -"Wachtwoord vergeten? Vul uw emailadres hieronder in, en we zullen " -"instructies voor het opnieuw instellen van uw wachtwoord mailen." +"Wachtwoord vergeten? Vul hieronder uw e-mailadres in, en we sturen " +"instructies voor het instellen van een nieuw wachtwoord." msgid "Email address:" -msgstr "Emailadres:" +msgstr "E-mailadres:" msgid "Reset my password" -msgstr "Herstel mijn wachtwoord" +msgstr "Mijn wachtwoord opnieuw instellen" msgid "All dates" msgstr "Alle data" @@ -671,6 +704,10 @@ msgstr "Selecteer %s" msgid "Select %s to change" msgstr "Selecteer %s om te wijzigen" +#, python-format +msgid "Select %s to view" +msgstr "Selecteer %s om te bekijken" + msgid "Date:" msgstr "Datum:" @@ -684,4 +721,4 @@ msgid "Currently:" msgstr "Huidig:" msgid "Change:" -msgstr "Wijzig:" +msgstr "Wijzigen:" diff --git a/django/contrib/admin/locale/nl/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/nl/LC_MESSAGES/djangojs.mo index 3dfa3a65b15e..348bbbc1ad7f 100644 Binary files a/django/contrib/admin/locale/nl/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/nl/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/nl/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/nl/LC_MESSAGES/djangojs.po index 4fd01bf93484..f89838cc88b1 100644 --- a/django/contrib/admin/locale/nl/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/nl/LC_MESSAGES/djangojs.po @@ -1,20 +1,22 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Bouke Haarsma , 2013 +# Bouke Haarsma , 2013 +# Evelijn Saaltink , 2016 # Harro van der Klauw , 2012 # Ilja Maas , 2015 # Jannis Leidel , 2011 # Jeffrey Gelens , 2011-2012 -# Sander Steffann , 2015 +# Sander Steffann , 2015 +# Tonnes , 2019 # wunki , 2011 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2019-02-24 20:42+0000\n" +"Last-Translator: Tonnes \n" "Language-Team: Dutch (http://www.transifex.com/django/django/language/nl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -31,19 +33,19 @@ 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 "" -"Dit is de lijst met beschikbare %s. U kunt kiezen uit een aantal door ze te " -"selecteren in het vak hieronder en vervolgens op de \"Kiezen\" pijl tussen " -"de twee lijsten te klikken." +"Dit is de lijst met beschikbare %s. U kunt er een aantal kiezen door ze in " +"het vak hieronder te selecteren en daarna op de pijl 'Kiezen' tussen de twee " +"vakken te klikken." #, javascript-format msgid "Type into this box to filter down the list of available %s." -msgstr "Type in dit vak om te filteren in de lijst met beschikbare %s." +msgstr "Typ in dit vak om de lijst met beschikbare %s te filteren." msgid "Filter" msgstr "Filter" msgid "Choose all" -msgstr "Kies alle" +msgstr "Alle kiezen" #, javascript-format msgid "Click to choose all %s at once." @@ -64,9 +66,9 @@ 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 "" -"Dit is de lijst van de gekozen %s. Je kunt ze verwijderen door ze te " -"selecteren in het vak hieronder en vervolgens op de \"Verwijderen\" pijl " -"tussen de twee lijsten te klikken." +"Dit is de lijst met gekozen %s. U kunt er een aantal verwijderen door ze in " +"het vak hieronder te selecteren en daarna op de pijl 'Verwijderen' tussen de " +"twee vakken te klikken." msgid "Remove all" msgstr "Verwijder alles" @@ -84,48 +86,30 @@ msgid "" "You have unsaved changes on individual editable fields. If you run an " "action, your unsaved changes will be lost." msgstr "" -"U heeft niet opgeslagen wijzigingen op enkele indviduele velden. Als u nu " -"een actie uitvoert zullen uw wijzigingen verloren gaan." +"U hebt niet-opgeslagen wijzigingen op afzonderlijke bewerkbare velden. Als u " +"een actie uitvoert, gaan uw wijzigingen verloren." 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 "" -"U heeft een actie geselecteerd, maar heeft de wijzigingen op de individuele " -"velden nog niet opgeslagen. Klik alstublieft op OK om op te slaan. U zult " -"vervolgens de actie opnieuw moeten uitvoeren." +"U hebt een actie geselecteerd, maar uw wijzigingen in afzonderlijke velden " +"nog niet opgeslagen. Klik op OK om op te slaan. U dient de actie opnieuw uit " +"te voeren." 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 "" -"U heeft een actie geselecteerd en heeft geen wijzigingen gemaakt op de " -"individuele velden. U zoekt waarschijnlijk naar de Gaan knop in plaats van " -"de Opslaan knop." - -#, javascript-format -msgid "Note: You are %s hour ahead of server time." -msgid_plural "Note: You are %s hours ahead of server time." -msgstr[0] "Let op: U ligt %s uur voor ten opzichte van de server-tijd." -msgstr[1] "Let op: U ligt %s uren voor ten opzichte van de server-tijd." - -#, javascript-format -msgid "Note: You are %s hour behind server time." -msgid_plural "Note: You are %s hours behind server time." -msgstr[0] "Let op: U ligt %s uur achter ten opzichte van de server-tijd." -msgstr[1] "Let op: U ligt %s uren achter ten opzichte van de server-tijd." +"U hebt een actie geselecteerd, en geen wijzigingen in afzonderlijke velden " +"aangebracht. Waarschijnlijk zoekt u de knop Gaan in plaats van de knop " +"Opslaan." msgid "Now" msgstr "Nu" -msgid "Choose a Time" -msgstr "Kies een tijdstip" - -msgid "Choose a time" -msgstr "Kies een tijd" - msgid "Midnight" msgstr "Middernacht" @@ -138,6 +122,24 @@ msgstr "12 uur 's middags" msgid "6 p.m." msgstr "6 uur 's avonds" +#, javascript-format +msgid "Note: You are %s hour ahead of server time." +msgid_plural "Note: You are %s hours ahead of server time." +msgstr[0] "Let op: u ligt %s uur voor ten opzichte van de servertijd." +msgstr[1] "Let op: u ligt %s uur voor ten opzichte van de servertijd." + +#, javascript-format +msgid "Note: You are %s hour behind server time." +msgid_plural "Note: You are %s hours behind server time." +msgstr[0] "Let op: u ligt %s uur achter ten opzichte van de servertijd." +msgstr[1] "Let op: u ligt %s uur achter ten opzichte van de servertijd." + +msgid "Choose a Time" +msgstr "Kies een tijdstip" + +msgid "Choose a time" +msgstr "Kies een tijd" + msgid "Cancel" msgstr "Annuleren" @@ -154,68 +156,68 @@ msgid "Tomorrow" msgstr "Morgen" msgid "January" -msgstr "" +msgstr "januari" msgid "February" -msgstr "" +msgstr "februari" msgid "March" -msgstr "" +msgstr "maart" msgid "April" -msgstr "" +msgstr "april" msgid "May" -msgstr "" +msgstr "mei" msgid "June" -msgstr "" +msgstr "juni" msgid "July" -msgstr "" +msgstr "juli" msgid "August" -msgstr "" +msgstr "augustus" msgid "September" -msgstr "" +msgstr "september" msgid "October" -msgstr "" +msgstr "oktober" msgid "November" -msgstr "" +msgstr "november" msgid "December" -msgstr "" +msgstr "december" msgctxt "one letter Sunday" msgid "S" -msgstr "" +msgstr "S" msgctxt "one letter Monday" msgid "M" -msgstr "" +msgstr "M" msgctxt "one letter Tuesday" msgid "T" -msgstr "" +msgstr "T" msgctxt "one letter Wednesday" msgid "W" -msgstr "" +msgstr "W" msgctxt "one letter Thursday" msgid "T" -msgstr "" +msgstr "T" msgctxt "one letter Friday" msgid "F" -msgstr "" +msgstr "F" msgctxt "one letter Saturday" msgid "S" -msgstr "" +msgstr "S" msgid "Show" msgstr "Tonen" diff --git a/django/contrib/admin/locale/nn/LC_MESSAGES/django.mo b/django/contrib/admin/locale/nn/LC_MESSAGES/django.mo index c431d91d7db6..78170f03f174 100644 Binary files a/django/contrib/admin/locale/nn/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/nn/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/nn/LC_MESSAGES/django.po b/django/contrib/admin/locale/nn/LC_MESSAGES/django.po index f6d2e244ccde..a85f011cacb2 100644 --- a/django/contrib/admin/locale/nn/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/nn/LC_MESSAGES/django.po @@ -10,8 +10,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Norwegian Nynorsk (http://www.transifex.com/django/django/" "language/nn/)\n" @@ -209,8 +209,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" vart sletta." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(name)s-objekt med primærnøkkelen %(key)r eksisterer ikkje." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" diff --git a/django/contrib/admin/locale/nn/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/nn/LC_MESSAGES/djangojs.mo index 4072b5dec5ab..c4c82413e535 100644 Binary files a/django/contrib/admin/locale/nn/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/nn/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/nn/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/nn/LC_MESSAGES/djangojs.po index feeda0935b2e..07ba2f636512 100644 --- a/django/contrib/admin/locale/nn/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/nn/LC_MESSAGES/djangojs.po @@ -9,7 +9,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Norwegian Nynorsk (http://www.transifex.com/django/django/" "language/nn/)\n" diff --git a/django/contrib/admin/locale/os/LC_MESSAGES/django.mo b/django/contrib/admin/locale/os/LC_MESSAGES/django.mo index 6298ea48b07f..dbf509f59e4f 100644 Binary files a/django/contrib/admin/locale/os/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/os/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/os/LC_MESSAGES/django.po b/django/contrib/admin/locale/os/LC_MESSAGES/django.po index 86142427b8b8..aae9d9c22d44 100644 --- a/django/contrib/admin/locale/os/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/os/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-19 16:40+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Ossetic (http://www.transifex.com/django/django/language/" "os/)\n" @@ -207,8 +207,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" хафт ӕрцыд." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(key)r фыццаг амонӕнимӕ %(name)s-ы объект нӕй." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" diff --git a/django/contrib/admin/locale/os/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/os/LC_MESSAGES/djangojs.mo index 79f84012fb4d..7af0f7931e4e 100644 Binary files a/django/contrib/admin/locale/os/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/os/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/os/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/os/LC_MESSAGES/djangojs.po index 60040cecc3ce..ec6c9c4591e7 100644 --- a/django/contrib/admin/locale/os/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/os/LC_MESSAGES/djangojs.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Ossetic (http://www.transifex.com/django/django/language/" "os/)\n" diff --git a/django/contrib/admin/locale/pa/LC_MESSAGES/django.mo b/django/contrib/admin/locale/pa/LC_MESSAGES/django.mo index 3c9c16da0602..7f9761593840 100644 Binary files a/django/contrib/admin/locale/pa/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/pa/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/pa/LC_MESSAGES/django.po b/django/contrib/admin/locale/pa/LC_MESSAGES/django.po index 0edf0d962b4b..14b83e881d3f 100644 --- a/django/contrib/admin/locale/pa/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/pa/LC_MESSAGES/django.po @@ -1,13 +1,14 @@ # This file is distributed under the same license as the Django package. # # Translators: +# A S Alam , 2018 # Jannis Leidel , 2011 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" +"POT-Creation-Date: 2018-05-21 14:16-0300\n" +"PO-Revision-Date: 2018-05-28 01:29+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Panjabi (Punjabi) (http://www.transifex.com/django/django/" "language/pa/)\n" @@ -23,7 +24,7 @@ msgstr "%(count)d %(items)s ਠੀਕ ਤਰ੍ਹਾਂ ਹਟਾਈਆਂ ਗ #, python-format msgid "Cannot delete %(name)s" -msgstr "" +msgstr "%(name)s ਨੂੰ ਹਟਾਇਆ ਨਹੀਂ ਜਾ ਸਕਦਾ" msgid "Are you sure?" msgstr "ਕੀ ਤੁਸੀਂ ਇਹ ਚਾਹੁੰਦੇ ਹੋ?" @@ -33,7 +34,7 @@ msgid "Delete selected %(verbose_name_plural)s" msgstr "ਚੁਣੇ %(verbose_name_plural)s ਹਟਾਓ" msgid "Administration" -msgstr "" +msgstr "ਪਰਸ਼ਾਸ਼ਨ" msgid "All" msgstr "ਸਭ" @@ -84,14 +85,23 @@ msgstr "%(verbose_name)s ਹੋਰ ਸ਼ਾਮਲ" msgid "Remove" msgstr "ਹਟਾਓ" +msgid "Addition" +msgstr "" + +msgid "Change" +msgstr "ਬਦਲੋ" + +msgid "Deletion" +msgstr "" + msgid "action time" msgstr "ਕਾਰਵਾਈ ਸਮਾਂ" msgid "user" -msgstr "" +msgstr "ਵਰਤੋਂਕਾਰ" msgid "content type" -msgstr "" +msgstr "ਸਮੱਗਰੀ ਕਿਸਮ" msgid "object id" msgstr "ਆਬਜੈਕਟ id" @@ -161,8 +171,10 @@ msgid "" msgstr "" #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "" + +msgid "You may edit it again below." msgstr "" #, python-brace-format @@ -172,12 +184,13 @@ msgid "" msgstr "" #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." +"The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" #, python-brace-format @@ -203,7 +216,7 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" ਠੀਕ ਤਰ੍ਹਾਂ ਹਟਾਇਆ ਗਿਆ ਹੈ।" #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" msgstr "" #, python-format @@ -214,6 +227,10 @@ msgstr "%s ਸ਼ਾਮਲ" msgid "Change %s" msgstr "%s ਬਦਲੋ" +#, python-format +msgid "View %s" +msgstr "" + msgid "Database error" msgstr "ਡਾਟਾਬੇਸ ਗਲਤੀ" @@ -316,7 +333,7 @@ msgid "Change password" msgstr "ਪਾਸਵਰਡ ਬਦਲੋ" msgid "Please correct the error below." -msgstr "ਹੇਠ ਦਿੱਤੀਆਂ ਗਲਤੀਆਂ ਠੀਕ ਕਰੋ ਜੀ।" +msgstr "" msgid "Please correct the errors below." msgstr "" @@ -413,8 +430,8 @@ msgid "" "following objects and their related items will be deleted:" msgstr "" -msgid "Change" -msgstr "ਬਦਲੋ" +msgid "View" +msgstr "" msgid "Delete?" msgstr "ਹਟਾਉਣਾ?" @@ -433,8 +450,8 @@ msgstr "" msgid "Add" msgstr "ਸ਼ਾਮਲ" -msgid "You don't have permission to edit anything." -msgstr "ਤੁਹਾਨੂੰ ਕੁਝ ਵੀ ਸੋਧਣ ਦਾ ਅਧਿਕਾਰ ਨਹੀਂ ਹੈ।" +msgid "You don't have permission to view or edit anything." +msgstr "" msgid "Recent actions" msgstr "" @@ -490,6 +507,10 @@ msgstr "" msgid "Change selected %(model)s" msgstr "" +#, python-format +msgid "View selected %(model)s" +msgstr "" + #, python-format msgid "Add another %(model)s" msgstr "" @@ -520,6 +541,12 @@ msgstr "ਸੰਭਾਲੋ ਤੇ ਹੋਰ ਸ਼ਾਮਲ" msgid "Save and continue editing" msgstr "ਸੰਭਾਲੋ ਤੇ ਸੋਧਣਾ ਜਾਰੀ ਰੱਖੋ" +msgid "Save and view" +msgstr "" + +msgid "Close" +msgstr "" + msgid "Thanks for spending some quality time with the Web site today." msgstr "ਅੱਜ ਵੈੱਬਸਾਈਟ ਨੂੰ ਕੁਝ ਚੰਗਾ ਸਮਾਂ ਦੇਣ ਲਈ ਧੰਨਵਾਦ ਹੈ।" @@ -621,6 +648,10 @@ msgstr "%s ਚੁਣੋ" msgid "Select %s to change" msgstr "ਬਦਲਣ ਲਈ %s ਚੁਣੋ" +#, python-format +msgid "Select %s to view" +msgstr "" + msgid "Date:" msgstr "ਮਿਤੀ:" diff --git a/django/contrib/admin/locale/pa/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/pa/LC_MESSAGES/djangojs.mo index c94a53054725..57cc79f362f4 100644 Binary files a/django/contrib/admin/locale/pa/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/pa/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/pa/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/pa/LC_MESSAGES/djangojs.po index 578323847e6f..2a3604630e6c 100644 --- a/django/contrib/admin/locale/pa/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/pa/LC_MESSAGES/djangojs.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:10+0000\n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Panjabi (Punjabi) (http://www.transifex.com/django/django/" "language/pa/)\n" @@ -86,6 +86,21 @@ msgid "" "button." msgstr "" +msgid "Now" +msgstr "ਹੁਣੇ" + +msgid "Midnight" +msgstr "ਅੱਧੀ-ਰਾਤ" + +msgid "6 a.m." +msgstr "6 ਸਵੇਰ" + +msgid "Noon" +msgstr "ਦੁਪਹਿਰ" + +msgid "6 p.m." +msgstr "" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." @@ -98,27 +113,12 @@ msgid_plural "Note: You are %s hours behind server time." msgstr[0] "" msgstr[1] "" -msgid "Now" -msgstr "ਹੁਣੇ" - msgid "Choose a Time" msgstr "" msgid "Choose a time" msgstr "ਸਮਾਂ ਚੁਣੋ" -msgid "Midnight" -msgstr "ਅੱਧੀ-ਰਾਤ" - -msgid "6 a.m." -msgstr "6 ਸਵੇਰ" - -msgid "Noon" -msgstr "ਦੁਪਹਿਰ" - -msgid "6 p.m." -msgstr "" - msgid "Cancel" msgstr "ਰੱਦ ਕਰੋ" diff --git a/django/contrib/admin/locale/pl/LC_MESSAGES/django.mo b/django/contrib/admin/locale/pl/LC_MESSAGES/django.mo index a3fe3bd83897..c27a1cb55bed 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 c2e19810d799..57eb78ba1b98 100644 --- a/django/contrib/admin/locale/pl/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/pl/LC_MESSAGES/django.po @@ -4,33 +4,35 @@ # angularcircle, 2011-2013 # angularcircle, 2013-2014 # Jannis Leidel , 2011 -# Janusz Harkot , 2014-2015 +# Janusz Harkot , 2014-2015 # Karol , 2012 # konryd , 2011 # konryd , 2011 -# m_aciek , 2016 +# m_aciek , 2016-2019 # m_aciek , 2015 # Ola Sitarska , 2013 # Ola Sitarska , 2013 -# Roman Barczyński , 2014 +# Roman Barczyński, 2014 +# Tomasz Kajtoch , 2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-08-14 22:58+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-26 20:42+0000\n" "Last-Translator: m_aciek \n" "Language-Team: Polish (http://www.transifex.com/django/django/language/pl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pl\n" -"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " -"|| n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n" +"%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n" +"%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" #, python-format msgid "Successfully deleted %(count)d %(items)s." -msgstr "Usunięto %(count)d %(items)s." +msgstr "Pomyślnie usunięto %(count)d %(items)s." #, python-format msgid "Cannot delete %(name)s" @@ -41,7 +43,7 @@ msgstr "Jesteś pewien?" #, python-format msgid "Delete selected %(verbose_name_plural)s" -msgstr "Usuń wybrane %(verbose_name_plural)s" +msgstr "Usuń wybranych %(verbose_name_plural)s" msgid "Administration" msgstr "Administracja" @@ -97,6 +99,15 @@ msgstr "Dodaj kolejne %(verbose_name)s" msgid "Remove" msgstr "Usuń" +msgid "Addition" +msgstr "Dodanie" + +msgid "Change" +msgstr "Zmień" + +msgid "Deletion" +msgstr "Usunięcie" + msgid "action time" msgstr "czas akcji" @@ -110,7 +121,7 @@ msgid "object id" msgstr "id obiektu" #. 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 "reprezentacja obiektu" @@ -132,42 +143,42 @@ msgstr "Dodano „%(object)s”." #, python-format msgid "Changed \"%(object)s\" - %(changes)s" -msgstr "Zmieniono „%(object)s” – %(changes)s " +msgstr "Zmieniono „%(object)s” - %(changes)s " #, python-format msgid "Deleted \"%(object)s.\"" msgstr "Usunięto „%(object)s”." msgid "LogEntry Object" -msgstr "Obiekt typu LogEntry" +msgstr "Obiekt LogEntry" #, python-brace-format msgid "Added {name} \"{object}\"." msgstr "Dodano {name} „{object}”." msgid "Added." -msgstr "Dodany." +msgstr "Dodano." msgid "and" msgstr "i" #, python-brace-format msgid "Changed {fields} for {name} \"{object}\"." -msgstr "Zmieniono {fields} w {name} „{object}”." +msgstr "Zmodyfikowano {fields} w {name} „{object}”." #, python-brace-format msgid "Changed {fields}." -msgstr "Zmieniono {fields}." +msgstr "Zmodyfikowano {fields}." #, python-brace-format msgid "Deleted {name} \"{object}\"." msgstr "Usunięto {name} „{object}”." msgid "No fields changed." -msgstr "Żadne pole nie zmienione." +msgstr "Żadne pole nie zostało zmienione." msgid "None" -msgstr "brak" +msgstr "Brak" msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." @@ -176,10 +187,11 @@ msgstr "" "więcej niż jeden wybór." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" -"{name} „{obj}” został dodany pomyślnie. Można edytować go ponownie poniżej." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "{name} „{obj}” został dodany pomyślnie." + +msgid "You may edit it again below." +msgstr "Poniżej możesz ponownie edytować." #, python-brace-format msgid "" @@ -188,10 +200,6 @@ msgid "" msgstr "" "{name} „{obj}” został dodany pomyślnie. Można dodać kolejny {name} poniżej." -#, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "{name} „{obj}” został dodany pomyślnie." - #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may edit it again below." @@ -199,6 +207,12 @@ msgstr "" "{name} „{obj}” został pomyślnie zmieniony. Można edytować go ponownie " "poniżej." +#, python-brace-format +msgid "" +"The {name} \"{obj}\" was added successfully. You may edit it again below." +msgstr "" +"{name} „{obj}” został dodany pomyślnie. Można edytować go ponownie poniżej." + #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " @@ -225,8 +239,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s „%(obj)s” usunięty pomyślnie." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Obiekt %(name)s o kluczu głównym %(key)r nie istnieje." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s z ID „%(key)s” nie istnieje. Może został usunięty?" #, python-format msgid "Add %s" @@ -236,6 +250,10 @@ msgstr "Dodaj %s" msgid "Change %s" msgstr "Zmień %s" +#, python-format +msgid "View %s" +msgstr "Obejrzyj %s" + msgid "Database error" msgstr "Błąd bazy danych" @@ -245,17 +263,19 @@ msgid_plural "%(count)s %(name)s were changed successfully." msgstr[0] "%(count)s %(name)s został pomyślnie zmieniony." msgstr[1] "%(count)s %(name)s zostały pomyślnie zmienione." msgstr[2] "%(count)s %(name)s zostało pomyślnie zmienionych." +msgstr[3] "%(count)s %(name)s zostało pomyślnie zmienionych." #, python-format msgid "%(total_count)s selected" msgid_plural "All %(total_count)s selected" -msgstr[0] "%(total_count)s wybrany" -msgstr[1] "%(total_count)s wybrane" -msgstr[2] "%(total_count)s wybranych" +msgstr[0] "Wybrano %(total_count)s" +msgstr[1] "Wybrano %(total_count)s" +msgstr[2] "Wybrano %(total_count)s" +msgstr[3] "Wybrano wszystkie %(total_count)s" #, python-format msgid "0 of %(cnt)s selected" -msgstr "0 z %(cnt)s wybranych" +msgstr "Wybrano 0 z %(cnt)s" #, python-format msgid "Change history: %s" @@ -272,8 +292,8 @@ msgid "" "Deleting %(class_name)s %(instance)s would require deleting the following " "protected related objects: %(related_objects)s" msgstr "" -"Usunięcie %(class_name)s %(instance)s spowoduje usunięcia następujących " -"chronionych obiektów pokrewnych: %(related_objects)s" +"Usunięcie %(class_name)s %(instance)s może wiązać się z usunięciem " +"następujących chronionych obiektów pokrewnych: %(related_objects)s" msgid "Django site admin" msgstr "Administracja stroną Django" @@ -289,16 +309,16 @@ msgstr "Zaloguj się" #, python-format msgid "%(app)s administration" -msgstr "%(app)s – administracja" +msgstr "%(app)s: administracja" msgid "Page not found" -msgstr "Strona nie znaleziona" +msgstr "Strona nie została znaleziona" msgid "We're sorry, but the requested page could not be found." -msgstr "Niestety, żądana strona nie została znaleziona." +msgstr "Przykro nam, ale żądana strona nie została znaleziona." msgid "Home" -msgstr "Początek" +msgstr "Strona główna" msgid "Server error" msgstr "Błąd serwera" @@ -313,9 +333,8 @@ 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 "" -"Niestety wystąpił błąd. Administratorzy strony zostali o nim powiadomieni " -"poprzez email i niebawem zaistniały problem powinien zostać rozwiązany. " -"Dziękujemy za wyrozumiałość." +"Niestety wystąpił błąd. Zostało to zgłoszone administratorom strony poprzez " +"email i niebawem powinno zostać naprawione. Dziękujemy za cierpliwość." msgid "Run the selected action" msgstr "Wykonaj wybraną akcję" @@ -347,7 +366,7 @@ msgid "Change password" msgstr "Zmiana hasła" msgid "Please correct the error below." -msgstr "Proszę, popraw poniższe błędy." +msgstr "Prosimy poprawić poniższy błąd." msgid "Please correct the errors below." msgstr "Proszę, popraw poniższe błędy." @@ -379,7 +398,7 @@ msgid "View on site" msgstr "Pokaż na stronie" msgid "Filter" -msgstr "Filtr" +msgstr "Filtruj" msgid "Remove from sorting" msgstr "Usuń z sortowania" @@ -389,7 +408,7 @@ msgid "Sorting priority: %(priority_number)s" msgstr "Priorytet sortowania: %(priority_number)s " msgid "Toggle sorting" -msgstr "Zmień sortowanie" +msgstr "Przełącz sortowanie" msgid "Delete" msgstr "Usuń" @@ -400,16 +419,16 @@ msgid "" "related objects, but your account doesn't have permission to delete the " "following types of objects:" msgstr "" -"Usunięcie %(object_name)s '%(escaped_object)s' spowoduje skasowanie " -"obiektów, które są z nim powiązane. Niestety nie posiadasz uprawnień do " -"usunięcia następujących typów obiektów:" +"Usunięcie %(object_name)s '%(escaped_object)s' może wiązać się z usunięciem " +"obiektów z nim powiązanych, ale niestety nie posiadasz uprawnień do " +"usunięcia obiektów następujących typów:" #, python-format msgid "" "Deleting the %(object_name)s '%(escaped_object)s' would require deleting the " "following protected related objects:" msgstr "" -"Usunięcie %(object_name)s '%(escaped_object)s' wymaga skasowania " +"Usunięcie %(object_name)s '%(escaped_object)s' może wymagać skasowania " "następujących chronionych obiektów, które są z nim powiązane:" #, python-format @@ -418,7 +437,7 @@ msgid "" "All of the following related items will be deleted:" msgstr "" "Czy chcesz skasować %(object_name)s „%(escaped_object)s”? Następujące " -"zależne obiekty zostaną skasowane:" +"obiekty powiązane zostaną usunięte:" msgid "Objects" msgstr "Obiekty" @@ -458,15 +477,15 @@ msgstr "" "Czy chcesz skasować zaznaczone %(objects_name)s? Następujące obiekty oraz " "obiekty od nich zależne zostaną skasowane:" -msgid "Change" -msgstr "Zmień" +msgid "View" +msgstr "Obejrzyj" msgid "Delete?" msgstr "Usunąć?" #, python-format msgid " By %(filter_title)s " -msgstr " Używając %(filter_title)s " +msgstr " Według pola %(filter_title)s " msgid "Summary" msgstr "Podsumowanie" @@ -478,8 +497,8 @@ msgstr "Modele w aplikacji %(name)s" msgid "Add" msgstr "Dodaj" -msgid "You don't have permission to edit anything." -msgstr "Nie masz uprawnień by edytować cokolwiek." +msgid "You don't have permission to view or edit anything." +msgstr "Nie masz uprawnień do oglądania ani edycji niczego." msgid "Recent actions" msgstr "Ostatnie działania" @@ -488,7 +507,7 @@ msgid "My actions" msgstr "Moje działania" msgid "None available" -msgstr "Brak" +msgstr "Brak dostępnych" msgid "Unknown content" msgstr "Zawartość nieznana" @@ -511,7 +530,7 @@ msgstr "" "dostępu do tej strony. Czy chciałbyś zalogować się na inne konto?" msgid "Forgotten your password or username?" -msgstr "Nie pamiętasz swojego hasła, bądź nazwy konta użytkownika?" +msgstr "Nie pamiętasz swojego hasła lub nazwy użytkownika?" msgid "Date/time" msgstr "Data/czas" @@ -526,8 +545,8 @@ msgid "" "This object doesn't have a change history. It probably wasn't added via this " "admin site." msgstr "" -"Ten obiekt nie ma historii zmian. Najprawdopodobniej wpis ten nie został " -"dodany poprzez panel administracyjny." +"Ten obiekt nie ma historii zmian. Najprawdopodobniej nie został on dodany " +"poprzez panel administracyjny." msgid "Show all" msgstr "Pokaż wszystko" @@ -535,20 +554,8 @@ msgstr "Pokaż wszystko" msgid "Save" msgstr "Zapisz" -msgid "Popup closing..." -msgstr "Zamykanie okienka..." - -#, python-format -msgid "Change selected %(model)s" -msgstr "Zmień wybrane %(model)s" - -#, python-format -msgid "Add another %(model)s" -msgstr "Dodaj kolejny %(model)s" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Usuń wybrane %(model)s" +msgid "Popup closing…" +msgstr "Zamykanie okna..." msgid "Search" msgstr "Szukaj" @@ -559,22 +566,41 @@ msgid_plural "%(counter)s results" msgstr[0] "%(counter)s wynik" msgstr[1] "%(counter)s wyniki" msgstr[2] "%(counter)s wyników" +msgstr[3] "%(counter)s wyników" #, python-format msgid "%(full_result_count)s total" -msgstr "%(full_result_count)s trafień" +msgstr "%(full_result_count)s łącznie" msgid "Save as new" -msgstr "Zapisz jako nowe" +msgstr "Zapisz jako nowy" msgid "Save and add another" -msgstr "Zapisz i dodaj nowe" +msgstr "Zapisz i dodaj nowy" msgid "Save and continue editing" msgstr "Zapisz i kontynuuj edycję" +msgid "Save and view" +msgstr "Zapisz i obejrzyj" + +msgid "Close" +msgstr "Zamknij" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Zmień wybrane %(model)s" + +#, python-format +msgid "Add another %(model)s" +msgstr "Dodaj kolejny %(model)s" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Usuń wybrane %(model)s" + msgid "Thanks for spending some quality time with the Web site today." -msgstr "Dziękujemy za odwiedzenie serwisu." +msgstr "Dziękujemy za spędzenie cennego czasu na stronie." msgid "Log in again" msgstr "Zaloguj się ponownie" @@ -588,7 +614,10 @@ msgstr "Twoje hasło zostało zmienione." 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 "Podaj swoje stare hasło i dwa razy nowe." +msgstr "" +"Podaj swoje stare hasło, ze względów bezpieczeństwa, a później wpisz " +"dwukrotnie Twoje nowe hasło, abyśmy mogli zweryfikować, że zostało wpisane " +"poprawnie." msgid "Change my password" msgstr "Zmień hasło" @@ -651,10 +680,10 @@ msgstr "" "poniżej:" msgid "Your username, in case you've forgotten:" -msgstr "Twoja nazwa użytkownika:" +msgstr "Twoja nazwa użytkownika, na wypadek, gdybyś zapomniał(a):" msgid "Thanks for using our site!" -msgstr "Dziękujemy za skorzystanie naszej strony." +msgstr "Dziękujemy za korzystanie naszej strony." #, python-format msgid "The %(site_name)s team" @@ -678,11 +707,15 @@ msgstr "Wszystkie daty" #, python-format msgid "Select %s" -msgstr "Zaznacz %s" +msgstr "Wybierz %s" #, python-format msgid "Select %s to change" -msgstr "Zaznacz %s do zmiany" +msgstr "Wybierz %s do zmiany" + +#, python-format +msgid "Select %s to view" +msgstr "Wybierz %s do obejrzenia" msgid "Date:" msgstr "Data:" diff --git a/django/contrib/admin/locale/pl/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/pl/LC_MESSAGES/djangojs.mo index aa482ce341a2..2685f40c076f 100644 Binary files a/django/contrib/admin/locale/pl/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/pl/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/pl/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/pl/LC_MESSAGES/djangojs.po index e55dc7c51f0e..9125a94ed4fc 100644 --- a/django/contrib/admin/locale/pl/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/pl/LC_MESSAGES/djangojs.po @@ -3,24 +3,26 @@ # Translators: # angularcircle, 2011 # Jannis Leidel , 2011 -# Janusz Harkot , 2014-2015 +# Janusz Harkot , 2014-2015 # konryd , 2011 -# m_aciek , 2016 -# Roman Barczyński , 2012 +# m_aciek , 2016,2018 +# Roman Barczyński, 2012 +# Tomasz Kajtoch , 2016-2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-08-09 13:22+0000\n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2018-12-21 22:38+0000\n" "Last-Translator: m_aciek \n" "Language-Team: Polish (http://www.transifex.com/django/django/language/pl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pl\n" -"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " -"|| n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n" +"%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n" +"%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" #, javascript-format msgid "Available %s" @@ -31,12 +33,12 @@ 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 "" -"To jest lista dostępnych %s. Aby wybrać pozycje zaznacz je i kliknij " -"strzałkę „Wybierz” pomiędzy listami." +"To lista dostępnych %s. Aby wybrać pozycje, zaznacz je i kliknij strzałkę " +"„Wybierz” pomiędzy listami." #, javascript-format msgid "Type into this box to filter down the list of available %s." -msgstr "Pisz tutaj aby wyfiltrować listę dostępnych %s." +msgstr "Wpisz coś tutaj, aby wyfiltrować listę dostępnych %s." msgid "Filter" msgstr "Filtr" @@ -46,7 +48,7 @@ msgstr "Wybierz wszystkie" #, javascript-format msgid "Click to choose all %s at once." -msgstr "Kliknij aby wybrać wszystkie %s na raz." +msgstr "Kliknij, aby wybrać jednocześnie wszystkie %s." msgid "Choose" msgstr "Wybierz" @@ -63,27 +65,28 @@ 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 "" -"To jest lista wybranych %s. Aby usunąć zaznacz pozycje wybrane do usunięcia " -"i kliknij strzałkę „Usuń” pomiędzy listami." +"To lista wybranych %s. Aby usunąć, zaznacz pozycje wybrane do usunięcia i " +"kliknij strzałkę „Usuń” pomiędzy listami." msgid "Remove all" msgstr "Usuń wszystkie" #, javascript-format msgid "Click to remove all chosen %s at once." -msgstr "Kliknij aby usunąć wszystkie wybrane %s na raz." +msgstr "Kliknij, aby usunąć jednocześnie wszystkie wybrane %s." msgid "%(sel)s of %(cnt)s selected" msgid_plural "%(sel)s of %(cnt)s selected" -msgstr[0] "Zaznaczono %(sel)s z %(cnt)s" -msgstr[1] "Zaznaczono %(sel)s z %(cnt)s" -msgstr[2] "Zaznaczono %(sel)s z %(cnt)s" +msgstr[0] "Wybrano %(sel)s z %(cnt)s" +msgstr[1] "Wybrano %(sel)s z %(cnt)s" +msgstr[2] "Wybrano %(sel)s z %(cnt)s" +msgstr[3] "Wybrano %(sel)s z %(cnt)s" msgid "" "You have unsaved changes on individual editable fields. If you run an " "action, your unsaved changes will be lost." msgstr "" -"Zmiany w niektórych polach nie zostały zachowane. Po wykonaniu akcji zmiany " +"Zmiany w niektórych polach nie zostały zachowane. Po wykonaniu akcji, zmiany " "te zostaną utracone." msgid "" @@ -91,7 +94,7 @@ msgid "" "individual fields yet. Please click OK to save. You'll need to re-run the " "action." msgstr "" -"Wybrano akcję, lecz część zmian w polach nie została zachowana. Kliknij OK " +"Wybrano akcję, lecz część zmian w polach nie została zachowana. Kliknij OK, " "aby zapisać. Aby wykonać akcję, należy ją ponownie uruchomić." msgid "" @@ -99,52 +102,62 @@ msgid "" "fields. You're probably looking for the Go button rather than the Save " "button." msgstr "" -"Wybrano akcję, lecz nie dokonano żadnych zmian. Prawdopodobnie szukasz " -"przycisku „Wykonaj” (a nie „Zapisz”)" +"Wybrano akcję, lecz nie dokonano żadnych zmian w polach. Prawdopodobnie " +"szukasz przycisku „Wykonaj”, a nie „Zapisz”." + +msgid "Now" +msgstr "Teraz" + +msgid "Midnight" +msgstr "Północ" + +msgid "6 a.m." +msgstr "6 rano" + +msgid "Noon" +msgstr "Południe" + +msgid "6 p.m." +msgstr "6 po południu" #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." msgstr[0] "" -"Uwaga: Czas lokalny jest przesunięty %s godzinę w stosunku do czasu serwera." +"Uwaga: Czas lokalny jest przesunięty o %s godzinę do przodu w stosunku do " +"czasu serwera." msgstr[1] "" -"Uwaga: Czas lokalny jest przesunięty %s godziny w stosunku do czasu serwera." +"Uwaga: Czas lokalny jest przesunięty o %s godziny do przodu w stosunku do " +"czasu serwera." msgstr[2] "" -"Uwaga: Czas lokalny jest przesunięty %s godzin w stosunku do czasu serwera." +"Uwaga: Czas lokalny jest przesunięty o %s godzin do przodu w stosunku do " +"czasu serwera." +msgstr[3] "" +"Uwaga: Czas lokalny jest przesunięty o %s godzin do przodu w stosunku do " +"czasu serwera." #, javascript-format msgid "Note: You are %s hour behind server time." msgid_plural "Note: You are %s hours behind server time." msgstr[0] "" -"Uwaga: Czas lokalny jest przesunięty o %s godzinę w stosunku do czasu " -"serwera." +"Uwaga: Czas lokalny jest przesunięty o %s godzinę do tyłu w stosunku do " +"czasu serwera." msgstr[1] "" -"Uwaga: Czas lokalny jest przesunięty o %s godziny w stosunku do czasu " -"serwera." +"Uwaga: Czas lokalny jest przesunięty o %s godziny do tyłu w stosunku do " +"czasu serwera." msgstr[2] "" -"Uwaga: Czas lokalny jest przesunięty o %s godzin w stosunku do czasu serwera." - -msgid "Now" -msgstr "Teraz" +"Uwaga: Czas lokalny jest przesunięty o %s godzin do tyłu w stosunku do czasu " +"serwera." +msgstr[3] "" +"Uwaga: Czas lokalny jest przesunięty o %s godzin do tyłu w stosunku do czasu " +"serwera." msgid "Choose a Time" -msgstr "Wybierz czas" +msgstr "Wybierz Czas" msgid "Choose a time" msgstr "Wybierz czas" -msgid "Midnight" -msgstr "Północ" - -msgid "6 a.m." -msgstr "6 rano" - -msgid "Noon" -msgstr "Południe" - -msgid "6 p.m." -msgstr "6 po południu" - msgid "Cancel" msgstr "Anuluj" @@ -152,7 +165,7 @@ msgid "Today" msgstr "Dzisiaj" msgid "Choose a Date" -msgstr "Wybierz datę" +msgstr "Wybierz Datę" msgid "Yesterday" msgstr "Wczoraj" @@ -161,68 +174,68 @@ msgid "Tomorrow" msgstr "Jutro" msgid "January" -msgstr "styczeń" +msgstr "Styczeń" msgid "February" -msgstr "luty" +msgstr "Luty" msgid "March" -msgstr "marzec" +msgstr "Marzec" msgid "April" -msgstr "kwiecień" +msgstr "Kwiecień" msgid "May" -msgstr "maj" +msgstr "Maj" msgid "June" -msgstr "czerwiec" +msgstr "Czerwiec" msgid "July" -msgstr "lipiec" +msgstr "Lipiec" msgid "August" -msgstr "sierpień" +msgstr "Sierpień" msgid "September" -msgstr "wrzesień" +msgstr "Wrzesień" msgid "October" -msgstr "październik" +msgstr "Październik" msgid "November" -msgstr "listopad" +msgstr "Listopad" msgid "December" -msgstr "grudzień" +msgstr "Grudzień" msgctxt "one letter Sunday" msgid "S" -msgstr "n" +msgstr "N" msgctxt "one letter Monday" msgid "M" -msgstr "pn" +msgstr "P" msgctxt "one letter Tuesday" msgid "T" -msgstr "w" +msgstr "W" msgctxt "one letter Wednesday" msgid "W" -msgstr "ś" +msgstr "Ś" msgctxt "one letter Thursday" msgid "T" -msgstr "c" +msgstr "C" msgctxt "one letter Friday" msgid "F" -msgstr "pt" +msgstr "P" msgctxt "one letter Saturday" msgid "S" -msgstr "s" +msgstr "S" msgid "Show" msgstr "Pokaż" diff --git a/django/contrib/admin/locale/pt/LC_MESSAGES/django.mo b/django/contrib/admin/locale/pt/LC_MESSAGES/django.mo index 4590986e6c95..d7ec87d28b83 100644 Binary files a/django/contrib/admin/locale/pt/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/pt/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/pt/LC_MESSAGES/django.po b/django/contrib/admin/locale/pt/LC_MESSAGES/django.po index b1d87b959264..2d39cdb30459 100644 --- a/django/contrib/admin/locale/pt/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/pt/LC_MESSAGES/django.po @@ -1,18 +1,20 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Henrique Azevedo , 2018 # Jannis Leidel , 2011 # jorgecarleitao , 2015 -# Nuno Mariz , 2013,2015 +# Nuno Mariz , 2013,2015,2017-2018 # Paulo Köch , 2011 # Raúl Pedro Fernandes Santos, 2014 +# Rui Dinis Silva, 2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 00:36+0000\n" +"Last-Translator: Ramiro Morales\n" "Language-Team: Portuguese (http://www.transifex.com/django/django/language/" "pt/)\n" "MIME-Version: 1.0\n" @@ -67,10 +69,10 @@ msgid "This year" msgstr "Este ano" msgid "No date" -msgstr "" +msgstr "Sem data" msgid "Has date" -msgstr "" +msgstr "Tem data" #, python-format msgid "" @@ -90,6 +92,15 @@ msgstr "Adicionar outro %(verbose_name)s" msgid "Remove" msgstr "Remover" +msgid "Addition" +msgstr "Adição" + +msgid "Change" +msgstr "Modificar" + +msgid "Deletion" +msgstr "Eliminação" + msgid "action time" msgstr "hora da ação" @@ -103,7 +114,7 @@ msgid "object id" msgstr "id do objeto" #. 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 do objeto" @@ -136,7 +147,7 @@ msgstr "Objeto LogEntry" #, python-brace-format msgid "Added {name} \"{object}\"." -msgstr "" +msgstr "Foi adicionado {name} \"{object}\"." msgid "Added." msgstr "Adicionado." @@ -146,15 +157,15 @@ msgstr "e" #, python-brace-format msgid "Changed {fields} for {name} \"{object}\"." -msgstr "" +msgstr "Foram modificados os {fields} para {name} \"{object}\"." #, python-brace-format msgid "Changed {fields}." -msgstr "" +msgstr "Foi modificado {fields}." #, python-brace-format msgid "Deleted {name} \"{object}\"." -msgstr "" +msgstr "Foi removido {name} \"{object}\"." msgid "No fields changed." msgstr "Nenhum campo foi modificado." @@ -169,34 +180,45 @@ msgstr "" "mais do que um." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" +msgid "The {name} \"{obj}\" was added successfully." +msgstr "O {name} \"{obj}\" foi adicionado com sucesso." + +msgid "You may edit it again below." +msgstr "Pode editar novamente abaixo." #, python-brace-format msgid "" "The {name} \"{obj}\" was added successfully. You may add another {name} " "below." msgstr "" +"O {name} \"{obj}\" foi adicionado com sucesso. Pode adicionar um novo {name} " +"abaixo." #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" +"O {name} \"{obj}\" foi modificado com sucesso. Pode voltar a editar " +"novamente abaixo." #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." +"The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" +"O {name} \"{obj}\" foi adicionado com sucesso. Pode voltar a editar " +"novamente abaixo." #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " "below." msgstr "" +"O {name} \"{obj}\" foi modificado com sucesso. Pode adicionar um novo {name} " +"abaixo." #, python-brace-format msgid "The {name} \"{obj}\" was changed successfully." -msgstr "" +msgstr "O {name} \"{obj}\" foi modificado com sucesso." msgid "" "Items must be selected in order to perform actions on them. No items have " @@ -213,8 +235,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "O(A) %(name)s \"%(obj)s\" foi removido(a) com sucesso." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "O object %(name)s com a chave primária %(key)r não existe." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s com ID \"%(key)s\" não existe. Talvez foi removido?" #, python-format msgid "Add %s" @@ -224,6 +246,10 @@ msgstr "Adicionar %s" msgid "Change %s" msgstr "Modificar %s" +#, python-format +msgid "View %s" +msgstr "View %s " + msgid "Database error" msgstr "Erro de base de dados" @@ -332,7 +358,7 @@ msgid "Change password" msgstr "Modificar palavra-passe" msgid "Please correct the error below." -msgstr "Por favor corrija os erros abaixo." +msgstr "Por favor corrija o erro abaixo." msgid "Please correct the errors below." msgstr "Por favor corrija os erros abaixo." @@ -445,8 +471,8 @@ msgstr "" "Tem certeza de que deseja remover %(objects_name)s selecionado? Todos os " "objetos seguintes e seus itens relacionados serão removidos:" -msgid "Change" -msgstr "Modificar" +msgid "View" +msgstr "View" msgid "Delete?" msgstr "Remover?" @@ -465,14 +491,14 @@ msgstr "Modelos na aplicação %(name)s" msgid "Add" msgstr "Adicionar" -msgid "You don't have permission to edit anything." -msgstr "Não tem permissão para modificar nada." +msgid "You don't have permission to view or edit anything." +msgstr "Não tem permissão para ver ou editar nada." msgid "Recent actions" -msgstr "" +msgstr "Ações recentes" msgid "My actions" -msgstr "" +msgstr "As minhas ações" msgid "None available" msgstr "Nenhum disponível" @@ -522,20 +548,8 @@ msgstr "Mostrar todos" msgid "Save" msgstr "Gravar" -msgid "Popup closing..." -msgstr "Fechando o popup..." - -#, python-format -msgid "Change selected %(model)s" -msgstr "Alterar %(model)s selecionado." - -#, python-format -msgid "Add another %(model)s" -msgstr "Adicionar outro %(model)s" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Remover %(model)s seleccionado" +msgid "Popup closing…" +msgstr "" msgid "Search" msgstr "Pesquisar" @@ -559,6 +573,24 @@ msgstr "Gravar e adicionar outro" msgid "Save and continue editing" msgstr "Gravar e continuar a editar" +msgid "Save and view" +msgstr "Gravar e ver" + +msgid "Close" +msgstr "Fechar" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Alterar %(model)s selecionado." + +#, python-format +msgid "Add another %(model)s" +msgstr "Adicionar outro %(model)s" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Remover %(model)s seleccionado" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Obrigado pela sua visita." @@ -673,6 +705,10 @@ msgstr "Selecionar %s" msgid "Select %s to change" msgstr "Selecione %s para modificar" +#, python-format +msgid "Select %s to view" +msgstr "Selecione %s para ver" + msgid "Date:" msgstr "Data:" diff --git a/django/contrib/admin/locale/pt/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/pt/LC_MESSAGES/djangojs.mo index 8829972bbc05..bc7ae616897a 100644 Binary files a/django/contrib/admin/locale/pt/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/pt/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/pt/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/pt/LC_MESSAGES/djangojs.po index ec907ff9a975..17379945a2fc 100644 --- a/django/contrib/admin/locale/pt/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/pt/LC_MESSAGES/djangojs.po @@ -2,7 +2,7 @@ # # Translators: # Jannis Leidel , 2011 -# Nuno Mariz , 2011-2012,2015 +# Nuno Mariz , 2011-2012,2015,2017 # Paulo Köch , 2011 # Raúl Pedro Fernandes Santos, 2014 msgid "" @@ -10,8 +10,8 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" -"Last-Translator: Jannis Leidel \n" +"PO-Revision-Date: 2017-11-30 23:49+0000\n" +"Last-Translator: Nuno Mariz \n" "Language-Team: Portuguese (http://www.transifex.com/django/django/language/" "pt/)\n" "MIME-Version: 1.0\n" @@ -152,68 +152,68 @@ msgid "Tomorrow" msgstr "Amanhã" msgid "January" -msgstr "" +msgstr "Janeiro" msgid "February" -msgstr "" +msgstr "Fevereiro" msgid "March" -msgstr "" +msgstr "Março" msgid "April" -msgstr "" +msgstr "Abril" msgid "May" -msgstr "" +msgstr "Maio" msgid "June" -msgstr "" +msgstr "Junho" msgid "July" -msgstr "" +msgstr "Julho" msgid "August" -msgstr "" +msgstr "Agosto" msgid "September" -msgstr "" +msgstr "Setembro" msgid "October" -msgstr "" +msgstr "Outubro" msgid "November" -msgstr "" +msgstr "Novembro" msgid "December" -msgstr "" +msgstr "Dezembro" msgctxt "one letter Sunday" msgid "S" -msgstr "" +msgstr "D" msgctxt "one letter Monday" msgid "M" -msgstr "" +msgstr "S" msgctxt "one letter Tuesday" msgid "T" -msgstr "" +msgstr "T" msgctxt "one letter Wednesday" msgid "W" -msgstr "" +msgstr "Q" msgctxt "one letter Thursday" msgid "T" -msgstr "" +msgstr "Q" msgctxt "one letter Friday" msgid "F" -msgstr "" +msgstr "S" msgctxt "one letter Saturday" msgid "S" -msgstr "" +msgstr "S" msgid "Show" msgstr "Mostrar" 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 79cf2c26eae8..d2644ce2a64a 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 99d6f9616288..ce5b5d9fbc2f 100644 --- a/django/contrib/admin/locale/pt_BR/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/pt_BR/LC_MESSAGES/django.po @@ -2,26 +2,32 @@ # # Translators: # Allisson Azevedo , 2014 +# Bruce de Sá , 2019 # bruno.devpod , 2014 -# Filipe Cifali , 2016 +# Filipe Cifali Stangler , 2016 # dudanogueira , 2012 # Elyézer Rezende , 2013 # Fábio C. Barrionuevo da Luz , 2015 -# Francisco Petry Rauber , 2016 +# Xico Petry , 2016 # Gladson , 2013 -# Guilherme Gondim, 2012-2013 +# Guilherme Ferreira , 2017 +# semente, 2012-2013 # Jannis Leidel , 2011 +# João Paulo Andrade , 2018 # Lucas Infante , 2015 +# Luiz Boaretto , 2017 +# Marcelo Moro Brondani , 2018 # Marco Rougeth , 2015 +# Otávio Reis , 2018 # Raysa Dutra, 2016 # Sergio Garcia , 2015 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-06-28 23:31+0000\n" -"Last-Translator: andrewsmedina \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-26 20:51+0000\n" +"Last-Translator: Bruce de Sá \n" "Language-Team: Portuguese (Brazil) (http://www.transifex.com/django/django/" "language/pt_BR/)\n" "MIME-Version: 1.0\n" @@ -99,6 +105,15 @@ msgstr "Adicionar outro(a) %(verbose_name)s" msgid "Remove" msgstr "Remover" +msgid "Addition" +msgstr "Adição" + +msgid "Change" +msgstr "Modificar" + +msgid "Deletion" +msgstr "Eliminação" + msgid "action time" msgstr "hora da ação" @@ -112,7 +127,7 @@ msgid "object id" msgstr "id do objeto" #. 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 do objeto" @@ -178,11 +193,11 @@ msgstr "" "mais de uma opção." #, 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 editar ele " -"novamente abaixo." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "O {name} \"{obj}\" foi adicionado com sucesso." + +msgid "You may edit it again below." +msgstr "Você pode editá-lo novamente abaixo." #, python-brace-format msgid "" @@ -192,10 +207,6 @@ msgstr "" "O {name} \"{obj}\" foi adicionado com sucesso. Você pode adicionar outro " "{name} abaixo." -#, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "O {name} \"{obj}\" foi adicionado com sucesso." - #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may edit it again below." @@ -203,6 +214,13 @@ msgstr "" "O {name} \"{obj}\" foi alterado com sucesso. Você pode modificar ele " "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 editar ele " +"novamente abaixo." + #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " @@ -230,8 +248,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\": excluído com sucesso." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Objeto %(name)s com chave primária %(key)r não existe. " +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s com o ID \"%(key)s\" não existe. Talvez tenha sido excluído?" #, python-format msgid "Add %s" @@ -241,6 +259,10 @@ msgstr "Adicionar %s" msgid "Change %s" msgstr "Modificar %s" +#, python-format +msgid "View %s" +msgstr "Visualizar %s" + msgid "Database error" msgstr "Erro no banco de dados" @@ -349,7 +371,7 @@ msgid "Change password" msgstr "Alterar senha" msgid "Please correct the error below." -msgstr "Por favor, corrija o erro abaixo." +msgstr "Por favor corrija o erro abaixo " msgid "Please correct the errors below." msgstr "Por favor, corrija os erros abaixo." @@ -460,8 +482,8 @@ msgstr "" "Tem certeza de que deseja apagar o %(objects_name)s selecionado? Todos os " "seguintes objetos e seus itens relacionados serão removidos:" -msgid "Change" -msgstr "Modificar" +msgid "View" +msgstr "Visualizar" msgid "Delete?" msgstr "Apagar?" @@ -480,8 +502,8 @@ msgstr "Modelos na aplicação %(name)s" msgid "Add" msgstr "Adicionar" -msgid "You don't have permission to edit anything." -msgstr "Você não tem permissão para edição." +msgid "You don't have permission to view or edit anything." +msgstr "Você não tem permissão para ver ou editar nada." msgid "Recent actions" msgstr "Ações recentes" @@ -537,20 +559,8 @@ msgstr "Mostrar tudo" msgid "Save" msgstr "Salvar" -msgid "Popup closing..." -msgstr "Fechando popup..." - -#, python-format -msgid "Change selected %(model)s" -msgstr "Alterar %(model)s selecionado" - -#, python-format -msgid "Add another %(model)s" -msgstr "Adicionar outro %(model)s" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Excluir %(model)s selecionado" +msgid "Popup closing…" +msgstr "Popup fechando…" msgid "Search" msgstr "Pesquisar" @@ -574,6 +584,24 @@ msgstr "Salvar e adicionar outro(a)" msgid "Save and continue editing" msgstr "Salvar e continuar editando" +msgid "Save and view" +msgstr "Salvar e visualizar" + +msgid "Close" +msgstr "Fechar" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Alterar %(model)s selecionado" + +#, python-format +msgid "Add another %(model)s" +msgstr "Adicionar outro %(model)s" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Excluir %(model)s selecionado" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Obrigado por visitar nosso Web site hoje." @@ -685,6 +713,10 @@ msgstr "Selecione %s" msgid "Select %s to change" msgstr "Selecione %s para modificar" +#, python-format +msgid "Select %s to view" +msgstr "Selecione %s para visualizar" + msgid "Date:" msgstr "Data:" 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 058403b93aa7..f499f4fe940d 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 81d0a652d037..a5a872bd9d52 100644 --- a/django/contrib/admin/locale/pt_BR/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/pt_BR/LC_MESSAGES/djangojs.po @@ -4,7 +4,7 @@ # Allisson Azevedo , 2014 # andrewsmedina , 2016 # Eduardo Cereto Carvalho, 2011 -# Guilherme Gondim, 2012 +# semente, 2012 # Jannis Leidel , 2011 # Lucas Infante , 2015 # Renata Barbosa Almeida , 2016 @@ -12,8 +12,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-06-28 23:30+0000\n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-23 18:54+0000\n" "Last-Translator: Tarsis Azevedo \n" "Language-Team: Portuguese (Brazil) (http://www.transifex.com/django/django/" "language/pt_BR/)\n" @@ -102,6 +102,21 @@ msgstr "" "Você selecionou uma ação, e você não fez alterações em campos individuais. " "Você provavelmente está procurando o botão Ir ao invés do botão Salvar." +msgid "Now" +msgstr "Agora" + +msgid "Midnight" +msgstr "Meia-noite" + +msgid "6 a.m." +msgstr "6 da manhã" + +msgid "Noon" +msgstr "Meio-dia" + +msgid "6 p.m." +msgstr "6 da tarde" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." @@ -114,27 +129,12 @@ msgid_plural "Note: You are %s hours behind server time." msgstr[0] "Nota: Você está %s hora atrás do tempo do servidor." msgstr[1] "Nota: Você está %s horas atrás do horário do servidor." -msgid "Now" -msgstr "Agora" - msgid "Choose a Time" msgstr "Escolha um horário" msgid "Choose a time" msgstr "Escolha uma hora" -msgid "Midnight" -msgstr "Meia-noite" - -msgid "6 a.m." -msgstr "6 da manhã" - -msgid "Noon" -msgstr "Meio-dia" - -msgid "6 p.m." -msgstr "6 da tarde" - msgid "Cancel" msgstr "Cancelar" diff --git a/django/contrib/admin/locale/ro/LC_MESSAGES/django.mo b/django/contrib/admin/locale/ro/LC_MESSAGES/django.mo index 938d70c565e2..de836e713341 100644 Binary files a/django/contrib/admin/locale/ro/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/ro/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/ro/LC_MESSAGES/django.po b/django/contrib/admin/locale/ro/LC_MESSAGES/django.po index 624da48e1c4c..a52959bc54ec 100644 --- a/django/contrib/admin/locale/ro/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/ro/LC_MESSAGES/django.po @@ -1,18 +1,19 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Daniel Ursache-Dogariu , 2011 +# Bogdan Mateescu, 2018-2019 +# Daniel Ursache-Dogariu, 2011 # Denis Darii , 2011,2014 # Ionel Cristian Mărieș , 2012 # Jannis Leidel , 2011 -# Razvan Stefanescu , 2015-2016 +# Razvan Stefanescu , 2015-2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 12:02+0000\n" +"Last-Translator: Bogdan Mateescu\n" "Language-Team: Romanian (http://www.transifex.com/django/django/language/" "ro/)\n" "MIME-Version: 1.0\n" @@ -68,10 +69,10 @@ msgid "This year" msgstr "Anul acesta" msgid "No date" -msgstr "" +msgstr "Fără dată" msgid "Has date" -msgstr "" +msgstr "Are o dată" #, python-format msgid "" @@ -91,6 +92,15 @@ msgstr "Adăugati încă un/o %(verbose_name)s" msgid "Remove" msgstr "Elimină" +msgid "Addition" +msgstr "Adăugare" + +msgid "Change" +msgstr "Schimbă" + +msgid "Deletion" +msgstr "Ștergere" + msgid "action time" msgstr "timp acțiune" @@ -104,7 +114,7 @@ msgid "object id" msgstr "id obiect" #. 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 obiect" @@ -137,7 +147,7 @@ msgstr "Obiect LogEntry" #, python-brace-format msgid "Added {name} \"{object}\"." -msgstr "" +msgstr "S-a adăugat {name} \"{object}\"." msgid "Added." msgstr "Adăugat." @@ -147,15 +157,15 @@ msgstr "și" #, python-brace-format msgid "Changed {fields} for {name} \"{object}\"." -msgstr "" +msgstr "S-au schimbat {fields} pentru {name} \"{object}\"." #, python-brace-format msgid "Changed {fields}." -msgstr "" +msgstr "S-au schimbat {fields}." #, python-brace-format msgid "Deleted {name} \"{object}\"." -msgstr "" +msgstr "S-a șters {name} \"{object}\"." msgid "No fields changed." msgstr "Niciun câmp modificat." @@ -170,34 +180,43 @@ msgstr "" "mult de unul." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" +msgid "The {name} \"{obj}\" was added successfully." +msgstr "{name} \"{obj}\" a fost adăugat cu succes." + +msgid "You may edit it again below." +msgstr "O poți edita din nou mai jos." #, python-brace-format msgid "" "The {name} \"{obj}\" was added successfully. You may add another {name} " "below." msgstr "" +"{name} \"{obj}\" a fost adăugat cu succes. Poți adăuga alt {name} mai jos." #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" +"{name} \"{obj}\" a fost modificat cu succes. Poți să îl editezi în " +"continuare mai jos." #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." +"The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" +"{name} \"{obj}\" a fost adăugat cu succes. Poți să îl editezi în continuare " +"mai jos." #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " "below." msgstr "" +"{name} \"{obj}\" a fost modificat cu succes. Poți adăuga alt {name} mai jos." #, python-brace-format msgid "The {name} \"{obj}\" was changed successfully." -msgstr "" +msgstr "{name} \"{obj}\" a fost schimbat cu succes." msgid "" "Items must be selected in order to perform actions on them. No items have " @@ -214,8 +233,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" eliminat(ă) cu succes." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Obiectul %(name)s ce are cheie primară %(key)r nu există." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s cu ID \"%(key)s\" inexistent. Posibil să fi fost șters?" #, python-format msgid "Add %s" @@ -225,6 +244,10 @@ msgstr "Adaugă %s" msgid "Change %s" msgstr "Schimbă %s" +#, python-format +msgid "View %s" +msgstr "Vizualizează %s" + msgid "Database error" msgstr "Eroare de bază de date" @@ -335,7 +358,7 @@ msgid "Change password" msgstr "Schimbă parola" msgid "Please correct the error below." -msgstr "Corectați erorile de mai jos" +msgstr "Corectați eroarea de mai jos." msgid "Please correct the errors below." msgstr "Corectați erorile de mai jos." @@ -355,7 +378,7 @@ msgid "Documentation" msgstr "Documentație" msgid "Log out" -msgstr "Deautentificare" +msgstr "Deconectează-te" #, python-format msgid "Add %(name)s" @@ -447,8 +470,8 @@ msgstr "" "Sigur doriţi să ștergeți %(objects_name)s conform selecției? Toate obiectele " "următoare alături de cele asociate lor vor fi șterse:" -msgid "Change" -msgstr "Schimbă" +msgid "View" +msgstr "Vizualizează" msgid "Delete?" msgstr "Elimină?" @@ -467,14 +490,14 @@ msgstr "Modele în aplicația %(name)s" msgid "Add" msgstr "Adaugă" -msgid "You don't have permission to edit anything." -msgstr "Nu nicio permisiune de editare." +msgid "You don't have permission to view or edit anything." +msgstr "Nu aveți permisiunea de a edita sau vizualiza nimic." msgid "Recent actions" -msgstr "" +msgstr "Acțiuni recente" msgid "My actions" -msgstr "" +msgstr "Acțiunile mele" msgid "None available" msgstr "Niciuna" @@ -524,21 +547,9 @@ msgstr "Arată totul" msgid "Save" msgstr "Salvează" -msgid "Popup closing..." +msgid "Popup closing…" msgstr "Fereastra se închide..." -#, python-format -msgid "Change selected %(model)s" -msgstr "Modifică %(model)s selectat" - -#, python-format -msgid "Add another %(model)s" -msgstr "Adaugă alt %(model)s" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Șterge %(model)s selectat" - msgid "Search" msgstr "Caută" @@ -562,6 +573,24 @@ msgstr "Salvați și mai adăugați" msgid "Save and continue editing" msgstr "Salvați și continuați editarea" +msgid "Save and view" +msgstr "Salvează și vizualizează" + +msgid "Close" +msgstr "Închide" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Modifică %(model)s selectat" + +#, python-format +msgid "Add another %(model)s" +msgstr "Adaugă alt %(model)s" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Șterge %(model)s selectat" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Mulţumiri pentru timpul petrecut astăzi pe sit." @@ -675,6 +704,10 @@ msgstr "Selectează %s" msgid "Select %s to change" msgstr "Selectează %s pentru schimbare" +#, python-format +msgid "Select %s to view" +msgstr "Selecteză %s pentru a vizualiza" + msgid "Date:" msgstr "Dată:" diff --git a/django/contrib/admin/locale/ro/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/ro/LC_MESSAGES/djangojs.mo index 03037a9afe6d..73da12644ec3 100644 Binary files a/django/contrib/admin/locale/ro/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/ro/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/ro/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/ro/LC_MESSAGES/djangojs.po index f907561f84dd..1ac469b2a802 100644 --- a/django/contrib/admin/locale/ro/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/ro/LC_MESSAGES/djangojs.po @@ -1,19 +1,20 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Daniel Ursache-Dogariu , 2011 +# Bogdan Mateescu, 2018 +# Daniel Ursache-Dogariu, 2011 # Denis Darii , 2011 # Ionel Cristian Mărieș , 2012 # Jannis Leidel , 2011 # Răzvan Ionescu , 2015 -# Razvan Stefanescu , 2016 +# Razvan Stefanescu , 2016-2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2018-02-27 12:32+0000\n" +"Last-Translator: Bogdan Mateescu\n" "Language-Team: Romanian (http://www.transifex.com/django/django/language/" "ro/)\n" "MIME-Version: 1.0\n" @@ -106,22 +107,34 @@ msgstr "" "Ați selectat o acţiune și nu ațţi făcut modificări în cîmpuri individuale. " "Probabil căutați butonul Go, în loc de Salvează." +msgid "Now" +msgstr "Acum" + +msgid "Midnight" +msgstr "Miezul nopții" + +msgid "6 a.m." +msgstr "6 a.m." + +msgid "Noon" +msgstr "Amiază" + +msgid "6 p.m." +msgstr "6 p.m." + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." -msgstr[0] "Notă: Sunteți cu %s ora înaintea orei serverului." +msgstr[0] "Notă: Sunteți cu %s oră înaintea orei serverului." msgstr[1] "Notă: Sunteți cu %s ore înaintea orei serverului." -msgstr[2] "Notă: Sunteți cu %s ore înaintea orei serverului." +msgstr[2] "Notă: Sunteți cu %s de ore înaintea orei serverului." #, javascript-format msgid "Note: You are %s hour behind server time." msgid_plural "Note: You are %s hours behind server time." msgstr[0] "Notă: Sunteți cu %s oră în urma orei serverului." msgstr[1] "Notă: Sunteți cu %s ore în urma orei serverului." -msgstr[2] "Notă: Sunteți cu %s ore în urma orei serverului." - -msgid "Now" -msgstr "Acum" +msgstr[2] "Notă: Sunteți cu %s de ore în urma orei serverului." msgid "Choose a Time" msgstr "Alege o oră" @@ -129,18 +142,6 @@ msgstr "Alege o oră" msgid "Choose a time" msgstr "Alege o oră" -msgid "Midnight" -msgstr "Miezul nopții" - -msgid "6 a.m." -msgstr "6 a.m." - -msgid "Noon" -msgstr "Amiază" - -msgid "6 p.m." -msgstr "6 p.m." - msgid "Cancel" msgstr "Anulează" @@ -157,68 +158,68 @@ msgid "Tomorrow" msgstr "Mâine" msgid "January" -msgstr "" +msgstr "Ianuarie" msgid "February" -msgstr "" +msgstr "Februarie" msgid "March" -msgstr "" +msgstr "Martie" msgid "April" -msgstr "" +msgstr "Aprilie" msgid "May" -msgstr "" +msgstr "Mai" msgid "June" -msgstr "" +msgstr "Iunie" msgid "July" -msgstr "" +msgstr "Iulie" msgid "August" -msgstr "" +msgstr "August" msgid "September" -msgstr "" +msgstr "Septembrie" msgid "October" -msgstr "" +msgstr "Octombrie" msgid "November" -msgstr "" +msgstr "Noiembrie" msgid "December" -msgstr "" +msgstr "Decembrie" msgctxt "one letter Sunday" msgid "S" -msgstr "" +msgstr "D" msgctxt "one letter Monday" msgid "M" -msgstr "" +msgstr "L" msgctxt "one letter Tuesday" msgid "T" -msgstr "" +msgstr "M" msgctxt "one letter Wednesday" msgid "W" -msgstr "" +msgstr "M" msgctxt "one letter Thursday" msgid "T" -msgstr "" +msgstr "J" msgctxt "one letter Friday" msgid "F" -msgstr "" +msgstr "V" msgctxt "one letter Saturday" msgid "S" -msgstr "" +msgstr "S" msgid "Show" msgstr "Arată" diff --git a/django/contrib/admin/locale/ru/LC_MESSAGES/django.mo b/django/contrib/admin/locale/ru/LC_MESSAGES/django.mo index 8c28226103a0..c11def1d49b9 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 da461243a771..e27b05c04d99 100644 --- a/django/contrib/admin/locale/ru/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/ru/LC_MESSAGES/django.po @@ -4,17 +4,19 @@ # Ivan Ivaschenko , 2013 # Denis Darii , 2011 # Dimmus , 2011 -# Eugene MechanisM , 2016 -# inoks , 2016 +# Eugene , 2016-2017 +# Sergey , 2016 # Jannis Leidel , 2011 # Алексей Борискин , 2012-2015 +# Дмитрий , 2019 +# Дмитрий Шатера , 2018 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-09-01 05:25+0000\n" -"Last-Translator: Eugene MechanisM \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-02-14 12:41+0000\n" +"Last-Translator: Дмитрий \n" "Language-Team: Russian (http://www.transifex.com/django/django/language/" "ru/)\n" "MIME-Version: 1.0\n" @@ -94,6 +96,15 @@ msgstr "Добавить еще один %(verbose_name)s" msgid "Remove" msgstr "Удалить" +msgid "Addition" +msgstr "Добавление" + +msgid "Change" +msgstr "Изменить" + +msgid "Deletion" +msgstr "Удаление" + msgid "action time" msgstr "время действия" @@ -107,7 +118,7 @@ msgid "object id" msgstr "идентификатор объекта" #. 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 "представление объекта" @@ -173,11 +184,11 @@ msgstr "" "значений." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" -"{name} \"{obj}\" был успешно добавлен. Вы можете отредактировать его еще раз " -"ниже." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "{name} \"{obj}\" было успешно добавлено." + +msgid "You may edit it again below." +msgstr "Вы можете снова изменить этот объект ниже." #, python-brace-format msgid "" @@ -187,10 +198,6 @@ msgstr "" "{name} \"{obj}\" был успешно добавлен. Вы можете добавить еще один {name} " "ниже." -#, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "{name} \"{obj}\" было успешно добавлено." - #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may edit it again below." @@ -198,6 +205,13 @@ 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} " @@ -223,8 +237,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" был успешно удален." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(name)s с первичным ключом %(key)r не существует." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s с ID \"%(key)s\" не существует. Возможно оно было удалено?" #, python-format msgid "Add %s" @@ -234,6 +248,10 @@ msgstr "Добавить %s" msgid "Change %s" msgstr "Изменить %s" +#, python-format +msgid "View %s" +msgstr "Просмотреть %s" + msgid "Database error" msgstr "Ошибка базы данных" @@ -346,7 +364,7 @@ msgid "Change password" msgstr "Изменить пароль" msgid "Please correct the error below." -msgstr "Пожалуйста, исправьте ошибки ниже." +msgstr "Пожалуйста, исправьте ошибку ниже." msgid "Please correct the errors below." msgstr "Пожалуйста, исправьте ошибки ниже." @@ -456,8 +474,8 @@ msgstr "" "Вы уверены, что хотите удалить %(objects_name)s? Все следующие объекты и " "связанные с ними элементы будут удалены:" -msgid "Change" -msgstr "Изменить" +msgid "View" +msgstr "Просмотреть" msgid "Delete?" msgstr "Удалить?" @@ -476,8 +494,8 @@ msgstr "Модели в приложении %(name)s" msgid "Add" msgstr "Добавить" -msgid "You don't have permission to edit anything." -msgstr "У вас недостаточно прав для редактирования." +msgid "You don't have permission to view or edit anything." +msgstr "У вас недостаточно полномочий для просмотра или изменения чего либо." msgid "Recent actions" msgstr "Последние действия" @@ -534,21 +552,9 @@ msgstr "Показать все" msgid "Save" msgstr "Сохранить" -msgid "Popup closing..." +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 "Search" msgstr "Найти" @@ -573,6 +579,24 @@ msgstr "Сохранить и добавить другой объект" msgid "Save and continue editing" 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\"" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Благодарим вас за время, проведенное на этом сайте." @@ -685,6 +709,10 @@ msgstr "Выберите %s" msgid "Select %s to change" msgstr "Выберите %s для изменения" +#, python-format +msgid "Select %s to view" +msgstr "Выберите %s для просмотра" + msgid "Date:" msgstr "Дата:" diff --git a/django/contrib/admin/locale/ru/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/ru/LC_MESSAGES/djangojs.mo index e85ef2407b9d..4a51b2f5f1d5 100644 Binary files a/django/contrib/admin/locale/ru/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/ru/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/ru/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/ru/LC_MESSAGES/djangojs.po index ce2f3ea64bf8..281cd5188987 100644 --- a/django/contrib/admin/locale/ru/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/ru/LC_MESSAGES/djangojs.po @@ -3,7 +3,8 @@ # Translators: # Denis Darii , 2011 # Dimmus , 2011 -# Eugene MechanisM , 2012 +# Eugene , 2012 +# Eugene , 2016 # Jannis Leidel , 2011 # Алексей Борискин , 2012,2014-2015 # Андрей Щуров , 2016 @@ -11,9 +12,9 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-06-23 13:50+0000\n" -"Last-Translator: Андрей Щуров \n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-23 18:54+0000\n" +"Last-Translator: Eugene \n" "Language-Team: Russian (http://www.transifex.com/django/django/language/" "ru/)\n" "MIME-Version: 1.0\n" @@ -108,6 +109,21 @@ msgstr "" "воспользоваться кнопкой \"Выполнить\", а не кнопкой \"Сохранить\". Если это " "так, то нажмите \"Отмена\", чтобы вернуться в интерфейс редактирования. " +msgid "Now" +msgstr "Сейчас" + +msgid "Midnight" +msgstr "Полночь" + +msgid "6 a.m." +msgstr "6 утра" + +msgid "Noon" +msgstr "Полдень" + +msgid "6 p.m." +msgstr "6 вечера" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." @@ -128,27 +144,12 @@ msgstr[2] "" msgstr[3] "" "Внимание: Ваше локальное время отстаёт от времени сервера на %s часов." -msgid "Now" -msgstr "Сейчас" - msgid "Choose a Time" msgstr "Выберите время" msgid "Choose a time" msgstr "Выберите время" -msgid "Midnight" -msgstr "Полночь" - -msgid "6 a.m." -msgstr "6 утра" - -msgid "Noon" -msgstr "Полдень" - -msgid "6 p.m." -msgstr "6 вечера" - msgid "Cancel" msgstr "Отмена" @@ -202,7 +203,7 @@ msgstr "Декабрь" msgctxt "one letter Sunday" msgid "S" -msgstr "С" +msgstr "В" msgctxt "one letter Monday" msgid "M" @@ -214,19 +215,19 @@ msgstr "В" msgctxt "one letter Wednesday" msgid "W" -msgstr "Ср" +msgstr "С" msgctxt "one letter Thursday" msgid "T" -msgstr "Чт" +msgstr "Ч" msgctxt "one letter Friday" msgid "F" -msgstr "Пт" +msgstr "П" msgctxt "one letter Saturday" msgid "S" -msgstr "Сб" +msgstr "С" msgid "Show" msgstr "Показать" diff --git a/django/contrib/admin/locale/sk/LC_MESSAGES/django.mo b/django/contrib/admin/locale/sk/LC_MESSAGES/django.mo index 703f312cc859..d7a5ba377792 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 bd93da78b9f6..3e9db2405e7d 100644 --- a/django/contrib/admin/locale/sk/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/sk/LC_MESSAGES/django.po @@ -3,21 +3,24 @@ # Translators: # Jannis Leidel , 2011 # Juraj Bubniak , 2012-2013 -# Marian Andre , 2013-2015 +# Marian Andre , 2013-2015,2017 # Martin Kosír, 2011 +# Martin Tóth , 2017 +# Zbynek Drlik , 2019 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-06-10 07:35+0000\n" +"Last-Translator: Zbynek Drlik \n" "Language-Team: Slovak (http://www.transifex.com/django/django/language/sk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: sk\n" -"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" +"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n " +">= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);\n" #, python-format msgid "Successfully deleted %(count)d %(items)s." @@ -65,10 +68,10 @@ msgid "This year" msgstr "Tento rok" msgid "No date" -msgstr "" +msgstr "Bez dátumu" msgid "Has date" -msgstr "" +msgstr "S dátumom" #, python-format msgid "" @@ -88,20 +91,29 @@ msgstr "Pridať ďalší %(verbose_name)s" msgid "Remove" msgstr "Odstrániť" +msgid "Addition" +msgstr "" + +msgid "Change" +msgstr "Zmeniť" + +msgid "Deletion" +msgstr "" + msgid "action time" msgstr "čas akcie" msgid "user" -msgstr "" +msgstr "používateľ" msgid "content type" -msgstr "" +msgstr "typ obsahu" msgid "object id" msgstr "identifikátor objektu" #. 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 "reprezentácia objektu" @@ -123,7 +135,7 @@ msgstr "Pridané \"%(object)s\"." #, python-format msgid "Changed \"%(object)s\" - %(changes)s" -msgstr "Zmenené \" %(object)s \" - %(changes)s " +msgstr "Zmenené \"%(object)s\" - %(changes)s " #, python-format msgid "Deleted \"%(object)s.\"" @@ -134,25 +146,25 @@ msgstr "Objekt LogEntry" #, python-brace-format msgid "Added {name} \"{object}\"." -msgstr "" +msgstr "Pridaný {name} \"{object}\"." msgid "Added." -msgstr "" +msgstr "Pridaný." msgid "and" msgstr "a" #, python-brace-format msgid "Changed {fields} for {name} \"{object}\"." -msgstr "" +msgstr "Zmenený {fields} pre {name} \"{object}\"." #, python-brace-format msgid "Changed {fields}." -msgstr "" +msgstr "Zmenené {fields}." #, python-brace-format msgid "Deleted {name} \"{object}\"." -msgstr "" +msgstr "Zmazaný {name} \"{object}\"." msgid "No fields changed." msgstr "Polia nezmenené." @@ -163,10 +175,14 @@ msgstr "Žiadne" msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "" +"Ak chcete vybrať viac ako jednu položku, podržte \"Control\", alebo \"Command" +"\" na počítači Mac." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "Objekt {name} \"{obj}\" bol úspešne pridaný." + +msgid "You may edit it again below." msgstr "" #, python-brace-format @@ -174,25 +190,34 @@ msgid "" "The {name} \"{obj}\" was added successfully. You may add another {name} " "below." msgstr "" +"Objekt {name} \"{obj}\" bol úspešne pridaný. Môžete pridať ďaľší {name} " +"nižšie." #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" +"Objekt {name} \"{obj}\" bol úspešne zmenený. Ďalšie zmeny môžete urobiť " +"nižšie." #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." +"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} " "below." msgstr "" +"Objekt {name} \"{obj}\" bol úspešne pridaný. Môžete pridať ďaľší {name} " +"nižšie." #, python-brace-format msgid "The {name} \"{obj}\" was changed successfully." -msgstr "" +msgstr "Objekt {name} \"{obj}\" bol úspešne pridaný." msgid "" "Items must be selected in order to perform actions on them. No items have " @@ -209,8 +234,9 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "Objekt %(name)s \"%(obj)s\" bol úspešne vymazaný." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Objekt %(name)s s primárnym kľúčom %(key)r neexistuje." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" +"Položka %(name)s s ID \"%(key)s\" neexistuje - pravdepodobne je vymazaná?" #, python-format msgid "Add %s" @@ -220,6 +246,10 @@ msgstr "Pridať %s" msgid "Change %s" msgstr "Zmeniť %s" +#, python-format +msgid "View %s" +msgstr "" + msgid "Database error" msgstr "Chyba databázy" @@ -229,6 +259,7 @@ msgid_plural "%(count)s %(name)s were changed successfully." msgstr[0] "%(count)s %(name)s bola úspešne zmenená." msgstr[1] "%(count)s %(name)s boli úspešne zmenené." msgstr[2] "%(count)s %(name)s bolo úspešne zmenených." +msgstr[3] "%(count)s %(name)s bolo úspešne zmenených." #, python-format msgid "%(total_count)s selected" @@ -236,6 +267,7 @@ msgid_plural "All %(total_count)s selected" msgstr[0] "%(total_count)s vybraná" msgstr[1] "Všetky %(total_count)s vybrané" msgstr[2] "Všetkých %(total_count)s vybraných" +msgstr[3] "Všetkých %(total_count)s vybraných" #, python-format msgid "0 of %(cnt)s selected" @@ -330,7 +362,7 @@ msgid "Change password" msgstr "Zmeniť heslo" msgid "Please correct the error below." -msgstr "Prosím, opravte chyby uvedené nižšie." +msgstr "" msgid "Please correct the errors below." msgstr "Prosím, opravte chyby uvedené nižšie." @@ -343,7 +375,7 @@ msgid "Welcome," msgstr "Vitajte," msgid "View site" -msgstr "" +msgstr "Pozrieť stránku" msgid "Documentation" msgstr "Dokumentácia" @@ -410,7 +442,7 @@ msgid "Yes, I'm sure" msgstr "Áno, som si istý" msgid "No, take me back" -msgstr "" +msgstr "Nie, chcem sa vrátiť" msgid "Delete multiple objects" msgstr "Zmazať viacero objektov" @@ -441,8 +473,8 @@ msgstr "" "Ste si isty, že chcete vymazať označené %(objects_name)s? Vymažú sa všetky " "nasledujúce objekty a ich súvisiace položky:" -msgid "Change" -msgstr "Zmeniť" +msgid "View" +msgstr "" msgid "Delete?" msgstr "Zmazať?" @@ -452,7 +484,7 @@ msgid " By %(filter_title)s " msgstr "Podľa %(filter_title)s " msgid "Summary" -msgstr "" +msgstr "Súhrn" #, python-format msgid "Models in the %(name)s application" @@ -461,14 +493,14 @@ msgstr "Modely v %(name)s aplikácii" msgid "Add" msgstr "Pridať" -msgid "You don't have permission to edit anything." -msgstr "Nemáte právo na vykonávanie zmien." +msgid "You don't have permission to view or edit anything." +msgstr "" msgid "Recent actions" -msgstr "" +msgstr "Posledné akcie" msgid "My actions" -msgstr "" +msgstr "Moje akcie" msgid "None available" msgstr "Nedostupné" @@ -490,6 +522,8 @@ 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 "" +"Ste prihlásený ako %(username)s, ale nemáte práva k tejto stránke. Chcete sa " +"prihlásiť do iného účtu?" msgid "Forgotten your password or username?" msgstr "Zabudli ste heslo alebo používateľské meno?" @@ -516,21 +550,9 @@ msgstr "Zobraziť všetky" msgid "Save" msgstr "Uložiť" -msgid "Popup closing..." -msgstr "" - -#, python-format -msgid "Change selected %(model)s" -msgstr "Zmeniť vybrané %(model)s" - -#, python-format -msgid "Add another %(model)s" +msgid "Popup closing…" msgstr "" -#, python-format -msgid "Delete selected %(model)s" -msgstr "Zmazať vybrané %(model)s" - msgid "Search" msgstr "Vyhľadávanie" @@ -540,6 +562,7 @@ msgid_plural "%(counter)s results" msgstr[0] "%(counter)s výsledok" msgstr[1] "%(counter)s výsledky" msgstr[2] "%(counter)s výsledkov" +msgstr[3] "%(counter)s výsledkov" #, python-format msgid "%(full_result_count)s total" @@ -554,6 +577,24 @@ msgstr "Uložiť a pridať ďalší" msgid "Save and continue editing" msgstr "Uložiť a pokračovať v úpravách" +msgid "Save and view" +msgstr "" + +msgid "Close" +msgstr "Zatvoriť" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Zmeniť vybrané %(model)s" + +#, python-format +msgid "Add another %(model)s" +msgstr "Pridať ďalší %(model)s" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Zmazať vybrané %(model)s" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Ďakujeme za čas strávený na našich stránkach." @@ -663,6 +704,10 @@ msgstr "Vybrať %s" msgid "Select %s to change" msgstr "Vybrať \"%s\" na úpravu" +#, python-format +msgid "Select %s to view" +msgstr "" + msgid "Date:" msgstr "Dátum:" diff --git a/django/contrib/admin/locale/sk/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/sk/LC_MESSAGES/djangojs.mo index 141b11355e35..798ad96eed64 100644 Binary files a/django/contrib/admin/locale/sk/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/sk/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/sk/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/sk/LC_MESSAGES/djangojs.po index ac8a9a38e21d..d703330d1fd2 100644 --- a/django/contrib/admin/locale/sk/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/sk/LC_MESSAGES/djangojs.po @@ -6,19 +6,21 @@ # Juraj Bubniak , 2012 # Marian Andre , 2012,2015 # Martin Kosír, 2011 +# Martin Tóth , 2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-23 18:54+0000\n" +"Last-Translator: Marian Andre \n" "Language-Team: Slovak (http://www.transifex.com/django/django/language/sk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: sk\n" -"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" +"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n " +">= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);\n" #, javascript-format msgid "Available %s" @@ -76,6 +78,7 @@ msgid_plural "%(sel)s of %(cnt)s selected" msgstr[0] "%(sel)s z %(cnt)s vybrané" msgstr[1] "%(sel)s z %(cnt)s vybrané" msgstr[2] "%(sel)s z %(cnt)s vybraných" +msgstr[3] "%(sel)s z %(cnt)s vybraných" msgid "" "You have unsaved changes on individual editable fields. If you run an " @@ -100,12 +103,28 @@ msgstr "" "Vybrali ste akciu, ale neurobili ste žiadne zmeny v jednotlivých poliach. " "Pravdepodobne ste chceli použiť tlačidlo vykonať namiesto uložiť." +msgid "Now" +msgstr "Teraz" + +msgid "Midnight" +msgstr "Polnoc" + +msgid "6 a.m." +msgstr "6:00" + +msgid "Noon" +msgstr "Poludnie" + +msgid "6 p.m." +msgstr "18:00" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." msgstr[0] "Poznámka: Ste %s hodinu pred časom servera." msgstr[1] "Poznámka: Ste %s hodiny pred časom servera." msgstr[2] "Poznámka: Ste %s hodín pred časom servera." +msgstr[3] "Poznámka: Ste %s hodín pred časom servera." #, javascript-format msgid "Note: You are %s hour behind server time." @@ -113,28 +132,14 @@ msgid_plural "Note: You are %s hours behind server time." msgstr[0] "Poznámka: Ste %s hodinu za časom servera." msgstr[1] "Poznámka: Ste %s hodiny za časom servera." msgstr[2] "Poznámka: Ste %s hodín za časom servera." - -msgid "Now" -msgstr "Teraz" +msgstr[3] "Poznámka: Ste %s hodín za časom servera." msgid "Choose a Time" -msgstr "" +msgstr "Vybrať Čas" msgid "Choose a time" msgstr "Vybrať čas" -msgid "Midnight" -msgstr "Polnoc" - -msgid "6 a.m." -msgstr "6:00" - -msgid "Noon" -msgstr "Poludnie" - -msgid "6 p.m." -msgstr "" - msgid "Cancel" msgstr "Zrušiť" @@ -142,7 +147,7 @@ msgid "Today" msgstr "Dnes" msgid "Choose a Date" -msgstr "" +msgstr "Vybrať Dátum" msgid "Yesterday" msgstr "Včera" @@ -151,68 +156,68 @@ msgid "Tomorrow" msgstr "Zajtra" msgid "January" -msgstr "" +msgstr "január" msgid "February" -msgstr "" +msgstr "február" msgid "March" -msgstr "" +msgstr "marec" msgid "April" -msgstr "" +msgstr "apríl" msgid "May" -msgstr "" +msgstr "máj" msgid "June" -msgstr "" +msgstr "jún" msgid "July" -msgstr "" +msgstr "júl" msgid "August" -msgstr "" +msgstr "august" msgid "September" -msgstr "" +msgstr "september" msgid "October" -msgstr "" +msgstr "október" msgid "November" -msgstr "" +msgstr "november" msgid "December" -msgstr "" +msgstr "december" msgctxt "one letter Sunday" msgid "S" -msgstr "" +msgstr "N" msgctxt "one letter Monday" msgid "M" -msgstr "" +msgstr "P" msgctxt "one letter Tuesday" msgid "T" -msgstr "" +msgstr "U" msgctxt "one letter Wednesday" msgid "W" -msgstr "" +msgstr "S" msgctxt "one letter Thursday" msgid "T" -msgstr "" +msgstr "Š" msgctxt "one letter Friday" msgid "F" -msgstr "" +msgstr "P" msgctxt "one letter Saturday" msgid "S" -msgstr "" +msgstr "S" msgid "Show" msgstr "Zobraziť" diff --git a/django/contrib/admin/locale/sl/LC_MESSAGES/django.mo b/django/contrib/admin/locale/sl/LC_MESSAGES/django.mo index fc6a8742fdac..0085a30fb2ca 100644 Binary files a/django/contrib/admin/locale/sl/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/sl/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/sl/LC_MESSAGES/django.po b/django/contrib/admin/locale/sl/LC_MESSAGES/django.po index e533b608ae0e..d45425701ad7 100644 --- a/django/contrib/admin/locale/sl/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/sl/LC_MESSAGES/django.po @@ -2,15 +2,16 @@ # # Translators: # Jannis Leidel , 2011 +# Primož Verdnik , 2017 # zejn , 2013,2016 # zejn , 2011-2013 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-09-27 19:24+0000\n" -"Last-Translator: zejn \n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-23 18:54+0000\n" +"Last-Translator: Primož Verdnik \n" "Language-Team: Slovenian (http://www.transifex.com/django/django/language/" "sl/)\n" "MIME-Version: 1.0\n" @@ -216,8 +217,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" je bil uspešno izbrisan." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Objekt %(name)s z glavnim ključem %(key)r ne obstaja." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s s ključem \"%(key)s\" ne obstaja. Morda je bil izbrisan?" #, python-format msgid "Add %s" @@ -243,8 +244,8 @@ msgid "%(total_count)s selected" msgid_plural "All %(total_count)s selected" msgstr[0] "%(total_count)s izbran" msgstr[1] "%(total_count)s izbrana" -msgstr[2] "%(total_count)s izbrani" -msgstr[3] "%(total_count)s izbranih" +msgstr[2] "Vsi %(total_count)s izbrani" +msgstr[3] "Vseh %(total_count)s izbranih" #, python-format msgid "0 of %(cnt)s selected" @@ -470,7 +471,7 @@ msgid "Add" msgstr "Dodaj" msgid "You don't have permission to edit anything." -msgstr "Nimate dovoljenja za urejanje." +msgstr "Nimate dovoljenja za urejanje česarkoli." msgid "Recent actions" msgstr "Nedavna dejanja" diff --git a/django/contrib/admin/locale/sl/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/sl/LC_MESSAGES/djangojs.mo index cbe0330f4244..255885ed28a0 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 e785c69f52ea..35ab1ce75654 100644 --- a/django/contrib/admin/locale/sl/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/sl/LC_MESSAGES/djangojs.po @@ -9,8 +9,8 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-09-27 19:33+0000\n" -"Last-Translator: zejn \n" +"PO-Revision-Date: 2017-09-23 18:54+0000\n" +"Last-Translator: Primož Verdnik \n" "Language-Team: Slovenian (http://www.transifex.com/django/django/language/" "sl/)\n" "MIME-Version: 1.0\n" diff --git a/django/contrib/admin/locale/sq/LC_MESSAGES/django.mo b/django/contrib/admin/locale/sq/LC_MESSAGES/django.mo index e8f49a731a66..fad9cd7833e0 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 fab1ee132e48..46d3678da3f6 100644 --- a/django/contrib/admin/locale/sq/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/sq/LC_MESSAGES/django.po @@ -1,15 +1,15 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Besnik , 2011 -# Besnik , 2015 +# Besnik , 2011,2015 +# Besnik , 2015,2018-2019 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 11:06+0000\n" +"Last-Translator: Besnik \n" "Language-Team: Albanian (http://www.transifex.com/django/django/language/" "sq/)\n" "MIME-Version: 1.0\n" @@ -24,14 +24,14 @@ msgstr "U fshinë me sukses %(count)d %(items)s." #, python-format msgid "Cannot delete %(name)s" -msgstr "S'mund të fshijë %(name)s" +msgstr "S’mund të fshijë %(name)s" msgid "Are you sure?" msgstr "Jeni i sigurt?" #, python-format msgid "Delete selected %(verbose_name_plural)s" -msgstr "Fshiji %(verbose_name_plural)s e përzgjdhur" +msgstr "Fshiji %(verbose_name_plural)s e përzgjedhur" msgid "Administration" msgstr "Administrim" @@ -64,10 +64,10 @@ msgid "This year" msgstr "Këtë vit" msgid "No date" -msgstr "" +msgstr "Pa datë" msgid "Has date" -msgstr "" +msgstr "Ka datë" #, python-format msgid "" @@ -75,7 +75,7 @@ msgid "" "that both fields may be case-sensitive." msgstr "" "Ju lutemi, jepni %(username)s dhe fjalëkalimin e saktë për një llogari " -"ekipi. Kini parasysh se që të dyja fushat mund të jenë të ndjeshme ndaj " +"ekipi. Kini parasysh se që të dy fushat mund të jenë të ndjeshme ndaj " "shkrimit me shkronja të mëdha ose të vogla." msgid "Action:" @@ -88,6 +88,15 @@ msgstr "Shtoni një tjetër %(verbose_name)s" msgid "Remove" msgstr "Hiqe" +msgid "Addition" +msgstr "Shtim" + +msgid "Change" +msgstr "Ndryshoje" + +msgid "Deletion" +msgstr "Fshirje" + msgid "action time" msgstr "kohë veprimi" @@ -101,9 +110,9 @@ msgid "object id" msgstr "id objekti" #. 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 "" +msgstr "paraqitje objekti" msgid "action flag" msgstr "shenjë veprimi" @@ -134,28 +143,28 @@ msgstr "Objekt LogEntry" #, python-brace-format msgid "Added {name} \"{object}\"." -msgstr "" +msgstr "U shtua {name} \"{object}\"." msgid "Added." msgstr "U shtua." msgid "and" -msgstr " dhe " +msgstr "dhe " #, python-brace-format msgid "Changed {fields} for {name} \"{object}\"." -msgstr "" +msgstr "U ndryshua {fields} për {name} \"{object}\"." #, python-brace-format msgid "Changed {fields}." -msgstr "" +msgstr "U ndryshuan {fields}." #, python-brace-format msgid "Deleted {name} \"{object}\"." -msgstr "" +msgstr "U fshi {name} \"{object}\"." msgid "No fields changed." -msgstr "Nuk u ndryshuan fusha." +msgstr "S’u ndryshua ndonjë fushë." msgid "None" msgstr "Asnjë" @@ -163,56 +172,63 @@ msgstr "Asnjë" msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "" -"Për të përzgjedhur më shumë se një, mbani të shtypur \"Control\", ose " -"\"Command\" në Mac, ." +"Për të përzgjedhur më shumë se një, në Mac mbani të shtypur \"Control\", ose " +"\"Command\"." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" +msgid "The {name} \"{obj}\" was added successfully." +msgstr "{name} \"{obj}\" u shtua me sukses." + +msgid "You may edit it again below." +msgstr "Mund ta ripërpunoni më poshtë." #, python-brace-format msgid "" "The {name} \"{obj}\" was added successfully. You may add another {name} " "below." msgstr "" +"{name} \"{obj}\" u ndryshua me sukses. Mund të shtoni një tjetër {name} më " +"poshtë." #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "" +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." +msgstr "{name} \"{obj}\" u ndryshua me sukses. Mund të ripërpunoni më poshtë." #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." -msgstr "" +"The {name} \"{obj}\" was added successfully. You may edit it again below." +msgstr "{name} \"{obj}\" u ndryshua me sukses. Mund ta ripërpunoni më poshtë." #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " "below." msgstr "" +"{name} \"{obj}\" u ndryshua me sukses. Mund të shtoni një tjetër {name} më " +"poshtë." #, python-brace-format msgid "The {name} \"{obj}\" was changed successfully." -msgstr "" +msgstr "{name} \"{obj}\" u ndryshua me sukses." msgid "" "Items must be selected in order to perform actions on them. No items have " "been changed." msgstr "" -"Duhen përzgjedhur objekte që të kryhen veprime mbi ta. Nuk u ndryshua ndonjë " +"Duhen përzgjedhur objekte që të kryhen veprime mbi ta. S’u ndryshua ndonjë " "objekt." msgid "No action selected." -msgstr "Pa përzgjedhje veprimi." +msgstr "S’u përzgjodh ndonjë veprim." #, python-format msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" u fshi me sukses." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Objekti %(name)s me kyç parësor %(key)r nuk ekziston." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s me ID \"%(key)s\" s’ekziston. Mos qe fshirë?" #, python-format msgid "Add %s" @@ -222,8 +238,12 @@ msgstr "Shtoni %s" msgid "Change %s" msgstr "Ndrysho %s" +#, python-format +msgid "View %s" +msgstr "Shiheni %s" + msgid "Database error" -msgstr "Gabimi baze të dhënash" +msgstr "Gabim baze të dhënash" #, python-format msgid "%(count)s %(name)s was changed successfully." @@ -234,7 +254,7 @@ msgstr[1] "%(count)s %(name)s u ndryshuan me sukses." #, python-format msgid "%(total_count)s selected" msgid_plural "All %(total_count)s selected" -msgstr[0] "%(total_count)s të përzgjedhur" +msgstr[0] "%(total_count)s i përzgjedhur" msgstr[1] "Krejt %(total_count)s të përzgjedhurat" #, python-format @@ -260,13 +280,13 @@ msgstr "" "vijuese të mbrojtura që kanë lidhje me ta: %(related_objects)s" msgid "Django site admin" -msgstr "Përgjegjësi i site-it Django" +msgstr "Përgjegjës sajti Django" msgid "Django administration" msgstr "Administrim i Django-s" msgid "Site administration" -msgstr "Administrim site-i" +msgstr "Administrim sajti" msgid "Log in" msgstr "Hyni" @@ -276,10 +296,10 @@ msgid "%(app)s administration" msgstr "Administrim %(app)s" msgid "Page not found" -msgstr "Nuk u gjet faqe" +msgstr "S’u gjet faqe" msgid "We're sorry, but the requested page could not be found." -msgstr "Na ndjeni, por faqja e kërkuar nuk gjendet dot." +msgstr "Na ndjeni, por faqja e kërkuar s’gjendet dot." msgid "Home" msgstr "Hyrje" @@ -297,11 +317,11 @@ 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 "" -"Pati një gabim. Iu është njoftuar përgjegjësve të site-it përmes email-it " -"dhe do të duhej të ndreqej shpejt. Faleminderit për durimin." +"Pati një gabim. Iu është njoftuar përgjegjësve të sajtit përmes email-i dhe " +"do të duhej të ndreqej shpejt. Faleminderit për durimin." msgid "Run the selected action" -msgstr "Xhironi veprimin e përzgjedhur" +msgstr "Xhiro veprimin e përzgjedhur" msgid "Go" msgstr "Shko tek" @@ -330,7 +350,7 @@ msgid "Change password" msgstr "Ndryshoni fjalëkalimin" msgid "Please correct the error below." -msgstr "Ju lutemi, ndreqini gabimet e mëposhtme." +msgstr "Ju lutemi, ndreqni gabimin më poshtë." msgid "Please correct the errors below." msgstr "Ju lutemi, ndreqni gabimet më poshtë." @@ -360,7 +380,7 @@ msgid "History" msgstr "Historik" msgid "View on site" -msgstr "Shiheni në site" +msgstr "Shiheni në sajt" msgid "Filter" msgstr "Filtër" @@ -373,7 +393,7 @@ msgid "Sorting priority: %(priority_number)s" msgstr "Përparësi renditjesh: %(priority_number)s" msgid "Toggle sorting" -msgstr "Këmbe renditjen" +msgstr "Shfaq/fshih renditjen" msgid "Delete" msgstr "Fshije" @@ -394,7 +414,7 @@ msgid "" "following protected related objects:" msgstr "" "Fshirja e %(object_name)s '%(escaped_object)s' do të kërkonte fshirjen e " -"objekteve vijues, të mbrojtur, të lidhur me të:" +"objekteve të mbrojtur vijues, të lidhur me të:" #, python-format msgid "" @@ -432,44 +452,44 @@ msgid "" "protected related objects:" msgstr "" "Fshirja e %(objects_name)s të përzgjedhur do të kërkonte fshirjen e " -"objekteve vijues, të mbrojtur, të lidhur me të:" +"objekteve të mbrojtur vijues, të lidhur me të:" #, python-format 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 "" -"Jeni i sigurt se doni të fshihen e %(objects_name)s përzgjedhur? Krejt " +"Jeni i sigurt se doni të fshihen %(objects_name)s e përzgjedhur? Krejt " "objektet vijues dhe gjëra të lidhura me ta do të fshihen:" -msgid "Change" -msgstr "Ndryshoje" +msgid "View" +msgstr "Shiheni" msgid "Delete?" msgstr "Të fshihet?" #, python-format msgid " By %(filter_title)s " -msgstr "Nga %(filter_title)s " +msgstr " Nga %(filter_title)s " msgid "Summary" msgstr "Përmbledhje" #, python-format msgid "Models in the %(name)s application" -msgstr "Modele te zbatimi %(name)s" +msgstr "Modele te aplikacioni %(name)s" msgid "Add" msgstr "Shtoni" -msgid "You don't have permission to edit anything." -msgstr "Nuk keni leje për të përpunuar ndonjë gjë." +msgid "You don't have permission to view or edit anything." +msgstr "S’keni leje të shihni apo përpunoni gjë." msgid "Recent actions" -msgstr "" +msgstr "Veprime së fundi" msgid "My actions" -msgstr "" +msgstr "Veprimet e mia" msgid "None available" msgstr "Asnjë i passhëm" @@ -482,8 +502,8 @@ msgid "" "database tables have been created, and make sure the database is readable by " "the appropriate user." msgstr "" -"Ka diçka që nuk shkon me instalimin e bazës suaj të të dhënave. Sigurohuni " -"që janë krijuar tabelat e duhura të bazës së të dhënave, dhe që baza e të " +"Ka diçka që s’shkon me instalimin e bazës suaj të të dhënave. Sigurohuni që " +"janë krijuar tabelat e duhura të bazës së të dhënave, dhe që baza e të " "dhënave është e lexueshme nga përdoruesi i duhur." #, python-format @@ -510,8 +530,8 @@ msgid "" "This object doesn't have a change history. It probably wasn't added via this " "admin site." msgstr "" -"Ky objekt nuk ka historik ndryshimesh. Ndoshta nuk qe shtuar përmes këtij " -"site-i administrimi." +"Ky objekt s’ka historik ndryshimesh. Ndoshta s’qe shtuar përmes këtij sajti " +"administrimi." msgid "Show all" msgstr "Shfaqi krejt" @@ -519,20 +539,8 @@ msgstr "Shfaqi krejt" msgid "Save" msgstr "Ruaje" -msgid "Popup closing..." -msgstr "Flluska po mbyllet..." - -#, python-format -msgid "Change selected %(model)s" -msgstr "Nryshoni %(model)s e përzgjedhur" - -#, python-format -msgid "Add another %(model)s" -msgstr "Shtoni një %(model)s tjetër" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Fshije %(model)s e përzgjedhur" +msgid "Popup closing…" +msgstr "Mbyllje flluske…" msgid "Search" msgstr "Kërko" @@ -556,8 +564,26 @@ msgstr "Ruajeni dhe shtoni një tjetër" msgid "Save and continue editing" msgstr "Ruajeni dhe vazhdoni përpunimin" +msgid "Save and view" +msgstr "Ruajeni dhe shiheni" + +msgid "Close" +msgstr "Mbylle" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Ndryshoni %(model)s e përzgjedhur" + +#, python-format +msgid "Add another %(model)s" +msgstr "Shtoni një %(model)s tjetër" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Fshije %(model)s e përzgjedhur" + msgid "Thanks for spending some quality time with the Web site today." -msgstr "Faleminderit që shpenzoni pak kohë të çmuar me site-in Web sot." +msgstr "Faleminderit që shpenzoni sot pak kohë të çmuar me sajtin Web." msgid "Log in again" msgstr "Hyni sërish" @@ -572,9 +598,9 @@ 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 "" -"Ju lutem, jepni fjalëkalimin tuaj të vjetër, për hir të sigurisë, dhe mandej " -"jepni dy herë fjalëkalimin tuaj të ri, që kështu të mund të verifikojmë se e " -"shtypët saktë." +"Ju lutemi, jepni fjalëkalimin tuaj të vjetër, për hir të sigurisë, dhe " +"mandej jepni dy herë fjalëkalimin tuaj të ri, që kështu të mund të " +"verifikojmë se e shtypët saktë." msgid "Change my password" msgstr "Ndrysho fjalëkalimin tim" @@ -584,7 +610,7 @@ msgstr "Ricaktim fjalëkalimi" msgid "Your password has been set. You may go ahead and log in now." msgstr "" -"Fjakalimi juaj u caktua. Mund të vazhdoni më tej dhe të bëni hyrjen tani." +"Fjalëkalimi juaj u caktua. Mund të vazhdoni më tej dhe të bëni hyrjen tani." msgid "Password reset confirmation" msgstr "Ripohim ricaktimi fjalëkalimi" @@ -593,8 +619,8 @@ msgid "" "Please enter your new password twice so we can verify you typed it in " "correctly." msgstr "" -"Ju lutem, jepeni fjalëkalimin tuaj dy herë, që kështu të mund të verifikojmë " -"që e shtypët saktë." +"Ju lutemi, jepeni fjalëkalimin tuaj dy herë, që kështu të mund të " +"verifikojmë që e shtypët saktë." msgid "New password:" msgstr "Fjalëkalim i ri:" @@ -607,22 +633,22 @@ msgid "" "used. Please request a new password reset." msgstr "" "Lidhja për ricaktimin e fjalëkalimit qe e pavlefshme, ndoshta ngaqë është " -"përdorur tashmë një herë. Ju lutem, kërkoni një ricaktim të ri fjalëkalimi." +"përdorur tashmë një herë. Ju lutemi, kërkoni një ricaktim të ri fjalëkalimi." 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 "" "Ju kemi dërguar me email udhëzime për caktimin e fjalëkalimit tuaj, nëse ka " -"një llogari me email-in që dhatë. Do të duhej t'ju vinin pas pak." +"një llogari me email-in që dhatë. Do të duhej t’ju vinin pas pak." 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 "" "Nëse nuk merrni një email, ju lutemi, sigurohuni që keni dhënë adresën e " -"saktë me të cilën u regjistruat, dhe kontrolloni dosjen tuaj të mesazheve " -"hedhurinë." +"saktë me të cilën u regjistruat, dhe kontrolloni dosjen tuaj të mesazheve të " +"padëshiruara." #, python-format msgid "" @@ -633,13 +659,13 @@ msgstr "" "si përdorues te %(site_name)s." msgid "Please go to the following page and choose a new password:" -msgstr "Ju lutem, shkoni te faqja vijuese dhe zgjidhni një fjalëkalim të ri:" +msgstr "Ju lutemi, shkoni te faqja vijuese dhe zgjidhni një fjalëkalim të ri:" msgid "Your username, in case you've forgotten:" msgstr "Emri juaj i përdoruesit, në rast se e keni harruar:" msgid "Thanks for using our site!" -msgstr "Faleminderit që përdorni site-in tonë!" +msgstr "Faleminderit që përdorni sajtin tonë!" #, python-format msgid "The %(site_name)s team" @@ -649,7 +675,7 @@ msgid "" "Forgotten your password? Enter your email address below, and we'll email " "instructions for setting a new one." msgstr "" -"Harruat fjalëkalimin tuaj? Jepni më poshtë adresën tuaj email, dhe do t'ju " +"Harruat fjalëkalimin tuaj? Jepni më poshtë adresën tuaj email, dhe do t’ju " "dërgojmë udhëzimet për të caktuar një të ri." msgid "Email address:" @@ -669,6 +695,10 @@ msgstr "Përzgjidhni %s" msgid "Select %s to change" msgstr "Përzgjidhni %s për ta ndryshuar" +#, python-format +msgid "Select %s to view" +msgstr "Përzgjidhni %s për parje" + msgid "Date:" msgstr "Datë:" diff --git a/django/contrib/admin/locale/sq/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/sq/LC_MESSAGES/djangojs.mo index 1c394a7b4545..7b4668bb6a29 100644 Binary files a/django/contrib/admin/locale/sq/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/sq/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/sq/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/sq/LC_MESSAGES/djangojs.po index 14d1182de0e5..163c24117cad 100644 --- a/django/contrib/admin/locale/sq/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/sq/LC_MESSAGES/djangojs.po @@ -1,15 +1,15 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Besnik , 2011-2012 -# Besnik , 2015 +# Besnik , 2011-2012,2015 +# Besnik , 2015,2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-11-29 22:17+0000\n" +"Last-Translator: Besnik \n" "Language-Team: Albanian (http://www.transifex.com/django/django/language/" "sq/)\n" "MIME-Version: 1.0\n" @@ -101,6 +101,21 @@ msgstr "" "individuale. Ndoshta po kërkonit për butonin Shko, në vend se për butonin " "Ruaje." +msgid "Now" +msgstr "Tani" + +msgid "Midnight" +msgstr "Mesnatë" + +msgid "6 a.m." +msgstr "6 a.m." + +msgid "Noon" +msgstr "Mesditë" + +msgid "6 p.m." +msgstr "6 p.m." + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." @@ -113,27 +128,12 @@ msgid_plural "Note: You are %s hours behind server time." msgstr[0] "Shënim: Jeni %s orë pas kohës së shërbyesit." msgstr[1] "Shënim: Jeni %s orë pas kohës së shërbyesit." -msgid "Now" -msgstr "Tani" - msgid "Choose a Time" msgstr "Zgjidhni një Kohë" msgid "Choose a time" msgstr "Zgjidhni një kohë" -msgid "Midnight" -msgstr "Mesnatë" - -msgid "6 a.m." -msgstr "6 a.m." - -msgid "Noon" -msgstr "Mesditë" - -msgid "6 p.m." -msgstr "6 p.m." - msgid "Cancel" msgstr "Anuloje" @@ -150,68 +150,68 @@ msgid "Tomorrow" msgstr "Nesër" msgid "January" -msgstr "" +msgstr "Janar" msgid "February" -msgstr "" +msgstr "Shkurt" msgid "March" -msgstr "" +msgstr "Mars" msgid "April" -msgstr "" +msgstr "Prill" msgid "May" -msgstr "" +msgstr "Maj" msgid "June" -msgstr "" +msgstr "Qershor" msgid "July" -msgstr "" +msgstr "Korrik" msgid "August" -msgstr "" +msgstr "Gusht" msgid "September" -msgstr "" +msgstr "Shtator" msgid "October" -msgstr "" +msgstr "Tetor" msgid "November" -msgstr "" +msgstr "Nëntor" msgid "December" -msgstr "" +msgstr "Dhjetor" msgctxt "one letter Sunday" msgid "S" -msgstr "" +msgstr "D" msgctxt "one letter Monday" msgid "M" -msgstr "" +msgstr "H" msgctxt "one letter Tuesday" msgid "T" -msgstr "" +msgstr "M" msgctxt "one letter Wednesday" msgid "W" -msgstr "" +msgstr "M" msgctxt "one letter Thursday" msgid "T" -msgstr "" +msgstr "E" msgctxt "one letter Friday" msgid "F" -msgstr "" +msgstr "P" msgctxt "one letter Saturday" msgid "S" -msgstr "" +msgstr "S" msgid "Show" msgstr "Shfaqe" diff --git a/django/contrib/admin/locale/sr/LC_MESSAGES/django.mo b/django/contrib/admin/locale/sr/LC_MESSAGES/django.mo index 8bb143049dcb..16d4e9db2944 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 0f3f4a18f5be..ad5c35dc15f6 100644 --- a/django/contrib/admin/locale/sr/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/sr/LC_MESSAGES/django.po @@ -1,15 +1,17 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Branko Kokanovic , 2018 +# Igor Jerosimić, 2019 # Jannis Leidel , 2011 # Janos Guljas , 2011-2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-06-27 19:30+0000\n" +"Last-Translator: Igor Jerosimić\n" "Language-Team: Serbian (http://www.transifex.com/django/django/language/" "sr/)\n" "MIME-Version: 1.0\n" @@ -35,7 +37,7 @@ msgid "Delete selected %(verbose_name_plural)s" msgstr "Бриши означене објекте класе %(verbose_name_plural)s" msgid "Administration" -msgstr "" +msgstr "Администрација" msgid "All" msgstr "Сви" @@ -65,16 +67,18 @@ msgid "This year" msgstr "Ова година" msgid "No date" -msgstr "" +msgstr "Нема датума" msgid "Has date" -msgstr "" +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 и лозинку. Обратите пажњу да мала и " +"велика слова представљају различите карактере." msgid "Action:" msgstr "Радња:" @@ -86,20 +90,29 @@ msgstr "Додај још један објекат класе %(verbose_name)s. msgid "Remove" msgstr "Обриши" +msgid "Addition" +msgstr "Додавања" + +msgid "Change" +msgstr "Измени" + +msgid "Deletion" +msgstr "Брисања" + msgid "action time" msgstr "време радње" msgid "user" -msgstr "" +msgstr "корисник" msgid "content type" -msgstr "" +msgstr "тип садржаја" 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 "опис објекта" @@ -132,25 +145,25 @@ msgstr "Објекат уноса лога" #, python-brace-format msgid "Added {name} \"{object}\"." -msgstr "" +msgstr "Додат објекат {name} \"{object}\"." msgid "Added." -msgstr "" +msgstr "Додато." msgid "and" msgstr "и" #, python-brace-format msgid "Changed {fields} for {name} \"{object}\"." -msgstr "" +msgstr "Измењена поља {fields} за {name} \"{object}\"." #, python-brace-format msgid "Changed {fields}." -msgstr "" +msgstr "Измењена поља {fields}." #, python-brace-format msgid "Deleted {name} \"{object}\"." -msgstr "" +msgstr "Обрисан објекат {name} \"{object}\"." msgid "No fields changed." msgstr "Без измена у пољима." @@ -161,36 +174,45 @@ msgstr "Ништа" msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "" +"Држите „Control“, или „Command“ на Mac-у да бисте обележили више од једне " +"ставке." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" +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 add another {name} " "below." msgstr "" +"Објекат {name} \"{obj}\" успешно додат. Можете додати још један {name} испод." #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." +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 "" +"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} " "below." msgstr "" +"Објекат {name} \"{obj}\" успешно измењен. Можете додати још један {name} " +"испод." #, python-brace-format msgid "The {name} \"{obj}\" was changed successfully." -msgstr "" +msgstr "Објекат {name} \"{obj}\" успешно измењен." msgid "" "Items must be selected in order to perform actions on them. No items have " @@ -207,8 +229,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "Објекат „%(obj)s“ класе %(name)s успешно је обрисан." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Објекат класе %(name)s са примарним кључем %(key)r не постоји." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s са идентификацијом \"%(key)s\" не постоји. Можда је избрисан?" #, python-format msgid "Add %s" @@ -218,6 +240,10 @@ msgstr "Додај објекат класе %s" msgid "Change %s" msgstr "Измени објекат класе %s" +#, python-format +msgid "View %s" +msgstr "Преглед %s" + msgid "Database error" msgstr "Грешка у бази података" @@ -247,13 +273,15 @@ msgstr "Историјат измена: %s" #. suitable to be an item in a list. #, python-format msgid "%(class_name)s %(instance)s" -msgstr "" +msgstr "%(class_name)s %(instance)s" #, python-format msgid "" "Deleting %(class_name)s %(instance)s would require deleting the following " "protected related objects: %(related_objects)s" msgstr "" +"Да би избрисали %(class_name)s%(instance)s потребно је брисати и следеће " +"заштићене повезане објекте: %(related_objects)s" msgid "Django site admin" msgstr "Django администрација сајта" @@ -269,7 +297,7 @@ msgstr "Пријава" #, python-format msgid "%(app)s administration" -msgstr "" +msgstr "%(app)s администрација" msgid "Page not found" msgstr "Страница није пронађена" @@ -293,6 +321,8 @@ 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 "" +"Десила се грешка. Пријављена је администраторима сајта преко е-поште и " +"требало би да ускоро буде исправљена. Хвала Вам на стрпљењу." msgid "Run the selected action" msgstr "Покрени одабрану радњу" @@ -324,10 +354,10 @@ msgid "Change password" msgstr "Промена лозинке" msgid "Please correct the error below." -msgstr "Исправите наведене грешке." +msgstr "Молимо исправите грешку испод." msgid "Please correct the errors below." -msgstr "" +msgstr "Исправите грешке испод." #, python-format msgid "Enter a new password for the user %(username)s." @@ -337,7 +367,7 @@ msgid "Welcome," msgstr "Добродошли," msgid "View site" -msgstr "" +msgstr "Погледај сајт" msgid "Documentation" msgstr "Документација" @@ -398,13 +428,13 @@ msgstr "" "Следећи објекти који су у вези са овим објектом ће такође бити обрисани:" msgid "Objects" -msgstr "" +msgstr "Објекти" msgid "Yes, I'm sure" msgstr "Да, сигуран сам" msgid "No, take me back" -msgstr "" +msgstr "Не, хоћу назад" msgid "Delete multiple objects" msgstr "Брисање више објеката" @@ -435,8 +465,8 @@ msgstr "" "Да ли сте сигурни да желите да избришете изабране %(objects_name)s? Сви " "следећи објекти и објекти са њима повезани ће бити избрисани:" -msgid "Change" -msgstr "Измени" +msgid "View" +msgstr "Преглед" msgid "Delete?" msgstr "Брисање?" @@ -446,23 +476,23 @@ msgid " By %(filter_title)s " msgstr " %(filter_title)s " msgid "Summary" -msgstr "" +msgstr "Сумарно" #, python-format msgid "Models in the %(name)s application" -msgstr "" +msgstr "Модели у апликацији %(name)s" msgid "Add" msgstr "Додај" -msgid "You don't have permission to edit anything." -msgstr "Немате дозволе да уносите било какве измене." +msgid "You don't have permission to view or edit anything." +msgstr "Немате дозвола да погледате или измените ништа." msgid "Recent actions" -msgstr "" +msgstr "Скорашње акције" msgid "My actions" -msgstr "" +msgstr "Моје акције" msgid "None available" msgstr "Нема података" @@ -483,6 +513,8 @@ 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, али немате овлашћења да приступите овој " +"страни. Да ли желите да се пријавите под неким другим налогом?" msgid "Forgotten your password or username?" msgstr "Заборавили сте лозинку или корисничко име?" @@ -509,20 +541,8 @@ msgstr "Прикажи све" msgid "Save" msgstr "Сачувај" -msgid "Popup closing..." -msgstr "" - -#, python-format -msgid "Change selected %(model)s" -msgstr "" - -#, python-format -msgid "Add another %(model)s" -msgstr "" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "" +msgid "Popup closing…" +msgstr "Попуп се затвара..." msgid "Search" msgstr "Претрага" @@ -547,6 +567,24 @@ msgstr "Сачувај и додај следећи" msgid "Save and continue editing" 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" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Хвала што сте данас провели време на овом сајту." @@ -602,17 +640,23 @@ 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 "" +"Послали смо Вам упутства за постављање лозинке, уколико налог са овом " +"адресом постоји. Требало би да их добијете ускоро." 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 "" +"Ако не добијете поруку, проверите да ли сте унели добру адресу са којом сте " +"се и регистровали и проверите спам фасциклу." #, python-format msgid "" "You're receiving this email because you requested a password reset for your " "user account at %(site_name)s." msgstr "" +"Примате ову поруку зато што сте затражили ресетовање лозинке за кориснички " +"налог на сајту %(site_name)s." msgid "Please go to the following page and choose a new password:" msgstr "Идите на следећу страницу и поставите нову лозинку." @@ -631,9 +675,11 @@ msgid "" "Forgotten your password? Enter your email address below, and we'll email " "instructions for setting a new one." msgstr "" +"Заборавили сте лозинку? Унесите адресу е-поште испод и послаћемо Вам на њу " +"упутства за постављање нове лозинке." msgid "Email address:" -msgstr "" +msgstr "Адреса е-поште:" msgid "Reset my password" msgstr "Ресетуј моју лозинку" @@ -649,6 +695,10 @@ msgstr "Одабери објекат класе %s" msgid "Select %s to change" msgstr "Одабери објекат класе %s за измену" +#, python-format +msgid "Select %s to view" +msgstr "Одабери %s за преглед" + msgid "Date:" msgstr "Датум:" @@ -659,7 +709,7 @@ msgid "Lookup" msgstr "Претражи" msgid "Currently:" -msgstr "" +msgstr "Тренутно:" msgid "Change:" -msgstr "" +msgstr "Измена:" diff --git a/django/contrib/admin/locale/sr/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/sr/LC_MESSAGES/djangojs.mo index 59055f09a3c8..3c6ee7f7a7b8 100644 Binary files a/django/contrib/admin/locale/sr/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/sr/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/sr/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/sr/LC_MESSAGES/djangojs.po index e5fc71626e33..325f7f4be368 100644 --- a/django/contrib/admin/locale/sr/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/sr/LC_MESSAGES/djangojs.po @@ -1,15 +1,16 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Branko Kokanovic , 2018 # Jannis Leidel , 2011 # Janos Guljas , 2011-2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2018-01-30 10:24+0000\n" +"Last-Translator: Branko Kokanovic \n" "Language-Team: Serbian (http://www.transifex.com/django/django/language/" "sr/)\n" "MIME-Version: 1.0\n" @@ -94,29 +95,9 @@ msgid "" "button." msgstr "Изабрали сте акцију али нисте изменили ни једно поље." -#, javascript-format -msgid "Note: You are %s hour ahead of server time." -msgid_plural "Note: You are %s hours ahead of server time." -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" - -#, javascript-format -msgid "Note: You are %s hour behind server time." -msgid_plural "Note: You are %s hours behind server time." -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" - msgid "Now" msgstr "Тренутно време" -msgid "Choose a Time" -msgstr "" - -msgid "Choose a time" -msgstr "Одабир времена" - msgid "Midnight" msgstr "Поноћ" @@ -127,7 +108,27 @@ msgid "Noon" msgstr "Подне" msgid "6 p.m." -msgstr "" +msgstr "18ч" + +#, javascript-format +msgid "Note: You are %s hour ahead of server time." +msgid_plural "Note: You are %s hours ahead of server time." +msgstr[0] "Обавештење: %s сат сте испред серверског времена." +msgstr[1] "Обавештење: %s сата сте испред серверског времена." +msgstr[2] "Обавештење: %s сати сте испред серверског времена." + +#, javascript-format +msgid "Note: You are %s hour behind server time." +msgid_plural "Note: You are %s hours behind server time." +msgstr[0] "Обавештење: %s сат сте иза серверског времена." +msgstr[1] "Обавештење: %s сата сте иза серверског времена." +msgstr[2] "Обавештење: %s сати сте иза серверског времена." + +msgid "Choose a Time" +msgstr "Одаберите време" + +msgid "Choose a time" +msgstr "Одабир времена" msgid "Cancel" msgstr "Поништи" @@ -136,7 +137,7 @@ msgid "Today" msgstr "Данас" msgid "Choose a Date" -msgstr "" +msgstr "Одаберите датум" msgid "Yesterday" msgstr "Јуче" @@ -145,68 +146,68 @@ msgid "Tomorrow" msgstr "Сутра" msgid "January" -msgstr "" +msgstr "Јануар" msgid "February" -msgstr "" +msgstr "Фебруар" msgid "March" -msgstr "" +msgstr "Март" msgid "April" -msgstr "" +msgstr "Април" msgid "May" -msgstr "" +msgstr "Мај" msgid "June" -msgstr "" +msgstr "Јун" msgid "July" -msgstr "" +msgstr "Јул" msgid "August" -msgstr "" +msgstr "Август" msgid "September" -msgstr "" +msgstr "Септембар" msgid "October" -msgstr "" +msgstr "Октобар" msgid "November" -msgstr "" +msgstr "Новембар" msgid "December" -msgstr "" +msgstr "Децембар" msgctxt "one letter Sunday" msgid "S" -msgstr "" +msgstr "Н" msgctxt "one letter Monday" msgid "M" -msgstr "" +msgstr "П" msgctxt "one letter Tuesday" msgid "T" -msgstr "" +msgstr "У" msgctxt "one letter Wednesday" msgid "W" -msgstr "" +msgstr "С" msgctxt "one letter Thursday" msgid "T" -msgstr "" +msgstr "Ч" msgctxt "one letter Friday" msgid "F" -msgstr "" +msgstr "П" msgctxt "one letter Saturday" msgid "S" -msgstr "" +msgstr "С" msgid "Show" msgstr "Покажи" diff --git a/django/contrib/admin/locale/sr_Latn/LC_MESSAGES/django.mo b/django/contrib/admin/locale/sr_Latn/LC_MESSAGES/django.mo index a14939b9cbf1..65c851bf9015 100644 Binary files a/django/contrib/admin/locale/sr_Latn/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/sr_Latn/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/sr_Latn/LC_MESSAGES/django.po b/django/contrib/admin/locale/sr_Latn/LC_MESSAGES/django.po index 0adbe4ee45e7..4c3d304a3cf0 100644 --- a/django/contrib/admin/locale/sr_Latn/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/sr_Latn/LC_MESSAGES/django.po @@ -1,15 +1,16 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Igor Jerosimić, 2019 # Jannis Leidel , 2011 # Janos Guljas , 2011-2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-06-27 12:37+0000\n" +"Last-Translator: Igor Jerosimić\n" "Language-Team: Serbian (Latin) (http://www.transifex.com/django/django/" "language/sr@latin/)\n" "MIME-Version: 1.0\n" @@ -35,7 +36,7 @@ msgid "Delete selected %(verbose_name_plural)s" msgstr "Briši označene objekte klase %(verbose_name_plural)s" msgid "Administration" -msgstr "" +msgstr "Administracija" msgid "All" msgstr "Svi" @@ -65,16 +66,18 @@ msgid "This year" msgstr "Ova godina" msgid "No date" -msgstr "" +msgstr "Nema datuma" msgid "Has date" -msgstr "" +msgstr "Ima datum" #, python-format msgid "" "Please enter the correct %(username)s and password for a staff account. Note " "that both fields may be case-sensitive." msgstr "" +"Molim vas unesite ispravno %(username)s i lozinku. Obratite pažnju da mala i " +"velika slova predstavljaju različite karaktere." msgid "Action:" msgstr "Radnja:" @@ -86,20 +89,29 @@ msgstr "Dodaj još jedan objekat klase %(verbose_name)s." msgid "Remove" msgstr "Obriši" +msgid "Addition" +msgstr "Dodavanja" + +msgid "Change" +msgstr "Izmeni" + +msgid "Deletion" +msgstr "Brisanja" + msgid "action time" msgstr "vreme radnje" msgid "user" -msgstr "" +msgstr "korisnik" msgid "content type" -msgstr "" +msgstr "tip sadržaja" msgid "object id" msgstr "id objekta" #. 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 "opis objekta" @@ -132,25 +144,25 @@ msgstr "Objekat unosa loga" #, python-brace-format msgid "Added {name} \"{object}\"." -msgstr "" +msgstr "Dodat objekat {name} \"{object}\"." msgid "Added." -msgstr "" +msgstr "Dodato." msgid "and" msgstr "i" #, python-brace-format msgid "Changed {fields} for {name} \"{object}\"." -msgstr "" +msgstr "Izmenjena polja {fields} za {name} \"{object}\"." #, python-brace-format msgid "Changed {fields}." -msgstr "" +msgstr "Izmenjena polja {fields}." #, python-brace-format msgid "Deleted {name} \"{object}\"." -msgstr "" +msgstr "Obrisan objekat {name} \"{object}\"." msgid "No fields changed." msgstr "Bez izmena u poljima." @@ -163,8 +175,10 @@ msgid "" msgstr "" #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "" + +msgid "You may edit it again below." msgstr "" #, python-brace-format @@ -174,12 +188,13 @@ msgid "" msgstr "" #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." +"The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" #, python-brace-format @@ -207,8 +222,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "Objekat „%(obj)s“ klase %(name)s uspešno je obrisan." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Objekat klase %(name)s sa primarnim ključem %(key)r ne postoji." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" @@ -218,6 +233,10 @@ msgstr "Dodaj objekat klase %s" msgid "Change %s" msgstr "Izmeni objekat klase %s" +#, python-format +msgid "View %s" +msgstr "Pregled %s" + msgid "Database error" msgstr "Greška u bazi podataka" @@ -324,7 +343,7 @@ msgid "Change password" msgstr "Promena lozinke" msgid "Please correct the error below." -msgstr "Ispravite navedene greške." +msgstr "" msgid "Please correct the errors below." msgstr "" @@ -337,7 +356,7 @@ msgid "Welcome," msgstr "Dobrodošli," msgid "View site" -msgstr "" +msgstr "Pogledaj sajt" msgid "Documentation" msgstr "Dokumentacija" @@ -435,8 +454,8 @@ msgstr "" "Da li ste sigurni da želite da izbrišete izabrane %(objects_name)s? Svi " "sledeći objekti i objekti sa njima povezani će biti izbrisani:" -msgid "Change" -msgstr "Izmeni" +msgid "View" +msgstr "Pregled" msgid "Delete?" msgstr "Brisanje?" @@ -455,8 +474,8 @@ msgstr "" msgid "Add" msgstr "Dodaj" -msgid "You don't have permission to edit anything." -msgstr "Nemate dozvole da unosite bilo kakve izmene." +msgid "You don't have permission to view or edit anything." +msgstr "Nemate dozvolu da pogledate ili izmenite bilo šta." msgid "Recent actions" msgstr "" @@ -509,19 +528,7 @@ msgstr "Prikaži sve" msgid "Save" msgstr "Sačuvaj" -msgid "Popup closing..." -msgstr "" - -#, python-format -msgid "Change selected %(model)s" -msgstr "" - -#, python-format -msgid "Add another %(model)s" -msgstr "" - -#, python-format -msgid "Delete selected %(model)s" +msgid "Popup closing…" msgstr "" msgid "Search" @@ -547,6 +554,24 @@ msgstr "Sačuvaj i dodaj sledeći" msgid "Save and continue editing" msgstr "Sačuvaj i nastavi sa izmenama" +msgid "Save and view" +msgstr "Snimi i pogledaj" + +msgid "Close" +msgstr "" + +#, python-format +msgid "Change selected %(model)s" +msgstr "" + +#, python-format +msgid "Add another %(model)s" +msgstr "" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Hvala što ste danas proveli vreme na ovom sajtu." @@ -649,6 +674,10 @@ msgstr "Odaberi objekat klase %s" msgid "Select %s to change" msgstr "Odaberi objekat klase %s za izmenu" +#, python-format +msgid "Select %s to view" +msgstr "Odaberi %sza pregled" + msgid "Date:" msgstr "Datum:" 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 60c50f3c40c5..5cc9a7aefc55 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 2d4226ef7660..1290502735e8 100644 --- a/django/contrib/admin/locale/sr_Latn/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/sr_Latn/LC_MESSAGES/djangojs.po @@ -1,15 +1,16 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Igor Jerosimić, 2019 # Jannis Leidel , 2011 # Janos Guljas , 2011-2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2019-06-27 19:12+0000\n" +"Last-Translator: Igor Jerosimić\n" "Language-Team: Serbian (Latin) (http://www.transifex.com/django/django/" "language/sr@latin/)\n" "MIME-Version: 1.0\n" @@ -94,29 +95,9 @@ msgid "" "button." msgstr "Izabrali ste akciju ali niste izmenili ni jedno polje." -#, javascript-format -msgid "Note: You are %s hour ahead of server time." -msgid_plural "Note: You are %s hours ahead of server time." -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" - -#, javascript-format -msgid "Note: You are %s hour behind server time." -msgid_plural "Note: You are %s hours behind server time." -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" - msgid "Now" msgstr "Trenutno vreme" -msgid "Choose a Time" -msgstr "" - -msgid "Choose a time" -msgstr "Odabir vremena" - msgid "Midnight" msgstr "Ponoć" @@ -127,7 +108,27 @@ msgid "Noon" msgstr "Podne" msgid "6 p.m." -msgstr "" +msgstr "18č" + +#, javascript-format +msgid "Note: You are %s hour ahead of server time." +msgid_plural "Note: You are %s hours ahead of server time." +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." + +#, javascript-format +msgid "Note: You are %s hour behind server time." +msgid_plural "Note: You are %s hours behind server time." +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." + +msgid "Choose a Time" +msgstr "Odaberite vreme" + +msgid "Choose a time" +msgstr "Odabir vremena" msgid "Cancel" msgstr "Poništi" @@ -136,7 +137,7 @@ msgid "Today" msgstr "Danas" msgid "Choose a Date" -msgstr "" +msgstr "Odaberite datum" msgid "Yesterday" msgstr "Juče" @@ -145,68 +146,68 @@ msgid "Tomorrow" msgstr "Sutra" msgid "January" -msgstr "" +msgstr "Januar" msgid "February" -msgstr "" +msgstr "Februar" msgid "March" -msgstr "" +msgstr "Mart" msgid "April" -msgstr "" +msgstr "April" msgid "May" -msgstr "" +msgstr "Maj" msgid "June" -msgstr "" +msgstr "Jun" msgid "July" -msgstr "" +msgstr "Jul" msgid "August" -msgstr "" +msgstr "Avgust" msgid "September" -msgstr "" +msgstr "Septembar" msgid "October" -msgstr "" +msgstr "Oktobar" msgid "November" -msgstr "" +msgstr "Novembar" msgid "December" -msgstr "" +msgstr "Decembar" msgctxt "one letter Sunday" msgid "S" -msgstr "" +msgstr "N" msgctxt "one letter Monday" msgid "M" -msgstr "" +msgstr "P" msgctxt "one letter Tuesday" msgid "T" -msgstr "" +msgstr "U" msgctxt "one letter Wednesday" msgid "W" -msgstr "" +msgstr "S" msgctxt "one letter Thursday" msgid "T" -msgstr "" +msgstr "Č" msgctxt "one letter Friday" msgid "F" -msgstr "" +msgstr "P" msgctxt "one letter Saturday" msgid "S" -msgstr "" +msgstr "S" msgid "Show" msgstr "Pokaži" diff --git a/django/contrib/admin/locale/sv/LC_MESSAGES/django.mo b/django/contrib/admin/locale/sv/LC_MESSAGES/django.mo index 29fdf24a60d7..3ca037edbd2f 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 cae0316db823..740b757cd387 100644 --- a/django/contrib/admin/locale/sv/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/sv/LC_MESSAGES/django.po @@ -3,21 +3,22 @@ # Translators: # Alex Nordlund , 2012 # Andreas Pelme , 2014 -# cvitan , 2011 +# d7bcbd5f5cbecdc2b959899620582440, 2011 # Cybjit , 2012 +# Henrik Palmlund Wahlgren , 2019 # Jannis Leidel , 2011 # Jonathan Lindén, 2015 # Jonathan Lindén, 2014 # Mattias Hansson , 2016 -# sorl , 2011 -# Thomas Lundqvist , 2013,2016 +# Mikko Hellsing , 2011 +# Thomas Lundqvist, 2013,2016-2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-07-07 19:24+0000\n" -"Last-Translator: Mattias Hansson \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-28 13:45+0000\n" +"Last-Translator: Henrik Palmlund Wahlgren \n" "Language-Team: Swedish (http://www.transifex.com/django/django/language/" "sv/)\n" "MIME-Version: 1.0\n" @@ -95,6 +96,15 @@ msgstr "Lägg till ytterligare %(verbose_name)s" msgid "Remove" msgstr "Ta bort" +msgid "Addition" +msgstr "Tillägg" + +msgid "Change" +msgstr "Ändra" + +msgid "Deletion" +msgstr "Borttagning" + msgid "action time" msgstr "händelsetid" @@ -108,7 +118,7 @@ msgid "object id" msgstr "objektets 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 "objektets beskrivning" @@ -173,34 +183,40 @@ msgstr "" "Håll ner \"Control\", eller \"Command\" på en Mac, för att välja fler än en." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" +msgid "The {name} \"{obj}\" was added successfully." +msgstr "{name} \"{obj}\" lades till." + +msgid "You may edit it again below." +msgstr "Du kan redigera det igen nedan" #, python-brace-format msgid "" "The {name} \"{obj}\" was added successfully. You may add another {name} " "below." msgstr "" +"{name} \"{obj}\" lades till. Du kan lägga till ytterligare {name} nedan." #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "" +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." +msgstr "{name} \"{obj}\" ändrades. Du kan ändra det igen nedan." #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." -msgstr "" +"The {name} \"{obj}\" was added successfully. You may edit it again below." +msgstr "{name} \"{obj}\" lades till. Du kan redigera objektet igen nedanför." #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " "below." msgstr "" +"The {name} \"{obj}\" was changed successfully. You may add another {name} " +"below." #, python-brace-format msgid "The {name} \"{obj}\" was changed successfully." -msgstr "" +msgstr "{name} \"{obj}\" ändrades." msgid "" "Items must be selected in order to perform actions on them. No items have " @@ -216,8 +232,9 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" togs bort." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(name)s-objekt med primärnyckel %(key)r finns inte." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" +"%(name)s med id \"%(key)s\" existerar inte. Kanske har den tagits bort?" #, python-format msgid "Add %s" @@ -227,6 +244,10 @@ msgstr "Lägg till %s" msgid "Change %s" msgstr "Ändra %s" +#, python-format +msgid "View %s" +msgstr "Visa 1%s" + msgid "Database error" msgstr "Databasfel" @@ -336,7 +357,7 @@ msgid "Change password" msgstr "Ändra lösenord" msgid "Please correct the error below." -msgstr "Rätta till felen nedan." +msgstr "Vänligen rätta nedanstående fel" msgid "Please correct the errors below." msgstr "Vänligen rätta till felen nedan." @@ -447,8 +468,8 @@ msgstr "" "Är du säker på att du vill ta bort valda %(objects_name)s? Alla följande " "objekt samt relaterade objekt kommer att tas bort: " -msgid "Change" -msgstr "Ändra" +msgid "View" +msgstr "Visa" msgid "Delete?" msgstr "Radera?" @@ -467,14 +488,14 @@ msgstr "Modeller i applikationen %(name)s" msgid "Add" msgstr "Lägg till" -msgid "You don't have permission to edit anything." -msgstr "Du har inte rättigheter att redigera något." +msgid "You don't have permission to view or edit anything." +msgstr "Du har inte tillåtelse att se eller redigera någonting." msgid "Recent actions" -msgstr "" +msgstr "Senaste Händelser" msgid "My actions" -msgstr "" +msgstr "Mina händelser" msgid "None available" msgstr "Inga tillgängliga" @@ -523,20 +544,8 @@ msgstr "Visa alla" msgid "Save" msgstr "Spara" -msgid "Popup closing..." -msgstr "Popup stänger..." - -#, python-format -msgid "Change selected %(model)s" -msgstr "Ändra markerade %(model)s" - -#, python-format -msgid "Add another %(model)s" -msgstr "Lägg till %(model)s" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Ta bort markerade %(model)s" +msgid "Popup closing…" +msgstr "Popup stängs..." msgid "Search" msgstr "Sök" @@ -560,6 +569,24 @@ msgstr "Spara och lägg till ny" msgid "Save and continue editing" msgstr "Spara och fortsätt redigera" +msgid "Save and view" +msgstr "Spara och visa" + +msgid "Close" +msgstr "Stäng" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Ändra markerade %(model)s" + +#, python-format +msgid "Add another %(model)s" +msgstr "Lägg till %(model)s" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Ta bort markerade %(model)s" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Tack för att du spenderade lite kvalitetstid med webbplatsen idag." @@ -671,6 +698,10 @@ msgstr "Välj %s" msgid "Select %s to change" msgstr "Välj %s att ändra" +#, python-format +msgid "Select %s to view" +msgstr "Välj 1%s för visning" + msgid "Date:" msgstr "Datum:" diff --git a/django/contrib/admin/locale/sv/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/sv/LC_MESSAGES/djangojs.mo index 777acbf8dc51..a318da0b7447 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 5054c938236e..0cb6bb51dd5a 100644 --- a/django/contrib/admin/locale/sv/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/sv/LC_MESSAGES/djangojs.po @@ -4,7 +4,8 @@ # Andreas Pelme , 2012 # Jannis Leidel , 2011 # Jonathan Lindén, 2014 -# Mattias Jansson , 2011 +# Mattias Hansson , 2016 +# Mattias Benjaminsson , 2011 # Samuel Linde , 2011 # Thomas Lundqvist , 2016 msgid "" @@ -12,8 +13,8 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" -"Last-Translator: Jannis Leidel \n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" +"Last-Translator: Mattias Hansson \n" "Language-Team: Swedish (http://www.transifex.com/django/django/language/" "sv/)\n" "MIME-Version: 1.0\n" @@ -152,68 +153,68 @@ msgid "Tomorrow" msgstr "I morgon" msgid "January" -msgstr "" +msgstr "januari" msgid "February" -msgstr "" +msgstr "februari" msgid "March" -msgstr "" +msgstr "mars" msgid "April" -msgstr "" +msgstr "april" msgid "May" -msgstr "" +msgstr "maj" msgid "June" -msgstr "" +msgstr "juni" msgid "July" -msgstr "" +msgstr "juli" msgid "August" -msgstr "" +msgstr "augusti" msgid "September" -msgstr "" +msgstr "september" msgid "October" -msgstr "" +msgstr "oktober" msgid "November" -msgstr "" +msgstr "november" msgid "December" -msgstr "" +msgstr "december" msgctxt "one letter Sunday" msgid "S" -msgstr "" +msgstr "S" msgctxt "one letter Monday" msgid "M" -msgstr "" +msgstr "M" msgctxt "one letter Tuesday" msgid "T" -msgstr "" +msgstr "T" msgctxt "one letter Wednesday" msgid "W" -msgstr "" +msgstr "O" msgctxt "one letter Thursday" msgid "T" -msgstr "" +msgstr "T" msgctxt "one letter Friday" msgid "F" -msgstr "" +msgstr "F" msgctxt "one letter Saturday" msgid "S" -msgstr "" +msgstr "L" msgid "Show" msgstr "Visa" diff --git a/django/contrib/admin/locale/sw/LC_MESSAGES/django.mo b/django/contrib/admin/locale/sw/LC_MESSAGES/django.mo index 4500b79b5308..6e917f5d949f 100644 Binary files a/django/contrib/admin/locale/sw/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/sw/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/sw/LC_MESSAGES/django.po b/django/contrib/admin/locale/sw/LC_MESSAGES/django.po index 93f495c7e554..1271dff51e5a 100644 --- a/django/contrib/admin/locale/sw/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/sw/LC_MESSAGES/django.po @@ -7,9 +7,9 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-08-09 20:22+0000\n" -"Last-Translator: Machaku \n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-23 18:54+0000\n" +"Last-Translator: Jannis Leidel \n" "Language-Team: Swahili (http://www.transifex.com/django/django/language/" "sw/)\n" "MIME-Version: 1.0\n" @@ -209,8 +209,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "Ufutaji wa \"%(obj)s\" %(name)s umefanikiwa." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Hakuna %(name)s yenye `primary key` %(key)r." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" diff --git a/django/contrib/admin/locale/sw/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/sw/LC_MESSAGES/djangojs.mo index 09e0e25c5b81..12f1466cf366 100644 Binary files a/django/contrib/admin/locale/sw/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/sw/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/sw/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/sw/LC_MESSAGES/djangojs.po index a41c1ad65a68..5806dd93971a 100644 --- a/django/contrib/admin/locale/sw/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/sw/LC_MESSAGES/djangojs.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" +"PO-Revision-Date: 2017-09-23 18:54+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Swahili (http://www.transifex.com/django/django/language/" "sw/)\n" diff --git a/django/contrib/admin/locale/ta/LC_MESSAGES/django.mo b/django/contrib/admin/locale/ta/LC_MESSAGES/django.mo index 9e73f54fa5fc..398f1f2850e8 100644 Binary files a/django/contrib/admin/locale/ta/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/ta/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/ta/LC_MESSAGES/django.po b/django/contrib/admin/locale/ta/LC_MESSAGES/django.po index 22f4f7418f5d..3a3cf1bb9e93 100644 --- a/django/contrib/admin/locale/ta/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/ta/LC_MESSAGES/django.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-19 16:40+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Tamil (http://www.transifex.com/django/django/language/ta/)\n" "MIME-Version: 1.0\n" @@ -202,7 +202,7 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" வெற்றிகரமாக அழிக்கப்பட்டுள்ளது." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" msgstr "" #, python-format diff --git a/django/contrib/admin/locale/ta/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/ta/LC_MESSAGES/djangojs.mo index ac3b70eaa565..339311151934 100644 Binary files a/django/contrib/admin/locale/ta/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/ta/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/ta/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/ta/LC_MESSAGES/djangojs.po index 9198fe153daa..0a7bfcc6b368 100644 --- a/django/contrib/admin/locale/ta/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/ta/LC_MESSAGES/djangojs.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:10+0000\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Tamil (http://www.transifex.com/django/django/language/ta/)\n" "MIME-Version: 1.0\n" diff --git a/django/contrib/admin/locale/te/LC_MESSAGES/django.mo b/django/contrib/admin/locale/te/LC_MESSAGES/django.mo index 6eaee96eb44e..17e7dc6bd4fa 100644 Binary files a/django/contrib/admin/locale/te/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/te/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/te/LC_MESSAGES/django.po b/django/contrib/admin/locale/te/LC_MESSAGES/django.po index fa7947a73784..f624d4f9352e 100644 --- a/django/contrib/admin/locale/te/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/te/LC_MESSAGES/django.po @@ -9,8 +9,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-19 16:40+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Telugu (http://www.transifex.com/django/django/language/te/)\n" "MIME-Version: 1.0\n" @@ -207,8 +207,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" జయప్రదంగా తీసివేయబడ్డడి" #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(key)r ప్రధాన కీ గా వున్న %(name)s అంశం ఏమి లేదు." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" diff --git a/django/contrib/admin/locale/te/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/te/LC_MESSAGES/djangojs.mo index f7ed58e1d751..92b65f1794ac 100644 Binary files a/django/contrib/admin/locale/te/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/te/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/te/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/te/LC_MESSAGES/djangojs.po index 12bc985ddb4c..cfa35a1e0b28 100644 --- a/django/contrib/admin/locale/te/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/te/LC_MESSAGES/djangojs.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:10+0000\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Telugu (http://www.transifex.com/django/django/language/te/)\n" "MIME-Version: 1.0\n" diff --git a/django/contrib/admin/locale/th/LC_MESSAGES/django.mo b/django/contrib/admin/locale/th/LC_MESSAGES/django.mo index 326ab9b5d7ff..1b2ec3f923b6 100644 Binary files a/django/contrib/admin/locale/th/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/th/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/th/LC_MESSAGES/django.po b/django/contrib/admin/locale/th/LC_MESSAGES/django.po index 90316fa10316..093c656ec529 100644 --- a/django/contrib/admin/locale/th/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/th/LC_MESSAGES/django.po @@ -2,15 +2,15 @@ # # Translators: # Jannis Leidel , 2011 -# Kowit Charoenratchatabhan , 2013-2014 +# Kowit Charoenratchatabhan , 2013-2014,2017-2018 # piti118 , 2012 # Suteepat Damrongyingsupab , 2011-2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" +"POT-Creation-Date: 2018-05-21 14:16-0300\n" +"PO-Revision-Date: 2018-05-28 01:29+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Thai (http://www.transifex.com/django/django/language/th/)\n" "MIME-Version: 1.0\n" @@ -86,11 +86,20 @@ msgstr "เพิ่ม %(verbose_name)s อีก" msgid "Remove" msgstr "ถอดออก" +msgid "Addition" +msgstr "" + +msgid "Change" +msgstr "เปลี่ยนแปลง" + +msgid "Deletion" +msgstr "" + msgid "action time" msgstr "เวลาลงมือ" msgid "user" -msgstr "" +msgstr "ผู้ใช้" msgid "content type" msgstr "" @@ -135,7 +144,7 @@ msgid "Added {name} \"{object}\"." msgstr "" msgid "Added." -msgstr "" +msgstr "เพิ่มแล้ว" msgid "and" msgstr "และ" @@ -146,7 +155,7 @@ msgstr "" #, python-brace-format msgid "Changed {fields}." -msgstr "" +msgstr "เปลี่ยน {fields}." #, python-brace-format msgid "Deleted {name} \"{object}\"." @@ -163,8 +172,10 @@ msgid "" msgstr "" #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "" + +msgid "You may edit it again below." msgstr "" #, python-brace-format @@ -174,12 +185,13 @@ msgid "" msgstr "" #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." +"The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" #, python-brace-format @@ -207,8 +219,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "ลบ %(name)s \"%(obj)s\" เรียบร้อยแล้ว" #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Primary key %(key)r ของอ็อบเจ็กต์ %(name)s ไม่มีอยู่" +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" @@ -218,6 +230,10 @@ msgstr "เพิ่ม %s" msgid "Change %s" msgstr "เปลี่ยน %s" +#, python-format +msgid "View %s" +msgstr "" + msgid "Database error" msgstr "เกิดความผิดพลาดที่ฐานข้อมูล" @@ -322,7 +338,7 @@ msgid "Change password" msgstr "เปลี่ยนรหัสผ่าน" msgid "Please correct the error below." -msgstr "โปรดแก้ไขข้อผิดพลาดด้านล่าง" +msgstr "" msgid "Please correct the errors below." msgstr "กรุณาแก้ไขข้อผิดพลาดด้านล่าง" @@ -428,8 +444,8 @@ msgstr "" "คุณแน่ใจหรือว่า ต้องการลบ %(objects_name)s ที่ถูกเลือก? เนื่องจากอ็อบเจ็กต์ " "และรายการที่เกี่ยวข้องทั้งหมดต่อไปนี้จะถูกลบด้วย" -msgid "Change" -msgstr "เปลี่ยนแปลง" +msgid "View" +msgstr "" msgid "Delete?" msgstr "ลบ?" @@ -448,8 +464,8 @@ msgstr "โมเดลในแอป %(name)s" msgid "Add" msgstr "เพิ่ม" -msgid "You don't have permission to edit anything." -msgstr "คุณไม่สิทธิ์ในการเปลี่ยนแปลงข้อมูลใดๆ ได้" +msgid "You don't have permission to view or edit anything." +msgstr "" msgid "Recent actions" msgstr "" @@ -507,6 +523,10 @@ msgstr "" msgid "Change selected %(model)s" msgstr "" +#, python-format +msgid "View selected %(model)s" +msgstr "" + #, python-format msgid "Add another %(model)s" msgstr "" @@ -536,6 +556,12 @@ msgstr "บันทึกและเพิ่ม" msgid "Save and continue editing" msgstr "บันทึกและกลับมาแก้ไข" +msgid "Save and view" +msgstr "" + +msgid "Close" +msgstr "" + msgid "Thanks for spending some quality time with the Web site today." msgstr "ขอบคุณที่สละเวลาอันมีค่าให้กับเว็บไซต์ของเราในวันนี้" @@ -587,7 +613,7 @@ msgstr "" 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 "" +msgstr "เราได้ส่งอีเมลวิธีการตั้งรหัสผ่าน ไปที่อีเมลที่คุณให้ไว้เรียบร้อยแล้ว และคุณจะได้รับเร็วๆ นี้" msgid "" "If you don't receive an email, please make sure you've entered the address " @@ -638,6 +664,10 @@ msgstr "เลือก %s" msgid "Select %s to change" msgstr "เลือก %s เพื่อเปลี่ยนแปลง" +#, python-format +msgid "Select %s to view" +msgstr "" + msgid "Date:" msgstr "วันที่ :" diff --git a/django/contrib/admin/locale/th/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/th/LC_MESSAGES/djangojs.mo index 0da80e35419e..71eff638706d 100644 Binary files a/django/contrib/admin/locale/th/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/th/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/th/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/th/LC_MESSAGES/djangojs.po index 4550f71933cc..5cca152ce971 100644 --- a/django/contrib/admin/locale/th/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/th/LC_MESSAGES/djangojs.po @@ -2,15 +2,16 @@ # # Translators: # Jannis Leidel , 2011 -# Kowit Charoenratchatabhan , 2011-2012 +# Kowit Charoenratchatabhan , 2011-2012,2018 +# Perry Roper , 2017 # Suteepat Damrongyingsupab , 2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2018-05-06 07:50+0000\n" +"Last-Translator: Kowit Charoenratchatabhan \n" "Language-Team: Thai (http://www.transifex.com/django/django/language/th/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -95,25 +96,9 @@ msgid "" msgstr "" "คุณได้เลือกคำสั่งและคุณยังไม่ได้ทำการเปลี่ยนแปลงใด ๆ ในฟิลด์ คุณอาจมองหาปุ่มไปมากกว่าปุ่มบันทึก" -#, javascript-format -msgid "Note: You are %s hour ahead of server time." -msgid_plural "Note: You are %s hours ahead of server time." -msgstr[0] "" - -#, javascript-format -msgid "Note: You are %s hour behind server time." -msgid_plural "Note: You are %s hours behind server time." -msgstr[0] "" - msgid "Now" msgstr "ขณะนี้" -msgid "Choose a Time" -msgstr "" - -msgid "Choose a time" -msgstr "เลือกเวลา" - msgid "Midnight" msgstr "เที่ยงคืน" @@ -124,7 +109,23 @@ msgid "Noon" msgstr "เที่ยงวัน" msgid "6 p.m." -msgstr "" +msgstr "หกโมงเย็น" + +#, javascript-format +msgid "Note: You are %s hour ahead of server time." +msgid_plural "Note: You are %s hours ahead of server time." +msgstr[0] "หมายเหตุ: เวลาคุณเร็วกว่าเวลาบนเซิร์ฟเวอร์อยู่ %s ชั่วโมง." + +#, javascript-format +msgid "Note: You are %s hour behind server time." +msgid_plural "Note: You are %s hours behind server time." +msgstr[0] "หมายเหตุ: เวลาคุณช้ากว่าเวลาบนเซิร์ฟเวอร์อยู่ %s ชั่วโมง." + +msgid "Choose a Time" +msgstr "เลือกเวลา" + +msgid "Choose a time" +msgstr "เลือกเวลา" msgid "Cancel" msgstr "ยกเลิก" @@ -133,7 +134,7 @@ msgid "Today" msgstr "วันนี้" msgid "Choose a Date" -msgstr "" +msgstr "เลือกวัน" msgid "Yesterday" msgstr "เมื่อวาน" @@ -142,68 +143,68 @@ msgid "Tomorrow" msgstr "พรุ่งนี้" msgid "January" -msgstr "" +msgstr "มกราคม" msgid "February" -msgstr "" +msgstr "กุมภาพันธ์" msgid "March" -msgstr "" +msgstr "มีนาคม" msgid "April" -msgstr "" +msgstr "เมษายน" msgid "May" -msgstr "" +msgstr "พฤษภาคม" msgid "June" -msgstr "" +msgstr "มิถุนายน" msgid "July" -msgstr "" +msgstr "กรกฎาคม" msgid "August" -msgstr "" +msgstr "สิงหาคม" msgid "September" -msgstr "" +msgstr "กันยายน" msgid "October" -msgstr "" +msgstr "ตุลาคม" msgid "November" -msgstr "" +msgstr "พฤศจิกายน" msgid "December" -msgstr "" +msgstr "ธันวาคม" msgctxt "one letter Sunday" msgid "S" -msgstr "" +msgstr "อา." msgctxt "one letter Monday" msgid "M" -msgstr "" +msgstr "จ." msgctxt "one letter Tuesday" msgid "T" -msgstr "" +msgstr "อ." msgctxt "one letter Wednesday" msgid "W" -msgstr "" +msgstr "พ." msgctxt "one letter Thursday" msgid "T" -msgstr "" +msgstr "พฤ." msgctxt "one letter Friday" msgid "F" -msgstr "" +msgstr "ศ." msgctxt "one letter Saturday" msgid "S" -msgstr "" +msgstr "ส." msgid "Show" msgstr "แสดง" diff --git a/django/contrib/admin/locale/tr/LC_MESSAGES/django.mo b/django/contrib/admin/locale/tr/LC_MESSAGES/django.mo index 28e4274a3092..f8f3f5c688a6 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 096ff1657edf..87ed6a55b1a9 100644 --- a/django/contrib/admin/locale/tr/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/tr/LC_MESSAGES/django.po @@ -1,9 +1,9 @@ # This file is distributed under the same license as the Django package. # # Translators: -# BouRock, 2015-2016 +# BouRock, 2015-2019 # BouRock, 2014-2015 -# Caner Başaran , 2013 +# Caner Başaran , 2013 # Cihad GÜNDOĞDU , 2012 # Cihad GÜNDOĞDU , 2014 # Cihan Okyay , 2014 @@ -14,8 +14,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:41+0000\n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 16:09+0000\n" "Last-Translator: BouRock\n" "Language-Team: Turkish (http://www.transifex.com/django/django/language/" "tr/)\n" @@ -94,6 +94,15 @@ msgstr "Başka bir %(verbose_name)s ekle" msgid "Remove" msgstr "Kaldır" +msgid "Addition" +msgstr "Ekleme" + +msgid "Change" +msgstr "Değiştir" + +msgid "Deletion" +msgstr "Silme" + msgid "action time" msgstr "eylem zamanı" @@ -107,7 +116,7 @@ msgid "object id" msgstr "nesne kimliği" #. 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 "nesne kodu" @@ -173,10 +182,11 @@ msgstr "" "basılı tutun." #, 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." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "{name} \"{obj}\" başarılı olarak eklendi." + +msgid "You may edit it again below." +msgstr "Aşağıdan bunu tekrar düzenleyebilirsiniz." #, python-brace-format msgid "" @@ -186,10 +196,6 @@ msgstr "" "{name} \"{obj}\" başarılı olarak eklendi. Aşağıda başka bir {name} " "ekleyebilirsiniz." -#, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "{name} \"{obj}\" başarılı olarak eklendi." - #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may edit it again below." @@ -197,6 +203,12 @@ 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} " @@ -224,8 +236,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" başarılı olarak silindi." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(key)r birincil anahtarı olan %(name)s nesnesi mevcut değil." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "\"%(key)s\" Kimliği ile %(name)s mevcut değil. Belki silinmiş midir?" #, python-format msgid "Add %s" @@ -235,6 +247,10 @@ msgstr "%s ekle" msgid "Change %s" msgstr "%s değiştir" +#, python-format +msgid "View %s" +msgstr "%s göster" + msgid "Database error" msgstr "Veritabanı hatası" @@ -454,8 +470,8 @@ msgstr "" "Seçilen %(objects_name)s nesnelerini silmek istediğinize emin misiniz? " "Aşağıdaki nesnelerin tümü ve onların ilgili öğeleri silinecektir:" -msgid "Change" -msgstr "Değiştir" +msgid "View" +msgstr "Göster" msgid "Delete?" msgstr "Silinsin mi?" @@ -474,8 +490,8 @@ msgstr "%(name)s uygulamasındaki modeller" msgid "Add" msgstr "Ekle" -msgid "You don't have permission to edit anything." -msgstr "Hiçbir şeyi düzenlemek için izne sahip değilsiniz." +msgid "You don't have permission to view or edit anything." +msgstr "Hiçbir şeyi düzenlemek ve göstermek için izne sahip değilsiniz." msgid "Recent actions" msgstr "Son eylemler" @@ -531,20 +547,8 @@ msgstr "Tümünü göster" msgid "Save" msgstr "Kaydet" -msgid "Popup closing..." -msgstr "Açılır pencere kapanıyor..." - -#, python-format -msgid "Change selected %(model)s" -msgstr "Seçilen %(model)s değiştir" - -#, python-format -msgid "Add another %(model)s" -msgstr "Başka bir %(model)s ekle" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Seçilen %(model)s sil" +msgid "Popup closing…" +msgstr "Açılır pencere kapanıyor…" msgid "Search" msgstr "Ara" @@ -568,6 +572,24 @@ msgstr "Kaydet ve başka birini ekle" msgid "Save and continue editing" msgstr "Kaydet ve düzenlemeye devam et" +msgid "Save and view" +msgstr "Kaydet ve göster" + +msgid "Close" +msgstr "Kapat" + +#, python-format +msgid "Change selected %(model)s" +msgstr "Seçilen %(model)s değiştir" + +#, python-format +msgid "Add another %(model)s" +msgstr "Başka bir %(model)s ekle" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Seçilen %(model)s sil" + msgid "Thanks for spending some quality time with the Web site today." msgstr "" "Bugün Web sitesinde biraz güzel zaman geçirdiğiniz için teşekkür ederiz." @@ -679,6 +701,10 @@ msgstr "%s seç" msgid "Select %s to change" msgstr "Değiştirmek için %s seçin" +#, python-format +msgid "Select %s to view" +msgstr "Göstermek için %s seçin" + msgid "Date:" msgstr "Tarih:" diff --git a/django/contrib/admin/locale/tr/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/tr/LC_MESSAGES/djangojs.mo index fd076edf0111..bdd81b69f989 100644 Binary files a/django/contrib/admin/locale/tr/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/tr/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/tr/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/tr/LC_MESSAGES/djangojs.po index dbad89036416..52d2c99f2662 100644 --- a/django/contrib/admin/locale/tr/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/tr/LC_MESSAGES/djangojs.po @@ -10,8 +10,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 11:20+0000\n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-23 18:54+0000\n" "Last-Translator: BouRock\n" "Language-Team: Turkish (http://www.transifex.com/django/django/language/" "tr/)\n" @@ -102,6 +102,21 @@ msgstr "" "Bir eylem seçtiniz, fakat bireysel alanlar üzerinde hiçbir değişiklik " "yapmadınız. Muhtemelen Kaydet düğmesi yerine Git düğmesini arıyorsunuz." +msgid "Now" +msgstr "Şimdi" + +msgid "Midnight" +msgstr "Geceyarısı" + +msgid "6 a.m." +msgstr "Sabah 6" + +msgid "Noon" +msgstr "Öğle" + +msgid "6 p.m." +msgstr "6 ö.s." + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." @@ -114,27 +129,12 @@ msgid_plural "Note: You are %s hours behind server time." msgstr[0] "Not: Sunucu saatinin %s saat gerisindesiniz." msgstr[1] "Not: Sunucu saatinin %s saat gerisindesiniz." -msgid "Now" -msgstr "Şimdi" - msgid "Choose a Time" msgstr "Bir Saat Seçin" msgid "Choose a time" msgstr "Bir saat seçin" -msgid "Midnight" -msgstr "Geceyarısı" - -msgid "6 a.m." -msgstr "Sabah 6" - -msgid "Noon" -msgstr "Öğle" - -msgid "6 p.m." -msgstr "6 ö.s." - msgid "Cancel" msgstr "İptal" diff --git a/django/contrib/admin/locale/tt/LC_MESSAGES/django.mo b/django/contrib/admin/locale/tt/LC_MESSAGES/django.mo index d6a3599ef9e2..6bfde60aa116 100644 Binary files a/django/contrib/admin/locale/tt/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/tt/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/tt/LC_MESSAGES/django.po b/django/contrib/admin/locale/tt/LC_MESSAGES/django.po index cbd51611a76d..9d0260bcc6e0 100644 --- a/django/contrib/admin/locale/tt/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/tt/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-19 16:40+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Tatar (http://www.transifex.com/django/django/language/tt/)\n" "MIME-Version: 1.0\n" @@ -205,8 +205,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" уңышлы рәвештә бетерелгән." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(key)r беренчел ачкыч белән булган %(name)s юк." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" diff --git a/django/contrib/admin/locale/tt/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/tt/LC_MESSAGES/djangojs.mo index 1a6e873f503f..16af5a0237f0 100644 Binary files a/django/contrib/admin/locale/tt/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/tt/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/tt/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/tt/LC_MESSAGES/djangojs.po index 35e17736d714..36e7c72eb039 100644 --- a/django/contrib/admin/locale/tt/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/tt/LC_MESSAGES/djangojs.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:10+0000\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Tatar (http://www.transifex.com/django/django/language/tt/)\n" "MIME-Version: 1.0\n" diff --git a/django/contrib/admin/locale/uk/LC_MESSAGES/django.mo b/django/contrib/admin/locale/uk/LC_MESSAGES/django.mo index 9ad9b1d8ea45..731bd86fd64c 100644 Binary files a/django/contrib/admin/locale/uk/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/uk/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/uk/LC_MESSAGES/django.po b/django/contrib/admin/locale/uk/LC_MESSAGES/django.po index 4cec81a66ca0..593ccc35023b 100644 --- a/django/contrib/admin/locale/uk/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/uk/LC_MESSAGES/django.po @@ -4,30 +4,34 @@ # Oleksandr Chernihov , 2014 # Andriy Sokolovskiy , 2015 # Boryslav Larin , 2011 -# Denis Podlesniy , 2016 -# Igor Melnyk, 2014 +# Денис Подлесный , 2016 +# Igor Melnyk, 2014,2017 +# Ivan Dmytrenko , 2019 # Jannis Leidel , 2011 # Kirill Gagarski , 2015 # Max V. Stotsky , 2014 # Mikhail Kolesnik , 2015 # Mykola Zamkovoi , 2014 # Sergiy Kuzmenko , 2011 +# tarasyyyk , 2018 # Zoriana Zaiats, 2016 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-07-07 22:38+0000\n" -"Last-Translator: Denis Podlesniy \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-02-18 21:37+0000\n" +"Last-Translator: Ivan Dmytrenko \n" "Language-Team: Ukrainian (http://www.transifex.com/django/django/language/" "uk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: uk\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=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != " +"11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % " +"100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || " +"(n % 100 >=11 && n % 100 <=14 )) ? 2: 3);\n" #, python-format msgid "Successfully deleted %(count)d %(items)s." @@ -98,6 +102,15 @@ msgstr "Додати ще %(verbose_name)s" msgid "Remove" msgstr "Видалити" +msgid "Addition" +msgstr "Додавання" + +msgid "Change" +msgstr "Змінити" + +msgid "Deletion" +msgstr "Видалення" + msgid "action time" msgstr "час дії" @@ -111,7 +124,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)" @@ -177,10 +190,11 @@ msgstr "" "однієї опції." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" -"{name} \"{obj}\" було додано успішно. Нижче Ви можете редагувати його знову." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "{name} \"{obj}\" було додано успішно." + +msgid "You may edit it again below." +msgstr "Ви можете відредагувати це знову." #, python-brace-format msgid "" @@ -189,16 +203,18 @@ msgid "" msgstr "" "{name} \"{obj}\" було додано успішно. Нижче Ви можете додати інше {name}." -#, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -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 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} " @@ -224,8 +240,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" був видалений успішно." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(name)s об'єкт з первинним ключем %(key)r не існує." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s з ID \"%(key)s\" не існує. Можливо воно було видалене?" #, python-format msgid "Add %s" @@ -235,6 +251,10 @@ msgstr "Додати %s" msgid "Change %s" msgstr "Змінити %s" +#, python-format +msgid "View %s" +msgstr "Переглянути %s" + msgid "Database error" msgstr "Помилка бази даних" @@ -244,6 +264,7 @@ msgid_plural "%(count)s %(name)s were changed successfully." msgstr[0] "%(count)s %(name)s був успішно змінений." msgstr[1] "%(count)s %(name)s були успішно змінені." msgstr[2] "%(count)s %(name)s було успішно змінено." +msgstr[3] "%(count)s %(name)s було успішно змінено." #, python-format msgid "%(total_count)s selected" @@ -251,6 +272,7 @@ msgid_plural "All %(total_count)s selected" msgstr[0] "%(total_count)s обраний" msgstr[1] "%(total_count)s обрані" msgstr[2] "Усі %(total_count)s обрано" +msgstr[3] "Усі %(total_count)s обрано" #, python-format msgid "0 of %(cnt)s selected" @@ -345,7 +367,7 @@ msgid "Change password" msgstr "Змінити пароль" msgid "Please correct the error below." -msgstr "Будь ласка, виправте помилку, вказану нижче." +msgstr "Будь ласка, виправіть помилку нижче." msgid "Please correct the errors below." msgstr "Будь ласка, виправте помилки, вказані нижче." @@ -455,8 +477,8 @@ msgstr "" "Ви впевнені, що хочете видалити вибрані %(objects_name)s? Всі вказані " "об'єкти та пов'язані з ними елементи будуть видалені:" -msgid "Change" -msgstr "Змінити" +msgid "View" +msgstr "Переглянути" msgid "Delete?" msgstr "Видалити?" @@ -475,8 +497,8 @@ msgstr "Моделі у %(name)s додатку" msgid "Add" msgstr "Додати" -msgid "You don't have permission to edit anything." -msgstr "У вас немає дозволу на редагування будь-чого." +msgid "You don't have permission to view or edit anything." +msgstr "У вас немає дозволу на перегляд чи редагування чого-небудь." msgid "Recent actions" msgstr "Недавні дії" @@ -532,20 +554,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 "Пошук" @@ -556,6 +566,7 @@ msgid_plural "%(counter)s results" msgstr[0] "%(counter)s результат" msgstr[1] "%(counter)s результати" msgstr[2] "%(counter)s результатів" +msgstr[3] "%(counter)s результатів" #, python-format msgid "%(full_result_count)s total" @@ -570,6 +581,24 @@ msgstr "Зберегти і додати інше" msgid "Save and continue editing" 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" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Дякуємо за час, проведений сьогодні на сайті." @@ -681,6 +710,10 @@ msgstr "Вибрати %s" msgid "Select %s to change" msgstr "Виберіть %s щоб змінити" +#, python-format +msgid "Select %s to view" +msgstr "Вибрати %s для перегляду" + msgid "Date:" msgstr "Дата:" diff --git a/django/contrib/admin/locale/uk/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/uk/LC_MESSAGES/djangojs.mo index aa987520efee..f70d010ac202 100644 Binary files a/django/contrib/admin/locale/uk/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/uk/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/uk/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/uk/LC_MESSAGES/djangojs.po index 0b2cd24dc541..502c54871289 100644 --- a/django/contrib/admin/locale/uk/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/uk/LC_MESSAGES/djangojs.po @@ -3,25 +3,27 @@ # Translators: # Oleksandr Chernihov , 2014 # Boryslav Larin , 2011 -# Denis Podlesniy , 2016 +# Денис Подлесный , 2016 # Jannis Leidel , 2011 -# Panasoft , 2016 +# panasoft , 2016 # Sergey Lysach , 2011-2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-07-07 22:59+0000\n" -"Last-Translator: Denis Podlesniy \n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" +"Last-Translator: Денис Подлесный \n" "Language-Team: Ukrainian (http://www.transifex.com/django/django/language/" "uk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: uk\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=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != " +"11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % " +"100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || " +"(n % 100 >=11 && n % 100 <=14 )) ? 2: 3);\n" #, javascript-format msgid "Available %s" @@ -80,6 +82,7 @@ msgid_plural "%(sel)s of %(cnt)s selected" msgstr[0] "Обрано %(sel)s з %(cnt)s" msgstr[1] "Обрано %(sel)s з %(cnt)s" msgstr[2] "Обрано %(sel)s з %(cnt)s" +msgstr[3] "Обрано %(sel)s з %(cnt)s" msgid "" "You have unsaved changes on individual editable fields. If you run an " @@ -104,12 +107,28 @@ msgstr "" "Ви обрали дію і не зробили жодних змін у полях. Ви, напевно, шукаєте кнопку " "\"Виконати\", а не \"Зберегти\"." +msgid "Now" +msgstr "Зараз" + +msgid "Midnight" +msgstr "Північ" + +msgid "6 a.m." +msgstr "6" + +msgid "Noon" +msgstr "Полудень" + +msgid "6 p.m." +msgstr "18:00" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." msgstr[0] "Примітка: Ви на %s годину попереду серверного часу." msgstr[1] "Примітка: Ви на %s години попереду серверного часу." msgstr[2] "Примітка: Ви на %s годин попереду серверного часу." +msgstr[3] "Примітка: Ви на %s годин попереду серверного часу." #, javascript-format msgid "Note: You are %s hour behind server time." @@ -117,9 +136,7 @@ msgid_plural "Note: You are %s hours behind server time." msgstr[0] "Примітка: Ви на %s годину позаду серверного часу." msgstr[1] "Примітка: Ви на %s години позаду серверного часу." msgstr[2] "Примітка: Ви на %s годин позаду серверного часу." - -msgid "Now" -msgstr "Зараз" +msgstr[3] "Примітка: Ви на %s годин позаду серверного часу." msgid "Choose a Time" msgstr "Оберіть час" @@ -127,18 +144,6 @@ msgstr "Оберіть час" msgid "Choose a time" msgstr "Оберіть час" -msgid "Midnight" -msgstr "Північ" - -msgid "6 a.m." -msgstr "6" - -msgid "Noon" -msgstr "Полудень" - -msgid "6 p.m." -msgstr "18:00" - msgid "Cancel" msgstr "Відмінити" diff --git a/django/contrib/admin/locale/ur/LC_MESSAGES/django.mo b/django/contrib/admin/locale/ur/LC_MESSAGES/django.mo index 0d20179c3307..0735f5d6d916 100644 Binary files a/django/contrib/admin/locale/ur/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/ur/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/ur/LC_MESSAGES/django.po b/django/contrib/admin/locale/ur/LC_MESSAGES/django.po index c29fbfc19803..81ef1118e6c3 100644 --- a/django/contrib/admin/locale/ur/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/ur/LC_MESSAGES/django.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-09-19 16:40+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Urdu (http://www.transifex.com/django/django/language/ur/)\n" "MIME-Version: 1.0\n" @@ -204,8 +204,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" کامیابی سے مٹایا گیا تھا۔" #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "%(name)s شے %(key)r پرائمری کلید کے ساتھ موجود نھیں ھے۔" +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" diff --git a/django/contrib/admin/locale/ur/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/ur/LC_MESSAGES/djangojs.mo index e2ce39aef8d7..65de1984ff62 100644 Binary files a/django/contrib/admin/locale/ur/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/ur/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/ur/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/ur/LC_MESSAGES/djangojs.po index bb7493718a69..a4f5642cd63c 100644 --- a/django/contrib/admin/locale/ur/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/ur/LC_MESSAGES/djangojs.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:10+0000\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Urdu (http://www.transifex.com/django/django/language/ur/)\n" "MIME-Version: 1.0\n" diff --git a/django/contrib/admin/locale/vi/LC_MESSAGES/django.mo b/django/contrib/admin/locale/vi/LC_MESSAGES/django.mo index acd57265b26c..298498a4a851 100644 Binary files a/django/contrib/admin/locale/vi/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/vi/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/vi/LC_MESSAGES/django.po b/django/contrib/admin/locale/vi/LC_MESSAGES/django.po index d44f3fe9b1aa..68fd78c640b8 100644 --- a/django/contrib/admin/locale/vi/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/vi/LC_MESSAGES/django.po @@ -5,16 +5,16 @@ # Jannis Leidel , 2011 # Thanh Le Viet , 2013 # Tran , 2011 -# Tran Van , 2011-2013,2016 +# Tran Van , 2011-2013,2016,2018 # Vuong Nguyen , 2011 # xgenvn , 2014 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:09+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-01-18 00:36+0000\n" +"Last-Translator: Ramiro Morales\n" "Language-Team: Vietnamese (http://www.transifex.com/django/django/language/" "vi/)\n" "MIME-Version: 1.0\n" @@ -91,6 +91,15 @@ msgstr "Thêm một %(verbose_name)s " msgid "Remove" msgstr "Gỡ bỏ" +msgid "Addition" +msgstr "" + +msgid "Change" +msgstr "Thay đổi" + +msgid "Deletion" +msgstr "" + msgid "action time" msgstr "Thời gian tác động" @@ -98,13 +107,13 @@ msgid "user" msgstr "" msgid "content type" -msgstr "" +msgstr "kiểu nội dung" msgid "object id" msgstr "Mã đối tượng" #. 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 "đối tượng repr" @@ -137,7 +146,7 @@ msgstr "LogEntry Object" #, python-brace-format msgid "Added {name} \"{object}\"." -msgstr "" +msgstr "{name} \"{object}\" đã được thêm vào." msgid "Added." msgstr "Được thêm." @@ -169,8 +178,10 @@ msgstr "" "Giữ phím \"Control\", hoặc \"Command\" trên Mac, để chọn nhiều hơn một." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." +msgid "The {name} \"{obj}\" was added successfully." +msgstr "" + +msgid "You may edit it again below." msgstr "" #, python-brace-format @@ -180,12 +191,13 @@ msgid "" msgstr "" #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." +msgid "" +"The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." +"The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" #, python-brace-format @@ -213,8 +225,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" đã được xóa thành công." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr " đối tượng %(name)s với khóa chính %(key)r không tồn tại." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "" #, python-format msgid "Add %s" @@ -224,6 +236,10 @@ msgstr "Thêm %s" msgid "Change %s" msgstr "Thay đổi %s" +#, python-format +msgid "View %s" +msgstr "" + msgid "Database error" msgstr "Cơ sở dữ liệu bị lỗi" @@ -327,7 +343,7 @@ msgid "Enter a username and password." msgstr "Điền tên đăng nhập và mật khẩu." msgid "Change password" -msgstr "Thay đổi mật khẩu" +msgstr "Đổi mật khẩu" msgid "Please correct the error below." msgstr "Hãy sửa lỗi sai dưới đây" @@ -439,8 +455,8 @@ msgstr "" "Bạn chắc chắn muốn xóa những lựa chọn %(objects_name)s? Tất cả những đối " "tượng sau và những đối tượng liên quan sẽ được xóa:" -msgid "Change" -msgstr "Thay đổi" +msgid "View" +msgstr "" msgid "Delete?" msgstr "Bạn muốn xóa?" @@ -459,8 +475,8 @@ msgstr "Các mô models trong %(name)s" msgid "Add" msgstr "Thêm vào" -msgid "You don't have permission to edit anything." -msgstr "Bạn không được cấp quyền chỉnh sửa bất cứ cái gì." +msgid "You don't have permission to view or edit anything." +msgstr "" msgid "Recent actions" msgstr "" @@ -516,21 +532,9 @@ msgstr "Hiện tất cả" msgid "Save" msgstr "Lưu lại" -msgid "Popup closing..." -msgstr "Đang đóng cửa sổ popup ..." - -#, python-format -msgid "Change selected %(model)s" +msgid "Popup closing…" msgstr "" -#, python-format -msgid "Add another %(model)s" -msgstr "Thêm %(model)s khác" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "Xóa %(model)s đã chọn" - msgid "Search" msgstr "Tìm kiếm" @@ -552,6 +556,24 @@ msgstr "Lưu và thêm mới" msgid "Save and continue editing" msgstr "Lưu và tiếp tục chỉnh sửa" +msgid "Save and view" +msgstr "" + +msgid "Close" +msgstr "" + +#, python-format +msgid "Change selected %(model)s" +msgstr "" + +#, python-format +msgid "Add another %(model)s" +msgstr "Thêm %(model)s khác" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "Xóa %(model)s đã chọn" + msgid "Thanks for spending some quality time with the Web site today." msgstr "Cảm ơn bạn đã dành thời gian với website này" @@ -660,6 +682,10 @@ msgstr "Chọn %s" msgid "Select %s to change" msgstr "Chọn %s để thay đổi" +#, python-format +msgid "Select %s to view" +msgstr "" + msgid "Date:" msgstr "Ngày:" diff --git a/django/contrib/admin/locale/vi/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/vi/LC_MESSAGES/djangojs.mo index 0ec34b2b6eb7..7588ed6ca7c5 100644 Binary files a/django/contrib/admin/locale/vi/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/vi/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/vi/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/vi/LC_MESSAGES/djangojs.po index 0fd6dc9be090..d2155ca4c99f 100644 --- a/django/contrib/admin/locale/vi/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/vi/LC_MESSAGES/djangojs.po @@ -11,7 +11,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 10:11+0000\n" +"PO-Revision-Date: 2017-09-23 18:54+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Vietnamese (http://www.transifex.com/django/django/language/" "vi/)\n" 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 0b3a613e371d..8a5dd9364286 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 fcac6abf7af0..f063f60879ec 100644 --- a/django/contrib/admin/locale/zh_Hans/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/zh_Hans/LC_MESSAGES/django.po @@ -1,27 +1,34 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Brian Wang , 2018 # Fulong Sun , 2016 # Jannis Leidel , 2011 # Kevin Sze , 2012 # Lele Long , 2011,2015 -# Liping Wang , 2016 +# Le Yang , 2018 +# Liping Wang , 2016-2017 # mozillazg , 2016 -# Ronald White , 2013-2014 +# Ronald White , 2013-2014 # Sean Lee , 2013 # Sean Lee , 2013 # slene , 2011 -# Ziang Song , 2012 +# Suntravel Chris , 2019 +# Wentao Han , 2018 +# xuyi wang , 2018 +# yf zhan , 2018 +# dykai , 2019 +# ced773123cfad7b4e8b79ca80f736af9, 2012 # Kevin Sze , 2012 # 雨翌 , 2016 -# Ronald White , 2013 +# Ronald White , 2013 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-09-20 02:14+0000\n" -"Last-Translator: Liping Wang \n" +"POT-Creation-Date: 2019-01-16 20:42+0100\n" +"PO-Revision-Date: 2019-02-15 05:12+0000\n" +"Last-Translator: dykai \n" "Language-Team: Chinese (China) (http://www.transifex.com/django/django/" "language/zh_CN/)\n" "MIME-Version: 1.0\n" @@ -97,6 +104,15 @@ msgstr "添加另一个 %(verbose_name)s" msgid "Remove" msgstr "删除" +msgid "Addition" +msgstr "添加" + +msgid "Change" +msgstr "修改" + +msgid "Deletion" +msgstr "删除" + msgid "action time" msgstr "动作时间" @@ -110,7 +126,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 "对象表示" @@ -143,7 +159,7 @@ msgstr "LogEntry对象" #, python-brace-format msgid "Added {name} \"{object}\"." -msgstr "以添加{name}\"{object}\"。" +msgstr "已添加{name}\"{object}\"。" msgid "Added." msgstr "已添加。" @@ -174,9 +190,11 @@ msgid "" msgstr "按住 ”Control“,或者Mac上的 “Command”,可以选择多个。" #, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "{name} \"{obj}\" 已经添加成功。你可以在下面再次编辑它。" +msgid "The {name} \"{obj}\" was added successfully." +msgstr "{name}\"{obj}\"添加成功。" + +msgid "You may edit it again below." +msgstr "您可以在下面再次编辑它." #, python-brace-format msgid "" @@ -184,15 +202,16 @@ msgid "" "below." msgstr "{name} \"{obj}\" 已经添加成功。你可以在下面添加其它的{name}。" -#, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -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 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} " @@ -216,8 +235,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" 删除成功。" #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "具有主键 %(key)r 的对象 %(name)s 不存在。" +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "ID为“%(key)s”的%(name)s不存在。也许它被删除了? " #, python-format msgid "Add %s" @@ -227,6 +246,10 @@ msgstr "增加 %s" msgid "Change %s" msgstr "修改 %s" +#, python-format +msgid "View %s" +msgstr "查看 %s" + msgid "Database error" msgstr "数据库错误" @@ -282,7 +305,7 @@ msgid "Page not found" msgstr "页面没有找到" msgid "We're sorry, but the requested page could not be found." -msgstr "很报歉,请求页面无法找到。" +msgstr "很抱歉,请求页面无法找到。" msgid "Home" msgstr "首页" @@ -325,13 +348,13 @@ msgid "" msgstr "首先,输入一个用户名和密码。然后,你就可以编辑更多的用户选项。" msgid "Enter a username and password." -msgstr "输入用户名和" +msgstr "输入用户名和密码" msgid "Change password" msgstr "修改密码" msgid "Please correct the error below." -msgstr "请修正下面的错误。" +msgstr "请更正下列错误。" msgid "Please correct the errors below." msgstr "请更正下列错误。" @@ -437,8 +460,8 @@ msgstr "" "请确认要删除选中的 %(objects_name)s 吗?以下所有对象和余它们相关的条目将都会" "被删除:" -msgid "Change" -msgstr "修改" +msgid "View" +msgstr "查看" msgid "Delete?" msgstr "删除?" @@ -457,8 +480,8 @@ msgstr "在应用程序 %(name)s 中的模型" msgid "Add" msgstr "增加" -msgid "You don't have permission to edit anything." -msgstr "你无权修改任何东西。" +msgid "You don't have permission to view or edit anything." +msgstr "无权查看或修改。" msgid "Recent actions" msgstr "最近动作" @@ -511,20 +534,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 "搜索" @@ -547,6 +558,24 @@ msgstr "保存并增加另一个" msgid "Save and continue editing" 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" + msgid "Thanks for spending some quality time with the Web site today." msgstr "感谢您今天在本站花费了一些宝贵时间。" @@ -573,7 +602,7 @@ msgid "Password reset" msgstr "密码重设" msgid "Your password has been set. You may go ahead and log in now." -msgstr "你的口令己经设置。现在你可以继续进行登录。" +msgstr "你的密码己经设置完成,现在你可以继续进行登录。" msgid "Password reset confirmation" msgstr "密码重设确认" @@ -650,6 +679,10 @@ msgstr "选择 %s" msgid "Select %s to change" msgstr "选择 %s 来修改" +#, python-format +msgid "Select %s to view" +msgstr "选择%s查看" + msgid "Date:" msgstr "日期:" diff --git a/django/contrib/admin/locale/zh_Hans/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/zh_Hans/LC_MESSAGES/djangojs.mo index deabaccff132..2df69307e176 100644 Binary files a/django/contrib/admin/locale/zh_Hans/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/zh_Hans/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/zh_Hans/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/zh_Hans/LC_MESSAGES/djangojs.po index 7d2ca6b37f5d..b37c86410e52 100644 --- a/django/contrib/admin/locale/zh_Hans/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/zh_Hans/LC_MESSAGES/djangojs.po @@ -1,6 +1,7 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Bai HuanCheng (Bestony) , 2018 # Jannis Leidel , 2011 # Kewei Ma , 2016 # Lele Long , 2011,2015 @@ -14,9 +15,9 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-09-20 02:18+0000\n" -"Last-Translator: Liping Wang \n" +"POT-Creation-Date: 2018-05-17 11:50+0200\n" +"PO-Revision-Date: 2018-01-14 07:41+0000\n" +"Last-Translator: Bai HuanCheng (Bestony) \n" "Language-Team: Chinese (China) (http://www.transifex.com/django/django/" "language/zh_CN/)\n" "MIME-Version: 1.0\n" @@ -74,7 +75,7 @@ msgstr "删除全部" #, javascript-format msgid "Click to remove all chosen %s at once." -msgstr "删除所有选择的%s。" +msgstr "删除所有已选择的%s。" msgid "%(sel)s of %(cnt)s selected" msgid_plural "%(sel)s of %(cnt)s selected" @@ -102,6 +103,21 @@ msgstr "" "你已选则执行一个动作, 但可编辑栏位沒有任何改变. 你应该尝试 '去' 按钮, 而不是 " "'保存' 按钮." +msgid "Now" +msgstr "现在" + +msgid "Midnight" +msgstr "午夜" + +msgid "6 a.m." +msgstr "上午6点" + +msgid "Noon" +msgstr "正午" + +msgid "6 p.m." +msgstr "下午6点" + #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." @@ -112,27 +128,12 @@ msgid "Note: You are %s hour behind server time." msgid_plural "Note: You are %s hours behind server time." msgstr[0] "注意:你比服务器时间滞后 %s 个小时。" -msgid "Now" -msgstr "现在" - msgid "Choose a Time" msgstr "选择一个时间" msgid "Choose a time" msgstr "选择一个时间" -msgid "Midnight" -msgstr "午夜" - -msgid "6 a.m." -msgstr "上午6点" - -msgid "Noon" -msgstr "正午" - -msgid "6 p.m." -msgstr "下午6点" - msgid "Cancel" 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 739d6d24d00c..a96ef9a02bff 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 0aad78d690dc..a2a1d9a3a506 100644 --- a/django/contrib/admin/locale/zh_Hant/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/zh_Hant/LC_MESSAGES/django.po @@ -7,15 +7,15 @@ # mail6543210 , 2013-2014 # ming hsien tzang , 2011 # tcc , 2011 -# Tzu-ping Chung , 2016 +# Tzu-ping Chung , 2016-2017 # Yeh-Yung , 2013 # Yeh-Yung , 2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-08-19 07:27+0000\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/" "language/zh_TW/)\n" @@ -211,8 +211,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" 已成功刪除。" #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "主鍵 %(key)r 的 %(name)s 物件不存在。" +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "不存在 ID 為「%(key)s」的 %(name)s。或許它已被刪除?" #, python-format msgid "Add %s" diff --git a/django/contrib/admin/locale/zh_Hant/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/zh_Hant/LC_MESSAGES/djangojs.mo index 26dc5358f1dc..9368f692a2e6 100644 Binary files a/django/contrib/admin/locale/zh_Hant/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/zh_Hant/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/zh_Hant/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/zh_Hant/LC_MESSAGES/djangojs.po index 9e7292f3bd93..48739ec01b13 100644 --- a/django/contrib/admin/locale/zh_Hant/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/zh_Hant/LC_MESSAGES/djangojs.po @@ -11,7 +11,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-08-19 07:29+0000\n" +"PO-Revision-Date: 2017-09-19 16:41+0000\n" "Last-Translator: Tzu-ping Chung \n" "Language-Team: Chinese (Taiwan) (http://www.transifex.com/django/django/" "language/zh_TW/)\n" diff --git a/django/contrib/admin/migrations/0001_initial.py b/django/contrib/admin/migrations/0001_initial.py index c615bd79c3c7..f1e2804ce4c9 100644 --- a/django/contrib/admin/migrations/0001_initial.py +++ b/django/contrib/admin/migrations/0001_initial.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import django.contrib.admin.models from django.conf import settings from django.db import migrations, models diff --git a/django/contrib/admin/migrations/0002_logentry_remove_auto_add.py b/django/contrib/admin/migrations/0002_logentry_remove_auto_add.py index fb66c31bd3e3..a2b19162f28c 100644 --- a/django/contrib/admin/migrations/0002_logentry_remove_auto_add.py +++ b/django/contrib/admin/migrations/0002_logentry_remove_auto_add.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models from django.utils import timezone diff --git a/django/contrib/admin/migrations/0003_logentry_add_action_flag_choices.py b/django/contrib/admin/migrations/0003_logentry_add_action_flag_choices.py new file mode 100644 index 000000000000..a041a9de0e13 --- /dev/null +++ b/django/contrib/admin/migrations/0003_logentry_add_action_flag_choices.py @@ -0,0 +1,20 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('admin', '0002_logentry_remove_auto_add'), + ] + + # No database changes; adds choices to action_flag. + operations = [ + migrations.AlterField( + model_name='logentry', + name='action_flag', + field=models.PositiveSmallIntegerField( + choices=[(1, 'Addition'), (2, 'Change'), (3, 'Deletion')], + verbose_name='action flag', + ), + ), + ] diff --git a/django/contrib/admin/models.py b/django/contrib/admin/models.py index a8245a20e4bb..f0138435cad8 100644 --- a/django/contrib/admin/models.py +++ b/django/contrib/admin/models.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import json from django.conf import settings @@ -8,14 +6,19 @@ from django.db import models from django.urls import NoReverseMatch, reverse from django.utils import timezone -from django.utils.encoding import force_text, python_2_unicode_compatible from django.utils.text import get_text_list -from django.utils.translation import ugettext, ugettext_lazy as _ +from django.utils.translation import gettext, gettext_lazy as _ ADDITION = 1 CHANGE = 2 DELETION = 3 +ACTION_FLAG_CHOICES = ( + (ADDITION, _('Addition')), + (CHANGE, _('Change')), + (DELETION, _('Deletion')), +) + class LogEntryManager(models.Manager): use_in_migrations = True @@ -26,14 +29,13 @@ def log_action(self, user_id, content_type_id, object_id, object_repr, action_fl return self.model.objects.create( user_id=user_id, content_type_id=content_type_id, - object_id=force_text(object_id), + object_id=str(object_id), object_repr=object_repr[:200], action_flag=action_flag, change_message=change_message, ) -@python_2_unicode_compatible class LogEntry(models.Model): action_time = models.DateTimeField( _('action time'), @@ -52,9 +54,9 @@ class LogEntry(models.Model): blank=True, null=True, ) object_id = models.TextField(_('object id'), blank=True, null=True) - # Translators: 'repr' means representation (https://docs.python.org/3/library/functions.html#repr) + # Translators: 'repr' means representation (https://docs.python.org/library/functions.html#repr) object_repr = models.CharField(_('object repr'), max_length=200) - action_flag = models.PositiveSmallIntegerField(_('action flag')) + action_flag = models.PositiveSmallIntegerField(_('action flag'), choices=ACTION_FLAG_CHOICES) # change_message is either a string or a JSON structure change_message = models.TextField(_('change message'), blank=True) @@ -67,20 +69,20 @@ class Meta: ordering = ('-action_time',) def __repr__(self): - return force_text(self.action_time) + return str(self.action_time) def __str__(self): if self.is_addition(): - return ugettext('Added "%(object)s".') % {'object': self.object_repr} + return gettext('Added "%(object)s".') % {'object': self.object_repr} elif self.is_change(): - return ugettext('Changed "%(object)s" - %(changes)s') % { + return gettext('Changed "%(object)s" - %(changes)s') % { 'object': self.object_repr, 'changes': self.get_change_message(), } elif self.is_deletion(): - return ugettext('Deleted "%(object)s."') % {'object': self.object_repr} + return gettext('Deleted "%(object)s."') % {'object': self.object_repr} - return ugettext('LogEntry Object') + return gettext('LogEntry Object') def is_addition(self): return self.action_flag == ADDITION @@ -99,45 +101,45 @@ def get_change_message(self): if self.change_message and self.change_message[0] == '[': try: change_message = json.loads(self.change_message) - except ValueError: + except json.JSONDecodeError: return self.change_message messages = [] for sub_message in change_message: if 'added' in sub_message: if sub_message['added']: - sub_message['added']['name'] = ugettext(sub_message['added']['name']) - messages.append(ugettext('Added {name} "{object}".').format(**sub_message['added'])) + sub_message['added']['name'] = gettext(sub_message['added']['name']) + messages.append(gettext('Added {name} "{object}".').format(**sub_message['added'])) else: - messages.append(ugettext('Added.')) + messages.append(gettext('Added.')) elif 'changed' in sub_message: sub_message['changed']['fields'] = get_text_list( - sub_message['changed']['fields'], ugettext('and') + sub_message['changed']['fields'], gettext('and') ) if 'name' in sub_message['changed']: - sub_message['changed']['name'] = ugettext(sub_message['changed']['name']) - messages.append(ugettext('Changed {fields} for {name} "{object}".').format( + sub_message['changed']['name'] = gettext(sub_message['changed']['name']) + messages.append(gettext('Changed {fields} for {name} "{object}".').format( **sub_message['changed'] )) else: - messages.append(ugettext('Changed {fields}.').format(**sub_message['changed'])) + messages.append(gettext('Changed {fields}.').format(**sub_message['changed'])) elif 'deleted' in sub_message: - sub_message['deleted']['name'] = ugettext(sub_message['deleted']['name']) - messages.append(ugettext('Deleted {name} "{object}".').format(**sub_message['deleted'])) + sub_message['deleted']['name'] = gettext(sub_message['deleted']['name']) + messages.append(gettext('Deleted {name} "{object}".').format(**sub_message['deleted'])) change_message = ' '.join(msg[0].upper() + msg[1:] for msg in messages) - return change_message or ugettext('No fields changed.') + return change_message or gettext('No fields changed.') else: return self.change_message def get_edited_object(self): - "Returns the edited object represented by this log entry" + """Return the edited object represented by this log entry.""" return self.content_type.get_object_for_this_type(pk=self.object_id) def get_admin_url(self): """ - Returns the admin URL to edit the object represented by this log entry. + Return the admin URL to edit the object represented by this log entry. """ if self.content_type and self.object_id: url_name = 'admin:%s_%s_change' % (self.content_type.app_label, self.content_type.model) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index aef1e2c24e8a..fbaaa849cb95 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -1,10 +1,10 @@ -from __future__ import unicode_literals - import copy import json import operator +import re from collections import OrderedDict from functools import partial, reduce, update_wrapper +from urllib.parse import quote as urlquote from django import forms from django.conf import settings @@ -17,8 +17,12 @@ from django.contrib.admin.templatetags.admin_urls import add_preserved_filters from django.contrib.admin.utils import ( NestedObjects, construct_change_message, flatten_fieldsets, - get_deleted_objects, lookup_needs_distinct, model_format_dict, quote, - unquote, + get_deleted_objects, lookup_needs_distinct, model_format_dict, + model_ngettext, quote, unquote, +) +from django.contrib.admin.views.autocomplete import AutocompleteJsonView +from django.contrib.admin.widgets import ( + AutocompleteSelect, AutocompleteSelectMultiple, ) from django.contrib.auth import get_permission_codename from django.core.exceptions import ( @@ -38,14 +42,13 @@ from django.http.response import HttpResponseBase from django.template.response import SimpleTemplateResponse, TemplateResponse from django.urls import reverse -from django.utils import six from django.utils.decorators import method_decorator -from django.utils.encoding import force_text, python_2_unicode_compatible from django.utils.html import format_html -from django.utils.http import urlencode, urlquote +from django.utils.http import urlencode +from django.utils.inspect import get_func_args from django.utils.safestring import mark_safe from django.utils.text import capfirst, format_lazy, get_text_list -from django.utils.translation import ugettext as _, ungettext +from django.utils.translation import gettext as _, ngettext from django.views.decorators.csrf import csrf_protect from django.views.generic import RedirectView @@ -89,14 +92,16 @@ class IncorrectLookupParameters(Exception): models.ImageField: {'widget': widgets.AdminFileWidget}, models.FileField: {'widget': widgets.AdminFileWidget}, models.EmailField: {'widget': widgets.AdminEmailInputWidget}, + models.UUIDField: {'widget': widgets.AdminUUIDInputWidget}, } csrf_protect_m = method_decorator(csrf_protect) -class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)): +class BaseModelAdmin(metaclass=forms.MediaDefiningClass): """Functionality common to both ModelAdmin and InlineAdmin.""" + autocomplete_fields = () raw_id_fields = () fields = None exclude = None @@ -109,6 +114,7 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)): formfield_overrides = {} readonly_fields = () ordering = None + sortable_by = None view_on_site = True show_full_result_count = True checks_class = BaseModelAdminChecks @@ -137,13 +143,13 @@ def formfield_for_dbfield(self, db_field, request, **kwargs): return self.formfield_for_choice_field(db_field, request, **kwargs) # ForeignKey or ManyToManyFields - if isinstance(db_field, models.ManyToManyField) or isinstance(db_field, models.ForeignKey): + if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)): # Combine the field kwargs with any options for formfield_overrides. # Make sure the passed in **kwargs override anything in # formfield_overrides because **kwargs is more specific, and should # always win. if db_field.__class__ in self.formfield_overrides: - kwargs = dict(self.formfield_overrides[db_field.__class__], **kwargs) + kwargs = {**self.formfield_overrides[db_field.__class__], **kwargs} # Get the correct formfield. if isinstance(db_field, models.ForeignKey): @@ -163,6 +169,7 @@ def formfield_for_dbfield(self, db_field, request, **kwargs): can_add_related=related_modeladmin.has_add_permission(request), can_change_related=related_modeladmin.has_change_permission(request), can_delete_related=related_modeladmin.has_delete_permission(request), + can_view_related=related_modeladmin.has_view_permission(request), ) formfield.widget = widgets.RelatedFieldWidgetWrapper( formfield.widget, db_field.remote_field, self.admin_site, **wrapper_kwargs @@ -174,7 +181,7 @@ def formfield_for_dbfield(self, db_field, request, **kwargs): # passed to formfield_for_dbfield override the defaults. for klass in db_field.__class__.mro(): if klass in self.formfield_overrides: - kwargs = dict(copy.deepcopy(self.formfield_overrides[klass]), **kwargs) + kwargs = {**copy.deepcopy(self.formfield_overrides[klass]), **kwargs} return db_field.formfield(**kwargs) # For any other type of field, just call its formfield() method. @@ -202,7 +209,7 @@ def get_field_queryset(self, db, db_field, request): """ If the ModelAdmin specifies ordering, the queryset should respect that ordering. Otherwise don't specify the queryset, let the field decide - (returns None in that case). + (return None in that case). """ related_admin = self.admin_site._registry.get(db_field.remote_field.model) if related_admin is not None: @@ -216,13 +223,17 @@ def formfield_for_foreignkey(self, db_field, request, **kwargs): Get a form Field for a ForeignKey. """ db = kwargs.get('using') - if db_field.name in self.raw_id_fields: - kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.remote_field, self.admin_site, using=db) - elif db_field.name in self.radio_fields: - kwargs['widget'] = widgets.AdminRadioSelect(attrs={ - 'class': get_ul_class(self.radio_fields[db_field.name]), - }) - kwargs['empty_label'] = _('None') if db_field.blank else None + + if 'widget' not in kwargs: + if db_field.name in self.get_autocomplete_fields(request): + kwargs['widget'] = AutocompleteSelect(db_field.remote_field, self.admin_site, using=db) + elif db_field.name in self.raw_id_fields: + kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.remote_field, self.admin_site, using=db) + elif db_field.name in self.radio_fields: + kwargs['widget'] = widgets.AdminRadioSelect(attrs={ + 'class': get_ul_class(self.radio_fields[db_field.name]), + }) + kwargs['empty_label'] = _('None') if db_field.blank else None if 'queryset' not in kwargs: queryset = self.get_field_queryset(db, db_field, request) @@ -241,9 +252,12 @@ def formfield_for_manytomany(self, db_field, request, **kwargs): return None db = kwargs.get('using') - if db_field.name in self.raw_id_fields: + autocomplete_fields = self.get_autocomplete_fields(request) + if db_field.name in autocomplete_fields: + kwargs['widget'] = AutocompleteSelectMultiple(db_field.remote_field, self.admin_site, using=db) + elif db_field.name in self.raw_id_fields: kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.remote_field, self.admin_site, using=db) - elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)): + elif db_field.name in [*self.filter_vertical, *self.filter_horizontal]: kwargs['widget'] = widgets.FilteredSelectMultiple( db_field.verbose_name, db_field.name in self.filter_vertical @@ -255,12 +269,20 @@ def formfield_for_manytomany(self, db_field, request, **kwargs): kwargs['queryset'] = queryset form_field = db_field.formfield(**kwargs) - if isinstance(form_field.widget, SelectMultiple) and not isinstance(form_field.widget, CheckboxSelectMultiple): + if (isinstance(form_field.widget, SelectMultiple) and + not isinstance(form_field.widget, (CheckboxSelectMultiple, AutocompleteSelectMultiple))): msg = _('Hold down "Control", or "Command" on a Mac, to select more than one.') help_text = form_field.help_text form_field.help_text = format_lazy('{} {}', help_text, msg) if help_text else msg return form_field + def get_autocomplete_fields(self, request): + """ + Return a list of ForeignKey and/or ManyToMany fields which should use + an autocomplete widget. + """ + return self.autocomplete_fields + def get_view_on_site_url(self, obj=None): if obj is None or not self.view_on_site: return None @@ -293,7 +315,11 @@ def get_fields(self, request, obj=None): """ Hook for specifying fields. """ - return self.fields + if self.fields: + return self.fields + # _get_form_for_get_fields() is implemented in subclasses. + form = self._get_form_for_get_fields(request, obj) + return [*form.base_fields, *self.get_readonly_fields(request, obj)] def get_fieldsets(self, request, obj=None): """ @@ -323,7 +349,7 @@ def get_prepopulated_fields(self, request, obj=None): def get_queryset(self, request): """ - Returns a QuerySet of all model instances that can be edited by the + Return a QuerySet of all model instances that can be edited by the admin site. This is used by changelist_view. """ qs = self.model._default_manager.get_queryset() @@ -333,6 +359,10 @@ def get_queryset(self, request): qs = qs.order_by(*ordering) return qs + def get_sortable_by(self, request): + """Hook for specifying which fields can be sorted in the changelist.""" + return self.sortable_by if self.sortable_by is not None else self.get_list_display(request) + def lookup_allowed(self, lookup, value): from django.contrib.admin.filters import SimpleListFilter @@ -344,9 +374,8 @@ def lookup_allowed(self, lookup, value): # As ``limit_choices_to`` can be a callable, invoke it here. if callable(fk_lookup): fk_lookup = fk_lookup() - for k, v in widgets.url_params_from_lookup_dict(fk_lookup).items(): - if k == lookup and v == value: - return True + if (lookup, value) in widgets.url_params_from_lookup_dict(fk_lookup).items(): + return True relation_parts = [] prev_field = None @@ -354,13 +383,13 @@ def lookup_allowed(self, lookup, value): try: field = model._meta.get_field(part) except FieldDoesNotExist: - # Lookups on non-existent fields are ok, since they're ignored + # Lookups on nonexistent fields are ok, since they're ignored # later. break # It is allowed to filter on values that would be found from local # model anyways. For example, if you filter on employee__department__id, # then the id value would be found already from employee__department_id. - if not prev_field or (prev_field.concrete and + if not prev_field or (prev_field.is_relation and field not in prev_field.get_path_info()[-1].target_fields): relation_parts.append(part) if not getattr(field, 'get_path_info', None): @@ -373,20 +402,24 @@ def lookup_allowed(self, lookup, value): if len(relation_parts) <= 1: # Either a local field filter, or no fields at all. return True - clean_lookup = LOOKUP_SEP.join(relation_parts) - valid_lookups = [self.date_hierarchy] + valid_lookups = {self.date_hierarchy} for filter_item in self.list_filter: if isinstance(filter_item, type) and issubclass(filter_item, SimpleListFilter): - valid_lookups.append(filter_item.parameter_name) + valid_lookups.add(filter_item.parameter_name) elif isinstance(filter_item, (list, tuple)): - valid_lookups.append(filter_item[0]) + valid_lookups.add(filter_item[0]) else: - valid_lookups.append(filter_item) - return clean_lookup in valid_lookups + valid_lookups.add(filter_item) + + # Is it a valid relational lookup? + return not { + LOOKUP_SEP.join(relation_parts), + LOOKUP_SEP.join(relation_parts + [part]) + }.isdisjoint(valid_lookups) def to_field_allowed(self, request, to_field): """ - Returns True if the model associated with this admin should be + Return True if the model associated with this admin should be allowed to be referenced by the specified field. """ opts = self.model._meta @@ -431,7 +464,7 @@ def to_field_allowed(self, request, to_field): def has_add_permission(self, request): """ - Returns True if the given request has permission to add an object. + Return True if the given request has permission to add an object. Can be overridden by the user in subclasses. """ opts = self.opts @@ -440,7 +473,7 @@ def has_add_permission(self, request): def has_change_permission(self, request, obj=None): """ - Returns True if the given request has permission to change the given + Return True if the given request has permission to change the given Django model instance, the default implementation doesn't examine the `obj` parameter. @@ -455,7 +488,7 @@ def has_change_permission(self, request, obj=None): def has_delete_permission(self, request, obj=None): """ - Returns True if the given request has permission to change the given + Return True if the given request has permission to change the given Django model instance, the default implementation doesn't examine the `obj` parameter. @@ -468,9 +501,31 @@ def has_delete_permission(self, request, obj=None): codename = get_permission_codename('delete', opts) return request.user.has_perm("%s.%s" % (opts.app_label, codename)) + def has_view_permission(self, request, obj=None): + """ + Return True if the given request has permission to view the given + Django model instance. The default implementation doesn't examine the + `obj` parameter. + + If overridden by the user in subclasses, it should return True if the + given request has permission to view the `obj` model instance. If `obj` + is None, it should return True if the request has permission to view + any object of the given type. + """ + opts = self.opts + codename_view = get_permission_codename('view', opts) + codename_change = get_permission_codename('change', opts) + return ( + request.user.has_perm('%s.%s' % (opts.app_label, codename_view)) or + request.user.has_perm('%s.%s' % (opts.app_label, codename_change)) + ) + + def has_view_or_change_permission(self, request, obj=None): + return self.has_view_permission(request, obj) or self.has_change_permission(request, obj) + def has_module_permission(self, request): """ - Returns True if the given request has any permission in the given + Return True if the given request has any permission in the given app label. Can be overridden by the user in subclasses. In such case it should @@ -482,9 +537,8 @@ def has_module_permission(self, request): return request.user.has_module_perms(self.opts.app_label) -@python_2_unicode_compatible class ModelAdmin(BaseModelAdmin): - "Encapsulates all admin options and functionality for a given model." + """Encapsulate all admin options and functionality for a given model.""" list_display = ('__str__',) list_display_links = () @@ -523,7 +577,7 @@ def __init__(self, model, admin_site): self.model = model self.opts = model._meta self.admin_site = admin_site - super(ModelAdmin, self).__init__() + super().__init__() def __str__(self): return "%s.%s" % (self.model._meta.app_label, self.__class__.__name__) @@ -533,18 +587,19 @@ def get_inline_instances(self, request, obj=None): for inline_class in self.inlines: inline = inline_class(self.model, self.admin_site) if request: - if not (inline.has_add_permission(request) or - inline.has_change_permission(request, obj) or + inline_has_add_permission = inline._has_add_permission(request, obj) + if not (inline.has_view_or_change_permission(request, obj) or + inline_has_add_permission or inline.has_delete_permission(request, obj)): continue - if not inline.has_add_permission(request): + if not inline_has_add_permission: inline.max_num = 0 inline_instances.append(inline) return inline_instances def get_urls(self): - from django.conf.urls import url + from django.urls import path def wrap(view): def wrapper(*args, **kwargs): @@ -555,13 +610,14 @@ def wrapper(*args, **kwargs): info = self.model._meta.app_label, self.model._meta.model_name urlpatterns = [ - url(r'^$', wrap(self.changelist_view), name='%s_%s_changelist' % info), - url(r'^add/$', wrap(self.add_view), name='%s_%s_add' % info), - url(r'^(.+)/history/$', wrap(self.history_view), name='%s_%s_history' % info), - url(r'^(.+)/delete/$', wrap(self.delete_view), name='%s_%s_delete' % info), - url(r'^(.+)/change/$', wrap(self.change_view), name='%s_%s_change' % info), + path('', wrap(self.changelist_view), name='%s_%s_changelist' % info), + path('add/', wrap(self.add_view), name='%s_%s_add' % info), + path('autocomplete/', wrap(self.autocomplete_view), name='%s_%s_autocomplete' % info), + path('/history/', wrap(self.history_view), name='%s_%s_history' % info), + path('/delete/', wrap(self.delete_view), name='%s_%s_delete' % info), + path('/change/', wrap(self.change_view), name='%s_%s_change' % info), # For backwards compatibility (was the change url before 1.9) - url(r'^(.+)/$', wrap(RedirectView.as_view( + path('/', wrap(RedirectView.as_view( pattern_name='%s:%s_%s_change' % ((self.admin_site.name,) + info) ))), ] @@ -575,9 +631,9 @@ def urls(self): def media(self): extra = '' if settings.DEBUG else '.min' js = [ - 'core.js', 'vendor/jquery/jquery%s.js' % extra, 'jquery.init.js', + 'core.js', 'admin/RelatedObjectLookups.js', 'actions%s.js' % extra, 'urlify.js', @@ -588,25 +644,23 @@ def media(self): def get_model_perms(self, request): """ - Returns a dict of all perms for this model. This dict has the keys - ``add``, ``change``, and ``delete`` mapping to the True/False for each - of those actions. + Return a dict of all perms for this model. This dict has the keys + ``add``, ``change``, ``delete``, and ``view`` mapping to the True/False + for each of those actions. """ return { 'add': self.has_add_permission(request), 'change': self.has_change_permission(request), 'delete': self.has_delete_permission(request), + 'view': self.has_view_permission(request), } - def get_fields(self, request, obj=None): - if self.fields: - return self.fields - form = self.get_form(request, obj, fields=None) - return list(form.base_fields) + list(self.get_readonly_fields(request, obj)) + def _get_form_for_get_fields(self, request, obj): + return self.get_form(request, obj, fields=None) - def get_form(self, request, obj=None, **kwargs): + def get_form(self, request, obj=None, change=False, **kwargs): """ - Returns a Form class for use in the admin add view. This is used by + Return a Form class for use in the admin add view. This is used by add_view and change_view. """ if 'fields' in kwargs: @@ -617,6 +671,10 @@ def get_form(self, request, obj=None, **kwargs): exclude = [] if excluded is None else list(excluded) readonly_fields = self.get_readonly_fields(request, obj) exclude.extend(readonly_fields) + # Exclude all fields if it's a change form and the user doesn't have + # the change permission. + if change and hasattr(request, 'user') and not self.has_change_permission(request, obj): + exclude.extend(fields) if excluded is None and hasattr(self.form, '_meta') and self.form._meta.exclude: # Take the custom ModelForm's Meta.exclude into account only if the # ModelAdmin doesn't define its own. @@ -626,19 +684,19 @@ def get_form(self, request, obj=None, **kwargs): exclude = exclude or None # Remove declared form fields which are in readonly_fields. - new_attrs = OrderedDict( - (f, None) for f in readonly_fields + new_attrs = OrderedDict.fromkeys( + f for f in readonly_fields if f in self.form.declared_fields ) form = type(self.form.__name__, (self.form,), new_attrs) defaults = { - "form": form, - "fields": fields, - "exclude": exclude, - "formfield_callback": partial(self.formfield_for_dbfield, request=request), + 'form': form, + 'fields': fields, + 'exclude': exclude, + 'formfield_callback': partial(self.formfield_for_dbfield, request=request), + **kwargs, } - defaults.update(kwargs) if defaults['fields'] is None and not modelform_defines_fields(defaults['form']): defaults['fields'] = forms.ALL_FIELDS @@ -653,15 +711,43 @@ def get_form(self, request, obj=None, **kwargs): def get_changelist(self, request, **kwargs): """ - Returns the ChangeList class for use on the changelist page. + Return the ChangeList class for use on the changelist page. """ from django.contrib.admin.views.main import ChangeList return ChangeList + def get_changelist_instance(self, request): + """ + Return a `ChangeList` instance based on `request`. May raise + `IncorrectLookupParameters`. + """ + list_display = self.get_list_display(request) + list_display_links = self.get_list_display_links(request, list_display) + # Add the action checkboxes if any actions are available. + if self.get_actions(request): + list_display = ['action_checkbox', *list_display] + sortable_by = self.get_sortable_by(request) + ChangeList = self.get_changelist(request) + return ChangeList( + request, + self.model, + list_display, + list_display_links, + self.get_list_filter(request), + self.date_hierarchy, + self.get_search_fields(request), + self.get_list_select_related(request), + self.list_per_page, + self.list_max_show_all, + self.list_editable, + self, + sortable_by, + ) + def get_object(self, request, object_id, from_field=None): """ - Returns an instance matching the field and value provided, the primary - key is used if no field is provided. Returns ``None`` if no match is + Return an instance matching the field and value provided, the primary + key is used if no field is provided. Return ``None`` if no match is found or the object_id fails validation. """ queryset = self.get_queryset(request) @@ -675,12 +761,12 @@ def get_object(self, request, object_id, from_field=None): def get_changelist_form(self, request, **kwargs): """ - Returns a Form class for use in the Formset on the changelist page. + Return a Form class for use in the Formset on the changelist page. """ defaults = { - "formfield_callback": partial(self.formfield_for_dbfield, request=request), + 'formfield_callback': partial(self.formfield_for_dbfield, request=request), + **kwargs, } - defaults.update(kwargs) if defaults.get('fields') is None and not modelform_defines_fields(defaults.get('form')): defaults['fields'] = forms.ALL_FIELDS @@ -688,13 +774,13 @@ def get_changelist_form(self, request, **kwargs): def get_changelist_formset(self, request, **kwargs): """ - Returns a FormSet class for use on the changelist page if list_editable + Return a FormSet class for use on the changelist page if list_editable is used. """ defaults = { - "formfield_callback": partial(self.formfield_for_dbfield, request=request), + 'formfield_callback': partial(self.formfield_for_dbfield, request=request), + **kwargs, } - defaults.update(kwargs) return modelformset_factory( self.model, self.get_changelist_form(request), extra=0, fields=self.list_editable, **defaults @@ -702,7 +788,7 @@ def get_changelist_formset(self, request, **kwargs): def get_formsets_with_inlines(self, request, obj=None): """ - Yields formsets and the corresponding inlines. + Yield formsets and the corresponding inlines. """ for inline in self.get_inline_instances(request, obj): yield inline.get_formset(request, obj), inline @@ -721,7 +807,7 @@ def log_addition(self, request, object, message): user_id=request.user.pk, content_type_id=get_content_type_for_model(object).pk, object_id=object.pk, - object_repr=force_text(object), + object_repr=str(object), action_flag=ADDITION, change_message=message, ) @@ -737,7 +823,7 @@ def log_change(self, request, object, message): user_id=request.user.pk, content_type_id=get_content_type_for_model(object).pk, object_id=object.pk, - object_repr=force_text(object), + object_repr=str(object), action_flag=CHANGE, change_message=message, ) @@ -762,53 +848,61 @@ def action_checkbox(self, obj): """ A list_display column containing a checkbox widget. """ - return helpers.checkbox.render(helpers.ACTION_CHECKBOX_NAME, force_text(obj.pk)) - action_checkbox.short_description = mark_safe('') - - def get_actions(self, request): - """ - Return a dictionary mapping the names of all actions for this - ModelAdmin to a tuple of (callable, name, description) for each action. - """ - # If self.actions is explicitly set to None that means that we don't - # want *any* actions enabled on this page. - if self.actions is None or IS_POPUP_VAR in request.GET: - return OrderedDict() + return helpers.checkbox.render(helpers.ACTION_CHECKBOX_NAME, str(obj.pk)) + action_checkbox.short_description = mark_safe('') + def _get_base_actions(self): + """Return the list of actions, prior to any request-based filtering.""" actions = [] # Gather actions from the admin site first for (name, func) in self.admin_site.actions: description = getattr(func, 'short_description', name.replace('_', ' ')) actions.append((func, name, description)) - - # Then gather them from the model admin and all parent classes, - # starting with self and working back up. - for klass in self.__class__.mro()[::-1]: - class_actions = getattr(klass, 'actions', []) - # Avoid trying to iterate over None - if not class_actions: - continue - actions.extend(self.get_action(action) for action in class_actions) - + # Add actions from this ModelAdmin. + actions.extend(self.get_action(action) for action in self.actions or []) # get_action might have returned None, so filter any of those out. - actions = filter(None, actions) + return filter(None, actions) + + def _filter_actions_by_permissions(self, request, actions): + """Filter out any actions that the user doesn't have access to.""" + filtered_actions = [] + for action in actions: + callable = action[0] + if not hasattr(callable, 'allowed_permissions'): + filtered_actions.append(action) + continue + permission_checks = ( + getattr(self, 'has_%s_permission' % permission) + for permission in callable.allowed_permissions + ) + if any(has_permission(request) for has_permission in permission_checks): + filtered_actions.append(action) + return filtered_actions + def get_actions(self, request): + """ + Return a dictionary mapping the names of all actions for this + ModelAdmin to a tuple of (callable, name, description) for each action. + """ + # If self.actions is set to None that means actions are disabled on + # this page. + if self.actions is None or IS_POPUP_VAR in request.GET: + return OrderedDict() + actions = self._filter_actions_by_permissions(request, self._get_base_actions()) # Convert the actions into an OrderedDict keyed by name. - actions = OrderedDict( + return OrderedDict( (name, (func, name, desc)) for func, name, desc in actions ) - return actions - def get_action_choices(self, request, default_choices=BLANK_CHOICE_DASH): """ Return a list of choices for use in a form object. Each choice is a tuple (name, description). """ choices = [] + default_choices - for func, name, description in six.itervalues(self.get_actions(request)): + for func, name, description in self.get_actions(request).values(): choice = (name, description % model_format_dict(self.opts)) choices.append(choice) return choices @@ -864,28 +958,28 @@ def get_list_display_links(self, request, list_display): def get_list_filter(self, request): """ - Returns a sequence containing the fields to be displayed as filters in + Return a sequence containing the fields to be displayed as filters in the right sidebar of the changelist page. """ return self.list_filter def get_list_select_related(self, request): """ - Returns a list of fields to add to the select_related() part of the + Return a list of fields to add to the select_related() part of the changelist items query. """ return self.list_select_related def get_search_fields(self, request): """ - Returns a sequence containing the fields to be searched whenever + Return a sequence containing the fields to be searched whenever somebody submits a search query. """ return self.search_fields def get_search_results(self, request, queryset, search_term): """ - Returns a tuple containing a queryset to implement the search, + Return a tuple containing a queryset to implement the search and a boolean indicating if the results may contain duplicates. """ # Apply keyword searches. @@ -896,8 +990,27 @@ def construct_search(field_name): return "%s__iexact" % field_name[1:] elif field_name.startswith('@'): return "%s__search" % field_name[1:] - else: - return "%s__icontains" % field_name + # Use field_name if it includes a lookup. + opts = queryset.model._meta + lookup_fields = field_name.split(LOOKUP_SEP) + # Go through the fields, following all relations. + prev_field = None + for path_part in lookup_fields: + if path_part == 'pk': + path_part = opts.pk.name + try: + field = opts.get_field(path_part) + except FieldDoesNotExist: + # Use valid query lookups. + if prev_field and prev_field.get_lookup(path_part): + return field_name + else: + prev_field = field + if hasattr(field, 'get_path_info'): + # Update opts to follow the relation. + opts = field.get_path_info()[-1].to_opts + # Otherwise, use the field with icontains. + return "%s__icontains" % field_name use_distinct = False search_fields = self.get_search_fields(request) @@ -908,17 +1021,13 @@ def construct_search(field_name): or_queries = [models.Q(**{orm_lookup: bit}) for orm_lookup in orm_lookups] queryset = queryset.filter(reduce(operator.or_, or_queries)) - if not use_distinct: - for search_spec in orm_lookups: - if lookup_needs_distinct(self.opts, search_spec): - use_distinct = True - break + use_distinct |= any(lookup_needs_distinct(self.opts, search_spec) for search_spec in orm_lookups) return queryset, use_distinct def get_preserved_filters(self, request): """ - Returns the preserved filters querystring. + Return the preserved filters querystring. """ match = request.resolver_match if self.preserve_filters and match: @@ -957,7 +1066,7 @@ def message_user(self, request, message, level=messages.INFO, extra_tags='', level = getattr(messages.constants, level.upper()) except AttributeError: levels = messages.constants.DEFAULT_TAGS.values() - levels_repr = ', '.join('`%s`' % l for l in levels) + levels_repr = ', '.join('`%s`' % level for level in levels) raise ValueError( 'Bad message level string: `%s`. Possible values are: %s' % (level, levels_repr) @@ -984,6 +1093,10 @@ def delete_model(self, request, obj): """ obj.delete() + def delete_queryset(self, request, queryset): + """Given a queryset, delete it from the database.""" + queryset.delete() + def save_formset(self, request, form, formset, change): """ Given an inline formset save it to the database. @@ -1008,13 +1121,23 @@ def render_change_form(self, request, context, add=False, change=False, form_url preserved_filters = self.get_preserved_filters(request) form_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, form_url) view_on_site_url = self.get_view_on_site_url(obj) + has_editable_inline_admin_formsets = False + for inline in context['inline_admin_formsets']: + if inline.has_add_permission or inline.has_change_permission or inline.has_delete_permission: + has_editable_inline_admin_formsets = True + break context.update({ 'add': add, 'change': change, + 'has_view_permission': self.has_view_permission(request, obj), 'has_add_permission': self.has_add_permission(request), 'has_change_permission': self.has_change_permission(request, obj), 'has_delete_permission': self.has_delete_permission(request, obj), - 'has_file_field': True, # FIXME - this should check if form or formsets have a FileField, + 'has_editable_inline_admin_formsets': has_editable_inline_admin_formsets, + 'has_file_field': context['adminform'].form.is_multipart() or any( + admin_formset.formset.form().is_multipart() + for admin_formset in context['inline_admin_formsets'] + ), 'has_absolute_url': view_on_site_url is not None, 'absolute_url': view_on_site_url, 'form_url': form_url, @@ -1041,23 +1164,22 @@ def render_change_form(self, request, context, add=False, change=False, form_url def response_add(self, request, obj, post_url_continue=None): """ - Determines the HttpResponse for the add_view stage. + Determine the HttpResponse for the add_view stage. """ opts = obj._meta - pk_value = obj._get_pk_val() preserved_filters = self.get_preserved_filters(request) obj_url = reverse( 'admin:%s_%s_change' % (opts.app_label, opts.model_name), - args=(quote(pk_value),), + args=(quote(obj.pk),), current_app=self.admin_site.name, ) # Add a link to the object's change form if the user can edit the obj. if self.has_change_permission(request, obj): obj_repr = format_html('{}', urlquote(obj_url), obj) else: - obj_repr = force_text(obj) + obj_repr = str(obj) msg_dict = { - 'name': force_text(opts.verbose_name), + 'name': opts.verbose_name, 'obj': obj_repr, } # Here, we distinguish between different save types by checking for @@ -1071,8 +1193,8 @@ def response_add(self, request, obj, post_url_continue=None): attr = obj._meta.pk.attname value = obj.serializable_value(attr) popup_response_data = json.dumps({ - 'value': six.text_type(value), - 'obj': six.text_type(obj), + 'value': str(value), + 'obj': str(obj), }) return TemplateResponse(request, self.popup_response_template or [ 'admin/%s/%s/popup_response.html' % (opts.app_label, opts.model_name), @@ -1087,11 +1209,10 @@ def response_add(self, request, obj, post_url_continue=None): "_saveasnew" in request.POST and self.save_as_continue and self.has_change_permission(request, obj) ): - msg = format_html( - _('The {name} "{obj}" was added successfully. You may edit it again below.'), - **msg_dict - ) - self.message_user(request, msg, messages.SUCCESS) + msg = _('The {name} "{obj}" was added successfully.') + if self.has_change_permission(request, obj): + msg += ' ' + _('You may edit it again below.') + self.message_user(request, format_html(msg, **msg_dict), messages.SUCCESS) if post_url_continue is None: post_url_continue = obj_url post_url_continue = add_preserved_filters( @@ -1120,21 +1241,20 @@ def response_add(self, request, obj, post_url_continue=None): def response_change(self, request, obj): """ - Determines the HttpResponse for the change_view stage. + Determine the HttpResponse for the change_view stage. """ if IS_POPUP_VAR in request.POST: opts = obj._meta to_field = request.POST.get(TO_FIELD_VAR) attr = str(to_field) if to_field else opts.pk.attname - # Retrieve the `object_id` from the resolved pattern arguments. - value = request.resolver_match.args[0] + value = request.resolver_match.kwargs['object_id'] new_value = obj.serializable_value(attr) popup_response_data = json.dumps({ 'action': 'change', - 'value': six.text_type(value), - 'obj': six.text_type(obj), - 'new_value': six.text_type(new_value), + 'value': str(value), + 'obj': str(obj), + 'new_value': str(new_value), }) return TemplateResponse(request, self.popup_response_template or [ 'admin/%s/%s/popup_response.html' % (opts.app_label, opts.model_name), @@ -1145,11 +1265,10 @@ def response_change(self, request, obj): }) opts = self.model._meta - pk_value = obj._get_pk_val() preserved_filters = self.get_preserved_filters(request) msg_dict = { - 'name': force_text(opts.verbose_name), + 'name': opts.verbose_name, 'obj': format_html('{}', urlquote(request.path), obj), } if "_continue" in request.POST: @@ -1170,7 +1289,7 @@ def response_change(self, request, obj): self.message_user(request, msg, messages.SUCCESS) redirect_url = reverse('admin:%s_%s_change' % (opts.app_label, opts.model_name), - args=(pk_value,), + args=(obj.pk,), current_app=self.admin_site.name) redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url) return HttpResponseRedirect(redirect_url) @@ -1195,13 +1314,9 @@ def response_change(self, request, obj): self.message_user(request, msg, messages.SUCCESS) return self.response_post_save_change(request, obj) - def response_post_save_add(self, request, obj): - """ - Figure out where to redirect after the 'Save' button has been pressed - when adding a new object. - """ + def _response_post_save(self, request, obj): opts = self.model._meta - if self.has_change_permission(request, None): + if self.has_view_or_change_permission(request): post_url = reverse('admin:%s_%s_changelist' % (opts.app_label, opts.model_name), current_app=self.admin_site.name) @@ -1212,23 +1327,19 @@ def response_post_save_add(self, request, obj): current_app=self.admin_site.name) return HttpResponseRedirect(post_url) + def response_post_save_add(self, request, obj): + """ + Figure out where to redirect after the 'Save' button has been pressed + when adding a new object. + """ + return self._response_post_save(request, obj) + def response_post_save_change(self, request, obj): """ Figure out where to redirect after the 'Save' button has been pressed when editing an existing object. """ - opts = self.model._meta - - if self.has_change_permission(request, None): - post_url = reverse('admin:%s_%s_changelist' % - (opts.app_label, opts.model_name), - current_app=self.admin_site.name) - preserved_filters = self.get_preserved_filters(request) - post_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, post_url) - else: - post_url = reverse('admin:index', - current_app=self.admin_site.name) - return HttpResponseRedirect(post_url) + return self._response_post_save(request, obj) def response_action(self, request, queryset): """ @@ -1299,9 +1410,8 @@ def response_action(self, request, queryset): def response_delete(self, request, obj_display, obj_id): """ - Determines the HttpResponse for the delete_view stage. + Determine the HttpResponse for the delete_view stage. """ - opts = self.model._meta if IS_POPUP_VAR in request.POST: @@ -1320,8 +1430,8 @@ def response_delete(self, request, obj_display, obj_id): self.message_user( request, _('The %(name)s "%(obj)s" was deleted successfully.') % { - 'name': force_text(opts.verbose_name), - 'obj': force_text(obj_display), + 'name': opts.verbose_name, + 'obj': obj_display, }, messages.SUCCESS, ) @@ -1361,22 +1471,33 @@ def render_delete_form(self, request, context): ) def get_inline_formsets(self, request, formsets, inline_instances, obj=None): + # Edit permissions on parent model are required for editable inlines. + can_edit_parent = self.has_change_permission(request, obj) if obj else self.has_add_permission(request) inline_admin_formsets = [] for inline, formset in zip(inline_instances, formsets): fieldsets = list(inline.get_fieldsets(request, obj)) readonly = list(inline.get_readonly_fields(request, obj)) + if can_edit_parent: + has_add_permission = inline._has_add_permission(request, obj) + has_change_permission = inline.has_change_permission(request, obj) + has_delete_permission = inline.has_delete_permission(request, obj) + else: + # Disable all edit-permissions, and overide formset settings. + has_add_permission = has_change_permission = has_delete_permission = False + formset.extra = formset.max_num = 0 + has_view_permission = inline.has_view_permission(request, obj) prepopulated = dict(inline.get_prepopulated_fields(request, obj)) inline_admin_formset = helpers.InlineAdminFormSet( - inline, formset, fieldsets, prepopulated, readonly, - model_admin=self, + inline, formset, fieldsets, prepopulated, readonly, model_admin=self, + has_add_permission=has_add_permission, has_change_permission=has_change_permission, + has_delete_permission=has_delete_permission, has_view_permission=has_view_permission, ) inline_admin_formsets.append(inline_admin_formset) return inline_admin_formsets def get_changeform_initial_data(self, request): """ - Get the initial form data. - Unless overridden, this populates from the GET params. + Get the initial form data from the request's GET params. """ initial = dict(request.GET.items()) for k in initial: @@ -1395,7 +1516,7 @@ def _get_obj_does_not_exist_redirect(self, request, opts, object_id): and return a redirect to the admin index page. """ msg = _("""%(name)s with ID "%(key)s" doesn't exist. Perhaps it was deleted?""") % { - 'name': force_text(opts.verbose_name), + 'name': opts.verbose_name, 'key': unquote(object_id), } self.message_user(request, msg, messages.WARNING) @@ -1428,20 +1549,23 @@ def _changeform_view(self, request, object_id, form_url, extra_context): else: obj = self.get_object(request, unquote(object_id), to_field) - if not self.has_change_permission(request, obj): - raise PermissionDenied + if request.method == 'POST': + if not self.has_change_permission(request, obj): + raise PermissionDenied + else: + if not self.has_view_or_change_permission(request, obj): + raise PermissionDenied if obj is None: return self._get_obj_does_not_exist_redirect(request, opts, object_id) - ModelForm = self.get_form(request, obj) + ModelForm = self.get_form(request, obj, change=not add) if request.method == 'POST': form = ModelForm(request.POST, request.FILES, instance=obj) - if form.is_valid(): - form_validated = True + form_validated = form.is_valid() + if form_validated: new_object = self.save_form(request, form, change=not add) else: - form_validated = False new_object = form.instance formsets, inline_instances = self._create_formsets(request, new_object, change=not add) if all_valid(formsets) and form_validated: @@ -1465,11 +1589,16 @@ def _changeform_view(self, request, object_id, form_url, extra_context): form = ModelForm(instance=obj) formsets, inline_instances = self._create_formsets(request, obj, change=True) + if not add and not self.has_change_permission(request, obj): + readonly_fields = flatten_fieldsets(self.get_fieldsets(request, obj)) + else: + readonly_fields = self.get_readonly_fields(request, obj) adminForm = helpers.AdminForm( form, list(self.get_fieldsets(request, obj)), - self.get_prepopulated_fields(request, obj), - self.get_readonly_fields(request, obj), + # Clear prepopulated fields on a view-only form to avoid a crash. + self.get_prepopulated_fields(request, obj) if add or self.has_change_permission(request, obj) else {}, + readonly_fields, model_admin=self) media = self.media + adminForm.media @@ -1477,20 +1606,25 @@ def _changeform_view(self, request, object_id, form_url, extra_context): for inline_formset in inline_formsets: media = media + inline_formset.media - context = dict( - self.admin_site.each_context(request), - title=(_('Add %s') if add else _('Change %s')) % force_text(opts.verbose_name), - adminform=adminForm, - object_id=object_id, - original=obj, - is_popup=(IS_POPUP_VAR in request.POST or - IS_POPUP_VAR in request.GET), - to_field=to_field, - media=media, - inline_admin_formsets=inline_formsets, - errors=helpers.AdminErrorList(form, formsets), - preserved_filters=self.get_preserved_filters(request), - ) + if add: + title = _('Add %s') + elif self.has_change_permission(request, obj): + title = _('Change %s') + else: + title = _('View %s') + context = { + **self.admin_site.each_context(request), + 'title': title % opts.verbose_name, + 'adminform': adminForm, + 'object_id': object_id, + 'original': obj, + 'is_popup': IS_POPUP_VAR in request.POST or IS_POPUP_VAR in request.GET, + 'to_field': to_field, + 'media': media, + 'inline_admin_formsets': inline_formsets, + 'errors': helpers.AdminErrorList(form, formsets), + 'preserved_filters': self.get_preserved_filters(request), + } # Hide the "Save" and "Save and continue" buttons if "Save as New" was # previously chosen to prevent the interface from getting confusing. @@ -1504,12 +1638,38 @@ def _changeform_view(self, request, object_id, form_url, extra_context): return self.render_change_form(request, context, add=add, change=not add, obj=obj, form_url=form_url) + def autocomplete_view(self, request): + return AutocompleteJsonView.as_view(model_admin=self)(request) + def add_view(self, request, form_url='', extra_context=None): return self.changeform_view(request, None, form_url, extra_context) def change_view(self, request, object_id, form_url='', extra_context=None): return self.changeform_view(request, object_id, form_url, extra_context) + def _get_edited_object_pks(self, request, prefix): + """Return POST data values of list_editable primary keys.""" + pk_pattern = re.compile( + r'{}-\d+-{}$'.format(re.escape(prefix), self.model._meta.pk.name) + ) + return [value for key, value in request.POST.items() if pk_pattern.match(key)] + + def _get_list_editable_queryset(self, request, prefix): + """ + Based on POST data, return a queryset of the objects that were edited + via list_editable. + """ + object_pks = self._get_edited_object_pks(request, prefix) + queryset = self.get_queryset(request) + validate = queryset.model._meta.pk.to_python + try: + for pk in object_pks: + validate(pk) + except ValidationError: + # Disable the optimization if the POST data was tampered with. + return queryset + return queryset.filter(pk__in=object_pks) + @csrf_protect_m def changelist_view(self, request, extra_context=None): """ @@ -1518,29 +1678,11 @@ def changelist_view(self, request, extra_context=None): from django.contrib.admin.views.main import ERROR_FLAG opts = self.model._meta app_label = opts.app_label - if not self.has_change_permission(request, None): + if not self.has_view_or_change_permission(request): raise PermissionDenied - list_display = self.get_list_display(request) - list_display_links = self.get_list_display_links(request, list_display) - list_filter = self.get_list_filter(request) - search_fields = self.get_search_fields(request) - list_select_related = self.get_list_select_related(request) - - # Check actions to see if any are available on this changelist - actions = self.get_actions(request) - if actions: - # Add the action checkboxes if there are any actions available. - list_display = ['action_checkbox'] + list(list_display) - - ChangeList = self.get_changelist(request) try: - cl = ChangeList( - request, self.model, list_display, - list_display_links, list_filter, self.date_hierarchy, - search_fields, list_select_related, self.list_per_page, - self.list_max_show_all, self.list_editable, self, - ) + cl = self.get_changelist_instance(request) except IncorrectLookupParameters: # Wacky lookup parameters were given, so redirect to the main # changelist page, without parameters, and pass an 'invalid=1' @@ -1548,7 +1690,7 @@ def changelist_view(self, request, extra_context=None): # and the 'invalid=1' parameter was already in the query string, # something is screwed up with the database, so display an error # page. - if ERROR_FLAG in request.GET.keys(): + if ERROR_FLAG in request.GET: return SimpleTemplateResponse('admin/invalid_setup.html', { 'title': _('Database error'), }) @@ -1561,6 +1703,7 @@ def changelist_view(self, request, extra_context=None): action_failed = False selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME) + actions = self.get_actions(request) # Actions with no confirmation if (actions and request.method == 'POST' and 'index' in request.POST and '_save' not in request.POST): @@ -1600,8 +1743,11 @@ def changelist_view(self, request, extra_context=None): # Handle POSTed bulk-edit data. if request.method == 'POST' and cl.list_editable and '_save' in request.POST: + if not self.has_change_permission(request): + raise PermissionDenied FormSet = self.get_changelist_formset(request) - formset = cl.formset = FormSet(request.POST, request.FILES, queryset=self.get_queryset(request)) + modified_objects = self._get_list_editable_queryset(request, FormSet.get_default_prefix()) + formset = cl.formset = FormSet(request.POST, request.FILES, queryset=modified_objects) if formset.is_valid(): changecount = 0 for form in formset.forms: @@ -1614,25 +1760,20 @@ def changelist_view(self, request, extra_context=None): changecount += 1 if changecount: - if changecount == 1: - name = force_text(opts.verbose_name) - else: - name = force_text(opts.verbose_name_plural) - msg = ungettext( + msg = ngettext( "%(count)s %(name)s was changed successfully.", "%(count)s %(name)s were changed successfully.", changecount ) % { 'count': changecount, - 'name': name, - 'obj': force_text(obj), + 'name': model_ngettext(opts, changecount), } self.message_user(request, msg, messages.SUCCESS) return HttpResponseRedirect(request.get_full_path()) # Handle GET -- construct a formset for display. - elif cl.list_editable: + elif cl.list_editable and self.has_change_permission(request): FormSet = self.get_changelist_formset(request) formset = cl.formset = FormSet(queryset=cl.result_list) @@ -1650,31 +1791,31 @@ def changelist_view(self, request, extra_context=None): else: action_form = None - selection_note_all = ungettext( + selection_note_all = ngettext( '%(total_count)s selected', 'All %(total_count)s selected', cl.result_count ) - context = dict( - self.admin_site.each_context(request), - module_name=force_text(opts.verbose_name_plural), - selection_note=_('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)}, - selection_note_all=selection_note_all % {'total_count': cl.result_count}, - title=cl.title, - is_popup=cl.is_popup, - to_field=cl.to_field, - cl=cl, - media=media, - has_add_permission=self.has_add_permission(request), - opts=cl.opts, - action_form=action_form, - actions_on_top=self.actions_on_top, - actions_on_bottom=self.actions_on_bottom, - actions_selection_counter=self.actions_selection_counter, - preserved_filters=self.get_preserved_filters(request), - ) - context.update(extra_context or {}) + context = { + **self.admin_site.each_context(request), + 'module_name': str(opts.verbose_name_plural), + 'selection_note': _('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)}, + 'selection_note_all': selection_note_all % {'total_count': cl.result_count}, + 'title': cl.title, + 'is_popup': cl.is_popup, + 'to_field': cl.to_field, + 'cl': cl, + 'media': media, + 'has_add_permission': self.has_add_permission(request), + 'opts': cl.opts, + 'action_form': action_form, + 'actions_on_top': self.actions_on_top, + 'actions_on_bottom': self.actions_on_bottom, + 'actions_selection_counter': self.actions_selection_counter, + 'preserved_filters': self.get_preserved_filters(request), + **(extra_context or {}), + } request.current_app = self.admin_site.name @@ -1684,6 +1825,13 @@ def changelist_view(self, request, extra_context=None): 'admin/change_list.html' ], context) + def get_deleted_objects(self, objs, request): + """ + Hook for customizing the delete process for the delete view and the + "delete selected" action. + """ + return get_deleted_objects(objs, request, self.admin_site) + @csrf_protect_m def delete_view(self, request, object_id, extra_context=None): with transaction.atomic(using=router.db_for_write(self.model)): @@ -1706,17 +1854,14 @@ def _delete_view(self, request, object_id, extra_context): if obj is None: return self._get_obj_does_not_exist_redirect(request, opts, object_id) - using = router.db_for_write(self.model) - # Populate deleted_objects, a data structure of all related objects that # will also be deleted. - (deleted_objects, model_count, perms_needed, protected) = get_deleted_objects( - [obj], opts, request.user, self.admin_site, using) + deleted_objects, model_count, perms_needed, protected = self.get_deleted_objects([obj], request) if request.POST and not protected: # The user has confirmed the deletion. if perms_needed: raise PermissionDenied - obj_display = force_text(obj) + obj_display = str(obj) attr = str(to_field) if to_field else opts.pk.attname obj_id = obj.serializable_value(attr) self.log_deletion(request, obj, obj_display) @@ -1724,30 +1869,29 @@ def _delete_view(self, request, object_id, extra_context): return self.response_delete(request, obj_display, obj_id) - object_name = force_text(opts.verbose_name) + object_name = str(opts.verbose_name) if perms_needed or protected: title = _("Cannot delete %(name)s") % {"name": object_name} else: title = _("Are you sure?") - context = dict( - self.admin_site.each_context(request), - title=title, - object_name=object_name, - object=obj, - deleted_objects=deleted_objects, - model_count=dict(model_count).items(), - perms_lacking=perms_needed, - protected=protected, - opts=opts, - app_label=app_label, - preserved_filters=self.get_preserved_filters(request), - is_popup=(IS_POPUP_VAR in request.POST or - IS_POPUP_VAR in request.GET), - to_field=to_field, - ) - context.update(extra_context or {}) + context = { + **self.admin_site.each_context(request), + 'title': title, + 'object_name': object_name, + 'object': obj, + 'deleted_objects': deleted_objects, + 'model_count': dict(model_count).items(), + 'perms_lacking': perms_needed, + 'protected': protected, + 'opts': opts, + 'app_label': app_label, + 'preserved_filters': self.get_preserved_filters(request), + 'is_popup': IS_POPUP_VAR in request.POST or IS_POPUP_VAR in request.GET, + 'to_field': to_field, + **(extra_context or {}), + } return self.render_delete_form(request, context) @@ -1760,7 +1904,7 @@ def history_view(self, request, object_id, extra_context=None): if obj is None: return self._get_obj_does_not_exist_redirect(request, model._meta, object_id) - if not self.has_change_permission(request, obj): + if not self.has_view_or_change_permission(request, obj): raise PermissionDenied # Then get the history for this object. @@ -1771,16 +1915,16 @@ def history_view(self, request, object_id, extra_context=None): content_type=get_content_type_for_model(model) ).select_related().order_by('action_time') - context = dict( - self.admin_site.each_context(request), - title=_('Change history: %s') % force_text(obj), - action_list=action_list, - module_name=capfirst(force_text(opts.verbose_name_plural)), - object=obj, - opts=opts, - preserved_filters=self.get_preserved_filters(request), - ) - context.update(extra_context or {}) + context = { + **self.admin_site.each_context(request), + 'title': _('Change history: %s') % obj, + 'action_list': action_list, + 'module_name': str(capfirst(opts.verbose_name_plural)), + 'object': obj, + 'opts': opts, + 'preserved_filters': self.get_preserved_filters(request), + **(extra_context or {}), + } request.current_app = self.admin_site.name @@ -1814,7 +1958,24 @@ def _create_formsets(self, request, obj, change): 'files': request.FILES, 'save_as_new': '_saveasnew' in request.POST }) - formsets.append(FormSet(**formset_params)) + formset = FormSet(**formset_params) + + def user_deleted_form(request, obj, formset, index): + """Return whether or not the user deleted the form.""" + return ( + inline.has_delete_permission(request, obj) and + '{}-{}-DELETE'.format(formset.prefix, index) in request.POST + ) + + # Bypass validation of each view-only inline form (since the form's + # data won't be in request.POST), unless the form was deleted. + if not inline.has_change_permission(request, obj if change else None): + for index, form in enumerate(formset.initial_forms): + if user_deleted_form(request, obj, formset, index): + continue + form._errors = {} + form.cleaned_data = form.initial + formsets.append(formset) inline_instances.append(inline) return formsets, inline_instances @@ -1846,7 +2007,7 @@ def __init__(self, parent_model, admin_site): self.parent_model = parent_model self.opts = self.model._meta self.has_registered_model = admin_site.is_registered(self.model) - super(InlineModelAdmin, self).__init__() + super().__init__() if self.verbose_name is None: self.verbose_name = self.model._meta.verbose_name if self.verbose_name_plural is None: @@ -1863,6 +2024,11 @@ def media(self): js.append('collapse%s.js' % extra) return forms.Media(js=['admin/js/%s' % url for url in js]) + def _has_add_permission(self, request, obj): + # RemovedInDjango30Warning: obj will be a required argument. + args = get_func_args(self.has_add_permission) + return self.has_add_permission(request, obj) if 'obj' in args else self.has_add_permission(request) + def get_extra(self, request, obj=None, **kwargs): """Hook for customizing the number of extra inline forms.""" return self.extra @@ -1876,7 +2042,7 @@ def get_max_num(self, request, obj=None, **kwargs): return self.max_num def get_formset(self, request, obj=None, **kwargs): - """Returns a BaseInlineFormSet class for use in admin add/change views.""" + """Return a BaseInlineFormSet class for use in admin add/change views.""" if 'fields' in kwargs: fields = kwargs.pop('fields') else: @@ -1893,22 +2059,25 @@ def get_formset(self, request, obj=None, **kwargs): exclude = exclude or None can_delete = self.can_delete and self.has_delete_permission(request, obj) defaults = { - "form": self.form, - "formset": self.formset, - "fk_name": self.fk_name, - "fields": fields, - "exclude": exclude, - "formfield_callback": partial(self.formfield_for_dbfield, request=request), - "extra": self.get_extra(request, obj, **kwargs), - "min_num": self.get_min_num(request, obj, **kwargs), - "max_num": self.get_max_num(request, obj, **kwargs), - "can_delete": can_delete, + 'form': self.form, + 'formset': self.formset, + 'fk_name': self.fk_name, + 'fields': fields, + 'exclude': exclude, + 'formfield_callback': partial(self.formfield_for_dbfield, request=request), + 'extra': self.get_extra(request, obj, **kwargs), + 'min_num': self.get_min_num(request, obj, **kwargs), + 'max_num': self.get_max_num(request, obj, **kwargs), + 'can_delete': can_delete, + **kwargs, } - defaults.update(kwargs) base_model_form = defaults['form'] + can_change = self.has_change_permission(request, obj) if request else True + can_add = self._has_add_permission(request, obj) if request else True class DeleteProtectedModelForm(base_model_form): + def hand_clean_DELETE(self): """ We don't validate the 'DELETE' field itself because on @@ -1918,7 +2087,7 @@ def hand_clean_DELETE(self): if self.cleaned_data.get(DELETION_FIELD_NAME, False): using = router.db_for_write(self._meta.model) collector = NestedObjects(using=using) - if self.instance.pk is None: + if self.instance._state.adding: return collector.collect([self.instance]) if collector.protected: @@ -1931,19 +2100,29 @@ def hand_clean_DELETE(self): 'class_name': p._meta.verbose_name, 'instance': p} ) - params = {'class_name': self._meta.model._meta.verbose_name, - 'instance': self.instance, - 'related_objects': get_text_list(objs, _('and'))} + params = { + 'class_name': self._meta.model._meta.verbose_name, + 'instance': self.instance, + 'related_objects': get_text_list(objs, _('and')), + } msg = _("Deleting %(class_name)s %(instance)s would require " "deleting the following protected related objects: " "%(related_objects)s") raise ValidationError(msg, code='deleting_protected', params=params) def is_valid(self): - result = super(DeleteProtectedModelForm, self).is_valid() + result = super().is_valid() self.hand_clean_DELETE() return result + def has_changed(self): + # Protect against unauthorized edits. + if not can_change and not self.instance._state.adding: + return False + if not can_add and self.instance._state.adding: + return False + return super().has_changed() + defaults['form'] = DeleteProtectedModelForm if defaults['fields'] is None and not modelform_defines_fields(defaults['form']): @@ -1951,47 +2130,61 @@ def is_valid(self): return inlineformset_factory(self.parent_model, self.model, **defaults) - def get_fields(self, request, obj=None): - if self.fields: - return self.fields - form = self.get_formset(request, obj, fields=None).form - return list(form.base_fields) + list(self.get_readonly_fields(request, obj)) + def _get_form_for_get_fields(self, request, obj=None): + return self.get_formset(request, obj, fields=None).form def get_queryset(self, request): - queryset = super(InlineModelAdmin, self).get_queryset(request) - if not self.has_change_permission(request): + queryset = super().get_queryset(request) + if not self.has_view_or_change_permission(request): queryset = queryset.none() return queryset - def has_add_permission(self, request): + def _has_any_perms_for_target_model(self, request, perms): + """ + This method is called only when the ModelAdmin's model is for an + ManyToManyField's implicit through model (if self.opts.auto_created). + Return True if the user has any of the given permissions ('add', + 'change', etc.) for the model that points to the through model. + """ + opts = self.opts + # Find the target model of an auto-created many-to-many relationship. + for field in opts.fields: + if field.remote_field and field.remote_field.model != self.parent_model: + opts = field.remote_field.model._meta + break + return any( + request.user.has_perm('%s.%s' % (opts.app_label, get_permission_codename(perm, opts))) + for perm in perms + ) + + def has_add_permission(self, request, obj=None): + # RemovedInDjango30Warning: obj becomes a mandatory argument. if self.opts.auto_created: - # We're checking the rights to an auto-created intermediate model, - # which doesn't have its own individual permissions. The user needs - # to have the change permission for the related model in order to - # be able to do anything with the intermediate model. - return self.has_change_permission(request) - return super(InlineModelAdmin, self).has_add_permission(request) + # Auto-created intermediate models don't have their own + # permissions. The user needs to have the change permission for the + # related model in order to be able to do anything with the + # intermediate model. + return self._has_any_perms_for_target_model(request, ['change']) + return super().has_add_permission(request) def has_change_permission(self, request, obj=None): - opts = self.opts - if opts.auto_created: - # The model was auto-created as intermediary for a - # ManyToMany-relationship, find the target model - for field in opts.fields: - if field.remote_field and field.remote_field.model != self.parent_model: - opts = field.remote_field.model._meta - break - codename = get_permission_codename('change', opts) - return request.user.has_perm("%s.%s" % (opts.app_label, codename)) + if self.opts.auto_created: + # Same comment as has_add_permission(). + return self._has_any_perms_for_target_model(request, ['change']) + return super().has_change_permission(request) def has_delete_permission(self, request, obj=None): if self.opts.auto_created: - # We're checking the rights to an auto-created intermediate model, - # which doesn't have its own individual permissions. The user needs - # to have the change permission for the related model in order to - # be able to do anything with the intermediate model. - return self.has_change_permission(request, obj) - return super(InlineModelAdmin, self).has_delete_permission(request, obj) + # Same comment as has_add_permission(). + return self._has_any_perms_for_target_model(request, ['change']) + return super().has_delete_permission(request, obj) + + def has_view_permission(self, request, obj=None): + if self.opts.auto_created: + # Same comment as has_add_permission(). The 'change' permission + # also implies the 'view' permission. + return self._has_any_perms_for_target_model(request, ['view', 'change']) + return super().has_view_permission(request) class StackedInline(InlineModelAdmin): diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py index b3297d04f6f3..6842f49684a2 100644 --- a/django/contrib/admin/sites.py +++ b/django/contrib/admin/sites.py @@ -9,9 +9,10 @@ from django.http import Http404, HttpResponseRedirect from django.template.response import TemplateResponse from django.urls import NoReverseMatch, reverse -from django.utils import six +from django.utils.functional import LazyObject +from django.utils.module_loading import import_string from django.utils.text import capfirst -from django.utils.translation import ugettext as _, ugettext_lazy +from django.utils.translation import gettext as _, gettext_lazy from django.views.decorators.cache import never_cache from django.views.decorators.csrf import csrf_protect from django.views.i18n import JavaScriptCatalog @@ -27,7 +28,7 @@ class NotRegistered(Exception): pass -class AdminSite(object): +class AdminSite: """ An AdminSite object encapsulates an instance of the Django admin application, ready to be hooked in to your URLconf. Models are registered with the AdminSite using the @@ -37,13 +38,13 @@ class AdminSite(object): """ # Text to put at the end of each page's . - site_title = ugettext_lazy('Django site admin') + site_title = gettext_lazy('Django site admin') # Text to put in each page's <h1>. - site_header = ugettext_lazy('Django administration') + site_header = gettext_lazy('Django administration') # Text to put at the top of the admin index page. - index_title = ugettext_lazy('Site administration') + index_title = gettext_lazy('Site administration') # URL for the "View site" link at the top of each admin page. site_url = '/' @@ -83,21 +84,19 @@ def check(self, app_configs): def register(self, model_or_iterable, admin_class=None, **options): """ - Registers the given model(s) with the given admin class. + Register the given model(s) with the given admin class. The model(s) should be Model classes, not instances. - If an admin class isn't given, it will use ModelAdmin (the default - admin options). If keyword arguments are given -- e.g., list_display -- - they'll be applied as options to the admin class. + If an admin class isn't given, use ModelAdmin (the default admin + options). If keyword arguments are given -- e.g., list_display -- + apply them as options to the admin class. - If a model is already registered, this will raise AlreadyRegistered. + If a model is already registered, raise AlreadyRegistered. - If a model is abstract, this will raise ImproperlyConfigured. + If a model is abstract, raise ImproperlyConfigured. """ - if not admin_class: - admin_class = ModelAdmin - + admin_class = admin_class or ModelAdmin if isinstance(model_or_iterable, ModelBase): model_or_iterable = [model_or_iterable] for model in model_or_iterable: @@ -126,9 +125,9 @@ def register(self, model_or_iterable, admin_class=None, **options): def unregister(self, model_or_iterable): """ - Unregisters the given model(s). + Unregister the given model(s). - If a model isn't already registered, this will raise NotRegistered. + If a model isn't already registered, raise NotRegistered. """ if isinstance(model_or_iterable, ModelBase): model_or_iterable = [model_or_iterable] @@ -153,14 +152,14 @@ def add_action(self, action, name=None): def disable_action(self, name): """ - Disable a globally-registered action. Raises KeyError for invalid names. + Disable a globally-registered action. Raise KeyError for invalid names. """ del self._actions[name] def get_action(self, name): """ Explicitly get a registered global action whether it's enabled or - not. Raises KeyError for invalid names. + not. Raise KeyError for invalid names. """ return self._global_actions[name] @@ -169,7 +168,7 @@ def actions(self): """ Get all the enabled actions as an iterable of (name, func). """ - return six.iteritems(self._actions) + return self._actions.items() @property def empty_value_display(self): @@ -181,7 +180,7 @@ def empty_value_display(self, empty_value_display): def has_permission(self, request): """ - Returns True if the given HttpRequest has permission to view + Return True if the given HttpRequest has permission to view *at least one* page in the admin site. """ return request.user.is_active and request.user.is_staff @@ -197,11 +196,11 @@ def admin_view(self, view, cacheable=False): class MyAdminSite(AdminSite): def get_urls(self): - from django.conf.urls import url + from django.urls import path - urls = super(MyAdminSite, self).get_urls() + urls = super().get_urls() urls += [ - url(r'^my_view/$', self.admin_view(some_view)) + path('my_view/', self.admin_view(some_view)) ] return urls @@ -231,7 +230,7 @@ def inner(request, *args, **kwargs): return update_wrapper(inner, view) def get_urls(self): - from django.conf.urls import url, include + from django.urls import include, path, re_path # Since this module gets imported in the application's root package, # it cannot import models from other applications at the module level, # and django.contrib.contenttypes.views imports ContentType. @@ -245,15 +244,21 @@ def wrapper(*args, **kwargs): # Admin-site-wide views. urlpatterns = [ - url(r'^$', wrap(self.index), name='index'), - url(r'^login/$', self.login, name='login'), - url(r'^logout/$', wrap(self.logout), name='logout'), - url(r'^password_change/$', wrap(self.password_change, cacheable=True), name='password_change'), - url(r'^password_change/done/$', wrap(self.password_change_done, cacheable=True), - name='password_change_done'), - url(r'^jsi18n/$', wrap(self.i18n_javascript, cacheable=True), name='jsi18n'), - url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$', wrap(contenttype_views.shortcut), - name='view_on_site'), + path('', wrap(self.index), name='index'), + path('login/', self.login, name='login'), + path('logout/', wrap(self.logout), name='logout'), + path('password_change/', wrap(self.password_change, cacheable=True), name='password_change'), + path( + 'password_change/done/', + wrap(self.password_change_done, cacheable=True), + name='password_change_done', + ), + path('jsi18n/', wrap(self.i18n_javascript, cacheable=True), name='jsi18n'), + path( + 'r/<int:content_type_id>/<path:object_id>/', + wrap(contenttype_views.shortcut), + name='view_on_site', + ), ] # Add in each model's views, and create a list of valid URLS for the @@ -261,7 +266,7 @@ def wrapper(*args, **kwargs): valid_app_labels = [] for model, model_admin in self._registry.items(): urlpatterns += [ - url(r'^%s/%s/' % (model._meta.app_label, model._meta.model_name), include(model_admin.urls)), + path('%s/%s/' % (model._meta.app_label, model._meta.model_name), include(model_admin.urls)), ] if model._meta.app_label not in valid_app_labels: valid_app_labels.append(model._meta.app_label) @@ -271,7 +276,7 @@ def wrapper(*args, **kwargs): if valid_app_labels: regex = r'^(?P<app_label>' + '|'.join(valid_app_labels) + ')/$' urlpatterns += [ - url(regex, wrap(self.app_index), name='app_list'), + re_path(regex, wrap(self.app_index), name='app_list'), ] return urlpatterns @@ -281,7 +286,7 @@ def urls(self): def each_context(self, request): """ - Returns a dictionary of variables to put in the template context for + Return a dictionary of variables to put in the template context for *every* page in the admin site. For sites running on a subpath, use the SCRIPT_NAME value if site_url @@ -295,11 +300,12 @@ def each_context(self, request): 'site_url': site_url, 'has_permission': self.has_permission(request), 'available_apps': self.get_app_list(request), + 'is_popup': False, } def password_change(self, request, extra_context=None): """ - Handles the "change password" task -- both form display and validation. + Handle the "change password" task -- both form display and validation. """ from django.contrib.admin.forms import AdminPasswordChangeForm from django.contrib.auth.views import PasswordChangeView @@ -307,7 +313,7 @@ def password_change(self, request, extra_context=None): defaults = { 'form_class': AdminPasswordChangeForm, 'success_url': url, - 'extra_context': dict(self.each_context(request), **(extra_context or {})), + 'extra_context': {**self.each_context(request), **(extra_context or {})}, } if self.password_change_template is not None: defaults['template_name'] = self.password_change_template @@ -316,11 +322,11 @@ def password_change(self, request, extra_context=None): def password_change_done(self, request, extra_context=None): """ - Displays the "success" page after a password change. + Display the "success" page after a password change. """ from django.contrib.auth.views import PasswordChangeDoneView defaults = { - 'extra_context': dict(self.each_context(request), **(extra_context or {})), + 'extra_context': {**self.each_context(request), **(extra_context or {})}, } if self.password_change_done_template is not None: defaults['template_name'] = self.password_change_done_template @@ -329,7 +335,7 @@ def password_change_done(self, request, extra_context=None): def i18n_javascript(self, request, extra_context=None): """ - Displays the i18n JavaScript that the Django admin requires. + Display the i18n JavaScript that the Django admin requires. `extra_context` is unused but present for consistency with the other admin views. @@ -339,19 +345,19 @@ def i18n_javascript(self, request, extra_context=None): @never_cache def logout(self, request, extra_context=None): """ - Logs out the user for the given HttpRequest. + Log out the user for the given HttpRequest. This should *not* assume the user is already logged in. """ from django.contrib.auth.views import LogoutView defaults = { - 'extra_context': dict( - self.each_context(request), + 'extra_context': { + **self.each_context(request), # Since the user isn't logged out at this point, the value of # has_permission must be overridden. - has_permission=False, + 'has_permission': False, **(extra_context or {}) - ), + }, } if self.logout_template is not None: defaults['template_name'] = self.logout_template @@ -361,7 +367,7 @@ def logout(self, request, extra_context=None): @never_cache def login(self, request, extra_context=None): """ - Displays the login form for the given HttpRequest. + Display the login form for the given HttpRequest. """ if request.method == 'GET' and self.has_permission(request): # Already logged-in, redirect to admin index @@ -373,12 +379,12 @@ def login(self, request, extra_context=None): # it cannot import models from other applications at the module level, # and django.contrib.admin.forms eventually imports User. from django.contrib.admin.forms import AdminAuthenticationForm - context = dict( - self.each_context(request), - title=_('Log in'), - app_path=request.get_full_path(), - username=request.user.get_username(), - ) + context = { + **self.each_context(request), + 'title': _('Log in'), + 'app_path': request.get_full_path(), + 'username': request.user.get_username(), + } if (REDIRECT_FIELD_NAME not in request.GET and REDIRECT_FIELD_NAME not in request.POST): context[REDIRECT_FIELD_NAME] = reverse('admin:index', current_app=self.name) @@ -394,8 +400,8 @@ def login(self, request, extra_context=None): def _build_app_dict(self, request, label=None): """ - Builds the app dictionary. Takes an optional label parameters to filter - models of a specific app. + Build the app dictionary. The optional `label` parameter filters models + of a specific app. """ app_dict = {} @@ -426,8 +432,11 @@ def _build_app_dict(self, request, label=None): 'name': capfirst(model._meta.verbose_name_plural), 'object_name': model._meta.object_name, 'perms': perms, + 'admin_url': None, + 'add_url': None, } - if perms.get('change'): + if perms.get('change') or perms.get('view'): + model_dict['view_only'] = not perms.get('change') try: model_dict['admin_url'] = reverse('admin:%s_%s_changelist' % info, current_app=self.name) except NoReverseMatch: @@ -459,7 +468,7 @@ def _build_app_dict(self, request, label=None): def get_app_list(self, request): """ - Returns a sorted list of all the installed apps that have been + Return a sorted list of all the installed apps that have been registered in this site. """ app_dict = self._build_app_dict(request) @@ -476,17 +485,17 @@ def get_app_list(self, request): @never_cache def index(self, request, extra_context=None): """ - Displays the main admin index page, which lists all of the installed + Display the main admin index page, which lists all of the installed apps that have been registered in this site. """ app_list = self.get_app_list(request) - context = dict( - self.each_context(request), - title=self.index_title, - app_list=app_list, - ) - context.update(extra_context or {}) + context = { + **self.each_context(request), + 'title': self.index_title, + 'app_list': app_list, + **(extra_context or {}), + } request.current_app = self.name @@ -499,13 +508,13 @@ def app_index(self, request, app_label, extra_context=None): # Sort the models alphabetically within each app. app_dict['models'].sort(key=lambda x: x['name']) app_name = apps.get_app_config(app_label).verbose_name - context = dict( - self.each_context(request), - title=_('%(app)s administration') % {'app': app_name}, - app_list=[app_dict], - app_label=app_label, - ) - context.update(extra_context or {}) + context = { + **self.each_context(request), + 'title': _('%(app)s administration') % {'app': app_name}, + 'app_list': [app_dict], + 'app_label': app_label, + **(extra_context or {}), + } request.current_app = self.name @@ -515,6 +524,14 @@ def app_index(self, request, app_label, extra_context=None): ], context) +class DefaultAdminSite(LazyObject): + def _setup(self): + AdminSiteClass = import_string(apps.get_app_config('admin').default_site) + self._wrapped = AdminSiteClass() + + # This global object represents the default admin site, for the common case. -# You can instantiate AdminSite in your own code to create a custom admin site. -site = AdminSite() +# You can provide your own AdminSite using the (Simple)AdminConfig.default_site +# attribute. You can also instantiate AdminSite in your own code to create a +# custom admin site. +site = DefaultAdminSite() diff --git a/django/contrib/admin/static/admin/css/autocomplete.css b/django/contrib/admin/static/admin/css/autocomplete.css new file mode 100644 index 000000000000..3ef95d15f0a1 --- /dev/null +++ b/django/contrib/admin/static/admin/css/autocomplete.css @@ -0,0 +1,260 @@ +select.admin-autocomplete { + width: 20em; +} + +.select2-container--admin-autocomplete.select2-container { + min-height: 30px; +} + +.select2-container--admin-autocomplete .select2-selection--single, +.select2-container--admin-autocomplete .select2-selection--multiple { + min-height: 30px; + padding: 0; +} + +.select2-container--admin-autocomplete.select2-container--focus .select2-selection, +.select2-container--admin-autocomplete.select2-container--open .select2-selection { + border-color: #999; + min-height: 30px; +} + +.select2-container--admin-autocomplete.select2-container--focus .select2-selection.select2-selection--single, +.select2-container--admin-autocomplete.select2-container--open .select2-selection.select2-selection--single { + padding: 0; +} + +.select2-container--admin-autocomplete.select2-container--focus .select2-selection.select2-selection--multiple, +.select2-container--admin-autocomplete.select2-container--open .select2-selection.select2-selection--multiple { + padding: 0; +} + +.select2-container--admin-autocomplete .select2-selection--single { + background-color: #fff; + border: 1px solid #ccc; + border-radius: 4px; +} + +.select2-container--admin-autocomplete .select2-selection--single .select2-selection__rendered { + color: #444; + line-height: 30px; +} + +.select2-container--admin-autocomplete .select2-selection--single .select2-selection__clear { + cursor: pointer; + float: right; + font-weight: bold; +} + +.select2-container--admin-autocomplete .select2-selection--single .select2-selection__placeholder { + color: #999; +} + +.select2-container--admin-autocomplete .select2-selection--single .select2-selection__arrow { + height: 26px; + position: absolute; + top: 1px; + right: 1px; + width: 20px; +} + +.select2-container--admin-autocomplete .select2-selection--single .select2-selection__arrow b { + border-color: #888 transparent transparent transparent; + border-style: solid; + border-width: 5px 4px 0 4px; + height: 0; + left: 50%; + margin-left: -4px; + margin-top: -2px; + position: absolute; + top: 50%; + width: 0; +} + +.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--single .select2-selection__clear { + float: left; +} + +.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--single .select2-selection__arrow { + left: 1px; + right: auto; +} + +.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--single { + background-color: #eee; + cursor: default; +} + +.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--single .select2-selection__clear { + display: none; +} + +.select2-container--admin-autocomplete.select2-container--open .select2-selection--single .select2-selection__arrow b { + border-color: transparent transparent #888 transparent; + border-width: 0 4px 5px 4px; +} + +.select2-container--admin-autocomplete .select2-selection--multiple { + background-color: white; + border: 1px solid #ccc; + border-radius: 4px; + cursor: text; +} + +.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__rendered { + box-sizing: border-box; + list-style: none; + margin: 0; + padding: 0 5px; + width: 100%; +} + +.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__rendered li { + list-style: none; +} + +.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__placeholder { + color: #999; + margin-top: 5px; + float: left; +} + +.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__clear { + cursor: pointer; + float: right; + font-weight: bold; + margin: 5px; +} + +.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice { + background-color: #e4e4e4; + border: 1px solid #ccc; + border-radius: 4px; + cursor: default; + float: left; + margin-right: 5px; + margin-top: 5px; + padding: 0 5px; +} + +.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice__remove { + color: #999; + cursor: pointer; + display: inline-block; + font-weight: bold; + margin-right: 2px; +} + +.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice__remove:hover { + color: #333; +} + +.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder, .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-search--inline { + float: right; +} + +.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice { + margin-left: 5px; + margin-right: auto; +} + +.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove { + margin-left: 2px; + margin-right: auto; +} + +.select2-container--admin-autocomplete.select2-container--focus .select2-selection--multiple { + border: solid #999 1px; + outline: 0; +} + +.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--multiple { + background-color: #eee; + cursor: default; +} + +.select2-container--admin-autocomplete.select2-container--disabled .select2-selection__choice__remove { + display: none; +} + +.select2-container--admin-autocomplete.select2-container--open.select2-container--above .select2-selection--single, .select2-container--admin-autocomplete.select2-container--open.select2-container--above .select2-selection--multiple { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.select2-container--admin-autocomplete.select2-container--open.select2-container--below .select2-selection--single, .select2-container--admin-autocomplete.select2-container--open.select2-container--below .select2-selection--multiple { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +.select2-container--admin-autocomplete .select2-search--dropdown .select2-search__field { + border: 1px solid #ccc; +} + +.select2-container--admin-autocomplete .select2-search--inline .select2-search__field { + background: transparent; + border: none; + outline: 0; + box-shadow: none; + -webkit-appearance: textfield; +} + +.select2-container--admin-autocomplete .select2-results > .select2-results__options { + max-height: 200px; + overflow-y: auto; +} + +.select2-container--admin-autocomplete .select2-results__option[role=group] { + padding: 0; +} + +.select2-container--admin-autocomplete .select2-results__option[aria-disabled=true] { + color: #999; +} + +.select2-container--admin-autocomplete .select2-results__option[aria-selected=true] { + background-color: #ddd; +} + +.select2-container--admin-autocomplete .select2-results__option .select2-results__option { + padding-left: 1em; +} + +.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__group { + padding-left: 0; +} + +.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option { + margin-left: -1em; + padding-left: 2em; +} + +.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option { + margin-left: -2em; + padding-left: 3em; +} + +.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { + margin-left: -3em; + padding-left: 4em; +} + +.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { + margin-left: -4em; + padding-left: 5em; +} + +.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { + margin-left: -5em; + padding-left: 6em; +} + +.select2-container--admin-autocomplete .select2-results__option--highlighted[aria-selected] { + background-color: #79aec8; + color: white; +} + +.select2-container--admin-autocomplete .select2-results__group { + cursor: default; + display: block; + padding: 6px; +} diff --git a/django/contrib/admin/static/admin/css/base.css b/django/contrib/admin/static/admin/css/base.css index 466b36bceccc..fd011a3f9a31 100644 --- a/django/contrib/admin/static/admin/css/base.css +++ b/django/contrib/admin/static/admin/css/base.css @@ -423,7 +423,7 @@ textarea { } input[type=text], input[type=password], input[type=email], input[type=url], -input[type=number], textarea, select, .vTextField { +input[type=number], input[type=tel], textarea, select, .vTextField { border: 1px solid #ccc; border-radius: 4px; padding: 5px 6px; @@ -431,8 +431,8 @@ input[type=number], textarea, select, .vTextField { } input[type=text]:focus, input[type=password]:focus, input[type=email]:focus, -input[type=url]:focus, input[type=number]:focus, textarea:focus, select:focus, -.vTextField:focus { +input[type=url]:focus, input[type=number]:focus, input[type=tel]:focus, +textarea:focus, select:focus, .vTextField:focus { border-color: #999; } @@ -441,6 +441,8 @@ select { } select[multiple] { + /* Allow HTML size attribute to override the height in the rule above. */ + height: auto; min-height: 150px; } @@ -662,6 +664,11 @@ div.breadcrumbs a:focus, div.breadcrumbs a:hover { /* ACTION ICONS */ +.viewlink, .inlineviewlink { + padding-left: 16px; + background: url(../img/icon-viewlink.svg) 0 1px no-repeat; +} + .addlink { padding-left: 16px; background: url(../img/icon-addlink.svg) 0 1px no-repeat; @@ -738,7 +745,7 @@ a.deletelink:focus, a.deletelink:hover { .object-tools a.viewsitelink, .object-tools a.golink,.object-tools a.addlink { background-repeat: no-repeat; - background-position: 93% center; + background-position: right 7px center; padding-right: 26px; } @@ -822,10 +829,12 @@ table#change-history tbody th { #header { width: auto; - height: 40px; + height: auto; + display: flex; + justify-content: space-between; + align-items: center; padding: 10px 40px; background: #417690; - line-height: 40px; color: #ffc; overflow: hidden; } diff --git a/django/contrib/admin/static/admin/css/forms.css b/django/contrib/admin/static/admin/css/forms.css index 77985d5d34ad..62a093f952f1 100644 --- a/django/contrib/admin/static/admin/css/forms.css +++ b/django/contrib/admin/static/admin/css/forms.css @@ -177,7 +177,7 @@ form .aligned table p { padding-left: 0; } -fieldset .field-box { +fieldset .fieldBox { float: left; margin-right: 20px; } @@ -291,12 +291,29 @@ body.popup .submit-row { color: #fff; } +.submit-row a.closelink { + display: inline-block; + background: #bbbbbb; + border-radius: 4px; + padding: 10px 15px; + height: 15px; + line-height: 15px; + margin: 0 0 0 5px; + color: #fff; +} + .submit-row a.deletelink:focus, .submit-row a.deletelink:hover, .submit-row a.deletelink:active { background: #a41515; } +.submit-row a.closelink:focus, +.submit-row a.closelink:hover, +.submit-row a.closelink:active { + background: #aaaaaa; +} + /* CUSTOM FORM FIELDS */ .vSelectMultipleField { @@ -336,7 +353,7 @@ body.popup .submit-row { width: 2.2em; } -.vTextField { +.vTextField, .vUUIDField { width: 20em; } diff --git a/django/contrib/admin/static/admin/css/login.css b/django/contrib/admin/static/admin/css/login.css index cab3bbf5856d..2ec241c27a7e 100644 --- a/django/contrib/admin/static/admin/css/login.css +++ b/django/contrib/admin/static/admin/css/login.css @@ -6,7 +6,8 @@ body.login { .login #header { height: auto; - padding: 5px 16px; + padding: 15px 16px; + justify-content: center; } .login #header h1 { diff --git a/django/contrib/admin/static/admin/css/responsive.css b/django/contrib/admin/static/admin/css/responsive.css new file mode 100644 index 000000000000..5b0d1ec39bd7 --- /dev/null +++ b/django/contrib/admin/static/admin/css/responsive.css @@ -0,0 +1,992 @@ +/* Tablets */ + +input[type="submit"], button { + -webkit-appearance: none; + appearance: none; +} + +@media (max-width: 1024px) { + /* Basic */ + + html { + -webkit-text-size-adjust: 100%; + } + + td, th { + padding: 10px; + font-size: 14px; + } + + .small { + font-size: 12px; + } + + /* Layout */ + + #container { + min-width: 0; + } + + #content { + padding: 20px 30px 30px; + } + + div.breadcrumbs { + padding: 10px 30px; + } + + /* Header */ + + #header { + flex-direction: column; + padding: 15px 30px; + justify-content: flex-start; + } + + #branding h1 { + margin: 0 0 8px; + font-size: 20px; + line-height: 1.2; + } + + #user-tools { + margin: 0; + font-weight: 400; + line-height: 1.85; + text-align: left; + } + + #user-tools a { + display: inline-block; + line-height: 1.4; + } + + /* Dashboard */ + + .dashboard #content { + width: auto; + } + + #content-related { + margin-right: -290px; + } + + .colSM #content-related { + margin-left: -290px; + } + + .colMS { + margin-right: 290px; + } + + .colSM { + margin-left: 290px; + } + + .dashboard .module table td a { + padding-right: 0; + } + + td .changelink, td .addlink { + font-size: 13px; + } + + /* Changelist */ + + #changelist #toolbar { + border: none; + padding: 15px; + } + + #changelist-search > div { + display: -webkit-flex; + display: flex; + -webkit-flex-wrap: wrap; + flex-wrap: wrap; + max-width: 480px; + } + + #changelist-search label { + line-height: 22px; + } + + #changelist #toolbar form #searchbar { + -webkit-flex: 1 0 auto; + flex: 1 0 auto; + width: 0; + height: 22px; + margin: 0 10px 0 6px; + } + + #changelist-search .quiet { + width: 100%; + margin: 5px 0 0 25px; + } + + #changelist .actions { + display: flex; + flex-wrap: wrap; + padding: 15px 0; + } + + #changelist .actions.selected { + border: none; + } + + #changelist .actions label { + display: flex; + } + + #changelist .actions select { + background: #fff; + } + + #changelist .actions .button { + min-width: 48px; + margin: 0 10px; + } + + #changelist .actions span.all, + #changelist .actions span.clear, + #changelist .actions span.question, + #changelist .actions span.action-counter { + font-size: 11px; + margin: 0 10px 0 0; + } + + #changelist-filter { + width: 200px; + } + + .change-list .filtered .results, + .change-list .filtered .paginator, + .filtered #toolbar, + .filtered .actions, + .filtered div.xfull { + margin-right: 230px; + } + + #changelist .paginator { + border-top-color: #eee; + } + + #changelist .results + .paginator { + border-top: none; + } + + /* Forms */ + + label { + font-size: 14px; + } + + .form-row input[type=text], + .form-row input[type=password], + .form-row input[type=email], + .form-row input[type=url], + .form-row input[type=tel], + .form-row input[type=number], + .form-row textarea, + .form-row select, + .form-row .vTextField { + box-sizing: border-box; + margin: 0; + padding: 6px 8px; + min-height: 36px; + font-size: 14px; + } + + .form-row select { + height: 36px; + } + + .form-row select[multiple] { + height: auto; + min-height: 0; + } + + fieldset .fieldBox { + float: none; + margin: 0 -10px; + padding: 0 10px; + } + + fieldset .fieldBox + .fieldBox { + margin-top: 10px; + padding-top: 10px; + border-top: 1px solid #eee; + } + + textarea { + max-width: 518px; + max-height: 120px; + } + + .aligned label { + padding-top: 6px; + } + + .aligned .add-another, + .aligned .related-lookup, + .aligned .datetimeshortcuts, + .aligned .related-lookup + strong { + align-self: center; + margin-left: 15px; + } + + form .aligned ul.radiolist { + margin-left: 2px; + } + + /* Related widget */ + + .related-widget-wrapper { + float: none; + } + + .related-widget-wrapper-link + .selector { + max-width: calc(100% - 30px); + margin-right: 15px; + } + + select + .related-widget-wrapper-link, + .related-widget-wrapper-link + .related-widget-wrapper-link { + margin-left: 10px; + } + + /* Selector */ + + .selector { + display: flex; + width: 100%; + } + + .selector .selector-filter { + display: flex; + align-items: center; + } + + .selector .selector-filter label { + margin: 0 8px 0 0; + } + + .selector .selector-filter input { + width: auto; + min-height: 0; + flex: 1 1; + } + + .selector-available, .selector-chosen { + width: auto; + flex: 1 1; + display: flex; + flex-direction: column; + } + + .selector select { + width: 100%; + flex: 1 0 auto; + margin-bottom: 5px; + } + + .selector ul.selector-chooser { + width: 26px; + height: 52px; + padding: 2px 0; + margin: auto 15px; + border-radius: 20px; + transform: translateY(-10px); + } + + .selector-add, .selector-remove { + width: 20px; + height: 20px; + background-size: 20px auto; + } + + .selector-add { + background-position: 0 -120px; + } + + .selector-remove { + background-position: 0 -80px; + } + + a.selector-chooseall, a.selector-clearall { + align-self: center; + } + + .stacked { + flex-direction: column; + max-width: 480px; + } + + .stacked > * { + flex: 0 1 auto; + } + + .stacked select { + margin-bottom: 0; + } + + .stacked .selector-available, .stacked .selector-chosen { + width: auto; + } + + .stacked ul.selector-chooser { + width: 52px; + height: 26px; + padding: 0 2px; + margin: 15px auto; + transform: none; + } + + .stacked .selector-chooser li { + padding: 3px; + } + + .stacked .selector-add, .stacked .selector-remove { + background-size: 20px auto; + } + + .stacked .selector-add { + background-position: 0 -40px; + } + + .stacked .active.selector-add { + background-position: 0 -60px; + } + + .stacked .selector-remove { + background-position: 0 0; + } + + .stacked .active.selector-remove { + background-position: 0 -20px; + } + + .help-tooltip, .selector .help-icon { + display: none; + } + + form .form-row p.datetime { + width: 100%; + } + + .datetime input { + width: 50%; + max-width: 120px; + } + + .datetime span { + font-size: 13px; + } + + .datetime .timezonewarning { + display: block; + font-size: 11px; + color: #999; + } + + .datetimeshortcuts { + color: #ccc; + } + + .inline-group { + overflow: auto; + } + + /* Messages */ + + ul.messagelist li { + padding-left: 55px; + background-position: 30px 12px; + } + + ul.messagelist li.error { + background-position: 30px 12px; + } + + ul.messagelist li.warning { + background-position: 30px 14px; + } + + /* Login */ + + .login #header { + padding: 15px 20px; + } + + .login #branding h1 { + margin: 0; + } + + /* GIS */ + + div.olMap { + max-width: calc(100vw - 30px); + max-height: 300px; + } + + .olMap + .clear_features { + display: block; + margin-top: 10px; + } + + /* Docs */ + + .module table.xfull { + width: 100%; + } + + pre.literal-block { + overflow: auto; + } +} + +/* Mobile */ + +@media (max-width: 767px) { + /* Layout */ + + #header, #content, #footer { + padding: 15px; + } + + #footer:empty { + padding: 0; + } + + div.breadcrumbs { + padding: 10px 15px; + } + + /* Dashboard */ + + .colMS, .colSM { + margin: 0; + } + + #content-related, .colSM #content-related { + width: 100%; + margin: 0; + } + + #content-related .module { + margin-bottom: 0; + } + + #content-related .module h2 { + padding: 10px 15px; + font-size: 16px; + } + + /* Changelist */ + + #changelist { + display: flex; + flex-direction: column; + } + + #changelist #toolbar { + order: 1; + padding: 10px; + } + + #changelist .xfull { + order: 2; + } + + #changelist-form { + order: 3; + } + + #changelist-filter { + order: 4; + } + + #changelist .actions label { + flex: 1 1; + } + + #changelist .actions select { + flex: 1 0; + width: 100%; + } + + #changelist .actions span { + flex: 1 0 100%; + } + + .change-list .filtered .results, .change-list .filtered .paginator, + .filtered #toolbar, .filtered .actions, .filtered div.xfull { + margin-right: 0; + } + + #changelist-filter { + position: static; + width: auto; + margin-top: 30px; + } + + .object-tools { + float: none; + margin: 0 0 15px; + padding: 0; + overflow: hidden; + } + + .object-tools li { + height: auto; + margin-left: 0; + } + + .object-tools li + li { + margin-left: 15px; + } + + /* Forms */ + + .form-row { + padding: 15px 0; + } + + .aligned .form-row, + .aligned .form-row > div { + display: flex; + flex-wrap: wrap; + max-width: 100vw; + } + + .aligned .form-row > div { + width: calc(100vw - 30px); + } + + textarea { + max-width: none; + } + + .vURLField { + width: auto; + } + + fieldset .fieldBox + .fieldBox { + margin-top: 15px; + padding-top: 15px; + } + + fieldset.collapsed .form-row { + display: none; + } + + .aligned label { + width: 100%; + padding: 0 0 10px; + } + + .aligned label:after { + max-height: 0; + } + + .aligned .form-row input, + .aligned .form-row select, + .aligned .form-row textarea { + flex: 1 1 auto; + max-width: 100%; + } + + .aligned .checkbox-row { + align-items: center; + } + + .aligned .checkbox-row input { + flex: 0 1 auto; + margin: 0; + } + + .aligned .vCheckboxLabel { + flex: 1 0; + padding: 1px 0 0 5px; + } + + .aligned label + p, + .aligned label + div.help, + .aligned label + div.readonly { + padding: 0; + margin-left: 0; + } + + .aligned p.file-upload { + margin-left: 0; + font-size: 13px; + } + + span.clearable-file-input { + margin-left: 15px; + } + + span.clearable-file-input label { + font-size: 13px; + padding-bottom: 0; + } + + .aligned .timezonewarning { + flex: 1 0 100%; + margin-top: 5px; + } + + form .aligned .form-row div.help { + width: 100%; + margin: 5px 0 0; + padding: 0; + } + + form .aligned ul { + margin-left: 0; + padding-left: 0; + } + + form .aligned ul.radiolist { + margin-right: 15px; + margin-bottom: -3px; + } + + form .aligned ul.radiolist li + li { + margin-top: 5px; + } + + /* Related widget */ + + .related-widget-wrapper { + width: 100%; + display: flex; + align-items: flex-start; + } + + .related-widget-wrapper .selector { + order: 1; + } + + .related-widget-wrapper > a { + order: 2; + } + + .related-widget-wrapper .radiolist ~ a { + align-self: flex-end; + } + + .related-widget-wrapper > select ~ a { + align-self: center; + } + + select + .related-widget-wrapper-link, + .related-widget-wrapper-link + .related-widget-wrapper-link { + margin-left: 15px; + } + + /* Selector */ + + .selector { + flex-direction: column; + } + + .selector > * { + float: none; + } + + .selector-available, .selector-chosen { + margin-bottom: 0; + flex: 1 1 auto; + } + + .selector select { + max-height: 96px; + } + + .selector ul.selector-chooser { + display: block; + float: none; + width: 52px; + height: 26px; + padding: 0 2px; + margin: 15px auto 20px; + transform: none; + } + + .selector ul.selector-chooser li { + float: left; + } + + .selector-remove { + background-position: 0 0; + } + + .selector-add { + background-position: 0 -40px; + } + + /* Inlines */ + + .inline-group[data-inline-type="stacked"] .inline-related { + border: 2px solid #eee; + border-radius: 4px; + margin-top: 15px; + overflow: auto; + } + + .inline-group[data-inline-type="stacked"] .inline-related > * { + box-sizing: border-box; + } + + .inline-group[data-inline-type="stacked"] .inline-related + .inline-related { + margin-top: 30px; + } + + .inline-group[data-inline-type="stacked"] .inline-related .module { + padding: 0 10px; + } + + .inline-group[data-inline-type="stacked"] .inline-related .module .form-row:last-child { + border-bottom: none; + } + + .inline-group[data-inline-type="stacked"] .inline-related h3 { + padding: 10px; + border-top-width: 0; + border-bottom-width: 2px; + display: flex; + flex-wrap: wrap; + align-items: center; + } + + .inline-group[data-inline-type="stacked"] .inline-related h3 .inline_label { + margin-right: auto; + } + + .inline-group[data-inline-type="stacked"] .inline-related h3 span.delete { + float: none; + flex: 1 1 100%; + margin-top: 5px; + } + + .inline-group[data-inline-type="stacked"] .aligned .form-row > div:not([class]) { + width: 100%; + } + + .inline-group[data-inline-type="stacked"] .aligned label { + width: 100%; + } + + .inline-group[data-inline-type="stacked"] div.add-row { + margin-top: 15px; + border: 1px solid #eee; + border-radius: 4px; + } + + .inline-group div.add-row, + .inline-group .tabular tr.add-row td { + padding: 0; + } + + .inline-group div.add-row a, + .inline-group .tabular tr.add-row td a { + display: block; + padding: 8px 10px 8px 26px; + background-position: 8px 9px; + } + + /* Submit row */ + + .submit-row { + padding: 10px 10px 0; + margin: 0 0 15px; + display: flex; + flex-direction: column; + } + + .submit-row > * { + width: 100%; + } + + .submit-row input, .submit-row input.default, .submit-row a, .submit-row a.closelink { + float: none; + margin: 0 0 10px; + text-align: center; + } + + .submit-row a.closelink { + padding: 10px 0; + } + + .submit-row p.deletelink-box { + order: 4; + } + + /* Messages */ + + ul.messagelist li { + padding-left: 40px; + background-position: 15px 12px; + } + + ul.messagelist li.error { + background-position: 15px 12px; + } + + ul.messagelist li.warning { + background-position: 15px 14px; + } + + /* Paginator */ + + .paginator .this-page, .paginator a:link, .paginator a:visited { + padding: 4px 10px; + } + + /* Login */ + + body.login { + padding: 0 15px; + } + + .login #container { + width: auto; + max-width: 480px; + margin: 50px auto; + } + + .login #header, + .login #content { + padding: 15px; + } + + .login #content-main { + float: none; + } + + .login .form-row { + padding: 0; + } + + .login .form-row + .form-row { + margin-top: 15px; + } + + .login .form-row label { + display: block; + margin: 0 0 5px; + padding: 0; + line-height: 1.2; + } + + .login .submit-row { + padding: 15px 0 0; + } + + .login br, .login .submit-row label { + display: none; + } + + .login .submit-row input { + margin: 0; + text-transform: uppercase; + } + + .errornote { + margin: 0 0 20px; + padding: 8px 12px; + font-size: 13px; + } + + /* Calendar and clock */ + + .calendarbox, .clockbox { + position: fixed !important; + top: 50% !important; + left: 50% !important; + transform: translate(-50%, -50%); + margin: 0; + border: none; + overflow: visible; + } + + .calendarbox:before, .clockbox:before { + content: ''; + position: fixed; + top: 50%; + left: 50%; + width: 100vw; + height: 100vh; + background: rgba(0, 0, 0, 0.75); + transform: translate(-50%, -50%); + } + + .calendarbox > *, .clockbox > * { + position: relative; + z-index: 1; + } + + .calendarbox > div:first-child { + z-index: 2; + } + + .calendarbox .calendar, .clockbox h2 { + border-radius: 4px 4px 0 0; + overflow: hidden; + } + + .calendarbox .calendar-cancel, .clockbox .calendar-cancel { + border-radius: 0 0 4px 4px; + overflow: hidden; + } + + .calendar-shortcuts { + padding: 10px 0; + font-size: 12px; + line-height: 12px; + } + + .calendar-shortcuts a { + margin: 0 4px; + } + + .timelist a { + background: #fff; + padding: 4px; + } + + .calendar-cancel { + padding: 8px 10px; + } + + .clockbox h2 { + padding: 8px 15px; + } + + .calendar caption { + padding: 10px; + } + + .calendarbox .calendarnav-previous, .calendarbox .calendarnav-next { + z-index: 1; + top: 10px; + } + + /* History */ + + table#change-history tbody th, table#change-history tbody td { + font-size: 13px; + word-break: break-word; + } + + table#change-history tbody th { + width: auto; + } + + /* Docs */ + + table.model tbody th, table.model tbody td { + font-size: 13px; + word-break: break-word; + } +} diff --git a/django/contrib/admin/static/admin/css/responsive_rtl.css b/django/contrib/admin/static/admin/css/responsive_rtl.css new file mode 100644 index 000000000000..f999cb128be2 --- /dev/null +++ b/django/contrib/admin/static/admin/css/responsive_rtl.css @@ -0,0 +1,84 @@ +/* TABLETS */ + +@media (max-width: 1024px) { + [dir="rtl"] .colMS { + margin-right: 0; + } + + [dir="rtl"] #user-tools { + text-align: right; + } + + [dir="rtl"] #changelist .actions label { + padding-left: 10px; + padding-right: 0; + } + + [dir="rtl"] #changelist .actions select { + margin-left: 0; + margin-right: 15px; + } + + [dir="rtl"] .change-list .filtered .results, + [dir="rtl"] .change-list .filtered .paginator, + [dir="rtl"] .filtered #toolbar, + [dir="rtl"] .filtered div.xfull, + [dir="rtl"] .filtered .actions { + margin-right: 0; + margin-left: 230px; + } + + [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; + background-position: calc(100% - 8px) 9px; + } + + [dir="rtl"] .related-widget-wrapper-link + .selector { + margin-right: 0; + margin-left: 15px; + } + + [dir="rtl"] .selector .selector-filter label { + margin-right: 0; + margin-left: 8px; + } + + [dir="rtl"] .object-tools li { + float: right; + } + + [dir="rtl"] .object-tools li + li { + margin-left: 0; + margin-right: 15px; + } + + [dir="rtl"] .dashboard .module table td a { + padding-left: 0; + padding-right: 16px; + } +} + +/* MOBILE */ + +@media (max-width: 767px) { + [dir="rtl"] .change-list .filtered .results, + [dir="rtl"] .change-list .filtered .paginator, + [dir="rtl"] .filtered #toolbar, + [dir="rtl"] .filtered div.xfull, + [dir="rtl"] .filtered .actions { + margin-left: 0; + } + + [dir="rtl"] .aligned .add-another, + [dir="rtl"] .aligned .related-lookup, + [dir="rtl"] .aligned .datetimeshortcuts { + margin-left: 0; + margin-right: 15px; + } + + [dir="rtl"] .aligned ul { + margin-right: 0; + } +} diff --git a/django/contrib/admin/static/admin/css/rtl.css b/django/contrib/admin/static/admin/css/rtl.css index ef397815e687..b9e26bfec12b 100644 --- a/django/contrib/admin/static/admin/css/rtl.css +++ b/django/contrib/admin/static/admin/css/rtl.css @@ -35,7 +35,7 @@ th { margin-right: 1.5em; } -.addlink, .changelink { +.viewlink, .addlink, .changelink { padding-left: 0; padding-right: 16px; background-position: 100% 1px; @@ -170,6 +170,11 @@ form .aligned p.help, form .aligned div.help { clear: right; } +form .aligned ul { + margin-right: 163px; + margin-left: 0; +} + form ul.inline li { float: right; padding-right: 0; @@ -180,7 +185,7 @@ input[type=submit].default, .submit-row input.default { float: left; } -fieldset .field-box { +fieldset .fieldBox { float: right; margin-left: 20px; margin-right: 0; diff --git a/django/contrib/admin/static/admin/css/vendor/select2/LICENSE-SELECT2.md b/django/contrib/admin/static/admin/css/vendor/select2/LICENSE-SELECT2.md new file mode 100644 index 000000000000..86c7c291a969 --- /dev/null +++ b/django/contrib/admin/static/admin/css/vendor/select2/LICENSE-SELECT2.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2012-2015 Kevin Brown, Igor Vaynberg, and Select2 contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/django/contrib/admin/static/admin/css/vendor/select2/select2.css b/django/contrib/admin/static/admin/css/vendor/select2/select2.css new file mode 100644 index 000000000000..447b2b86cc00 --- /dev/null +++ b/django/contrib/admin/static/admin/css/vendor/select2/select2.css @@ -0,0 +1,484 @@ +.select2-container { + box-sizing: border-box; + display: inline-block; + margin: 0; + position: relative; + vertical-align: middle; } + .select2-container .select2-selection--single { + box-sizing: border-box; + cursor: pointer; + display: block; + height: 28px; + user-select: none; + -webkit-user-select: none; } + .select2-container .select2-selection--single .select2-selection__rendered { + display: block; + padding-left: 8px; + padding-right: 20px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } + .select2-container .select2-selection--single .select2-selection__clear { + position: relative; } + .select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered { + padding-right: 8px; + padding-left: 20px; } + .select2-container .select2-selection--multiple { + box-sizing: border-box; + cursor: pointer; + display: block; + min-height: 32px; + user-select: none; + -webkit-user-select: none; } + .select2-container .select2-selection--multiple .select2-selection__rendered { + display: inline-block; + overflow: hidden; + padding-left: 8px; + text-overflow: ellipsis; + white-space: nowrap; } + .select2-container .select2-search--inline { + float: left; } + .select2-container .select2-search--inline .select2-search__field { + box-sizing: border-box; + border: none; + font-size: 100%; + margin-top: 5px; + padding: 0; } + .select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button { + -webkit-appearance: none; } + +.select2-dropdown { + background-color: white; + border: 1px solid #aaa; + border-radius: 4px; + box-sizing: border-box; + display: block; + position: absolute; + left: -100000px; + width: 100%; + z-index: 1051; } + +.select2-results { + display: block; } + +.select2-results__options { + list-style: none; + margin: 0; + padding: 0; } + +.select2-results__option { + padding: 6px; + user-select: none; + -webkit-user-select: none; } + .select2-results__option[aria-selected] { + cursor: pointer; } + +.select2-container--open .select2-dropdown { + left: 0; } + +.select2-container--open .select2-dropdown--above { + border-bottom: none; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } + +.select2-container--open .select2-dropdown--below { + border-top: none; + border-top-left-radius: 0; + border-top-right-radius: 0; } + +.select2-search--dropdown { + display: block; + padding: 4px; } + .select2-search--dropdown .select2-search__field { + padding: 4px; + width: 100%; + box-sizing: border-box; } + .select2-search--dropdown .select2-search__field::-webkit-search-cancel-button { + -webkit-appearance: none; } + .select2-search--dropdown.select2-search--hide { + display: none; } + +.select2-close-mask { + border: 0; + margin: 0; + padding: 0; + display: block; + position: fixed; + left: 0; + top: 0; + min-height: 100%; + min-width: 100%; + height: auto; + width: auto; + opacity: 0; + z-index: 99; + background-color: #fff; + filter: alpha(opacity=0); } + +.select2-hidden-accessible { + border: 0 !important; + clip: rect(0 0 0 0) !important; + height: 1px !important; + margin: -1px !important; + overflow: hidden !important; + padding: 0 !important; + position: absolute !important; + width: 1px !important; } + +.select2-container--default .select2-selection--single { + background-color: #fff; + border: 1px solid #aaa; + border-radius: 4px; } + .select2-container--default .select2-selection--single .select2-selection__rendered { + color: #444; + line-height: 28px; } + .select2-container--default .select2-selection--single .select2-selection__clear { + cursor: pointer; + float: right; + font-weight: bold; } + .select2-container--default .select2-selection--single .select2-selection__placeholder { + color: #999; } + .select2-container--default .select2-selection--single .select2-selection__arrow { + height: 26px; + position: absolute; + top: 1px; + right: 1px; + width: 20px; } + .select2-container--default .select2-selection--single .select2-selection__arrow b { + border-color: #888 transparent transparent transparent; + border-style: solid; + border-width: 5px 4px 0 4px; + height: 0; + left: 50%; + margin-left: -4px; + margin-top: -2px; + position: absolute; + top: 50%; + width: 0; } + +.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear { + float: left; } + +.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow { + left: 1px; + right: auto; } + +.select2-container--default.select2-container--disabled .select2-selection--single { + background-color: #eee; + cursor: default; } + .select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear { + display: none; } + +.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b { + border-color: transparent transparent #888 transparent; + border-width: 0 4px 5px 4px; } + +.select2-container--default .select2-selection--multiple { + background-color: white; + border: 1px solid #aaa; + border-radius: 4px; + cursor: text; } + .select2-container--default .select2-selection--multiple .select2-selection__rendered { + box-sizing: border-box; + list-style: none; + margin: 0; + padding: 0 5px; + width: 100%; } + .select2-container--default .select2-selection--multiple .select2-selection__rendered li { + list-style: none; } + .select2-container--default .select2-selection--multiple .select2-selection__placeholder { + color: #999; + margin-top: 5px; + float: left; } + .select2-container--default .select2-selection--multiple .select2-selection__clear { + cursor: pointer; + float: right; + font-weight: bold; + margin-top: 5px; + margin-right: 10px; } + .select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: #e4e4e4; + border: 1px solid #aaa; + border-radius: 4px; + cursor: default; + float: left; + margin-right: 5px; + margin-top: 5px; + padding: 0 5px; } + .select2-container--default .select2-selection--multiple .select2-selection__choice__remove { + color: #999; + cursor: pointer; + display: inline-block; + font-weight: bold; + margin-right: 2px; } + .select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover { + color: #333; } + +.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline { + float: right; } + +.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice { + margin-left: 5px; + margin-right: auto; } + +.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove { + margin-left: 2px; + margin-right: auto; } + +.select2-container--default.select2-container--focus .select2-selection--multiple { + border: solid black 1px; + outline: 0; } + +.select2-container--default.select2-container--disabled .select2-selection--multiple { + background-color: #eee; + cursor: default; } + +.select2-container--default.select2-container--disabled .select2-selection__choice__remove { + display: none; } + +.select2-container--default.select2-container--open.select2-container--above .select2-selection--single, .select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple { + border-top-left-radius: 0; + border-top-right-radius: 0; } + +.select2-container--default.select2-container--open.select2-container--below .select2-selection--single, .select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } + +.select2-container--default .select2-search--dropdown .select2-search__field { + border: 1px solid #aaa; } + +.select2-container--default .select2-search--inline .select2-search__field { + background: transparent; + border: none; + outline: 0; + box-shadow: none; + -webkit-appearance: textfield; } + +.select2-container--default .select2-results > .select2-results__options { + max-height: 200px; + overflow-y: auto; } + +.select2-container--default .select2-results__option[role=group] { + padding: 0; } + +.select2-container--default .select2-results__option[aria-disabled=true] { + color: #999; } + +.select2-container--default .select2-results__option[aria-selected=true] { + background-color: #ddd; } + +.select2-container--default .select2-results__option .select2-results__option { + padding-left: 1em; } + .select2-container--default .select2-results__option .select2-results__option .select2-results__group { + padding-left: 0; } + .select2-container--default .select2-results__option .select2-results__option .select2-results__option { + margin-left: -1em; + padding-left: 2em; } + .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option { + margin-left: -2em; + padding-left: 3em; } + .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { + margin-left: -3em; + padding-left: 4em; } + .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { + margin-left: -4em; + padding-left: 5em; } + .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { + margin-left: -5em; + padding-left: 6em; } + +.select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: #5897fb; + color: white; } + +.select2-container--default .select2-results__group { + cursor: default; + display: block; + padding: 6px; } + +.select2-container--classic .select2-selection--single { + background-color: #f7f7f7; + border: 1px solid #aaa; + border-radius: 4px; + outline: 0; + background-image: -webkit-linear-gradient(top, white 50%, #eeeeee 100%); + background-image: -o-linear-gradient(top, white 50%, #eeeeee 100%); + background-image: linear-gradient(to bottom, white 50%, #eeeeee 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); } + .select2-container--classic .select2-selection--single:focus { + border: 1px solid #5897fb; } + .select2-container--classic .select2-selection--single .select2-selection__rendered { + color: #444; + line-height: 28px; } + .select2-container--classic .select2-selection--single .select2-selection__clear { + cursor: pointer; + float: right; + font-weight: bold; + margin-right: 10px; } + .select2-container--classic .select2-selection--single .select2-selection__placeholder { + color: #999; } + .select2-container--classic .select2-selection--single .select2-selection__arrow { + background-color: #ddd; + border: none; + border-left: 1px solid #aaa; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + height: 26px; + position: absolute; + top: 1px; + right: 1px; + width: 20px; + background-image: -webkit-linear-gradient(top, #eeeeee 50%, #cccccc 100%); + background-image: -o-linear-gradient(top, #eeeeee 50%, #cccccc 100%); + background-image: linear-gradient(to bottom, #eeeeee 50%, #cccccc 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0); } + .select2-container--classic .select2-selection--single .select2-selection__arrow b { + border-color: #888 transparent transparent transparent; + border-style: solid; + border-width: 5px 4px 0 4px; + height: 0; + left: 50%; + margin-left: -4px; + margin-top: -2px; + position: absolute; + top: 50%; + width: 0; } + +.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear { + float: left; } + +.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow { + border: none; + border-right: 1px solid #aaa; + border-radius: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + left: 1px; + right: auto; } + +.select2-container--classic.select2-container--open .select2-selection--single { + border: 1px solid #5897fb; } + .select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow { + background: transparent; + border: none; } + .select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b { + border-color: transparent transparent #888 transparent; + border-width: 0 4px 5px 4px; } + +.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single { + border-top: none; + border-top-left-radius: 0; + border-top-right-radius: 0; + background-image: -webkit-linear-gradient(top, white 0%, #eeeeee 50%); + background-image: -o-linear-gradient(top, white 0%, #eeeeee 50%); + background-image: linear-gradient(to bottom, white 0%, #eeeeee 50%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); } + +.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single { + border-bottom: none; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + background-image: -webkit-linear-gradient(top, #eeeeee 50%, white 100%); + background-image: -o-linear-gradient(top, #eeeeee 50%, white 100%); + background-image: linear-gradient(to bottom, #eeeeee 50%, white 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0); } + +.select2-container--classic .select2-selection--multiple { + background-color: white; + border: 1px solid #aaa; + border-radius: 4px; + cursor: text; + outline: 0; } + .select2-container--classic .select2-selection--multiple:focus { + border: 1px solid #5897fb; } + .select2-container--classic .select2-selection--multiple .select2-selection__rendered { + list-style: none; + margin: 0; + padding: 0 5px; } + .select2-container--classic .select2-selection--multiple .select2-selection__clear { + display: none; } + .select2-container--classic .select2-selection--multiple .select2-selection__choice { + background-color: #e4e4e4; + border: 1px solid #aaa; + border-radius: 4px; + cursor: default; + float: left; + margin-right: 5px; + margin-top: 5px; + padding: 0 5px; } + .select2-container--classic .select2-selection--multiple .select2-selection__choice__remove { + color: #888; + cursor: pointer; + display: inline-block; + font-weight: bold; + margin-right: 2px; } + .select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover { + color: #555; } + +.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice { + float: right; } + +.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice { + margin-left: 5px; + margin-right: auto; } + +.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove { + margin-left: 2px; + margin-right: auto; } + +.select2-container--classic.select2-container--open .select2-selection--multiple { + border: 1px solid #5897fb; } + +.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple { + border-top: none; + border-top-left-radius: 0; + border-top-right-radius: 0; } + +.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple { + border-bottom: none; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } + +.select2-container--classic .select2-search--dropdown .select2-search__field { + border: 1px solid #aaa; + outline: 0; } + +.select2-container--classic .select2-search--inline .select2-search__field { + outline: 0; + box-shadow: none; } + +.select2-container--classic .select2-dropdown { + background-color: white; + border: 1px solid transparent; } + +.select2-container--classic .select2-dropdown--above { + border-bottom: none; } + +.select2-container--classic .select2-dropdown--below { + border-top: none; } + +.select2-container--classic .select2-results > .select2-results__options { + max-height: 200px; + overflow-y: auto; } + +.select2-container--classic .select2-results__option[role=group] { + padding: 0; } + +.select2-container--classic .select2-results__option[aria-disabled=true] { + color: grey; } + +.select2-container--classic .select2-results__option--highlighted[aria-selected] { + background-color: #3875d7; + color: white; } + +.select2-container--classic .select2-results__group { + cursor: default; + display: block; + padding: 6px; } + +.select2-container--classic.select2-container--open .select2-dropdown { + border-color: #5897fb; } diff --git a/django/contrib/admin/static/admin/css/vendor/select2/select2.min.css b/django/contrib/admin/static/admin/css/vendor/select2/select2.min.css new file mode 100644 index 000000000000..76de04d92330 --- /dev/null +++ b/django/contrib/admin/static/admin/css/vendor/select2/select2.min.css @@ -0,0 +1 @@ +.select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-selection--single .select2-selection__clear{position:relative}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline-block;overflow:hidden;padding-left:8px;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-search--inline{float:left}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;padding:0}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-dropdown{background-color:white;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051}.select2-results{display:block}.select2-results__options{list-style:none;margin:0;padding:0}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none}.select2-results__option[aria-selected]{cursor:pointer}.select2-container--open .select2-dropdown{left:0}.select2-container--open .select2-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--open .select2-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-search--dropdown{display:block;padding:4px}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-search--dropdown.select2-search--hide{display:none}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0)}.select2-hidden-accessible{border:0 !important;clip:rect(0 0 0 0) !important;height:1px !important;margin:-1px !important;overflow:hidden !important;padding:0 !important;position:absolute !important;width:1px !important}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow{left:1px;right:auto}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear{display:none}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--default .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text}.select2-container--default .select2-selection--multiple .select2-selection__rendered{box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%}.select2-container--default .select2-selection--multiple .select2-selection__rendered li{list-style:none}.select2-container--default .select2-selection--multiple .select2-selection__placeholder{color:#999;margin-top:5px;float:left}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-top:5px;margin-right:10px}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{color:#999;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#333}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline{float:right}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--default.select2-container--focus .select2-selection--multiple{border:solid black 1px;outline:0}.select2-container--default.select2-container--disabled .select2-selection--multiple{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection__choice__remove{display:none}.select2-container--default.select2-container--open.select2-container--above .select2-selection--single,.select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple{border-top-left-radius:0;border-top-right-radius:0}.select2-container--default.select2-container--open.select2-container--below .select2-selection--single,.select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--default .select2-search--dropdown .select2-search__field{border:1px solid #aaa}.select2-container--default .select2-search--inline .select2-search__field{background:transparent;border:none;outline:0;box-shadow:none;-webkit-appearance:textfield}.select2-container--default .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--default .select2-results__option[role=group]{padding:0}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option .select2-results__option{padding-left:1em}.select2-container--default .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--default .select2-results__option .select2-results__option .select2-results__option{margin-left:-1em;padding-left:2em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-2em;padding-left:3em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-3em;padding-left:4em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-4em;padding-left:5em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-5em;padding-left:6em}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#5897fb;color:white}.select2-container--default .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic .select2-selection--single{background-color:#f7f7f7;border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top, #fff 50%, #eee 100%);background-image:-o-linear-gradient(top, #fff 50%, #eee 100%);background-image:linear-gradient(to bottom, #fff 50%, #eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic .select2-selection--single:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--classic .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-right:10px}.select2-container--classic .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--classic .select2-selection--single .select2-selection__arrow{background-color:#ddd;border:none;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top, #eee 50%, #ccc 100%);background-image:-o-linear-gradient(top, #eee 50%, #ccc 100%);background-image:linear-gradient(to bottom, #eee 50%, #ccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0)}.select2-container--classic .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow{border:none;border-right:1px solid #aaa;border-radius:0;border-top-left-radius:4px;border-bottom-left-radius:4px;left:1px;right:auto}.select2-container--classic.select2-container--open .select2-selection--single{border:1px solid #5897fb}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow{background:transparent;border:none}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single{border-top:none;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top, #fff 0%, #eee 50%);background-image:-o-linear-gradient(top, #fff 0%, #eee 50%);background-image:linear-gradient(to bottom, #fff 0%, #eee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top, #eee 50%, #fff 100%);background-image:-o-linear-gradient(top, #eee 50%, #fff 100%);background-image:linear-gradient(to bottom, #eee 50%, #fff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0)}.select2-container--classic .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0}.select2-container--classic .select2-selection--multiple:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--multiple .select2-selection__rendered{list-style:none;margin:0;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__clear{display:none}.select2-container--classic .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove{color:#888;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover{color:#555}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{float:right}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--classic.select2-container--open .select2-selection--multiple{border:1px solid #5897fb}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--classic .select2-search--dropdown .select2-search__field{border:1px solid #aaa;outline:0}.select2-container--classic .select2-search--inline .select2-search__field{outline:0;box-shadow:none}.select2-container--classic .select2-dropdown{background-color:#fff;border:1px solid transparent}.select2-container--classic .select2-dropdown--above{border-bottom:none}.select2-container--classic .select2-dropdown--below{border-top:none}.select2-container--classic .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--classic .select2-results__option[role=group]{padding:0}.select2-container--classic .select2-results__option[aria-disabled=true]{color:grey}.select2-container--classic .select2-results__option--highlighted[aria-selected]{background-color:#3875d7;color:#fff}.select2-container--classic .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic.select2-container--open .select2-dropdown{border-color:#5897fb} diff --git a/django/contrib/admin/static/admin/fonts/README.txt b/django/contrib/admin/static/admin/fonts/README.txt index cc2135a30ae1..b247bef33cc5 100644 --- a/django/contrib/admin/static/admin/fonts/README.txt +++ b/django/contrib/admin/static/admin/fonts/README.txt @@ -1,2 +1,3 @@ Roboto webfont source: https://www.google.com/fonts/specimen/Roboto +WOFF files extracted using https://github.com/majodev/google-webfonts-helper Weights used in this project: Light (300), Regular (400), Bold (700) diff --git a/django/contrib/admin/static/admin/fonts/Roboto-Bold-webfont.woff b/django/contrib/admin/static/admin/fonts/Roboto-Bold-webfont.woff index 03357ce4f583..6e0f56267035 100644 Binary files a/django/contrib/admin/static/admin/fonts/Roboto-Bold-webfont.woff and b/django/contrib/admin/static/admin/fonts/Roboto-Bold-webfont.woff differ diff --git a/django/contrib/admin/static/admin/fonts/Roboto-Light-webfont.woff b/django/contrib/admin/static/admin/fonts/Roboto-Light-webfont.woff index f6abd871351b..b9e99185c830 100644 Binary files a/django/contrib/admin/static/admin/fonts/Roboto-Light-webfont.woff and b/django/contrib/admin/static/admin/fonts/Roboto-Light-webfont.woff differ diff --git a/django/contrib/admin/static/admin/fonts/Roboto-Regular-webfont.woff b/django/contrib/admin/static/admin/fonts/Roboto-Regular-webfont.woff index 6ff6afd8c863..96c1986f0145 100644 Binary files a/django/contrib/admin/static/admin/fonts/Roboto-Regular-webfont.woff and b/django/contrib/admin/static/admin/fonts/Roboto-Regular-webfont.woff differ diff --git a/django/contrib/admin/static/admin/img/README.txt b/django/contrib/admin/static/admin/img/README.txt index 43373ad1c252..4eb2e492a9be 100644 --- a/django/contrib/admin/static/admin/img/README.txt +++ b/django/contrib/admin/static/admin/img/README.txt @@ -1,6 +1,6 @@ All icons are taken from Font Awesome (http://fontawesome.io/) project. The Font Awesome font is licensed under the SIL OFL 1.1: -- http://scripts.sil.org/OFL +- https://scripts.sil.org/OFL SVG icons source: https://github.com/encharm/Font-Awesome-SVG-PNG Font-Awesome-SVG-PNG is licensed under the MIT license (see file license diff --git a/django/contrib/admin/static/admin/img/icon-viewlink.svg b/django/contrib/admin/static/admin/img/icon-viewlink.svg new file mode 100644 index 000000000000..a1ca1d3f4e24 --- /dev/null +++ b/django/contrib/admin/static/admin/img/icon-viewlink.svg @@ -0,0 +1,3 @@ +<svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"> + <path fill="#2b70bf" d="M1664 960q-152-236-381-353 61 104 61 225 0 185-131.5 316.5t-316.5 131.5-316.5-131.5-131.5-316.5q0-121 61-225-229 117-381 353 133 205 333.5 326.5t434.5 121.5 434.5-121.5 333.5-326.5zm-720-384q0-20-14-34t-34-14q-125 0-214.5 89.5t-89.5 214.5q0 20 14 34t34 14 34-14 14-34q0-86 61-147t147-61q20 0 34-14t14-34zm848 384q0 34-20 69-140 230-376.5 368.5t-499.5 138.5-499.5-139-376.5-368q-20-35-20-69t20-69q140-229 376.5-368t499.5-139 499.5 139 376.5 368q20 35 20 69z"/> +</svg> diff --git a/django/contrib/admin/static/admin/js/SelectBox.js b/django/contrib/admin/static/admin/js/SelectBox.js index 1a14959bcada..2073f03dd819 100644 --- a/django/contrib/admin/static/admin/js/SelectBox.js +++ b/django/contrib/admin/static/admin/js/SelectBox.js @@ -19,7 +19,7 @@ var box = document.getElementById(id); var node; $(box).empty(); // clear all options - var new_options = box.outerHTML.slice(0, -9); // grab just the opening tag + var new_options = box.outerHTML.slice(0, -9); // grab just the opening tag var cache = SelectBox.cache[id]; for (var i = 0, j = cache.length; i < j; i++) { node = cache[i]; @@ -48,7 +48,7 @@ token = tokens[k]; if (node_text.indexOf(token) === -1) { node.displayed = 0; - break; // Once the first token isn't found we're done + break; // Once the first token isn't found we're done } } } diff --git a/django/contrib/admin/static/admin/js/SelectFilter2.js b/django/contrib/admin/static/admin/js/SelectFilter2.js index 0f9a188d4b3a..4221778bb2b0 100644 --- a/django/contrib/admin/static/admin/js/SelectFilter2.js +++ b/django/contrib/admin/static/admin/js/SelectFilter2.js @@ -1,4 +1,4 @@ -/*global SelectBox, addEvent, gettext, interpolate, quickElement, SelectFilter*/ +/*global SelectBox, gettext, interpolate, quickElement, SelectFilter*/ /* SelectFilter2 - Turns a multiple-select box into a filter interface. @@ -118,19 +118,33 @@ Requires jQuery, core.js, and SelectBox.js. } e.preventDefault(); }; - addEvent(choose_all, 'click', function(e) { move_selection(e, this, SelectBox.move_all, field_id + '_from', field_id + '_to'); }); - addEvent(add_link, 'click', function(e) { move_selection(e, this, SelectBox.move, field_id + '_from', field_id + '_to'); }); - addEvent(remove_link, 'click', function(e) { move_selection(e, this, SelectBox.move, field_id + '_to', field_id + '_from'); }); - addEvent(clear_all, 'click', function(e) { move_selection(e, this, SelectBox.move_all, field_id + '_to', field_id + '_from'); }); - addEvent(filter_input, 'keypress', function(e) { SelectFilter.filter_key_press(e, field_id); }); - addEvent(filter_input, 'keyup', function(e) { SelectFilter.filter_key_up(e, field_id); }); - addEvent(filter_input, 'keydown', function(e) { SelectFilter.filter_key_down(e, field_id); }); - addEvent(selector_div, 'change', function(e) { + choose_all.addEventListener('click', function(e) { + move_selection(e, this, SelectBox.move_all, field_id + '_from', field_id + '_to'); + }); + add_link.addEventListener('click', function(e) { + move_selection(e, this, SelectBox.move, field_id + '_from', field_id + '_to'); + }); + remove_link.addEventListener('click', function(e) { + move_selection(e, this, SelectBox.move, field_id + '_to', field_id + '_from'); + }); + clear_all.addEventListener('click', function(e) { + move_selection(e, this, SelectBox.move_all, field_id + '_to', field_id + '_from'); + }); + filter_input.addEventListener('keypress', function(e) { + SelectFilter.filter_key_press(e, field_id); + }); + filter_input.addEventListener('keyup', function(e) { + SelectFilter.filter_key_up(e, field_id); + }); + filter_input.addEventListener('keydown', function(e) { + SelectFilter.filter_key_down(e, field_id); + }); + selector_div.addEventListener('change', function(e) { if (e.target.tagName === 'SELECT') { SelectFilter.refresh_icons(field_id); } }); - addEvent(selector_div, 'dblclick', function(e) { + selector_div.addEventListener('dblclick', function(e) { if (e.target.tagName === 'OPTION') { if (e.target.closest('select').id === field_id + '_to') { SelectBox.move(field_id + '_to', field_id + '_from'); @@ -140,7 +154,9 @@ Requires jQuery, core.js, and SelectBox.js. SelectFilter.refresh_icons(field_id); } }); - addEvent(findForm(from_box), 'submit', function() { SelectBox.select_all(field_id + '_to'); }); + findForm(from_box).addEventListener('submit', function() { + SelectBox.select_all(field_id + '_to'); + }); SelectBox.init(field_id + '_from'); SelectBox.init(field_id + '_to'); // Move selected from_box options to to_box @@ -148,15 +164,9 @@ Requires jQuery, core.js, and SelectBox.js. if (!is_stacked) { // In horizontal mode, give the same height to the two boxes. - var j_from_box = $(from_box); - var j_to_box = $(to_box); - var resize_filters = function() { j_to_box.height($(filter_p).outerHeight() + j_from_box.outerHeight()); }; - if (j_from_box.outerHeight() > 0) { - resize_filters(); // This fieldset is already open. Resize now. - } else { - // This fieldset is probably collapsed. Wait for its 'show' event. - j_to_box.closest('fieldset').one('show.fieldset', resize_filters); - } + var j_from_box = $('#' + field_id + '_from'); + var j_to_box = $('#' + field_id + '_to'); + j_to_box.height($(filter_p).outerHeight() + j_from_box.outerHeight()); } // Initial icon refresh @@ -225,7 +235,7 @@ Requires jQuery, core.js, and SelectBox.js. } }; - addEvent(window, 'load', function(e) { + window.addEventListener('load', function(e) { $('select.selectfilter, select.selectfilterstacked').each(function() { var $el = $(this), data = $el.data(); diff --git a/django/contrib/admin/static/admin/js/actions.js b/django/contrib/admin/static/admin/js/actions.js index 7041701f271b..27c60a6e5a7b 100644 --- a/django/contrib/admin/static/admin/js/actions.js +++ b/django/contrib/admin/static/admin/js/actions.js @@ -8,59 +8,59 @@ var actionCheckboxes = $(this); var list_editable_changed = false; var showQuestion = function() { - $(options.acrossClears).hide(); - $(options.acrossQuestions).show(); - $(options.allContainer).hide(); - }, - showClear = function() { - $(options.acrossClears).show(); - $(options.acrossQuestions).hide(); - $(options.actionContainer).toggleClass(options.selectedClass); - $(options.allContainer).show(); - $(options.counterContainer).hide(); - }, - reset = function() { - $(options.acrossClears).hide(); - $(options.acrossQuestions).hide(); - $(options.allContainer).hide(); - $(options.counterContainer).show(); - }, - clearAcross = function() { - reset(); - $(options.acrossInput).val(0); - $(options.actionContainer).removeClass(options.selectedClass); - }, - checker = function(checked) { - if (checked) { - showQuestion(); - } else { + $(options.acrossClears).hide(); + $(options.acrossQuestions).show(); + $(options.allContainer).hide(); + }, + showClear = function() { + $(options.acrossClears).show(); + $(options.acrossQuestions).hide(); + $(options.actionContainer).toggleClass(options.selectedClass); + $(options.allContainer).show(); + $(options.counterContainer).hide(); + }, + reset = function() { + $(options.acrossClears).hide(); + $(options.acrossQuestions).hide(); + $(options.allContainer).hide(); + $(options.counterContainer).show(); + }, + clearAcross = function() { reset(); - } - $(actionCheckboxes).prop("checked", checked) - .parent().parent().toggleClass(options.selectedClass, checked); - }, - updateCounter = function() { - var sel = $(actionCheckboxes).filter(":checked").length; - // data-actions-icnt is defined in the generated HTML - // and contains the total amount of objects in the queryset - var actions_icnt = $('.action-counter').data('actionsIcnt'); - $(options.counterContainer).html(interpolate( - ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), { - sel: sel, - cnt: actions_icnt - }, true)); - $(options.allToggle).prop("checked", function() { - var value; - if (sel === actionCheckboxes.length) { - value = true; + $(options.acrossInput).val(0); + $(options.actionContainer).removeClass(options.selectedClass); + }, + checker = function(checked) { + if (checked) { showQuestion(); } else { - value = false; - clearAcross(); + reset(); } - return value; - }); - }; + $(actionCheckboxes).prop("checked", checked) + .parent().parent().toggleClass(options.selectedClass, checked); + }, + updateCounter = function() { + var sel = $(actionCheckboxes).filter(":checked").length; + // data-actions-icnt is defined in the generated HTML + // and contains the total amount of objects in the queryset + var actions_icnt = $('.action-counter').data('actionsIcnt'); + $(options.counterContainer).html(interpolate( + ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), { + sel: sel, + cnt: actions_icnt + }, true)); + $(options.allToggle).prop("checked", function() { + var value; + if (sel === actionCheckboxes.length) { + value = true; + showQuestion(); + } else { + value = false; + clearAcross(); + } + return value; + }); + }; // Show counter by default $(options.counterContainer).show(); // Check state of checkboxes and reinit state if needed @@ -71,16 +71,16 @@ showClear(); } }); - $(options.allToggle).show().click(function() { + $(options.allToggle).show().on('click', function() { checker($(this).prop("checked")); updateCounter(); }); - $("a", options.acrossQuestions).click(function(event) { + $("a", options.acrossQuestions).on('click', function(event) { event.preventDefault(); $(options.acrossInput).val(1); showClear(); }); - $("a", options.acrossClears).click(function(event) { + $("a", options.acrossClears).on('click', function(event) { event.preventDefault(); $(options.allToggle).prop("checked", false); clearAcross(); @@ -88,7 +88,7 @@ updateCounter(); }); lastChecked = null; - $(actionCheckboxes).click(function(event) { + $(actionCheckboxes).on('click', function(event) { if (!event) { event = window.event; } var target = event.target ? event.target : event.srcElement; if (lastChecked && $.data(lastChecked) !== $.data(target) && event.shiftKey === true) { @@ -109,15 +109,15 @@ lastChecked = target; updateCounter(); }); - $('form#changelist-form table#result_list tr').find('td:gt(0) :input').change(function() { + $('form#changelist-form table#result_list tr').on('change', 'td:gt(0) :input', function() { list_editable_changed = true; }); - $('form#changelist-form button[name="index"]').click(function(event) { + $('form#changelist-form button[name="index"]').on('click', function(event) { if (list_editable_changed) { return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost.")); } }); - $('form#changelist-form input[name="_save"]').click(function(event) { + $('form#changelist-form input[name="_save"]').on('click', function(event) { var action_changed = false; $('select option:selected', options.actionContainer).each(function() { if ($(this).val()) { diff --git a/django/contrib/admin/static/admin/js/actions.min.js b/django/contrib/admin/static/admin/js/actions.min.js index c83b06a5f8cb..31e83c1f9355 100644 --- a/django/contrib/admin/static/admin/js/actions.min.js +++ b/django/contrib/admin/static/admin/js/actions.min.js @@ -1,6 +1,7 @@ (function(a){var f;a.fn.actions=function(e){var b=a.extend({},a.fn.actions.defaults,e),g=a(this),k=!1,l=function(){a(b.acrossClears).hide();a(b.acrossQuestions).show();a(b.allContainer).hide()},m=function(){a(b.acrossClears).show();a(b.acrossQuestions).hide();a(b.actionContainer).toggleClass(b.selectedClass);a(b.allContainer).show();a(b.counterContainer).hide()},n=function(){a(b.acrossClears).hide();a(b.acrossQuestions).hide();a(b.allContainer).hide();a(b.counterContainer).show()},p=function(){n(); -a(b.acrossInput).val(0);a(b.actionContainer).removeClass(b.selectedClass)},q=function(c){c?l():n();a(g).prop("checked",c).parent().parent().toggleClass(b.selectedClass,c)},h=function(){var c=a(g).filter(":checked").length,d=a(".action-counter").data("actionsIcnt");a(b.counterContainer).html(interpolate(ngettext("%(sel)s of %(cnt)s selected","%(sel)s of %(cnt)s selected",c),{sel:c,cnt:d},!0));a(b.allToggle).prop("checked",function(){var a;c===g.length?(a=!0,l()):(a=!1,p());return a})};a(b.counterContainer).show(); -a(this).filter(":checked").each(function(c){a(this).parent().parent().toggleClass(b.selectedClass);h();1===a(b.acrossInput).val()&&m()});a(b.allToggle).show().click(function(){q(a(this).prop("checked"));h()});a("a",b.acrossQuestions).click(function(c){c.preventDefault();a(b.acrossInput).val(1);m()});a("a",b.acrossClears).click(function(c){c.preventDefault();a(b.allToggle).prop("checked",!1);p();q(0);h()});f=null;a(g).click(function(c){c||(c=window.event);var d=c.target?c.target:c.srcElement;if(f&& -a.data(f)!==a.data(d)&&!0===c.shiftKey){var e=!1;a(f).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked);a(g).each(function(){if(a.data(this)===a.data(f)||a.data(this)===a.data(d))e=e?!1:!0;e&&a(this).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked)})}a(d).parent().parent().toggleClass(b.selectedClass,d.checked);f=d;h()});a("form#changelist-form table#result_list tr").find("td:gt(0) :input").change(function(){k=!0});a('form#changelist-form button[name="index"]').click(function(a){if(k)return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."))}); -a('form#changelist-form input[name="_save"]').click(function(c){var d=!1;a("select option:selected",b.actionContainer).each(function(){a(this).val()&&(d=!0)});if(d)return k?confirm(gettext("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.")):confirm(gettext("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."))})}; -a.fn.actions.defaults={actionContainer:"div.actions",counterContainer:"span.action-counter",allContainer:"div.actions span.all",acrossInput:"div.actions input.select-across",acrossQuestions:"div.actions span.question",acrossClears:"div.actions span.clear",allToggle:"#action-toggle",selectedClass:"selected"};a(document).ready(function(){var e=a("tr input.action-select");0<e.length&&e.actions()})})(django.jQuery); +a(b.acrossInput).val(0);a(b.actionContainer).removeClass(b.selectedClass)},q=function(c){c?l():n();a(g).prop("checked",c).parent().parent().toggleClass(b.selectedClass,c)},h=function(){var c=a(g).filter(":checked").length,d=a(".action-counter").data("actionsIcnt");a(b.counterContainer).html(interpolate(ngettext("%(sel)s of %(cnt)s selected","%(sel)s of %(cnt)s selected",c),{sel:c,cnt:d},!0));a(b.allToggle).prop("checked",function(){if(c===g.length){var a=!0;l()}else a=!1,p();return a})};a(b.counterContainer).show(); +a(this).filter(":checked").each(function(c){a(this).parent().parent().toggleClass(b.selectedClass);h();1===a(b.acrossInput).val()&&m()});a(b.allToggle).show().on("click",function(){q(a(this).prop("checked"));h()});a("a",b.acrossQuestions).on("click",function(c){c.preventDefault();a(b.acrossInput).val(1);m()});a("a",b.acrossClears).on("click",function(c){c.preventDefault();a(b.allToggle).prop("checked",!1);p();q(0);h()});f=null;a(g).on("click",function(c){c||(c=window.event);var d=c.target?c.target: +c.srcElement;if(f&&a.data(f)!==a.data(d)&&!0===c.shiftKey){var e=!1;a(f).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked);a(g).each(function(){if(a.data(this)===a.data(f)||a.data(this)===a.data(d))e=e?!1:!0;e&&a(this).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked)})}a(d).parent().parent().toggleClass(b.selectedClass,d.checked);f=d;h()});a("form#changelist-form table#result_list tr").on("change","td:gt(0) :input",function(){k=!0}); +a('form#changelist-form button[name="index"]').on("click",function(a){if(k)return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."))});a('form#changelist-form input[name="_save"]').on("click",function(c){var d=!1;a("select option:selected",b.actionContainer).each(function(){a(this).val()&&(d=!0)});if(d)return k?confirm(gettext("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.")): +confirm(gettext("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."))})};a.fn.actions.defaults={actionContainer:"div.actions",counterContainer:"span.action-counter",allContainer:"div.actions span.all",acrossInput:"div.actions input.select-across",acrossQuestions:"div.actions span.question",acrossClears:"div.actions span.clear",allToggle:"#action-toggle",selectedClass:"selected"};a(document).ready(function(){var e= +a("tr input.action-select");0<e.length&&e.actions()})})(django.jQuery); diff --git a/django/contrib/admin/static/admin/js/admin/DateTimeShortcuts.js b/django/contrib/admin/static/admin/js/admin/DateTimeShortcuts.js index ce865936543c..1ee7c2a9c4cd 100644 --- a/django/contrib/admin/static/admin/js/admin/DateTimeShortcuts.js +++ b/django/contrib/admin/static/admin/js/admin/DateTimeShortcuts.js @@ -1,4 +1,4 @@ -/*global addEvent, Calendar, cancelEventPropagation, findPosX, findPosY, getStyle, get_format, gettext, interpolate, ngettext, quickElement, removeEvent*/ +/*global Calendar, findPosX, findPosY, getStyle, get_format, gettext, gettext_noop, interpolate, ngettext, quickElement*/ // Inserts shortcut buttons after all of the following: // <input type="text" class="vDateField"> // <input type="text" class="vTimeField"> @@ -8,13 +8,22 @@ calendars: [], calendarInputs: [], clockInputs: [], + clockHours: { + default_: [ + [gettext_noop('Now'), -1], + [gettext_noop('Midnight'), 0], + [gettext_noop('6 a.m.'), 6], + [gettext_noop('Noon'), 12], + [gettext_noop('6 p.m.'), 18] + ] + }, dismissClockFunc: [], dismissCalendarFunc: [], calendarDivName1: 'calendarbox', // name of calendar <div> that gets toggled - calendarDivName2: 'calendarin', // name of <div> that contains calendar - calendarLinkName: 'calendarlink',// name of the link that is used to toggle - clockDivName: 'clockbox', // name of clock <div> that gets toggled - clockLinkName: 'clocklink', // name of the link that is used to toggle + calendarDivName2: 'calendarin', // name of <div> that contains calendar + calendarLinkName: 'calendarlink', // name of the link that is used to toggle + clockDivName: 'clockbox', // name of clock <div> that gets toggled + clockLinkName: 'clocklink', // name of the link that is used to toggle shortCutsClass: 'datetimeshortcuts', // class of the clock and cal shortcuts timezoneWarningClass: 'timezonewarning', // class of the warning for timezone mismatch timezoneOffset: 0, @@ -54,7 +63,6 @@ }, // Add a warning when the time zone in the browser and backend do not match. addTimezoneWarning: function(inp) { - var $ = django.jQuery; var warningClass = DateTimeShortcuts.timezoneWarningClass; var timezoneOffset = DateTimeShortcuts.timezoneOffset / 3600; @@ -64,7 +72,7 @@ } // Check if warning is already there. - if ($(inp).siblings('.' + warningClass).length) { + if (inp.parentNode.querySelectorAll('.' + warningClass).length) { return; } @@ -86,13 +94,11 @@ } message = interpolate(message, [timezoneOffset]); - var $warning = $('<span>'); - $warning.attr('class', warningClass); - $warning.text(message); - - $(inp).parent() - .append($('<br>')) - .append($warning); + var warning = document.createElement('span'); + warning.className = warningClass; + warning.textContent = message; + inp.parentNode.appendChild(document.createElement('br')); + inp.parentNode.appendChild(warning); }, // Add clock widget to a given field addClock: function(inp) { @@ -106,15 +112,15 @@ inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling); var now_link = document.createElement('a'); now_link.setAttribute('href', "#"); - now_link.appendChild(document.createTextNode(gettext('Now'))); - addEvent(now_link, 'click', function(e) { + now_link.textContent = gettext('Now'); + now_link.addEventListener('click', function(e) { e.preventDefault(); DateTimeShortcuts.handleClockQuicklink(num, -1); }); var clock_link = document.createElement('a'); clock_link.setAttribute('href', '#'); clock_link.id = DateTimeShortcuts.clockLinkName + num; - addEvent(clock_link, 'click', function(e) { + clock_link.addEventListener('click', function(e) { e.preventDefault(); // avoid triggering the document click handler to dismiss the clock e.stopPropagation(); @@ -152,46 +158,32 @@ clock_box.className = 'clockbox module'; clock_box.setAttribute('id', DateTimeShortcuts.clockDivName + num); document.body.appendChild(clock_box); - addEvent(clock_box, 'click', cancelEventPropagation); + clock_box.addEventListener('click', function(e) { e.stopPropagation(); }); quickElement('h2', clock_box, gettext('Choose a time')); var time_list = quickElement('ul', clock_box); time_list.className = 'timelist'; - var time_link = quickElement("a", quickElement("li", time_list), gettext("Now"), "href", "#"); - addEvent(time_link, 'click', function(e) { - e.preventDefault(); - DateTimeShortcuts.handleClockQuicklink(num, -1); - }); - time_link = quickElement("a", quickElement("li", time_list), gettext("Midnight"), "href", "#"); - addEvent(time_link, 'click', function(e) { - e.preventDefault(); - DateTimeShortcuts.handleClockQuicklink(num, 0); - }); - time_link = quickElement("a", quickElement("li", time_list), gettext("6 a.m."), "href", "#"); - addEvent(time_link, 'click', function(e) { - e.preventDefault(); - DateTimeShortcuts.handleClockQuicklink(num, 6); - }); - time_link = quickElement("a", quickElement("li", time_list), gettext("Noon"), "href", "#"); - addEvent(time_link, 'click', function(e) { - e.preventDefault(); - DateTimeShortcuts.handleClockQuicklink(num, 12); - }); - time_link = quickElement("a", quickElement("li", time_list), gettext("6 p.m."), "href", "#"); - addEvent(time_link, 'click', function(e) { - e.preventDefault(); - DateTimeShortcuts.handleClockQuicklink(num, 18); + // The list of choices can be overridden in JavaScript like this: + // DateTimeShortcuts.clockHours.name = [['3 a.m.', 3]]; + // where name is the name attribute of the <input>. + var name = typeof DateTimeShortcuts.clockHours[inp.name] === 'undefined' ? 'default_' : inp.name; + DateTimeShortcuts.clockHours[name].forEach(function(element) { + var time_link = quickElement('a', quickElement('li', time_list), gettext(element[0]), 'href', '#'); + time_link.addEventListener('click', function(e) { + e.preventDefault(); + DateTimeShortcuts.handleClockQuicklink(num, element[1]); + }); }); var cancel_p = quickElement('p', clock_box); cancel_p.className = 'calendar-cancel'; var cancel_link = quickElement('a', cancel_p, gettext('Cancel'), 'href', '#'); - addEvent(cancel_link, 'click', function(e) { + cancel_link.addEventListener('click', function(e) { e.preventDefault(); DateTimeShortcuts.dismissClock(num); }); - django.jQuery(document).bind('keyup', function(event) { + document.addEventListener('keyup', function(event) { if (event.which === 27) { // ESC key closes popup DateTimeShortcuts.dismissClock(num); @@ -219,11 +211,11 @@ // Show the clock box clock_box.style.display = 'block'; - addEvent(document, 'click', DateTimeShortcuts.dismissClockFunc[num]); + document.addEventListener('click', DateTimeShortcuts.dismissClockFunc[num]); }, dismissClock: function(num) { document.getElementById(DateTimeShortcuts.clockDivName + num).style.display = 'none'; - removeEvent(document, 'click', DateTimeShortcuts.dismissClockFunc[num]); + document.removeEventListener('click', DateTimeShortcuts.dismissClockFunc[num]); }, handleClockQuicklink: function(num, val) { var d; @@ -251,14 +243,14 @@ var today_link = document.createElement('a'); today_link.setAttribute('href', '#'); today_link.appendChild(document.createTextNode(gettext('Today'))); - addEvent(today_link, 'click', function(e) { + today_link.addEventListener('click', function(e) { e.preventDefault(); DateTimeShortcuts.handleCalendarQuickLink(num, 0); }); var cal_link = document.createElement('a'); cal_link.setAttribute('href', '#'); cal_link.id = DateTimeShortcuts.calendarLinkName + num; - addEvent(cal_link, 'click', function(e) { + cal_link.addEventListener('click', function(e) { e.preventDefault(); // avoid triggering the document click handler to dismiss the calendar e.stopPropagation(); @@ -297,20 +289,20 @@ cal_box.className = 'calendarbox module'; cal_box.setAttribute('id', DateTimeShortcuts.calendarDivName1 + num); document.body.appendChild(cal_box); - addEvent(cal_box, 'click', cancelEventPropagation); + cal_box.addEventListener('click', function(e) { e.stopPropagation(); }); // next-prev links var cal_nav = quickElement('div', cal_box); var cal_nav_prev = quickElement('a', cal_nav, '<', 'href', '#'); cal_nav_prev.className = 'calendarnav-previous'; - addEvent(cal_nav_prev, 'click', function(e) { + cal_nav_prev.addEventListener('click', function(e) { e.preventDefault(); DateTimeShortcuts.drawPrev(num); }); var cal_nav_next = quickElement('a', cal_nav, '>', 'href', '#'); cal_nav_next.className = 'calendarnav-next'; - addEvent(cal_nav_next, 'click', function(e) { + cal_nav_next.addEventListener('click', function(e) { e.preventDefault(); DateTimeShortcuts.drawNext(num); }); @@ -325,19 +317,19 @@ var shortcuts = quickElement('div', cal_box); shortcuts.className = 'calendar-shortcuts'; var day_link = quickElement('a', shortcuts, gettext('Yesterday'), 'href', '#'); - addEvent(day_link, 'click', function(e) { + day_link.addEventListener('click', function(e) { e.preventDefault(); DateTimeShortcuts.handleCalendarQuickLink(num, -1); }); shortcuts.appendChild(document.createTextNode('\u00A0|\u00A0')); day_link = quickElement('a', shortcuts, gettext('Today'), 'href', '#'); - addEvent(day_link, 'click', function(e) { + day_link.addEventListener('click', function(e) { e.preventDefault(); DateTimeShortcuts.handleCalendarQuickLink(num, 0); }); shortcuts.appendChild(document.createTextNode('\u00A0|\u00A0')); day_link = quickElement('a', shortcuts, gettext('Tomorrow'), 'href', '#'); - addEvent(day_link, 'click', function(e) { + day_link.addEventListener('click', function(e) { e.preventDefault(); DateTimeShortcuts.handleCalendarQuickLink(num, +1); }); @@ -346,11 +338,11 @@ var cancel_p = quickElement('p', cal_box); cancel_p.className = 'calendar-cancel'; var cancel_link = quickElement('a', cancel_p, gettext('Cancel'), 'href', '#'); - addEvent(cancel_link, 'click', function(e) { + cancel_link.addEventListener('click', function(e) { e.preventDefault(); DateTimeShortcuts.dismissCalendar(num); }); - django.jQuery(document).bind('keyup', function(event) { + document.addEventListener('keyup', function(event) { if (event.which === 27) { // ESC key closes popup DateTimeShortcuts.dismissCalendar(num); @@ -391,11 +383,11 @@ cal_box.style.top = Math.max(0, findPosY(cal_link) - 75) + 'px'; cal_box.style.display = 'block'; - addEvent(document, 'click', DateTimeShortcuts.dismissCalendarFunc[num]); + document.addEventListener('click', DateTimeShortcuts.dismissCalendarFunc[num]); }, dismissCalendar: function(num) { document.getElementById(DateTimeShortcuts.calendarDivName1 + num).style.display = 'none'; - removeEvent(document, 'click', DateTimeShortcuts.dismissCalendarFunc[num]); + document.removeEventListener('click', DateTimeShortcuts.dismissCalendarFunc[num]); }, drawPrev: function(num) { DateTimeShortcuts.calendars[num].drawPreviousMonth(); @@ -406,11 +398,11 @@ handleCalendarCallback: function(num) { var format = get_format('DATE_INPUT_FORMATS')[0]; // the format needs to be escaped a little - format = format.replace('\\', '\\\\'); - format = format.replace('\r', '\\r'); - format = format.replace('\n', '\\n'); - format = format.replace('\t', '\\t'); - format = format.replace("'", "\\'"); + format = format.replace('\\', '\\\\') + .replace('\r', '\\r') + .replace('\n', '\\n') + .replace('\t', '\\t') + .replace("'", "\\'"); return function(y, m, d) { DateTimeShortcuts.calendarInputs[num].value = new Date(y, m - 1, d).strftime(format); DateTimeShortcuts.calendarInputs[num].focus(); @@ -426,6 +418,6 @@ } }; - addEvent(window, 'load', DateTimeShortcuts.init); + window.addEventListener('load', DateTimeShortcuts.init); window.DateTimeShortcuts = DateTimeShortcuts; })(); diff --git a/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js b/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js index 3fb1e5255043..f4c57c40e5c3 100644 --- a/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js +++ b/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js @@ -58,7 +58,7 @@ function updateRelatedObjectLinks(triggeringLink) { var $this = $(triggeringLink); - var siblings = $this.nextAll('.change-related, .delete-related'); + var siblings = $this.nextAll('.view-related, .change-related, .delete-related'); if (!siblings.length) { return; } @@ -108,6 +108,12 @@ this.value = newId; } }); + selects.next().find('.select2-selection__rendered').each(function() { + // The element can have a clear button as a child. + // Use the lastChild to modify only the displayed value. + this.lastChild.textContent = newRepr; + this.title = newRepr; + }); win.close(); } @@ -140,7 +146,7 @@ window.dismissAddAnotherPopup = dismissAddRelatedObjectPopup; $(document).ready(function() { - $("a[data-popup-opener]").click(function(event) { + $("a[data-popup-opener]").on('click', function(event) { event.preventDefault(); opener.dismissRelatedLookupPopup(window, $(this).data("popup-opener")); }); diff --git a/django/contrib/admin/static/admin/js/autocomplete.js b/django/contrib/admin/static/admin/js/autocomplete.js new file mode 100644 index 000000000000..65c0702dd97c --- /dev/null +++ b/django/contrib/admin/static/admin/js/autocomplete.js @@ -0,0 +1,37 @@ +(function($) { + 'use strict'; + var init = function($element, options) { + var settings = $.extend({ + ajax: { + data: function(params) { + return { + term: params.term, + page: params.page + }; + } + } + }, options); + $element.select2(settings); + }; + + $.fn.djangoAdminSelect2 = function(options) { + var settings = $.extend({}, options); + $.each(this, function(i, element) { + var $element = $(element); + init($element, settings); + }); + return this; + }; + + $(function() { + // Initialize all autocomplete widgets except the one in the template + // form used when a new formset is added. + $('.admin-autocomplete').not('[name*=__prefix__]').djangoAdminSelect2(); + }); + + $(document).on('formset:added', (function() { + return function(event, $newFormset) { + return $newFormset.find('.admin-autocomplete').djangoAdminSelect2(); + }; + })(this)); +}(django.jQuery)); diff --git a/django/contrib/admin/static/admin/js/calendar.js b/django/contrib/admin/static/admin/js/calendar.js index 57655602eef2..a4c047aa72fe 100644 --- a/django/contrib/admin/static/admin/js/calendar.js +++ b/django/contrib/admin/static/admin/js/calendar.js @@ -1,4 +1,4 @@ -/*global gettext, pgettext, get_format, quickElement, removeChildren, addEvent*/ +/*global gettext, pgettext, get_format, quickElement, removeChildren*/ /* calendar.js - Calendar functions by Adrian Holovaty depends on core.js for utility functions like removeChildren or quickElement @@ -103,7 +103,7 @@ depends on core.js for utility functions like removeChildren or quickElement function calendarMonth(y, m) { function onClick(e) { e.preventDefault(); - callback(y, m, django.jQuery(this).text()); + callback(y, m, this.textContent); } return onClick; } @@ -130,7 +130,7 @@ depends on core.js for utility functions like removeChildren or quickElement var cell = quickElement('td', tableRow, '', 'class', todayClass); var link = quickElement('a', cell, currentDay, 'href', '#'); - addEvent(link, 'click', calendarMonth(year, month)); + link.addEventListener('click', calendarMonth(year, month)); currentDay++; } diff --git a/django/contrib/admin/static/admin/js/cancel.js b/django/contrib/admin/static/admin/js/cancel.js index b64138789fd1..8809ee773fd2 100644 --- a/django/contrib/admin/static/admin/js/cancel.js +++ b/django/contrib/admin/static/admin/js/cancel.js @@ -1,9 +1,13 @@ (function($) { 'use strict'; $(function() { - $('.cancel-link').click(function(e) { + $('.cancel-link').on('click', function(e) { e.preventDefault(); - window.history.back(); + if (window.location.search.indexOf('&_popup=1') === -1) { + window.history.back(); // Go back if not a popup. + } else { + window.close(); // Otherwise, close the popup. + } }); }); })(django.jQuery); diff --git a/django/contrib/admin/static/admin/js/collapse.js b/django/contrib/admin/static/admin/js/collapse.js index 7cb936288edd..20e7030e7e15 100644 --- a/django/contrib/admin/static/admin/js/collapse.js +++ b/django/contrib/admin/static/admin/js/collapse.js @@ -1,26 +1,55 @@ /*global gettext*/ -(function($) { +(function() { 'use strict'; - $(document).ready(function() { + var closestElem = function(elem, tagName) { + if (elem.nodeName === tagName.toUpperCase()) { + return elem; + } + if (elem.parentNode.nodeName === 'BODY') { + return null; + } + return elem.parentNode && closestElem(elem.parentNode, tagName); + }; + + window.addEventListener('load', function() { // Add anchor tag for Show/Hide link - $("fieldset.collapse").each(function(i, elem) { + var fieldsets = document.querySelectorAll('fieldset.collapse'); + for (var i = 0; i < fieldsets.length; i++) { + var elem = fieldsets[i]; // Don't hide if fields in this fieldset have errors - if ($(elem).find("div.errors").length === 0) { - $(elem).addClass("collapsed").find("h2").first().append(' (<a id="fieldsetcollapser' + - i + '" class="collapse-toggle" href="#">' + gettext("Show") + - '</a>)'); + if (elem.querySelectorAll('div.errors').length === 0) { + elem.classList.add('collapsed'); + var h2 = elem.querySelector('h2'); + var link = document.createElement('a'); + link.setAttribute('id', 'fieldsetcollapser' + i); + link.setAttribute('class', 'collapse-toggle'); + link.setAttribute('href', '#'); + link.textContent = gettext('Show'); + h2.appendChild(document.createTextNode(' (')); + h2.appendChild(link); + h2.appendChild(document.createTextNode(')')); } - }); - // Add toggle to anchor tag - $("fieldset.collapse a.collapse-toggle").click(function(ev) { - if ($(this).closest("fieldset").hasClass("collapsed")) { - // Show - $(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset", [$(this).attr("id")]); - } else { - // Hide - $(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset", [$(this).attr("id")]); + } + // Add toggle to hide/show anchor tag + var toggleFunc = function(ev) { + if (ev.target.matches('.collapse-toggle')) { + ev.preventDefault(); + ev.stopPropagation(); + var fieldset = closestElem(ev.target, 'fieldset'); + if (fieldset.classList.contains('collapsed')) { + // Show + ev.target.textContent = gettext('Hide'); + fieldset.classList.remove('collapsed'); + } else { + // Hide + ev.target.textContent = gettext('Show'); + fieldset.classList.add('collapsed'); + } } - return false; - }); + }; + var inlineDivs = document.querySelectorAll('fieldset.module'); + for (i = 0; i < inlineDivs.length; i++) { + inlineDivs[i].addEventListener('click', toggleFunc); + } }); -})(django.jQuery); +})(); diff --git a/django/contrib/admin/static/admin/js/collapse.min.js b/django/contrib/admin/static/admin/js/collapse.min.js index 6251d916668f..9e16a21eb966 100644 --- a/django/contrib/admin/static/admin/js/collapse.min.js +++ b/django/contrib/admin/static/admin/js/collapse.min.js @@ -1,2 +1,3 @@ -(function(a){a(document).ready(function(){a("fieldset.collapse").each(function(b,c){0===a(c).find("div.errors").length&&a(c).addClass("collapsed").find("h2").first().append(' (<a id="fieldsetcollapser'+b+'" class="collapse-toggle" href="#">'+gettext("Show")+"</a>)")});a("fieldset.collapse a.collapse-toggle").click(function(b){a(this).closest("fieldset").hasClass("collapsed")?a(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset",[a(this).attr("id")]):a(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset", -[a(this).attr("id")]);return!1})})})(django.jQuery); +(function(){var e=function(b,a){return b.nodeName===a.toUpperCase()?b:"BODY"===b.parentNode.nodeName?null:b.parentNode&&e(b.parentNode,a)};window.addEventListener("load",function(){for(var b=document.querySelectorAll("fieldset.collapse"),a=0;a<b.length;a++){var c=b[a];if(0===c.querySelectorAll("div.errors").length){c.classList.add("collapsed");c=c.querySelector("h2");var d=document.createElement("a");d.setAttribute("id","fieldsetcollapser"+a);d.setAttribute("class","collapse-toggle");d.setAttribute("href", +"#");d.textContent=gettext("Show");c.appendChild(document.createTextNode(" ("));c.appendChild(d);c.appendChild(document.createTextNode(")"))}}b=function(a){if(a.target.matches(".collapse-toggle")){a.preventDefault();a.stopPropagation();var b=e(a.target,"fieldset");b.classList.contains("collapsed")?(a.target.textContent=gettext("Hide"),b.classList.remove("collapsed")):(a.target.textContent=gettext("Show"),b.classList.add("collapsed"))}};c=document.querySelectorAll("fieldset.module");for(a=0;a<c.length;a++)c[a].addEventListener("click", +b)})})(); diff --git a/django/contrib/admin/static/admin/js/core.js b/django/contrib/admin/static/admin/js/core.js index edccdc0217dc..4f2fe1389c4a 100644 --- a/django/contrib/admin/static/admin/js/core.js +++ b/django/contrib/admin/static/admin/js/core.js @@ -4,44 +4,6 @@ var isOpera = (navigator.userAgent.indexOf("Opera") >= 0) && parseFloat(navigator.appVersion); var isIE = ((document.all) && (!isOpera)) && parseFloat(navigator.appVersion.split("MSIE ")[1].split(";")[0]); -// Cross-browser event handlers. -function addEvent(obj, evType, fn) { - 'use strict'; - if (obj.addEventListener) { - obj.addEventListener(evType, fn, false); - return true; - } else if (obj.attachEvent) { - var r = obj.attachEvent("on" + evType, fn); - return r; - } else { - return false; - } -} - -function removeEvent(obj, evType, fn) { - 'use strict'; - if (obj.removeEventListener) { - obj.removeEventListener(evType, fn, false); - return true; - } else if (obj.detachEvent) { - obj.detachEvent("on" + evType, fn); - return true; - } else { - return false; - } -} - -function cancelEventPropagation(e) { - 'use strict'; - if (!e) { - e = window.event; - } - e.cancelBubble = true; - if (e.stopPropagation) { - e.stopPropagation(); - } -} - // quickElement(tagType, parentReference [, textInChildNode, attribute, attributeValue ...]); function quickElement() { 'use strict'; @@ -68,7 +30,7 @@ function removeChildren(a) { // ---------------------------------------------------------------------------- // Find-position functions by PPK -// See http://www.quirksmode.org/js/findpos.html +// See https://www.quirksmode.org/js/findpos.html // ---------------------------------------------------------------------------- function findPosX(obj) { 'use strict'; @@ -191,9 +153,9 @@ function findPosY(obj) { return result; }; -// ---------------------------------------------------------------------------- -// String object extensions -// ---------------------------------------------------------------------------- + // ---------------------------------------------------------------------------- + // String object extensions + // ---------------------------------------------------------------------------- String.prototype.pad_left = function(pad_length, pad_string) { var new_string = this; for (var i = 0; new_string.length < pad_length; i++) { @@ -209,18 +171,18 @@ function findPosY(obj) { var day, month, year; while (i < split_format.length) { switch (split_format[i]) { - case "%d": - day = date[i]; - break; - case "%m": - month = date[i] - 1; - break; - case "%Y": - year = date[i]; - break; - case "%y": - year = date[i]; - break; + case "%d": + day = date[i]; + break; + case "%m": + month = date[i] - 1; + break; + case "%Y": + year = date[i]; + break; + case "%y": + year = date[i]; + break; } ++i; } diff --git a/django/contrib/admin/static/admin/js/inlines.js b/django/contrib/admin/static/admin/js/inlines.js index 4e9bb77e9c45..ba8c9cd7fab2 100644 --- a/django/contrib/admin/static/admin/js/inlines.js +++ b/django/contrib/admin/static/admin/js/inlines.js @@ -13,7 +13,7 @@ * and modified for Django by Jannis Leidel, Travis Swicegood and Julien Phalip. * * Licensed under the New BSD License - * See: http://www.opensource.org/licenses/bsd-license.php + * See: https://opensource.org/licenses/bsd-license.php */ (function($) { 'use strict'; @@ -58,13 +58,13 @@ addButton = $this.filter(":last").next().find("a"); } } - addButton.click(function(e) { + addButton.on('click', function(e) { e.preventDefault(); var template = $("#" + options.prefix + "-empty"); var row = template.clone(true); row.removeClass(options.emptyCssClass) - .addClass(options.formCssClass) - .attr("id", options.prefix + "-" + nextIndex); + .addClass(options.formCssClass) + .attr("id", options.prefix + "-" + nextIndex); if (row.is("tr")) { // If the forms are laid out in table rows, insert // the remove button into the last table cell: @@ -91,7 +91,7 @@ addButton.parent().hide(); } // The delete button of each row triggers a bunch of other things - row.find("a." + options.deleteCssClass).click(function(e1) { + row.find("a." + options.deleteCssClass).on('click', function(e1) { e1.preventDefault(); // Remove the parent form containing this button: row.remove(); @@ -131,26 +131,26 @@ /* Setup plugin defaults */ $.fn.formset.defaults = { - prefix: "form", // The form prefix for your django formset - addText: "add another", // Text for the add link - deleteText: "remove", // Text for the delete link - addCssClass: "add-row", // CSS class applied to the add link - deleteCssClass: "delete-row", // CSS class applied to the delete link - emptyCssClass: "empty-row", // CSS class applied to the empty row - formCssClass: "dynamic-form", // CSS class applied to each form in a formset - added: null, // Function called each time a new form is added - removed: null, // Function called each time a form is deleted - addButton: null // Existing add button to use + prefix: "form", // The form prefix for your django formset + addText: "add another", // Text for the add link + deleteText: "remove", // Text for the delete link + addCssClass: "add-row", // CSS class applied to the add link + deleteCssClass: "delete-row", // CSS class applied to the delete link + emptyCssClass: "empty-row", // CSS class applied to the empty row + formCssClass: "dynamic-form", // CSS class applied to each form in a formset + added: null, // Function called each time a new form is added + removed: null, // Function called each time a form is deleted + addButton: null // Existing add button to use }; // Tabular inlines --------------------------------------------------------- - $.fn.tabularFormset = function(options) { + $.fn.tabularFormset = function(selector, options) { var $rows = $(this); var alternatingRows = function(row) { - $($rows.selector).not(".add-row").removeClass("row1 row2") - .filter(":even").addClass("row1").end() - .filter(":odd").addClass("row2"); + $(selector).not(".add-row").removeClass("row1 row2") + .filter(":even").addClass("row1").end() + .filter(":odd").addClass("row2"); }; var reinitDateTimeShortCuts = function() { @@ -212,10 +212,10 @@ }; // Stacked inlines --------------------------------------------------------- - $.fn.stackedFormset = function(options) { + $.fn.stackedFormset = function(selector, options) { var $rows = $(this); var updateInlineLabel = function(row) { - $($rows.selector).find(".inline_label").each(function(i) { + $(selector).find(".inline_label").each(function(i) { var count = i + 1; $(this).html($(this).html().replace(/(#\d+)/g, "#" + count)); }); @@ -281,13 +281,16 @@ $(document).ready(function() { $(".js-inline-admin-formset").each(function() { var data = $(this).data(), - inlineOptions = data.inlineFormset; + inlineOptions = data.inlineFormset, + selector; switch(data.inlineType) { case "stacked": - $(inlineOptions.name + "-group .inline-related").stackedFormset(inlineOptions.options); + selector = inlineOptions.name + "-group .inline-related"; + $(selector).stackedFormset(selector, inlineOptions.options); break; case "tabular": - $(inlineOptions.name + "-group .tabular.inline-related tbody tr").tabularFormset(inlineOptions.options); + selector = inlineOptions.name + "-group .tabular.inline-related tbody:first > tr"; + $(selector).tabularFormset(selector, inlineOptions.options); break; } }); diff --git a/django/contrib/admin/static/admin/js/inlines.min.js b/django/contrib/admin/static/admin/js/inlines.min.js index 1968ac2ae4bd..65af8eb3ba66 100644 --- a/django/contrib/admin/static/admin/js/inlines.min.js +++ b/django/contrib/admin/static/admin/js/inlines.min.js @@ -1,10 +1,13 @@ -(function(c){c.fn.formset=function(b){var a=c.extend({},c.fn.formset.defaults,b),d=c(this);b=d.parent();var k=function(a,g,l){var b=new RegExp("("+g+"-(\\d+|__prefix__))");g=g+"-"+l;c(a).prop("for")&&c(a).prop("for",c(a).prop("for").replace(b,g));a.id&&(a.id=a.id.replace(b,g));a.name&&(a.name=a.name.replace(b,g))},e=c("#id_"+a.prefix+"-TOTAL_FORMS").prop("autocomplete","off"),l=parseInt(e.val(),10),g=c("#id_"+a.prefix+"-MAX_NUM_FORMS").prop("autocomplete","off"),h=""===g.val()||0<g.val()-e.val(); -d.each(function(g){c(this).not("."+a.emptyCssClass).addClass(a.formCssClass)});if(d.length&&h){var m=a.addButton;null===m&&("TR"===d.prop("tagName")?(d=this.eq(-1).children().length,b.append('<tr class="'+a.addCssClass+'"><td colspan="'+d+'"><a href="#">'+a.addText+"</a></tr>"),m=b.find("tr:last a")):(d.filter(":last").after('<div class="'+a.addCssClass+'"><a href="#">'+a.addText+"</a></div>"),m=d.filter(":last").next().find("a")));m.click(function(b){b.preventDefault();b=c("#"+a.prefix+"-empty"); -var f=b.clone(!0);f.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+l);f.is("tr")?f.children(":last").append('<div><a class="'+a.deleteCssClass+'" href="#">'+a.deleteText+"</a></div>"):f.is("ul")||f.is("ol")?f.append('<li><a class="'+a.deleteCssClass+'" href="#">'+a.deleteText+"</a></li>"):f.children(":first").append('<span><a class="'+a.deleteCssClass+'" href="#">'+a.deleteText+"</a></span>");f.find("*").each(function(){k(this,a.prefix,e.val())});f.insertBefore(c(b)); -c(e).val(parseInt(e.val(),10)+1);l+=1;""!==g.val()&&0>=g.val()-e.val()&&m.parent().hide();f.find("a."+a.deleteCssClass).click(function(b){b.preventDefault();f.remove();--l;a.removed&&a.removed(f);c(document).trigger("formset:removed",[f,a.prefix]);b=c("."+a.formCssClass);c("#id_"+a.prefix+"-TOTAL_FORMS").val(b.length);(""===g.val()||0<g.val()-b.length)&&m.parent().show();var h,d,e=function(){k(this,a.prefix,h)};h=0;for(d=b.length;h<d;h++)k(c(b).get(h),a.prefix,h),c(b.get(h)).find("*").each(e)});a.added&& -a.added(f);c(document).trigger("formset:added",[f,a.prefix])})}return this};c.fn.formset.defaults={prefix:"form",addText:"add another",deleteText:"remove",addCssClass:"add-row",deleteCssClass:"delete-row",emptyCssClass:"empty-row",formCssClass:"dynamic-form",added:null,removed:null,addButton:null};c.fn.tabularFormset=function(b){var a=c(this),d=function(b){c(a.selector).not(".add-row").removeClass("row1 row2").filter(":even").addClass("row1").end().filter(":odd").addClass("row2")},k=function(){"undefined"!== -typeof SelectFilter&&(c(".selectfilter").each(function(a,c){var b=c.name.split("-");SelectFilter.init(c.id,b[b.length-1],!1)}),c(".selectfilterstacked").each(function(a,c){var b=c.name.split("-");SelectFilter.init(c.id,b[b.length-1],!0)}))},e=function(a){a.find(".prepopulated_field").each(function(){var b=c(this).find("input, select, textarea"),h=b.data("dependency_list")||[],d=[];c.each(h,function(c,b){d.push("#"+a.find(".field-"+b).find("input, select, textarea").attr("id"))});d.length&&b.prepopulate(d, -b.attr("maxlength"))})};a.formset({prefix:b.prefix,addText:b.addText,formCssClass:"dynamic-"+b.prefix,deleteCssClass:"inline-deletelink",deleteText:b.deleteText,emptyCssClass:"empty-form",removed:d,added:function(a){e(a);"undefined"!==typeof DateTimeShortcuts&&(c(".datetimeshortcuts").remove(),DateTimeShortcuts.init());k();d(a)},addButton:b.addButton});return a};c.fn.stackedFormset=function(b){var a=c(this),d=function(b){c(a.selector).find(".inline_label").each(function(a){a+=1;c(this).html(c(this).html().replace(/(#\d+)/g, -"#"+a))})},k=function(){"undefined"!==typeof SelectFilter&&(c(".selectfilter").each(function(a,c){var b=c.name.split("-");SelectFilter.init(c.id,b[b.length-1],!1)}),c(".selectfilterstacked").each(function(a,c){var b=c.name.split("-");SelectFilter.init(c.id,b[b.length-1],!0)}))},e=function(a){a.find(".prepopulated_field").each(function(){var b=c(this).find("input, select, textarea"),d=b.data("dependency_list")||[],e=[];c.each(d,function(b,c){e.push("#"+a.find(".form-row .field-"+c).find("input, select, textarea").attr("id"))}); -e.length&&b.prepopulate(e,b.attr("maxlength"))})};a.formset({prefix:b.prefix,addText:b.addText,formCssClass:"dynamic-"+b.prefix,deleteCssClass:"inline-deletelink",deleteText:b.deleteText,emptyCssClass:"empty-form",removed:d,added:function(a){e(a);"undefined"!==typeof DateTimeShortcuts&&(c(".datetimeshortcuts").remove(),DateTimeShortcuts.init());k();d(a)},addButton:b.addButton});return a};c(document).ready(function(){c(".js-inline-admin-formset").each(function(){var b=c(this).data(),a=b.inlineFormset; -switch(b.inlineType){case "stacked":c(a.name+"-group .inline-related").stackedFormset(a.options);break;case "tabular":c(a.name+"-group .tabular.inline-related tbody tr").tabularFormset(a.options)}})})})(django.jQuery); +var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.findInternal=function(b,d,a){b instanceof String&&(b=String(b));for(var c=b.length,f=0;f<c;f++){var g=b[f];if(d.call(a,g,f,b))return{i:f,v:g}}return{i:-1,v:void 0}};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(b,d,a){b!=Array.prototype&&b!=Object.prototype&&(b[d]=a.value)}; +$jscomp.getGlobal=function(b){return"undefined"!=typeof window&&window===b?b:"undefined"!=typeof global&&null!=global?global:b};$jscomp.global=$jscomp.getGlobal(this);$jscomp.polyfill=function(b,d,a,c){if(d){a=$jscomp.global;b=b.split(".");for(c=0;c<b.length-1;c++){var f=b[c];f in a||(a[f]={});a=a[f]}b=b[b.length-1];c=a[b];d=d(c);d!=c&&null!=d&&$jscomp.defineProperty(a,b,{configurable:!0,writable:!0,value:d})}}; +$jscomp.polyfill("Array.prototype.find",function(b){return b?b:function(b,a){return $jscomp.findInternal(this,b,a).v}},"es6","es3"); +(function(b){b.fn.formset=function(d){var a=b.extend({},b.fn.formset.defaults,d),c=b(this);d=c.parent();var f=function(a,e,d){var c=new RegExp("("+e+"-(\\d+|__prefix__))");e=e+"-"+d;b(a).prop("for")&&b(a).prop("for",b(a).prop("for").replace(c,e));a.id&&(a.id=a.id.replace(c,e));a.name&&(a.name=a.name.replace(c,e))},g=b("#id_"+a.prefix+"-TOTAL_FORMS").prop("autocomplete","off"),h=parseInt(g.val(),10),e=b("#id_"+a.prefix+"-MAX_NUM_FORMS").prop("autocomplete","off"),m=""===e.val()||0<e.val()-g.val(); +c.each(function(e){b(this).not("."+a.emptyCssClass).addClass(a.formCssClass)});if(c.length&&m){var l=a.addButton;null===l&&("TR"===c.prop("tagName")?(c=this.eq(-1).children().length,d.append('<tr class="'+a.addCssClass+'"><td colspan="'+c+'"><a href="#">'+a.addText+"</a></tr>"),l=d.find("tr:last a")):(c.filter(":last").after('<div class="'+a.addCssClass+'"><a href="#">'+a.addText+"</a></div>"),l=c.filter(":last").next().find("a")));l.on("click",function(d){d.preventDefault();d=b("#"+a.prefix+"-empty"); +var c=d.clone(!0);c.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+h);c.is("tr")?c.children(":last").append('<div><a class="'+a.deleteCssClass+'" href="#">'+a.deleteText+"</a></div>"):c.is("ul")||c.is("ol")?c.append('<li><a class="'+a.deleteCssClass+'" href="#">'+a.deleteText+"</a></li>"):c.children(":first").append('<span><a class="'+a.deleteCssClass+'" href="#">'+a.deleteText+"</a></span>");c.find("*").each(function(){f(this,a.prefix,g.val())});c.insertBefore(b(d)); +b(g).val(parseInt(g.val(),10)+1);h+=1;""!==e.val()&&0>=e.val()-g.val()&&l.parent().hide();c.find("a."+a.deleteCssClass).on("click",function(d){d.preventDefault();c.remove();--h;a.removed&&a.removed(c);b(document).trigger("formset:removed",[c,a.prefix]);d=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(d.length);(""===e.val()||0<e.val()-d.length)&&l.parent().show();var m,g=function(){f(this,a.prefix,k)};var k=0;for(m=d.length;k<m;k++)f(b(d).get(k),a.prefix,k),b(d.get(k)).find("*").each(g)}); +a.added&&a.added(c);b(document).trigger("formset:added",[c,a.prefix])})}return this};b.fn.formset.defaults={prefix:"form",addText:"add another",deleteText:"remove",addCssClass:"add-row",deleteCssClass:"delete-row",emptyCssClass:"empty-row",formCssClass:"dynamic-form",added:null,removed:null,addButton:null};b.fn.tabularFormset=function(d,a){var c=b(this),f=function(a){b(d).not(".add-row").removeClass("row1 row2").filter(":even").addClass("row1").end().filter(":odd").addClass("row2")},g=function(){"undefined"!== +typeof SelectFilter&&(b(".selectfilter").each(function(b,a){b=a.name.split("-");SelectFilter.init(a.id,b[b.length-1],!1)}),b(".selectfilterstacked").each(function(b,a){b=a.name.split("-");SelectFilter.init(a.id,b[b.length-1],!0)}))},h=function(a){a.find(".prepopulated_field").each(function(){var c=b(this).find("input, select, textarea"),d=c.data("dependency_list")||[],e=[];b.each(d,function(b,c){e.push("#"+a.find(".field-"+c).find("input, select, textarea").attr("id"))});e.length&&c.prepopulate(e, +c.attr("maxlength"))})};c.formset({prefix:a.prefix,addText:a.addText,formCssClass:"dynamic-"+a.prefix,deleteCssClass:"inline-deletelink",deleteText:a.deleteText,emptyCssClass:"empty-form",removed:f,added:function(a){h(a);"undefined"!==typeof DateTimeShortcuts&&(b(".datetimeshortcuts").remove(),DateTimeShortcuts.init());g();f(a)},addButton:a.addButton});return c};b.fn.stackedFormset=function(d,a){var c=b(this),f=function(a){b(d).find(".inline_label").each(function(a){a+=1;b(this).html(b(this).html().replace(/(#\d+)/g, +"#"+a))})},g=function(){"undefined"!==typeof SelectFilter&&(b(".selectfilter").each(function(a,b){a=b.name.split("-");SelectFilter.init(b.id,a[a.length-1],!1)}),b(".selectfilterstacked").each(function(a,b){a=b.name.split("-");SelectFilter.init(b.id,a[a.length-1],!0)}))},h=function(a){a.find(".prepopulated_field").each(function(){var c=b(this).find("input, select, textarea"),d=c.data("dependency_list")||[],e=[];b.each(d,function(b,c){e.push("#"+a.find(".form-row .field-"+c).find("input, select, textarea").attr("id"))}); +e.length&&c.prepopulate(e,c.attr("maxlength"))})};c.formset({prefix:a.prefix,addText:a.addText,formCssClass:"dynamic-"+a.prefix,deleteCssClass:"inline-deletelink",deleteText:a.deleteText,emptyCssClass:"empty-form",removed:f,added:function(a){h(a);"undefined"!==typeof DateTimeShortcuts&&(b(".datetimeshortcuts").remove(),DateTimeShortcuts.init());g();f(a)},addButton:a.addButton});return c};b(document).ready(function(){b(".js-inline-admin-formset").each(function(){var d=b(this).data(),a=d.inlineFormset; +switch(d.inlineType){case "stacked":d=a.name+"-group .inline-related";b(d).stackedFormset(d,a.options);break;case "tabular":d=a.name+"-group .tabular.inline-related tbody:first > tr",b(d).tabularFormset(d,a.options)}})})})(django.jQuery); diff --git a/django/contrib/admin/static/admin/js/prepopulate.js b/django/contrib/admin/static/admin/js/prepopulate.js index 5d4b0e8c2773..bef45a3adb35 100644 --- a/django/contrib/admin/static/admin/js/prepopulate.js +++ b/django/contrib/admin/static/admin/js/prepopulate.js @@ -30,12 +30,12 @@ }; prepopulatedField.data('_changed', false); - prepopulatedField.change(function() { + prepopulatedField.on('change', function() { prepopulatedField.data('_changed', true); }); if (!prepopulatedField.val()) { - $(dependencies.join(',')).keyup(populate).change(populate).focus(populate); + $(dependencies.join(',')).on('keyup change focus', populate); } }); }; diff --git a/django/contrib/admin/static/admin/js/prepopulate.min.js b/django/contrib/admin/static/admin/js/prepopulate.min.js index 75f3c17aa98b..43c1b79e528e 100644 --- a/django/contrib/admin/static/admin/js/prepopulate.min.js +++ b/django/contrib/admin/static/admin/js/prepopulate.min.js @@ -1 +1 @@ -(function(c){c.fn.prepopulate=function(e,f,g){return this.each(function(){var a=c(this),b=function(){if(!a.data("_changed")){var b=[];c.each(e,function(a,d){d=c(d);0<d.val().length&&b.push(d.val())});a.val(URLify(b.join(" "),f,g))}};a.data("_changed",!1);a.change(function(){a.data("_changed",!0)});a.val()||c(e.join(",")).keyup(b).change(b).focus(b)})}})(django.jQuery); +(function(b){b.fn.prepopulate=function(d,f,g){return this.each(function(){var a=b(this),h=function(){if(!a.data("_changed")){var e=[];b.each(d,function(a,c){c=b(c);0<c.val().length&&e.push(c.val())});a.val(URLify(e.join(" "),f,g))}};a.data("_changed",!1);a.on("change",function(){a.data("_changed",!0)});if(!a.val())b(d.join(",")).on("keyup change focus",h)})}})(django.jQuery); diff --git a/django/contrib/admin/static/admin/js/urlify.js b/django/contrib/admin/static/admin/js/urlify.js index 9dcbc82d13a7..c3342b9d59cd 100644 --- a/django/contrib/admin/static/admin/js/urlify.js +++ b/django/contrib/admin/static/admin/js/urlify.js @@ -58,6 +58,14 @@ 'ů': 'u', 'ž': 'z', 'Č': 'C', 'Ď': 'D', 'Ě': 'E', 'Ň': 'N', 'Ř': 'R', 'Š': 'S', 'Ť': 'T', 'Ů': 'U', 'Ž': 'Z' }; + var SLOVAK_MAP = { + 'á': 'a', 'ä': 'a', 'č': 'c', 'ď': 'd', 'é': 'e', 'í': 'i', 'ľ': 'l', + 'ĺ': 'l', 'ň': 'n', 'ó': 'o', 'ô': 'o', 'ŕ': 'r', 'š': 's', 'ť': 't', + 'ú': 'u', 'ý': 'y', 'ž': 'z', + 'Á': 'a', 'Ä': 'A', 'Č': 'C', 'Ď': 'D', 'É': 'E', 'Í': 'I', 'Ľ': 'L', + 'Ĺ': 'L', 'Ň': 'N', 'Ó': 'O', 'Ô': 'O', 'Ŕ': 'R', 'Š': 'S', 'Ť': 'T', + 'Ú': 'U', 'Ý': 'Y', 'Ž': 'Z' + }; var POLISH_MAP = { 'ą': 'a', 'ć': 'c', 'ę': 'e', 'ł': 'l', 'ń': 'n', 'ó': 'o', 'ś': 's', 'ź': 'z', 'ż': 'z', @@ -108,6 +116,7 @@ RUSSIAN_MAP, UKRAINIAN_MAP, CZECH_MAP, + SLOVAK_MAP, POLISH_MAP, LATVIAN_MAP, ARABIC_MAP, @@ -119,7 +128,7 @@ var Downcoder = { 'Initialize': function() { - if (Downcoder.map) { // already made + if (Downcoder.map) { // already made return; } Downcoder.map = {}; @@ -155,25 +164,32 @@ if (!allowUnicode) { s = downcode(s); } - var removelist = [ - "a", "an", "as", "at", "before", "but", "by", "for", "from", "is", - "in", "into", "like", "of", "off", "on", "onto", "per", "since", - "than", "the", "this", "that", "to", "up", "via", "with" - ]; - var r = new RegExp('\\b(' + removelist.join('|') + ')\\b', 'gi'); - s = s.replace(r, ''); + var hasUnicodeChars = /[^\u0000-\u007f]/.test(s); + // Remove English words only if the string contains ASCII (English) + // characters. + if (!hasUnicodeChars) { + var removeList = [ + "a", "an", "as", "at", "before", "but", "by", "for", "from", + "is", "in", "into", "like", "of", "off", "on", "onto", "per", + "since", "than", "the", "this", "that", "to", "up", "via", + "with" + ]; + var r = new RegExp('\\b(' + removeList.join('|') + ')\\b', 'gi'); + s = s.replace(r, ''); + } // if downcode doesn't hit, the char will be stripped here if (allowUnicode) { // Keep Unicode letters including both lowercase and uppercase // characters, whitespace, and dash; remove other characters. s = XRegExp.replace(s, XRegExp('[^-_\\p{L}\\p{N}\\s]', 'g'), ''); } else { - s = s.replace(/[^-\w\s]/g, ''); // remove unneeded chars + s = s.replace(/[^-\w\s]/g, ''); // remove unneeded chars } - s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces - s = s.replace(/[-\s]+/g, '-'); // convert spaces to hyphens - s = s.toLowerCase(); // convert to lowercase - return s.substring(0, num_chars); // trim to first num_chars chars + s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces + s = s.replace(/[-\s]+/g, '-'); // convert spaces to hyphens + s = s.substring(0, num_chars); // trim to first num_chars chars + s = s.replace(/-+$/g, ''); // trim any trailing hyphens + return s.toLowerCase(); // convert to lowercase } window.URLify = URLify; })(); diff --git a/django/contrib/admin/static/admin/js/vendor/jquery/LICENSE-JQUERY.txt b/django/contrib/admin/static/admin/js/vendor/jquery/LICENSE.txt similarity index 79% rename from django/contrib/admin/static/admin/js/vendor/jquery/LICENSE-JQUERY.txt rename to django/contrib/admin/static/admin/js/vendor/jquery/LICENSE.txt index d930e62ac149..e3dbacb999ce 100644 --- a/django/contrib/admin/static/admin/js/vendor/jquery/LICENSE-JQUERY.txt +++ b/django/contrib/admin/static/admin/js/vendor/jquery/LICENSE.txt @@ -1,10 +1,4 @@ -Copyright jQuery Foundation and other contributors, https://jquery.org/ - -This software consists of voluntary contributions made by many -individuals. For exact contribution history, see the revision history -available at https://github.com/jquery/jquery - -==== +Copyright JS Foundation and other contributors, https://js.foundation/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/django/contrib/admin/static/admin/js/vendor/jquery/jquery.js b/django/contrib/admin/static/admin/js/vendor/jquery/jquery.js index 385474756fe0..50937333b99a 100644 --- a/django/contrib/admin/static/admin/js/vendor/jquery/jquery.js +++ b/django/contrib/admin/static/admin/js/vendor/jquery/jquery.js @@ -1,20 +1,22 @@ /*! - * jQuery JavaScript Library v2.2.3 - * http://jquery.com/ + * jQuery JavaScript Library v3.5.1 + * https://jquery.com/ * * Includes Sizzle.js - * http://sizzlejs.com/ + * https://sizzlejs.com/ * - * Copyright jQuery Foundation and other contributors + * Copyright JS Foundation and other contributors * Released under the MIT license - * http://jquery.org/license + * https://jquery.org/license * - * Date: 2016-04-05T19:26Z + * Date: 2020-05-04T22:49Z */ +( function( global, factory ) { -(function( global, factory ) { + "use strict"; if ( typeof module === "object" && typeof module.exports === "object" ) { + // For CommonJS and CommonJS-like environments where a proper `window` // is present, execute the factory and get jQuery. // For environments that do not have a `window` with a `document` @@ -35,20 +37,26 @@ } // Pass this if window is not defined yet -}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; -// Support: Firefox 18+ -// Can't be in strict mode, several libs including ASP.NET trace -// the stack via arguments.caller.callee and Firefox dies if -// you try to trace through "use strict" call chains. (#13335) -//"use strict"; var arr = []; -var document = window.document; +var getProto = Object.getPrototypeOf; var slice = arr.slice; -var concat = arr.concat; +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + var push = arr.push; @@ -60,12 +68,86 @@ var toString = class2type.toString; var hasOwn = class2type.hasOwnProperty; +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + var support = {}; +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML <object> elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + return typeof obj === "function" && typeof obj.nodeType !== "number"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + +var document = window.document; + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + var - version = "2.2.3", + version = "3.5.1", // Define a local copy of jQuery jQuery = function( selector, context ) { @@ -73,19 +155,6 @@ var // The jQuery object is actually just the init constructor 'enhanced' // Need init if jQuery is called (just allow error to be thrown if not included) return new jQuery.fn.init( selector, context ); - }, - - // Support: Android<4.1 - // Make sure we trim BOM and NBSP - rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, - - // Matches dashed string for camelizing - rmsPrefix = /^-ms-/, - rdashAlpha = /-([\da-z])/gi, - - // Used by jQuery.camelCase as callback to replace() - fcamelCase = function( all, letter ) { - return letter.toUpperCase(); }; jQuery.fn = jQuery.prototype = { @@ -95,9 +164,6 @@ jQuery.fn = jQuery.prototype = { constructor: jQuery, - // Start with an empty selector - selector: "", - // The default length of a jQuery object is 0 length: 0, @@ -108,13 +174,14 @@ jQuery.fn = jQuery.prototype = { // Get the Nth element in the matched element set OR // Get the whole matched element set as a clean array get: function( num ) { - return num != null ? - // Return just the one element from the set - ( num < 0 ? this[ num + this.length ] : this[ num ] ) : + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } - // Return all the elements in a clean array - slice.call( this ); + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; }, // Take an array of elements and push it onto the stack @@ -126,7 +193,6 @@ jQuery.fn = jQuery.prototype = { // Add the old object onto the stack (as a reference) ret.prevObject = this; - ret.context = this.context; // Return the newly-formed element set return ret; @@ -155,6 +221,18 @@ jQuery.fn = jQuery.prototype = { return this.eq( -1 ); }, + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + eq: function( i ) { var len = this.length, j = +i + ( i < 0 ? len : 0 ); @@ -189,7 +267,7 @@ jQuery.extend = jQuery.fn.extend = function() { } // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { + if ( typeof target !== "object" && !isFunction( target ) ) { target = {}; } @@ -206,25 +284,28 @@ jQuery.extend = jQuery.fn.extend = function() { // Extend the base object for ( name in options ) { - src = target[ name ]; copy = options[ name ]; + // Prevent Object.prototype pollution // Prevent never-ending loop - if ( target === copy ) { + if ( name === "__proto__" || target === copy ) { continue; } // Recurse if we're merging plain objects or arrays if ( deep && copy && ( jQuery.isPlainObject( copy ) || - ( copyIsArray = jQuery.isArray( copy ) ) ) ) { - - if ( copyIsArray ) { - copyIsArray = false; - clone = src && jQuery.isArray( src ) ? src : []; - + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; } else { - clone = src && jQuery.isPlainObject( src ) ? src : {}; + clone = src; } + copyIsArray = false; // Never move original objects, clone them target[ name ] = jQuery.extend( deep, clone, copy ); @@ -255,105 +336,40 @@ jQuery.extend( { noop: function() {}, - isFunction: function( obj ) { - return jQuery.type( obj ) === "function"; - }, - - isArray: Array.isArray, - - isWindow: function( obj ) { - return obj != null && obj === obj.window; - }, - - isNumeric: function( obj ) { - - // parseFloat NaNs numeric-cast false positives (null|true|false|"") - // ...but misinterprets leading-number strings, particularly hex literals ("0x...") - // subtraction forces infinities to NaN - // adding 1 corrects loss of precision from parseFloat (#15100) - var realStringObj = obj && obj.toString(); - return !jQuery.isArray( obj ) && ( realStringObj - parseFloat( realStringObj ) + 1 ) >= 0; - }, - isPlainObject: function( obj ) { - var key; + var proto, Ctor; - // Not plain objects: - // - Any object or value whose internal [[Class]] property is not "[object Object]" - // - DOM nodes - // - window - if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { return false; } - // Not own constructor property must be Object - if ( obj.constructor && - !hasOwn.call( obj, "constructor" ) && - !hasOwn.call( obj.constructor.prototype || {}, "isPrototypeOf" ) ) { - return false; - } + proto = getProto( obj ); - // Own properties are enumerated firstly, so to speed up, - // if last one is own, then all properties are own - for ( key in obj ) {} + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } - return key === undefined || hasOwn.call( obj, key ); + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; }, isEmptyObject: function( obj ) { var name; + for ( name in obj ) { return false; } return true; }, - type: function( obj ) { - if ( obj == null ) { - return obj + ""; - } - - // Support: Android<4.0, iOS<6 (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call( obj ) ] || "object" : - typeof obj; - }, - - // Evaluates a script in a global context - globalEval: function( code ) { - var script, - indirect = eval; - - code = jQuery.trim( code ); - - if ( code ) { - - // If the code includes a valid, prologue position - // strict mode pragma, execute code by injecting a - // script tag into the document. - if ( code.indexOf( "use strict" ) === 1 ) { - script = document.createElement( "script" ); - script.text = code; - document.head.appendChild( script ).parentNode.removeChild( script ); - } else { - - // Otherwise, avoid the DOM node creation, insertion - // and removal by using an indirect global eval - - indirect( code ); - } - } - }, - - // Convert dashed to camelCase; used by the css and data modules - // Support: IE9-11+ - // Microsoft forgot to hump their vendor prefix (#9572) - camelCase: function( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); }, each: function( obj, callback ) { @@ -377,13 +393,6 @@ jQuery.extend( { return obj; }, - // Support: Android<4.1 - trim: function( text ) { - return text == null ? - "" : - ( text + "" ).replace( rtrim, "" ); - }, - // results is for internal usage only makeArray: function( arr, results ) { var ret = results || []; @@ -406,6 +415,8 @@ jQuery.extend( { return arr == null ? -1 : indexOf.call( arr, elem, i ); }, + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit merge: function( first, second ) { var len = +second.length, j = 0, @@ -468,74 +479,37 @@ jQuery.extend( { } // Flatten any nested arrays - return concat.apply( [], ret ); + return flat( ret ); }, // A global GUID counter for objects guid: 1, - // Bind a function to a context, optionally partially applying any - // arguments. - proxy: function( fn, context ) { - var tmp, args, proxy; - - if ( typeof context === "string" ) { - tmp = fn[ context ]; - context = fn; - fn = tmp; - } - - // Quick check to determine if target is callable, in the spec - // this throws a TypeError, but we will just return undefined. - if ( !jQuery.isFunction( fn ) ) { - return undefined; - } - - // Simulated bind - args = slice.call( arguments, 2 ); - proxy = function() { - return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); - }; - - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || jQuery.guid++; - - return proxy; - }, - - now: Date.now, - // jQuery.support is not used in Core but other projects attach their // properties to it so it needs to exist. support: support } ); -// JSHint would error on this code due to the Symbol not being defined in ES5. -// Defining this global in .jshintrc would create a danger of using the global -// unguarded in another place, it seems safer to just disable JSHint for these -// three lines. -/* jshint ignore: start */ if ( typeof Symbol === "function" ) { jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; } -/* jshint ignore: end */ // Populate the class2type map jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), -function( i, name ) { +function( _i, name ) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); } ); function isArrayLike( obj ) { - // Support: iOS 8.2 (not reproducible in simulator) + // Support: real iOS 8.2 only (not reproducible in simulator) // `in` check used to prevent JIT error (gh-2145) // hasOwn isn't used here due to false negatives // regarding Nodelist length in IE var length = !!obj && "length" in obj && obj.length, - type = jQuery.type( obj ); + type = toType( obj ); - if ( type === "function" || jQuery.isWindow( obj ) ) { + if ( isFunction( obj ) || isWindow( obj ) ) { return false; } @@ -544,17 +518,16 @@ function isArrayLike( obj ) { } var Sizzle = /*! - * Sizzle CSS Selector Engine v2.2.1 - * http://sizzlejs.com/ + * Sizzle CSS Selector Engine v2.3.5 + * https://sizzlejs.com/ * - * Copyright jQuery Foundation and other contributors + * Copyright JS Foundation and other contributors * Released under the MIT license - * http://jquery.org/license + * https://js.foundation/ * - * Date: 2015-10-17 + * Date: 2020-03-14 */ -(function( window ) { - +( function( window ) { var i, support, Expr, @@ -585,6 +558,7 @@ var i, classCache = createCache(), tokenCache = createCache(), compilerCache = createCache(), + nonnativeSelectorCache = createCache(), sortOrder = function( a, b ) { if ( a === b ) { hasDuplicate = true; @@ -592,65 +566,72 @@ var i, return 0; }, - // General-purpose constants - MAX_NEGATIVE = 1 << 31, - // Instance methods - hasOwn = ({}).hasOwnProperty, + hasOwn = ( {} ).hasOwnProperty, arr = [], pop = arr.pop, - push_native = arr.push, + pushNative = arr.push, push = arr.push, slice = arr.slice, + // Use a stripped-down indexOf as it's faster than native - // http://jsperf.com/thor-indexof-vs-for/5 + // https://jsperf.com/thor-indexof-vs-for/5 indexOf = function( list, elem ) { var i = 0, len = list.length; for ( ; i < len; i++ ) { - if ( list[i] === elem ) { + if ( list[ i ] === elem ) { return i; } } return -1; }, - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + + "ismap|loop|multiple|open|readonly|required|scoped", // Regular expressions // http://www.w3.org/TR/css3-selectors/#whitespace whitespace = "[\\x20\\t\\r\\n\\f]", - // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + // Operator (capture 2) "*([*^$|!~]?=)" + whitespace + - // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + - "*\\]", + + // "Attribute values must be CSS identifiers [capture 5] + // or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", pseudos = ":(" + identifier + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: // 1. quoted (capture 3; capture 4 or capture 5) "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) ".*" + ")\\)|)", // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + + whitespace + "+$", "g" ), rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), - - rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + + "*" ), + rdescend = new RegExp( whitespace + "|>" ), rpseudo = new RegExp( pseudos ), ridentifier = new RegExp( "^" + identifier + "$" ), @@ -661,16 +642,19 @@ var i, "TAG": new RegExp( "^(" + identifier + "|[*])" ), "ATTR": new RegExp( "^" + attributes ), "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + - "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + - "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + - whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + "needsContext": new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) }, + rhtml = /HTML$/i, rinputs = /^(?:input|select|textarea|button)$/i, rheader = /^h\d$/i, @@ -680,47 +664,79 @@ var i, rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, rsibling = /[+~]/, - rescape = /'|\\/g, - - // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), - funescape = function( _, escaped, escapedWhitespace ) { - var high = "0x" + escaped - 0x10000; - // NaN means non-codepoint - // Support: Firefox<24 - // Workaround erroneous numeric interpretation of +"0x" - return high !== high || escapedWhitespace ? - escaped : + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + return nonHex ? + + // Strip the backslash prefix from a non-hex escape sequence + nonHex : + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair high < 0 ? - // BMP codepoint String.fromCharCode( high + 0x10000 ) : - // Supplemental Plane codepoint (surrogate pair) String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); }, + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + // Used for iframes // See setDocument() // Removing the function wrapper causes a "Permission Denied" // error in IE unloadHandler = function() { setDocument(); - }; + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); // Optimize for push.apply( _, NodeList ) try { push.apply( - (arr = slice.call( preferredDoc.childNodes )), + ( arr = slice.call( preferredDoc.childNodes ) ), preferredDoc.childNodes ); + // Support: Android<4.0 // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions arr[ preferredDoc.childNodes.length ].nodeType; } catch ( e ) { push = { apply: arr.length ? // Leverage slice if possible function( target, els ) { - push_native.apply( target, slice.call(els) ); + pushNative.apply( target, slice.call( els ) ); } : // Support: IE<9 @@ -728,15 +744,16 @@ try { function( target, els ) { var j = target.length, i = 0; + // Can't trust NodeList.length - while ( (target[j++] = els[i++]) ) {} + while ( ( target[ j++ ] = els[ i++ ] ) ) {} target.length = j - 1; } }; } function Sizzle( selector, context, results, seed ) { - var m, i, elem, nid, nidselect, match, groups, newSelector, + var m, i, elem, nid, match, groups, newSelector, newContext = context && context.ownerDocument, // nodeType defaults to 9, since context defaults to document @@ -753,24 +770,21 @@ function Sizzle( selector, context, results, seed ) { // Try to shortcut find operations (as opposed to filters) in HTML documents if ( !seed ) { - - if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { - setDocument( context ); - } + setDocument( context ); context = context || document; if ( documentIsHTML ) { // If the selector is sufficiently simple, try using a "get*By*" DOM method // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { // ID selector - if ( (m = match[1]) ) { + if ( ( m = match[ 1 ] ) ) { // Document context if ( nodeType === 9 ) { - if ( (elem = context.getElementById( m )) ) { + if ( ( elem = context.getElementById( m ) ) ) { // Support: IE, Opera, Webkit // TODO: identify versions @@ -789,7 +803,7 @@ function Sizzle( selector, context, results, seed ) { // Support: IE, Opera, Webkit // TODO: identify versions // getElementById can match elements by name instead of ID - if ( newContext && (elem = newContext.getElementById( m )) && + if ( newContext && ( elem = newContext.getElementById( m ) ) && contains( context, elem ) && elem.id === m ) { @@ -799,12 +813,12 @@ function Sizzle( selector, context, results, seed ) { } // Type selector - } else if ( match[2] ) { + } else if ( match[ 2 ] ) { push.apply( results, context.getElementsByTagName( selector ) ); return results; // Class selector - } else if ( (m = match[3]) && support.getElementsByClassName && + } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && context.getElementsByClassName ) { push.apply( results, context.getElementsByClassName( m ) ); @@ -814,51 +828,62 @@ function Sizzle( selector, context, results, seed ) { // Take advantage of querySelectorAll if ( support.qsa && - !compilerCache[ selector + " " ] && - (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && - if ( nodeType !== 1 ) { - newContext = context; - newSelector = selector; - - // qSA looks outside Element context, which is not what we want - // Thanks to Andrew Dupont for this workaround technique - // Support: IE <=8 + // Support: IE 8 only // Exclude object elements - } else if ( context.nodeName.toLowerCase() !== "object" ) { + ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { - // Capture the context ID, setting it first if necessary - if ( (nid = context.getAttribute( "id" )) ) { - nid = nid.replace( rescape, "\\$&" ); - } else { - context.setAttribute( "id", (nid = expando) ); + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + if ( newContext !== context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } } // Prefix every selector in the list groups = tokenize( selector ); i = groups.length; - nidselect = ridentifier.test( nid ) ? "#" + nid : "[id='" + nid + "']"; while ( i-- ) { - groups[i] = nidselect + " " + toSelector( groups[i] ); + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); } newSelector = groups.join( "," ); - - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; } - if ( newSelector ) { - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); } } } @@ -879,12 +904,14 @@ function createCache() { var keys = []; function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries delete cache[ keys.shift() ]; } - return (cache[ key + " " ] = value); + return ( cache[ key + " " ] = value ); } return cache; } @@ -900,22 +927,24 @@ function markFunction( fn ) { /** * Support testing using an element - * @param {Function} fn Passed the created div and expects a boolean result + * @param {Function} fn Passed the created element and returns a boolean result */ function assert( fn ) { - var div = document.createElement("div"); + var el = document.createElement( "fieldset" ); try { - return !!fn( div ); - } catch (e) { + return !!fn( el ); + } catch ( e ) { return false; } finally { + // Remove from its parent by default - if ( div.parentNode ) { - div.parentNode.removeChild( div ); + if ( el.parentNode ) { + el.parentNode.removeChild( el ); } + // release memory in IE - div = null; + el = null; } } @@ -925,11 +954,11 @@ function assert( fn ) { * @param {Function} handler The method that will be applied */ function addHandle( attrs, handler ) { - var arr = attrs.split("|"), + var arr = attrs.split( "|" ), i = arr.length; while ( i-- ) { - Expr.attrHandle[ arr[i] ] = handler; + Expr.attrHandle[ arr[ i ] ] = handler; } } @@ -942,8 +971,7 @@ function addHandle( attrs, handler ) { function siblingCheck( a, b ) { var cur = b && a, diff = cur && a.nodeType === 1 && b.nodeType === 1 && - ( ~b.sourceIndex || MAX_NEGATIVE ) - - ( ~a.sourceIndex || MAX_NEGATIVE ); + a.sourceIndex - b.sourceIndex; // Use IE sourceIndex if available on both nodes if ( diff ) { @@ -952,7 +980,7 @@ function siblingCheck( a, b ) { // Check if b follows a if ( cur ) { - while ( (cur = cur.nextSibling) ) { + while ( ( cur = cur.nextSibling ) ) { if ( cur === b ) { return -1; } @@ -980,7 +1008,63 @@ function createInputPseudo( type ) { function createButtonPseudo( type ) { return function( elem ) { var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && elem.type === type; + return ( name === "input" || name === "button" ) && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; }; } @@ -989,21 +1073,21 @@ function createButtonPseudo( type ) { * @param {Function} fn */ function createPositionalPseudo( fn ) { - return markFunction(function( argument ) { + return markFunction( function( argument ) { argument = +argument; - return markFunction(function( seed, matches ) { + return markFunction( function( seed, matches ) { var j, matchIndexes = fn( [], seed.length, argument ), i = matchIndexes.length; // Match elements found at the specified indexes while ( i-- ) { - if ( seed[ (j = matchIndexes[i]) ] ) { - seed[j] = !(matches[j] = seed[j]); + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); } } - }); - }); + } ); + } ); } /** @@ -1024,10 +1108,13 @@ support = Sizzle.support = {}; * @returns {Boolean} True iff elem is a non-HTML XML node */ isXML = Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = elem && (elem.ownerDocument || elem).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; + var namespace = elem.namespaceURI, + docElem = ( elem.ownerDocument || elem ).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); }; /** @@ -1036,11 +1123,15 @@ isXML = Sizzle.isXML = function( elem ) { * @returns {Object} Returns the current document */ setDocument = Sizzle.setDocument = function( node ) { - var hasCompare, parent, + var hasCompare, subWindow, doc = node ? node.ownerDocument || node : preferredDoc; // Return early if doc is invalid or already selected - if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { return document; } @@ -1049,82 +1140,125 @@ setDocument = Sizzle.setDocument = function( node ) { docElem = document.documentElement; documentIsHTML = !isXML( document ); - // Support: IE 9-11, Edge + // Support: IE 9 - 11+, Edge 12 - 18+ // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - if ( (parent = document.defaultView) && parent.top !== parent ) { - // Support: IE 11 - if ( parent.addEventListener ) { - parent.addEventListener( "unload", unloadHandler, false ); + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); // Support: IE 9 - 10 only - } else if ( parent.attachEvent ) { - parent.attachEvent( "onunload", unloadHandler ); + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); } } + // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, + // Safari 4 - 5 only, Opera <=11.6 - 12.x only + // IE/Edge & older browsers don't support the :scope pseudo-class. + // Support: Safari 6.0 only + // Safari 6.0 supports :scope but it's an alias of :root there. + support.scope = assert( function( el ) { + docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); + return typeof el.querySelectorAll !== "undefined" && + !el.querySelectorAll( ":scope fieldset div" ).length; + } ); + /* Attributes ---------------------------------------------------------------------- */ // Support: IE<8 // Verify that getAttribute really returns attributes and not properties // (excepting IE8 booleans) - support.attributes = assert(function( div ) { - div.className = "i"; - return !div.getAttribute("className"); - }); + support.attributes = assert( function( el ) { + el.className = "i"; + return !el.getAttribute( "className" ); + } ); /* getElement(s)By* ---------------------------------------------------------------------- */ // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert(function( div ) { - div.appendChild( document.createComment("") ); - return !div.getElementsByTagName("*").length; - }); + support.getElementsByTagName = assert( function( el ) { + el.appendChild( document.createComment( "" ) ); + return !el.getElementsByTagName( "*" ).length; + } ); // Support: IE<9 support.getElementsByClassName = rnative.test( document.getElementsByClassName ); // Support: IE<10 // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programatically-set names, + // The broken getElementById methods don't pick up programmatically-set names, // so use a roundabout getElementsByName test - support.getById = assert(function( div ) { - docElem.appendChild( div ).id = expando; + support.getById = assert( function( el ) { + docElem.appendChild( el ).id = expando; return !document.getElementsByName || !document.getElementsByName( expando ).length; - }); + } ); - // ID find and filter + // ID filter and find if ( support.getById ) { - Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var m = context.getElementById( id ); - return m ? [ m ] : []; - } - }; - Expr.filter["ID"] = function( id ) { + Expr.filter[ "ID" ] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { - return elem.getAttribute("id") === attrId; + return elem.getAttribute( "id" ) === attrId; }; }; + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; } else { - // Support: IE6/7 - // getElementById is not reliable as a find shortcut - delete Expr.find["ID"]; - - Expr.filter["ID"] = function( id ) { + Expr.filter[ "ID" ] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { var node = typeof elem.getAttributeNode !== "undefined" && - elem.getAttributeNode("id"); + elem.getAttributeNode( "id" ); return node && node.value === attrId; }; }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; } // Tag - Expr.find["TAG"] = support.getElementsByTagName ? + Expr.find[ "TAG" ] = support.getElementsByTagName ? function( tag, context ) { if ( typeof context.getElementsByTagName !== "undefined" ) { return context.getElementsByTagName( tag ); @@ -1139,12 +1273,13 @@ setDocument = Sizzle.setDocument = function( node ) { var elem, tmp = [], i = 0, + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too results = context.getElementsByTagName( tag ); // Filter out possible comments if ( tag === "*" ) { - while ( (elem = results[i++]) ) { + while ( ( elem = results[ i++ ] ) ) { if ( elem.nodeType === 1 ) { tmp.push( elem ); } @@ -1156,7 +1291,7 @@ setDocument = Sizzle.setDocument = function( node ) { }; // Class - Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { return context.getElementsByClassName( className ); } @@ -1174,101 +1309,135 @@ setDocument = Sizzle.setDocument = function( node ) { // We allow this because of a bug in IE8/9 that throws an error // whenever `document.activeElement` is accessed on an iframe // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See http://bugs.jquery.com/ticket/13378 + // See https://bugs.jquery.com/ticket/13378 rbuggyQSA = []; - if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { + if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + // Build QSA regex // Regex strategy adopted from Diego Perini - assert(function( div ) { + assert( function( el ) { + + var input; + // Select is set to empty string on purpose // This is to test IE's treatment of not explicitly // setting a boolean content attribute, // since its presence should be enough - // http://bugs.jquery.com/ticket/12359 - docElem.appendChild( div ).innerHTML = "<a id='" + expando + "'></a>" + + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "<a id='" + expando + "'></a>" + "<select id='" + expando + "-\r\\' msallowcapture=''>" + "<option selected=''></option></select>"; // Support: IE8, Opera 11-12.16 // Nothing should be selected when empty strings follow ^= or $= or *= // The test attribute must be unknown in Opera but "safe" for WinRT - // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( div.querySelectorAll("[msallowcapture^='']").length ) { + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); } // Support: IE8 // Boolean attributes and "value" are not treated correctly - if ( !div.querySelectorAll("[selected]").length ) { + if ( !el.querySelectorAll( "[selected]" ).length ) { rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); } // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ - if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push("~="); + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); } // Webkit/Opera - :checked should return selected option elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked // IE8 throws error here and will not see later tests - if ( !div.querySelectorAll(":checked").length ) { - rbuggyQSA.push(":checked"); + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); } // Support: Safari 8+, iOS 8+ // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibing-combinator selector` fails - if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push(".#.+[+~]"); + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); } - }); - assert(function( div ) { + // Support: Firefox <=3.6 - 5 only + // Old Firefox doesn't throw on a badly-escaped identifier. + el.querySelectorAll( "\\\f" ); + rbuggyQSA.push( "[\\r\\n\\f]" ); + } ); + + assert( function( el ) { + el.innerHTML = "<a href='' disabled='disabled'></a>" + + "<select disabled='disabled'><option/></select>"; + // Support: Windows 8 Native Apps // The type and name attributes are restricted during .innerHTML assignment - var input = document.createElement("input"); + var input = document.createElement( "input" ); input.setAttribute( "type", "hidden" ); - div.appendChild( input ).setAttribute( "name", "D" ); + el.appendChild( input ).setAttribute( "name", "D" ); // Support: IE8 // Enforce case-sensitivity of name attribute - if ( div.querySelectorAll("[name=d]").length ) { + if ( el.querySelectorAll( "[name=d]" ).length ) { rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); } // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) // IE8 throws error here and will not see later tests - if ( !div.querySelectorAll(":enabled").length ) { + if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { rbuggyQSA.push( ":enabled", ":disabled" ); } + // Support: Opera 10 - 11 only // Opera 10-11 does not throw on post-comma invalid pseudos - div.querySelectorAll("*,:x"); - rbuggyQSA.push(",.*:"); - }); + el.querySelectorAll( "*,:x" ); + rbuggyQSA.push( ",.*:" ); + } ); } - if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || + if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || docElem.webkitMatchesSelector || docElem.mozMatchesSelector || docElem.oMatchesSelector || - docElem.msMatchesSelector) )) ) { + docElem.msMatchesSelector ) ) ) ) { + + assert( function( el ) { - assert(function( div ) { // Check to see if it's possible to do matchesSelector // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( div, "div" ); + support.disconnectedMatch = matches.call( el, "*" ); // This should fail with an exception // Gecko does not error, returns false instead - matches.call( div, "[s!='']:x" ); + matches.call( el, "[s!='']:x" ); rbuggyMatches.push( "!=", pseudos ); - }); + } ); } - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); /* Contains ---------------------------------------------------------------------- */ @@ -1285,11 +1454,11 @@ setDocument = Sizzle.setDocument = function( node ) { adown.contains ? adown.contains( bup ) : a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - )); + ) ); } : function( a, b ) { if ( b ) { - while ( (b = b.parentNode) ) { + while ( ( b = b.parentNode ) ) { if ( b === a ) { return true; } @@ -1318,7 +1487,11 @@ setDocument = Sizzle.setDocument = function( node ) { } // Calculate position if both inputs belong to the same document - compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? a.compareDocumentPosition( b ) : // Otherwise we know they are disconnected @@ -1326,13 +1499,24 @@ setDocument = Sizzle.setDocument = function( node ) { // Disconnected nodes if ( compare & 1 || - (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { // Choose the first element that is related to our preferred document - if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a == document || a.ownerDocument == preferredDoc && + contains( preferredDoc, a ) ) { return -1; } - if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b == document || b.ownerDocument == preferredDoc && + contains( preferredDoc, b ) ) { return 1; } @@ -1345,6 +1529,7 @@ setDocument = Sizzle.setDocument = function( node ) { return compare & 4 ? -1 : 1; } : function( a, b ) { + // Exit early if the nodes are identical if ( a === b ) { hasDuplicate = true; @@ -1360,8 +1545,14 @@ setDocument = Sizzle.setDocument = function( node ) { // Parentless nodes are either documents or disconnected if ( !aup || !bup ) { - return a === document ? -1 : - b === document ? 1 : + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + return a == document ? -1 : + b == document ? 1 : + /* eslint-enable eqeqeq */ aup ? -1 : bup ? 1 : sortInput ? @@ -1375,26 +1566,32 @@ setDocument = Sizzle.setDocument = function( node ) { // Otherwise we need full lists of their ancestors for comparison cur = a; - while ( (cur = cur.parentNode) ) { + while ( ( cur = cur.parentNode ) ) { ap.unshift( cur ); } cur = b; - while ( (cur = cur.parentNode) ) { + while ( ( cur = cur.parentNode ) ) { bp.unshift( cur ); } // Walk down the tree looking for a discrepancy - while ( ap[i] === bp[i] ) { + while ( ap[ i ] === bp[ i ] ) { i++; } return i ? + // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[i], bp[i] ) : + siblingCheck( ap[ i ], bp[ i ] ) : // Otherwise nodes in our document sort first - ap[i] === preferredDoc ? -1 : - bp[i] === preferredDoc ? 1 : + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + ap[ i ] == preferredDoc ? -1 : + bp[ i ] == preferredDoc ? 1 : + /* eslint-enable eqeqeq */ 0; }; @@ -1406,16 +1603,10 @@ Sizzle.matches = function( expr, elements ) { }; Sizzle.matchesSelector = function( elem, expr ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - // Make sure that attribute selectors are quoted - expr = expr.replace( rattributeQuotes, "='$1']" ); + setDocument( elem ); if ( support.matchesSelector && documentIsHTML && - !compilerCache[ expr + " " ] && + !nonnativeSelectorCache[ expr + " " ] && ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { @@ -1424,32 +1615,46 @@ Sizzle.matchesSelector = function( elem, expr ) { // IE 9's matchesSelector returns false on disconnected nodes if ( ret || support.disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { return ret; } - } catch (e) {} + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } } return Sizzle( expr, document, null, [ elem ] ).length > 0; }; Sizzle.contains = function( context, elem ) { + // Set document vars if needed - if ( ( context.ownerDocument || context ) !== document ) { + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { setDocument( context ); } return contains( context, elem ); }; Sizzle.attr = function( elem, name ) { + // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { setDocument( elem ); } var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? fn( elem, name, !documentIsHTML ) : @@ -1459,11 +1664,15 @@ Sizzle.attr = function( elem, name ) { val : support.attributes || !documentIsHTML ? elem.getAttribute( name ) : - (val = elem.getAttributeNode(name)) && val.specified ? + ( val = elem.getAttributeNode( name ) ) && val.specified ? val.value : null; }; +Sizzle.escape = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + Sizzle.error = function( msg ) { throw new Error( "Syntax error, unrecognized expression: " + msg ); }; @@ -1484,7 +1693,7 @@ Sizzle.uniqueSort = function( results ) { results.sort( sortOrder ); if ( hasDuplicate ) { - while ( (elem = results[i++]) ) { + while ( ( elem = results[ i++ ] ) ) { if ( elem === results[ i ] ) { j = duplicates.push( i ); } @@ -1512,17 +1721,21 @@ getText = Sizzle.getText = function( elem ) { nodeType = elem.nodeType; if ( !nodeType ) { + // If no nodeType, this is expected to be an array - while ( (node = elem[i++]) ) { + while ( ( node = elem[ i++ ] ) ) { + // Do not traverse comment nodes ret += getText( node ); } } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements // innerText usage removed for consistency of new lines (jQuery #11153) if ( typeof elem.textContent === "string" ) { return elem.textContent; } else { + // Traverse its children for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { ret += getText( elem ); @@ -1531,6 +1744,7 @@ getText = Sizzle.getText = function( elem ) { } else if ( nodeType === 3 || nodeType === 4 ) { return elem.nodeValue; } + // Do not include comment or processing instruction nodes return ret; @@ -1558,19 +1772,21 @@ Expr = Sizzle.selectors = { preFilter: { "ATTR": function( match ) { - match[1] = match[1].replace( runescape, funescape ); + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); // Move the given value to match[3] whether quoted or unquoted - match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || + match[ 5 ] || "" ).replace( runescape, funescape ); - if ( match[2] === "~=" ) { - match[3] = " " + match[3] + " "; + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; } return match.slice( 0, 4 ); }, "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] 1 type (only|nth|...) 2 what (child|of-type) @@ -1581,22 +1797,25 @@ Expr = Sizzle.selectors = { 7 sign of y-component 8 y of y-component */ - match[1] = match[1].toLowerCase(); + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { - if ( match[1].slice( 0, 3 ) === "nth" ) { // nth-* requires argument - if ( !match[3] ) { - Sizzle.error( match[0] ); + if ( !match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); } // numeric x and y parameters for Expr.filter.CHILD // remember that false/true cast respectively to 0/1 - match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); - match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); - // other types prohibit arguments - } else if ( match[3] ) { - Sizzle.error( match[0] ); + // other types prohibit arguments + } else if ( match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); } return match; @@ -1604,26 +1823,28 @@ Expr = Sizzle.selectors = { "PSEUDO": function( match ) { var excess, - unquoted = !match[6] && match[2]; + unquoted = !match[ 6 ] && match[ 2 ]; - if ( matchExpr["CHILD"].test( match[0] ) ) { + if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { return null; } // Accept quoted arguments as-is - if ( match[3] ) { - match[2] = match[4] || match[5] || ""; + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; // Strip excess characters from unquoted arguments } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) - (excess = tokenize( unquoted, true )) && + ( excess = tokenize( unquoted, true ) ) && + // advance to the next closing parenthesis - (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { // excess is a negative index - match[0] = match[0].slice( 0, excess ); - match[2] = unquoted.slice( 0, excess ); + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); } // Return only captures needed by the pseudo filter method (type and argument) @@ -1636,7 +1857,9 @@ Expr = Sizzle.selectors = { "TAG": function( nodeNameSelector ) { var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); return nodeNameSelector === "*" ? - function() { return true; } : + function() { + return true; + } : function( elem ) { return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; }; @@ -1646,10 +1869,16 @@ Expr = Sizzle.selectors = { var pattern = classCache[ className + " " ]; return pattern || - (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && - classCache( className, function( elem ) { - return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); - }); + ( pattern = new RegExp( "(^|" + whitespace + + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( + className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); }, "ATTR": function( name, operator, check ) { @@ -1665,6 +1894,8 @@ Expr = Sizzle.selectors = { result += ""; + /* eslint-disable max-len */ + return operator === "=" ? result === check : operator === "!=" ? result !== check : operator === "^=" ? check && result.indexOf( check ) === 0 : @@ -1673,10 +1904,12 @@ Expr = Sizzle.selectors = { operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : false; + /* eslint-enable max-len */ + }; }, - "CHILD": function( type, what, argument, first, last ) { + "CHILD": function( type, what, _argument, first, last ) { var simple = type.slice( 0, 3 ) !== "nth", forward = type.slice( -4 ) !== "last", ofType = what === "of-type"; @@ -1688,7 +1921,7 @@ Expr = Sizzle.selectors = { return !!elem.parentNode; } : - function( elem, context, xml ) { + function( elem, _context, xml ) { var cache, uniqueCache, outerCache, node, nodeIndex, start, dir = simple !== forward ? "nextSibling" : "previousSibling", parent = elem.parentNode, @@ -1702,7 +1935,7 @@ Expr = Sizzle.selectors = { if ( simple ) { while ( dir ) { node = elem; - while ( (node = node[ dir ]) ) { + while ( ( node = node[ dir ] ) ) { if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { @@ -1710,6 +1943,7 @@ Expr = Sizzle.selectors = { return false; } } + // Reverse direction for :only-* (if we haven't yet done so) start = dir = type === "only" && !start && "nextSibling"; } @@ -1725,22 +1959,22 @@ Expr = Sizzle.selectors = { // ...in a gzip-friendly way node = parent; - outerCache = node[ expando ] || (node[ expando ] = {}); + outerCache = node[ expando ] || ( node[ expando ] = {} ); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); + ( outerCache[ node.uniqueID ] = {} ); cache = uniqueCache[ type ] || []; nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; diff = nodeIndex && cache[ 2 ]; node = nodeIndex && parent.childNodes[ nodeIndex ]; - while ( (node = ++nodeIndex && node && node[ dir ] || + while ( ( node = ++nodeIndex && node && node[ dir ] || // Fallback to seeking `elem` from the start - (diff = nodeIndex = 0) || start.pop()) ) { + ( diff = nodeIndex = 0 ) || start.pop() ) ) { // When found, cache indexes on `parent` and break if ( node.nodeType === 1 && ++diff && node === elem ) { @@ -1750,16 +1984,18 @@ Expr = Sizzle.selectors = { } } else { + // Use previously-cached element index if available if ( useCache ) { + // ...in a gzip-friendly way node = elem; - outerCache = node[ expando ] || (node[ expando ] = {}); + outerCache = node[ expando ] || ( node[ expando ] = {} ); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); + ( outerCache[ node.uniqueID ] = {} ); cache = uniqueCache[ type ] || []; nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; @@ -1769,9 +2005,10 @@ Expr = Sizzle.selectors = { // xml :nth-child(...) // or :nth-last-child(...) or :nth(-last)?-of-type(...) if ( diff === false ) { + // Use the same loop as above to seek `elem` from the start - while ( (node = ++nodeIndex && node && node[ dir ] || - (diff = nodeIndex = 0) || start.pop()) ) { + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { if ( ( ofType ? node.nodeName.toLowerCase() === name : @@ -1780,12 +2017,13 @@ Expr = Sizzle.selectors = { // Cache the index of each encountered element if ( useCache ) { - outerCache = node[ expando ] || (node[ expando ] = {}); + outerCache = node[ expando ] || + ( node[ expando ] = {} ); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); + ( outerCache[ node.uniqueID ] = {} ); uniqueCache[ type ] = [ dirruns, diff ]; } @@ -1806,6 +2044,7 @@ Expr = Sizzle.selectors = { }, "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive // http://www.w3.org/TR/selectors/#pseudo-classes // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters @@ -1825,15 +2064,15 @@ Expr = Sizzle.selectors = { if ( fn.length > 1 ) { args = [ pseudo, pseudo, "", argument ]; return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction(function( seed, matches ) { + markFunction( function( seed, matches ) { var idx, matched = fn( seed, argument ), i = matched.length; while ( i-- ) { - idx = indexOf( seed, matched[i] ); - seed[ idx ] = !( matches[ idx ] = matched[i] ); + idx = indexOf( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); } - }) : + } ) : function( elem ) { return fn( elem, 0, args ); }; @@ -1844,8 +2083,10 @@ Expr = Sizzle.selectors = { }, pseudos: { + // Potentially complex pseudos - "not": markFunction(function( selector ) { + "not": markFunction( function( selector ) { + // Trim the selector passed to compile // to avoid treating leading and trailing // spaces as combinators @@ -1854,39 +2095,40 @@ Expr = Sizzle.selectors = { matcher = compile( selector.replace( rtrim, "$1" ) ); return matcher[ expando ] ? - markFunction(function( seed, matches, context, xml ) { + markFunction( function( seed, matches, _context, xml ) { var elem, unmatched = matcher( seed, null, xml, [] ), i = seed.length; // Match elements unmatched by `matcher` while ( i-- ) { - if ( (elem = unmatched[i]) ) { - seed[i] = !(matches[i] = elem); + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); } } - }) : - function( elem, context, xml ) { - input[0] = elem; + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; matcher( input, null, xml, results ); + // Don't keep the element (issue #299) - input[0] = null; + input[ 0 ] = null; return !results.pop(); }; - }), + } ), - "has": markFunction(function( selector ) { + "has": markFunction( function( selector ) { return function( elem ) { return Sizzle( selector, elem ).length > 0; }; - }), + } ), - "contains": markFunction(function( text ) { + "contains": markFunction( function( text ) { text = text.replace( runescape, funescape ); return function( elem ) { - return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; }; - }), + } ), // "Whether an element is represented by a :lang() selector // is based solely on the element's language value @@ -1896,25 +2138,26 @@ Expr = Sizzle.selectors = { // The identifier C does not have to be a valid language name." // http://www.w3.org/TR/selectors/#lang-pseudo "lang": markFunction( function( lang ) { + // lang value must be a valid identifier - if ( !ridentifier.test(lang || "") ) { + if ( !ridentifier.test( lang || "" ) ) { Sizzle.error( "unsupported lang: " + lang ); } lang = lang.replace( runescape, funescape ).toLowerCase(); return function( elem ) { var elemLang; do { - if ( (elemLang = documentIsHTML ? + if ( ( elemLang = documentIsHTML ? elem.lang : - elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { elemLang = elemLang.toLowerCase(); return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; } - } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); return false; }; - }), + } ), // Miscellaneous "target": function( elem ) { @@ -1927,29 +2170,30 @@ Expr = Sizzle.selectors = { }, "focus": function( elem ) { - return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + return elem === document.activeElement && + ( !document.hasFocus || document.hasFocus() ) && + !!( elem.type || elem.href || ~elem.tabIndex ); }, // Boolean properties - "enabled": function( elem ) { - return elem.disabled === false; - }, - - "disabled": function( elem ) { - return elem.disabled === true; - }, + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked var nodeName = elem.nodeName.toLowerCase(); - return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + return ( nodeName === "input" && !!elem.checked ) || + ( nodeName === "option" && !!elem.selected ); }, "selected": function( elem ) { + // Accessing this property makes selected-by-default // options in Safari work properly if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions elem.parentNode.selectedIndex; } @@ -1958,6 +2202,7 @@ Expr = Sizzle.selectors = { // Contents "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), // but not by others (comment: 8; processing instruction: 7; etc.) @@ -1971,7 +2216,7 @@ Expr = Sizzle.selectors = { }, "parent": function( elem ) { - return !Expr.pseudos["empty"]( elem ); + return !Expr.pseudos[ "empty" ]( elem ); }, // Element/input types @@ -1995,57 +2240,62 @@ Expr = Sizzle.selectors = { // Support: IE<8 // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" - ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); }, // Position-in-collection - "first": createPositionalPseudo(function() { + "first": createPositionalPseudo( function() { return [ 0 ]; - }), + } ), - "last": createPositionalPseudo(function( matchIndexes, length ) { + "last": createPositionalPseudo( function( _matchIndexes, length ) { return [ length - 1 ]; - }), + } ), - "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { return [ argument < 0 ? argument + length : argument ]; - }), + } ), - "even": createPositionalPseudo(function( matchIndexes, length ) { + "even": createPositionalPseudo( function( matchIndexes, length ) { var i = 0; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; - }), + } ), - "odd": createPositionalPseudo(function( matchIndexes, length ) { + "odd": createPositionalPseudo( function( matchIndexes, length ) { var i = 1; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; - }), + } ), - "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; + "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; for ( ; --i >= 0; ) { matchIndexes.push( i ); } return matchIndexes; - }), + } ), - "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { var i = argument < 0 ? argument + length : argument; for ( ; ++i < length; ) { matchIndexes.push( i ); } return matchIndexes; - }) + } ) } }; -Expr.pseudos["nth"] = Expr.pseudos["eq"]; +Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; // Add button/input type pseudos for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { @@ -2076,37 +2326,39 @@ tokenize = Sizzle.tokenize = function( selector, parseOnly ) { while ( soFar ) { // Comma and first run - if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { if ( match ) { + // Don't consume trailing commas as valid - soFar = soFar.slice( match[0].length ) || soFar; + soFar = soFar.slice( match[ 0 ].length ) || soFar; } - groups.push( (tokens = []) ); + groups.push( ( tokens = [] ) ); } matched = false; // Combinators - if ( (match = rcombinators.exec( soFar )) ) { + if ( ( match = rcombinators.exec( soFar ) ) ) { matched = match.shift(); - tokens.push({ + tokens.push( { value: matched, + // Cast descendant combinators to space - type: match[0].replace( rtrim, " " ) - }); + type: match[ 0 ].replace( rtrim, " " ) + } ); soFar = soFar.slice( matched.length ); } // Filters for ( type in Expr.filter ) { - if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || - (match = preFilters[ type ]( match ))) ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { matched = match.shift(); - tokens.push({ + tokens.push( { value: matched, type: type, matches: match - }); + } ); soFar = soFar.slice( matched.length ); } } @@ -2123,6 +2375,7 @@ tokenize = Sizzle.tokenize = function( selector, parseOnly ) { soFar.length : soFar ? Sizzle.error( selector ) : + // Cache the tokens tokenCache( selector, groups ).slice( 0 ); }; @@ -2132,24 +2385,28 @@ function toSelector( tokens ) { len = tokens.length, selector = ""; for ( ; i < len; i++ ) { - selector += tokens[i].value; + selector += tokens[ i ].value; } return selector; } function addCombinator( matcher, combinator, base ) { var dir = combinator.dir, - checkNonElements = base && dir === "parentNode", + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", doneName = done++; return combinator.first ? + // Check against closest ancestor/preceding element function( elem, context, xml ) { - while ( (elem = elem[ dir ]) ) { + while ( ( elem = elem[ dir ] ) ) { if ( elem.nodeType === 1 || checkNonElements ) { return matcher( elem, context, xml ); } } + return false; } : // Check against all ancestor/preceding elements @@ -2159,7 +2416,7 @@ function addCombinator( matcher, combinator, base ) { // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching if ( xml ) { - while ( (elem = elem[ dir ]) ) { + while ( ( elem = elem[ dir ] ) ) { if ( elem.nodeType === 1 || checkNonElements ) { if ( matcher( elem, context, xml ) ) { return true; @@ -2167,31 +2424,36 @@ function addCombinator( matcher, combinator, base ) { } } } else { - while ( (elem = elem[ dir ]) ) { + while ( ( elem = elem[ dir ] ) ) { if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || (elem[ expando ] = {}); + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); + uniqueCache = outerCache[ elem.uniqueID ] || + ( outerCache[ elem.uniqueID ] = {} ); - if ( (oldCache = uniqueCache[ dir ]) && + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = uniqueCache[ key ] ) && oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { // Assign to newCache so results back-propagate to previous elements - return (newCache[ 2 ] = oldCache[ 2 ]); + return ( newCache[ 2 ] = oldCache[ 2 ] ); } else { + // Reuse newcache so results back-propagate to previous elements - uniqueCache[ dir ] = newCache; + uniqueCache[ key ] = newCache; // A match means we're done; a fail means we have to keep checking - if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { return true; } } } } } + return false; }; } @@ -2200,20 +2462,20 @@ function elementMatcher( matchers ) { function( elem, context, xml ) { var i = matchers.length; while ( i-- ) { - if ( !matchers[i]( elem, context, xml ) ) { + if ( !matchers[ i ]( elem, context, xml ) ) { return false; } } return true; } : - matchers[0]; + matchers[ 0 ]; } function multipleContexts( selector, contexts, results ) { var i = 0, len = contexts.length; for ( ; i < len; i++ ) { - Sizzle( selector, contexts[i], results ); + Sizzle( selector, contexts[ i ], results ); } return results; } @@ -2226,7 +2488,7 @@ function condense( unmatched, map, filter, context, xml ) { mapped = map != null; for ( ; i < len; i++ ) { - if ( (elem = unmatched[i]) ) { + if ( ( elem = unmatched[ i ] ) ) { if ( !filter || filter( elem, context, xml ) ) { newUnmatched.push( elem ); if ( mapped ) { @@ -2246,14 +2508,18 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS if ( postFinder && !postFinder[ expando ] ) { postFinder = setMatcher( postFinder, postSelector ); } - return markFunction(function( seed, results, context, xml ) { + return markFunction( function( seed, results, context, xml ) { var temp, i, elem, preMap = [], postMap = [], preexisting = results.length, // Get initial elements from seed or context - elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + elems = seed || multipleContexts( + selector || "*", + context.nodeType ? [ context ] : context, + [] + ), // Prefilter to get matcher input, preserving a map for seed-results synchronization matcherIn = preFilter && ( seed || !selector ) ? @@ -2261,6 +2527,7 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS elems, matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, postFinder || ( seed ? preFilter : preexisting || postFilter ) ? @@ -2284,8 +2551,8 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS // Un-match failing elements by moving them back to matcherIn i = temp.length; while ( i-- ) { - if ( (elem = temp[i]) ) { - matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); } } } @@ -2293,25 +2560,27 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS if ( seed ) { if ( postFinder || preFilter ) { if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts temp = []; i = matcherOut.length; while ( i-- ) { - if ( (elem = matcherOut[i]) ) { + if ( ( elem = matcherOut[ i ] ) ) { + // Restore matcherIn since elem is not yet a final match - temp.push( (matcherIn[i] = elem) ); + temp.push( ( matcherIn[ i ] = elem ) ); } } - postFinder( null, (matcherOut = []), temp, xml ); + postFinder( null, ( matcherOut = [] ), temp, xml ); } // Move matched elements from seed to results to keep them synchronized i = matcherOut.length; while ( i-- ) { - if ( (elem = matcherOut[i]) && - (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { - seed[temp] = !(results[temp] = elem); + seed[ temp ] = !( results[ temp ] = elem ); } } } @@ -2329,14 +2598,14 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS push.apply( results, matcherOut ); } } - }); + } ); } function matcherFromTokens( tokens ) { var checkContext, matcher, j, len = tokens.length, - leadingRelative = Expr.relative[ tokens[0].type ], - implicitRelative = leadingRelative || Expr.relative[" "], + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], i = leadingRelative ? 1 : 0, // The foundational matcher ensures that elements are reachable from top-level context(s) @@ -2348,38 +2617,43 @@ function matcherFromTokens( tokens ) { }, implicitRelative, true ), matchers = [ function( elem, context, xml ) { var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - (checkContext = context).nodeType ? + ( checkContext = context ).nodeType ? matchContext( elem, context, xml ) : matchAnyContext( elem, context, xml ) ); + // Avoid hanging onto element (issue #299) checkContext = null; return ret; } ]; for ( ; i < len; i++ ) { - if ( (matcher = Expr.relative[ tokens[i].type ]) ) { - matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; } else { - matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); // Return special upon seeing a positional matcher if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling j = ++i; for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[j].type ] ) { + if ( Expr.relative[ tokens[ j ].type ] ) { break; } } return setMatcher( i > 1 && elementMatcher( matchers ), i > 1 && toSelector( - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens + .slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) ).replace( rtrim, "$1" ), matcher, i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), j < len && toSelector( tokens ) ); } @@ -2400,28 +2674,40 @@ function matcherFromGroupMatchers( elementMatchers, setMatchers ) { unmatched = seed && [], setMatched = [], contextBackup = outermostContext, + // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), len = elems.length; if ( outermost ) { - outermostContext = context === document || context || outermost; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; } // Add elements passing elementMatchers directly to results // Support: IE<9, Safari // Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id - for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { if ( byElement && elem ) { j = 0; - if ( !context && elem.ownerDocument !== document ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { setDocument( elem ); xml = !documentIsHTML; } - while ( (matcher = elementMatchers[j++]) ) { - if ( matcher( elem, context || document, xml) ) { + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { results.push( elem ); break; } @@ -2433,8 +2719,9 @@ function matcherFromGroupMatchers( elementMatchers, setMatchers ) { // Track unmatched elements for set filters if ( bySet ) { + // They will have gone through all possible matchers - if ( (elem = !matcher && elem) ) { + if ( ( elem = !matcher && elem ) ) { matchedCount--; } @@ -2458,16 +2745,17 @@ function matcherFromGroupMatchers( elementMatchers, setMatchers ) { // numerically zero. if ( bySet && i !== matchedCount ) { j = 0; - while ( (matcher = setMatchers[j++]) ) { + while ( ( matcher = setMatchers[ j++ ] ) ) { matcher( unmatched, setMatched, context, xml ); } if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting if ( matchedCount > 0 ) { while ( i-- ) { - if ( !(unmatched[i] || setMatched[i]) ) { - setMatched[i] = pop.call( results ); + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); } } } @@ -2508,13 +2796,14 @@ compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { cached = compilerCache[ selector + " " ]; if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element if ( !match ) { match = tokenize( selector ); } i = match.length; while ( i-- ) { - cached = matcherFromTokens( match[i] ); + cached = matcherFromTokens( match[ i ] ); if ( cached[ expando ] ) { setMatchers.push( cached ); } else { @@ -2523,7 +2812,10 @@ compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { } // Cache the compiled function - cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + cached = compilerCache( + selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) + ); // Save selector and tokenization cached.selector = selector; @@ -2543,7 +2835,7 @@ compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { select = Sizzle.select = function( selector, context, results, seed ) { var i, tokens, token, type, find, compiled = typeof selector === "function" && selector, - match = !seed && tokenize( (selector = compiled.selector || selector) ); + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); results = results || []; @@ -2552,12 +2844,12 @@ select = Sizzle.select = function( selector, context, results, seed ) { if ( match.length === 1 ) { // Reduce context if the leading compound selector is an ID - tokens = match[0] = match[0].slice( 0 ); - if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && - support.getById && context.nodeType === 9 && documentIsHTML && - Expr.relative[ tokens[1].type ] ) { + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { - context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + context = ( Expr.find[ "ID" ]( token.matches[ 0 ] + .replace( runescape, funescape ), context ) || [] )[ 0 ]; if ( !context ) { return results; @@ -2570,20 +2862,22 @@ select = Sizzle.select = function( selector, context, results, seed ) { } // Fetch a seed set for right-to-left matching - i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; while ( i-- ) { - token = tokens[i]; + token = tokens[ i ]; // Abort if we hit a combinator - if ( Expr.relative[ (type = token.type) ] ) { + if ( Expr.relative[ ( type = token.type ) ] ) { break; } - if ( (find = Expr.find[ type ]) ) { + if ( ( find = Expr.find[ type ] ) ) { + // Search, expanding context for leading sibling combinators - if ( (seed = find( - token.matches[0].replace( runescape, funescape ), - rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context - )) ) { + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || + context + ) ) ) { // If seed is empty or no tokens remain, we can return early tokens.splice( i, 1 ); @@ -2614,7 +2908,7 @@ select = Sizzle.select = function( selector, context, results, seed ) { // One-time assignments // Sort stability -support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; // Support: Chrome 14-35+ // Always assume duplicates if they aren't passed to the comparison function @@ -2625,68 +2919,73 @@ setDocument(); // Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) // Detached nodes confoundingly follow *each other* -support.sortDetached = assert(function( div1 ) { +support.sortDetached = assert( function( el ) { + // Should return 1, but returns 4 (following) - return div1.compareDocumentPosition( document.createElement("div") ) & 1; -}); + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); // Support: IE<8 // Prevent attribute/property "interpolation" -// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert(function( div ) { - div.innerHTML = "<a href='#'></a>"; - return div.firstChild.getAttribute("href") === "#" ; -}) ) { +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert( function( el ) { + el.innerHTML = "<a href='#'></a>"; + return el.firstChild.getAttribute( "href" ) === "#"; +} ) ) { addHandle( "type|href|height|width", function( elem, name, isXML ) { if ( !isXML ) { return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); } - }); + } ); } // Support: IE<9 // Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert(function( div ) { - div.innerHTML = "<input/>"; - div.firstChild.setAttribute( "value", "" ); - return div.firstChild.getAttribute( "value" ) === ""; -}) ) { - addHandle( "value", function( elem, name, isXML ) { +if ( !support.attributes || !assert( function( el ) { + el.innerHTML = "<input/>"; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +} ) ) { + addHandle( "value", function( elem, _name, isXML ) { if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { return elem.defaultValue; } - }); + } ); } // Support: IE<9 // Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert(function( div ) { - return div.getAttribute("disabled") == null; -}) ) { +if ( !assert( function( el ) { + return el.getAttribute( "disabled" ) == null; +} ) ) { addHandle( booleans, function( elem, name, isXML ) { var val; if ( !isXML ) { return elem[ name ] === true ? name.toLowerCase() : - (val = elem.getAttributeNode( name )) && val.specified ? + ( val = elem.getAttributeNode( name ) ) && val.specified ? val.value : - null; + null; } - }); + } ); } return Sizzle; -})( window ); +} )( window ); jQuery.find = Sizzle; jQuery.expr = Sizzle.selectors; + +// Deprecated jQuery.expr[ ":" ] = jQuery.expr.pseudos; jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; jQuery.text = Sizzle.getText; jQuery.isXMLDoc = Sizzle.isXML; jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + @@ -2721,40 +3020,41 @@ var siblings = function( n, elem ) { var rneedsContext = jQuery.expr.match.needsContext; -var rsingleTag = ( /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/ ); +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +}; +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + -var risSimple = /^.[^:#\[\.,]*$/; // Implement the identical functionality for filter and not function winnow( elements, qualifier, not ) { - if ( jQuery.isFunction( qualifier ) ) { + if ( isFunction( qualifier ) ) { return jQuery.grep( elements, function( elem, i ) { - /* jshint -W018 */ return !!qualifier.call( elem, i, elem ) !== not; } ); - } + // Single element if ( qualifier.nodeType ) { return jQuery.grep( elements, function( elem ) { return ( elem === qualifier ) !== not; } ); - } - if ( typeof qualifier === "string" ) { - if ( risSimple.test( qualifier ) ) { - return jQuery.filter( qualifier, elements, not ); - } - - qualifier = jQuery.filter( qualifier, elements ); + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); } - return jQuery.grep( elements, function( elem ) { - return ( indexOf.call( qualifier, elem ) > -1 ) !== not; - } ); + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); } jQuery.filter = function( expr, elems, not ) { @@ -2764,18 +3064,19 @@ jQuery.filter = function( expr, elems, not ) { expr = ":not(" + expr + ")"; } - return elems.length === 1 && elem.nodeType === 1 ? - jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : - jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { - return elem.nodeType === 1; - } ) ); + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); }; jQuery.fn.extend( { find: function( selector ) { - var i, + var i, ret, len = this.length, - ret = [], self = this; if ( typeof selector !== "string" ) { @@ -2788,14 +3089,13 @@ jQuery.fn.extend( { } ) ); } + ret = this.pushStack( [] ); + for ( i = 0; i < len; i++ ) { jQuery.find( selector, self[ i ], ret ); } - // Needed because $( selector, context ) becomes $( context ).find( selector ) - ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); - ret.selector = this.selector ? this.selector + " " + selector : selector; - return ret; + return len > 1 ? jQuery.uniqueSort( ret ) : ret; }, filter: function( selector ) { return this.pushStack( winnow( this, selector || [], false ) ); @@ -2827,7 +3127,8 @@ var rootjQuery, // A simple way to check for HTML strings // Prioritize #id over <tag> to avoid XSS via location.hash (#9521) // Strict HTML recognition (#11290: must start with <) - rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, init = jQuery.fn.init = function( selector, context, root ) { var match, elem; @@ -2874,7 +3175,7 @@ var rootjQuery, for ( match in context ) { // Properties of context are called as methods if possible - if ( jQuery.isFunction( this[ match ] ) ) { + if ( isFunction( this[ match ] ) ) { this[ match ]( context[ match ] ); // ...and otherwise set as attributes @@ -2890,17 +3191,12 @@ var rootjQuery, } else { elem = document.getElementById( match[ 2 ] ); - // Support: Blackberry 4.6 - // gEBID returns nodes no longer in the document (#6963) - if ( elem && elem.parentNode ) { + if ( elem ) { // Inject the element directly into the jQuery object - this.length = 1; this[ 0 ] = elem; + this.length = 1; } - - this.context = document; - this.selector = selector; return this; } @@ -2916,13 +3212,13 @@ var rootjQuery, // HANDLE: $(DOMElement) } else if ( selector.nodeType ) { - this.context = this[ 0 ] = selector; + this[ 0 ] = selector; this.length = 1; return this; // HANDLE: $(function) // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) { + } else if ( isFunction( selector ) ) { return root.ready !== undefined ? root.ready( selector ) : @@ -2930,11 +3226,6 @@ var rootjQuery, selector( jQuery ); } - if ( selector.selector !== undefined ) { - this.selector = selector.selector; - this.context = selector.context; - } - return jQuery.makeArray( selector, this ); }; @@ -2975,23 +3266,24 @@ jQuery.fn.extend( { i = 0, l = this.length, matched = [], - pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? - jQuery( selectors, context || this.context ) : - 0; + targets = typeof selectors !== "string" && jQuery( selectors ); - for ( ; i < l; i++ ) { - for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { - // Always skip document fragments - if ( cur.nodeType < 11 && ( pos ? - pos.index( cur ) > -1 : + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : - // Don't pass non-elements to Sizzle - cur.nodeType === 1 && - jQuery.find.matchesSelector( cur, selectors ) ) ) { + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { - matched.push( cur ); - break; + matched.push( cur ); + break; + } } } } @@ -3048,7 +3340,7 @@ jQuery.each( { parents: function( elem ) { return dir( elem, "parentNode" ); }, - parentsUntil: function( elem, i, until ) { + parentsUntil: function( elem, _i, until ) { return dir( elem, "parentNode", until ); }, next: function( elem ) { @@ -3063,10 +3355,10 @@ jQuery.each( { prevAll: function( elem ) { return dir( elem, "previousSibling" ); }, - nextUntil: function( elem, i, until ) { + nextUntil: function( elem, _i, until ) { return dir( elem, "nextSibling", until ); }, - prevUntil: function( elem, i, until ) { + prevUntil: function( elem, _i, until ) { return dir( elem, "previousSibling", until ); }, siblings: function( elem ) { @@ -3076,7 +3368,24 @@ jQuery.each( { return siblings( elem.firstChild ); }, contents: function( elem ) { - return elem.contentDocument || jQuery.merge( [], elem.childNodes ); + if ( elem.contentDocument != null && + + // Support: IE 11+ + // <object> elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { + + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); } }, function( name, fn ) { jQuery.fn[ name ] = function( until, selector ) { @@ -3106,14 +3415,14 @@ jQuery.each( { return this.pushStack( matched ); }; } ); -var rnotwhite = ( /\S+/g ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); // Convert String-formatted options into Object-formatted ones function createOptions( options ) { var object = {}; - jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { object[ flag ] = true; } ); return object; @@ -3174,7 +3483,7 @@ jQuery.Callbacks = function( options ) { fire = function() { // Enforce single-firing - locked = options.once; + locked = locked || options.once; // Execute callbacks for all pending executions, // respecting firingIndex overrides and runtime changes @@ -3230,11 +3539,11 @@ jQuery.Callbacks = function( options ) { ( function add( args ) { jQuery.each( args, function( _, arg ) { - if ( jQuery.isFunction( arg ) ) { + if ( isFunction( arg ) ) { if ( !options.unique || !self.has( arg ) ) { list.push( arg ); } - } else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) { + } else if ( arg && arg.length && toType( arg ) !== "string" ) { // Inspect recursively add( arg ); @@ -3298,7 +3607,7 @@ jQuery.Callbacks = function( options ) { // Abort any pending executions lock: function() { locked = queue = []; - if ( !memory ) { + if ( !memory && !firing ) { list = memory = ""; } return this; @@ -3336,15 +3645,59 @@ jQuery.Callbacks = function( options ) { }; +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + jQuery.extend( { Deferred: function( func ) { var tuples = [ - // action, add listener, listener list, final state - [ "resolve", "done", jQuery.Callbacks( "once memory" ), "resolved" ], - [ "reject", "fail", jQuery.Callbacks( "once memory" ), "rejected" ], - [ "notify", "progress", jQuery.Callbacks( "memory" ) ] + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] ], state = "pending", promise = { @@ -3355,23 +3708,33 @@ jQuery.extend( { deferred.done( arguments ).fail( arguments ); return this; }, - then: function( /* fnDone, fnFail, fnProgress */ ) { + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { var fns = arguments; + return jQuery.Deferred( function( newDefer ) { - jQuery.each( tuples, function( i, tuple ) { - var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + jQuery.each( tuples, function( _i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; - // deferred[ done | fail | progress ] for forwarding actions to newDefer + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) deferred[ tuple[ 1 ] ]( function() { var returned = fn && fn.apply( this, arguments ); - if ( returned && jQuery.isFunction( returned.promise ) ) { + if ( returned && isFunction( returned.promise ) ) { returned.promise() .progress( newDefer.notify ) .done( newDefer.resolve ) .fail( newDefer.reject ); } else { newDefer[ tuple[ 0 ] + "With" ]( - this === promise ? newDefer.promise() : this, + this, fn ? [ returned ] : arguments ); } @@ -3380,6 +3743,170 @@ jQuery.extend( { fns = null; } ).promise(); }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object @@ -3389,33 +3916,58 @@ jQuery.extend( { }, deferred = {}; - // Keep pipe for back-compat - promise.pipe = promise.then; - // Add list-specific methods jQuery.each( tuples, function( i, tuple ) { var list = tuple[ 2 ], - stateString = tuple[ 3 ]; + stateString = tuple[ 5 ]; - // promise[ done | fail | progress ] = list.add + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add promise[ tuple[ 1 ] ] = list.add; // Handle state if ( stateString ) { - list.add( function() { + list.add( + function() { - // state = [ resolved | rejected ] - state = stateString; + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, - // [ reject_list | resolve_list ].disable; progress_list.lock - }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); } - // deferred[ resolve | reject | notify ] + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } deferred[ tuple[ 0 ] ] = function() { - deferred[ tuple[ 0 ] + "With" ]( this === deferred ? promise : this, arguments ); + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); return this; }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith deferred[ tuple[ 0 ] + "With" ] = list.fireWith; } ); @@ -3432,68 +3984,95 @@ jQuery.extend( { }, // Deferred helper - when: function( subordinate /* , ..., subordinateN */ ) { - var i = 0, - resolveValues = slice.call( arguments ), - length = resolveValues.length, + when: function( singleValue ) { + var - // the count of uncompleted subordinates - remaining = length !== 1 || - ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + // count of uncompleted subordinates + remaining = arguments.length, - // the master Deferred. - // If resolveValues consist of only a single Deferred, just use that. - deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + // count of unprocessed arguments + i = remaining, - // Update function for both resolve and progress values - updateFunc = function( i, contexts, values ) { + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { return function( value ) { - contexts[ i ] = this; - values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; - if ( values === progressValues ) { - deferred.notifyWith( contexts, values ); - } else if ( !( --remaining ) ) { - deferred.resolveWith( contexts, values ); + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); } }; - }, + }; - progressValues, progressContexts, resolveContexts; + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, + !remaining ); - // Add listeners to Deferred subordinates; treat others as resolved - if ( length > 1 ) { - progressValues = new Array( length ); - progressContexts = new Array( length ); - resolveContexts = new Array( length ); - for ( ; i < length; i++ ) { - if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { - resolveValues[ i ].promise() - .progress( updateFunc( i, progressContexts, progressValues ) ) - .done( updateFunc( i, resolveContexts, resolveValues ) ) - .fail( deferred.reject ); - } else { - --remaining; - } + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); } } - // If we're not waiting on anything, resolve the master - if ( !remaining ) { - deferred.resolveWith( resolveContexts, resolveValues ); + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); } - return deferred.promise(); + return master.promise(); } } ); +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + // The deferred used on DOM ready -var readyList; +var readyList = jQuery.Deferred(); jQuery.fn.ready = function( fn ) { - // Add the callback - jQuery.ready.promise().done( fn ); + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); return this; }; @@ -3507,15 +4086,6 @@ jQuery.extend( { // the ready event fires. See #6781 readyWait: 1, - // Hold (or release) the ready event - holdReady: function( hold ) { - if ( hold ) { - jQuery.readyWait++; - } else { - jQuery.ready( true ); - } - }, - // Handle when the DOM is ready ready: function( wait ) { @@ -3534,53 +4104,36 @@ jQuery.extend( { // If there are functions bound, to execute readyList.resolveWith( document, [ jQuery ] ); - - // Trigger any bound ready events - if ( jQuery.fn.triggerHandler ) { - jQuery( document ).triggerHandler( "ready" ); - jQuery( document ).off( "ready" ); - } } } ); -/** - * The ready event handler and self cleanup method - */ +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method function completed() { document.removeEventListener( "DOMContentLoaded", completed ); window.removeEventListener( "load", completed ); jQuery.ready(); } -jQuery.ready.promise = function( obj ) { - if ( !readyList ) { +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { - readyList = jQuery.Deferred(); - - // Catch cases where $(document).ready() is called - // after the browser event has already occurred. - // Support: IE9-10 only - // Older IE sometimes signals "interactive" too soon - if ( document.readyState === "complete" || - ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { - - // Handle it asynchronously to allow scripts the opportunity to delay ready - window.setTimeout( jQuery.ready ); - - } else { + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed ); +} else { - // A fallback to window.onload, that will always work - window.addEventListener( "load", completed ); - } - } - return readyList.promise( obj ); -}; + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); -// Kick off the DOM ready check even if the user does not -jQuery.ready.promise(); + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} @@ -3593,7 +4146,7 @@ var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { bulk = key == null; // Sets many values - if ( jQuery.type( key ) === "object" ) { + if ( toType( key ) === "object" ) { chainable = true; for ( i in key ) { access( elems, fn, i, key[ i ], true, emptyGet, raw ); @@ -3603,7 +4156,7 @@ var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { } else if ( value !== undefined ) { chainable = true; - if ( !jQuery.isFunction( value ) ) { + if ( !isFunction( value ) ) { raw = true; } @@ -3617,7 +4170,7 @@ var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { // ...except when executing function values } else { bulk = fn; - fn = function( elem, key, value ) { + fn = function( elem, _key, value ) { return bulk.call( jQuery( elem ), value ); }; } @@ -3634,14 +4187,34 @@ var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { } } - return chainable ? - elems : + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } - // Gets - bulk ? - fn.call( elems ) : - len ? fn( elems[ 0 ], key ) : emptyGet; + return len ? fn( elems[ 0 ], key ) : emptyGet; }; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} var acceptData = function( owner ) { // Accepts only: @@ -3650,7 +4223,6 @@ var acceptData = function( owner ) { // - Node.DOCUMENT_NODE // - Object // - Any - /* jshint -W018 */ return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); }; @@ -3665,35 +4237,8 @@ Data.uid = 1; Data.prototype = { - register: function( owner, initial ) { - var value = initial || {}; - - // If it is a node unlikely to be stringify-ed or looped over - // use plain assignment - if ( owner.nodeType ) { - owner[ this.expando ] = value; - - // Otherwise secure it in a non-enumerable, non-writable property - // configurability must be true to allow the property to be - // deleted with the delete operator - } else { - Object.defineProperty( owner, this.expando, { - value: value, - writable: true, - configurable: true - } ); - } - return owner[ this.expando ]; - }, cache: function( owner ) { - // We can accept data for non-element nodes in modern browsers, - // but we should not, see #8335. - // Always return an empty object. - if ( !acceptData( owner ) ) { - return {}; - } - // Check if the owner object already has a cache var value = owner[ this.expando ]; @@ -3730,15 +4275,16 @@ Data.prototype = { cache = this.cache( owner ); // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) if ( typeof data === "string" ) { - cache[ data ] = value; + cache[ camelCase( data ) ] = value; // Handle: [ owner, { properties } ] args } else { // Copy the properties one-by-one to the cache object for ( prop in data ) { - cache[ prop ] = data[ prop ]; + cache[ camelCase( prop ) ] = data[ prop ]; } } return cache; @@ -3746,10 +4292,11 @@ Data.prototype = { get: function( owner, key ) { return key === undefined ? this.cache( owner ) : - owner[ this.expando ] && owner[ this.expando ][ key ]; + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; }, access: function( owner, key, value ) { - var stored; // In cases where either: // @@ -3765,10 +4312,7 @@ Data.prototype = { if ( key === undefined || ( ( key && typeof key === "string" ) && value === undefined ) ) { - stored = this.get( owner, key ); - - return stored !== undefined ? - stored : this.get( owner, jQuery.camelCase( key ) ); + return this.get( owner, key ); } // When the key is not a string, or both a key and value @@ -3784,58 +4328,45 @@ Data.prototype = { return value !== undefined ? value : key; }, remove: function( owner, key ) { - var i, name, camel, + var i, cache = owner[ this.expando ]; if ( cache === undefined ) { return; } - if ( key === undefined ) { - this.register( owner ); - - } else { + if ( key !== undefined ) { // Support array or space separated string of keys - if ( jQuery.isArray( key ) ) { - - // If "name" is an array of keys... - // When data is initially created, via ("key", "val") signature, - // keys will be converted to camelCase. - // Since there is no way to tell _how_ a key was added, remove - // both plain key and camelCase key. #12786 - // This will only penalize the array argument path. - name = key.concat( key.map( jQuery.camelCase ) ); - } else { - camel = jQuery.camelCase( key ); + if ( Array.isArray( key ) ) { - // Try the string as a key before any manipulation - if ( key in cache ) { - name = [ key, camel ]; - } else { + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); - // If a key with the spaces exists, use it. - // Otherwise, create an array by matching non-whitespace - name = camel; - name = name in cache ? - [ name ] : ( name.match( rnotwhite ) || [] ); - } + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); } - i = name.length; + i = key.length; while ( i-- ) { - delete cache[ name[ i ] ]; + delete cache[ key[ i ] ]; } } // Remove the expando if there's no more data if ( key === undefined || jQuery.isEmptyObject( cache ) ) { - // Support: Chrome <= 35-45+ + // Support: Chrome <=35 - 45 // Webkit & Blink performance suffers when deleting properties // from DOM nodes, so set to undefined instead - // https://code.google.com/p/chromium/issues/detail?id=378607 + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) if ( owner.nodeType ) { owner[ this.expando ] = undefined; } else { @@ -3867,6 +4398,31 @@ var dataUser = new Data(); var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, rmultiDash = /[A-Z]/g; +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + function dataAttr( elem, key, data ) { var name; @@ -3878,14 +4434,7 @@ function dataAttr( elem, key, data ) { if ( typeof data === "string" ) { try { - data = data === "true" ? true : - data === "false" ? false : - data === "null" ? null : - - // Only convert to a number if it doesn't change the string - +data + "" === data ? +data : - rbrace.test( data ) ? jQuery.parseJSON( data ) : - data; + data = getData( data ); } catch ( e ) {} // Make sure we set the data so it isn't changed later @@ -3936,12 +4485,12 @@ jQuery.fn.extend( { i = attrs.length; while ( i-- ) { - // Support: IE11+ + // Support: IE 11 only // The attrs elements can be null (#14894) if ( attrs[ i ] ) { name = attrs[ i ].name; if ( name.indexOf( "data-" ) === 0 ) { - name = jQuery.camelCase( name.slice( 5 ) ); + name = camelCase( name.slice( 5 ) ); dataAttr( elem, name, data[ name ] ); } } @@ -3961,7 +4510,7 @@ jQuery.fn.extend( { } return access( this, function( value ) { - var data, camelKey; + var data; // The calling jQuery object (element matches) is not empty // (and therefore has an element appears at this[ 0 ]) and the @@ -3971,29 +4520,15 @@ jQuery.fn.extend( { if ( elem && value === undefined ) { // Attempt to get data from the cache - // with the key as-is - data = dataUser.get( elem, key ) || - - // Try to find dashed key if it exists (gh-2779) - // This is for 2.2.x only - dataUser.get( elem, key.replace( rmultiDash, "-$&" ).toLowerCase() ); - - if ( data !== undefined ) { - return data; - } - - camelKey = jQuery.camelCase( key ); - - // Attempt to get data from the cache - // with the key camelized - data = dataUser.get( elem, camelKey ); + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); if ( data !== undefined ) { return data; } // Attempt to "discover" the data in // HTML5 custom data-* attrs - data = dataAttr( elem, camelKey, undefined ); + data = dataAttr( elem, key ); if ( data !== undefined ) { return data; } @@ -4003,24 +4538,10 @@ jQuery.fn.extend( { } // Set the data... - camelKey = jQuery.camelCase( key ); this.each( function() { - // First, attempt to store a copy or reference of any - // data that might've been store with a camelCased key. - var data = dataUser.get( this, camelKey ); - - // For HTML5 data-* attribute interop, we have to - // store property names with dashes in a camelCase form. - // This might not apply to all properties...* - dataUser.set( this, camelKey, value ); - - // *... In the case of properties that might _actually_ - // have dashes, we need to also store a copy of that - // unchanged property. - if ( key.indexOf( "-" ) > -1 && data !== undefined ) { - dataUser.set( this, key, value ); - } + // We always store the camelCased key + dataUser.set( this, key, value ); } ); }, null, value, arguments.length > 1, null, true ); }, @@ -4043,7 +4564,7 @@ jQuery.extend( { // Speed up dequeue by getting out quickly if this is just a lookup if ( data ) { - if ( !queue || jQuery.isArray( data ) ) { + if ( !queue || Array.isArray( data ) ) { queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); } else { queue.push( data ); @@ -4173,58 +4694,95 @@ var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; -var isHidden = function( elem, el ) { +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { - // isHidden might be called from jQuery#filter function; + // isHiddenWithinTree might be called from jQuery#filter function; // in that case, element will be second argument elem = el || elem; - return jQuery.css( elem, "display" ) === "none" || - !jQuery.contains( elem.ownerDocument, elem ); + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; }; function adjustCSS( elem, prop, valueParts, tween ) { - var adjusted, - scale = 1, + var adjusted, scale, maxIterations = 20, currentValue = tween ? - function() { return tween.cur(); } : - function() { return jQuery.css( elem, prop, "" ); }, + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, initial = currentValue(), unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), // Starting value computation is required for potential unit mismatches - initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && rcssNum.exec( jQuery.css( elem, prop ) ); if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + // Trust units reported by jQuery.css unit = unit || initialInUnit[ 3 ]; - // Make sure we update the tween properties later on - valueParts = valueParts || []; - // Iteratively approximate from a nonzero starting point initialInUnit = +initial || 1; - do { - - // If previous iteration zeroed out, double until we get *something*. - // Use string for doubling so we don't accidentally see scale as unchanged below - scale = scale || ".5"; + while ( maxIterations-- ) { - // Adjust and apply - initialInUnit = initialInUnit / scale; + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; - // Update scale, tolerating zero or NaN from tween.cur() - // Break the loop if scale is unchanged or perfect, or if we've just had enough. - } while ( - scale !== ( scale = currentValue() / initial ) && scale !== 1 && --maxIterations - ); - } + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } if ( valueParts ) { initialInUnit = +initialInUnit || +initial || 0; @@ -4241,20 +4799,145 @@ function adjustCSS( elem, prop, valueParts, tween ) { } return adjusted; } + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); var rcheckableType = ( /^(?:checkbox|radio)$/i ); -var rtagName = ( /<([\w:-]+)/ ); +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); -var rscriptType = ( /^$|\/(?:java|ecma)script/i ); + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = "<textarea>x</textarea>"; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + // Support: IE <=9 only + // IE <=9 replaces <option> tags with their contents when inserted outside of + // the select element. + div.innerHTML = "<option></option>"; + support.option = !!div.lastChild; +} )(); // We have to close these tags to support XHTML (#13200) var wrapMap = { - // Support: IE9 - option: [ 1, "<select multiple='multiple'>", "</select>" ], - // XHTML parsers do not magically insert elements in the // same way that tag soup parsers do. So we cannot shorten // this by omitting <tbody> or other required elements. @@ -4266,26 +4949,36 @@ var wrapMap = { _default: [ 0, "", "" ] }; -// Support: IE9 -wrapMap.optgroup = wrapMap.option; - wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; wrapMap.th = wrapMap.td; +// Support: IE <=9 only +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "<select multiple='multiple'>", "</select>" ]; +} + function getAll( context, tag ) { - // Support: IE9-11+ + // Support: IE <=9 - 11 only // Use typeof to avoid zero-argument method invocation on host objects (#15151) - var ret = typeof context.getElementsByTagName !== "undefined" ? - context.getElementsByTagName( tag || "*" ) : - typeof context.querySelectorAll !== "undefined" ? - context.querySelectorAll( tag || "*" ) : - []; - - return tag === undefined || tag && jQuery.nodeName( context, tag ) ? - jQuery.merge( [ context ], ret ) : - ret; + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; } @@ -4307,7 +5000,7 @@ function setGlobalEval( elems, refElements ) { var rhtml = /<|&#?\w+;/; function buildFragment( elems, context, scripts, selection, ignored ) { - var elem, tmp, tag, wrap, contains, j, + var elem, tmp, tag, wrap, attached, j, fragment = context.createDocumentFragment(), nodes = [], i = 0, @@ -4319,9 +5012,9 @@ function buildFragment( elems, context, scripts, selection, ignored ) { if ( elem || elem === 0 ) { // Add nodes directly - if ( jQuery.type( elem ) === "object" ) { + if ( toType( elem ) === "object" ) { - // Support: Android<4.1, PhantomJS<2 + // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); @@ -4344,7 +5037,7 @@ function buildFragment( elems, context, scripts, selection, ignored ) { tmp = tmp.lastChild; } - // Support: Android<4.1, PhantomJS<2 + // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit jQuery.merge( nodes, tmp.childNodes ); @@ -4371,13 +5064,13 @@ function buildFragment( elems, context, scripts, selection, ignored ) { continue; } - contains = jQuery.contains( elem.ownerDocument, elem ); + attached = isAttached( elem ); // Append to fragment tmp = getAll( fragment.appendChild( elem ), "script" ); // Preserve script evaluation history - if ( contains ) { + if ( attached ) { setGlobalEval( tmp ); } @@ -4396,32 +5089,6 @@ function buildFragment( elems, context, scripts, selection, ignored ) { } -( function() { - var fragment = document.createDocumentFragment(), - div = fragment.appendChild( document.createElement( "div" ) ), - input = document.createElement( "input" ); - - // Support: Android 4.0-4.3, Safari<=5.1 - // Check state lost if the name is set (#11217) - // Support: Windows Web Apps (WWA) - // `name` and `type` must use .setAttribute for WWA (#14901) - input.setAttribute( "type", "radio" ); - input.setAttribute( "checked", "checked" ); - input.setAttribute( "name", "t" ); - - div.appendChild( input ); - - // Support: Safari<=5.1, Android<4.2 - // Older WebKit doesn't clone checked state correctly in fragments - support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE<=11+ - // Make sure textarea (and checkbox) defaultValue is properly cloned - div.innerHTML = "<textarea>x</textarea>"; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; -} )(); - - var rkeyEvent = /^key/, rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, @@ -4435,8 +5102,19 @@ function returnFalse() { return false; } -// Support: IE9 -// See #13393 for more info +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 function safeActiveElement() { try { return document.activeElement; @@ -4519,8 +5197,8 @@ jQuery.event = { special, handlers, type, namespaces, origType, elemData = dataPriv.get( elem ); - // Don't attach events to noData or text/comment nodes (but allow plain objects) - if ( !elemData ) { + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { return; } @@ -4531,6 +5209,12 @@ jQuery.event = { selector = handleObjIn.selector; } + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + // Make sure that the handler has a unique ID, used to find/remove it later if ( !handler.guid ) { handler.guid = jQuery.guid++; @@ -4538,7 +5222,7 @@ jQuery.event = { // Init the element's event structure and main handler, if this is the first if ( !( events = elemData.events ) ) { - events = elemData.events = {}; + events = elemData.events = Object.create( null ); } if ( !( eventHandle = elemData.handle ) ) { eventHandle = elemData.handle = function( e ) { @@ -4551,7 +5235,7 @@ jQuery.event = { } // Handle multiple events separated by a space - types = ( types || "" ).match( rnotwhite ) || [ "" ]; + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; t = types.length; while ( t-- ) { tmp = rtypenamespace.exec( types[ t ] ) || []; @@ -4633,7 +5317,7 @@ jQuery.event = { } // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( rnotwhite ) || [ "" ]; + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; t = types.length; while ( t-- ) { tmp = rtypenamespace.exec( types[ t ] ) || []; @@ -4694,19 +5378,26 @@ jQuery.event = { } }, - dispatch: function( event ) { + dispatch: function( nativeEvent ) { - // Make a writable jQuery.Event from the native event object - event = jQuery.event.fix( event ); + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), - var i, j, ret, matched, handleObj, - handlerQueue = [], - args = slice.call( arguments ), - handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], special = jQuery.event.special[ event.type ] || {}; // Use the fix-ed jQuery.Event rather than the (read-only) native event args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + event.delegateTarget = this; // Call the preDispatch hook for the mapped type, and let it bail if desired @@ -4726,9 +5417,10 @@ jQuery.event = { while ( ( handleObj = matched.handlers[ j++ ] ) && !event.isImmediatePropagationStopped() ) { - // Triggered event must either 1) have no namespace, or 2) have namespace(s) - // a subset or equal to those in the bound event (both can have no namespace). - if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) { + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { event.handleObj = handleObj; event.data = handleObj.data; @@ -4755,146 +5447,95 @@ jQuery.event = { }, handlers: function( event, handlers ) { - var i, matches, sel, handleObj, + var i, handleObj, sel, matchedHandlers, matchedSelectors, handlerQueue = [], delegateCount = handlers.delegateCount, cur = event.target; - // Support (at least): Chrome, IE9 // Find delegate handlers - // Black-hole SVG <use> instance trees (#13180) - // - // Support: Firefox<=42+ - // Avoid non-left-click in FF but don't block IE radio events (#3861, gh-2343) - if ( delegateCount && cur.nodeType && - ( event.type !== "click" || isNaN( event.button ) || event.button < 1 ) ) { + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG <use> instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { for ( ; cur !== this; cur = cur.parentNode || this ) { // Don't check non-elements (#13208) // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.nodeType === 1 && ( cur.disabled !== true || event.type !== "click" ) ) { - matches = []; + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; for ( i = 0; i < delegateCount; i++ ) { handleObj = handlers[ i ]; // Don't conflict with Object.prototype properties (#13203) sel = handleObj.selector + " "; - if ( matches[ sel ] === undefined ) { - matches[ sel ] = handleObj.needsContext ? + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? jQuery( sel, this ).index( cur ) > -1 : jQuery.find( sel, this, null, [ cur ] ).length; } - if ( matches[ sel ] ) { - matches.push( handleObj ); + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); } } - if ( matches.length ) { - handlerQueue.push( { elem: cur, handlers: matches } ); + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); } } } } // Add the remaining (directly-bound) handlers + cur = this; if ( delegateCount < handlers.length ) { - handlerQueue.push( { elem: this, handlers: handlers.slice( delegateCount ) } ); + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); } return handlerQueue; }, - // Includes some event props shared by KeyEvent and MouseEvent - props: ( "altKey bubbles cancelable ctrlKey currentTarget detail eventPhase " + - "metaKey relatedTarget shiftKey target timeStamp view which" ).split( " " ), - - fixHooks: {}, - - keyHooks: { - props: "char charCode key keyCode".split( " " ), - filter: function( event, original ) { - - // Add which for key events - if ( event.which == null ) { - event.which = original.charCode != null ? original.charCode : original.keyCode; - } + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, - return event; - } - }, - - mouseHooks: { - props: ( "button buttons clientX clientY offsetX offsetY pageX pageY " + - "screenX screenY toElement" ).split( " " ), - filter: function( event, original ) { - var eventDoc, doc, body, - button = original.button; - - // Calculate pageX/Y if missing and clientX/Y available - if ( event.pageX == null && original.clientX != null ) { - eventDoc = event.target.ownerDocument || document; - doc = eventDoc.documentElement; - body = eventDoc.body; - - event.pageX = original.clientX + - ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); - event.pageY = original.clientY + - ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - - ( doc && doc.clientTop || body && body.clientTop || 0 ); - } + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, - // Add which for click: 1 === left; 2 === middle; 3 === right - // Note: button is not normalized, so don't use it - if ( !event.which && button !== undefined ) { - event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); } - - return event; - } + } ); }, - fix: function( event ) { - if ( event[ jQuery.expando ] ) { - return event; - } - - // Create a writable copy of the event object and normalize some properties - var i, prop, copy, - type = event.type, - originalEvent = event, - fixHook = this.fixHooks[ type ]; - - if ( !fixHook ) { - this.fixHooks[ type ] = fixHook = - rmouseEvent.test( type ) ? this.mouseHooks : - rkeyEvent.test( type ) ? this.keyHooks : - {}; - } - copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; - - event = new jQuery.Event( originalEvent ); - - i = copy.length; - while ( i-- ) { - prop = copy[ i ]; - event[ prop ] = originalEvent[ prop ]; - } - - // Support: Cordova 2.5 (WebKit) (#13255) - // All events should have a target; Cordova deviceready doesn't - if ( !event.target ) { - event.target = document; - } - - // Support: Safari 6.0+, Chrome<28 - // Target should not be a text node (#504, #13143) - if ( event.target.nodeType === 3 ) { - event.target = event.target.parentNode; - } - - return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); }, special: { @@ -4903,39 +5544,51 @@ jQuery.event = { // Prevent triggered image.load events from bubbling to window.load noBubble: true }, - focus: { + click: { - // Fire native event if possible so blur/focus sequence is correct - trigger: function() { - if ( this !== safeActiveElement() && this.focus ) { - this.focus(); - return false; - } - }, - delegateType: "focusin" - }, - blur: { - trigger: function() { - if ( this === safeActiveElement() && this.blur ) { - this.blur(); - return false; + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); } + + // Return false to allow normal processing in the caller + return false; }, - delegateType: "focusout" - }, - click: { + trigger: function( data ) { - // For checkbox, fire native event so checked state will be right - trigger: function() { - if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) { - this.click(); - return false; + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); } + + // Return non-false to allow normal event-path propagation + return true; }, - // For cross-browser consistency, don't fire native .click() on links + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack _default: function( event ) { - return jQuery.nodeName( event.target, "a" ); + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); } }, @@ -4952,6 +5605,93 @@ jQuery.event = { } }; +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + return result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + jQuery.removeEvent = function( elem, type, handle ) { // This "if" is needed for plain objects @@ -4977,11 +5717,21 @@ jQuery.Event = function( src, props ) { this.isDefaultPrevented = src.defaultPrevented || src.defaultPrevented === undefined && - // Support: Android<4.0 + // Support: Android <=2.3 only src.returnValue === false ? returnTrue : returnFalse; + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + // Event type } else { this.type = src; @@ -4993,26 +5743,27 @@ jQuery.Event = function( src, props ) { } // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || jQuery.now(); + this.timeStamp = src && src.timeStamp || Date.now(); // Mark it as fixed this[ jQuery.expando ] = true; }; // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html jQuery.Event.prototype = { constructor: jQuery.Event, isDefaultPrevented: returnFalse, isPropagationStopped: returnFalse, isImmediatePropagationStopped: returnFalse, + isSimulated: false, preventDefault: function() { var e = this.originalEvent; this.isDefaultPrevented = returnTrue; - if ( e ) { + if ( e && !this.isSimulated ) { e.preventDefault(); } }, @@ -5021,7 +5772,7 @@ jQuery.Event.prototype = { this.isPropagationStopped = returnTrue; - if ( e ) { + if ( e && !this.isSimulated ) { e.stopPropagation(); } }, @@ -5030,7 +5781,7 @@ jQuery.Event.prototype = { this.isImmediatePropagationStopped = returnTrue; - if ( e ) { + if ( e && !this.isSimulated ) { e.stopImmediatePropagation(); } @@ -5038,13 +5789,102 @@ jQuery.Event.prototype = { } }; +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + delegateType: delegateType + }; +} ); + // Create mouseenter/leave events using mouseover/out and event-time checks // so that event delegation works in jQuery. // Do the same for pointerenter/pointerleave and pointerover/pointerout // // Support: Safari 7 only // Safari sends mouseenter too often; see: -// https://code.google.com/p/chromium/issues/detail?id=470258 +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 // for the description of the bug (it existed in older Chrome versions as well). jQuery.each( { mouseenter: "mouseover", @@ -5075,6 +5915,7 @@ jQuery.each( { } ); jQuery.fn.extend( { + on: function( types, selector, data, fn ) { return on( this, types, selector, data, fn ); }, @@ -5121,26 +5962,25 @@ jQuery.fn.extend( { var - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi, - // Support: IE 10-11, Edge 10240+ + // Support: IE <=10 - 11, Edge 12 - 13 only // In IE/Edge using regex groups here causes severe slowdowns. // See https://connect.microsoft.com/IE/feedback/details/1736512/ rnoInnerhtml = /<script|<style|<link/i, // checked="checked" or checked rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, - rscriptTypeMasked = /^true\/(.*)/, rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g; -// Manipulating tables requires a tbody +// Prefer a tbody over its parent table for containing new rows function manipulationTarget( elem, content ) { - return jQuery.nodeName( elem, "table" ) && - jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ? + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } - elem.getElementsByTagName( "tbody" )[ 0 ] || - elem.appendChild( elem.ownerDocument.createElement( "tbody" ) ) : - elem; + return elem; } // Replace/restore the type attribute of script elements for safe DOM manipulation @@ -5149,10 +5989,8 @@ function disableScript( elem ) { return elem; } function restoreScript( elem ) { - var match = rscriptTypeMasked.exec( elem.type ); - - if ( match ) { - elem.type = match[ 1 ]; + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); } else { elem.removeAttribute( "type" ); } @@ -5161,7 +5999,7 @@ function restoreScript( elem ) { } function cloneCopyEvent( src, dest ) { - var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + var i, l, type, pdataOld, udataOld, udataCur, events; if ( dest.nodeType !== 1 ) { return; @@ -5169,13 +6007,11 @@ function cloneCopyEvent( src, dest ) { // 1. Copy private data: events, handlers, etc. if ( dataPriv.hasData( src ) ) { - pdataOld = dataPriv.access( src ); - pdataCur = dataPriv.set( dest, pdataOld ); + pdataOld = dataPriv.get( src ); events = pdataOld.events; if ( events ) { - delete pdataCur.handle; - pdataCur.events = {}; + dataPriv.remove( dest, "handle events" ); for ( type in events ) { for ( i = 0, l = events[ type ].length; i < l; i++ ) { @@ -5211,22 +6047,22 @@ function fixInput( src, dest ) { function domManip( collection, args, callback, ignored ) { // Flatten any nested arrays - args = concat.apply( [], args ); + args = flat( args ); var fragment, first, scripts, hasScripts, node, doc, i = 0, l = collection.length, iNoClone = l - 1, value = args[ 0 ], - isFunction = jQuery.isFunction( value ); + valueIsFunction = isFunction( value ); // We can't cloneNode fragments that contain checked, in WebKit - if ( isFunction || + if ( valueIsFunction || ( l > 1 && typeof value === "string" && !support.checkClone && rchecked.test( value ) ) ) { return collection.each( function( index ) { var self = collection.eq( index ); - if ( isFunction ) { + if ( valueIsFunction ) { args[ 0 ] = value.call( this, index, self.html() ); } domManip( self, args, callback, ignored ); @@ -5258,7 +6094,7 @@ function domManip( collection, args, callback, ignored ) { // Keep references to cloned scripts for later restoration if ( hasScripts ) { - // Support: Android<4.1, PhantomJS<2 + // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit jQuery.merge( scripts, getAll( node, "script" ) ); } @@ -5280,14 +6116,16 @@ function domManip( collection, args, callback, ignored ) { !dataPriv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) { - if ( node.src ) { + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl ) { - jQuery._evalUrl( node.src ); + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); } } else { - jQuery.globalEval( node.textContent.replace( rcleanScript, "" ) ); + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); } } } @@ -5309,7 +6147,7 @@ function remove( elem, selector, keepData ) { } if ( node.parentNode ) { - if ( keepData && jQuery.contains( node.ownerDocument, node ) ) { + if ( keepData && isAttached( node ) ) { setGlobalEval( getAll( node, "script" ) ); } node.parentNode.removeChild( node ); @@ -5321,19 +6159,19 @@ function remove( elem, selector, keepData ) { jQuery.extend( { htmlPrefilter: function( html ) { - return html.replace( rxhtmlTag, "<$1></$2>" ); + return html; }, clone: function( elem, dataAndEvents, deepDataAndEvents ) { var i, l, srcElements, destElements, clone = elem.cloneNode( true ), - inPage = jQuery.contains( elem.ownerDocument, elem ); + inPage = isAttached( elem ); // Fix IE cloning issues if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && !jQuery.isXMLDoc( elem ) ) { - // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 destElements = getAll( clone ); srcElements = getAll( elem ); @@ -5386,13 +6224,13 @@ jQuery.extend( { } } - // Support: Chrome <= 35-45+ + // Support: Chrome <=35 - 45+ // Assign undefined instead of using delete, see Data#remove elem[ dataPriv.expando ] = undefined; } if ( elem[ dataUser.expando ] ) { - // Support: Chrome <= 35-45+ + // Support: Chrome <=35 - 45+ // Assign undefined instead of using delete, see Data#remove elem[ dataUser.expando ] = undefined; } @@ -5402,10 +6240,6 @@ jQuery.extend( { } ); jQuery.fn.extend( { - - // Keep domManip exposed until 3.0 (gh-2225) - domManip: domManip, - detach: function( selector ) { return remove( this, selector, true ); }, @@ -5563,86 +6397,19 @@ jQuery.each( { elems = i === last ? this : this.clone( true ); jQuery( insert[ i ] )[ original ]( elems ); - // Support: QtWebKit - // .get() because push.apply(_, arraylike) throws + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit push.apply( ret, elems.get() ); } return this.pushStack( ret ); }; } ); - - -var iframe, - elemdisplay = { - - // Support: Firefox - // We have to pre-define these values for FF (#10227) - HTML: "block", - BODY: "block" - }; - -/** - * Retrieve the actual display of a element - * @param {String} name nodeName of the element - * @param {Object} doc Document object - */ - -// Called only from within defaultDisplay -function actualDisplay( name, doc ) { - var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ), - - display = jQuery.css( elem[ 0 ], "display" ); - - // We don't have any data stored on the element, - // so use "detach" method as fast way to get rid of the element - elem.detach(); - - return display; -} - -/** - * Try to determine the default display value of an element - * @param {String} nodeName - */ -function defaultDisplay( nodeName ) { - var doc = document, - display = elemdisplay[ nodeName ]; - - if ( !display ) { - display = actualDisplay( nodeName, doc ); - - // If the simple way fails, read from inside an iframe - if ( display === "none" || !display ) { - - // Use the already-created iframe if possible - iframe = ( iframe || jQuery( "<iframe frameborder='0' width='0' height='0'/>" ) ) - .appendTo( doc.documentElement ); - - // Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse - doc = iframe[ 0 ].contentDocument; - - // Support: IE - doc.write(); - doc.close(); - - display = actualDisplay( nodeName, doc ); - iframe.detach(); - } - - // Store the correct default display - elemdisplay[ nodeName ] = display; - } - - return display; -} -var rmargin = ( /^margin/ ); - var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); var getStyles = function( elem ) { - // Support: IE<=11+, Firefox<=30+ (#15098, #14150) + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) // IE throws on elements created in popups // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" var view = elem.ownerDocument.defaultView; @@ -5654,7 +6421,7 @@ var getStyles = function( elem ) { return view.getComputedStyle( elem ); }; -var swap = function( elem, options, callback, args ) { +var swap = function( elem, options, callback ) { var ret, name, old = {}; @@ -5664,7 +6431,7 @@ var swap = function( elem, options, callback, args ) { elem.style[ name ] = options[ name ]; } - ret = callback.apply( elem, args || [] ); + ret = callback.call( elem ); // Revert the old values for ( name in options ) { @@ -5675,117 +6442,127 @@ var swap = function( elem, options, callback, args ) { }; -var documentElement = document.documentElement; +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); ( function() { - var pixelPositionVal, boxSizingReliableVal, pixelMarginRightVal, reliableMarginLeftVal, - container = document.createElement( "div" ), - div = document.createElement( "div" ); - - // Finish early in limited (non-browser) environments - if ( !div.style ) { - return; - } - - // Support: IE9-11+ - // Style of cloned element affects source element cloned (#8908) - div.style.backgroundClip = "content-box"; - div.cloneNode( true ).style.backgroundClip = ""; - support.clearCloneStyle = div.style.backgroundClip === "content-box"; - - container.style.cssText = "border:0;width:8px;height:0;top:0;left:-9999px;" + - "padding:0;margin-top:1px;position:absolute"; - container.appendChild( div ); // Executing both pixelPosition & boxSizingReliable tests require only one layout // so they're executed at the same time to save the second computation. function computeStyleTests() { - div.style.cssText = - // Support: Firefox<29, Android 2.3 - // Vendor-prefix box-sizing - "-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;" + - "position:relative;display:block;" + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + "margin:auto;border:1px;padding:1px;" + - "top:1%;width:50%"; - div.innerHTML = ""; - documentElement.appendChild( container ); + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); var divStyle = window.getComputedStyle( div ); pixelPositionVal = divStyle.top !== "1%"; - reliableMarginLeftVal = divStyle.marginLeft === "2px"; - boxSizingReliableVal = divStyle.width === "4px"; - // Support: Android 4.0 - 4.3 only + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 // Some styles come back with percentage values, even though they shouldn't - div.style.marginRight = "50%"; - pixelMarginRightVal = divStyle.marginRight === "4px"; + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; } - jQuery.extend( support, { - pixelPosition: function() { + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } - // This test is executed only once but we still do memoizing - // since we can use the boxSizingReliable pre-computing. - // No need to check if the test was already performed, though. - computeStyleTests(); - return pixelPositionVal; - }, + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { boxSizingReliable: function() { - if ( boxSizingReliableVal == null ) { - computeStyleTests(); - } + computeStyleTests(); return boxSizingReliableVal; }, - pixelMarginRight: function() { - - // Support: Android 4.0-4.3 - // We're checking for boxSizingReliableVal here instead of pixelMarginRightVal - // since that compresses better and they're computed together anyway. - if ( boxSizingReliableVal == null ) { - computeStyleTests(); - } - return pixelMarginRightVal; + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; }, reliableMarginLeft: function() { - - // Support: IE <=8 only, Android 4.0 - 4.3 only, Firefox <=3 - 37 - if ( boxSizingReliableVal == null ) { - computeStyleTests(); - } + computeStyleTests(); return reliableMarginLeftVal; }, - reliableMarginRight: function() { - - // Support: Android 2.3 - // Check if div with explicit width and no margin-right incorrectly - // gets computed margin-right based on width of container. (#3333) - // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right - // This support function is only executed once so no memoizing is needed. - var ret, - marginDiv = div.appendChild( document.createElement( "div" ) ); + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, - // Reset CSS: box-sizing; display; margin; border; padding - marginDiv.style.cssText = div.style.cssText = + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); - // Support: Android 2.3 - // Vendor-prefix box-sizing - "-webkit-box-sizing:content-box;box-sizing:content-box;" + - "display:block;margin:0;border:0;padding:0"; - marginDiv.style.marginRight = marginDiv.style.width = "0"; - div.style.width = "1px"; - documentElement.appendChild( container ); + table.style.cssText = "position:absolute;left:-11111px"; + tr.style.height = "1px"; + trChild.style.height = "9px"; - ret = !parseFloat( window.getComputedStyle( marginDiv ).marginRight ); + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); - documentElement.removeChild( container ); - div.removeChild( marginDiv ); + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; - return ret; + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; } } ); } )(); @@ -5793,28 +6570,31 @@ var documentElement = document.documentElement; function curCSS( elem, name, computed ) { var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements style = elem.style; computed = computed || getStyles( elem ); - ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined; - // Support: Opera 12.1x only - // Fall back to style even without computed - // computed is undefined for elems on document fragments - if ( ( ret === "" || ret === undefined ) && !jQuery.contains( elem.ownerDocument, elem ) ) { - ret = jQuery.style( elem, name ); - } - - // Support: IE9 - // getPropertyValue is only needed for .css('filter') (#12537) + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } // A tribute to the "awesome hack by Dean Edwards" // Android Browser returns percentage for some values, // but width seems to be reliably pixels. // This is against the CSSOM draft spec: - // http://dev.w3.org/csswg/cssom/#resolved-values - if ( !support.pixelMarginRight() && rnumnonpx.test( ret ) && rmargin.test( name ) ) { + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { // Remember the original values width = style.width; @@ -5834,7 +6614,7 @@ function curCSS( elem, name, computed ) { return ret !== undefined ? - // Support: IE9-11+ + // Support: IE <=9 - 11 only // IE returns zIndex value as an integer. ret + "" : ret; @@ -5861,30 +6641,13 @@ function addGetHookIf( conditionFn, hookFn ) { } -var - - // Swappable if display is none or starts with table - // except "table", "table-cell", or "table-caption" - // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: "0", - fontWeight: "400" - }, - - cssPrefixes = [ "Webkit", "O", "Moz", "ms" ], - emptyStyle = document.createElement( "div" ).style; +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; -// Return a css property mapped to a potentially vendor prefixed property +// Return a vendor-prefixed property or undefined function vendorPropName( name ) { - // Shortcut for names that are not vendor prefixed - if ( name in emptyStyle ) { - return name; - } - // Check for vendor prefixed names var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), i = cssPrefixes.length; @@ -5897,7 +6660,34 @@ function vendorPropName( name ) { } } -function setPositiveNumber( elem, value, subtract ) { +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { // Any relative (+/-) values have already been // normalized at this point @@ -5909,166 +6699,148 @@ function setPositiveNumber( elem, value, subtract ) { value; } -function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { - var i = extra === ( isBorderBox ? "border" : "content" ) ? - - // If we already have the right measurement, avoid augmentation - 4 : +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; - // Otherwise initialize for horizontal or vertical properties - name === "width" ? 1 : 0, - - val = 0; + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } for ( ; i < 4; i += 2 ) { - // Both box models exclude margin, so add it if we want it - if ( extra === "margin" ) { - val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); } - if ( isBorderBox ) { + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { - // border-box includes padding, so remove it if we want content - if ( extra === "content" ) { - val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - } + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - // At this point, extra isn't border nor margin, so remove border - if ( extra !== "margin" ) { - val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" } else { - // At this point, extra isn't content, so add padding - val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } - // At this point, extra isn't content nor padding, so add border - if ( extra !== "padding" ) { - val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); } } } - return val; -} + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { -function getWidthOrHeight( elem, name, extra ) { + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 - // Start with offset property, which is equivalent to the border-box value - var valueIsBorderBox = true, - val = name === "width" ? elem.offsetWidth : elem.offsetHeight, - styles = getStyles( elem ), - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } - // Support: IE11 only - // In IE 11 fullscreen elements inside of an iframe have - // 100x too small dimensions (gh-1764). - if ( document.msFullscreenElement && window.top !== window ) { + return delta; +} - // Support: IE11 only - // Running getBoundingClientRect on a disconnected node - // in IE throws an error. - if ( elem.getClientRects().length ) { - val = Math.round( elem.getBoundingClientRect()[ name ] * 100 ); - } - } +function getWidthOrHeight( elem, dimension, extra ) { - // Some non-html elements return undefined for offsetWidth, so check for null/undefined - // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 - // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 - if ( val <= 0 || val == null ) { + // Start with computed style + var styles = getStyles( elem ), - // Fall back to computed then uncomputed css if necessary - val = curCSS( elem, name, styles ); - if ( val < 0 || val == null ) { - val = elem.style[ name ]; - } + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, - // Computed unit is not pixels. Stop here and return. - if ( rnumnonpx.test( val ) ) { + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { return val; } - - // Check for style in case a browser which returns unreliable values - // for getComputedStyle silently falls back to the reliable elem.style - valueIsBorderBox = isBorderBox && - ( support.boxSizingReliable() || val === elem.style[ name ] ); - - // Normalize "", auto, and prepare for extra - val = parseFloat( val ) || 0; + val = "auto"; } - // Use the active box-sizing model to add/subtract irrelevant styles - return ( val + - augmentWidthOrHeight( - elem, - name, - extra || ( isBorderBox ? "border" : "content" ), - valueIsBorderBox, - styles - ) - ) + "px"; -} -function showHide( elements, show ) { - var display, elem, hidden, - values = [], - index = 0, - length = elements.length; + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + if ( ( !support.boxSizingReliable() && isBorderBox || - for ( ; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || - values[ index ] = dataPriv.get( elem, "olddisplay" ); - display = elem.style.display; - if ( show ) { + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || - // Reset the inline display of this element to learn if it is - // being hidden by cascaded rules or not - if ( !values[ index ] && display === "none" ) { - elem.style.display = ""; - } + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && - // Set elements which have been overridden with display: none - // in a stylesheet to whatever the default browser style is - // for such an element - if ( elem.style.display === "" && isHidden( elem ) ) { - values[ index ] = dataPriv.access( - elem, - "olddisplay", - defaultDisplay( elem.nodeName ) - ); - } - } else { - hidden = isHidden( elem ); + // Make sure the element is visible & connected + elem.getClientRects().length ) { - if ( display !== "none" || !hidden ) { - dataPriv.set( - elem, - "olddisplay", - hidden ? display : jQuery.css( elem, "display" ) - ); - } - } - } + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; - // Set the display of most of the elements in a second loop - // to avoid the constant reflow - for ( index = 0; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } - if ( !show || elem.style.display === "none" || elem.style.display === "" ) { - elem.style.display = show ? values[ index ] || "" : "none"; + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; } } - return elements; + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; } jQuery.extend( { @@ -6096,6 +6868,13 @@ jQuery.extend( { "flexGrow": true, "flexShrink": true, "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, "lineHeight": true, "opacity": true, "order": true, @@ -6107,9 +6886,7 @@ jQuery.extend( { // Add in properties whose names you wish to fix before // setting or getting the value - cssProps: { - "float": "cssFloat" - }, + cssProps: {}, // Get and set the style property on a DOM Node style: function( elem, name, value, extra ) { @@ -6121,11 +6898,16 @@ jQuery.extend( { // Make sure that we're working with the right name var ret, type, hooks, - origName = jQuery.camelCase( name ), + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), style = elem.style; - name = jQuery.cssProps[ origName ] || - ( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName ); + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } // Gets hook for the prefixed version, then unprefixed version hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; @@ -6148,11 +6930,12 @@ jQuery.extend( { } // If a number was passed in, add the unit (except for certain CSS properties) - if ( type === "number" ) { + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); } - // Support: IE9-11+ // background-* props affect original clone's values if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { style[ name ] = "inherit"; @@ -6162,7 +6945,11 @@ jQuery.extend( { if ( !hooks || !( "set" in hooks ) || ( value = hooks.set( elem, value, extra ) ) !== undefined ) { - style[ name ] = value; + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } } } else { @@ -6181,11 +6968,15 @@ jQuery.extend( { css: function( elem, name, extra, styles ) { var val, num, hooks, - origName = jQuery.camelCase( name ); + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); - // Make sure that we're working with the right name - name = jQuery.cssProps[ origName ] || - ( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName ); + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } // Try prefixed name followed by the unprefixed name hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; @@ -6210,43 +7001,74 @@ jQuery.extend( { num = parseFloat( val ); return extra === true || isFinite( num ) ? num || 0 : val; } + return val; } } ); -jQuery.each( [ "height", "width" ], function( i, name ) { - jQuery.cssHooks[ name ] = { +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { get: function( elem, computed, extra ) { if ( computed ) { // Certain elements can have dimension info if we invisibly show them // but it must have a current display style that would benefit return rdisplayswap.test( jQuery.css( elem, "display" ) ) && - elem.offsetWidth === 0 ? + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? swap( elem, cssShow, function() { - return getWidthOrHeight( elem, name, extra ); + return getWidthOrHeight( elem, dimension, extra ); } ) : - getWidthOrHeight( elem, name, extra ); + getWidthOrHeight( elem, dimension, extra ); } }, set: function( elem, value, extra ) { var matches, - styles = extra && getStyles( elem ), - subtract = extra && augmentWidthOrHeight( - elem, - name, - extra, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - styles + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 ); + } // Convert to pixels if value adjustment is needed if ( subtract && ( matches = rcssNum.exec( value ) ) && ( matches[ 3 ] || "px" ) !== "px" ) { - elem.style[ name ] = value; - value = jQuery.css( elem, name ); + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); } return setPositiveNumber( elem, value, subtract ); @@ -6267,16 +7089,6 @@ jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, } ); -// Support: Android 2.3 -jQuery.cssHooks.marginRight = addGetHookIf( support.reliableMarginRight, - function( elem, computed ) { - if ( computed ) { - return swap( elem, { "display": "inline-block" }, - curCSS, [ elem, "marginRight" ] ); - } - } -); - // These hooks are used by animate to expand properties jQuery.each( { margin: "", @@ -6300,7 +7112,7 @@ jQuery.each( { } }; - if ( !rmargin.test( prefix ) ) { + if ( prefix !== "margin" ) { jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; } } ); @@ -6312,7 +7124,7 @@ jQuery.fn.extend( { map = {}, i = 0; - if ( jQuery.isArray( name ) ) { + if ( Array.isArray( name ) ) { styles = getStyles( elem ); len = name.length; @@ -6327,25 +7139,6 @@ jQuery.fn.extend( { jQuery.style( elem, name, value ) : jQuery.css( elem, name ); }, name, value, arguments.length > 1 ); - }, - show: function() { - return showHide( this, true ); - }, - hide: function() { - return showHide( this ); - }, - toggle: function( state ) { - if ( typeof state === "boolean" ) { - return state ? this.show() : this.hide(); - } - - return this.each( function() { - if ( isHidden( this ) ) { - jQuery( this ).show(); - } else { - jQuery( this ).hide(); - } - } ); } } ); @@ -6429,9 +7222,9 @@ Tween.propHooks = { // Use .style if available and use plain properties where available. if ( jQuery.fx.step[ tween.prop ] ) { jQuery.fx.step[ tween.prop ]( tween ); - } else if ( tween.elem.nodeType === 1 && - ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || - jQuery.cssHooks[ tween.prop ] ) ) { + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); } else { tween.elem[ tween.prop ] = tween.now; @@ -6440,7 +7233,7 @@ Tween.propHooks = { } }; -// Support: IE9 +// Support: IE <=9 only // Panic based approach to setting things on disconnected nodes Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { set: function( tween ) { @@ -6462,23 +7255,35 @@ jQuery.easing = { jQuery.fx = Tween.prototype.init; -// Back Compat <1.8 extension point +// Back compat <1.8 extension point jQuery.fx.step = {}; var - fxNow, timerId, + fxNow, inProgress, rfxtypes = /^(?:toggle|show|hide)$/, rrun = /queueHooks$/; +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + // Animations created synchronously will run synchronously function createFxNow() { window.setTimeout( function() { fxNow = undefined; } ); - return ( fxNow = jQuery.now() ); + return ( fxNow = Date.now() ); } // Generate parameters to create a standard animation @@ -6490,7 +7295,7 @@ function genFx( type, includeWidth ) { // If we include width, step value is 1 to do all cssExpand values, // otherwise step value is 2 to skip over Left and Right includeWidth = includeWidth ? 1 : 0; - for ( ; i < 4 ; i += 2 - includeWidth ) { + for ( ; i < 4; i += 2 - includeWidth ) { which = cssExpand[ i ]; attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; } @@ -6517,15 +7322,15 @@ function createTween( value, prop, animation ) { } function defaultPrefilter( elem, props, opts ) { - /* jshint validthis: true */ - var prop, value, toggle, tween, hooks, oldfire, display, checkDisplay, + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, anim = this, orig = {}, style = elem.style, - hidden = elem.nodeType && isHidden( elem ), + hidden = elem.nodeType && isHiddenWithinTree( elem ), dataShow = dataPriv.get( elem, "fxshow" ); - // Handle queue: false promises + // Queue-skipping animations hijack the fx hooks if ( !opts.queue ) { hooks = jQuery._queueHooks( elem, "fx" ); if ( hooks.unqueued == null ) { @@ -6551,25 +7356,78 @@ function defaultPrefilter( elem, props, opts ) { } ); } - // Height/width overflow pass - if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) { + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } - // Make sure that nothing sneaks out - // Record all 3 overflow attributes because IE9-10 do not - // change the overflow attribute when overflowX and - // overflowY are set to the same value + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; - // Set display property to inline-block for height/width - // animations on inline elements that are having width/height animated + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } - // Test default display if display is currently "none" - checkDisplay = display === "none" ? - dataPriv.get( elem, "olddisplay" ) || defaultDisplay( elem.nodeName ) : display; + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { - if ( checkDisplay === "inline" && jQuery.css( elem, "float" ) === "none" ) { - style.display = "inline-block"; + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } } } @@ -6582,73 +7440,56 @@ function defaultPrefilter( elem, props, opts ) { } ); } - // show/hide pass - for ( prop in props ) { - value = props[ prop ]; - if ( rfxtypes.exec( value ) ) { - delete props[ prop ]; - toggle = toggle || value === "toggle"; - if ( value === ( hidden ? "hide" : "show" ) ) { + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { - // If there is dataShow left over from a stopped hide or show - // and we are going to proceed with show, we should pretend to be hidden - if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { - hidden = true; - } else { - continue; + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); } - orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); - // Any non-fx value stops us from restoring the original display value - } else { - display = undefined; - } - } + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } - if ( !jQuery.isEmptyObject( orig ) ) { - if ( dataShow ) { - if ( "hidden" in dataShow ) { - hidden = dataShow.hidden; + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); } - } else { - dataShow = dataPriv.access( elem, "fxshow", {} ); - } - // Store state if its toggle - enables .stop().toggle() to "reverse" - if ( toggle ) { - dataShow.hidden = !hidden; - } - if ( hidden ) { - jQuery( elem ).show(); - } else { + /* eslint-disable no-loop-func */ + anim.done( function() { - jQuery( elem ).hide(); - } ); - } - anim.done( function() { - var prop; - dataPriv.remove( elem, "fxshow" ); - for ( prop in orig ) { - jQuery.style( elem, prop, orig[ prop ] ); - } - } ); - for ( prop in orig ) { - tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + /* eslint-enable no-loop-func */ - if ( !( prop in dataShow ) ) { - dataShow[ prop ] = tween.start; - if ( hidden ) { - tween.end = tween.start; - tween.start = prop === "width" || prop === "height" ? 1 : 0; + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); } - } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); } - // If this is a noop like .hide().hide(), restore an overwritten display value - } else if ( ( display === "none" ? defaultDisplay( elem.nodeName ) : display ) === "inline" ) { - style.display = display; + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } } } @@ -6657,10 +7498,10 @@ function propFilter( props, specialEasing ) { // camelCase, specialEasing and expand cssHook pass for ( index in props ) { - name = jQuery.camelCase( index ); + name = camelCase( index ); easing = specialEasing[ name ]; value = props[ index ]; - if ( jQuery.isArray( value ) ) { + if ( Array.isArray( value ) ) { easing = value[ 1 ]; value = props[ index ] = value[ 0 ]; } @@ -6706,25 +7547,32 @@ function Animation( elem, properties, options ) { var currentTime = fxNow || createFxNow(), remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), - // Support: Android 2.3 + // Support: Android 2.3 only // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) temp = remaining / animation.duration || 0, percent = 1 - temp, index = 0, length = animation.tweens.length; - for ( ; index < length ; index++ ) { + for ( ; index < length; index++ ) { animation.tweens[ index ].run( percent ); } deferred.notifyWith( elem, [ animation, percent, remaining ] ); + // If there's more to do, yield if ( percent < 1 && length ) { return remaining; - } else { - deferred.resolveWith( elem, [ animation ] ); - return false; } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; }, animation = deferred.promise( { elem: elem, @@ -6754,7 +7602,7 @@ function Animation( elem, properties, options ) { return this; } stopped = true; - for ( ; index < length ; index++ ) { + for ( ; index < length; index++ ) { animation.tweens[ index ].run( 1 ); } @@ -6772,12 +7620,12 @@ function Animation( elem, properties, options ) { propFilter( props, animation.opts.specialEasing ); - for ( ; index < length ; index++ ) { + for ( ; index < length; index++ ) { result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); if ( result ) { - if ( jQuery.isFunction( result.stop ) ) { + if ( isFunction( result.stop ) ) { jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = - jQuery.proxy( result.stop, result ); + result.stop.bind( result ); } return result; } @@ -6785,10 +7633,17 @@ function Animation( elem, properties, options ) { jQuery.map( props, createTween, animation ); - if ( jQuery.isFunction( animation.opts.start ) ) { + if ( isFunction( animation.opts.start ) ) { animation.opts.start.call( elem, animation ); } + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + jQuery.fx.timer( jQuery.extend( tick, { elem: elem, @@ -6797,14 +7652,11 @@ function Animation( elem, properties, options ) { } ) ); - // attach callbacks from options - return animation.progress( animation.opts.progress ) - .done( animation.opts.done, animation.opts.complete ) - .fail( animation.opts.fail ) - .always( animation.opts.always ); + return animation; } jQuery.Animation = jQuery.extend( Animation, { + tweeners: { "*": [ function( prop, value ) { var tween = this.createTween( prop, value ); @@ -6814,18 +7666,18 @@ jQuery.Animation = jQuery.extend( Animation, { }, tweener: function( props, callback ) { - if ( jQuery.isFunction( props ) ) { + if ( isFunction( props ) ) { callback = props; props = [ "*" ]; } else { - props = props.match( rnotwhite ); + props = props.match( rnothtmlwhite ); } var prop, index = 0, length = props.length; - for ( ; index < length ; index++ ) { + for ( ; index < length; index++ ) { prop = props[ index ]; Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; Animation.tweeners[ prop ].unshift( callback ); @@ -6846,14 +7698,25 @@ jQuery.Animation = jQuery.extend( Animation, { jQuery.speed = function( speed, easing, fn ) { var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { complete: fn || !fn && easing || - jQuery.isFunction( speed ) && speed, + isFunction( speed ) && speed, duration: speed, - easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing + easing: fn && easing || easing && !isFunction( easing ) && easing }; - opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? - opt.duration : opt.duration in jQuery.fx.speeds ? - jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default; + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } // Normalize opt.queue - true/undefined/null -> "fx" if ( opt.queue == null || opt.queue === true ) { @@ -6864,7 +7727,7 @@ jQuery.speed = function( speed, easing, fn ) { opt.old = opt.complete; opt.complete = function() { - if ( jQuery.isFunction( opt.old ) ) { + if ( isFunction( opt.old ) ) { opt.old.call( this ); } @@ -6880,7 +7743,7 @@ jQuery.fn.extend( { fadeTo: function( speed, to, easing, callback ) { // Show any hidden elements after setting opacity to 0 - return this.filter( isHidden ).css( "opacity", 0 ).show() + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() // Animate to the value specified .end().animate( { opacity: to }, speed, easing, callback ); @@ -6916,7 +7779,7 @@ jQuery.fn.extend( { clearQueue = type; type = undefined; } - if ( clearQueue && type !== false ) { + if ( clearQueue ) { this.queue( type || "fx", [] ); } @@ -6999,7 +7862,7 @@ jQuery.fn.extend( { } } ); -jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) { +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { var cssFn = jQuery.fn[ name ]; jQuery.fn[ name ] = function( speed, easing, callback ) { return speed == null || typeof speed === "boolean" ? @@ -7028,12 +7891,12 @@ jQuery.fx.tick = function() { i = 0, timers = jQuery.timers; - fxNow = jQuery.now(); + fxNow = Date.now(); for ( ; i < timers.length; i++ ) { timer = timers[ i ]; - // Checks the timer has not already been removed + // Run the timer and safely remove it when done (allowing for external removal) if ( !timer() && timers[ i ] === timer ) { timers.splice( i--, 1 ); } @@ -7047,24 +7910,21 @@ jQuery.fx.tick = function() { jQuery.fx.timer = function( timer ) { jQuery.timers.push( timer ); - if ( timer() ) { - jQuery.fx.start(); - } else { - jQuery.timers.pop(); - } + jQuery.fx.start(); }; jQuery.fx.interval = 13; jQuery.fx.start = function() { - if ( !timerId ) { - timerId = window.setInterval( jQuery.fx.tick, jQuery.fx.interval ); + if ( inProgress ) { + return; } + + inProgress = true; + schedule(); }; jQuery.fx.stop = function() { - window.clearInterval( timerId ); - - timerId = null; + inProgress = null; }; jQuery.fx.speeds = { @@ -7077,7 +7937,7 @@ jQuery.fx.speeds = { // Based off of the plugin by Clint Helfers, with permission. -// http://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ jQuery.fn.delay = function( time, type ) { time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; type = type || "fx"; @@ -7098,20 +7958,15 @@ jQuery.fn.delay = function( time, type ) { input.type = "checkbox"; - // Support: iOS<=5.1, Android<=4.2+ + // Support: Android <=4.3 only // Default value for a checkbox should be "on" support.checkOn = input.value !== ""; - // Support: IE<=11+ + // Support: IE <=11 only // Must access selectedIndex to make default options select support.optSelected = opt.selected; - // Support: Android<=2.3 - // Options inside disabled selects are incorrectly marked as disabled - select.disabled = true; - support.optDisabled = !opt.disabled; - - // Support: IE<=11+ + // Support: IE <=11 only // An input loses its value after becoming a radio input = document.createElement( "input" ); input.value = "t"; @@ -7150,11 +8005,10 @@ jQuery.extend( { return jQuery.prop( elem, name, value ); } - // All attributes are lowercase + // Attribute hooks are determined by the lowercase version // Grab necessary hook if one is defined if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - name = name.toLowerCase(); - hooks = jQuery.attrHooks[ name ] || + hooks = jQuery.attrHooks[ name.toLowerCase() ] || ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); } @@ -7187,7 +8041,7 @@ jQuery.extend( { type: { set: function( elem, value ) { if ( !support.radioValue && value === "radio" && - jQuery.nodeName( elem, "input" ) ) { + nodeName( elem, "input" ) ) { var val = elem.value; elem.setAttribute( "type", value ); if ( val ) { @@ -7200,21 +8054,15 @@ jQuery.extend( { }, removeAttr: function( elem, value ) { - var name, propName, + var name, i = 0, - attrNames = value && value.match( rnotwhite ); + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); if ( attrNames && elem.nodeType === 1 ) { while ( ( name = attrNames[ i++ ] ) ) { - propName = jQuery.propFix[ name ] || name; - - // Boolean attributes get special treatment (#10870) - if ( jQuery.expr.match.bool.test( name ) ) { - - // Set corresponding property to false - elem[ propName ] = false; - } - elem.removeAttribute( name ); } } @@ -7234,20 +8082,23 @@ boolHook = { return name; } }; -jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { var getter = attrHandle[ name ] || jQuery.find.attr; attrHandle[ name ] = function( elem, name, isXML ) { - var ret, handle; + var ret, handle, + lowercaseName = name.toLowerCase(); + if ( !isXML ) { // Avoid an infinite loop by temporarily removing this function from the getter - handle = attrHandle[ name ]; - attrHandle[ name ] = ret; + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; ret = getter( elem, name, isXML ) != null ? - name.toLowerCase() : + lowercaseName : null; - attrHandle[ name ] = handle; + attrHandle[ lowercaseName ] = handle; } return ret; }; @@ -7308,18 +8159,26 @@ jQuery.extend( { tabIndex: { get: function( elem ) { + // Support: IE <=9 - 11 only // elem.tabIndex doesn't always return the // correct value when it hasn't been explicitly set - // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ // Use proper attribute retrieval(#12072) var tabindex = jQuery.find.attr( elem, "tabindex" ); - return tabindex ? - parseInt( tabindex, 10 ) : + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( rfocusable.test( elem.nodeName ) || - rclickable.test( elem.nodeName ) && elem.href ? - 0 : - -1; + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; } } }, @@ -7336,9 +8195,14 @@ jQuery.extend( { // on the option // The getter ensures a default option is selected // when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop if ( !support.optSelected ) { jQuery.propHooks.selected = { get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + var parent = elem.parentNode; if ( parent && parent.parentNode ) { parent.parentNode.selectedIndex; @@ -7346,6 +8210,9 @@ if ( !support.optSelected ) { return null; }, set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + var parent = elem.parentNode; if ( parent ) { parent.selectedIndex; @@ -7376,30 +8243,45 @@ jQuery.each( [ -var rclass = /[\t\r\n\f]/g; + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + function getClass( elem ) { return elem.getAttribute && elem.getAttribute( "class" ) || ""; } +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + jQuery.fn.extend( { addClass: function( value ) { var classes, elem, cur, curValue, clazz, j, finalValue, i = 0; - if ( jQuery.isFunction( value ) ) { + if ( isFunction( value ) ) { return this.each( function( j ) { jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); } ); } - if ( typeof value === "string" && value ) { - classes = value.match( rnotwhite ) || []; + classes = classesToArray( value ); + if ( classes.length ) { while ( ( elem = this[ i++ ] ) ) { curValue = getClass( elem ); - cur = elem.nodeType === 1 && - ( " " + curValue + " " ).replace( rclass, " " ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); if ( cur ) { j = 0; @@ -7410,7 +8292,7 @@ jQuery.fn.extend( { } // Only assign if different to avoid unneeded rendering. - finalValue = jQuery.trim( cur ); + finalValue = stripAndCollapse( cur ); if ( curValue !== finalValue ) { elem.setAttribute( "class", finalValue ); } @@ -7425,7 +8307,7 @@ jQuery.fn.extend( { var classes, elem, cur, curValue, clazz, j, finalValue, i = 0; - if ( jQuery.isFunction( value ) ) { + if ( isFunction( value ) ) { return this.each( function( j ) { jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); } ); @@ -7435,15 +8317,14 @@ jQuery.fn.extend( { return this.attr( "class", "" ); } - if ( typeof value === "string" && value ) { - classes = value.match( rnotwhite ) || []; + classes = classesToArray( value ); + if ( classes.length ) { while ( ( elem = this[ i++ ] ) ) { curValue = getClass( elem ); // This expression is here for better compressibility (see addClass) - cur = elem.nodeType === 1 && - ( " " + curValue + " " ).replace( rclass, " " ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); if ( cur ) { j = 0; @@ -7456,7 +8337,7 @@ jQuery.fn.extend( { } // Only assign if different to avoid unneeded rendering. - finalValue = jQuery.trim( cur ); + finalValue = stripAndCollapse( cur ); if ( curValue !== finalValue ) { elem.setAttribute( "class", finalValue ); } @@ -7468,13 +8349,14 @@ jQuery.fn.extend( { }, toggleClass: function( value, stateVal ) { - var type = typeof value; + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); - if ( typeof stateVal === "boolean" && type === "string" ) { + if ( typeof stateVal === "boolean" && isValidValue ) { return stateVal ? this.addClass( value ) : this.removeClass( value ); } - if ( jQuery.isFunction( value ) ) { + if ( isFunction( value ) ) { return this.each( function( i ) { jQuery( this ).toggleClass( value.call( this, i, getClass( this ), stateVal ), @@ -7486,12 +8368,12 @@ jQuery.fn.extend( { return this.each( function() { var className, i, self, classNames; - if ( type === "string" ) { + if ( isValidValue ) { // Toggle individual class names i = 0; self = jQuery( this ); - classNames = value.match( rnotwhite ) || []; + classNames = classesToArray( value ); while ( ( className = classNames[ i++ ] ) ) { @@ -7534,10 +8416,8 @@ jQuery.fn.extend( { className = " " + selector + " "; while ( ( elem = this[ i++ ] ) ) { if ( elem.nodeType === 1 && - ( " " + getClass( elem ) + " " ).replace( rclass, " " ) - .indexOf( className ) > -1 - ) { - return true; + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; } } @@ -7548,12 +8428,11 @@ jQuery.fn.extend( { -var rreturn = /\r/g, - rspaces = /[\x20\t\r\n\f]+/g; +var rreturn = /\r/g; jQuery.fn.extend( { val: function( value ) { - var hooks, ret, isFunction, + var hooks, ret, valueIsFunction, elem = this[ 0 ]; if ( !arguments.length ) { @@ -7570,19 +8449,19 @@ jQuery.fn.extend( { ret = elem.value; - return typeof ret === "string" ? - - // Handle most common string cases - ret.replace( rreturn, "" ) : + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } - // Handle cases where value is null/undef or number - ret == null ? "" : ret; + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; } return; } - isFunction = jQuery.isFunction( value ); + valueIsFunction = isFunction( value ); return this.each( function( i ) { var val; @@ -7591,7 +8470,7 @@ jQuery.fn.extend( { return; } - if ( isFunction ) { + if ( valueIsFunction ) { val = value.call( this, i, jQuery( this ).val() ); } else { val = value; @@ -7604,7 +8483,7 @@ jQuery.fn.extend( { } else if ( typeof val === "number" ) { val += ""; - } else if ( jQuery.isArray( val ) ) { + } else if ( Array.isArray( val ) ) { val = jQuery.map( val, function( value ) { return value == null ? "" : value + ""; } ); @@ -7629,37 +8508,41 @@ jQuery.extend( { return val != null ? val : - // Support: IE10-11+ + // Support: IE <=10 - 11 only // option.text throws exceptions (#14686, #14858) // Strip and collapse whitespace // https://html.spec.whatwg.org/#strip-and-collapse-whitespace - jQuery.trim( jQuery.text( elem ) ).replace( rspaces, " " ); + stripAndCollapse( jQuery.text( elem ) ); } }, select: { get: function( elem ) { - var value, option, + var value, option, i, options = elem.options, index = elem.selectedIndex, - one = elem.type === "select-one" || index < 0, + one = elem.type === "select-one", values = one ? null : [], - max = one ? index + 1 : options.length, - i = index < 0 ? - max : - one ? index : 0; + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } // Loop through all the selected options for ( ; i < max; i++ ) { option = options[ i ]; + // Support: IE <=9 only // IE8-9 doesn't update selected after form reset (#2551) if ( ( option.selected || i === index ) && // Don't return options that are disabled or in a disabled optgroup - ( support.optDisabled ? - !option.disabled : option.getAttribute( "disabled" ) === null ) && + !option.disabled && ( !option.parentNode.disabled || - !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) { + !nodeName( option.parentNode, "optgroup" ) ) ) { // Get the specific value for the option value = jQuery( option ).val(); @@ -7685,11 +8568,16 @@ jQuery.extend( { while ( i-- ) { option = options[ i ]; + + /* eslint-disable no-cond-assign */ + if ( option.selected = jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 ) { optionSet = true; } + + /* eslint-enable no-cond-assign */ } // Force browsers to behave consistently when non-matching value is set @@ -7706,7 +8594,7 @@ jQuery.extend( { jQuery.each( [ "radio", "checkbox" ], function() { jQuery.valHooks[ this ] = { set: function( elem, value ) { - if ( jQuery.isArray( value ) ) { + if ( Array.isArray( value ) ) { return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); } } @@ -7724,18 +8612,24 @@ jQuery.each( [ "radio", "checkbox" ], function() { // Return jQuery for attributes-only inclusion -var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/; +support.focusin = "onfocusin" in window; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; jQuery.extend( jQuery.event, { trigger: function( event, data, elem, onlyHandlers ) { - var i, cur, tmp, bubbleType, ontype, handle, special, + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, eventPath = [ elem || document ], type = hasOwn.call( event, "type" ) ? event.type : event, namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; - cur = tmp = elem = elem || document; + cur = lastElement = tmp = elem = elem || document; // Don't do events on text and comment nodes if ( elem.nodeType === 3 || elem.nodeType === 8 ) { @@ -7787,7 +8681,7 @@ jQuery.extend( jQuery.event, { // Determine event propagation path in advance, per W3C events spec (#9951) // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { bubbleType = special.delegateType || type; if ( !rfocusMorph.test( bubbleType + type ) ) { @@ -7807,13 +8701,15 @@ jQuery.extend( jQuery.event, { // Fire handlers on the event path i = 0; while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { - + lastElement = cur; event.type = i > 1 ? bubbleType : special.bindType || type; // jQuery handler - handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && + handle = ( + dataPriv.get( cur, "events" ) || Object.create( null ) + )[ event.type ] && dataPriv.get( cur, "handle" ); if ( handle ) { handle.apply( cur, data ); @@ -7837,9 +8733,9 @@ jQuery.extend( jQuery.event, { special._default.apply( eventPath.pop(), data ) === false ) && acceptData( elem ) ) { - // Call a native DOM method on the target with the same name name as the event. + // Call a native DOM method on the target with the same name as the event. // Don't do default actions on window, that's where global variables be (#6170) - if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) { + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { // Don't re-trigger an onFOO event when we call its FOO() method tmp = elem[ ontype ]; @@ -7850,7 +8746,17 @@ jQuery.extend( jQuery.event, { // Prevent re-triggering of the same event, since we already bubbled it above jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + jQuery.event.triggered = undefined; if ( tmp ) { @@ -7864,6 +8770,7 @@ jQuery.extend( jQuery.event, { }, // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events simulate: function( type, elem, event ) { var e = jQuery.extend( new jQuery.Event(), @@ -7871,27 +8778,10 @@ jQuery.extend( jQuery.event, { { type: type, isSimulated: true - - // Previously, `originalEvent: {}` was set here, so stopPropagation call - // would not be triggered on donor event, since in our own - // jQuery.event.stopPropagation function we had a check for existence of - // originalEvent.stopPropagation method, so, consequently it would be a noop. - // - // But now, this "simulate" function is used only for events - // for which stopPropagation() is noop, so there is no need for that anymore. - // - // For the 1.x branch though, guard for "click" and "submit" - // events is still used, but was moved to jQuery.event.stopPropagation function - // because `originalEvent` should point to the original event for the constancy - // with other events and for more focused logic } ); jQuery.event.trigger( e, null, elem ); - - if ( e.isDefaultPrevented() ) { - event.preventDefault(); - } } } ); @@ -7912,39 +8802,14 @@ jQuery.fn.extend( { } ); -jQuery.each( ( "blur focus focusin focusout load resize scroll unload click dblclick " + - "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + - "change select submit keydown keypress keyup error contextmenu" ).split( " " ), - function( i, name ) { - - // Handle event binding - jQuery.fn[ name ] = function( data, fn ) { - return arguments.length > 0 ? - this.on( name, null, data, fn ) : - this.trigger( name ); - }; -} ); - -jQuery.fn.extend( { - hover: function( fnOver, fnOut ) { - return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); - } -} ); - - - - -support.focusin = "onfocusin" in window; - - -// Support: Firefox +// Support: Firefox <=44 // Firefox doesn't have focus(in | out) events // Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 // -// Support: Chrome, Safari +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 // focus(in | out) events fire after focus & blur events, // which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order -// Related ticket - https://code.google.com/p/chromium/issues/detail?id=449857 +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 if ( !support.focusin ) { jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { @@ -7955,7 +8820,10 @@ if ( !support.focusin ) { jQuery.event.special[ fix ] = { setup: function() { - var doc = this.ownerDocument || this, + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, attaches = dataPriv.access( doc, fix ); if ( !attaches ) { @@ -7964,7 +8832,7 @@ if ( !support.focusin ) { dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); }, teardown: function() { - var doc = this.ownerDocument || this, + var doc = this.ownerDocument || this.document || this, attaches = dataPriv.access( doc, fix ) - 1; if ( !attaches ) { @@ -7980,19 +8848,12 @@ if ( !support.focusin ) { } var location = window.location; -var nonce = jQuery.now(); +var nonce = { guid: Date.now() }; var rquery = ( /\?/ ); -// Support: Android 2.3 -// Workaround failure to string-cast null input -jQuery.parseJSON = function( data ) { - return JSON.parse( data + "" ); -}; - - // Cross-browser xml parsing jQuery.parseXML = function( data ) { var xml; @@ -8000,23 +8861,148 @@ jQuery.parseXML = function( data ) { return null; } - // Support: IE9 + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. try { xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); } catch ( e ) { xml = undefined; } - if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { - jQuery.error( "Invalid XML: " + data ); + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( _i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); } - return xml; -}; +} ); var + r20 = /%20/g, rhash = /#.*$/, - rts = /([?&])_=[^&]*/, + rantiCache = /([?&])_=[^&]*/, rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, // #7653, #8125, #8152: local protocol detection @@ -8062,9 +9048,9 @@ function addToPrefiltersOrTransports( structure ) { var dataType, i = 0, - dataTypes = dataTypeExpression.toLowerCase().match( rnotwhite ) || []; + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; - if ( jQuery.isFunction( func ) ) { + if ( isFunction( func ) ) { // For each dataType in the dataTypeExpression while ( ( dataType = dataTypes[ i++ ] ) ) { @@ -8224,7 +9210,7 @@ function ajaxConvert( s, response, jqXHR, isSuccess ) { if ( current ) { - // There's only work to do if current dataType is non-auto + // There's only work to do if current dataType is non-auto if ( current === "*" ) { current = prev; @@ -8304,6 +9290,7 @@ jQuery.extend( { processData: true, async: true, contentType: "application/x-www-form-urlencoded; charset=UTF-8", + /* timeout: 0, data: null, @@ -8347,7 +9334,7 @@ jQuery.extend( { "text html": true, // Evaluate text as a json expression - "text json": jQuery.parseJSON, + "text json": JSON.parse, // Parse text as xml "text xml": jQuery.parseXML @@ -8406,12 +9393,18 @@ jQuery.extend( { // Url cleanup var urlAnchor, + // Request state (becomes false upon send and true upon completion) + completed, + // To know if global events are to be dispatched fireGlobals, // Loop variable i, + // uncached part of the url + uncached, + // Create the final options object s = jQuery.ajaxSetup( {}, options ), @@ -8435,9 +9428,6 @@ jQuery.extend( { requestHeaders = {}, requestHeadersNames = {}, - // The jqXHR state - state = 0, - // Default abort message strAbort = "canceled", @@ -8448,28 +9438,30 @@ jQuery.extend( { // Builds headers hashtable if needed getResponseHeader: function( key ) { var match; - if ( state === 2 ) { + if ( completed ) { if ( !responseHeaders ) { responseHeaders = {}; while ( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); } } - match = responseHeaders[ key.toLowerCase() ]; + match = responseHeaders[ key.toLowerCase() + " " ]; } - return match == null ? null : match; + return match == null ? null : match.join( ", " ); }, // Raw string getAllResponseHeaders: function() { - return state === 2 ? responseHeadersString : null; + return completed ? responseHeadersString : null; }, // Caches the header setRequestHeader: function( name, value ) { - var lname = name.toLowerCase(); - if ( !state ) { - name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name; + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; requestHeaders[ name ] = value; } return this; @@ -8477,7 +9469,7 @@ jQuery.extend( { // Overrides response content-type header overrideMimeType: function( type ) { - if ( !state ) { + if ( completed == null ) { s.mimeType = type; } return this; @@ -8487,16 +9479,16 @@ jQuery.extend( { statusCode: function( map ) { var code; if ( map ) { - if ( state < 2 ) { - for ( code in map ) { - - // Lazy-add the new callback in a way that preserves old ones - statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; - } - } else { + if ( completed ) { // Execute the appropriate callbacks jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } } } return this; @@ -8514,33 +9506,31 @@ jQuery.extend( { }; // Attach deferreds - deferred.promise( jqXHR ).complete = completeDeferred.add; - jqXHR.success = jqXHR.done; - jqXHR.error = jqXHR.fail; + deferred.promise( jqXHR ); - // Remove hash character (#7531: and string promotion) // Add protocol if not provided (prefilters might expect it) // Handle falsy url in the settings object (#10093: consistency with old signature) // We also use the url parameter if available - s.url = ( ( url || s.url || location.href ) + "" ).replace( rhash, "" ) + s.url = ( ( url || s.url || location.href ) + "" ) .replace( rprotocol, location.protocol + "//" ); // Alias method option to type as per ticket #12004 s.type = options.method || options.type || s.method || s.type; // Extract dataTypes list - s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( rnotwhite ) || [ "" ]; + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; // A cross-domain request is in order when the origin doesn't match the current origin. if ( s.crossDomain == null ) { urlAnchor = document.createElement( "a" ); - // Support: IE8-11+ - // IE throws exception if url is malformed, e.g. http://example.com:80x/ + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ try { urlAnchor.href = s.url; - // Support: IE8-11+ + // Support: IE <=8 - 11 only // Anchor's host property isn't correctly set when s.url is relative urlAnchor.href = urlAnchor.href; s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== @@ -8562,7 +9552,7 @@ jQuery.extend( { inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); // If request was aborted inside a prefilter, stop there - if ( state === 2 ) { + if ( completed ) { return jqXHR; } @@ -8583,29 +9573,37 @@ jQuery.extend( { // Save the URL in case we're toying with the If-Modified-Since // and/or If-None-Match header later on - cacheURL = s.url; + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); // More options handling for requests with no content if ( !s.hasContent ) { - // If data is available, append data to url - if ( s.data ) { - cacheURL = ( s.url += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data ); + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; // #9682: remove data so that it's not used in an eventual retry delete s.data; } - // Add anti-cache in url if needed + // Add or update anti-cache param if needed if ( s.cache === false ) { - s.url = rts.test( cacheURL ) ? + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } - // If there is already a '_' parameter, set its value - cacheURL.replace( rts, "$1_=" + nonce++ ) : + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; - // Otherwise add one to the end - cacheURL + ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + nonce++; - } + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); } // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. @@ -8639,7 +9637,7 @@ jQuery.extend( { // Allow custom headers/mimetypes and early abort if ( s.beforeSend && - ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) { + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { // Abort if not done already and return return jqXHR.abort(); @@ -8649,9 +9647,9 @@ jQuery.extend( { strAbort = "abort"; // Install callbacks on deferreds - for ( i in { success: 1, error: 1, complete: 1 } ) { - jqXHR[ i ]( s[ i ] ); - } + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); // Get transport transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); @@ -8668,7 +9666,7 @@ jQuery.extend( { } // If request was aborted inside ajaxSend, stop there - if ( state === 2 ) { + if ( completed ) { return jqXHR; } @@ -8680,18 +9678,17 @@ jQuery.extend( { } try { - state = 1; + completed = false; transport.send( requestHeaders, done ); } catch ( e ) { - // Propagate exception as error if not done - if ( state < 2 ) { - done( -1, e ); - - // Simply rethrow otherwise - } else { + // Rethrow post-completion exceptions + if ( completed ) { throw e; } + + // Propagate others as results + done( -1, e ); } } @@ -8700,13 +9697,12 @@ jQuery.extend( { var isSuccess, success, error, response, modified, statusText = nativeStatusText; - // Called once - if ( state === 2 ) { + // Ignore repeat invocations + if ( completed ) { return; } - // State is "done" now - state = 2; + completed = true; // Clear timeout if it exists if ( timeoutTimer ) { @@ -8731,6 +9727,11 @@ jQuery.extend( { response = ajaxHandleResponses( s, jqXHR, responses ); } + // Use a noop converter for missing script + if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { + s.converters[ "text script" ] = function() {}; + } + // Convert no matter what (that way responseXXX fields are always set) response = ajaxConvert( s, response, jqXHR, isSuccess ); @@ -8821,11 +9822,11 @@ jQuery.extend( { } } ); -jQuery.each( [ "get", "post" ], function( i, method ) { +jQuery.each( [ "get", "post" ], function( _i, method ) { jQuery[ method ] = function( url, data, callback, type ) { // Shift arguments if data argument was omitted - if ( jQuery.isFunction( data ) ) { + if ( isFunction( data ) ) { type = type || callback; callback = data; data = undefined; @@ -8842,17 +9843,36 @@ jQuery.each( [ "get", "post" ], function( i, method ) { }; } ); +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + -jQuery._evalUrl = function( url ) { +jQuery._evalUrl = function( url, options, doc ) { return jQuery.ajax( { url: url, // Make this explicit, since user can override this through ajaxSetup (#11264) type: "GET", dataType: "script", + cache: true, async: false, global: false, - "throws": true + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } } ); }; @@ -8861,13 +9881,10 @@ jQuery.fn.extend( { wrapAll: function( html ) { var wrap; - if ( jQuery.isFunction( html ) ) { - return this.each( function( i ) { - jQuery( this ).wrapAll( html.call( this, i ) ); - } ); - } - if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } // The elements to wrap the target around wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); @@ -8891,7 +9908,7 @@ jQuery.fn.extend( { }, wrapInner: function( html ) { - if ( jQuery.isFunction( html ) ) { + if ( isFunction( html ) ) { return this.each( function( i ) { jQuery( this ).wrapInner( html.call( this, i ) ); } ); @@ -8911,152 +9928,30 @@ jQuery.fn.extend( { }, wrap: function( html ) { - var isFunction = jQuery.isFunction( html ); + var htmlIsFunction = isFunction( html ); return this.each( function( i ) { - jQuery( this ).wrapAll( isFunction ? html.call( this, i ) : html ); + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); } ); }, - unwrap: function() { - return this.parent().each( function() { - if ( !jQuery.nodeName( this, "body" ) ) { - jQuery( this ).replaceWith( this.childNodes ); - } - } ).end(); + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; } } ); -jQuery.expr.filters.hidden = function( elem ) { - return !jQuery.expr.filters.visible( elem ); -}; -jQuery.expr.filters.visible = function( elem ) { - - // Support: Opera <= 12.12 - // Opera reports offsetWidths and offsetHeights less than zero on some elements - // Use OR instead of AND as the element is not visible if either is true - // See tickets #10406 and #13132 - return elem.offsetWidth > 0 || elem.offsetHeight > 0 || elem.getClientRects().length > 0; +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); }; - - - - -var r20 = /%20/g, - rbracket = /\[\]$/, - rCRLF = /\r?\n/g, - rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, - rsubmittable = /^(?:input|select|textarea|keygen)/i; - -function buildParams( prefix, obj, traditional, add ) { - var name; - - if ( jQuery.isArray( obj ) ) { - - // Serialize array item. - jQuery.each( obj, function( i, v ) { - if ( traditional || rbracket.test( prefix ) ) { - - // Treat each array item as a scalar. - add( prefix, v ); - - } else { - - // Item is non-scalar (array or object), encode its numeric index. - buildParams( - prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", - v, - traditional, - add - ); - } - } ); - - } else if ( !traditional && jQuery.type( obj ) === "object" ) { - - // Serialize object item. - for ( name in obj ) { - buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); - } - - } else { - - // Serialize scalar item. - add( prefix, obj ); - } -} - -// Serialize an array of form elements or a set of -// key/values into a query string -jQuery.param = function( a, traditional ) { - var prefix, - s = [], - add = function( key, value ) { - - // If value is a function, invoke it and return its value - value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value ); - s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value ); - }; - - // Set traditional to true for jQuery <= 1.3.2 behavior. - if ( traditional === undefined ) { - traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional; - } - - // If an array was passed in, assume that it is an array of form elements. - if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { - - // Serialize the form elements - jQuery.each( a, function() { - add( this.name, this.value ); - } ); - - } else { - - // If traditional, encode the "old" way (the way 1.3.2 or older - // did it), otherwise encode params recursively. - for ( prefix in a ) { - buildParams( prefix, a[ prefix ], traditional, add ); - } - } - - // Return the resulting serialization - return s.join( "&" ).replace( r20, "+" ); +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); }; -jQuery.fn.extend( { - serialize: function() { - return jQuery.param( this.serializeArray() ); - }, - serializeArray: function() { - return this.map( function() { - - // Can add propHook for "elements" to filter or add form elements - var elements = jQuery.prop( this, "elements" ); - return elements ? jQuery.makeArray( elements ) : this; - } ) - .filter( function() { - var type = this.type; - - // Use .is( ":disabled" ) so that fieldset[disabled] works - return this.name && !jQuery( this ).is( ":disabled" ) && - rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && - ( this.checked || !rcheckableType.test( type ) ); - } ) - .map( function( i, elem ) { - var val = jQuery( this ).val(); - return val == null ? - null : - jQuery.isArray( val ) ? - jQuery.map( val, function( val ) { - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ) : - { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ).get(); - } -} ); jQuery.ajaxSettings.xhr = function() { @@ -9070,7 +9965,7 @@ var xhrSuccessStatus = { // File protocol always yields status code 0, assume 200 0: 200, - // Support: IE9 + // Support: IE <=9 only // #1450: sometimes IE returns 1223 when it should be 204 1223: 204 }, @@ -9128,13 +10023,14 @@ jQuery.ajaxTransport( function( options ) { return function() { if ( callback ) { callback = errorCallback = xhr.onload = - xhr.onerror = xhr.onabort = xhr.onreadystatechange = null; + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; if ( type === "abort" ) { xhr.abort(); } else if ( type === "error" ) { - // Support: IE9 + // Support: IE <=9 only // On a manual native abort, IE9 throws // errors on any property access that is not readyState if ( typeof xhr.status !== "number" ) { @@ -9152,7 +10048,7 @@ jQuery.ajaxTransport( function( options ) { xhrSuccessStatus[ xhr.status ] || xhr.status, xhr.statusText, - // Support: IE9 only + // Support: IE <=9 only // IE9 has no XHR2 but throws on binary (trac-11426) // For XHR2 non-text, let the caller handle it (gh-2498) ( xhr.responseType || "text" ) !== "text" || @@ -9168,9 +10064,9 @@ jQuery.ajaxTransport( function( options ) { // Listen to events xhr.onload = callback(); - errorCallback = xhr.onerror = callback( "error" ); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); - // Support: IE9 + // Support: IE 9 only // Use onreadystatechange to replace onabort // to handle uncaught aborts if ( xhr.onabort !== undefined ) { @@ -9222,6 +10118,13 @@ jQuery.ajaxTransport( function( options ) { +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + // Install script dataType jQuery.ajaxSetup( { accepts: { @@ -9252,24 +10155,21 @@ jQuery.ajaxPrefilter( "script", function( s ) { // Bind script tag hack transport jQuery.ajaxTransport( "script", function( s ) { - // This transport only deals with cross domain requests - if ( s.crossDomain ) { + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { var script, callback; return { send: function( _, complete ) { - script = jQuery( "<script>" ).prop( { - charset: s.scriptCharset, - src: s.url - } ).on( - "load error", - callback = function( evt ) { + script = jQuery( "<script>" ) + .attr( s.scriptAttrs || {} ) + .prop( { charset: s.scriptCharset, src: s.url } ) + .on( "load error", callback = function( evt ) { script.remove(); callback = null; if ( evt ) { complete( evt.type === "error" ? 404 : 200, evt.type ); } - } - ); + } ); // Use native DOM manipulation to avoid our domManip AJAX trickery document.head.appendChild( script[ 0 ] ); @@ -9293,7 +10193,7 @@ var oldCallbacks = [], jQuery.ajaxSetup( { jsonp: "callback", jsonpCallback: function() { - var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) ); + var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce.guid++ ) ); this[ callback ] = true; return callback; } @@ -9315,7 +10215,7 @@ jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) { // Get callback name, remembering preexisting value associated with it - callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ? + callbackName = s.jsonpCallback = isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback; @@ -9366,7 +10266,7 @@ jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { } // Call if it was a function and we have a response - if ( responseContainer && jQuery.isFunction( overwritten ) ) { + if ( responseContainer && isFunction( overwritten ) ) { overwritten( responseContainer[ 0 ] ); } @@ -9381,22 +10281,53 @@ jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { +// Support: Safari 8 only +// In Safari 8 documents created via document.implementation.createHTMLDocument +// collapse sibling forms: the second one becomes a child of the first one. +// Because of that, this security measure has to be disabled in Safari 8. +// https://bugs.webkit.org/show_bug.cgi?id=137337 +support.createHTMLDocument = ( function() { + var body = document.implementation.createHTMLDocument( "" ).body; + body.innerHTML = "<form></form><form></form>"; + return body.childNodes.length === 2; +} )(); + + // Argument "data" should be string of html // context (optional): If specified, the fragment will be created in this context, // defaults to document // keepScripts (optional): If true, will include scripts passed in the html string jQuery.parseHTML = function( data, context, keepScripts ) { - if ( !data || typeof data !== "string" ) { - return null; + if ( typeof data !== "string" ) { + return []; } if ( typeof context === "boolean" ) { keepScripts = context; context = false; } - context = context || document; - var parsed = rsingleTag.exec( data ), - scripts = !keepScripts && []; + var base, parsed, scripts; + + if ( !context ) { + + // Stop scripts or inline event handlers from being executed immediately + // by using document.implementation + if ( support.createHTMLDocument ) { + context = document.implementation.createHTMLDocument( "" ); + + // Set the base href for the created document + // so any parsed elements with URLs + // are based on the document's URL (gh-2965) + base = context.createElement( "base" ); + base.href = document.location.href; + context.head.appendChild( base ); + } else { + context = document; + } + } + + parsed = rsingleTag.exec( data ); + scripts = !keepScripts && []; // Single tag if ( parsed ) { @@ -9413,28 +10344,21 @@ jQuery.parseHTML = function( data, context, keepScripts ) { }; -// Keep a copy of the old load method -var _load = jQuery.fn.load; - /** * Load a url into a page */ jQuery.fn.load = function( url, params, callback ) { - if ( typeof url !== "string" && _load ) { - return _load.apply( this, arguments ); - } - var selector, type, response, self = this, off = url.indexOf( " " ); if ( off > -1 ) { - selector = jQuery.trim( url.slice( off ) ); + selector = stripAndCollapse( url.slice( off ) ); url = url.slice( 0, off ); } // If it's a function - if ( jQuery.isFunction( params ) ) { + if ( isFunction( params ) ) { // We assume that it's the callback callback = params; @@ -9486,24 +10410,7 @@ jQuery.fn.load = function( url, params, callback ) { -// Attach a bunch of functions for handling common AJAX events -jQuery.each( [ - "ajaxStart", - "ajaxStop", - "ajaxComplete", - "ajaxError", - "ajaxSuccess", - "ajaxSend" -], function( i, type ) { - jQuery.fn[ type ] = function( fn ) { - return this.on( type, fn ); - }; -} ); - - - - -jQuery.expr.filters.animated = function( elem ) { +jQuery.expr.pseudos.animated = function( elem ) { return jQuery.grep( jQuery.timers, function( fn ) { return elem === fn.elem; } ).length; @@ -9512,13 +10419,6 @@ jQuery.expr.filters.animated = function( elem ) { -/** - * Gets a window from an element - */ -function getWindow( elem ) { - return jQuery.isWindow( elem ) ? elem : elem.nodeType === 9 && elem.defaultView; -} - jQuery.offset = { setOffset: function( elem, options, i ) { var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition, @@ -9549,7 +10449,7 @@ jQuery.offset = { curLeft = parseFloat( curCSSLeft ) || 0; } - if ( jQuery.isFunction( options ) ) { + if ( isFunction( options ) ) { // Use jQuery.extend here to allow modification of coordinates argument (gh-1848) options = options.call( elem, i, jQuery.extend( {}, curOffset ) ); @@ -9566,13 +10466,23 @@ jQuery.offset = { options.using.call( elem, props ); } else { + if ( typeof props.top === "number" ) { + props.top += "px"; + } + if ( typeof props.left === "number" ) { + props.left += "px"; + } curElem.css( props ); } } }; jQuery.fn.extend( { + + // offset() relates an element's border box to the document origin offset: function( options ) { + + // Preserve chaining for setter if ( arguments.length ) { return options === undefined ? this : @@ -9581,60 +10491,67 @@ jQuery.fn.extend( { } ); } - var docElem, win, - elem = this[ 0 ], - box = { top: 0, left: 0 }, - doc = elem && elem.ownerDocument; + var rect, win, + elem = this[ 0 ]; - if ( !doc ) { + if ( !elem ) { return; } - docElem = doc.documentElement; - - // Make sure it's not a disconnected DOM node - if ( !jQuery.contains( docElem, elem ) ) { - return box; + // Return zeros for disconnected and hidden (display: none) elements (gh-2310) + // Support: IE <=11 only + // Running getBoundingClientRect on a + // disconnected node in IE throws an error + if ( !elem.getClientRects().length ) { + return { top: 0, left: 0 }; } - box = elem.getBoundingClientRect(); - win = getWindow( doc ); + // Get document-relative position by adding viewport scroll to viewport-relative gBCR + rect = elem.getBoundingClientRect(); + win = elem.ownerDocument.defaultView; return { - top: box.top + win.pageYOffset - docElem.clientTop, - left: box.left + win.pageXOffset - docElem.clientLeft + top: rect.top + win.pageYOffset, + left: rect.left + win.pageXOffset }; }, + // position() relates an element's margin box to its offset parent's padding box + // This corresponds to the behavior of CSS absolute positioning position: function() { if ( !this[ 0 ] ) { return; } - var offsetParent, offset, + var offsetParent, offset, doc, elem = this[ 0 ], parentOffset = { top: 0, left: 0 }; - // Fixed elements are offset from window (parentOffset = {top:0, left: 0}, - // because it is its only offset parent + // position:fixed elements are offset from the viewport, which itself always has zero offset if ( jQuery.css( elem, "position" ) === "fixed" ) { - // Assume getBoundingClientRect is there when computed position is fixed + // Assume position:fixed implies availability of getBoundingClientRect offset = elem.getBoundingClientRect(); } else { + offset = this.offset(); - // Get *real* offsetParent - offsetParent = this.offsetParent(); + // Account for the *real* offset parent, which can be the document or its root element + // when a statically positioned element is identified + doc = elem.ownerDocument; + offsetParent = elem.offsetParent || doc.documentElement; + while ( offsetParent && + ( offsetParent === doc.body || offsetParent === doc.documentElement ) && + jQuery.css( offsetParent, "position" ) === "static" ) { - // Get correct offsets - offset = this.offset(); - if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) { - parentOffset = offsetParent.offset(); + offsetParent = offsetParent.parentNode; } + if ( offsetParent && offsetParent !== elem && offsetParent.nodeType === 1 ) { - // Add offsetParent borders - parentOffset.top += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true ); - parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true ); + // Incorporate borders into its offset, since they are outside its content origin + parentOffset = jQuery( offsetParent ).offset(); + parentOffset.top += jQuery.css( offsetParent, "borderTopWidth", true ); + parentOffset.left += jQuery.css( offsetParent, "borderLeftWidth", true ); + } } // Subtract parent offsets and element margins @@ -9673,7 +10590,14 @@ jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( jQuery.fn[ method ] = function( val ) { return access( this, function( elem, method, val ) { - var win = getWindow( elem ); + + // Coalesce documents and windows + var win; + if ( isWindow( elem ) ) { + win = elem; + } else if ( elem.nodeType === 9 ) { + win = elem.defaultView; + } if ( val === undefined ) { return win ? win[ prop ] : elem[ method ]; @@ -9692,13 +10616,13 @@ jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( }; } ); -// Support: Safari<7-8+, Chrome<37-44+ +// Support: Safari <=7 - 9.1, Chrome <=37 - 49 // Add the top/left cssHooks using jQuery.fn.position // Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084 -// Blink bug: https://code.google.com/p/chromium/issues/detail?id=229280 +// Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347 // getComputedStyle returns percent when specified for top/left/bottom/right; // rather than make the css module depend on the offset module, just check for it here -jQuery.each( [ "top", "left" ], function( i, prop ) { +jQuery.each( [ "top", "left" ], function( _i, prop ) { jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition, function( elem, computed ) { if ( computed ) { @@ -9727,12 +10651,12 @@ jQuery.each( { Height: "height", Width: "width" }, function( name, type ) { return access( this, function( elem, type, value ) { var doc; - if ( jQuery.isWindow( elem ) ) { + if ( isWindow( elem ) ) { - // As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there - // isn't a whole lot we can do. See pull request at this URL for discussion: - // https://github.com/jquery/jquery/pull/764 - return elem.document.documentElement[ "client" + name ]; + // $( window ).outerWidth/Height return w/h including scrollbars (gh-1729) + return funcName.indexOf( "outer" ) === 0 ? + elem[ "inner" + name ] : + elem.document.documentElement[ "client" + name ]; } // Get document width or height @@ -9755,12 +10679,28 @@ jQuery.each( { Height: "height", Width: "width" }, function( name, type ) { // Set width or height on the element jQuery.style( elem, type, value, extra ); - }, type, chainable ? margin : undefined, chainable, null ); + }, type, chainable ? margin : undefined, chainable ); }; } ); } ); +jQuery.each( [ + "ajaxStart", + "ajaxStop", + "ajaxComplete", + "ajaxError", + "ajaxSuccess", + "ajaxSend" +], function( _i, type ) { + jQuery.fn[ type ] = function( fn ) { + return this.on( type, fn ); + }; +} ); + + + + jQuery.fn.extend( { bind: function( types, data, fn ) { @@ -9780,13 +10720,99 @@ jQuery.fn.extend( { this.off( selector, "**" ) : this.off( types, selector || "**", fn ); }, - size: function() { - return this.length; + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); } } ); -jQuery.fn.andSelf = jQuery.fn.addBack; +jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup contextmenu" ).split( " " ), + function( _i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + return arguments.length > 0 ? + this.on( name, null, data, fn ) : + this.trigger( name ); + }; + } ); + + + + +// Support: Android <=4.0 only +// Make sure we trim BOM and NBSP +var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; + +// Bind a function to a context, optionally partially applying any +// arguments. +// jQuery.proxy is deprecated to promote standards (specifically Function#bind) +// However, it is not slated for removal any time soon +jQuery.proxy = function( fn, context ) { + var tmp, args, proxy; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; +}; + +jQuery.holdReady = function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } +}; +jQuery.isArray = Array.isArray; +jQuery.parseJSON = JSON.parse; +jQuery.nodeName = nodeName; +jQuery.isFunction = isFunction; +jQuery.isWindow = isWindow; +jQuery.camelCase = camelCase; +jQuery.type = toType; + +jQuery.now = Date.now; + +jQuery.isNumeric = function( obj ) { + + // As of jQuery 3.0, isNumeric is limited to + // strings and numbers (primitives or objects) + // that can be coerced to finite numbers (gh-2662) + var type = jQuery.type( obj ); + return ( type === "number" || type === "string" ) && + + // parseFloat NaNs numeric-cast false positives ("") + // ...but misinterprets leading-number strings, particularly hex literals ("0x...") + // subtraction forces infinities to NaN + !isNaN( obj - parseFloat( obj ) ); +}; +jQuery.trim = function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); +}; @@ -9811,6 +10837,7 @@ if ( typeof define === "function" && define.amd ) { + var // Map over jQuery in case of overwrite @@ -9834,9 +10861,12 @@ jQuery.noConflict = function( deep ) { // Expose jQuery and $ identifiers, even in AMD // (#7102#comment:10, https://github.com/jquery/jquery/pull/557) // and CommonJS for browser emulators (#13566) -if ( !noGlobal ) { +if ( typeof noGlobal === "undefined" ) { window.jQuery = window.$ = jQuery; } + + + return jQuery; -})); +} ); diff --git a/django/contrib/admin/static/admin/js/vendor/jquery/jquery.min.js b/django/contrib/admin/static/admin/js/vendor/jquery/jquery.min.js index b8c4187de18d..b0614034ad3a 100644 --- a/django/contrib/admin/static/admin/js/vendor/jquery/jquery.min.js +++ b/django/contrib/admin/static/admin/js/vendor/jquery/jquery.min.js @@ -1,4 +1,2 @@ -/*! jQuery v2.2.3 | (c) jQuery Foundation | jquery.org/license */ -!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="2.2.3",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isPlainObject:function(a){var b;if("object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype||{},"isPrototypeOf"))return!1;for(b in a);return void 0===b||k.call(a,b)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=d.createElement("script"),b.text=a,d.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:h.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(d=e.call(arguments,2),f=function(){return a.apply(b||this,d.concat(e.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="<a id='"+u+"'></a><select id='"+u+"-\r\\' msallowcapture=''><option selected=''></option></select>",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=la(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=ma(b);function pa(){}pa.prototype=d.filters=d.pseudos,d.setFilters=new pa,g=fa.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){c&&!(e=R.exec(h))||(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=S.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(Q," ")}),h=h.slice(c.length));for(g in d.filter)!(e=W[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?fa.error(a):z(a,i).slice(0)};function qa(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return h.call(b,a)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&f.parentNode&&(this.length=1,this[0]=f),this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?void 0!==c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?h.call(n(a),this[0]):h.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||n.uniqueSort(e),D.test(a)&&e.reverse()),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h<f.length)f[h].apply(c[0],c[1])===!1&&a.stopOnFalse&&(h=f.length,c=!1)}a.memory||(c=!1),b=!1,e&&(f=c?[]:"")},j={add:function(){return f&&(c&&!b&&(h=f.length-1,g.push(c)),function d(b){n.each(b,function(b,c){n.isFunction(c)?a.unique&&j.has(c)||f.push(c):c&&c.length&&"string"!==n.type(c)&&d(c)})}(arguments),c&&!b&&i()),this},remove:function(){return n.each(arguments,function(a,b){var c;while((c=n.inArray(b,f,c))>-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.removeEventListener("DOMContentLoaded",J),a.removeEventListener("load",J),n.ready()}n.ready.promise=function(b){return I||(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(n.ready):(d.addEventListener("DOMContentLoaded",J),a.addEventListener("load",J))),I.promise(b)},n.ready.promise();var K=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)K(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},L=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function M(){this.expando=n.expando+M.uid++}M.uid=1,M.prototype={register:function(a,b){var c=b||{};return a.nodeType?a[this.expando]=c:Object.defineProperty(a,this.expando,{value:c,writable:!0,configurable:!0}),a[this.expando]},cache:function(a){if(!L(a))return{};var b=a[this.expando];return b||(b={},L(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[b]=c;else for(d in b)e[d]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=a[this.expando];if(void 0!==f){if(void 0===b)this.register(a);else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in f?d=[b,e]:(d=e,d=d in f?[d]:d.match(G)||[])),c=d.length;while(c--)delete f[d[c]]}(void 0===b||n.isEmptyObject(f))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!n.isEmptyObject(b)}};var N=new M,O=new M,P=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Q=/[A-Z]/g;function R(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Q,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:P.test(c)?n.parseJSON(c):c; -}catch(e){}O.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return O.hasData(a)||N.hasData(a)},data:function(a,b,c){return O.access(a,b,c)},removeData:function(a,b){O.remove(a,b)},_data:function(a,b,c){return N.access(a,b,c)},_removeData:function(a,b){N.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=O.get(f),1===f.nodeType&&!N.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),R(f,d,e[d])));N.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){O.set(this,a)}):K(this,function(b){var c,d;if(f&&void 0===b){if(c=O.get(f,a)||O.get(f,a.replace(Q,"-$&").toLowerCase()),void 0!==c)return c;if(d=n.camelCase(a),c=O.get(f,d),void 0!==c)return c;if(c=R(f,d,void 0),void 0!==c)return c}else d=n.camelCase(a),this.each(function(){var c=O.get(this,d);O.set(this,d,b),a.indexOf("-")>-1&&void 0!==c&&O.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){O.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=N.get(a,b),c&&(!d||n.isArray(c)?d=N.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return N.get(a,c)||N.access(a,c,{empty:n.Callbacks("once memory").add(function(){N.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?n.queue(this[0],a):void 0===b?this:this.each(function(){var c=n.queue(this,a,b);n._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&n.dequeue(this,a)})},dequeue:function(a){return this.each(function(){n.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=n.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=N.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var S=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=new RegExp("^(?:([+-])=|)("+S+")([a-z%]*)$","i"),U=["Top","Right","Bottom","Left"],V=function(a,b){return a=b||a,"none"===n.css(a,"display")||!n.contains(a.ownerDocument,a)};function W(a,b,c,d){var e,f=1,g=20,h=d?function(){return d.cur()}:function(){return n.css(a,b,"")},i=h(),j=c&&c[3]||(n.cssNumber[b]?"":"px"),k=(n.cssNumber[b]||"px"!==j&&+i)&&T.exec(n.css(a,b));if(k&&k[3]!==j){j=j||k[3],c=c||[],k=+i||1;do f=f||".5",k/=f,n.style(a,b,k+j);while(f!==(f=h()/i)&&1!==f&&--g)}return c&&(k=+k||+i||0,e=c[1]?k+(c[1]+1)*c[2]:+c[2],d&&(d.unit=j,d.start=k,d.end=e)),e}var X=/^(?:checkbox|radio)$/i,Y=/<([\w:-]+)/,Z=/^$|\/(?:java|ecma)script/i,$={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};$.optgroup=$.option,$.tbody=$.tfoot=$.colgroup=$.caption=$.thead,$.th=$.td;function _(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function aa(a,b){for(var c=0,d=a.length;d>c;c++)N.set(a[c],"globalEval",!b||N.get(b[c],"globalEval"))}var ba=/<|&#?\w+;/;function ca(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],o=0,p=a.length;p>o;o++)if(f=a[o],f||0===f)if("object"===n.type(f))n.merge(m,f.nodeType?[f]:f);else if(ba.test(f)){g=g||l.appendChild(b.createElement("div")),h=(Y.exec(f)||["",""])[1].toLowerCase(),i=$[h]||$._default,g.innerHTML=i[1]+n.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;n.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",o=0;while(f=m[o++])if(d&&n.inArray(f,d)>-1)e&&e.push(f);else if(j=n.contains(f.ownerDocument,f),g=_(l.appendChild(f),"script"),j&&aa(g),c){k=0;while(f=g[k++])Z.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var da=/^key/,ea=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,fa=/^([^.]*)(?:\.(.+)|)/;function ga(){return!0}function ha(){return!1}function ia(){try{return d.activeElement}catch(a){}}function ja(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ja(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=ha;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return"undefined"!=typeof n&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(G)||[""],j=b.length;while(j--)h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.hasData(a)&&N.get(a);if(r&&(i=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&N.remove(a,"handle events")}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(N.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())a.rnamespace&&!a.rnamespace.test(g.namespace)||(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!==this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},props:"altKey bubbles cancelable ctrlKey currentTarget detail eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,e,f,g=b.button;return null==a.pageX&&null!=b.clientX&&(c=a.target.ownerDocument||d,e=c.documentElement,f=c.body,a.pageX=b.clientX+(e&&e.scrollLeft||f&&f.scrollLeft||0)-(e&&e.clientLeft||f&&f.clientLeft||0),a.pageY=b.clientY+(e&&e.scrollTop||f&&f.scrollTop||0)-(e&&e.clientTop||f&&f.clientTop||0)),a.which||void 0===g||(a.which=1&g?1:2&g?3:4&g?2:0),a}},fix:function(a){if(a[n.expando])return a;var b,c,e,f=a.type,g=a,h=this.fixHooks[f];h||(this.fixHooks[f]=h=ea.test(f)?this.mouseHooks:da.test(f)?this.keyHooks:{}),e=h.props?this.props.concat(h.props):this.props,a=new n.Event(g),b=e.length;while(b--)c=e[b],a[c]=g[c];return a.target||(a.target=d),3===a.target.nodeType&&(a.target=a.target.parentNode),h.filter?h.filter(a,g):a},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==ia()&&this.focus?(this.focus(),!1):void 0},delegateType:"focusin"},blur:{trigger:function(){return this===ia()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&n.nodeName(this,"input")?(this.click(),!1):void 0},_default:function(a){return n.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}}},n.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c)},n.Event=function(a,b){return this instanceof n.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?ga:ha):this.type=a,b&&n.extend(this,b),this.timeStamp=a&&a.timeStamp||n.now(),void(this[n.expando]=!0)):new n.Event(a,b)},n.Event.prototype={constructor:n.Event,isDefaultPrevented:ha,isPropagationStopped:ha,isImmediatePropagationStopped:ha,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=ga,a&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=ga,a&&a.stopPropagation()},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=ga,a&&a.stopImmediatePropagation(),this.stopPropagation()}},n.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){n.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return e&&(e===d||n.contains(d,e))||(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),n.fn.extend({on:function(a,b,c,d){return ja(this,a,b,c,d)},one:function(a,b,c,d){return ja(this,a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,n(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return b!==!1&&"function"!=typeof b||(c=b,b=void 0),c===!1&&(c=ha),this.each(function(){n.event.remove(this,a,c,b)})}});var ka=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,la=/<script|<style|<link/i,ma=/checked\s*(?:[^=]|=\s*.checked.)/i,na=/^true\/(.*)/,oa=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;function pa(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function qa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function ra(a){var b=na.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function sa(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(N.hasData(a)&&(f=N.access(a),g=N.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}O.hasData(a)&&(h=O.access(a),i=n.extend({},h),O.set(b,i))}}function ta(a,b){var c=b.nodeName.toLowerCase();"input"===c&&X.test(a.type)?b.checked=a.checked:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}function ua(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&ma.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),ua(f,b,c,d)});if(o&&(e=ca(b,a[0].ownerDocument,!1,a,d),g=e.firstChild,1===e.childNodes.length&&(e=g),g||d)){for(h=n.map(_(e,"script"),qa),i=h.length;o>m;m++)j=e,m!==p&&(j=n.clone(j,!0,!0),i&&n.merge(h,_(j,"script"))),c.call(a[m],j,m);if(i)for(k=h[h.length-1].ownerDocument,n.map(h,ra),m=0;i>m;m++)j=h[m],Z.test(j.type||"")&&!N.access(j,"globalEval")&&n.contains(k,j)&&(j.src?n._evalUrl&&n._evalUrl(j.src):n.globalEval(j.textContent.replace(oa,"")))}return a}function va(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(_(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&aa(_(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(ka,"<$1></$2>")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=_(h),f=_(a),d=0,e=f.length;e>d;d++)ta(f[d],g[d]);if(b)if(c)for(f=f||_(a),g=g||_(h),d=0,e=f.length;e>d;d++)sa(f[d],g[d]);else sa(a,h);return g=_(h,"script"),g.length>0&&aa(g,!i&&_(a,"script")),h},cleanData:function(a){for(var b,c,d,e=n.event.special,f=0;void 0!==(c=a[f]);f++)if(L(c)){if(b=c[N.expando]){if(b.events)for(d in b.events)e[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);c[N.expando]=void 0}c[O.expando]&&(c[O.expando]=void 0)}}}),n.fn.extend({domManip:ua,detach:function(a){return va(this,a,!0)},remove:function(a){return va(this,a)},text:function(a){return K(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.appendChild(a)}})},prepend:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(_(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return K(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!la.test(a)&&!$[(Y.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(_(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return ua(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(_(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),f=e.length-1,h=0;f>=h;h++)c=h===f?this:this.clone(!0),n(e[h])[b](c),g.apply(d,c.get());return this.pushStack(d)}});var wa,xa={HTML:"block",BODY:"block"};function ya(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function za(a){var b=d,c=xa[a];return c||(c=ya(a,b),"none"!==c&&c||(wa=(wa||n("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=wa[0].contentDocument,b.write(),b.close(),c=ya(a,b),wa.detach()),xa[a]=c),c}var Aa=/^margin/,Ba=new RegExp("^("+S+")(?!px)[a-z%]+$","i"),Ca=function(b){var c=b.ownerDocument.defaultView;return c&&c.opener||(c=a),c.getComputedStyle(b)},Da=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e},Ea=d.documentElement;!function(){var b,c,e,f,g=d.createElement("div"),h=d.createElement("div");if(h.style){h.style.backgroundClip="content-box",h.cloneNode(!0).style.backgroundClip="",l.clearCloneStyle="content-box"===h.style.backgroundClip,g.style.cssText="border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute",g.appendChild(h);function i(){h.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;position:relative;display:block;margin:auto;border:1px;padding:1px;top:1%;width:50%",h.innerHTML="",Ea.appendChild(g);var d=a.getComputedStyle(h);b="1%"!==d.top,f="2px"===d.marginLeft,c="4px"===d.width,h.style.marginRight="50%",e="4px"===d.marginRight,Ea.removeChild(g)}n.extend(l,{pixelPosition:function(){return i(),b},boxSizingReliable:function(){return null==c&&i(),c},pixelMarginRight:function(){return null==c&&i(),e},reliableMarginLeft:function(){return null==c&&i(),f},reliableMarginRight:function(){var b,c=h.appendChild(d.createElement("div"));return c.style.cssText=h.style.cssText="-webkit-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",c.style.marginRight=c.style.width="0",h.style.width="1px",Ea.appendChild(g),b=!parseFloat(a.getComputedStyle(c).marginRight),Ea.removeChild(g),h.removeChild(c),b}})}}();function Fa(a,b,c){var d,e,f,g,h=a.style;return c=c||Ca(a),g=c?c.getPropertyValue(b)||c[b]:void 0,""!==g&&void 0!==g||n.contains(a.ownerDocument,a)||(g=n.style(a,b)),c&&!l.pixelMarginRight()&&Ba.test(g)&&Aa.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f),void 0!==g?g+"":g}function Ga(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}var Ha=/^(none|table(?!-c[ea]).+)/,Ia={position:"absolute",visibility:"hidden",display:"block"},Ja={letterSpacing:"0",fontWeight:"400"},Ka=["Webkit","O","Moz","ms"],La=d.createElement("div").style;function Ma(a){if(a in La)return a;var b=a[0].toUpperCase()+a.slice(1),c=Ka.length;while(c--)if(a=Ka[c]+b,a in La)return a}function Na(a,b,c){var d=T.exec(b);return d?Math.max(0,d[2]-(c||0))+(d[3]||"px"):b}function Oa(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=n.css(a,c+U[f],!0,e)),d?("content"===c&&(g-=n.css(a,"padding"+U[f],!0,e)),"margin"!==c&&(g-=n.css(a,"border"+U[f]+"Width",!0,e))):(g+=n.css(a,"padding"+U[f],!0,e),"padding"!==c&&(g+=n.css(a,"border"+U[f]+"Width",!0,e)));return g}function Pa(b,c,e){var f=!0,g="width"===c?b.offsetWidth:b.offsetHeight,h=Ca(b),i="border-box"===n.css(b,"boxSizing",!1,h);if(d.msFullscreenElement&&a.top!==a&&b.getClientRects().length&&(g=Math.round(100*b.getBoundingClientRect()[c])),0>=g||null==g){if(g=Fa(b,c,h),(0>g||null==g)&&(g=b.style[c]),Ba.test(g))return g;f=i&&(l.boxSizingReliable()||g===b.style[c]),g=parseFloat(g)||0}return g+Oa(b,c,e||(i?"border":"content"),f,h)+"px"}function Qa(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=N.get(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&V(d)&&(f[g]=N.access(d,"olddisplay",za(d.nodeName)))):(e=V(d),"none"===c&&e||N.set(d,"olddisplay",e?c:n.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}n.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Fa(a,"opacity");return""===c?"1":c}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=n.camelCase(b),i=a.style;return b=n.cssProps[h]||(n.cssProps[h]=Ma(h)||h),g=n.cssHooks[b]||n.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b]:(f=typeof c,"string"===f&&(e=T.exec(c))&&e[1]&&(c=W(a,b,e),f="number"),null!=c&&c===c&&("number"===f&&(c+=e&&e[3]||(n.cssNumber[h]?"":"px")),l.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=n.camelCase(b);return b=n.cssProps[h]||(n.cssProps[h]=Ma(h)||h),g=n.cssHooks[b]||n.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=Fa(a,b,d)),"normal"===e&&b in Ja&&(e=Ja[b]),""===c||c?(f=parseFloat(e),c===!0||isFinite(f)?f||0:e):e}}),n.each(["height","width"],function(a,b){n.cssHooks[b]={get:function(a,c,d){return c?Ha.test(n.css(a,"display"))&&0===a.offsetWidth?Da(a,Ia,function(){return Pa(a,b,d)}):Pa(a,b,d):void 0},set:function(a,c,d){var e,f=d&&Ca(a),g=d&&Oa(a,b,d,"border-box"===n.css(a,"boxSizing",!1,f),f);return g&&(e=T.exec(c))&&"px"!==(e[3]||"px")&&(a.style[b]=c,c=n.css(a,b)),Na(a,c,g)}}}),n.cssHooks.marginLeft=Ga(l.reliableMarginLeft,function(a,b){return b?(parseFloat(Fa(a,"marginLeft"))||a.getBoundingClientRect().left-Da(a,{marginLeft:0},function(){return a.getBoundingClientRect().left}))+"px":void 0}),n.cssHooks.marginRight=Ga(l.reliableMarginRight,function(a,b){return b?Da(a,{display:"inline-block"},Fa,[a,"marginRight"]):void 0}),n.each({margin:"",padding:"",border:"Width"},function(a,b){n.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+U[d]+b]=f[d]||f[d-2]||f[0];return e}},Aa.test(a)||(n.cssHooks[a+b].set=Na)}),n.fn.extend({css:function(a,b){return K(this,function(a,b,c){var d,e,f={},g=0;if(n.isArray(b)){for(d=Ca(a),e=b.length;e>g;g++)f[b[g]]=n.css(a,b[g],!1,d);return f}return void 0!==c?n.style(a,b,c):n.css(a,b)},a,b,arguments.length>1)},show:function(){return Qa(this,!0)},hide:function(){return Qa(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){V(this)?n(this).show():n(this).hide()})}});function Ra(a,b,c,d,e){return new Ra.prototype.init(a,b,c,d,e)}n.Tween=Ra,Ra.prototype={constructor:Ra,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||n.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(n.cssNumber[c]?"":"px")},cur:function(){var a=Ra.propHooks[this.prop];return a&&a.get?a.get(this):Ra.propHooks._default.get(this)},run:function(a){var b,c=Ra.propHooks[this.prop];return this.options.duration?this.pos=b=n.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Ra.propHooks._default.set(this),this}},Ra.prototype.init.prototype=Ra.prototype,Ra.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=n.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){n.fx.step[a.prop]?n.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[n.cssProps[a.prop]]&&!n.cssHooks[a.prop]?a.elem[a.prop]=a.now:n.style(a.elem,a.prop,a.now+a.unit)}}},Ra.propHooks.scrollTop=Ra.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},n.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},n.fx=Ra.prototype.init,n.fx.step={};var Sa,Ta,Ua=/^(?:toggle|show|hide)$/,Va=/queueHooks$/;function Wa(){return a.setTimeout(function(){Sa=void 0}),Sa=n.now()}function Xa(a,b){var c,d=0,e={height:a};for(b=b?1:0;4>d;d+=2-b)c=U[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function Ya(a,b,c){for(var d,e=(_a.tweeners[b]||[]).concat(_a.tweeners["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function Za(a,b,c){var d,e,f,g,h,i,j,k,l=this,m={},o=a.style,p=a.nodeType&&V(a),q=N.get(a,"fxshow");c.queue||(h=n._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,l.always(function(){l.always(function(){h.unqueued--,n.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[o.overflow,o.overflowX,o.overflowY],j=n.css(a,"display"),k="none"===j?N.get(a,"olddisplay")||za(a.nodeName):j,"inline"===k&&"none"===n.css(a,"float")&&(o.display="inline-block")),c.overflow&&(o.overflow="hidden",l.always(function(){o.overflow=c.overflow[0],o.overflowX=c.overflow[1],o.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],Ua.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(p?"hide":"show")){if("show"!==e||!q||void 0===q[d])continue;p=!0}m[d]=q&&q[d]||n.style(a,d)}else j=void 0;if(n.isEmptyObject(m))"inline"===("none"===j?za(a.nodeName):j)&&(o.display=j);else{q?"hidden"in q&&(p=q.hidden):q=N.access(a,"fxshow",{}),f&&(q.hidden=!p),p?n(a).show():l.done(function(){n(a).hide()}),l.done(function(){var b;N.remove(a,"fxshow");for(b in m)n.style(a,b,m[b])});for(d in m)g=Ya(p?q[d]:0,d,l),d in q||(q[d]=g.start,p&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function $a(a,b){var c,d,e,f,g;for(c in a)if(d=n.camelCase(c),e=b[d],f=a[c],n.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=n.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function _a(a,b,c){var d,e,f=0,g=_a.prefilters.length,h=n.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=Sa||Wa(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:n.extend({},b),opts:n.extend(!0,{specialEasing:{},easing:n.easing._default},c),originalProperties:b,originalOptions:c,startTime:Sa||Wa(),duration:c.duration,tweens:[],createTween:function(b,c){var d=n.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?(h.notifyWith(a,[j,1,0]),h.resolveWith(a,[j,b])):h.rejectWith(a,[j,b]),this}}),k=j.props;for($a(k,j.opts.specialEasing);g>f;f++)if(d=_a.prefilters[f].call(j,a,k,j.opts))return n.isFunction(d.stop)&&(n._queueHooks(j.elem,j.opts.queue).stop=n.proxy(d.stop,d)),d;return n.map(k,Ya,j),n.isFunction(j.opts.start)&&j.opts.start.call(a,j),n.fx.timer(n.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}n.Animation=n.extend(_a,{tweeners:{"*":[function(a,b){var c=this.createTween(a,b);return W(c.elem,a,T.exec(b),c),c}]},tweener:function(a,b){n.isFunction(a)?(b=a,a=["*"]):a=a.match(G);for(var c,d=0,e=a.length;e>d;d++)c=a[d],_a.tweeners[c]=_a.tweeners[c]||[],_a.tweeners[c].unshift(b)},prefilters:[Za],prefilter:function(a,b){b?_a.prefilters.unshift(a):_a.prefilters.push(a)}}),n.speed=function(a,b,c){var d=a&&"object"==typeof a?n.extend({},a):{complete:c||!c&&b||n.isFunction(a)&&a,duration:a,easing:c&&b||b&&!n.isFunction(b)&&b};return d.duration=n.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in n.fx.speeds?n.fx.speeds[d.duration]:n.fx.speeds._default,null!=d.queue&&d.queue!==!0||(d.queue="fx"),d.old=d.complete,d.complete=function(){n.isFunction(d.old)&&d.old.call(this),d.queue&&n.dequeue(this,d.queue)},d},n.fn.extend({fadeTo:function(a,b,c,d){return this.filter(V).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=n.isEmptyObject(a),f=n.speed(b,c,d),g=function(){var b=_a(this,n.extend({},a),f);(e||N.get(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=n.timers,g=N.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&Va.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));!b&&c||n.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=N.get(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=n.timers,g=d?d.length:0;for(c.finish=!0,n.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),n.each(["toggle","show","hide"],function(a,b){var c=n.fn[b];n.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(Xa(b,!0),a,d,e)}}),n.each({slideDown:Xa("show"),slideUp:Xa("hide"),slideToggle:Xa("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){n.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),n.timers=[],n.fx.tick=function(){var a,b=0,c=n.timers;for(Sa=n.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||n.fx.stop(),Sa=void 0},n.fx.timer=function(a){n.timers.push(a),a()?n.fx.start():n.timers.pop()},n.fx.interval=13,n.fx.start=function(){Ta||(Ta=a.setInterval(n.fx.tick,n.fx.interval))},n.fx.stop=function(){a.clearInterval(Ta),Ta=null},n.fx.speeds={slow:600,fast:200,_default:400},n.fn.delay=function(b,c){return b=n.fx?n.fx.speeds[b]||b:b,c=c||"fx",this.queue(c,function(c,d){var e=a.setTimeout(c,b);d.stop=function(){a.clearTimeout(e)}})},function(){var a=d.createElement("input"),b=d.createElement("select"),c=b.appendChild(d.createElement("option"));a.type="checkbox",l.checkOn=""!==a.value,l.optSelected=c.selected,b.disabled=!0,l.optDisabled=!c.disabled,a=d.createElement("input"),a.value="t",a.type="radio",l.radioValue="t"===a.value}();var ab,bb=n.expr.attrHandle;n.fn.extend({attr:function(a,b){return K(this,n.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){n.removeAttr(this,a)})}}),n.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?n.prop(a,b,c):(1===f&&n.isXMLDoc(a)||(b=b.toLowerCase(),e=n.attrHooks[b]||(n.expr.match.bool.test(b)?ab:void 0)),void 0!==c?null===c?void n.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=n.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!l.radioValue&&"radio"===b&&n.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(G);if(f&&1===a.nodeType)while(c=f[e++])d=n.propFix[c]||c,n.expr.match.bool.test(c)&&(a[d]=!1),a.removeAttribute(c)}}),ab={set:function(a,b,c){return b===!1?n.removeAttr(a,c):a.setAttribute(c,c),c}},n.each(n.expr.match.bool.source.match(/\w+/g),function(a,b){var c=bb[b]||n.find.attr;bb[b]=function(a,b,d){var e,f;return d||(f=bb[b],bb[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,bb[b]=f),e}});var cb=/^(?:input|select|textarea|button)$/i,db=/^(?:a|area)$/i;n.fn.extend({prop:function(a,b){return K(this,n.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[n.propFix[a]||a]})}}),n.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&n.isXMLDoc(a)||(b=n.propFix[b]||b, -e=n.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=n.find.attr(a,"tabindex");return b?parseInt(b,10):cb.test(a.nodeName)||db.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),l.optSelected||(n.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),n.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){n.propFix[this.toLowerCase()]=this});var eb=/[\t\r\n\f]/g;function fb(a){return a.getAttribute&&a.getAttribute("class")||""}n.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(n.isFunction(a))return this.each(function(b){n(this).addClass(a.call(this,b,fb(this)))});if("string"==typeof a&&a){b=a.match(G)||[];while(c=this[i++])if(e=fb(c),d=1===c.nodeType&&(" "+e+" ").replace(eb," ")){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=n.trim(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(n.isFunction(a))return this.each(function(b){n(this).removeClass(a.call(this,b,fb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(G)||[];while(c=this[i++])if(e=fb(c),d=1===c.nodeType&&(" "+e+" ").replace(eb," ")){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=n.trim(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):n.isFunction(a)?this.each(function(c){n(this).toggleClass(a.call(this,c,fb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=n(this),f=a.match(G)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=fb(this),b&&N.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":N.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+fb(c)+" ").replace(eb," ").indexOf(b)>-1)return!0;return!1}});var gb=/\r/g,hb=/[\x20\t\r\n\f]+/g;n.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=n.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,n(this).val()):a,null==e?e="":"number"==typeof e?e+="":n.isArray(e)&&(e=n.map(e,function(a){return null==a?"":a+""})),b=n.valHooks[this.type]||n.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=n.valHooks[e.type]||n.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(gb,""):null==c?"":c)}}}),n.extend({valHooks:{option:{get:function(a){var b=n.find.attr(a,"value");return null!=b?b:n.trim(n.text(a)).replace(hb," ")}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],(c.selected||i===e)&&(l.optDisabled?!c.disabled:null===c.getAttribute("disabled"))&&(!c.parentNode.disabled||!n.nodeName(c.parentNode,"optgroup"))){if(b=n(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=n.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=n.inArray(n.valHooks.option.get(d),f)>-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),n.each(["radio","checkbox"],function(){n.valHooks[this]={set:function(a,b){return n.isArray(b)?a.checked=n.inArray(n(a).val(),b)>-1:void 0}},l.checkOn||(n.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var ib=/^(?:focusinfocus|focusoutblur)$/;n.extend(n.event,{trigger:function(b,c,e,f){var g,h,i,j,l,m,o,p=[e||d],q=k.call(b,"type")?b.type:b,r=k.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!ib.test(q+n.event.triggered)&&(q.indexOf(".")>-1&&(r=q.split("."),q=r.shift(),r.sort()),l=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=r.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},f||!o.trigger||o.trigger.apply(e,c)!==!1)){if(!f&&!o.noBubble&&!n.isWindow(e)){for(j=o.delegateType||q,ib.test(j+q)||(h=h.parentNode);h;h=h.parentNode)p.push(h),i=h;i===(e.ownerDocument||d)&&p.push(i.defaultView||i.parentWindow||a)}g=0;while((h=p[g++])&&!b.isPropagationStopped())b.type=g>1?j:o.bindType||q,m=(N.get(h,"events")||{})[b.type]&&N.get(h,"handle"),m&&m.apply(h,c),m=l&&h[l],m&&m.apply&&L(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=q,f||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!L(e)||l&&n.isFunction(e[q])&&!n.isWindow(e)&&(i=e[l],i&&(e[l]=null),n.event.triggered=q,e[q](),n.event.triggered=void 0,i&&(e[l]=i)),b.result}},simulate:function(a,b,c){var d=n.extend(new n.Event,c,{type:a,isSimulated:!0});n.event.trigger(d,null,b),d.isDefaultPrevented()&&c.preventDefault()}}),n.fn.extend({trigger:function(a,b){return this.each(function(){n.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?n.event.trigger(a,b,c,!0):void 0}}),n.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){n.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),n.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),l.focusin="onfocusin"in a,l.focusin||n.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){n.event.simulate(b,a.target,n.event.fix(a))};n.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=N.access(d,b);e||d.addEventListener(a,c,!0),N.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=N.access(d,b)-1;e?N.access(d,b,e):(d.removeEventListener(a,c,!0),N.remove(d,b))}}});var jb=a.location,kb=n.now(),lb=/\?/;n.parseJSON=function(a){return JSON.parse(a+"")},n.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||n.error("Invalid XML: "+b),c};var mb=/#.*$/,nb=/([?&])_=[^&]*/,ob=/^(.*?):[ \t]*([^\r\n]*)$/gm,pb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,qb=/^(?:GET|HEAD)$/,rb=/^\/\//,sb={},tb={},ub="*/".concat("*"),vb=d.createElement("a");vb.href=jb.href;function wb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(G)||[];if(n.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function xb(a,b,c,d){var e={},f=a===tb;function g(h){var i;return e[h]=!0,n.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function yb(a,b){var c,d,e=n.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&n.extend(!0,a,d),a}function zb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function Ab(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}n.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:jb.href,type:"GET",isLocal:pb.test(jb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":ub,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":n.parseJSON,"text xml":n.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?yb(yb(a,n.ajaxSettings),b):yb(n.ajaxSettings,a)},ajaxPrefilter:wb(sb),ajaxTransport:wb(tb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m=n.ajaxSetup({},c),o=m.context||m,p=m.context&&(o.nodeType||o.jquery)?n(o):n.event,q=n.Deferred(),r=n.Callbacks("once memory"),s=m.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,getResponseHeader:function(a){var b;if(2===v){if(!h){h={};while(b=ob.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===v?g:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return v||(a=u[c]=u[c]||a,t[a]=b),this},overrideMimeType:function(a){return v||(m.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>v)for(b in a)s[b]=[s[b],a[b]];else x.always(a[x.status]);return this},abort:function(a){var b=a||w;return e&&e.abort(b),z(0,b),this}};if(q.promise(x).complete=r.add,x.success=x.done,x.error=x.fail,m.url=((b||m.url||jb.href)+"").replace(mb,"").replace(rb,jb.protocol+"//"),m.type=c.method||c.type||m.method||m.type,m.dataTypes=n.trim(m.dataType||"*").toLowerCase().match(G)||[""],null==m.crossDomain){j=d.createElement("a");try{j.href=m.url,j.href=j.href,m.crossDomain=vb.protocol+"//"+vb.host!=j.protocol+"//"+j.host}catch(y){m.crossDomain=!0}}if(m.data&&m.processData&&"string"!=typeof m.data&&(m.data=n.param(m.data,m.traditional)),xb(sb,m,c,x),2===v)return x;k=n.event&&m.global,k&&0===n.active++&&n.event.trigger("ajaxStart"),m.type=m.type.toUpperCase(),m.hasContent=!qb.test(m.type),f=m.url,m.hasContent||(m.data&&(f=m.url+=(lb.test(f)?"&":"?")+m.data,delete m.data),m.cache===!1&&(m.url=nb.test(f)?f.replace(nb,"$1_="+kb++):f+(lb.test(f)?"&":"?")+"_="+kb++)),m.ifModified&&(n.lastModified[f]&&x.setRequestHeader("If-Modified-Since",n.lastModified[f]),n.etag[f]&&x.setRequestHeader("If-None-Match",n.etag[f])),(m.data&&m.hasContent&&m.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",m.contentType),x.setRequestHeader("Accept",m.dataTypes[0]&&m.accepts[m.dataTypes[0]]?m.accepts[m.dataTypes[0]]+("*"!==m.dataTypes[0]?", "+ub+"; q=0.01":""):m.accepts["*"]);for(l in m.headers)x.setRequestHeader(l,m.headers[l]);if(m.beforeSend&&(m.beforeSend.call(o,x,m)===!1||2===v))return x.abort();w="abort";for(l in{success:1,error:1,complete:1})x[l](m[l]);if(e=xb(tb,m,c,x)){if(x.readyState=1,k&&p.trigger("ajaxSend",[x,m]),2===v)return x;m.async&&m.timeout>0&&(i=a.setTimeout(function(){x.abort("timeout")},m.timeout));try{v=1,e.send(t,z)}catch(y){if(!(2>v))throw y;z(-1,y)}}else z(-1,"No Transport");function z(b,c,d,h){var j,l,t,u,w,y=c;2!==v&&(v=2,i&&a.clearTimeout(i),e=void 0,g=h||"",x.readyState=b>0?4:0,j=b>=200&&300>b||304===b,d&&(u=zb(m,x,d)),u=Ab(m,u,x,j),j?(m.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(n.lastModified[f]=w),w=x.getResponseHeader("etag"),w&&(n.etag[f]=w)),204===b||"HEAD"===m.type?y="nocontent":304===b?y="notmodified":(y=u.state,l=u.data,t=u.error,j=!t)):(t=y,!b&&y||(y="error",0>b&&(b=0))),x.status=b,x.statusText=(c||y)+"",j?q.resolveWith(o,[l,y,x]):q.rejectWith(o,[x,y,t]),x.statusCode(s),s=void 0,k&&p.trigger(j?"ajaxSuccess":"ajaxError",[x,m,j?l:t]),r.fireWith(o,[x,y]),k&&(p.trigger("ajaxComplete",[x,m]),--n.active||n.event.trigger("ajaxStop")))}return x},getJSON:function(a,b,c){return n.get(a,b,c,"json")},getScript:function(a,b){return n.get(a,void 0,b,"script")}}),n.each(["get","post"],function(a,b){n[b]=function(a,c,d,e){return n.isFunction(c)&&(e=e||d,d=c,c=void 0),n.ajax(n.extend({url:a,type:b,dataType:e,data:c,success:d},n.isPlainObject(a)&&a))}}),n._evalUrl=function(a){return n.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},n.fn.extend({wrapAll:function(a){var b;return n.isFunction(a)?this.each(function(b){n(this).wrapAll(a.call(this,b))}):(this[0]&&(b=n(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this)},wrapInner:function(a){return n.isFunction(a)?this.each(function(b){n(this).wrapInner(a.call(this,b))}):this.each(function(){var b=n(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=n.isFunction(a);return this.each(function(c){n(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){n.nodeName(this,"body")||n(this).replaceWith(this.childNodes)}).end()}}),n.expr.filters.hidden=function(a){return!n.expr.filters.visible(a)},n.expr.filters.visible=function(a){return a.offsetWidth>0||a.offsetHeight>0||a.getClientRects().length>0};var Bb=/%20/g,Cb=/\[\]$/,Db=/\r?\n/g,Eb=/^(?:submit|button|image|reset|file)$/i,Fb=/^(?:input|select|textarea|keygen)/i;function Gb(a,b,c,d){var e;if(n.isArray(b))n.each(b,function(b,e){c||Cb.test(a)?d(a,e):Gb(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==n.type(b))d(a,b);else for(e in b)Gb(a+"["+e+"]",b[e],c,d)}n.param=function(a,b){var c,d=[],e=function(a,b){b=n.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=n.ajaxSettings&&n.ajaxSettings.traditional),n.isArray(a)||a.jquery&&!n.isPlainObject(a))n.each(a,function(){e(this.name,this.value)});else for(c in a)Gb(c,a[c],b,e);return d.join("&").replace(Bb,"+")},n.fn.extend({serialize:function(){return n.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=n.prop(this,"elements");return a?n.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!n(this).is(":disabled")&&Fb.test(this.nodeName)&&!Eb.test(a)&&(this.checked||!X.test(a))}).map(function(a,b){var c=n(this).val();return null==c?null:n.isArray(c)?n.map(c,function(a){return{name:b.name,value:a.replace(Db,"\r\n")}}):{name:b.name,value:c.replace(Db,"\r\n")}}).get()}}),n.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Hb={0:200,1223:204},Ib=n.ajaxSettings.xhr();l.cors=!!Ib&&"withCredentials"in Ib,l.ajax=Ib=!!Ib,n.ajaxTransport(function(b){var c,d;return l.cors||Ib&&!b.crossDomain?{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Hb[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}:void 0}),n.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return n.globalEval(a),a}}}),n.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),n.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=n("<script>").prop({charset:a.scriptCharset,src:a.url}).on("load error",c=function(a){b.remove(),c=null,a&&f("error"===a.type?404:200,a.type)}),d.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Jb=[],Kb=/(=)\?(?=&|$)|\?\?/;n.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=Jb.pop()||n.expando+"_"+kb++;return this[a]=!0,a}}),n.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Kb.test(b.url)?"url":"string"==typeof b.data&&0===(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&Kb.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=n.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Kb,"$1"+e):b.jsonp!==!1&&(b.url+=(lb.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||n.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){void 0===f?n(a).removeProp(e):a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Jb.push(e)),g&&n.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),n.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||d;var e=x.exec(a),f=!c&&[];return e?[b.createElement(e[1])]:(e=ca([a],b,f),f&&f.length&&n(f).remove(),n.merge([],e.childNodes))};var Lb=n.fn.load;n.fn.load=function(a,b,c){if("string"!=typeof a&&Lb)return Lb.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>-1&&(d=n.trim(a.slice(h)),a=a.slice(0,h)),n.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&n.ajax({url:a,type:e||"GET",dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?n("<div>").append(n.parseHTML(a)).find(d):a)}).always(c&&function(a,b){g.each(function(){c.apply(this,f||[a.responseText,b,a])})}),this},n.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){n.fn[b]=function(a){return this.on(b,a)}}),n.expr.filters.animated=function(a){return n.grep(n.timers,function(b){return a===b.elem}).length};function Mb(a){return n.isWindow(a)?a:9===a.nodeType&&a.defaultView}n.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=n.css(a,"position"),l=n(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=n.css(a,"top"),i=n.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),n.isFunction(b)&&(b=b.call(a,c,n.extend({},h))),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},n.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){n.offset.setOffset(this,a,b)});var b,c,d=this[0],e={top:0,left:0},f=d&&d.ownerDocument;if(f)return b=f.documentElement,n.contains(b,d)?(e=d.getBoundingClientRect(),c=Mb(f),{top:e.top+c.pageYOffset-b.clientTop,left:e.left+c.pageXOffset-b.clientLeft}):e},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===n.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),n.nodeName(a[0],"html")||(d=a.offset()),d.top+=n.css(a[0],"borderTopWidth",!0),d.left+=n.css(a[0],"borderLeftWidth",!0)),{top:b.top-d.top-n.css(c,"marginTop",!0),left:b.left-d.left-n.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent;while(a&&"static"===n.css(a,"position"))a=a.offsetParent;return a||Ea})}}),n.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c="pageYOffset"===b;n.fn[a]=function(d){return K(this,function(a,d,e){var f=Mb(a);return void 0===e?f?f[b]:a[d]:void(f?f.scrollTo(c?f.pageXOffset:e,c?e:f.pageYOffset):a[d]=e)},a,d,arguments.length)}}),n.each(["top","left"],function(a,b){n.cssHooks[b]=Ga(l.pixelPosition,function(a,c){return c?(c=Fa(a,b),Ba.test(c)?n(a).position()[b]+"px":c):void 0})}),n.each({Height:"height",Width:"width"},function(a,b){n.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){n.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return K(this,function(b,c,d){var e;return n.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?n.css(b,c,g):n.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),n.fn.extend({bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)},size:function(){return this.length}}),n.fn.andSelf=n.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return n});var Nb=a.jQuery,Ob=a.$;return n.noConflict=function(b){return a.$===n&&(a.$=Ob),b&&a.jQuery===n&&(a.jQuery=Nb),n},b||(a.jQuery=a.$=n),n}); +/*! jQuery v3.5.1 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0<t&&t-1 in e)}S.fn=S.prototype={jquery:f,constructor:S,length:0,toArray:function(){return s.call(this)},get:function(e){return null==e?s.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=S.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return S.each(this,e)},map:function(n){return this.pushStack(S.map(this,function(e,t){return n.call(e,t,e)}))},slice:function(){return this.pushStack(s.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(S.grep(this,function(e,t){return(t+1)%2}))},odd:function(){return this.pushStack(S.grep(this,function(e,t){return t%2}))},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(0<=n&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:u,sort:t.sort,splice:t.splice},S.extend=S.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||m(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)r=e[t],"__proto__"!==t&&a!==r&&(l&&r&&(S.isPlainObject(r)||(i=Array.isArray(r)))?(n=a[t],o=i&&!Array.isArray(n)?[]:i||S.isPlainObject(n)?n:{},i=!1,a[t]=S.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},S.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==o.call(e))&&(!(t=r(e))||"function"==typeof(n=v.call(t,"constructor")&&t.constructor)&&a.call(n)===l)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e,t,n){b(e,{nonce:t&&t.nonce},n)},each:function(e,t){var n,r=0;if(p(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},makeArray:function(e,t){var n=t||[];return null!=e&&(p(Object(e))?S.merge(n,"string"==typeof e?[e]:e):u.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:i.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r=[],i=0,o=e.length,a=!n;i<o;i++)!t(e[i],i)!==a&&r.push(e[i]);return r},map:function(e,t,n){var r,i,o=0,a=[];if(p(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&a.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&a.push(i);return g(a)},guid:1,support:y}),"function"==typeof Symbol&&(S.fn[Symbol.iterator]=t[Symbol.iterator]),S.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){n["[object "+t+"]"]=t.toLowerCase()});var d=function(n){var e,d,b,o,i,h,f,g,w,u,l,T,C,a,E,v,s,c,y,S="sizzle"+1*new Date,p=n.document,k=0,r=0,m=ue(),x=ue(),A=ue(),N=ue(),D=function(e,t){return e===t&&(l=!0),0},j={}.hasOwnProperty,t=[],q=t.pop,L=t.push,H=t.push,O=t.slice,P=function(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1},R="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",I="(?:\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",W="\\["+M+"*("+I+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+I+"))|)"+M+"*\\]",F=":("+I+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+W+")*)|.*)\\)|)",B=new RegExp(M+"+","g"),$=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),_=new RegExp("^"+M+"*,"+M+"*"),z=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="<a id='"+S+"'></a><select id='"+S+"-\r\\' msallowcapture=''><option selected=''></option></select>",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0<se(t,C,null,[e]).length},se.contains=function(e,t){return(e.ownerDocument||e)!=C&&T(e),y(e,t)},se.attr=function(e,t){(e.ownerDocument||e)!=C&&T(e);var n=b.attrHandle[t.toLowerCase()],r=n&&j.call(b.attrHandle,t.toLowerCase())?n(e,t,!E):void 0;return void 0!==r?r:d.attributes||!E?e.getAttribute(t):(r=e.getAttributeNode(t))&&r.specified?r.value:null},se.escape=function(e){return(e+"").replace(re,ie)},se.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},se.uniqueSort=function(e){var t,n=[],r=0,i=0;if(l=!d.detectDuplicates,u=!d.sortStable&&e.slice(0),e.sort(D),l){while(t=e[i++])t===e[i]&&(r=n.push(i));while(r--)e.splice(n[r],1)}return u=null,e},o=se.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else while(t=e[r++])n+=o(t);return n},(b=se.selectors={cacheLength:50,createPseudo:le,match:G,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1<t.indexOf(i):"$="===r?i&&t.slice(-i.length)===i:"~="===r?-1<(" "+t.replace(B," ")+" ").indexOf(i):"|="===r&&(t===i||t.slice(0,i.length+1)===i+"-"))}},CHILD:function(h,e,t,g,v){var y="nth"!==h.slice(0,3),m="last"!==h.slice(-4),x="of-type"===e;return 1===g&&0===v?function(e){return!!e.parentNode}:function(e,t,n){var r,i,o,a,s,u,l=y!==m?"nextSibling":"previousSibling",c=e.parentNode,f=x&&e.nodeName.toLowerCase(),p=!n&&!x,d=!1;if(c){if(y){while(l){a=e;while(a=a[l])if(x?a.nodeName.toLowerCase()===f:1===a.nodeType)return!1;u=l="only"===h&&!u&&"nextSibling"}return!0}if(u=[m?c.firstChild:c.lastChild],m&&p){d=(s=(r=(i=(o=(a=c)[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===k&&r[1])&&r[2],a=s&&c.childNodes[s];while(a=++s&&a&&a[l]||(d=s=0)||u.pop())if(1===a.nodeType&&++d&&a===e){i[h]=[k,s,d];break}}else if(p&&(d=s=(r=(i=(o=(a=e)[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===k&&r[1]),!1===d)while(a=++s&&a&&a[l]||(d=s=0)||u.pop())if((x?a.nodeName.toLowerCase()===f:1===a.nodeType)&&++d&&(p&&((i=(o=a[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]=[k,d]),a===e))break;return(d-=v)===g||d%g==0&&0<=d/g}}},PSEUDO:function(e,o){var t,a=b.pseudos[e]||b.setFilters[e.toLowerCase()]||se.error("unsupported pseudo: "+e);return a[S]?a(o):1<a.length?(t=[e,e,"",o],b.setFilters.hasOwnProperty(e.toLowerCase())?le(function(e,t){var n,r=a(e,o),i=r.length;while(i--)e[n=P(e,r[i])]=!(t[n]=r[i])}):function(e){return a(e,0,t)}):a}},pseudos:{not:le(function(e){var r=[],i=[],s=f(e.replace($,"$1"));return s[S]?le(function(e,t,n,r){var i,o=s(e,null,r,[]),a=e.length;while(a--)(i=o[a])&&(e[a]=!(t[a]=i))}):function(e,t,n){return r[0]=e,s(r,null,n,i),r[0]=null,!i.pop()}}),has:le(function(t){return function(e){return 0<se(t,e).length}}),contains:le(function(t){return t=t.replace(te,ne),function(e){return-1<(e.textContent||o(e)).indexOf(t)}}),lang:le(function(n){return V.test(n||"")||se.error("unsupported lang: "+n),n=n.replace(te,ne).toLowerCase(),function(e){var t;do{if(t=E?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return(t=t.toLowerCase())===n||0===t.indexOf(n+"-")}while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var t=n.location&&n.location.hash;return t&&t.slice(1)===e.id},root:function(e){return e===a},focus:function(e){return e===C.activeElement&&(!C.hasFocus||C.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:ge(!1),disabled:ge(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!b.pseudos.empty(e)},header:function(e){return J.test(e.nodeName)},input:function(e){return Q.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:ve(function(){return[0]}),last:ve(function(e,t){return[t-1]}),eq:ve(function(e,t,n){return[n<0?n+t:n]}),even:ve(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:ve(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:ve(function(e,t,n){for(var r=n<0?n+t:t<n?t:n;0<=--r;)e.push(r);return e}),gt:ve(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}}).pseudos.nth=b.pseudos.eq,{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})b.pseudos[e]=de(e);for(e in{submit:!0,reset:!0})b.pseudos[e]=he(e);function me(){}function xe(e){for(var t=0,n=e.length,r="";t<n;t++)r+=e[t].value;return r}function be(s,e,t){var u=e.dir,l=e.next,c=l||u,f=t&&"parentNode"===c,p=r++;return e.first?function(e,t,n){while(e=e[u])if(1===e.nodeType||f)return s(e,t,n);return!1}:function(e,t,n){var r,i,o,a=[k,p];if(n){while(e=e[u])if((1===e.nodeType||f)&&s(e,t,n))return!0}else while(e=e[u])if(1===e.nodeType||f)if(i=(o=e[S]||(e[S]={}))[e.uniqueID]||(o[e.uniqueID]={}),l&&l===e.nodeName.toLowerCase())e=e[u]||e;else{if((r=i[c])&&r[0]===k&&r[1]===p)return a[2]=r[2];if((i[c]=a)[2]=s(e,t,n))return!0}return!1}}function we(i){return 1<i.length?function(e,t,n){var r=i.length;while(r--)if(!i[r](e,t,n))return!1;return!0}:i[0]}function Te(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;s<u;s++)(o=e[s])&&(n&&!n(o,r,i)||(a.push(o),l&&t.push(s)));return a}function Ce(d,h,g,v,y,e){return v&&!v[S]&&(v=Ce(v)),y&&!y[S]&&(y=Ce(y,e)),le(function(e,t,n,r){var i,o,a,s=[],u=[],l=t.length,c=e||function(e,t,n){for(var r=0,i=t.length;r<i;r++)se(e,t[r],n);return n}(h||"*",n.nodeType?[n]:n,[]),f=!d||!e&&h?c:Te(c,s,d,n,r),p=g?y||(e?d:l||v)?[]:t:f;if(g&&g(f,p,n,r),v){i=Te(p,u),v(i,[],n,r),o=i.length;while(o--)(a=i[o])&&(p[u[o]]=!(f[u[o]]=a))}if(e){if(y||d){if(y){i=[],o=p.length;while(o--)(a=p[o])&&i.push(f[o]=a);y(null,p=[],i,r)}o=p.length;while(o--)(a=p[o])&&-1<(i=y?P(e,a):s[o])&&(e[i]=!(t[i]=a))}}else p=Te(p===t?p.splice(l,p.length):p),y?y(null,t,p,r):H.apply(t,p)})}function Ee(e){for(var i,t,n,r=e.length,o=b.relative[e[0].type],a=o||b.relative[" "],s=o?1:0,u=be(function(e){return e===i},a,!0),l=be(function(e){return-1<P(i,e)},a,!0),c=[function(e,t,n){var r=!o&&(n||t!==w)||((i=t).nodeType?u(e,t,n):l(e,t,n));return i=null,r}];s<r;s++)if(t=b.relative[e[s].type])c=[be(we(c),t)];else{if((t=b.filter[e[s].type].apply(null,e[s].matches))[S]){for(n=++s;n<r;n++)if(b.relative[e[n].type])break;return Ce(1<s&&we(c),1<s&&xe(e.slice(0,s-1).concat({value:" "===e[s-2].type?"*":""})).replace($,"$1"),t,s<n&&Ee(e.slice(s,n)),n<r&&Ee(e=e.slice(n)),n<r&&xe(e))}c.push(t)}return we(c)}return me.prototype=b.filters=b.pseudos,b.setFilters=new me,h=se.tokenize=function(e,t){var n,r,i,o,a,s,u,l=x[e+" "];if(l)return t?0:l.slice(0);a=e,s=[],u=b.preFilter;while(a){for(o in n&&!(r=_.exec(a))||(r&&(a=a.slice(r[0].length)||a),s.push(i=[])),n=!1,(r=z.exec(a))&&(n=r.shift(),i.push({value:n,type:r[0].replace($," ")}),a=a.slice(n.length)),b.filter)!(r=G[o].exec(a))||u[o]&&!(r=u[o](r))||(n=r.shift(),i.push({value:n,type:o,matches:r}),a=a.slice(n.length));if(!n)break}return t?a.length:a?se.error(e):x(e,s).slice(0)},f=se.compile=function(e,t){var n,v,y,m,x,r,i=[],o=[],a=A[e+" "];if(!a){t||(t=h(e)),n=t.length;while(n--)(a=Ee(t[n]))[S]?i.push(a):o.push(a);(a=A(e,(v=o,m=0<(y=i).length,x=0<v.length,r=function(e,t,n,r,i){var o,a,s,u=0,l="0",c=e&&[],f=[],p=w,d=e||x&&b.find.TAG("*",i),h=k+=null==p?1:Math.random()||.1,g=d.length;for(i&&(w=t==C||t||i);l!==g&&null!=(o=d[l]);l++){if(x&&o){a=0,t||o.ownerDocument==C||(T(o),n=!E);while(s=v[a++])if(s(o,t||C,n)){r.push(o);break}i&&(k=h)}m&&((o=!s&&o)&&u--,e&&c.push(o))}if(u+=l,m&&l!==u){a=0;while(s=y[a++])s(c,f,t,n);if(e){if(0<u)while(l--)c[l]||f[l]||(f[l]=q.call(r));f=Te(f)}H.apply(r,f),i&&!e&&0<f.length&&1<u+y.length&&se.uniqueSort(r)}return i&&(k=h,w=p),c},m?le(r):r))).selector=e}return a},g=se.select=function(e,t,n,r){var i,o,a,s,u,l="function"==typeof e&&e,c=!r&&h(e=l.selector||e);if(n=n||[],1===c.length){if(2<(o=c[0]=c[0].slice(0)).length&&"ID"===(a=o[0]).type&&9===t.nodeType&&E&&b.relative[o[1].type]){if(!(t=(b.find.ID(a.matches[0].replace(te,ne),t)||[])[0]))return n;l&&(t=t.parentNode),e=e.slice(o.shift().value.length)}i=G.needsContext.test(e)?0:o.length;while(i--){if(a=o[i],b.relative[s=a.type])break;if((u=b.find[s])&&(r=u(a.matches[0].replace(te,ne),ee.test(o[0].type)&&ye(t.parentNode)||t))){if(o.splice(i,1),!(e=r.length&&xe(o)))return H.apply(n,r),n;break}}}return(l||f(e,c))(r,t,!E,n,!t||ee.test(e)&&ye(t.parentNode)||t),n},d.sortStable=S.split("").sort(D).join("")===S,d.detectDuplicates=!!l,T(),d.sortDetached=ce(function(e){return 1&e.compareDocumentPosition(C.createElement("fieldset"))}),ce(function(e){return e.innerHTML="<a href='#'></a>","#"===e.firstChild.getAttribute("href")})||fe("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),d.attributes&&ce(function(e){return e.innerHTML="<input/>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||fe("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ce(function(e){return null==e.getAttribute("disabled")})||fe(R,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),se}(C);S.find=d,S.expr=d.selectors,S.expr[":"]=S.expr.pseudos,S.uniqueSort=S.unique=d.uniqueSort,S.text=d.getText,S.isXMLDoc=d.isXML,S.contains=d.contains,S.escapeSelector=d.escape;var h=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&S(e).is(n))break;r.push(e)}return r},T=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},k=S.expr.match.needsContext;function A(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var N=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1<i.call(n,e)!==r}):S.filter(n,e,r)}S.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?S.find.matchesSelector(r,e)?[r]:[]:S.find.matches(e,S.grep(t,function(e){return 1===e.nodeType}))},S.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(S(e).filter(function(){for(t=0;t<r;t++)if(S.contains(i[t],this))return!0}));for(n=this.pushStack([]),t=0;t<r;t++)S.find(e,i[t],n);return 1<r?S.uniqueSort(n):n},filter:function(e){return this.pushStack(D(this,e||[],!1))},not:function(e){return this.pushStack(D(this,e||[],!0))},is:function(e){return!!D(this,"string"==typeof e&&k.test(e)?S(e):e||[],!1).length}});var j,q=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e<n;e++)if(S.contains(this,t[e]))return!0})},closest:function(e,t){var n,r=0,i=this.length,o=[],a="string"!=typeof e&&S(e);if(!k.test(e))for(;r<i;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(n.nodeType<11&&(a?-1<a.index(n):1===n.nodeType&&S.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(1<o.length?S.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?i.call(S(e),this[0]):i.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(S.uniqueSort(S.merge(this.get(),S(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),S.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return h(e,"parentNode")},parentsUntil:function(e,t,n){return h(e,"parentNode",n)},next:function(e){return O(e,"nextSibling")},prev:function(e){return O(e,"previousSibling")},nextAll:function(e){return h(e,"nextSibling")},prevAll:function(e){return h(e,"previousSibling")},nextUntil:function(e,t,n){return h(e,"nextSibling",n)},prevUntil:function(e,t,n){return h(e,"previousSibling",n)},siblings:function(e){return T((e.parentNode||{}).firstChild,e)},children:function(e){return T(e.firstChild)},contents:function(e){return null!=e.contentDocument&&r(e.contentDocument)?e.contentDocument:(A(e,"template")&&(e=e.content||e),S.merge([],e.childNodes))}},function(r,i){S.fn[r]=function(e,t){var n=S.map(this,i,e);return"Until"!==r.slice(-5)&&(t=e),t&&"string"==typeof t&&(n=S.filter(t,n)),1<this.length&&(H[r]||S.uniqueSort(n),L.test(r)&&n.reverse()),this.pushStack(n)}});var P=/[^\x20\t\r\n\f]+/g;function R(e){return e}function M(e){throw e}function I(e,t,n,r){var i;try{e&&m(i=e.promise)?i.call(e).done(t).fail(n):e&&m(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}S.Callbacks=function(r){var e,n;r="string"==typeof r?(e=r,n={},S.each(e.match(P)||[],function(e,t){n[t]=!0}),n):S.extend({},r);var i,t,o,a,s=[],u=[],l=-1,c=function(){for(a=a||r.once,o=i=!0;u.length;l=-1){t=u.shift();while(++l<s.length)!1===s[l].apply(t[0],t[1])&&r.stopOnFalse&&(l=s.length,t=!1)}r.memory||(t=!1),i=!1,a&&(s=t?[]:"")},f={add:function(){return s&&(t&&!i&&(l=s.length-1,u.push(t)),function n(e){S.each(e,function(e,t){m(t)?r.unique&&f.has(t)||s.push(t):t&&t.length&&"string"!==w(t)&&n(t)})}(arguments),t&&!i&&c()),this},remove:function(){return S.each(arguments,function(e,t){var n;while(-1<(n=S.inArray(t,s,n)))s.splice(n,1),n<=l&&l--}),this},has:function(e){return e?-1<S.inArray(e,s):0<s.length},empty:function(){return s&&(s=[]),this},disable:function(){return a=u=[],s=t="",this},disabled:function(){return!s},lock:function(){return a=u=[],t||i||(s=t=""),this},locked:function(){return!!a},fireWith:function(e,t){return a||(t=[e,(t=t||[]).slice?t.slice():t],u.push(t),i||c()),this},fire:function(){return f.fireWith(this,arguments),this},fired:function(){return!!o}};return f},S.extend({Deferred:function(e){var o=[["notify","progress",S.Callbacks("memory"),S.Callbacks("memory"),2],["resolve","done",S.Callbacks("once memory"),S.Callbacks("once memory"),0,"resolved"],["reject","fail",S.Callbacks("once memory"),S.Callbacks("once memory"),1,"rejected"]],i="pending",a={state:function(){return i},always:function(){return s.done(arguments).fail(arguments),this},"catch":function(e){return a.then(null,e)},pipe:function(){var i=arguments;return S.Deferred(function(r){S.each(o,function(e,t){var n=m(i[t[4]])&&i[t[4]];s[t[1]](function(){var e=n&&n.apply(this,arguments);e&&m(e.promise)?e.promise().progress(r.notify).done(r.resolve).fail(r.reject):r[t[0]+"With"](this,n?[e]:arguments)})}),i=null}).promise()},then:function(t,n,r){var u=0;function l(i,o,a,s){return function(){var n=this,r=arguments,e=function(){var e,t;if(!(i<u)){if((e=a.apply(n,r))===o.promise())throw new TypeError("Thenable self-resolution");t=e&&("object"==typeof e||"function"==typeof e)&&e.then,m(t)?s?t.call(e,l(u,o,R,s),l(u,o,M,s)):(u++,t.call(e,l(u,o,R,s),l(u,o,M,s),l(u,o,R,o.notifyWith))):(a!==R&&(n=void 0,r=[e]),(s||o.resolveWith)(n,r))}},t=s?e:function(){try{e()}catch(e){S.Deferred.exceptionHook&&S.Deferred.exceptionHook(e,t.stackTrace),u<=i+1&&(a!==M&&(n=void 0,r=[e]),o.rejectWith(n,r))}};i?t():(S.Deferred.getStackHook&&(t.stackTrace=S.Deferred.getStackHook()),C.setTimeout(t))}}return S.Deferred(function(e){o[0][3].add(l(0,e,m(r)?r:R,e.notifyWith)),o[1][3].add(l(0,e,m(t)?t:R)),o[2][3].add(l(0,e,m(n)?n:M))}).promise()},promise:function(e){return null!=e?S.extend(e,a):a}},s={};return S.each(o,function(e,t){var n=t[2],r=t[5];a[t[1]]=n.add,r&&n.add(function(){i=r},o[3-e][2].disable,o[3-e][3].disable,o[0][2].lock,o[0][3].lock),n.add(t[3].fire),s[t[0]]=function(){return s[t[0]+"With"](this===s?void 0:this,arguments),this},s[t[0]+"With"]=n.fireWith}),a.promise(s),e&&e.call(s,s),s},when:function(e){var n=arguments.length,t=n,r=Array(t),i=s.call(arguments),o=S.Deferred(),a=function(t){return function(e){r[t]=this,i[t]=1<arguments.length?s.call(arguments):e,--n||o.resolveWith(r,i)}};if(n<=1&&(I(e,o.done(a(t)).resolve,o.reject,!n),"pending"===o.state()||m(i[t]&&i[t].then)))return o.then();while(t--)I(i[t],a(t),o.reject);return o.promise()}});var W=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;S.Deferred.exceptionHook=function(e,t){C.console&&C.console.warn&&e&&W.test(e.name)&&C.console.warn("jQuery.Deferred exception: "+e.message,e.stack,t)},S.readyException=function(e){C.setTimeout(function(){throw e})};var F=S.Deferred();function B(){E.removeEventListener("DOMContentLoaded",B),C.removeEventListener("load",B),S.ready()}S.fn.ready=function(e){return F.then(e)["catch"](function(e){S.readyException(e)}),this},S.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--S.readyWait:S.isReady)||(S.isReady=!0)!==e&&0<--S.readyWait||F.resolveWith(E,[S])}}),S.ready.then=F.then,"complete"===E.readyState||"loading"!==E.readyState&&!E.documentElement.doScroll?C.setTimeout(S.ready):(E.addEventListener("DOMContentLoaded",B),C.addEventListener("load",B));var $=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===w(n))for(s in i=!0,n)$(e,t,s,n[s],!0,o,a);else if(void 0!==r&&(i=!0,m(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(S(e),n)})),t))for(;s<u;s++)t(e[s],n,a?r:r.call(e[s],s,t(e[s],n)));return i?e:l?t.call(e):u?t(e[0],n):o},_=/^-ms-/,z=/-([a-z])/g;function U(e,t){return t.toUpperCase()}function X(e){return e.replace(_,"ms-").replace(z,U)}var V=function(e){return 1===e.nodeType||9===e.nodeType||!+e.nodeType};function G(){this.expando=S.expando+G.uid++}G.uid=1,G.prototype={cache:function(e){var t=e[this.expando];return t||(t={},V(e)&&(e.nodeType?e[this.expando]=t:Object.defineProperty(e,this.expando,{value:t,configurable:!0}))),t},set:function(e,t,n){var r,i=this.cache(e);if("string"==typeof t)i[X(t)]=n;else for(r in t)i[X(r)]=t[r];return i},get:function(e,t){return void 0===t?this.cache(e):e[this.expando]&&e[this.expando][X(t)]},access:function(e,t,n){return void 0===t||t&&"string"==typeof t&&void 0===n?this.get(e,t):(this.set(e,t,n),void 0!==n?n:t)},remove:function(e,t){var n,r=e[this.expando];if(void 0!==r){if(void 0!==t){n=(t=Array.isArray(t)?t.map(X):(t=X(t))in r?[t]:t.match(P)||[]).length;while(n--)delete r[t[n]]}(void 0===t||S.isEmptyObject(r))&&(e.nodeType?e[this.expando]=void 0:delete e[this.expando])}},hasData:function(e){var t=e[this.expando];return void 0!==t&&!S.isEmptyObject(t)}};var Y=new G,Q=new G,J=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,K=/[A-Z]/g;function Z(e,t,n){var r,i;if(void 0===n&&1===e.nodeType)if(r="data-"+t.replace(K,"-$&").toLowerCase(),"string"==typeof(n=e.getAttribute(r))){try{n="true"===(i=n)||"false"!==i&&("null"===i?null:i===+i+""?+i:J.test(i)?JSON.parse(i):i)}catch(e){}Q.set(e,t,n)}else n=void 0;return n}S.extend({hasData:function(e){return Q.hasData(e)||Y.hasData(e)},data:function(e,t,n){return Q.access(e,t,n)},removeData:function(e,t){Q.remove(e,t)},_data:function(e,t,n){return Y.access(e,t,n)},_removeData:function(e,t){Y.remove(e,t)}}),S.fn.extend({data:function(n,e){var t,r,i,o=this[0],a=o&&o.attributes;if(void 0===n){if(this.length&&(i=Q.get(o),1===o.nodeType&&!Y.get(o,"hasDataAttrs"))){t=a.length;while(t--)a[t]&&0===(r=a[t].name).indexOf("data-")&&(r=X(r.slice(5)),Z(o,r,i[r]));Y.set(o,"hasDataAttrs",!0)}return i}return"object"==typeof n?this.each(function(){Q.set(this,n)}):$(this,function(e){var t;if(o&&void 0===e)return void 0!==(t=Q.get(o,n))?t:void 0!==(t=Z(o,n))?t:void 0;this.each(function(){Q.set(this,n,e)})},null,e,1<arguments.length,null,!0)},removeData:function(e){return this.each(function(){Q.remove(this,e)})}}),S.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=Y.get(e,t),n&&(!r||Array.isArray(n)?r=Y.access(e,t,S.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=S.queue(e,t),r=n.length,i=n.shift(),o=S._queueHooks(e,t);"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,function(){S.dequeue(e,t)},o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return Y.get(e,n)||Y.access(e,n,{empty:S.Callbacks("once memory").add(function(){Y.remove(e,[t+"queue",n])})})}}),S.fn.extend({queue:function(t,n){var e=2;return"string"!=typeof t&&(n=t,t="fx",e--),arguments.length<e?S.queue(this[0],t):void 0===n?this:this.each(function(){var e=S.queue(this,t,n);S._queueHooks(this,t),"fx"===t&&"inprogress"!==e[0]&&S.dequeue(this,t)})},dequeue:function(e){return this.each(function(){S.dequeue(this,e)})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=S.Deferred(),o=this,a=this.length,s=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=void 0),e=e||"fx";while(a--)(n=Y.get(o[a],e+"queueHooks"))&&n.empty&&(r++,n.empty.add(s));return s(),i.promise(t)}});var ee=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,te=new RegExp("^(?:([+-])=|)("+ee+")([a-z%]*)$","i"),ne=["Top","Right","Bottom","Left"],re=E.documentElement,ie=function(e){return S.contains(e.ownerDocument,e)},oe={composed:!0};re.getRootNode&&(ie=function(e){return S.contains(e.ownerDocument,e)||e.getRootNode(oe)===e.ownerDocument});var ae=function(e,t){return"none"===(e=t||e).style.display||""===e.style.display&&ie(e)&&"none"===S.css(e,"display")};function se(e,t,n,r){var i,o,a=20,s=r?function(){return r.cur()}:function(){return S.css(e,t,"")},u=s(),l=n&&n[3]||(S.cssNumber[t]?"":"px"),c=e.nodeType&&(S.cssNumber[t]||"px"!==l&&+u)&&te.exec(S.css(e,t));if(c&&c[3]!==l){u/=2,l=l||c[3],c=+u||1;while(a--)S.style(e,t,c+l),(1-o)*(1-(o=s()/u||.5))<=0&&(a=0),c/=o;c*=2,S.style(e,t,c+l),n=n||[]}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}var ue={};function le(e,t){for(var n,r,i,o,a,s,u,l=[],c=0,f=e.length;c<f;c++)(r=e[c]).style&&(n=r.style.display,t?("none"===n&&(l[c]=Y.get(r,"display")||null,l[c]||(r.style.display="")),""===r.style.display&&ae(r)&&(l[c]=(u=a=o=void 0,a=(i=r).ownerDocument,s=i.nodeName,(u=ue[s])||(o=a.body.appendChild(a.createElement(s)),u=S.css(o,"display"),o.parentNode.removeChild(o),"none"===u&&(u="block"),ue[s]=u)))):"none"!==n&&(l[c]="none",Y.set(r,"display",n)));for(c=0;c<f;c++)null!=l[c]&&(e[c].style.display=l[c]);return e}S.fn.extend({show:function(){return le(this,!0)},hide:function(){return le(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){ae(this)?S(this).show():S(this).hide()})}});var ce,fe,pe=/^(?:checkbox|radio)$/i,de=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="<textarea>x</textarea>",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="<option></option>",y.option=!!ce.lastChild;var ge={thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n<r;n++)Y.set(e[n],"globalEval",!t||Y.get(t[n],"globalEval"))}ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td,y.option||(ge.optgroup=ge.option=[1,"<select multiple='multiple'>","</select>"]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d<h;d++)if((o=e[d])||0===o)if("object"===w(o))S.merge(p,o.nodeType?[o]:o);else if(me.test(o)){a=a||f.appendChild(t.createElement("div")),s=(de.exec(o)||["",""])[1].toLowerCase(),u=ge[s]||ge._default,a.innerHTML=u[1]+S.htmlPrefilter(o)+u[2],c=u[0];while(c--)a=a.lastChild;S.merge(p,a.childNodes),(a=f.firstChild).textContent=""}else p.push(t.createTextNode(o));f.textContent="",d=0;while(o=p[d++])if(r&&-1<S.inArray(o,r))i&&i.push(o);else if(l=ie(o),a=ve(f.appendChild(o),"script"),l&&ye(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}var be=/^key/,we=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Te=/^([^.]*)(?:\.(.+)|)/;function Ce(){return!0}function Ee(){return!1}function Se(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function ke(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)ke(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Ee;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return S().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=S.guid++)),e.each(function(){S.event.add(this,t,i,r,n)})}function Ae(e,i,o){o?(Y.set(e,i,!1),S.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Y.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(S.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Y.set(this,i,r),t=o(this,i),this[i](),r!==(n=Y.get(this,i))||t?Y.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Y.set(this,i,{value:S.event.trigger(S.extend(r[0],S.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Y.get(e,i)&&S.event.add(e,i,Ce)}S.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Y.get(t);if(V(t)){n.handler&&(n=(o=n).handler,i=o.selector),i&&S.find.matchesSelector(re,i),n.guid||(n.guid=S.guid++),(u=v.events)||(u=v.events=Object.create(null)),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof S&&S.event.triggered!==e.type?S.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(P)||[""]).length;while(l--)d=g=(s=Te.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=S.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=S.event.special[d]||{},c=S.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&S.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),S.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Y.hasData(e)&&Y.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(P)||[""]).length;while(l--)if(d=g=(s=Te.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=S.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||S.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)S.event.remove(e,d+t[l],n,r,!0);S.isEmptyObject(u)&&Y.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=new Array(arguments.length),u=S.event.fix(e),l=(Y.get(this,"events")||Object.create(null))[u.type]||[],c=S.event.special[u.type]||{};for(s[0]=u,t=1;t<arguments.length;t++)s[t]=arguments[t];if(u.delegateTarget=this,!c.preDispatch||!1!==c.preDispatch.call(this,u)){a=S.event.handlers.call(this,u,l),t=0;while((i=a[t++])&&!u.isPropagationStopped()){u.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!u.isImmediatePropagationStopped())u.rnamespace&&!1!==o.namespace&&!u.rnamespace.test(o.namespace)||(u.handleObj=o,u.data=o.data,void 0!==(r=((S.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,s))&&!1===(u.result=r)&&(u.preventDefault(),u.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,u),u.result}},handlers:function(e,t){var n,r,i,o,a,s=[],u=t.delegateCount,l=e.target;if(u&&l.nodeType&&!("click"===e.type&&1<=e.button))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n<u;n++)void 0===a[i=(r=t[n]).selector+" "]&&(a[i]=r.needsContext?-1<S(i,this).index(l):S.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u<t.length&&s.push({elem:l,handlers:t.slice(u)}),s},addProp:function(t,e){Object.defineProperty(S.Event.prototype,t,{enumerable:!0,configurable:!0,get:m(e)?function(){if(this.originalEvent)return e(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[t]},set:function(e){Object.defineProperty(this,t,{enumerable:!0,configurable:!0,writable:!0,value:e})}})},fix:function(e){return e[S.expando]?e:new S.Event(e)},special:{load:{noBubble:!0},click:{setup:function(e){var t=this||e;return pe.test(t.type)&&t.click&&A(t,"input")&&Ae(t,"click",Ce),!1},trigger:function(e){var t=this||e;return pe.test(t.type)&&t.click&&A(t,"input")&&Ae(t,"click"),!0},_default:function(e){var t=e.target;return pe.test(t.type)&&t.click&&A(t,"input")&&Y.get(t,"click")||A(t,"a")}},beforeunload:{postDispatch:function(e){void 0!==e.result&&e.originalEvent&&(e.originalEvent.returnValue=e.result)}}}},S.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n)},S.Event=function(e,t){if(!(this instanceof S.Event))return new S.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||void 0===e.defaultPrevented&&!1===e.returnValue?Ce:Ee,this.target=e.target&&3===e.target.nodeType?e.target.parentNode:e.target,this.currentTarget=e.currentTarget,this.relatedTarget=e.relatedTarget):this.type=e,t&&S.extend(this,t),this.timeStamp=e&&e.timeStamp||Date.now(),this[S.expando]=!0},S.Event.prototype={constructor:S.Event,isDefaultPrevented:Ee,isPropagationStopped:Ee,isImmediatePropagationStopped:Ee,isSimulated:!1,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=Ce,e&&!this.isSimulated&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=Ce,e&&!this.isSimulated&&e.stopPropagation()},stopImmediatePropagation:function(){var e=this.originalEvent;this.isImmediatePropagationStopped=Ce,e&&!this.isSimulated&&e.stopImmediatePropagation(),this.stopPropagation()}},S.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,"char":!0,code:!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:function(e){var t=e.button;return null==e.which&&be.test(e.type)?null!=e.charCode?e.charCode:e.keyCode:!e.which&&void 0!==t&&we.test(e.type)?1&t?1:2&t?3:4&t?2:0:e.which}},S.event.addProp),S.each({focus:"focusin",blur:"focusout"},function(e,t){S.event.special[e]={setup:function(){return Ae(this,e,Se),!1},trigger:function(){return Ae(this,e),!0},delegateType:t}}),S.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(e,i){S.event.special[e]={delegateType:i,bindType:i,handle:function(e){var t,n=e.relatedTarget,r=e.handleObj;return n&&(n===this||S.contains(this,n))||(e.type=r.origType,t=r.handler.apply(this,arguments),e.type=i),t}}}),S.fn.extend({on:function(e,t,n,r){return ke(this,e,t,n,r)},one:function(e,t,n,r){return ke(this,e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,S(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return!1!==t&&"function"!=typeof t||(n=t,t=void 0),!1===n&&(n=Ee),this.each(function(){S.event.remove(this,e,n,t)})}});var Ne=/<script|<style|<link/i,De=/checked\s*(?:[^=]|=\s*.checked.)/i,je=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n<r;n++)S.event.add(t,i,s[i][n]);Q.hasData(e)&&(o=Q.access(e),a=S.extend({},o),Q.set(t,a))}}function Pe(n,r,i,o){r=g(r);var e,t,a,s,u,l,c=0,f=n.length,p=f-1,d=r[0],h=m(d);if(h||1<f&&"string"==typeof d&&!y.checkClone&&De.test(d))return n.each(function(e){var t=n.eq(e);h&&(r[0]=d.call(this,e,t.html())),Pe(t,r,i,o)});if(f&&(t=(e=xe(r,n[0].ownerDocument,!1,n,o)).firstChild,1===e.childNodes.length&&(e=t),t||o)){for(s=(a=S.map(ve(e,"script"),Le)).length;c<f;c++)u=e,c!==p&&(u=S.clone(u,!0,!0),s&&S.merge(a,ve(u,"script"))),i.call(n[c],u,c);if(s)for(l=a[a.length-1].ownerDocument,S.map(a,He),c=0;c<s;c++)u=a[c],he.test(u.type||"")&&!Y.access(u,"globalEval")&&S.contains(l,u)&&(u.src&&"module"!==(u.type||"").toLowerCase()?S._evalUrl&&!u.noModule&&S._evalUrl(u.src,{nonce:u.nonce||u.getAttribute("nonce")},l):b(u.textContent.replace(je,""),u,l))}return n}function Re(e,t,n){for(var r,i=t?S.filter(t,e):e,o=0;null!=(r=i[o]);o++)n||1!==r.nodeType||S.cleanData(ve(r)),r.parentNode&&(n&&ie(r)&&ye(ve(r,"script")),r.parentNode.removeChild(r));return e}S.extend({htmlPrefilter:function(e){return e},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=ie(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||S.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r<i;r++)s=o[r],u=a[r],void 0,"input"===(l=u.nodeName.toLowerCase())&&pe.test(s.type)?u.checked=s.checked:"input"!==l&&"textarea"!==l||(u.defaultValue=s.defaultValue);if(t)if(n)for(o=o||ve(e),a=a||ve(c),r=0,i=o.length;r<i;r++)Oe(o[r],a[r]);else Oe(e,c);return 0<(a=ve(c,"script")).length&&ye(a,!f&&ve(e,"script")),c},cleanData:function(e){for(var t,n,r,i=S.event.special,o=0;void 0!==(n=e[o]);o++)if(V(n)){if(t=n[Y.expando]){if(t.events)for(r in t.events)i[r]?S.event.remove(n,r):S.removeEvent(n,r,t.handle);n[Y.expando]=void 0}n[Q.expando]&&(n[Q.expando]=void 0)}}}),S.fn.extend({detach:function(e){return Re(this,e,!0)},remove:function(e){return Re(this,e)},text:function(e){return $(this,function(e){return void 0===e?S.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Pe(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||qe(this,e).appendChild(e)})},prepend:function(){return Pe(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=qe(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Pe(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Pe(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(S.cleanData(ve(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return S.clone(this,e,t)})},html:function(e){return $(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Ne.test(e)&&!ge[(de.exec(e)||["",""])[1].toLowerCase()]){e=S.htmlPrefilter(e);try{for(;n<r;n++)1===(t=this[n]||{}).nodeType&&(S.cleanData(ve(t,!1)),t.innerHTML=e);t=0}catch(e){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var n=[];return Pe(this,arguments,function(e){var t=this.parentNode;S.inArray(this,n)<0&&(S.cleanData(ve(this)),t&&t.replaceChild(e,this))},n)}}),S.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,a){S.fn[e]=function(e){for(var t,n=[],r=S(e),i=r.length-1,o=0;o<=i;o++)t=o===i?this:this.clone(!0),S(r[o])[a](t),u.apply(n,t.get());return this.pushStack(n)}});var Me=new RegExp("^("+ee+")(?!px)[a-z%]+$","i"),Ie=function(e){var t=e.ownerDocument.defaultView;return t&&t.opener||(t=C),t.getComputedStyle(e)},We=function(e,t,n){var r,i,o={};for(i in t)o[i]=e.style[i],e.style[i]=t[i];for(i in r=n.call(e),t)e.style[i]=o[i];return r},Fe=new RegExp(ne.join("|"),"i");function Be(e,t,n){var r,i,o,a,s=e.style;return(n=n||Ie(e))&&(""!==(a=n.getPropertyValue(t)||n[t])||ie(e)||(a=S.style(e,t)),!y.pixelBoxStyles()&&Me.test(a)&&Fe.test(t)&&(r=s.width,i=s.minWidth,o=s.maxWidth,s.minWidth=s.maxWidth=s.width=a,a=n.width,s.width=r,s.minWidth=i,s.maxWidth=o)),void 0!==a?a+"":a}function $e(e,t){return{get:function(){if(!e())return(this.get=t).apply(this,arguments);delete this.get}}}!function(){function e(){if(l){u.style.cssText="position:absolute;left:-11111px;width:60px;margin-top:1px;padding:0;border:0",l.style.cssText="position:relative;display:block;box-sizing:border-box;overflow:scroll;margin:auto;border:1px;padding:1px;width:60%;top:1%",re.appendChild(u).appendChild(l);var e=C.getComputedStyle(l);n="1%"!==e.top,s=12===t(e.marginLeft),l.style.right="60%",o=36===t(e.right),r=36===t(e.width),l.style.position="absolute",i=12===t(l.offsetWidth/3),re.removeChild(u),l=null}}function t(e){return Math.round(parseFloat(e))}var n,r,i,o,a,s,u=E.createElement("div"),l=E.createElement("div");l.style&&(l.style.backgroundClip="content-box",l.cloneNode(!0).style.backgroundClip="",y.clearCloneStyle="content-box"===l.style.backgroundClip,S.extend(y,{boxSizingReliable:function(){return e(),r},pixelBoxStyles:function(){return e(),o},pixelPosition:function(){return e(),n},reliableMarginLeft:function(){return e(),s},scrollboxSize:function(){return e(),i},reliableTrDimensions:function(){var e,t,n,r;return null==a&&(e=E.createElement("table"),t=E.createElement("tr"),n=E.createElement("div"),e.style.cssText="position:absolute;left:-11111px",t.style.height="1px",n.style.height="9px",re.appendChild(e).appendChild(t).appendChild(n),r=C.getComputedStyle(t),a=3<parseInt(r.height),re.removeChild(e)),a}}))}();var _e=["Webkit","Moz","ms"],ze=E.createElement("div").style,Ue={};function Xe(e){var t=S.cssProps[e]||Ue[e];return t||(e in ze?e:Ue[e]=function(e){var t=e[0].toUpperCase()+e.slice(1),n=_e.length;while(n--)if((e=_e[n]+t)in ze)return e}(e)||e)}var Ve=/^(none|table(?!-c[ea]).+)/,Ge=/^--/,Ye={position:"absolute",visibility:"hidden",display:"block"},Qe={letterSpacing:"0",fontWeight:"400"};function Je(e,t,n){var r=te.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||"px"):t}function Ke(e,t,n,r,i,o){var a="width"===t?1:0,s=0,u=0;if(n===(r?"border":"content"))return 0;for(;a<4;a+=2)"margin"===n&&(u+=S.css(e,n+ne[a],!0,i)),r?("content"===n&&(u-=S.css(e,"padding"+ne[a],!0,i)),"margin"!==n&&(u-=S.css(e,"border"+ne[a]+"Width",!0,i))):(u+=S.css(e,"padding"+ne[a],!0,i),"padding"!==n?u+=S.css(e,"border"+ne[a]+"Width",!0,i):s+=S.css(e,"border"+ne[a]+"Width",!0,i));return!r&&0<=o&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))||0),u}function Ze(e,t,n){var r=Ie(e),i=(!y.boxSizingReliable()||n)&&"border-box"===S.css(e,"boxSizing",!1,r),o=i,a=Be(e,t,r),s="offset"+t[0].toUpperCase()+t.slice(1);if(Me.test(a)){if(!n)return a;a="auto"}return(!y.boxSizingReliable()&&i||!y.reliableTrDimensions()&&A(e,"tr")||"auto"===a||!parseFloat(a)&&"inline"===S.css(e,"display",!1,r))&&e.getClientRects().length&&(i="border-box"===S.css(e,"boxSizing",!1,r),(o=s in e)&&(a=e[s])),(a=parseFloat(a)||0)+Ke(e,t,n||(i?"border":"content"),o,r,a)+"px"}function et(e,t,n,r,i){return new et.prototype.init(e,t,n,r,i)}S.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Be(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=X(t),u=Ge.test(t),l=e.style;if(u||(t=Xe(s)),a=S.cssHooks[t]||S.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"===(o=typeof n)&&(i=te.exec(n))&&i[1]&&(n=se(e,t,i),o="number"),null!=n&&n==n&&("number"!==o||u||(n+=i&&i[3]||(S.cssNumber[s]?"":"px")),y.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=X(t);return Ge.test(t)||(t=Xe(s)),(a=S.cssHooks[t]||S.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Be(e,t,r)),"normal"===i&&t in Qe&&(i=Qe[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),S.each(["height","width"],function(e,u){S.cssHooks[u]={get:function(e,t,n){if(t)return!Ve.test(S.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?Ze(e,u,n):We(e,Ye,function(){return Ze(e,u,n)})},set:function(e,t,n){var r,i=Ie(e),o=!y.scrollboxSize()&&"absolute"===i.position,a=(o||n)&&"border-box"===S.css(e,"boxSizing",!1,i),s=n?Ke(e,u,n,a,i):0;return a&&o&&(s-=Math.ceil(e["offset"+u[0].toUpperCase()+u.slice(1)]-parseFloat(i[u])-Ke(e,u,"border",!1,i)-.5)),s&&(r=te.exec(t))&&"px"!==(r[3]||"px")&&(e.style[u]=t,t=S.css(e,u)),Je(0,t,s)}}}),S.cssHooks.marginLeft=$e(y.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Be(e,"marginLeft"))||e.getBoundingClientRect().left-We(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),S.each({margin:"",padding:"",border:"Width"},function(i,o){S.cssHooks[i+o]={expand:function(e){for(var t=0,n={},r="string"==typeof e?e.split(" "):[e];t<4;t++)n[i+ne[t]+o]=r[t]||r[t-2]||r[0];return n}},"margin"!==i&&(S.cssHooks[i+o].set=Je)}),S.fn.extend({css:function(e,t){return $(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=Ie(e),i=t.length;a<i;a++)o[t[a]]=S.css(e,t[a],!1,r);return o}return void 0!==n?S.style(e,t,n):S.css(e,t)},e,t,1<arguments.length)}}),((S.Tween=et).prototype={constructor:et,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||S.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(S.cssNumber[n]?"":"px")},cur:function(){var e=et.propHooks[this.prop];return e&&e.get?e.get(this):et.propHooks._default.get(this)},run:function(e){var t,n=et.propHooks[this.prop];return this.options.duration?this.pos=t=S.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):et.propHooks._default.set(this),this}}).init.prototype=et.prototype,(et.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=S.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){S.fx.step[e.prop]?S.fx.step[e.prop](e):1!==e.elem.nodeType||!S.cssHooks[e.prop]&&null==e.elem.style[Xe(e.prop)]?e.elem[e.prop]=e.now:S.style(e.elem,e.prop,e.now+e.unit)}}}).scrollTop=et.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},S.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},S.fx=et.prototype.init,S.fx.step={};var tt,nt,rt,it,ot=/^(?:toggle|show|hide)$/,at=/queueHooks$/;function st(){nt&&(!1===E.hidden&&C.requestAnimationFrame?C.requestAnimationFrame(st):C.setTimeout(st,S.fx.interval),S.fx.tick())}function ut(){return C.setTimeout(function(){tt=void 0}),tt=Date.now()}function lt(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=ne[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function ct(e,t,n){for(var r,i=(ft.tweeners[t]||[]).concat(ft.tweeners["*"]),o=0,a=i.length;o<a;o++)if(r=i[o].call(n,t,e))return r}function ft(o,e,t){var n,a,r=0,i=ft.prefilters.length,s=S.Deferred().always(function(){delete u.elem}),u=function(){if(a)return!1;for(var e=tt||ut(),t=Math.max(0,l.startTime+l.duration-e),n=1-(t/l.duration||0),r=0,i=l.tweens.length;r<i;r++)l.tweens[r].run(n);return s.notifyWith(o,[l,n,t]),n<1&&i?t:(i||s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l]),!1)},l=s.promise({elem:o,props:S.extend({},e),opts:S.extend(!0,{specialEasing:{},easing:S.easing._default},t),originalProperties:e,originalOptions:t,startTime:tt||ut(),duration:t.duration,tweens:[],createTween:function(e,t){var n=S.Tween(o,l.opts,e,t,l.opts.specialEasing[e]||l.opts.easing);return l.tweens.push(n),n},stop:function(e){var t=0,n=e?l.tweens.length:0;if(a)return this;for(a=!0;t<n;t++)l.tweens[t].run(1);return e?(s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l,e])):s.rejectWith(o,[l,e]),this}}),c=l.props;for(!function(e,t){var n,r,i,o,a;for(n in e)if(i=t[r=X(n)],o=e[n],Array.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),(a=S.cssHooks[r])&&"expand"in a)for(n in o=a.expand(o),delete e[r],o)n in e||(e[n]=o[n],t[n]=i);else t[r]=i}(c,l.opts.specialEasing);r<i;r++)if(n=ft.prefilters[r].call(l,o,c,l.opts))return m(n.stop)&&(S._queueHooks(l.elem,l.opts.queue).stop=n.stop.bind(n)),n;return S.map(c,ct,l),m(l.opts.start)&&l.opts.start.call(o,l),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always),S.fx.timer(S.extend(u,{elem:o,anim:l,queue:l.opts.queue})),l}S.Animation=S.extend(ft,{tweeners:{"*":[function(e,t){var n=this.createTween(e,t);return se(n.elem,e,te.exec(t),n),n}]},tweener:function(e,t){m(e)?(t=e,e=["*"]):e=e.match(P);for(var n,r=0,i=e.length;r<i;r++)n=e[r],ft.tweeners[n]=ft.tweeners[n]||[],ft.tweeners[n].unshift(t)},prefilters:[function(e,t,n){var r,i,o,a,s,u,l,c,f="width"in t||"height"in t,p=this,d={},h=e.style,g=e.nodeType&&ae(e),v=Y.get(e,"fxshow");for(r in n.queue||(null==(a=S._queueHooks(e,"fx")).unqueued&&(a.unqueued=0,s=a.empty.fire,a.empty.fire=function(){a.unqueued||s()}),a.unqueued++,p.always(function(){p.always(function(){a.unqueued--,S.queue(e,"fx").length||a.empty.fire()})})),t)if(i=t[r],ot.test(i)){if(delete t[r],o=o||"toggle"===i,i===(g?"hide":"show")){if("show"!==i||!v||void 0===v[r])continue;g=!0}d[r]=v&&v[r]||S.style(e,r)}if((u=!S.isEmptyObject(t))||!S.isEmptyObject(d))for(r in f&&1===e.nodeType&&(n.overflow=[h.overflow,h.overflowX,h.overflowY],null==(l=v&&v.display)&&(l=Y.get(e,"display")),"none"===(c=S.css(e,"display"))&&(l?c=l:(le([e],!0),l=e.style.display||l,c=S.css(e,"display"),le([e]))),("inline"===c||"inline-block"===c&&null!=l)&&"none"===S.css(e,"float")&&(u||(p.done(function(){h.display=l}),null==l&&(c=h.display,l="none"===c?"":c)),h.display="inline-block")),n.overflow&&(h.overflow="hidden",p.always(function(){h.overflow=n.overflow[0],h.overflowX=n.overflow[1],h.overflowY=n.overflow[2]})),u=!1,d)u||(v?"hidden"in v&&(g=v.hidden):v=Y.access(e,"fxshow",{display:l}),o&&(v.hidden=!g),g&&le([e],!0),p.done(function(){for(r in g||le([e]),Y.remove(e,"fxshow"),d)S.style(e,r,d[r])})),u=ct(g?v[r]:0,r,p),r in v||(v[r]=u.start,g&&(u.end=u.start,u.start=0))}],prefilter:function(e,t){t?ft.prefilters.unshift(e):ft.prefilters.push(e)}}),S.speed=function(e,t,n){var r=e&&"object"==typeof e?S.extend({},e):{complete:n||!n&&t||m(e)&&e,duration:e,easing:n&&t||t&&!m(t)&&t};return S.fx.off?r.duration=0:"number"!=typeof r.duration&&(r.duration in S.fx.speeds?r.duration=S.fx.speeds[r.duration]:r.duration=S.fx.speeds._default),null!=r.queue&&!0!==r.queue||(r.queue="fx"),r.old=r.complete,r.complete=function(){m(r.old)&&r.old.call(this),r.queue&&S.dequeue(this,r.queue)},r},S.fn.extend({fadeTo:function(e,t,n,r){return this.filter(ae).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(t,e,n,r){var i=S.isEmptyObject(t),o=S.speed(e,n,r),a=function(){var e=ft(this,S.extend({},t),o);(i||Y.get(this,"finish"))&&e.stop(!0)};return a.finish=a,i||!1===o.queue?this.each(a):this.queue(o.queue,a)},stop:function(i,e,o){var a=function(e){var t=e.stop;delete e.stop,t(o)};return"string"!=typeof i&&(o=e,e=i,i=void 0),e&&this.queue(i||"fx",[]),this.each(function(){var e=!0,t=null!=i&&i+"queueHooks",n=S.timers,r=Y.get(this);if(t)r[t]&&r[t].stop&&a(r[t]);else for(t in r)r[t]&&r[t].stop&&at.test(t)&&a(r[t]);for(t=n.length;t--;)n[t].elem!==this||null!=i&&n[t].queue!==i||(n[t].anim.stop(o),e=!1,n.splice(t,1));!e&&o||S.dequeue(this,i)})},finish:function(a){return!1!==a&&(a=a||"fx"),this.each(function(){var e,t=Y.get(this),n=t[a+"queue"],r=t[a+"queueHooks"],i=S.timers,o=n?n.length:0;for(t.finish=!0,S.queue(this,a,[]),r&&r.stop&&r.stop.call(this,!0),e=i.length;e--;)i[e].elem===this&&i[e].queue===a&&(i[e].anim.stop(!0),i.splice(e,1));for(e=0;e<o;e++)n[e]&&n[e].finish&&n[e].finish.call(this);delete t.finish})}}),S.each(["toggle","show","hide"],function(e,r){var i=S.fn[r];S.fn[r]=function(e,t,n){return null==e||"boolean"==typeof e?i.apply(this,arguments):this.animate(lt(r,!0),e,t,n)}}),S.each({slideDown:lt("show"),slideUp:lt("hide"),slideToggle:lt("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,r){S.fn[e]=function(e,t,n){return this.animate(r,e,t,n)}}),S.timers=[],S.fx.tick=function(){var e,t=0,n=S.timers;for(tt=Date.now();t<n.length;t++)(e=n[t])()||n[t]!==e||n.splice(t--,1);n.length||S.fx.stop(),tt=void 0},S.fx.timer=function(e){S.timers.push(e),S.fx.start()},S.fx.interval=13,S.fx.start=function(){nt||(nt=!0,st())},S.fx.stop=function(){nt=null},S.fx.speeds={slow:600,fast:200,_default:400},S.fn.delay=function(r,e){return r=S.fx&&S.fx.speeds[r]||r,e=e||"fx",this.queue(e,function(e,t){var n=C.setTimeout(e,r);t.stop=function(){C.clearTimeout(n)}})},rt=E.createElement("input"),it=E.createElement("select").appendChild(E.createElement("option")),rt.type="checkbox",y.checkOn=""!==rt.value,y.optSelected=it.selected,(rt=E.createElement("input")).value="t",rt.type="radio",y.radioValue="t"===rt.value;var pt,dt=S.expr.attrHandle;S.fn.extend({attr:function(e,t){return $(this,S.attr,e,t,1<arguments.length)},removeAttr:function(e){return this.each(function(){S.removeAttr(this,e)})}}),S.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?S.prop(e,t,n):(1===o&&S.isXMLDoc(e)||(i=S.attrHooks[t.toLowerCase()]||(S.expr.match.bool.test(t)?pt:void 0)),void 0!==n?null===n?void S.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=S.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!y.radioValue&&"radio"===t&&A(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(P);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),pt={set:function(e,t,n){return!1===t?S.removeAttr(e,n):e.setAttribute(n,n),n}},S.each(S.expr.match.bool.source.match(/\w+/g),function(e,t){var a=dt[t]||S.find.attr;dt[t]=function(e,t,n){var r,i,o=t.toLowerCase();return n||(i=dt[o],dt[o]=r,r=null!=a(e,t,n)?o:null,dt[o]=i),r}});var ht=/^(?:input|select|textarea|button)$/i,gt=/^(?:a|area)$/i;function vt(e){return(e.match(P)||[]).join(" ")}function yt(e){return e.getAttribute&&e.getAttribute("class")||""}function mt(e){return Array.isArray(e)?e:"string"==typeof e&&e.match(P)||[]}S.fn.extend({prop:function(e,t){return $(this,S.prop,e,t,1<arguments.length)},removeProp:function(e){return this.each(function(){delete this[S.propFix[e]||e]})}}),S.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&S.isXMLDoc(e)||(t=S.propFix[t]||t,i=S.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=S.find.attr(e,"tabindex");return t?parseInt(t,10):ht.test(e.nodeName)||gt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),y.optSelected||(S.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),S.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){S.propFix[this.toLowerCase()]=this}),S.fn.extend({addClass:function(t){var e,n,r,i,o,a,s,u=0;if(m(t))return this.each(function(e){S(this).addClass(t.call(this,e,yt(this)))});if((e=mt(t)).length)while(n=this[u++])if(i=yt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=e[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},removeClass:function(t){var e,n,r,i,o,a,s,u=0;if(m(t))return this.each(function(e){S(this).removeClass(t.call(this,e,yt(this)))});if(!arguments.length)return this.attr("class","");if((e=mt(t)).length)while(n=this[u++])if(i=yt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=e[a++])while(-1<r.indexOf(" "+o+" "))r=r.replace(" "+o+" "," ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},toggleClass:function(i,t){var o=typeof i,a="string"===o||Array.isArray(i);return"boolean"==typeof t&&a?t?this.addClass(i):this.removeClass(i):m(i)?this.each(function(e){S(this).toggleClass(i.call(this,e,yt(this),t),t)}):this.each(function(){var e,t,n,r;if(a){t=0,n=S(this),r=mt(i);while(e=r[t++])n.hasClass(e)?n.removeClass(e):n.addClass(e)}else void 0!==i&&"boolean"!==o||((e=yt(this))&&Y.set(this,"__className__",e),this.setAttribute&&this.setAttribute("class",e||!1===i?"":Y.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&-1<(" "+vt(yt(n))+" ").indexOf(t))return!0;return!1}});var xt=/\r/g;S.fn.extend({val:function(n){var r,e,i,t=this[0];return arguments.length?(i=m(n),this.each(function(e){var t;1===this.nodeType&&(null==(t=i?n.call(this,e,S(this).val()):n)?t="":"number"==typeof t?t+="":Array.isArray(t)&&(t=S.map(t,function(e){return null==e?"":e+""})),(r=S.valHooks[this.type]||S.valHooks[this.nodeName.toLowerCase()])&&"set"in r&&void 0!==r.set(this,t,"value")||(this.value=t))})):t?(r=S.valHooks[t.type]||S.valHooks[t.nodeName.toLowerCase()])&&"get"in r&&void 0!==(e=r.get(t,"value"))?e:"string"==typeof(e=t.value)?e.replace(xt,""):null==e?"":e:void 0}}),S.extend({valHooks:{option:{get:function(e){var t=S.find.attr(e,"value");return null!=t?t:vt(S.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r<u;r++)if(((n=i[r]).selected||r===o)&&!n.disabled&&(!n.parentNode.disabled||!A(n.parentNode,"optgroup"))){if(t=S(n).val(),a)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=S.makeArray(t),a=i.length;while(a--)((r=i[a]).selected=-1<S.inArray(S.valHooks.option.get(r),o))&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),S.each(["radio","checkbox"],function(){S.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=-1<S.inArray(S(e).val(),t)}},y.checkOn||(S.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),y.focusin="onfocusin"in C;var bt=/^(?:focusinfocus|focusoutblur)$/,wt=function(e){e.stopPropagation()};S.extend(S.event,{trigger:function(e,t,n,r){var i,o,a,s,u,l,c,f,p=[n||E],d=v.call(e,"type")?e.type:e,h=v.call(e,"namespace")?e.namespace.split("."):[];if(o=f=a=n=n||E,3!==n.nodeType&&8!==n.nodeType&&!bt.test(d+S.event.triggered)&&(-1<d.indexOf(".")&&(d=(h=d.split(".")).shift(),h.sort()),u=d.indexOf(":")<0&&"on"+d,(e=e[S.expando]?e:new S.Event(d,"object"==typeof e&&e)).isTrigger=r?2:3,e.namespace=h.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=n),t=null==t?[e]:S.makeArray(t,[e]),c=S.event.special[d]||{},r||!c.trigger||!1!==c.trigger.apply(n,t))){if(!r&&!c.noBubble&&!x(n)){for(s=c.delegateType||d,bt.test(s+d)||(o=o.parentNode);o;o=o.parentNode)p.push(o),a=o;a===(n.ownerDocument||E)&&p.push(a.defaultView||a.parentWindow||C)}i=0;while((o=p[i++])&&!e.isPropagationStopped())f=o,e.type=1<i?s:c.bindType||d,(l=(Y.get(o,"events")||Object.create(null))[e.type]&&Y.get(o,"handle"))&&l.apply(o,t),(l=u&&o[u])&&l.apply&&V(o)&&(e.result=l.apply(o,t),!1===e.result&&e.preventDefault());return e.type=d,r||e.isDefaultPrevented()||c._default&&!1!==c._default.apply(p.pop(),t)||!V(n)||u&&m(n[d])&&!x(n)&&((a=n[u])&&(n[u]=null),S.event.triggered=d,e.isPropagationStopped()&&f.addEventListener(d,wt),n[d](),e.isPropagationStopped()&&f.removeEventListener(d,wt),S.event.triggered=void 0,a&&(n[u]=a)),e.result}},simulate:function(e,t,n){var r=S.extend(new S.Event,n,{type:e,isSimulated:!0});S.event.trigger(r,null,t)}}),S.fn.extend({trigger:function(e,t){return this.each(function(){S.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return S.event.trigger(e,t,n,!0)}}),y.focusin||S.each({focus:"focusin",blur:"focusout"},function(n,r){var i=function(e){S.event.simulate(r,e.target,S.event.fix(e))};S.event.special[r]={setup:function(){var e=this.ownerDocument||this.document||this,t=Y.access(e,r);t||e.addEventListener(n,i,!0),Y.access(e,r,(t||0)+1)},teardown:function(){var e=this.ownerDocument||this.document||this,t=Y.access(e,r)-1;t?Y.access(e,r,t):(e.removeEventListener(n,i,!0),Y.remove(e,r))}}});var Tt=C.location,Ct={guid:Date.now()},Et=/\?/;S.parseXML=function(e){var t;if(!e||"string"!=typeof e)return null;try{t=(new C.DOMParser).parseFromString(e,"text/xml")}catch(e){t=void 0}return t&&!t.getElementsByTagName("parsererror").length||S.error("Invalid XML: "+e),t};var St=/\[\]$/,kt=/\r?\n/g,At=/^(?:submit|button|image|reset|file)$/i,Nt=/^(?:input|select|textarea|keygen)/i;function Dt(n,e,r,i){var t;if(Array.isArray(e))S.each(e,function(e,t){r||St.test(n)?i(n,t):Dt(n+"["+("object"==typeof t&&null!=t?e:"")+"]",t,r,i)});else if(r||"object"!==w(e))i(n,e);else for(t in e)Dt(n+"["+t+"]",e[t],r,i)}S.param=function(e,t){var n,r=[],i=function(e,t){var n=m(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(null==e)return"";if(Array.isArray(e)||e.jquery&&!S.isPlainObject(e))S.each(e,function(){i(this.name,this.value)});else for(n in e)Dt(n,e[n],t,i);return r.join("&")},S.fn.extend({serialize:function(){return S.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=S.prop(this,"elements");return e?S.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!S(this).is(":disabled")&&Nt.test(this.nodeName)&&!At.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=S(this).val();return null==n?null:Array.isArray(n)?S.map(n,function(e){return{name:t.name,value:e.replace(kt,"\r\n")}}):{name:t.name,value:n.replace(kt,"\r\n")}}).get()}});var jt=/%20/g,qt=/#.*$/,Lt=/([?&])_=[^&]*/,Ht=/^(.*?):[ \t]*([^\r\n]*)$/gm,Ot=/^(?:GET|HEAD)$/,Pt=/^\/\//,Rt={},Mt={},It="*/".concat("*"),Wt=E.createElement("a");function Ft(o){return function(e,t){"string"!=typeof e&&(t=e,e="*");var n,r=0,i=e.toLowerCase().match(P)||[];if(m(t))while(n=i[r++])"+"===n[0]?(n=n.slice(1)||"*",(o[n]=o[n]||[]).unshift(t)):(o[n]=o[n]||[]).push(t)}}function Bt(t,i,o,a){var s={},u=t===Mt;function l(e){var r;return s[e]=!0,S.each(t[e]||[],function(e,t){var n=t(i,o,a);return"string"!=typeof n||u||s[n]?u?!(r=n):void 0:(i.dataTypes.unshift(n),l(n),!1)}),r}return l(i.dataTypes[0])||!s["*"]&&l("*")}function $t(e,t){var n,r,i=S.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&S.extend(!0,e,r),e}Wt.href=Tt.href,S.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Tt.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(Tt.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":It,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":S.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?$t($t(e,S.ajaxSettings),t):$t(S.ajaxSettings,e)},ajaxPrefilter:Ft(Rt),ajaxTransport:Ft(Mt),ajax:function(e,t){"object"==typeof e&&(t=e,e=void 0),t=t||{};var c,f,p,n,d,r,h,g,i,o,v=S.ajaxSetup({},t),y=v.context||v,m=v.context&&(y.nodeType||y.jquery)?S(y):S.event,x=S.Deferred(),b=S.Callbacks("once memory"),w=v.statusCode||{},a={},s={},u="canceled",T={readyState:0,getResponseHeader:function(e){var t;if(h){if(!n){n={};while(t=Ht.exec(p))n[t[1].toLowerCase()+" "]=(n[t[1].toLowerCase()+" "]||[]).concat(t[2])}t=n[e.toLowerCase()+" "]}return null==t?null:t.join(", ")},getAllResponseHeaders:function(){return h?p:null},setRequestHeader:function(e,t){return null==h&&(e=s[e.toLowerCase()]=s[e.toLowerCase()]||e,a[e]=t),this},overrideMimeType:function(e){return null==h&&(v.mimeType=e),this},statusCode:function(e){var t;if(e)if(h)T.always(e[T.status]);else for(t in e)w[t]=[w[t],e[t]];return this},abort:function(e){var t=e||u;return c&&c.abort(t),l(0,t),this}};if(x.promise(T),v.url=((e||v.url||Tt.href)+"").replace(Pt,Tt.protocol+"//"),v.type=t.method||t.type||v.method||v.type,v.dataTypes=(v.dataType||"*").toLowerCase().match(P)||[""],null==v.crossDomain){r=E.createElement("a");try{r.href=v.url,r.href=r.href,v.crossDomain=Wt.protocol+"//"+Wt.host!=r.protocol+"//"+r.host}catch(e){v.crossDomain=!0}}if(v.data&&v.processData&&"string"!=typeof v.data&&(v.data=S.param(v.data,v.traditional)),Bt(Rt,v,t,T),h)return T;for(i in(g=S.event&&v.global)&&0==S.active++&&S.event.trigger("ajaxStart"),v.type=v.type.toUpperCase(),v.hasContent=!Ot.test(v.type),f=v.url.replace(qt,""),v.hasContent?v.data&&v.processData&&0===(v.contentType||"").indexOf("application/x-www-form-urlencoded")&&(v.data=v.data.replace(jt,"+")):(o=v.url.slice(f.length),v.data&&(v.processData||"string"==typeof v.data)&&(f+=(Et.test(f)?"&":"?")+v.data,delete v.data),!1===v.cache&&(f=f.replace(Lt,"$1"),o=(Et.test(f)?"&":"?")+"_="+Ct.guid+++o),v.url=f+o),v.ifModified&&(S.lastModified[f]&&T.setRequestHeader("If-Modified-Since",S.lastModified[f]),S.etag[f]&&T.setRequestHeader("If-None-Match",S.etag[f])),(v.data&&v.hasContent&&!1!==v.contentType||t.contentType)&&T.setRequestHeader("Content-Type",v.contentType),T.setRequestHeader("Accept",v.dataTypes[0]&&v.accepts[v.dataTypes[0]]?v.accepts[v.dataTypes[0]]+("*"!==v.dataTypes[0]?", "+It+"; q=0.01":""):v.accepts["*"]),v.headers)T.setRequestHeader(i,v.headers[i]);if(v.beforeSend&&(!1===v.beforeSend.call(y,T,v)||h))return T.abort();if(u="abort",b.add(v.complete),T.done(v.success),T.fail(v.error),c=Bt(Mt,v,t,T)){if(T.readyState=1,g&&m.trigger("ajaxSend",[T,v]),h)return T;v.async&&0<v.timeout&&(d=C.setTimeout(function(){T.abort("timeout")},v.timeout));try{h=!1,c.send(a,l)}catch(e){if(h)throw e;l(-1,e)}}else l(-1,"No Transport");function l(e,t,n,r){var i,o,a,s,u,l=t;h||(h=!0,d&&C.clearTimeout(d),c=void 0,p=r||"",T.readyState=0<e?4:0,i=200<=e&&e<300||304===e,n&&(s=function(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}(v,T,n)),!i&&-1<S.inArray("script",v.dataTypes)&&(v.converters["text script"]=function(){}),s=function(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}(v,s,T,i),i?(v.ifModified&&((u=T.getResponseHeader("Last-Modified"))&&(S.lastModified[f]=u),(u=T.getResponseHeader("etag"))&&(S.etag[f]=u)),204===e||"HEAD"===v.type?l="nocontent":304===e?l="notmodified":(l=s.state,o=s.data,i=!(a=s.error))):(a=l,!e&&l||(l="error",e<0&&(e=0))),T.status=e,T.statusText=(t||l)+"",i?x.resolveWith(y,[o,l,T]):x.rejectWith(y,[T,l,a]),T.statusCode(w),w=void 0,g&&m.trigger(i?"ajaxSuccess":"ajaxError",[T,v,i?o:a]),b.fireWith(y,[T,l]),g&&(m.trigger("ajaxComplete",[T,v]),--S.active||S.event.trigger("ajaxStop")))}return T},getJSON:function(e,t,n){return S.get(e,t,n,"json")},getScript:function(e,t){return S.get(e,void 0,t,"script")}}),S.each(["get","post"],function(e,i){S[i]=function(e,t,n,r){return m(t)&&(r=r||n,n=t,t=void 0),S.ajax(S.extend({url:e,type:i,dataType:r,data:t,success:n},S.isPlainObject(e)&&e))}}),S.ajaxPrefilter(function(e){var t;for(t in e.headers)"content-type"===t.toLowerCase()&&(e.contentType=e.headers[t]||"")}),S._evalUrl=function(e,t,n){return S.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(e){S.globalEval(e,t,n)}})},S.fn.extend({wrapAll:function(e){var t;return this[0]&&(m(e)&&(e=e.call(this[0])),t=S(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(n){return m(n)?this.each(function(e){S(this).wrapInner(n.call(this,e))}):this.each(function(){var e=S(this),t=e.contents();t.length?t.wrapAll(n):e.append(n)})},wrap:function(t){var n=m(t);return this.each(function(e){S(this).wrapAll(n?t.call(this,e):t)})},unwrap:function(e){return this.parent(e).not("body").each(function(){S(this).replaceWith(this.childNodes)}),this}}),S.expr.pseudos.hidden=function(e){return!S.expr.pseudos.visible(e)},S.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},S.ajaxSettings.xhr=function(){try{return new C.XMLHttpRequest}catch(e){}};var _t={0:200,1223:204},zt=S.ajaxSettings.xhr();y.cors=!!zt&&"withCredentials"in zt,y.ajax=zt=!!zt,S.ajaxTransport(function(i){var o,a;if(y.cors||zt&&!i.crossDomain)return{send:function(e,t){var n,r=i.xhr();if(r.open(i.type,i.url,i.async,i.username,i.password),i.xhrFields)for(n in i.xhrFields)r[n]=i.xhrFields[n];for(n in i.mimeType&&r.overrideMimeType&&r.overrideMimeType(i.mimeType),i.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest"),e)r.setRequestHeader(n,e[n]);o=function(e){return function(){o&&(o=a=r.onload=r.onerror=r.onabort=r.ontimeout=r.onreadystatechange=null,"abort"===e?r.abort():"error"===e?"number"!=typeof r.status?t(0,"error"):t(r.status,r.statusText):t(_t[r.status]||r.status,r.statusText,"text"!==(r.responseType||"text")||"string"!=typeof r.responseText?{binary:r.response}:{text:r.responseText},r.getAllResponseHeaders()))}},r.onload=o(),a=r.onerror=r.ontimeout=o("error"),void 0!==r.onabort?r.onabort=a:r.onreadystatechange=function(){4===r.readyState&&C.setTimeout(function(){o&&a()})},o=o("abort");try{r.send(i.hasContent&&i.data||null)}catch(e){if(o)throw e}},abort:function(){o&&o()}}}),S.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),S.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return S.globalEval(e),e}}}),S.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),S.ajaxTransport("script",function(n){var r,i;if(n.crossDomain||n.scriptAttrs)return{send:function(e,t){r=S("<script>").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="<form></form><form></form>",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1<s&&(r=vt(e.slice(s)),e=e.slice(0,s)),m(t)?(n=t,t=void 0):t&&"object"==typeof t&&(i="POST"),0<a.length&&S.ajax({url:e,type:i||"GET",dataType:"html",data:t}).done(function(e){o=arguments,a.html(r?S("<div>").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0<arguments.length?this.on(n,null,e,t):this.trigger(n)}});var Gt=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;S.proxy=function(e,t){var n,r,i;if("string"==typeof t&&(n=e[t],t=e,e=n),m(e))return r=s.call(arguments,2),(i=function(){return e.apply(t||this,r.concat(s.call(arguments)))}).guid=e.guid=e.guid||S.guid++,i},S.holdReady=function(e){e?S.readyWait++:S.ready(!0)},S.isArray=Array.isArray,S.parseJSON=JSON.parse,S.nodeName=A,S.isFunction=m,S.isWindow=x,S.camelCase=X,S.type=w,S.now=Date.now,S.isNumeric=function(e){var t=S.type(e);return("number"===t||"string"===t)&&!isNaN(e-parseFloat(e))},S.trim=function(e){return null==e?"":(e+"").replace(Gt,"")},"function"==typeof define&&define.amd&&define("jquery",[],function(){return S});var Yt=C.jQuery,Qt=C.$;return S.noConflict=function(e){return C.$===S&&(C.$=Qt),e&&C.jQuery===S&&(C.jQuery=Yt),S},"undefined"==typeof e&&(C.jQuery=C.$=S),S}); diff --git a/django/contrib/admin/static/admin/js/vendor/select2/LICENSE.md b/django/contrib/admin/static/admin/js/vendor/select2/LICENSE.md new file mode 100644 index 000000000000..86c7c291a969 --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2012-2015 Kevin Brown, Igor Vaynberg, and Select2 contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/ar.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/ar.js new file mode 100644 index 000000000000..01a688294847 --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/ar.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ar",[],function(){return{errorLoading:function(){return"لا يمكن تحميل النتائج"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="الرجاء حذف "+t+" عناصر";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="الرجاء إضافة "+t+" عناصر";return n},loadingMore:function(){return"جاري تحميل نتائج إضافية..."},maximumSelected:function(e){var t="تستطيع إختيار "+e.maximum+" بنود فقط";return t},noResults:function(){return"لم يتم العثور على أي نتائج"},searching:function(){return"جاري البحث…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/az.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/az.js new file mode 100644 index 000000000000..2accb973f63f --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/az.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/az",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum;return t+" simvol silin"},inputTooShort:function(e){var t=e.minimum-e.input.length;return t+" simvol daxil edin"},loadingMore:function(){return"Daha çox nəticə yüklənir…"},maximumSelected:function(e){return"Sadəcə "+e.maximum+" element seçə bilərsiniz"},noResults:function(){return"Nəticə tapılmadı"},searching:function(){return"Axtarılır…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/bg.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/bg.js new file mode 100644 index 000000000000..35ae989447d2 --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/bg.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/bg",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Моля въведете с "+t+" по-малко символ";return t>1&&(n+="a"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Моля въведете още "+t+" символ";return t>1&&(n+="a"),n},loadingMore:function(){return"Зареждат се още…"},maximumSelected:function(e){var t="Можете да направите до "+e.maximum+" ";return e.maximum>1?t+="избора":t+="избор",t},noResults:function(){return"Няма намерени съвпадения"},searching:function(){return"Търсене…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/ca.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/ca.js new file mode 100644 index 000000000000..fdb5f3d2a5e6 --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/ca.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ca",[],function(){return{errorLoading:function(){return"La càrrega ha fallat"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Si us plau, elimina "+t+" car";return t==1?n+="àcter":n+="àcters",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Si us plau, introdueix "+t+" car";return t==1?n+="àcter":n+="àcters",n},loadingMore:function(){return"Carregant més resultats…"},maximumSelected:function(e){var t="Només es pot seleccionar "+e.maximum+" element";return e.maximum!=1&&(t+="s"),t},noResults:function(){return"No s'han trobat resultats"},searching:function(){return"Cercant…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/cs.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/cs.js new file mode 100644 index 000000000000..9651378a609f --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/cs.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/cs",[],function(){function e(e,t){switch(e){case 2:return t?"dva":"dvě";case 3:return"tři";case 4:return"čtyři"}return""}return{errorLoading:function(){return"Výsledky nemohly být načteny."},inputTooLong:function(t){var n=t.input.length-t.maximum;return n==1?"Prosím zadejte o jeden znak méně":n<=4?"Prosím zadejte o "+e(n,!0)+" znaky méně":"Prosím zadejte o "+n+" znaků méně"},inputTooShort:function(t){var n=t.minimum-t.input.length;return n==1?"Prosím zadejte ještě jeden znak":n<=4?"Prosím zadejte ještě další "+e(n,!0)+" znaky":"Prosím zadejte ještě dalších "+n+" znaků"},loadingMore:function(){return"Načítají se další výsledky…"},maximumSelected:function(t){var n=t.maximum;return n==1?"Můžete zvolit jen jednu položku":n<=4?"Můžete zvolit maximálně "+e(n,!1)+" položky":"Můžete zvolit maximálně "+n+" položek"},noResults:function(){return"Nenalezeny žádné položky"},searching:function(){return"Vyhledávání…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/da.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/da.js new file mode 100644 index 000000000000..501c51e9334f --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/da.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/da",[],function(){return{errorLoading:function(){return"Resultaterne kunne ikke indlæses."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Angiv venligst "+t+" tegn mindre";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Angiv venligst "+t+" tegn mere";return n},loadingMore:function(){return"Indlæser flere resultater…"},maximumSelected:function(e){var t="Du kan kun vælge "+e.maximum+" emne";return e.maximum!=1&&(t+="r"),t},noResults:function(){return"Ingen resultater fundet"},searching:function(){return"Søger…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/de.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/de.js new file mode 100644 index 000000000000..9a6d55366f5a --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/de.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/de",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum;return"Bitte "+t+" Zeichen weniger eingeben"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Bitte "+t+" Zeichen mehr eingeben"},loadingMore:function(){return"Lade mehr Ergebnisse…"},maximumSelected:function(e){var t="Sie können nur "+e.maximum+" Eintr";return e.maximum===1?t+="ag":t+="äge",t+=" auswählen",t},noResults:function(){return"Keine Übereinstimmungen gefunden"},searching:function(){return"Suche…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/el.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/el.js new file mode 100644 index 000000000000..4735d140528f --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/el.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/el",[],function(){return{errorLoading:function(){return"Τα αποτελέσματα δεν μπόρεσαν να φορτώσουν."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Παρακαλώ διαγράψτε "+t+" χαρακτήρ";return t==1&&(n+="α"),t!=1&&(n+="ες"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Παρακαλώ συμπληρώστε "+t+" ή περισσότερους χαρακτήρες";return n},loadingMore:function(){return"Φόρτωση περισσότερων αποτελεσμάτων…"},maximumSelected:function(e){var t="Μπορείτε να επιλέξετε μόνο "+e.maximum+" επιλογ";return e.maximum==1&&(t+="ή"),e.maximum!=1&&(t+="ές"),t},noResults:function(){return"Δεν βρέθηκαν αποτελέσματα"},searching:function(){return"Αναζήτηση…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/en.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/en.js new file mode 100644 index 000000000000..8e80ede8db95 --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/en.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/en",[],function(){return{errorLoading:function(){return"The results could not be loaded."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Please delete "+t+" character";return t!=1&&(n+="s"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Please enter "+t+" or more characters";return n},loadingMore:function(){return"Loading more results…"},maximumSelected:function(e){var t="You can only select "+e.maximum+" item";return e.maximum!=1&&(t+="s"),t},noResults:function(){return"No results found"},searching:function(){return"Searching…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/es.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/es.js new file mode 100644 index 000000000000..0a096502da9b --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/es.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/es",[],function(){return{errorLoading:function(){return"La carga falló"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Por favor, elimine "+t+" car";return t==1?n+="ácter":n+="acteres",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Por favor, introduzca "+t+" car";return t==1?n+="ácter":n+="acteres",n},loadingMore:function(){return"Cargando más resultados…"},maximumSelected:function(e){var t="Sólo puede seleccionar "+e.maximum+" elemento";return e.maximum!=1&&(t+="s"),t},noResults:function(){return"No se encontraron resultados"},searching:function(){return"Buscando…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/et.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/et.js new file mode 100644 index 000000000000..c70f4a5b312d --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/et.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/et",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Sisesta "+t+" täht";return t!=1&&(n+="e"),n+=" vähem",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Sisesta "+t+" täht";return t!=1&&(n+="e"),n+=" rohkem",n},loadingMore:function(){return"Laen tulemusi…"},maximumSelected:function(e){var t="Saad vaid "+e.maximum+" tulemus";return e.maximum==1?t+="e":t+="t",t+=" valida",t},noResults:function(){return"Tulemused puuduvad"},searching:function(){return"Otsin…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/eu.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/eu.js new file mode 100644 index 000000000000..9336053a7d1d --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/eu.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/eu",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Idatzi ";return t==1?n+="karaktere bat":n+=t+" karaktere",n+=" gutxiago",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Idatzi ";return t==1?n+="karaktere bat":n+=t+" karaktere",n+=" gehiago",n},loadingMore:function(){return"Emaitza gehiago kargatzen…"},maximumSelected:function(e){return e.maximum===1?"Elementu bakarra hauta dezakezu":e.maximum+" elementu hauta ditzakezu soilik"},noResults:function(){return"Ez da bat datorrenik aurkitu"},searching:function(){return"Bilatzen…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/fa.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/fa.js new file mode 100644 index 000000000000..5118cd28f83f --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/fa.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/fa",[],function(){return{errorLoading:function(){return"امکان بارگذاری نتایج وجود ندارد."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="لطفاً "+t+" کاراکتر را حذف نمایید";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="لطفاً تعداد "+t+" کاراکتر یا بیشتر وارد نمایید";return n},loadingMore:function(){return"در حال بارگذاری نتایج بیشتر..."},maximumSelected:function(e){var t="شما تنها می‌توانید "+e.maximum+" آیتم را انتخاب نمایید";return t},noResults:function(){return"هیچ نتیجه‌ای یافت نشد"},searching:function(){return"در حال جستجو..."}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/fi.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/fi.js new file mode 100644 index 000000000000..9e60f26a05e8 --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/fi.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/fi",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum;return"Ole hyvä ja anna "+t+" merkkiä vähemmän"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Ole hyvä ja anna "+t+" merkkiä lisää"},loadingMore:function(){return"Ladataan lisää tuloksia…"},maximumSelected:function(e){return"Voit valita ainoastaan "+e.maximum+" kpl"},noResults:function(){return"Ei tuloksia"},searching:function(){}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/fr.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/fr.js new file mode 100644 index 000000000000..e4a665009769 --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/fr.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/fr",[],function(){return{errorLoading:function(){return"Les résultats ne peuvent pas être chargés."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Supprimez "+t+" caractère";return t!==1&&(n+="s"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Saisissez "+t+" caractère";return t!==1&&(n+="s"),n},loadingMore:function(){return"Chargement de résultats supplémentaires…"},maximumSelected:function(e){var t="Vous pouvez seulement sélectionner "+e.maximum+" élément";return e.maximum!==1&&(t+="s"),t},noResults:function(){return"Aucun résultat trouvé"},searching:function(){return"Recherche en cours…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/gl.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/gl.js new file mode 100644 index 000000000000..02f258f9289a --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/gl.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/gl",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Elimine ";return t===1?n+="un carácter":n+=t+" caracteres",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Engada ";return t===1?n+="un carácter":n+=t+" caracteres",n},loadingMore:function(){return"Cargando máis resultados…"},maximumSelected:function(e){var t="Só pode ";return e.maximum===1?t+="un elemento":t+=e.maximum+" elementos",t},noResults:function(){return"Non se atoparon resultados"},searching:function(){return"Buscando…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/he.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/he.js new file mode 100644 index 000000000000..881f8d389f4f --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/he.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/he",[],function(){return{errorLoading:function(){return"שגיאה בטעינת התוצאות"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="נא למחוק ";return t===1?n+="תו אחד":n+=t+" תווים",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="נא להכניס ";return t===1?n+="תו אחד":n+=t+" תווים",n+=" או יותר",n},loadingMore:function(){return"טוען תוצאות נוספות…"},maximumSelected:function(e){var t="באפשרותך לבחור עד ";return e.maximum===1?t+="פריט אחד":t+=e.maximum+" פריטים",t},noResults:function(){return"לא נמצאו תוצאות"},searching:function(){return"מחפש…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/hi.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/hi.js new file mode 100644 index 000000000000..e82968426890 --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/hi.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hi",[],function(){return{errorLoading:function(){return"परिणामों को लोड नहीं किया जा सका।"},inputTooLong:function(e){var t=e.input.length-e.maximum,n=t+" अक्षर को हटा दें";return t>1&&(n=t+" अक्षरों को हटा दें "),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="कृपया "+t+" या अधिक अक्षर दर्ज करें";return n},loadingMore:function(){return"अधिक परिणाम लोड हो रहे है..."},maximumSelected:function(e){var t="आप केवल "+e.maximum+" आइटम का चयन कर सकते हैं";return t},noResults:function(){return"कोई परिणाम नहीं मिला"},searching:function(){return"खोज रहा है..."}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/hr.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/hr.js new file mode 100644 index 000000000000..89f7b12bff38 --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/hr.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hr",[],function(){function e(e){var t=" "+e+" znak";return e%10<5&&e%10>0&&(e%100<5||e%100>19)?e%10>1&&(t+="a"):t+="ova",t}return{errorLoading:function(){return"Preuzimanje nije uspjelo."},inputTooLong:function(t){var n=t.input.length-t.maximum;return"Unesite "+e(n)},inputTooShort:function(t){var n=t.minimum-t.input.length;return"Unesite još "+e(n)},loadingMore:function(){return"Učitavanje rezultata…"},maximumSelected:function(e){return"Maksimalan broj odabranih stavki je "+e.maximum},noResults:function(){return"Nema rezultata"},searching:function(){return"Pretraga…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/hu.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/hu.js new file mode 100644 index 000000000000..74c8a90dec4a --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/hu.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hu",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum;return"Túl hosszú. "+t+" karakterrel több, mint kellene."},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Túl rövid. Még "+t+" karakter hiányzik."},loadingMore:function(){return"Töltés…"},maximumSelected:function(e){return"Csak "+e.maximum+" elemet lehet kiválasztani."},noResults:function(){return"Nincs találat."},searching:function(){return"Keresés…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/id.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/id.js new file mode 100644 index 000000000000..9586782618ca --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/id.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/id",[],function(){return{errorLoading:function(){return"Data tidak boleh diambil."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Hapuskan "+t+" huruf"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Masukkan "+t+" huruf lagi"},loadingMore:function(){return"Mengambil data…"},maximumSelected:function(e){return"Anda hanya dapat memilih "+e.maximum+" pilihan"},noResults:function(){return"Tidak ada data yang sesuai"},searching:function(){return"Mencari…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/is.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/is.js new file mode 100644 index 000000000000..ab97a14d186c --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/is.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/is",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vinsamlegast styttið texta um "+t+" staf";return t<=1?n:n+"i"},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vinsamlegast skrifið "+t+" staf";return t>1&&(n+="i"),n+=" í viðbót",n},loadingMore:function(){return"Sæki fleiri niðurstöður…"},maximumSelected:function(e){return"Þú getur aðeins valið "+e.maximum+" atriði"},noResults:function(){return"Ekkert fannst"},searching:function(){return"Leita…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/it.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/it.js new file mode 100644 index 000000000000..7796b9f76a40 --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/it.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/it",[],function(){return{errorLoading:function(){return"I risultati non possono essere caricati."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Per favore cancella "+t+" caratter";return t!==1?n+="i":n+="e",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Per favore inserisci "+t+" o più caratteri";return n},loadingMore:function(){return"Caricando più risultati…"},maximumSelected:function(e){var t="Puoi selezionare solo "+e.maximum+" element";return e.maximum!==1?t+="i":t+="o",t},noResults:function(){return"Nessun risultato trovato"},searching:function(){return"Sto cercando…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/ja.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/ja.js new file mode 100644 index 000000000000..9f4fff6cb2ea --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/ja.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ja",[],function(){return{errorLoading:function(){return"結果が読み込まれませんでした"},inputTooLong:function(e){var t=e.input.length-e.maximum,n=t+" 文字を削除してください";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="少なくとも "+t+" 文字を入力してください";return n},loadingMore:function(){return"読み込み中…"},maximumSelected:function(e){var t=e.maximum+" 件しか選択できません";return t},noResults:function(){return"対象が見つかりません"},searching:function(){return"検索しています…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/km.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/km.js new file mode 100644 index 000000000000..8e94adcf3afc --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/km.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/km",[],function(){return{errorLoading:function(){return"មិនអាចទាញយកទិន្នន័យ"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="សូមលុបចេញ "+t+" អក្សរ";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="សូមបញ្ចូល"+t+" អក្សរ រឺ ច្រើនជាងនេះ";return n},loadingMore:function(){return"កំពុងទាញយកទិន្នន័យបន្ថែម..."},maximumSelected:function(e){var t="អ្នកអាចជ្រើសរើសបានតែ "+e.maximum+" ជម្រើសប៉ុណ្ណោះ";return t},noResults:function(){return"មិនមានលទ្ធផល"},searching:function(){return"កំពុងស្វែងរក..."}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/ko.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/ko.js new file mode 100644 index 000000000000..4ed03215fc52 --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/ko.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ko",[],function(){return{errorLoading:function(){return"결과를 불러올 수 없습니다."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="너무 깁니다. "+t+" 글자 지워주세요.";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="너무 짧습니다. "+t+" 글자 더 입력해주세요.";return n},loadingMore:function(){return"불러오는 중…"},maximumSelected:function(e){var t="최대 "+e.maximum+"개까지만 선택 가능합니다.";return t},noResults:function(){return"결과가 없습니다."},searching:function(){return"검색 중…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/lt.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/lt.js new file mode 100644 index 000000000000..05f3a6e5eb18 --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/lt.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/lt",[],function(){function e(e,t,n,r){return e%10===1&&(e%100<11||e%100>19)?t:e%10>=2&&e%10<=9&&(e%100<11||e%100>19)?n:r}return{inputTooLong:function(t){var n=t.input.length-t.maximum,r="Pašalinkite "+n+" simbol";return r+=e(n,"į","ius","ių"),r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Įrašykite dar "+n+" simbol";return r+=e(n,"į","ius","ių"),r},loadingMore:function(){return"Kraunama daugiau rezultatų…"},maximumSelected:function(t){var n="Jūs galite pasirinkti tik "+t.maximum+" element";return n+=e(t.maximum,"ą","us","ų"),n},noResults:function(){return"Atitikmenų nerasta"},searching:function(){return"Ieškoma…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/lv.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/lv.js new file mode 100644 index 000000000000..df8ee94232da --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/lv.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/lv",[],function(){function e(e,t,n,r){return e===11?t:e%10===1?n:r}return{inputTooLong:function(t){var n=t.input.length-t.maximum,r="Lūdzu ievadiet par "+n;return r+=" simbol"+e(n,"iem","u","iem"),r+" mazāk"},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Lūdzu ievadiet vēl "+n;return r+=" simbol"+e(n,"us","u","us"),r},loadingMore:function(){return"Datu ielāde…"},maximumSelected:function(t){var n="Jūs varat izvēlēties ne vairāk kā "+t.maximum;return n+=" element"+e(t.maximum,"us","u","us"),n},noResults:function(){return"Sakritību nav"},searching:function(){return"Meklēšana…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/mk.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/mk.js new file mode 100644 index 000000000000..319ecca14b22 --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/mk.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/mk",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Ве молиме внесете "+e.maximum+" помалку карактер";return e.maximum!==1&&(n+="и"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Ве молиме внесете уште "+e.maximum+" карактер";return e.maximum!==1&&(n+="и"),n},loadingMore:function(){return"Вчитување резултати…"},maximumSelected:function(e){var t="Можете да изберете само "+e.maximum+" ставк";return e.maximum===1?t+="а":t+="и",t},noResults:function(){return"Нема пронајдено совпаѓања"},searching:function(){return"Пребарување…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/ms.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/ms.js new file mode 100644 index 000000000000..4258f125b59c --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/ms.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ms",[],function(){return{errorLoading:function(){return"Keputusan tidak berjaya dimuatkan."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Sila hapuskan "+t+" aksara"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Sila masukkan "+t+" atau lebih aksara"},loadingMore:function(){return"Sedang memuatkan keputusan…"},maximumSelected:function(e){return"Anda hanya boleh memilih "+e.maximum+" pilihan"},noResults:function(){return"Tiada padanan yang ditemui"},searching:function(){return"Mencari…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/nb.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/nb.js new file mode 100644 index 000000000000..6770087ceea4 --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/nb.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/nb",[],function(){return{errorLoading:function(){return"Kunne ikke hente resultater."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Vennligst fjern "+t+" tegn"},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vennligst skriv inn ";return t>1?n+=" flere tegn":n+=" tegn til",n},loadingMore:function(){return"Laster flere resultater…"},maximumSelected:function(e){return"Du kan velge maks "+e.maximum+" elementer"},noResults:function(){return"Ingen treff"},searching:function(){return"Søker…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/nl.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/nl.js new file mode 100644 index 000000000000..8bd5e3cf4339 --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/nl.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/nl",[],function(){return{errorLoading:function(){return"De resultaten konden niet worden geladen."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Gelieve "+t+" karakters te verwijderen";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Gelieve "+t+" of meer karakters in te voeren";return n},loadingMore:function(){return"Meer resultaten laden…"},maximumSelected:function(e){var t=e.maximum==1?"kan":"kunnen",n="Er "+t+" maar "+e.maximum+" item";return e.maximum!=1&&(n+="s"),n+=" worden geselecteerd",n},noResults:function(){return"Geen resultaten gevonden…"},searching:function(){return"Zoeken…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/pl.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/pl.js new file mode 100644 index 000000000000..54ba28e9ba1a --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/pl.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/pl",[],function(){var e=["znak","znaki","znaków"],t=["element","elementy","elementów"],n=function(t,n){if(t===1)return n[0];if(t>1&&t<=4)return n[1];if(t>=5)return n[2]};return{errorLoading:function(){return"Nie można załadować wyników."},inputTooLong:function(t){var r=t.input.length-t.maximum;return"Usuń "+r+" "+n(r,e)},inputTooShort:function(t){var r=t.minimum-t.input.length;return"Podaj przynajmniej "+r+" "+n(r,e)},loadingMore:function(){return"Trwa ładowanie…"},maximumSelected:function(e){return"Możesz zaznaczyć tylko "+e.maximum+" "+n(e.maximum,t)},noResults:function(){return"Brak wyników"},searching:function(){return"Trwa wyszukiwanie…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/pt-BR.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/pt-BR.js new file mode 100644 index 000000000000..a6629c8aec0d --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/pt-BR.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/pt-BR",[],function(){return{errorLoading:function(){return"Os resultados não puderam ser carregados."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Apague "+t+" caracter";return t!=1&&(n+="es"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Digite "+t+" ou mais caracteres";return n},loadingMore:function(){return"Carregando mais resultados…"},maximumSelected:function(e){var t="Você só pode selecionar "+e.maximum+" ite";return e.maximum==1?t+="m":t+="ns",t},noResults:function(){return"Nenhum resultado encontrado"},searching:function(){return"Buscando…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/pt.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/pt.js new file mode 100644 index 000000000000..0cbda561b959 --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/pt.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/pt",[],function(){return{errorLoading:function(){return"Os resultados não puderam ser carregados."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Por favor apague "+t+" ";return n+=t!=1?"caracteres":"carácter",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Introduza "+t+" ou mais caracteres";return n},loadingMore:function(){return"A carregar mais resultados…"},maximumSelected:function(e){var t="Apenas pode seleccionar "+e.maximum+" ";return t+=e.maximum!=1?"itens":"item",t},noResults:function(){return"Sem resultados"},searching:function(){return"A procurar…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/ro.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/ro.js new file mode 100644 index 000000000000..788a26376ad5 --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/ro.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ro",[],function(){return{errorLoading:function(){return"Rezultatele nu au putut fi incărcate."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vă rugăm să ștergeți"+t+" caracter";return t!==1&&(n+="e"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vă rugăm să introduceți "+t+"sau mai multe caractere";return n},loadingMore:function(){return"Se încarcă mai multe rezultate…"},maximumSelected:function(e){var t="Aveți voie să selectați cel mult "+e.maximum;return t+=" element",e.maximum!==1&&(t+="e"),t},noResults:function(){return"Nu au fost găsite rezultate"},searching:function(){return"Căutare…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/ru.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/ru.js new file mode 100644 index 000000000000..9ecab80911b7 --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/ru.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ru",[],function(){function e(e,t,n,r){return e%10<5&&e%10>0&&e%100<5||e%100>20?e%10>1?n:t:r}return{errorLoading:function(){return"Невозможно загрузить результаты"},inputTooLong:function(t){var n=t.input.length-t.maximum,r="Пожалуйста, введите на "+n+" символ";return r+=e(n,"","a","ов"),r+=" меньше",r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Пожалуйста, введите еще хотя бы "+n+" символ";return r+=e(n,"","a","ов"),r},loadingMore:function(){return"Загрузка данных…"},maximumSelected:function(t){var n="Вы можете выбрать не более "+t.maximum+" элемент";return n+=e(t.maximum,"","a","ов"),n},noResults:function(){return"Совпадений не найдено"},searching:function(){return"Поиск…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/sk.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/sk.js new file mode 100644 index 000000000000..82f294138a8d --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/sk.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sk",[],function(){var e={2:function(e){return e?"dva":"dve"},3:function(){return"tri"},4:function(){return"štyri"}};return{inputTooLong:function(t){var n=t.input.length-t.maximum;return n==1?"Prosím, zadajte o jeden znak menej":n>=2&&n<=4?"Prosím, zadajte o "+e[n](!0)+" znaky menej":"Prosím, zadajte o "+n+" znakov menej"},inputTooShort:function(t){var n=t.minimum-t.input.length;return n==1?"Prosím, zadajte ešte jeden znak":n<=4?"Prosím, zadajte ešte ďalšie "+e[n](!0)+" znaky":"Prosím, zadajte ešte ďalších "+n+" znakov"},loadingMore:function(){return"Loading more results…"},maximumSelected:function(t){return t.maximum==1?"Môžete zvoliť len jednu položku":t.maximum>=2&&t.maximum<=4?"Môžete zvoliť najviac "+e[t.maximum](!1)+" položky":"Môžete zvoliť najviac "+t.maximum+" položiek"},noResults:function(){return"Nenašli sa žiadne položky"},searching:function(){return"Vyhľadávanie…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/sr-Cyrl.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/sr-Cyrl.js new file mode 100644 index 000000000000..e9453940cc2c --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/sr-Cyrl.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sr-Cyrl",[],function(){function e(e,t,n,r){return e%10==1&&e%100!=11?t:e%10>=2&&e%10<=4&&(e%100<12||e%100>14)?n:r}return{errorLoading:function(){return"Преузимање није успело."},inputTooLong:function(t){var n=t.input.length-t.maximum,r="Обришите "+n+" симбол";return r+=e(n,"","а","а"),r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Укуцајте бар још "+n+" симбол";return r+=e(n,"","а","а"),r},loadingMore:function(){return"Преузимање још резултата…"},maximumSelected:function(t){var n="Можете изабрати само "+t.maximum+" ставк";return n+=e(t.maximum,"у","е","и"),n},noResults:function(){return"Ништа није пронађено"},searching:function(){return"Претрага…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/sr.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/sr.js new file mode 100644 index 000000000000..ac0cc721fd7f --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/sr.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sr",[],function(){function e(e,t,n,r){return e%10==1&&e%100!=11?t:e%10>=2&&e%10<=4&&(e%100<12||e%100>14)?n:r}return{errorLoading:function(){return"Preuzimanje nije uspelo."},inputTooLong:function(t){var n=t.input.length-t.maximum,r="Obrišite "+n+" simbol";return r+=e(n,"","a","a"),r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Ukucajte bar još "+n+" simbol";return r+=e(n,"","a","a"),r},loadingMore:function(){return"Preuzimanje još rezultata…"},maximumSelected:function(t){var n="Možete izabrati samo "+t.maximum+" stavk";return n+=e(t.maximum,"u","e","i"),n},noResults:function(){return"Ništa nije pronađeno"},searching:function(){return"Pretraga…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/sv.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/sv.js new file mode 100644 index 000000000000..bedac08c47c3 --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/sv.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sv",[],function(){return{errorLoading:function(){return"Resultat kunde inte laddas."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vänligen sudda ut "+t+" tecken";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vänligen skriv in "+t+" eller fler tecken";return n},loadingMore:function(){return"Laddar fler resultat…"},maximumSelected:function(e){var t="Du kan max välja "+e.maximum+" element";return t},noResults:function(){return"Inga träffar"},searching:function(){return"Söker…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/th.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/th.js new file mode 100644 index 000000000000..097a86c699a4 --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/th.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/th",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="โปรดลบออก "+t+" ตัวอักษร";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="โปรดพิมพ์เพิ่มอีก "+t+" ตัวอักษร";return n},loadingMore:function(){return"กำลังค้นข้อมูลเพิ่ม…"},maximumSelected:function(e){var t="คุณสามารถเลือกได้ไม่เกิน "+e.maximum+" รายการ";return t},noResults:function(){return"ไม่พบข้อมูล"},searching:function(){return"กำลังค้นข้อมูล…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/tr.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/tr.js new file mode 100644 index 000000000000..25d27a877f8b --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/tr.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/tr",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n=t+" karakter daha girmelisiniz";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="En az "+t+" karakter daha girmelisiniz";return n},loadingMore:function(){return"Daha fazla…"},maximumSelected:function(e){var t="Sadece "+e.maximum+" seçim yapabilirsiniz";return t},noResults:function(){return"Sonuç bulunamadı"},searching:function(){return"Aranıyor…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/uk.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/uk.js new file mode 100644 index 000000000000..eb3ca890319a --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/uk.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/uk",[],function(){function e(e,t,n,r){return e%100>10&&e%100<15?r:e%10===1?t:e%10>1&&e%10<5?n:r}return{errorLoading:function(){return"Неможливо завантажити результати"},inputTooLong:function(t){var n=t.input.length-t.maximum;return"Будь ласка, видаліть "+n+" "+e(t.maximum,"літеру","літери","літер")},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Будь ласка, введіть "+t+" або більше літер"},loadingMore:function(){return"Завантаження інших результатів…"},maximumSelected:function(t){return"Ви можете вибрати лише "+t.maximum+" "+e(t.maximum,"пункт","пункти","пунктів")},noResults:function(){return"Нічого не знайдено"},searching:function(){return"Пошук…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/vi.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/vi.js new file mode 100644 index 000000000000..8975b8ac6e74 --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/vi.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/vi",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vui lòng nhập ít hơn "+t+" ký tự";return t!=1&&(n+="s"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vui lòng nhập nhiều hơn "+t+' ký tự"';return n},loadingMore:function(){return"Đang lấy thêm kết quả…"},maximumSelected:function(e){var t="Chỉ có thể chọn được "+e.maximum+" lựa chọn";return t},noResults:function(){return"Không tìm thấy kết quả"},searching:function(){return"Đang tìm…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/zh-CN.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/zh-CN.js new file mode 100644 index 000000000000..2ed959723d87 --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/zh-CN.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/zh-CN",[],function(){return{errorLoading:function(){return"无法载入结果。"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="请删除"+t+"个字符";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="请再输入至少"+t+"个字符";return n},loadingMore:function(){return"载入更多结果…"},maximumSelected:function(e){var t="最多只能选择"+e.maximum+"个项目";return t},noResults:function(){return"未找到结果"},searching:function(){return"搜索中…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/i18n/zh-TW.js b/django/contrib/admin/static/admin/js/vendor/select2/i18n/zh-TW.js new file mode 100644 index 000000000000..ea0812ee0dbc --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/i18n/zh-TW.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ + +(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/zh-TW",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="請刪掉"+t+"個字元";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="請再輸入"+t+"個字元";return n},loadingMore:function(){return"載入中…"},maximumSelected:function(e){var t="你只能選擇最多"+e.maximum+"項";return t},noResults:function(){return"沒有找到相符的項目"},searching:function(){return"搜尋中…"}}}),{define:e.define,require:e.require}})(); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/select2/select2.full.js b/django/contrib/admin/static/admin/js/vendor/select2/select2.full.js new file mode 100644 index 000000000000..e750834ef5d2 --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/select2.full.js @@ -0,0 +1,6436 @@ +/*! + * Select2 4.0.3 + * https://select2.github.io + * + * Released under the MIT license + * https://github.com/select2/select2/blob/master/LICENSE.md + */ +(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS + factory(require('jquery')); + } else { + // Browser globals + factory(jQuery); + } +}(function (jQuery) { + // This is needed so we can catch the AMD loader configuration and use it + // The inner file should be wrapped (by `banner.start.js`) in a function that + // returns the AMD loader references. + var S2 = +(function () { + // Restore the Select2 AMD loader so it can be used + // Needed mostly in the language files, where the loader is not inserted + if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) { + var S2 = jQuery.fn.select2.amd; + } +var S2;(function () { if (!S2 || !S2.requirejs) { +if (!S2) { S2 = {}; } else { require = S2; } +/** + * @license almond 0.3.1 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved. + * Available via the MIT or new BSD license. + * see: http://github.com/jrburke/almond for details + */ +//Going sloppy to avoid 'use strict' string cost, but strict practices should +//be followed. +/*jslint sloppy: true */ +/*global setTimeout: false */ + +var requirejs, require, define; +(function (undef) { + var main, req, makeMap, handlers, + defined = {}, + waiting = {}, + config = {}, + defining = {}, + hasOwn = Object.prototype.hasOwnProperty, + aps = [].slice, + jsSuffixRegExp = /\.js$/; + + function hasProp(obj, prop) { + return hasOwn.call(obj, prop); + } + + /** + * Given a relative module name, like ./something, normalize it to + * a real name that can be mapped to a path. + * @param {String} name the relative name + * @param {String} baseName a real name that the name arg is relative + * to. + * @returns {String} normalized name + */ + function normalize(name, baseName) { + var nameParts, nameSegment, mapValue, foundMap, lastIndex, + foundI, foundStarMap, starI, i, j, part, + baseParts = baseName && baseName.split("/"), + map = config.map, + starMap = (map && map['*']) || {}; + + //Adjust any relative paths. + if (name && name.charAt(0) === ".") { + //If have a base name, try to normalize against it, + //otherwise, assume it is a top-level require that will + //be relative to baseUrl in the end. + if (baseName) { + name = name.split('/'); + lastIndex = name.length - 1; + + // Node .js allowance: + if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { + name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); + } + + //Lop off the last part of baseParts, so that . matches the + //"directory" and not name of the baseName's module. For instance, + //baseName of "one/two/three", maps to "one/two/three.js", but we + //want the directory, "one/two" for this normalization. + name = baseParts.slice(0, baseParts.length - 1).concat(name); + + //start trimDots + for (i = 0; i < name.length; i += 1) { + part = name[i]; + if (part === ".") { + name.splice(i, 1); + i -= 1; + } else if (part === "..") { + if (i === 1 && (name[2] === '..' || name[0] === '..')) { + //End of the line. Keep at least one non-dot + //path segment at the front so it can be mapped + //correctly to disk. Otherwise, there is likely + //no path mapping for a path starting with '..'. + //This can still fail, but catches the most reasonable + //uses of .. + break; + } else if (i > 0) { + name.splice(i - 1, 2); + i -= 2; + } + } + } + //end trimDots + + name = name.join("/"); + } else if (name.indexOf('./') === 0) { + // No baseName, so this is ID is resolved relative + // to baseUrl, pull off the leading dot. + name = name.substring(2); + } + } + + //Apply map config if available. + if ((baseParts || starMap) && map) { + nameParts = name.split('/'); + + for (i = nameParts.length; i > 0; i -= 1) { + nameSegment = nameParts.slice(0, i).join("/"); + + if (baseParts) { + //Find the longest baseName segment match in the config. + //So, do joins on the biggest to smallest lengths of baseParts. + for (j = baseParts.length; j > 0; j -= 1) { + mapValue = map[baseParts.slice(0, j).join('/')]; + + //baseName segment has config, find if it has one for + //this name. + if (mapValue) { + mapValue = mapValue[nameSegment]; + if (mapValue) { + //Match, update name to the new value. + foundMap = mapValue; + foundI = i; + break; + } + } + } + } + + if (foundMap) { + break; + } + + //Check for a star map match, but just hold on to it, + //if there is a shorter segment match later in a matching + //config, then favor over this star map. + if (!foundStarMap && starMap && starMap[nameSegment]) { + foundStarMap = starMap[nameSegment]; + starI = i; + } + } + + if (!foundMap && foundStarMap) { + foundMap = foundStarMap; + foundI = starI; + } + + if (foundMap) { + nameParts.splice(0, foundI, foundMap); + name = nameParts.join('/'); + } + } + + return name; + } + + function makeRequire(relName, forceSync) { + return function () { + //A version of a require function that passes a moduleName + //value for items that may need to + //look up paths relative to the moduleName + var args = aps.call(arguments, 0); + + //If first arg is not require('string'), and there is only + //one arg, it is the array form without a callback. Insert + //a null so that the following concat is correct. + if (typeof args[0] !== 'string' && args.length === 1) { + args.push(null); + } + return req.apply(undef, args.concat([relName, forceSync])); + }; + } + + function makeNormalize(relName) { + return function (name) { + return normalize(name, relName); + }; + } + + function makeLoad(depName) { + return function (value) { + defined[depName] = value; + }; + } + + function callDep(name) { + if (hasProp(waiting, name)) { + var args = waiting[name]; + delete waiting[name]; + defining[name] = true; + main.apply(undef, args); + } + + if (!hasProp(defined, name) && !hasProp(defining, name)) { + throw new Error('No ' + name); + } + return defined[name]; + } + + //Turns a plugin!resource to [plugin, resource] + //with the plugin being undefined if the name + //did not have a plugin prefix. + function splitPrefix(name) { + var prefix, + index = name ? name.indexOf('!') : -1; + if (index > -1) { + prefix = name.substring(0, index); + name = name.substring(index + 1, name.length); + } + return [prefix, name]; + } + + /** + * Makes a name map, normalizing the name, and using a plugin + * for normalization if necessary. Grabs a ref to plugin + * too, as an optimization. + */ + makeMap = function (name, relName) { + var plugin, + parts = splitPrefix(name), + prefix = parts[0]; + + name = parts[1]; + + if (prefix) { + prefix = normalize(prefix, relName); + plugin = callDep(prefix); + } + + //Normalize according + if (prefix) { + if (plugin && plugin.normalize) { + name = plugin.normalize(name, makeNormalize(relName)); + } else { + name = normalize(name, relName); + } + } else { + name = normalize(name, relName); + parts = splitPrefix(name); + prefix = parts[0]; + name = parts[1]; + if (prefix) { + plugin = callDep(prefix); + } + } + + //Using ridiculous property names for space reasons + return { + f: prefix ? prefix + '!' + name : name, //fullName + n: name, + pr: prefix, + p: plugin + }; + }; + + function makeConfig(name) { + return function () { + return (config && config.config && config.config[name]) || {}; + }; + } + + handlers = { + require: function (name) { + return makeRequire(name); + }, + exports: function (name) { + var e = defined[name]; + if (typeof e !== 'undefined') { + return e; + } else { + return (defined[name] = {}); + } + }, + module: function (name) { + return { + id: name, + uri: '', + exports: defined[name], + config: makeConfig(name) + }; + } + }; + + main = function (name, deps, callback, relName) { + var cjsModule, depName, ret, map, i, + args = [], + callbackType = typeof callback, + usingExports; + + //Use name if no relName + relName = relName || name; + + //Call the callback to define the module, if necessary. + if (callbackType === 'undefined' || callbackType === 'function') { + //Pull out the defined dependencies and pass the ordered + //values to the callback. + //Default to [require, exports, module] if no deps + deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; + for (i = 0; i < deps.length; i += 1) { + map = makeMap(deps[i], relName); + depName = map.f; + + //Fast path CommonJS standard dependencies. + if (depName === "require") { + args[i] = handlers.require(name); + } else if (depName === "exports") { + //CommonJS module spec 1.1 + args[i] = handlers.exports(name); + usingExports = true; + } else if (depName === "module") { + //CommonJS module spec 1.1 + cjsModule = args[i] = handlers.module(name); + } else if (hasProp(defined, depName) || + hasProp(waiting, depName) || + hasProp(defining, depName)) { + args[i] = callDep(depName); + } else if (map.p) { + map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {}); + args[i] = defined[depName]; + } else { + throw new Error(name + ' missing ' + depName); + } + } + + ret = callback ? callback.apply(defined[name], args) : undefined; + + if (name) { + //If setting exports via "module" is in play, + //favor that over return value and exports. After that, + //favor a non-undefined return value over exports use. + if (cjsModule && cjsModule.exports !== undef && + cjsModule.exports !== defined[name]) { + defined[name] = cjsModule.exports; + } else if (ret !== undef || !usingExports) { + //Use the return value from the function. + defined[name] = ret; + } + } + } else if (name) { + //May just be an object definition for the module. Only + //worry about defining if have a module name. + defined[name] = callback; + } + }; + + requirejs = require = req = function (deps, callback, relName, forceSync, alt) { + if (typeof deps === "string") { + if (handlers[deps]) { + //callback in this case is really relName + return handlers[deps](callback); + } + //Just return the module wanted. In this scenario, the + //deps arg is the module name, and second arg (if passed) + //is just the relName. + //Normalize module name, if it contains . or .. + return callDep(makeMap(deps, callback).f); + } else if (!deps.splice) { + //deps is a config object, not an array. + config = deps; + if (config.deps) { + req(config.deps, config.callback); + } + if (!callback) { + return; + } + + if (callback.splice) { + //callback is an array, which means it is a dependency list. + //Adjust args if there are dependencies + deps = callback; + callback = relName; + relName = null; + } else { + deps = undef; + } + } + + //Support require(['a']) + callback = callback || function () {}; + + //If relName is a function, it is an errback handler, + //so remove it. + if (typeof relName === 'function') { + relName = forceSync; + forceSync = alt; + } + + //Simulate async callback; + if (forceSync) { + main(undef, deps, callback, relName); + } else { + //Using a non-zero value because of concern for what old browsers + //do, and latest browsers "upgrade" to 4 if lower value is used: + //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout: + //If want a value immediately, use require('id') instead -- something + //that works in almond on the global level, but not guaranteed and + //unlikely to work in other AMD implementations. + setTimeout(function () { + main(undef, deps, callback, relName); + }, 4); + } + + return req; + }; + + /** + * Just drops the config on the floor, but returns req in case + * the config return value is used. + */ + req.config = function (cfg) { + return req(cfg); + }; + + /** + * Expose module registry for debugging and tooling + */ + requirejs._defined = defined; + + define = function (name, deps, callback) { + if (typeof name !== 'string') { + throw new Error('See almond README: incorrect module build, no module name'); + } + + //This module may not have dependencies + if (!deps.splice) { + //deps is not an array, so probably means + //an object literal or factory function for + //the value. Adjust args. + callback = deps; + deps = []; + } + + if (!hasProp(defined, name) && !hasProp(waiting, name)) { + waiting[name] = [name, deps, callback]; + } + }; + + define.amd = { + jQuery: true + }; +}()); + +S2.requirejs = requirejs;S2.require = require;S2.define = define; +} +}()); +S2.define("almond", function(){}); + +/* global jQuery:false, $:false */ +S2.define('jquery',[],function () { + var _$ = jQuery || $; + + if (_$ == null && console && console.error) { + console.error( + 'Select2: An instance of jQuery or a jQuery-compatible library was not ' + + 'found. Make sure that you are including jQuery before Select2 on your ' + + 'web page.' + ); + } + + return _$; +}); + +S2.define('select2/utils',[ + 'jquery' +], function ($) { + var Utils = {}; + + Utils.Extend = function (ChildClass, SuperClass) { + var __hasProp = {}.hasOwnProperty; + + function BaseConstructor () { + this.constructor = ChildClass; + } + + for (var key in SuperClass) { + if (__hasProp.call(SuperClass, key)) { + ChildClass[key] = SuperClass[key]; + } + } + + BaseConstructor.prototype = SuperClass.prototype; + ChildClass.prototype = new BaseConstructor(); + ChildClass.__super__ = SuperClass.prototype; + + return ChildClass; + }; + + function getMethods (theClass) { + var proto = theClass.prototype; + + var methods = []; + + for (var methodName in proto) { + var m = proto[methodName]; + + if (typeof m !== 'function') { + continue; + } + + if (methodName === 'constructor') { + continue; + } + + methods.push(methodName); + } + + return methods; + } + + Utils.Decorate = function (SuperClass, DecoratorClass) { + var decoratedMethods = getMethods(DecoratorClass); + var superMethods = getMethods(SuperClass); + + function DecoratedClass () { + var unshift = Array.prototype.unshift; + + var argCount = DecoratorClass.prototype.constructor.length; + + var calledConstructor = SuperClass.prototype.constructor; + + if (argCount > 0) { + unshift.call(arguments, SuperClass.prototype.constructor); + + calledConstructor = DecoratorClass.prototype.constructor; + } + + calledConstructor.apply(this, arguments); + } + + DecoratorClass.displayName = SuperClass.displayName; + + function ctr () { + this.constructor = DecoratedClass; + } + + DecoratedClass.prototype = new ctr(); + + for (var m = 0; m < superMethods.length; m++) { + var superMethod = superMethods[m]; + + DecoratedClass.prototype[superMethod] = + SuperClass.prototype[superMethod]; + } + + var calledMethod = function (methodName) { + // Stub out the original method if it's not decorating an actual method + var originalMethod = function () {}; + + if (methodName in DecoratedClass.prototype) { + originalMethod = DecoratedClass.prototype[methodName]; + } + + var decoratedMethod = DecoratorClass.prototype[methodName]; + + return function () { + var unshift = Array.prototype.unshift; + + unshift.call(arguments, originalMethod); + + return decoratedMethod.apply(this, arguments); + }; + }; + + for (var d = 0; d < decoratedMethods.length; d++) { + var decoratedMethod = decoratedMethods[d]; + + DecoratedClass.prototype[decoratedMethod] = calledMethod(decoratedMethod); + } + + return DecoratedClass; + }; + + var Observable = function () { + this.listeners = {}; + }; + + Observable.prototype.on = function (event, callback) { + this.listeners = this.listeners || {}; + + if (event in this.listeners) { + this.listeners[event].push(callback); + } else { + this.listeners[event] = [callback]; + } + }; + + Observable.prototype.trigger = function (event) { + var slice = Array.prototype.slice; + var params = slice.call(arguments, 1); + + this.listeners = this.listeners || {}; + + // Params should always come in as an array + if (params == null) { + params = []; + } + + // If there are no arguments to the event, use a temporary object + if (params.length === 0) { + params.push({}); + } + + // Set the `_type` of the first object to the event + params[0]._type = event; + + if (event in this.listeners) { + this.invoke(this.listeners[event], slice.call(arguments, 1)); + } + + if ('*' in this.listeners) { + this.invoke(this.listeners['*'], arguments); + } + }; + + Observable.prototype.invoke = function (listeners, params) { + for (var i = 0, len = listeners.length; i < len; i++) { + listeners[i].apply(this, params); + } + }; + + Utils.Observable = Observable; + + Utils.generateChars = function (length) { + var chars = ''; + + for (var i = 0; i < length; i++) { + var randomChar = Math.floor(Math.random() * 36); + chars += randomChar.toString(36); + } + + return chars; + }; + + Utils.bind = function (func, context) { + return function () { + func.apply(context, arguments); + }; + }; + + Utils._convertData = function (data) { + for (var originalKey in data) { + var keys = originalKey.split('-'); + + var dataLevel = data; + + if (keys.length === 1) { + continue; + } + + for (var k = 0; k < keys.length; k++) { + var key = keys[k]; + + // Lowercase the first letter + // By default, dash-separated becomes camelCase + key = key.substring(0, 1).toLowerCase() + key.substring(1); + + if (!(key in dataLevel)) { + dataLevel[key] = {}; + } + + if (k == keys.length - 1) { + dataLevel[key] = data[originalKey]; + } + + dataLevel = dataLevel[key]; + } + + delete data[originalKey]; + } + + return data; + }; + + Utils.hasScroll = function (index, el) { + // Adapted from the function created by @ShadowScripter + // and adapted by @BillBarry on the Stack Exchange Code Review website. + // The original code can be found at + // http://codereview.stackexchange.com/q/13338 + // and was designed to be used with the Sizzle selector engine. + + var $el = $(el); + var overflowX = el.style.overflowX; + var overflowY = el.style.overflowY; + + //Check both x and y declarations + if (overflowX === overflowY && + (overflowY === 'hidden' || overflowY === 'visible')) { + return false; + } + + if (overflowX === 'scroll' || overflowY === 'scroll') { + return true; + } + + return ($el.innerHeight() < el.scrollHeight || + $el.innerWidth() < el.scrollWidth); + }; + + Utils.escapeMarkup = function (markup) { + var replaceMap = { + '\\': '\', + '&': '&', + '<': '<', + '>': '>', + '"': '"', + '\'': ''', + '/': '/' + }; + + // Do not try to escape the markup if it's not a string + if (typeof markup !== 'string') { + return markup; + } + + return String(markup).replace(/[&<>"'\/\\]/g, function (match) { + return replaceMap[match]; + }); + }; + + // Append an array of jQuery nodes to a given element. + Utils.appendMany = function ($element, $nodes) { + // jQuery 1.7.x does not support $.fn.append() with an array + // Fall back to a jQuery object collection using $.fn.add() + if ($.fn.jquery.substr(0, 3) === '1.7') { + var $jqNodes = $(); + + $.map($nodes, function (node) { + $jqNodes = $jqNodes.add(node); + }); + + $nodes = $jqNodes; + } + + $element.append($nodes); + }; + + return Utils; +}); + +S2.define('select2/results',[ + 'jquery', + './utils' +], function ($, Utils) { + function Results ($element, options, dataAdapter) { + this.$element = $element; + this.data = dataAdapter; + this.options = options; + + Results.__super__.constructor.call(this); + } + + Utils.Extend(Results, Utils.Observable); + + Results.prototype.render = function () { + var $results = $( + '<ul class="select2-results__options" role="tree"></ul>' + ); + + if (this.options.get('multiple')) { + $results.attr('aria-multiselectable', 'true'); + } + + this.$results = $results; + + return $results; + }; + + Results.prototype.clear = function () { + this.$results.empty(); + }; + + Results.prototype.displayMessage = function (params) { + var escapeMarkup = this.options.get('escapeMarkup'); + + this.clear(); + this.hideLoading(); + + var $message = $( + '<li role="treeitem" aria-live="assertive"' + + ' class="select2-results__option"></li>' + ); + + var message = this.options.get('translations').get(params.message); + + $message.append( + escapeMarkup( + message(params.args) + ) + ); + + $message[0].className += ' select2-results__message'; + + this.$results.append($message); + }; + + Results.prototype.hideMessages = function () { + this.$results.find('.select2-results__message').remove(); + }; + + Results.prototype.append = function (data) { + this.hideLoading(); + + var $options = []; + + if (data.results == null || data.results.length === 0) { + if (this.$results.children().length === 0) { + this.trigger('results:message', { + message: 'noResults' + }); + } + + return; + } + + data.results = this.sort(data.results); + + for (var d = 0; d < data.results.length; d++) { + var item = data.results[d]; + + var $option = this.option(item); + + $options.push($option); + } + + this.$results.append($options); + }; + + Results.prototype.position = function ($results, $dropdown) { + var $resultsContainer = $dropdown.find('.select2-results'); + $resultsContainer.append($results); + }; + + Results.prototype.sort = function (data) { + var sorter = this.options.get('sorter'); + + return sorter(data); + }; + + Results.prototype.highlightFirstItem = function () { + var $options = this.$results + .find('.select2-results__option[aria-selected]'); + + var $selected = $options.filter('[aria-selected=true]'); + + // Check if there are any selected options + if ($selected.length > 0) { + // If there are selected options, highlight the first + $selected.first().trigger('mouseenter'); + } else { + // If there are no selected options, highlight the first option + // in the dropdown + $options.first().trigger('mouseenter'); + } + + this.ensureHighlightVisible(); + }; + + Results.prototype.setClasses = function () { + var self = this; + + this.data.current(function (selected) { + var selectedIds = $.map(selected, function (s) { + return s.id.toString(); + }); + + var $options = self.$results + .find('.select2-results__option[aria-selected]'); + + $options.each(function () { + var $option = $(this); + + var item = $.data(this, 'data'); + + // id needs to be converted to a string when comparing + var id = '' + item.id; + + if ((item.element != null && item.element.selected) || + (item.element == null && $.inArray(id, selectedIds) > -1)) { + $option.attr('aria-selected', 'true'); + } else { + $option.attr('aria-selected', 'false'); + } + }); + + }); + }; + + Results.prototype.showLoading = function (params) { + this.hideLoading(); + + var loadingMore = this.options.get('translations').get('searching'); + + var loading = { + disabled: true, + loading: true, + text: loadingMore(params) + }; + var $loading = this.option(loading); + $loading.className += ' loading-results'; + + this.$results.prepend($loading); + }; + + Results.prototype.hideLoading = function () { + this.$results.find('.loading-results').remove(); + }; + + Results.prototype.option = function (data) { + var option = document.createElement('li'); + option.className = 'select2-results__option'; + + var attrs = { + 'role': 'treeitem', + 'aria-selected': 'false' + }; + + if (data.disabled) { + delete attrs['aria-selected']; + attrs['aria-disabled'] = 'true'; + } + + if (data.id == null) { + delete attrs['aria-selected']; + } + + if (data._resultId != null) { + option.id = data._resultId; + } + + if (data.title) { + option.title = data.title; + } + + if (data.children) { + attrs.role = 'group'; + attrs['aria-label'] = data.text; + delete attrs['aria-selected']; + } + + for (var attr in attrs) { + var val = attrs[attr]; + + option.setAttribute(attr, val); + } + + if (data.children) { + var $option = $(option); + + var label = document.createElement('strong'); + label.className = 'select2-results__group'; + + var $label = $(label); + this.template(data, label); + + var $children = []; + + for (var c = 0; c < data.children.length; c++) { + var child = data.children[c]; + + var $child = this.option(child); + + $children.push($child); + } + + var $childrenContainer = $('<ul></ul>', { + 'class': 'select2-results__options select2-results__options--nested' + }); + + $childrenContainer.append($children); + + $option.append(label); + $option.append($childrenContainer); + } else { + this.template(data, option); + } + + $.data(option, 'data', data); + + return option; + }; + + Results.prototype.bind = function (container, $container) { + var self = this; + + var id = container.id + '-results'; + + this.$results.attr('id', id); + + container.on('results:all', function (params) { + self.clear(); + self.append(params.data); + + if (container.isOpen()) { + self.setClasses(); + self.highlightFirstItem(); + } + }); + + container.on('results:append', function (params) { + self.append(params.data); + + if (container.isOpen()) { + self.setClasses(); + } + }); + + container.on('query', function (params) { + self.hideMessages(); + self.showLoading(params); + }); + + container.on('select', function () { + if (!container.isOpen()) { + return; + } + + self.setClasses(); + self.highlightFirstItem(); + }); + + container.on('unselect', function () { + if (!container.isOpen()) { + return; + } + + self.setClasses(); + self.highlightFirstItem(); + }); + + container.on('open', function () { + // When the dropdown is open, aria-expended="true" + self.$results.attr('aria-expanded', 'true'); + self.$results.attr('aria-hidden', 'false'); + + self.setClasses(); + self.ensureHighlightVisible(); + }); + + container.on('close', function () { + // When the dropdown is closed, aria-expended="false" + self.$results.attr('aria-expanded', 'false'); + self.$results.attr('aria-hidden', 'true'); + self.$results.removeAttr('aria-activedescendant'); + }); + + container.on('results:toggle', function () { + var $highlighted = self.getHighlightedResults(); + + if ($highlighted.length === 0) { + return; + } + + $highlighted.trigger('mouseup'); + }); + + container.on('results:select', function () { + var $highlighted = self.getHighlightedResults(); + + if ($highlighted.length === 0) { + return; + } + + var data = $highlighted.data('data'); + + if ($highlighted.attr('aria-selected') == 'true') { + self.trigger('close', {}); + } else { + self.trigger('select', { + data: data + }); + } + }); + + container.on('results:previous', function () { + var $highlighted = self.getHighlightedResults(); + + var $options = self.$results.find('[aria-selected]'); + + var currentIndex = $options.index($highlighted); + + // If we are already at te top, don't move further + if (currentIndex === 0) { + return; + } + + var nextIndex = currentIndex - 1; + + // If none are highlighted, highlight the first + if ($highlighted.length === 0) { + nextIndex = 0; + } + + var $next = $options.eq(nextIndex); + + $next.trigger('mouseenter'); + + var currentOffset = self.$results.offset().top; + var nextTop = $next.offset().top; + var nextOffset = self.$results.scrollTop() + (nextTop - currentOffset); + + if (nextIndex === 0) { + self.$results.scrollTop(0); + } else if (nextTop - currentOffset < 0) { + self.$results.scrollTop(nextOffset); + } + }); + + container.on('results:next', function () { + var $highlighted = self.getHighlightedResults(); + + var $options = self.$results.find('[aria-selected]'); + + var currentIndex = $options.index($highlighted); + + var nextIndex = currentIndex + 1; + + // If we are at the last option, stay there + if (nextIndex >= $options.length) { + return; + } + + var $next = $options.eq(nextIndex); + + $next.trigger('mouseenter'); + + var currentOffset = self.$results.offset().top + + self.$results.outerHeight(false); + var nextBottom = $next.offset().top + $next.outerHeight(false); + var nextOffset = self.$results.scrollTop() + nextBottom - currentOffset; + + if (nextIndex === 0) { + self.$results.scrollTop(0); + } else if (nextBottom > currentOffset) { + self.$results.scrollTop(nextOffset); + } + }); + + container.on('results:focus', function (params) { + params.element.addClass('select2-results__option--highlighted'); + }); + + container.on('results:message', function (params) { + self.displayMessage(params); + }); + + if ($.fn.mousewheel) { + this.$results.on('mousewheel', function (e) { + var top = self.$results.scrollTop(); + + var bottom = self.$results.get(0).scrollHeight - top + e.deltaY; + + var isAtTop = e.deltaY > 0 && top - e.deltaY <= 0; + var isAtBottom = e.deltaY < 0 && bottom <= self.$results.height(); + + if (isAtTop) { + self.$results.scrollTop(0); + + e.preventDefault(); + e.stopPropagation(); + } else if (isAtBottom) { + self.$results.scrollTop( + self.$results.get(0).scrollHeight - self.$results.height() + ); + + e.preventDefault(); + e.stopPropagation(); + } + }); + } + + this.$results.on('mouseup', '.select2-results__option[aria-selected]', + function (evt) { + var $this = $(this); + + var data = $this.data('data'); + + if ($this.attr('aria-selected') === 'true') { + if (self.options.get('multiple')) { + self.trigger('unselect', { + originalEvent: evt, + data: data + }); + } else { + self.trigger('close', {}); + } + + return; + } + + self.trigger('select', { + originalEvent: evt, + data: data + }); + }); + + this.$results.on('mouseenter', '.select2-results__option[aria-selected]', + function (evt) { + var data = $(this).data('data'); + + self.getHighlightedResults() + .removeClass('select2-results__option--highlighted'); + + self.trigger('results:focus', { + data: data, + element: $(this) + }); + }); + }; + + Results.prototype.getHighlightedResults = function () { + var $highlighted = this.$results + .find('.select2-results__option--highlighted'); + + return $highlighted; + }; + + Results.prototype.destroy = function () { + this.$results.remove(); + }; + + Results.prototype.ensureHighlightVisible = function () { + var $highlighted = this.getHighlightedResults(); + + if ($highlighted.length === 0) { + return; + } + + var $options = this.$results.find('[aria-selected]'); + + var currentIndex = $options.index($highlighted); + + var currentOffset = this.$results.offset().top; + var nextTop = $highlighted.offset().top; + var nextOffset = this.$results.scrollTop() + (nextTop - currentOffset); + + var offsetDelta = nextTop - currentOffset; + nextOffset -= $highlighted.outerHeight(false) * 2; + + if (currentIndex <= 2) { + this.$results.scrollTop(0); + } else if (offsetDelta > this.$results.outerHeight() || offsetDelta < 0) { + this.$results.scrollTop(nextOffset); + } + }; + + Results.prototype.template = function (result, container) { + var template = this.options.get('templateResult'); + var escapeMarkup = this.options.get('escapeMarkup'); + + var content = template(result, container); + + if (content == null) { + container.style.display = 'none'; + } else if (typeof content === 'string') { + container.innerHTML = escapeMarkup(content); + } else { + $(container).append(content); + } + }; + + return Results; +}); + +S2.define('select2/keys',[ + +], function () { + var KEYS = { + BACKSPACE: 8, + TAB: 9, + ENTER: 13, + SHIFT: 16, + CTRL: 17, + ALT: 18, + ESC: 27, + SPACE: 32, + PAGE_UP: 33, + PAGE_DOWN: 34, + END: 35, + HOME: 36, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, + DELETE: 46 + }; + + return KEYS; +}); + +S2.define('select2/selection/base',[ + 'jquery', + '../utils', + '../keys' +], function ($, Utils, KEYS) { + function BaseSelection ($element, options) { + this.$element = $element; + this.options = options; + + BaseSelection.__super__.constructor.call(this); + } + + Utils.Extend(BaseSelection, Utils.Observable); + + BaseSelection.prototype.render = function () { + var $selection = $( + '<span class="select2-selection" role="combobox" ' + + ' aria-haspopup="true" aria-expanded="false">' + + '</span>' + ); + + this._tabindex = 0; + + if (this.$element.data('old-tabindex') != null) { + this._tabindex = this.$element.data('old-tabindex'); + } else if (this.$element.attr('tabindex') != null) { + this._tabindex = this.$element.attr('tabindex'); + } + + $selection.attr('title', this.$element.attr('title')); + $selection.attr('tabindex', this._tabindex); + + this.$selection = $selection; + + return $selection; + }; + + BaseSelection.prototype.bind = function (container, $container) { + var self = this; + + var id = container.id + '-container'; + var resultsId = container.id + '-results'; + + this.container = container; + + this.$selection.on('focus', function (evt) { + self.trigger('focus', evt); + }); + + this.$selection.on('blur', function (evt) { + self._handleBlur(evt); + }); + + this.$selection.on('keydown', function (evt) { + self.trigger('keypress', evt); + + if (evt.which === KEYS.SPACE) { + evt.preventDefault(); + } + }); + + container.on('results:focus', function (params) { + self.$selection.attr('aria-activedescendant', params.data._resultId); + }); + + container.on('selection:update', function (params) { + self.update(params.data); + }); + + container.on('open', function () { + // When the dropdown is open, aria-expanded="true" + self.$selection.attr('aria-expanded', 'true'); + self.$selection.attr('aria-owns', resultsId); + + self._attachCloseHandler(container); + }); + + container.on('close', function () { + // When the dropdown is closed, aria-expanded="false" + self.$selection.attr('aria-expanded', 'false'); + self.$selection.removeAttr('aria-activedescendant'); + self.$selection.removeAttr('aria-owns'); + + self.$selection.focus(); + + self._detachCloseHandler(container); + }); + + container.on('enable', function () { + self.$selection.attr('tabindex', self._tabindex); + }); + + container.on('disable', function () { + self.$selection.attr('tabindex', '-1'); + }); + }; + + BaseSelection.prototype._handleBlur = function (evt) { + var self = this; + + // This needs to be delayed as the active element is the body when the tab + // key is pressed, possibly along with others. + window.setTimeout(function () { + // Don't trigger `blur` if the focus is still in the selection + if ( + (document.activeElement == self.$selection[0]) || + ($.contains(self.$selection[0], document.activeElement)) + ) { + return; + } + + self.trigger('blur', evt); + }, 1); + }; + + BaseSelection.prototype._attachCloseHandler = function (container) { + var self = this; + + $(document.body).on('mousedown.select2.' + container.id, function (e) { + var $target = $(e.target); + + var $select = $target.closest('.select2'); + + var $all = $('.select2.select2-container--open'); + + $all.each(function () { + var $this = $(this); + + if (this == $select[0]) { + return; + } + + var $element = $this.data('element'); + + $element.select2('close'); + }); + }); + }; + + BaseSelection.prototype._detachCloseHandler = function (container) { + $(document.body).off('mousedown.select2.' + container.id); + }; + + BaseSelection.prototype.position = function ($selection, $container) { + var $selectionContainer = $container.find('.selection'); + $selectionContainer.append($selection); + }; + + BaseSelection.prototype.destroy = function () { + this._detachCloseHandler(this.container); + }; + + BaseSelection.prototype.update = function (data) { + throw new Error('The `update` method must be defined in child classes.'); + }; + + return BaseSelection; +}); + +S2.define('select2/selection/single',[ + 'jquery', + './base', + '../utils', + '../keys' +], function ($, BaseSelection, Utils, KEYS) { + function SingleSelection () { + SingleSelection.__super__.constructor.apply(this, arguments); + } + + Utils.Extend(SingleSelection, BaseSelection); + + SingleSelection.prototype.render = function () { + var $selection = SingleSelection.__super__.render.call(this); + + $selection.addClass('select2-selection--single'); + + $selection.html( + '<span class="select2-selection__rendered"></span>' + + '<span class="select2-selection__arrow" role="presentation">' + + '<b role="presentation"></b>' + + '</span>' + ); + + return $selection; + }; + + SingleSelection.prototype.bind = function (container, $container) { + var self = this; + + SingleSelection.__super__.bind.apply(this, arguments); + + var id = container.id + '-container'; + + this.$selection.find('.select2-selection__rendered').attr('id', id); + this.$selection.attr('aria-labelledby', id); + + this.$selection.on('mousedown', function (evt) { + // Only respond to left clicks + if (evt.which !== 1) { + return; + } + + self.trigger('toggle', { + originalEvent: evt + }); + }); + + this.$selection.on('focus', function (evt) { + // User focuses on the container + }); + + this.$selection.on('blur', function (evt) { + // User exits the container + }); + + container.on('focus', function (evt) { + if (!container.isOpen()) { + self.$selection.focus(); + } + }); + + container.on('selection:update', function (params) { + self.update(params.data); + }); + }; + + SingleSelection.prototype.clear = function () { + this.$selection.find('.select2-selection__rendered').empty(); + }; + + SingleSelection.prototype.display = function (data, container) { + var template = this.options.get('templateSelection'); + var escapeMarkup = this.options.get('escapeMarkup'); + + return escapeMarkup(template(data, container)); + }; + + SingleSelection.prototype.selectionContainer = function () { + return $('<span></span>'); + }; + + SingleSelection.prototype.update = function (data) { + if (data.length === 0) { + this.clear(); + return; + } + + var selection = data[0]; + + var $rendered = this.$selection.find('.select2-selection__rendered'); + var formatted = this.display(selection, $rendered); + + $rendered.empty().append(formatted); + $rendered.prop('title', selection.title || selection.text); + }; + + return SingleSelection; +}); + +S2.define('select2/selection/multiple',[ + 'jquery', + './base', + '../utils' +], function ($, BaseSelection, Utils) { + function MultipleSelection ($element, options) { + MultipleSelection.__super__.constructor.apply(this, arguments); + } + + Utils.Extend(MultipleSelection, BaseSelection); + + MultipleSelection.prototype.render = function () { + var $selection = MultipleSelection.__super__.render.call(this); + + $selection.addClass('select2-selection--multiple'); + + $selection.html( + '<ul class="select2-selection__rendered"></ul>' + ); + + return $selection; + }; + + MultipleSelection.prototype.bind = function (container, $container) { + var self = this; + + MultipleSelection.__super__.bind.apply(this, arguments); + + this.$selection.on('click', function (evt) { + self.trigger('toggle', { + originalEvent: evt + }); + }); + + this.$selection.on( + 'click', + '.select2-selection__choice__remove', + function (evt) { + // Ignore the event if it is disabled + if (self.options.get('disabled')) { + return; + } + + var $remove = $(this); + var $selection = $remove.parent(); + + var data = $selection.data('data'); + + self.trigger('unselect', { + originalEvent: evt, + data: data + }); + } + ); + }; + + MultipleSelection.prototype.clear = function () { + this.$selection.find('.select2-selection__rendered').empty(); + }; + + MultipleSelection.prototype.display = function (data, container) { + var template = this.options.get('templateSelection'); + var escapeMarkup = this.options.get('escapeMarkup'); + + return escapeMarkup(template(data, container)); + }; + + MultipleSelection.prototype.selectionContainer = function () { + var $container = $( + '<li class="select2-selection__choice">' + + '<span class="select2-selection__choice__remove" role="presentation">' + + '×' + + '</span>' + + '</li>' + ); + + return $container; + }; + + MultipleSelection.prototype.update = function (data) { + this.clear(); + + if (data.length === 0) { + return; + } + + var $selections = []; + + for (var d = 0; d < data.length; d++) { + var selection = data[d]; + + var $selection = this.selectionContainer(); + var formatted = this.display(selection, $selection); + + $selection.append(formatted); + $selection.prop('title', selection.title || selection.text); + + $selection.data('data', selection); + + $selections.push($selection); + } + + var $rendered = this.$selection.find('.select2-selection__rendered'); + + Utils.appendMany($rendered, $selections); + }; + + return MultipleSelection; +}); + +S2.define('select2/selection/placeholder',[ + '../utils' +], function (Utils) { + function Placeholder (decorated, $element, options) { + this.placeholder = this.normalizePlaceholder(options.get('placeholder')); + + decorated.call(this, $element, options); + } + + Placeholder.prototype.normalizePlaceholder = function (_, placeholder) { + if (typeof placeholder === 'string') { + placeholder = { + id: '', + text: placeholder + }; + } + + return placeholder; + }; + + Placeholder.prototype.createPlaceholder = function (decorated, placeholder) { + var $placeholder = this.selectionContainer(); + + $placeholder.html(this.display(placeholder)); + $placeholder.addClass('select2-selection__placeholder') + .removeClass('select2-selection__choice'); + + return $placeholder; + }; + + Placeholder.prototype.update = function (decorated, data) { + var singlePlaceholder = ( + data.length == 1 && data[0].id != this.placeholder.id + ); + var multipleSelections = data.length > 1; + + if (multipleSelections || singlePlaceholder) { + return decorated.call(this, data); + } + + this.clear(); + + var $placeholder = this.createPlaceholder(this.placeholder); + + this.$selection.find('.select2-selection__rendered').append($placeholder); + }; + + return Placeholder; +}); + +S2.define('select2/selection/allowClear',[ + 'jquery', + '../keys' +], function ($, KEYS) { + function AllowClear () { } + + AllowClear.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + if (this.placeholder == null) { + if (this.options.get('debug') && window.console && console.error) { + console.error( + 'Select2: The `allowClear` option should be used in combination ' + + 'with the `placeholder` option.' + ); + } + } + + this.$selection.on('mousedown', '.select2-selection__clear', + function (evt) { + self._handleClear(evt); + }); + + container.on('keypress', function (evt) { + self._handleKeyboardClear(evt, container); + }); + }; + + AllowClear.prototype._handleClear = function (_, evt) { + // Ignore the event if it is disabled + if (this.options.get('disabled')) { + return; + } + + var $clear = this.$selection.find('.select2-selection__clear'); + + // Ignore the event if nothing has been selected + if ($clear.length === 0) { + return; + } + + evt.stopPropagation(); + + var data = $clear.data('data'); + + for (var d = 0; d < data.length; d++) { + var unselectData = { + data: data[d] + }; + + // Trigger the `unselect` event, so people can prevent it from being + // cleared. + this.trigger('unselect', unselectData); + + // If the event was prevented, don't clear it out. + if (unselectData.prevented) { + return; + } + } + + this.$element.val(this.placeholder.id).trigger('change'); + + this.trigger('toggle', {}); + }; + + AllowClear.prototype._handleKeyboardClear = function (_, evt, container) { + if (container.isOpen()) { + return; + } + + if (evt.which == KEYS.DELETE || evt.which == KEYS.BACKSPACE) { + this._handleClear(evt); + } + }; + + AllowClear.prototype.update = function (decorated, data) { + decorated.call(this, data); + + if (this.$selection.find('.select2-selection__placeholder').length > 0 || + data.length === 0) { + return; + } + + var $remove = $( + '<span class="select2-selection__clear">' + + '×' + + '</span>' + ); + $remove.data('data', data); + + this.$selection.find('.select2-selection__rendered').prepend($remove); + }; + + return AllowClear; +}); + +S2.define('select2/selection/search',[ + 'jquery', + '../utils', + '../keys' +], function ($, Utils, KEYS) { + function Search (decorated, $element, options) { + decorated.call(this, $element, options); + } + + Search.prototype.render = function (decorated) { + var $search = $( + '<li class="select2-search select2-search--inline">' + + '<input class="select2-search__field" type="search" tabindex="-1"' + + ' autocomplete="off" autocorrect="off" autocapitalize="off"' + + ' spellcheck="false" role="textbox" aria-autocomplete="list" />' + + '</li>' + ); + + this.$searchContainer = $search; + this.$search = $search.find('input'); + + var $rendered = decorated.call(this); + + this._transferTabIndex(); + + return $rendered; + }; + + Search.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + container.on('open', function () { + self.$search.trigger('focus'); + }); + + container.on('close', function () { + self.$search.val(''); + self.$search.removeAttr('aria-activedescendant'); + self.$search.trigger('focus'); + }); + + container.on('enable', function () { + self.$search.prop('disabled', false); + + self._transferTabIndex(); + }); + + container.on('disable', function () { + self.$search.prop('disabled', true); + }); + + container.on('focus', function (evt) { + self.$search.trigger('focus'); + }); + + container.on('results:focus', function (params) { + self.$search.attr('aria-activedescendant', params.id); + }); + + this.$selection.on('focusin', '.select2-search--inline', function (evt) { + self.trigger('focus', evt); + }); + + this.$selection.on('focusout', '.select2-search--inline', function (evt) { + self._handleBlur(evt); + }); + + this.$selection.on('keydown', '.select2-search--inline', function (evt) { + evt.stopPropagation(); + + self.trigger('keypress', evt); + + self._keyUpPrevented = evt.isDefaultPrevented(); + + var key = evt.which; + + if (key === KEYS.BACKSPACE && self.$search.val() === '') { + var $previousChoice = self.$searchContainer + .prev('.select2-selection__choice'); + + if ($previousChoice.length > 0) { + var item = $previousChoice.data('data'); + + self.searchRemoveChoice(item); + + evt.preventDefault(); + } + } + }); + + // Try to detect the IE version should the `documentMode` property that + // is stored on the document. This is only implemented in IE and is + // slightly cleaner than doing a user agent check. + // This property is not available in Edge, but Edge also doesn't have + // this bug. + var msie = document.documentMode; + var disableInputEvents = msie && msie <= 11; + + // Workaround for browsers which do not support the `input` event + // This will prevent double-triggering of events for browsers which support + // both the `keyup` and `input` events. + this.$selection.on( + 'input.searchcheck', + '.select2-search--inline', + function (evt) { + // IE will trigger the `input` event when a placeholder is used on a + // search box. To get around this issue, we are forced to ignore all + // `input` events in IE and keep using `keyup`. + if (disableInputEvents) { + self.$selection.off('input.search input.searchcheck'); + return; + } + + // Unbind the duplicated `keyup` event + self.$selection.off('keyup.search'); + } + ); + + this.$selection.on( + 'keyup.search input.search', + '.select2-search--inline', + function (evt) { + // IE will trigger the `input` event when a placeholder is used on a + // search box. To get around this issue, we are forced to ignore all + // `input` events in IE and keep using `keyup`. + if (disableInputEvents && evt.type === 'input') { + self.$selection.off('input.search input.searchcheck'); + return; + } + + var key = evt.which; + + // We can freely ignore events from modifier keys + if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) { + return; + } + + // Tabbing will be handled during the `keydown` phase + if (key == KEYS.TAB) { + return; + } + + self.handleSearch(evt); + } + ); + }; + + /** + * This method will transfer the tabindex attribute from the rendered + * selection to the search box. This allows for the search box to be used as + * the primary focus instead of the selection container. + * + * @private + */ + Search.prototype._transferTabIndex = function (decorated) { + this.$search.attr('tabindex', this.$selection.attr('tabindex')); + this.$selection.attr('tabindex', '-1'); + }; + + Search.prototype.createPlaceholder = function (decorated, placeholder) { + this.$search.attr('placeholder', placeholder.text); + }; + + Search.prototype.update = function (decorated, data) { + var searchHadFocus = this.$search[0] == document.activeElement; + + this.$search.attr('placeholder', ''); + + decorated.call(this, data); + + this.$selection.find('.select2-selection__rendered') + .append(this.$searchContainer); + + this.resizeSearch(); + if (searchHadFocus) { + this.$search.focus(); + } + }; + + Search.prototype.handleSearch = function () { + this.resizeSearch(); + + if (!this._keyUpPrevented) { + var input = this.$search.val(); + + this.trigger('query', { + term: input + }); + } + + this._keyUpPrevented = false; + }; + + Search.prototype.searchRemoveChoice = function (decorated, item) { + this.trigger('unselect', { + data: item + }); + + this.$search.val(item.text); + this.handleSearch(); + }; + + Search.prototype.resizeSearch = function () { + this.$search.css('width', '25px'); + + var width = ''; + + if (this.$search.attr('placeholder') !== '') { + width = this.$selection.find('.select2-selection__rendered').innerWidth(); + } else { + var minimumWidth = this.$search.val().length + 1; + + width = (minimumWidth * 0.75) + 'em'; + } + + this.$search.css('width', width); + }; + + return Search; +}); + +S2.define('select2/selection/eventRelay',[ + 'jquery' +], function ($) { + function EventRelay () { } + + EventRelay.prototype.bind = function (decorated, container, $container) { + var self = this; + var relayEvents = [ + 'open', 'opening', + 'close', 'closing', + 'select', 'selecting', + 'unselect', 'unselecting' + ]; + + var preventableEvents = ['opening', 'closing', 'selecting', 'unselecting']; + + decorated.call(this, container, $container); + + container.on('*', function (name, params) { + // Ignore events that should not be relayed + if ($.inArray(name, relayEvents) === -1) { + return; + } + + // The parameters should always be an object + params = params || {}; + + // Generate the jQuery event for the Select2 event + var evt = $.Event('select2:' + name, { + params: params + }); + + self.$element.trigger(evt); + + // Only handle preventable events if it was one + if ($.inArray(name, preventableEvents) === -1) { + return; + } + + params.prevented = evt.isDefaultPrevented(); + }); + }; + + return EventRelay; +}); + +S2.define('select2/translation',[ + 'jquery', + 'require' +], function ($, require) { + function Translation (dict) { + this.dict = dict || {}; + } + + Translation.prototype.all = function () { + return this.dict; + }; + + Translation.prototype.get = function (key) { + return this.dict[key]; + }; + + Translation.prototype.extend = function (translation) { + this.dict = $.extend({}, translation.all(), this.dict); + }; + + // Static functions + + Translation._cache = {}; + + Translation.loadPath = function (path) { + if (!(path in Translation._cache)) { + var translations = require(path); + + Translation._cache[path] = translations; + } + + return new Translation(Translation._cache[path]); + }; + + return Translation; +}); + +S2.define('select2/diacritics',[ + +], function () { + var diacritics = { + '\u24B6': 'A', + '\uFF21': 'A', + '\u00C0': 'A', + '\u00C1': 'A', + '\u00C2': 'A', + '\u1EA6': 'A', + '\u1EA4': 'A', + '\u1EAA': 'A', + '\u1EA8': 'A', + '\u00C3': 'A', + '\u0100': 'A', + '\u0102': 'A', + '\u1EB0': 'A', + '\u1EAE': 'A', + '\u1EB4': 'A', + '\u1EB2': 'A', + '\u0226': 'A', + '\u01E0': 'A', + '\u00C4': 'A', + '\u01DE': 'A', + '\u1EA2': 'A', + '\u00C5': 'A', + '\u01FA': 'A', + '\u01CD': 'A', + '\u0200': 'A', + '\u0202': 'A', + '\u1EA0': 'A', + '\u1EAC': 'A', + '\u1EB6': 'A', + '\u1E00': 'A', + '\u0104': 'A', + '\u023A': 'A', + '\u2C6F': 'A', + '\uA732': 'AA', + '\u00C6': 'AE', + '\u01FC': 'AE', + '\u01E2': 'AE', + '\uA734': 'AO', + '\uA736': 'AU', + '\uA738': 'AV', + '\uA73A': 'AV', + '\uA73C': 'AY', + '\u24B7': 'B', + '\uFF22': 'B', + '\u1E02': 'B', + '\u1E04': 'B', + '\u1E06': 'B', + '\u0243': 'B', + '\u0182': 'B', + '\u0181': 'B', + '\u24B8': 'C', + '\uFF23': 'C', + '\u0106': 'C', + '\u0108': 'C', + '\u010A': 'C', + '\u010C': 'C', + '\u00C7': 'C', + '\u1E08': 'C', + '\u0187': 'C', + '\u023B': 'C', + '\uA73E': 'C', + '\u24B9': 'D', + '\uFF24': 'D', + '\u1E0A': 'D', + '\u010E': 'D', + '\u1E0C': 'D', + '\u1E10': 'D', + '\u1E12': 'D', + '\u1E0E': 'D', + '\u0110': 'D', + '\u018B': 'D', + '\u018A': 'D', + '\u0189': 'D', + '\uA779': 'D', + '\u01F1': 'DZ', + '\u01C4': 'DZ', + '\u01F2': 'Dz', + '\u01C5': 'Dz', + '\u24BA': 'E', + '\uFF25': 'E', + '\u00C8': 'E', + '\u00C9': 'E', + '\u00CA': 'E', + '\u1EC0': 'E', + '\u1EBE': 'E', + '\u1EC4': 'E', + '\u1EC2': 'E', + '\u1EBC': 'E', + '\u0112': 'E', + '\u1E14': 'E', + '\u1E16': 'E', + '\u0114': 'E', + '\u0116': 'E', + '\u00CB': 'E', + '\u1EBA': 'E', + '\u011A': 'E', + '\u0204': 'E', + '\u0206': 'E', + '\u1EB8': 'E', + '\u1EC6': 'E', + '\u0228': 'E', + '\u1E1C': 'E', + '\u0118': 'E', + '\u1E18': 'E', + '\u1E1A': 'E', + '\u0190': 'E', + '\u018E': 'E', + '\u24BB': 'F', + '\uFF26': 'F', + '\u1E1E': 'F', + '\u0191': 'F', + '\uA77B': 'F', + '\u24BC': 'G', + '\uFF27': 'G', + '\u01F4': 'G', + '\u011C': 'G', + '\u1E20': 'G', + '\u011E': 'G', + '\u0120': 'G', + '\u01E6': 'G', + '\u0122': 'G', + '\u01E4': 'G', + '\u0193': 'G', + '\uA7A0': 'G', + '\uA77D': 'G', + '\uA77E': 'G', + '\u24BD': 'H', + '\uFF28': 'H', + '\u0124': 'H', + '\u1E22': 'H', + '\u1E26': 'H', + '\u021E': 'H', + '\u1E24': 'H', + '\u1E28': 'H', + '\u1E2A': 'H', + '\u0126': 'H', + '\u2C67': 'H', + '\u2C75': 'H', + '\uA78D': 'H', + '\u24BE': 'I', + '\uFF29': 'I', + '\u00CC': 'I', + '\u00CD': 'I', + '\u00CE': 'I', + '\u0128': 'I', + '\u012A': 'I', + '\u012C': 'I', + '\u0130': 'I', + '\u00CF': 'I', + '\u1E2E': 'I', + '\u1EC8': 'I', + '\u01CF': 'I', + '\u0208': 'I', + '\u020A': 'I', + '\u1ECA': 'I', + '\u012E': 'I', + '\u1E2C': 'I', + '\u0197': 'I', + '\u24BF': 'J', + '\uFF2A': 'J', + '\u0134': 'J', + '\u0248': 'J', + '\u24C0': 'K', + '\uFF2B': 'K', + '\u1E30': 'K', + '\u01E8': 'K', + '\u1E32': 'K', + '\u0136': 'K', + '\u1E34': 'K', + '\u0198': 'K', + '\u2C69': 'K', + '\uA740': 'K', + '\uA742': 'K', + '\uA744': 'K', + '\uA7A2': 'K', + '\u24C1': 'L', + '\uFF2C': 'L', + '\u013F': 'L', + '\u0139': 'L', + '\u013D': 'L', + '\u1E36': 'L', + '\u1E38': 'L', + '\u013B': 'L', + '\u1E3C': 'L', + '\u1E3A': 'L', + '\u0141': 'L', + '\u023D': 'L', + '\u2C62': 'L', + '\u2C60': 'L', + '\uA748': 'L', + '\uA746': 'L', + '\uA780': 'L', + '\u01C7': 'LJ', + '\u01C8': 'Lj', + '\u24C2': 'M', + '\uFF2D': 'M', + '\u1E3E': 'M', + '\u1E40': 'M', + '\u1E42': 'M', + '\u2C6E': 'M', + '\u019C': 'M', + '\u24C3': 'N', + '\uFF2E': 'N', + '\u01F8': 'N', + '\u0143': 'N', + '\u00D1': 'N', + '\u1E44': 'N', + '\u0147': 'N', + '\u1E46': 'N', + '\u0145': 'N', + '\u1E4A': 'N', + '\u1E48': 'N', + '\u0220': 'N', + '\u019D': 'N', + '\uA790': 'N', + '\uA7A4': 'N', + '\u01CA': 'NJ', + '\u01CB': 'Nj', + '\u24C4': 'O', + '\uFF2F': 'O', + '\u00D2': 'O', + '\u00D3': 'O', + '\u00D4': 'O', + '\u1ED2': 'O', + '\u1ED0': 'O', + '\u1ED6': 'O', + '\u1ED4': 'O', + '\u00D5': 'O', + '\u1E4C': 'O', + '\u022C': 'O', + '\u1E4E': 'O', + '\u014C': 'O', + '\u1E50': 'O', + '\u1E52': 'O', + '\u014E': 'O', + '\u022E': 'O', + '\u0230': 'O', + '\u00D6': 'O', + '\u022A': 'O', + '\u1ECE': 'O', + '\u0150': 'O', + '\u01D1': 'O', + '\u020C': 'O', + '\u020E': 'O', + '\u01A0': 'O', + '\u1EDC': 'O', + '\u1EDA': 'O', + '\u1EE0': 'O', + '\u1EDE': 'O', + '\u1EE2': 'O', + '\u1ECC': 'O', + '\u1ED8': 'O', + '\u01EA': 'O', + '\u01EC': 'O', + '\u00D8': 'O', + '\u01FE': 'O', + '\u0186': 'O', + '\u019F': 'O', + '\uA74A': 'O', + '\uA74C': 'O', + '\u01A2': 'OI', + '\uA74E': 'OO', + '\u0222': 'OU', + '\u24C5': 'P', + '\uFF30': 'P', + '\u1E54': 'P', + '\u1E56': 'P', + '\u01A4': 'P', + '\u2C63': 'P', + '\uA750': 'P', + '\uA752': 'P', + '\uA754': 'P', + '\u24C6': 'Q', + '\uFF31': 'Q', + '\uA756': 'Q', + '\uA758': 'Q', + '\u024A': 'Q', + '\u24C7': 'R', + '\uFF32': 'R', + '\u0154': 'R', + '\u1E58': 'R', + '\u0158': 'R', + '\u0210': 'R', + '\u0212': 'R', + '\u1E5A': 'R', + '\u1E5C': 'R', + '\u0156': 'R', + '\u1E5E': 'R', + '\u024C': 'R', + '\u2C64': 'R', + '\uA75A': 'R', + '\uA7A6': 'R', + '\uA782': 'R', + '\u24C8': 'S', + '\uFF33': 'S', + '\u1E9E': 'S', + '\u015A': 'S', + '\u1E64': 'S', + '\u015C': 'S', + '\u1E60': 'S', + '\u0160': 'S', + '\u1E66': 'S', + '\u1E62': 'S', + '\u1E68': 'S', + '\u0218': 'S', + '\u015E': 'S', + '\u2C7E': 'S', + '\uA7A8': 'S', + '\uA784': 'S', + '\u24C9': 'T', + '\uFF34': 'T', + '\u1E6A': 'T', + '\u0164': 'T', + '\u1E6C': 'T', + '\u021A': 'T', + '\u0162': 'T', + '\u1E70': 'T', + '\u1E6E': 'T', + '\u0166': 'T', + '\u01AC': 'T', + '\u01AE': 'T', + '\u023E': 'T', + '\uA786': 'T', + '\uA728': 'TZ', + '\u24CA': 'U', + '\uFF35': 'U', + '\u00D9': 'U', + '\u00DA': 'U', + '\u00DB': 'U', + '\u0168': 'U', + '\u1E78': 'U', + '\u016A': 'U', + '\u1E7A': 'U', + '\u016C': 'U', + '\u00DC': 'U', + '\u01DB': 'U', + '\u01D7': 'U', + '\u01D5': 'U', + '\u01D9': 'U', + '\u1EE6': 'U', + '\u016E': 'U', + '\u0170': 'U', + '\u01D3': 'U', + '\u0214': 'U', + '\u0216': 'U', + '\u01AF': 'U', + '\u1EEA': 'U', + '\u1EE8': 'U', + '\u1EEE': 'U', + '\u1EEC': 'U', + '\u1EF0': 'U', + '\u1EE4': 'U', + '\u1E72': 'U', + '\u0172': 'U', + '\u1E76': 'U', + '\u1E74': 'U', + '\u0244': 'U', + '\u24CB': 'V', + '\uFF36': 'V', + '\u1E7C': 'V', + '\u1E7E': 'V', + '\u01B2': 'V', + '\uA75E': 'V', + '\u0245': 'V', + '\uA760': 'VY', + '\u24CC': 'W', + '\uFF37': 'W', + '\u1E80': 'W', + '\u1E82': 'W', + '\u0174': 'W', + '\u1E86': 'W', + '\u1E84': 'W', + '\u1E88': 'W', + '\u2C72': 'W', + '\u24CD': 'X', + '\uFF38': 'X', + '\u1E8A': 'X', + '\u1E8C': 'X', + '\u24CE': 'Y', + '\uFF39': 'Y', + '\u1EF2': 'Y', + '\u00DD': 'Y', + '\u0176': 'Y', + '\u1EF8': 'Y', + '\u0232': 'Y', + '\u1E8E': 'Y', + '\u0178': 'Y', + '\u1EF6': 'Y', + '\u1EF4': 'Y', + '\u01B3': 'Y', + '\u024E': 'Y', + '\u1EFE': 'Y', + '\u24CF': 'Z', + '\uFF3A': 'Z', + '\u0179': 'Z', + '\u1E90': 'Z', + '\u017B': 'Z', + '\u017D': 'Z', + '\u1E92': 'Z', + '\u1E94': 'Z', + '\u01B5': 'Z', + '\u0224': 'Z', + '\u2C7F': 'Z', + '\u2C6B': 'Z', + '\uA762': 'Z', + '\u24D0': 'a', + '\uFF41': 'a', + '\u1E9A': 'a', + '\u00E0': 'a', + '\u00E1': 'a', + '\u00E2': 'a', + '\u1EA7': 'a', + '\u1EA5': 'a', + '\u1EAB': 'a', + '\u1EA9': 'a', + '\u00E3': 'a', + '\u0101': 'a', + '\u0103': 'a', + '\u1EB1': 'a', + '\u1EAF': 'a', + '\u1EB5': 'a', + '\u1EB3': 'a', + '\u0227': 'a', + '\u01E1': 'a', + '\u00E4': 'a', + '\u01DF': 'a', + '\u1EA3': 'a', + '\u00E5': 'a', + '\u01FB': 'a', + '\u01CE': 'a', + '\u0201': 'a', + '\u0203': 'a', + '\u1EA1': 'a', + '\u1EAD': 'a', + '\u1EB7': 'a', + '\u1E01': 'a', + '\u0105': 'a', + '\u2C65': 'a', + '\u0250': 'a', + '\uA733': 'aa', + '\u00E6': 'ae', + '\u01FD': 'ae', + '\u01E3': 'ae', + '\uA735': 'ao', + '\uA737': 'au', + '\uA739': 'av', + '\uA73B': 'av', + '\uA73D': 'ay', + '\u24D1': 'b', + '\uFF42': 'b', + '\u1E03': 'b', + '\u1E05': 'b', + '\u1E07': 'b', + '\u0180': 'b', + '\u0183': 'b', + '\u0253': 'b', + '\u24D2': 'c', + '\uFF43': 'c', + '\u0107': 'c', + '\u0109': 'c', + '\u010B': 'c', + '\u010D': 'c', + '\u00E7': 'c', + '\u1E09': 'c', + '\u0188': 'c', + '\u023C': 'c', + '\uA73F': 'c', + '\u2184': 'c', + '\u24D3': 'd', + '\uFF44': 'd', + '\u1E0B': 'd', + '\u010F': 'd', + '\u1E0D': 'd', + '\u1E11': 'd', + '\u1E13': 'd', + '\u1E0F': 'd', + '\u0111': 'd', + '\u018C': 'd', + '\u0256': 'd', + '\u0257': 'd', + '\uA77A': 'd', + '\u01F3': 'dz', + '\u01C6': 'dz', + '\u24D4': 'e', + '\uFF45': 'e', + '\u00E8': 'e', + '\u00E9': 'e', + '\u00EA': 'e', + '\u1EC1': 'e', + '\u1EBF': 'e', + '\u1EC5': 'e', + '\u1EC3': 'e', + '\u1EBD': 'e', + '\u0113': 'e', + '\u1E15': 'e', + '\u1E17': 'e', + '\u0115': 'e', + '\u0117': 'e', + '\u00EB': 'e', + '\u1EBB': 'e', + '\u011B': 'e', + '\u0205': 'e', + '\u0207': 'e', + '\u1EB9': 'e', + '\u1EC7': 'e', + '\u0229': 'e', + '\u1E1D': 'e', + '\u0119': 'e', + '\u1E19': 'e', + '\u1E1B': 'e', + '\u0247': 'e', + '\u025B': 'e', + '\u01DD': 'e', + '\u24D5': 'f', + '\uFF46': 'f', + '\u1E1F': 'f', + '\u0192': 'f', + '\uA77C': 'f', + '\u24D6': 'g', + '\uFF47': 'g', + '\u01F5': 'g', + '\u011D': 'g', + '\u1E21': 'g', + '\u011F': 'g', + '\u0121': 'g', + '\u01E7': 'g', + '\u0123': 'g', + '\u01E5': 'g', + '\u0260': 'g', + '\uA7A1': 'g', + '\u1D79': 'g', + '\uA77F': 'g', + '\u24D7': 'h', + '\uFF48': 'h', + '\u0125': 'h', + '\u1E23': 'h', + '\u1E27': 'h', + '\u021F': 'h', + '\u1E25': 'h', + '\u1E29': 'h', + '\u1E2B': 'h', + '\u1E96': 'h', + '\u0127': 'h', + '\u2C68': 'h', + '\u2C76': 'h', + '\u0265': 'h', + '\u0195': 'hv', + '\u24D8': 'i', + '\uFF49': 'i', + '\u00EC': 'i', + '\u00ED': 'i', + '\u00EE': 'i', + '\u0129': 'i', + '\u012B': 'i', + '\u012D': 'i', + '\u00EF': 'i', + '\u1E2F': 'i', + '\u1EC9': 'i', + '\u01D0': 'i', + '\u0209': 'i', + '\u020B': 'i', + '\u1ECB': 'i', + '\u012F': 'i', + '\u1E2D': 'i', + '\u0268': 'i', + '\u0131': 'i', + '\u24D9': 'j', + '\uFF4A': 'j', + '\u0135': 'j', + '\u01F0': 'j', + '\u0249': 'j', + '\u24DA': 'k', + '\uFF4B': 'k', + '\u1E31': 'k', + '\u01E9': 'k', + '\u1E33': 'k', + '\u0137': 'k', + '\u1E35': 'k', + '\u0199': 'k', + '\u2C6A': 'k', + '\uA741': 'k', + '\uA743': 'k', + '\uA745': 'k', + '\uA7A3': 'k', + '\u24DB': 'l', + '\uFF4C': 'l', + '\u0140': 'l', + '\u013A': 'l', + '\u013E': 'l', + '\u1E37': 'l', + '\u1E39': 'l', + '\u013C': 'l', + '\u1E3D': 'l', + '\u1E3B': 'l', + '\u017F': 'l', + '\u0142': 'l', + '\u019A': 'l', + '\u026B': 'l', + '\u2C61': 'l', + '\uA749': 'l', + '\uA781': 'l', + '\uA747': 'l', + '\u01C9': 'lj', + '\u24DC': 'm', + '\uFF4D': 'm', + '\u1E3F': 'm', + '\u1E41': 'm', + '\u1E43': 'm', + '\u0271': 'm', + '\u026F': 'm', + '\u24DD': 'n', + '\uFF4E': 'n', + '\u01F9': 'n', + '\u0144': 'n', + '\u00F1': 'n', + '\u1E45': 'n', + '\u0148': 'n', + '\u1E47': 'n', + '\u0146': 'n', + '\u1E4B': 'n', + '\u1E49': 'n', + '\u019E': 'n', + '\u0272': 'n', + '\u0149': 'n', + '\uA791': 'n', + '\uA7A5': 'n', + '\u01CC': 'nj', + '\u24DE': 'o', + '\uFF4F': 'o', + '\u00F2': 'o', + '\u00F3': 'o', + '\u00F4': 'o', + '\u1ED3': 'o', + '\u1ED1': 'o', + '\u1ED7': 'o', + '\u1ED5': 'o', + '\u00F5': 'o', + '\u1E4D': 'o', + '\u022D': 'o', + '\u1E4F': 'o', + '\u014D': 'o', + '\u1E51': 'o', + '\u1E53': 'o', + '\u014F': 'o', + '\u022F': 'o', + '\u0231': 'o', + '\u00F6': 'o', + '\u022B': 'o', + '\u1ECF': 'o', + '\u0151': 'o', + '\u01D2': 'o', + '\u020D': 'o', + '\u020F': 'o', + '\u01A1': 'o', + '\u1EDD': 'o', + '\u1EDB': 'o', + '\u1EE1': 'o', + '\u1EDF': 'o', + '\u1EE3': 'o', + '\u1ECD': 'o', + '\u1ED9': 'o', + '\u01EB': 'o', + '\u01ED': 'o', + '\u00F8': 'o', + '\u01FF': 'o', + '\u0254': 'o', + '\uA74B': 'o', + '\uA74D': 'o', + '\u0275': 'o', + '\u01A3': 'oi', + '\u0223': 'ou', + '\uA74F': 'oo', + '\u24DF': 'p', + '\uFF50': 'p', + '\u1E55': 'p', + '\u1E57': 'p', + '\u01A5': 'p', + '\u1D7D': 'p', + '\uA751': 'p', + '\uA753': 'p', + '\uA755': 'p', + '\u24E0': 'q', + '\uFF51': 'q', + '\u024B': 'q', + '\uA757': 'q', + '\uA759': 'q', + '\u24E1': 'r', + '\uFF52': 'r', + '\u0155': 'r', + '\u1E59': 'r', + '\u0159': 'r', + '\u0211': 'r', + '\u0213': 'r', + '\u1E5B': 'r', + '\u1E5D': 'r', + '\u0157': 'r', + '\u1E5F': 'r', + '\u024D': 'r', + '\u027D': 'r', + '\uA75B': 'r', + '\uA7A7': 'r', + '\uA783': 'r', + '\u24E2': 's', + '\uFF53': 's', + '\u00DF': 's', + '\u015B': 's', + '\u1E65': 's', + '\u015D': 's', + '\u1E61': 's', + '\u0161': 's', + '\u1E67': 's', + '\u1E63': 's', + '\u1E69': 's', + '\u0219': 's', + '\u015F': 's', + '\u023F': 's', + '\uA7A9': 's', + '\uA785': 's', + '\u1E9B': 's', + '\u24E3': 't', + '\uFF54': 't', + '\u1E6B': 't', + '\u1E97': 't', + '\u0165': 't', + '\u1E6D': 't', + '\u021B': 't', + '\u0163': 't', + '\u1E71': 't', + '\u1E6F': 't', + '\u0167': 't', + '\u01AD': 't', + '\u0288': 't', + '\u2C66': 't', + '\uA787': 't', + '\uA729': 'tz', + '\u24E4': 'u', + '\uFF55': 'u', + '\u00F9': 'u', + '\u00FA': 'u', + '\u00FB': 'u', + '\u0169': 'u', + '\u1E79': 'u', + '\u016B': 'u', + '\u1E7B': 'u', + '\u016D': 'u', + '\u00FC': 'u', + '\u01DC': 'u', + '\u01D8': 'u', + '\u01D6': 'u', + '\u01DA': 'u', + '\u1EE7': 'u', + '\u016F': 'u', + '\u0171': 'u', + '\u01D4': 'u', + '\u0215': 'u', + '\u0217': 'u', + '\u01B0': 'u', + '\u1EEB': 'u', + '\u1EE9': 'u', + '\u1EEF': 'u', + '\u1EED': 'u', + '\u1EF1': 'u', + '\u1EE5': 'u', + '\u1E73': 'u', + '\u0173': 'u', + '\u1E77': 'u', + '\u1E75': 'u', + '\u0289': 'u', + '\u24E5': 'v', + '\uFF56': 'v', + '\u1E7D': 'v', + '\u1E7F': 'v', + '\u028B': 'v', + '\uA75F': 'v', + '\u028C': 'v', + '\uA761': 'vy', + '\u24E6': 'w', + '\uFF57': 'w', + '\u1E81': 'w', + '\u1E83': 'w', + '\u0175': 'w', + '\u1E87': 'w', + '\u1E85': 'w', + '\u1E98': 'w', + '\u1E89': 'w', + '\u2C73': 'w', + '\u24E7': 'x', + '\uFF58': 'x', + '\u1E8B': 'x', + '\u1E8D': 'x', + '\u24E8': 'y', + '\uFF59': 'y', + '\u1EF3': 'y', + '\u00FD': 'y', + '\u0177': 'y', + '\u1EF9': 'y', + '\u0233': 'y', + '\u1E8F': 'y', + '\u00FF': 'y', + '\u1EF7': 'y', + '\u1E99': 'y', + '\u1EF5': 'y', + '\u01B4': 'y', + '\u024F': 'y', + '\u1EFF': 'y', + '\u24E9': 'z', + '\uFF5A': 'z', + '\u017A': 'z', + '\u1E91': 'z', + '\u017C': 'z', + '\u017E': 'z', + '\u1E93': 'z', + '\u1E95': 'z', + '\u01B6': 'z', + '\u0225': 'z', + '\u0240': 'z', + '\u2C6C': 'z', + '\uA763': 'z', + '\u0386': '\u0391', + '\u0388': '\u0395', + '\u0389': '\u0397', + '\u038A': '\u0399', + '\u03AA': '\u0399', + '\u038C': '\u039F', + '\u038E': '\u03A5', + '\u03AB': '\u03A5', + '\u038F': '\u03A9', + '\u03AC': '\u03B1', + '\u03AD': '\u03B5', + '\u03AE': '\u03B7', + '\u03AF': '\u03B9', + '\u03CA': '\u03B9', + '\u0390': '\u03B9', + '\u03CC': '\u03BF', + '\u03CD': '\u03C5', + '\u03CB': '\u03C5', + '\u03B0': '\u03C5', + '\u03C9': '\u03C9', + '\u03C2': '\u03C3' + }; + + return diacritics; +}); + +S2.define('select2/data/base',[ + '../utils' +], function (Utils) { + function BaseAdapter ($element, options) { + BaseAdapter.__super__.constructor.call(this); + } + + Utils.Extend(BaseAdapter, Utils.Observable); + + BaseAdapter.prototype.current = function (callback) { + throw new Error('The `current` method must be defined in child classes.'); + }; + + BaseAdapter.prototype.query = function (params, callback) { + throw new Error('The `query` method must be defined in child classes.'); + }; + + BaseAdapter.prototype.bind = function (container, $container) { + // Can be implemented in subclasses + }; + + BaseAdapter.prototype.destroy = function () { + // Can be implemented in subclasses + }; + + BaseAdapter.prototype.generateResultId = function (container, data) { + var id = container.id + '-result-'; + + id += Utils.generateChars(4); + + if (data.id != null) { + id += '-' + data.id.toString(); + } else { + id += '-' + Utils.generateChars(4); + } + return id; + }; + + return BaseAdapter; +}); + +S2.define('select2/data/select',[ + './base', + '../utils', + 'jquery' +], function (BaseAdapter, Utils, $) { + function SelectAdapter ($element, options) { + this.$element = $element; + this.options = options; + + SelectAdapter.__super__.constructor.call(this); + } + + Utils.Extend(SelectAdapter, BaseAdapter); + + SelectAdapter.prototype.current = function (callback) { + var data = []; + var self = this; + + this.$element.find(':selected').each(function () { + var $option = $(this); + + var option = self.item($option); + + data.push(option); + }); + + callback(data); + }; + + SelectAdapter.prototype.select = function (data) { + var self = this; + + data.selected = true; + + // If data.element is a DOM node, use it instead + if ($(data.element).is('option')) { + data.element.selected = true; + + this.$element.trigger('change'); + + return; + } + + if (this.$element.prop('multiple')) { + this.current(function (currentData) { + var val = []; + + data = [data]; + data.push.apply(data, currentData); + + for (var d = 0; d < data.length; d++) { + var id = data[d].id; + + if ($.inArray(id, val) === -1) { + val.push(id); + } + } + + self.$element.val(val); + self.$element.trigger('change'); + }); + } else { + var val = data.id; + + this.$element.val(val); + this.$element.trigger('change'); + } + }; + + SelectAdapter.prototype.unselect = function (data) { + var self = this; + + if (!this.$element.prop('multiple')) { + return; + } + + data.selected = false; + + if ($(data.element).is('option')) { + data.element.selected = false; + + this.$element.trigger('change'); + + return; + } + + this.current(function (currentData) { + var val = []; + + for (var d = 0; d < currentData.length; d++) { + var id = currentData[d].id; + + if (id !== data.id && $.inArray(id, val) === -1) { + val.push(id); + } + } + + self.$element.val(val); + + self.$element.trigger('change'); + }); + }; + + SelectAdapter.prototype.bind = function (container, $container) { + var self = this; + + this.container = container; + + container.on('select', function (params) { + self.select(params.data); + }); + + container.on('unselect', function (params) { + self.unselect(params.data); + }); + }; + + SelectAdapter.prototype.destroy = function () { + // Remove anything added to child elements + this.$element.find('*').each(function () { + // Remove any custom data set by Select2 + $.removeData(this, 'data'); + }); + }; + + SelectAdapter.prototype.query = function (params, callback) { + var data = []; + var self = this; + + var $options = this.$element.children(); + + $options.each(function () { + var $option = $(this); + + if (!$option.is('option') && !$option.is('optgroup')) { + return; + } + + var option = self.item($option); + + var matches = self.matches(params, option); + + if (matches !== null) { + data.push(matches); + } + }); + + callback({ + results: data + }); + }; + + SelectAdapter.prototype.addOptions = function ($options) { + Utils.appendMany(this.$element, $options); + }; + + SelectAdapter.prototype.option = function (data) { + var option; + + if (data.children) { + option = document.createElement('optgroup'); + option.label = data.text; + } else { + option = document.createElement('option'); + + if (option.textContent !== undefined) { + option.textContent = data.text; + } else { + option.innerText = data.text; + } + } + + if (data.id) { + option.value = data.id; + } + + if (data.disabled) { + option.disabled = true; + } + + if (data.selected) { + option.selected = true; + } + + if (data.title) { + option.title = data.title; + } + + var $option = $(option); + + var normalizedData = this._normalizeItem(data); + normalizedData.element = option; + + // Override the option's data with the combined data + $.data(option, 'data', normalizedData); + + return $option; + }; + + SelectAdapter.prototype.item = function ($option) { + var data = {}; + + data = $.data($option[0], 'data'); + + if (data != null) { + return data; + } + + if ($option.is('option')) { + data = { + id: $option.val(), + text: $option.text(), + disabled: $option.prop('disabled'), + selected: $option.prop('selected'), + title: $option.prop('title') + }; + } else if ($option.is('optgroup')) { + data = { + text: $option.prop('label'), + children: [], + title: $option.prop('title') + }; + + var $children = $option.children('option'); + var children = []; + + for (var c = 0; c < $children.length; c++) { + var $child = $($children[c]); + + var child = this.item($child); + + children.push(child); + } + + data.children = children; + } + + data = this._normalizeItem(data); + data.element = $option[0]; + + $.data($option[0], 'data', data); + + return data; + }; + + SelectAdapter.prototype._normalizeItem = function (item) { + if (!$.isPlainObject(item)) { + item = { + id: item, + text: item + }; + } + + item = $.extend({}, { + text: '' + }, item); + + var defaults = { + selected: false, + disabled: false + }; + + if (item.id != null) { + item.id = item.id.toString(); + } + + if (item.text != null) { + item.text = item.text.toString(); + } + + if (item._resultId == null && item.id && this.container != null) { + item._resultId = this.generateResultId(this.container, item); + } + + return $.extend({}, defaults, item); + }; + + SelectAdapter.prototype.matches = function (params, data) { + var matcher = this.options.get('matcher'); + + return matcher(params, data); + }; + + return SelectAdapter; +}); + +S2.define('select2/data/array',[ + './select', + '../utils', + 'jquery' +], function (SelectAdapter, Utils, $) { + function ArrayAdapter ($element, options) { + var data = options.get('data') || []; + + ArrayAdapter.__super__.constructor.call(this, $element, options); + + this.addOptions(this.convertToOptions(data)); + } + + Utils.Extend(ArrayAdapter, SelectAdapter); + + ArrayAdapter.prototype.select = function (data) { + var $option = this.$element.find('option').filter(function (i, elm) { + return elm.value == data.id.toString(); + }); + + if ($option.length === 0) { + $option = this.option(data); + + this.addOptions($option); + } + + ArrayAdapter.__super__.select.call(this, data); + }; + + ArrayAdapter.prototype.convertToOptions = function (data) { + var self = this; + + var $existing = this.$element.find('option'); + var existingIds = $existing.map(function () { + return self.item($(this)).id; + }).get(); + + var $options = []; + + // Filter out all items except for the one passed in the argument + function onlyItem (item) { + return function () { + return $(this).val() == item.id; + }; + } + + for (var d = 0; d < data.length; d++) { + var item = this._normalizeItem(data[d]); + + // Skip items which were pre-loaded, only merge the data + if ($.inArray(item.id, existingIds) >= 0) { + var $existingOption = $existing.filter(onlyItem(item)); + + var existingData = this.item($existingOption); + var newData = $.extend(true, {}, item, existingData); + + var $newOption = this.option(newData); + + $existingOption.replaceWith($newOption); + + continue; + } + + var $option = this.option(item); + + if (item.children) { + var $children = this.convertToOptions(item.children); + + Utils.appendMany($option, $children); + } + + $options.push($option); + } + + return $options; + }; + + return ArrayAdapter; +}); + +S2.define('select2/data/ajax',[ + './array', + '../utils', + 'jquery' +], function (ArrayAdapter, Utils, $) { + function AjaxAdapter ($element, options) { + this.ajaxOptions = this._applyDefaults(options.get('ajax')); + + if (this.ajaxOptions.processResults != null) { + this.processResults = this.ajaxOptions.processResults; + } + + AjaxAdapter.__super__.constructor.call(this, $element, options); + } + + Utils.Extend(AjaxAdapter, ArrayAdapter); + + AjaxAdapter.prototype._applyDefaults = function (options) { + var defaults = { + data: function (params) { + return $.extend({}, params, { + q: params.term + }); + }, + transport: function (params, success, failure) { + var $request = $.ajax(params); + + $request.then(success); + $request.fail(failure); + + return $request; + } + }; + + return $.extend({}, defaults, options, true); + }; + + AjaxAdapter.prototype.processResults = function (results) { + return results; + }; + + AjaxAdapter.prototype.query = function (params, callback) { + var matches = []; + var self = this; + + if (this._request != null) { + // JSONP requests cannot always be aborted + if ($.isFunction(this._request.abort)) { + this._request.abort(); + } + + this._request = null; + } + + var options = $.extend({ + type: 'GET' + }, this.ajaxOptions); + + if (typeof options.url === 'function') { + options.url = options.url.call(this.$element, params); + } + + if (typeof options.data === 'function') { + options.data = options.data.call(this.$element, params); + } + + function request () { + var $request = options.transport(options, function (data) { + var results = self.processResults(data, params); + + if (self.options.get('debug') && window.console && console.error) { + // Check to make sure that the response included a `results` key. + if (!results || !results.results || !$.isArray(results.results)) { + console.error( + 'Select2: The AJAX results did not return an array in the ' + + '`results` key of the response.' + ); + } + } + + callback(results); + }, function () { + // Attempt to detect if a request was aborted + // Only works if the transport exposes a status property + if ($request.status && $request.status === '0') { + return; + } + + self.trigger('results:message', { + message: 'errorLoading' + }); + }); + + self._request = $request; + } + + if (this.ajaxOptions.delay && params.term != null) { + if (this._queryTimeout) { + window.clearTimeout(this._queryTimeout); + } + + this._queryTimeout = window.setTimeout(request, this.ajaxOptions.delay); + } else { + request(); + } + }; + + return AjaxAdapter; +}); + +S2.define('select2/data/tags',[ + 'jquery' +], function ($) { + function Tags (decorated, $element, options) { + var tags = options.get('tags'); + + var createTag = options.get('createTag'); + + if (createTag !== undefined) { + this.createTag = createTag; + } + + var insertTag = options.get('insertTag'); + + if (insertTag !== undefined) { + this.insertTag = insertTag; + } + + decorated.call(this, $element, options); + + if ($.isArray(tags)) { + for (var t = 0; t < tags.length; t++) { + var tag = tags[t]; + var item = this._normalizeItem(tag); + + var $option = this.option(item); + + this.$element.append($option); + } + } + } + + Tags.prototype.query = function (decorated, params, callback) { + var self = this; + + this._removeOldTags(); + + if (params.term == null || params.page != null) { + decorated.call(this, params, callback); + return; + } + + function wrapper (obj, child) { + var data = obj.results; + + for (var i = 0; i < data.length; i++) { + var option = data[i]; + + var checkChildren = ( + option.children != null && + !wrapper({ + results: option.children + }, true) + ); + + var checkText = option.text === params.term; + + if (checkText || checkChildren) { + if (child) { + return false; + } + + obj.data = data; + callback(obj); + + return; + } + } + + if (child) { + return true; + } + + var tag = self.createTag(params); + + if (tag != null) { + var $option = self.option(tag); + $option.attr('data-select2-tag', true); + + self.addOptions([$option]); + + self.insertTag(data, tag); + } + + obj.results = data; + + callback(obj); + } + + decorated.call(this, params, wrapper); + }; + + Tags.prototype.createTag = function (decorated, params) { + var term = $.trim(params.term); + + if (term === '') { + return null; + } + + return { + id: term, + text: term + }; + }; + + Tags.prototype.insertTag = function (_, data, tag) { + data.unshift(tag); + }; + + Tags.prototype._removeOldTags = function (_) { + var tag = this._lastTag; + + var $options = this.$element.find('option[data-select2-tag]'); + + $options.each(function () { + if (this.selected) { + return; + } + + $(this).remove(); + }); + }; + + return Tags; +}); + +S2.define('select2/data/tokenizer',[ + 'jquery' +], function ($) { + function Tokenizer (decorated, $element, options) { + var tokenizer = options.get('tokenizer'); + + if (tokenizer !== undefined) { + this.tokenizer = tokenizer; + } + + decorated.call(this, $element, options); + } + + Tokenizer.prototype.bind = function (decorated, container, $container) { + decorated.call(this, container, $container); + + this.$search = container.dropdown.$search || container.selection.$search || + $container.find('.select2-search__field'); + }; + + Tokenizer.prototype.query = function (decorated, params, callback) { + var self = this; + + function createAndSelect (data) { + // Normalize the data object so we can use it for checks + var item = self._normalizeItem(data); + + // Check if the data object already exists as a tag + // Select it if it doesn't + var $existingOptions = self.$element.find('option').filter(function () { + return $(this).val() === item.id; + }); + + // If an existing option wasn't found for it, create the option + if (!$existingOptions.length) { + var $option = self.option(item); + $option.attr('data-select2-tag', true); + + self._removeOldTags(); + self.addOptions([$option]); + } + + // Select the item, now that we know there is an option for it + select(item); + } + + function select (data) { + self.trigger('select', { + data: data + }); + } + + params.term = params.term || ''; + + var tokenData = this.tokenizer(params, this.options, createAndSelect); + + if (tokenData.term !== params.term) { + // Replace the search term if we have the search box + if (this.$search.length) { + this.$search.val(tokenData.term); + this.$search.focus(); + } + + params.term = tokenData.term; + } + + decorated.call(this, params, callback); + }; + + Tokenizer.prototype.tokenizer = function (_, params, options, callback) { + var separators = options.get('tokenSeparators') || []; + var term = params.term; + var i = 0; + + var createTag = this.createTag || function (params) { + return { + id: params.term, + text: params.term + }; + }; + + while (i < term.length) { + var termChar = term[i]; + + if ($.inArray(termChar, separators) === -1) { + i++; + + continue; + } + + var part = term.substr(0, i); + var partParams = $.extend({}, params, { + term: part + }); + + var data = createTag(partParams); + + if (data == null) { + i++; + continue; + } + + callback(data); + + // Reset the term to not include the tokenized portion + term = term.substr(i + 1) || ''; + i = 0; + } + + return { + term: term + }; + }; + + return Tokenizer; +}); + +S2.define('select2/data/minimumInputLength',[ + +], function () { + function MinimumInputLength (decorated, $e, options) { + this.minimumInputLength = options.get('minimumInputLength'); + + decorated.call(this, $e, options); + } + + MinimumInputLength.prototype.query = function (decorated, params, callback) { + params.term = params.term || ''; + + if (params.term.length < this.minimumInputLength) { + this.trigger('results:message', { + message: 'inputTooShort', + args: { + minimum: this.minimumInputLength, + input: params.term, + params: params + } + }); + + return; + } + + decorated.call(this, params, callback); + }; + + return MinimumInputLength; +}); + +S2.define('select2/data/maximumInputLength',[ + +], function () { + function MaximumInputLength (decorated, $e, options) { + this.maximumInputLength = options.get('maximumInputLength'); + + decorated.call(this, $e, options); + } + + MaximumInputLength.prototype.query = function (decorated, params, callback) { + params.term = params.term || ''; + + if (this.maximumInputLength > 0 && + params.term.length > this.maximumInputLength) { + this.trigger('results:message', { + message: 'inputTooLong', + args: { + maximum: this.maximumInputLength, + input: params.term, + params: params + } + }); + + return; + } + + decorated.call(this, params, callback); + }; + + return MaximumInputLength; +}); + +S2.define('select2/data/maximumSelectionLength',[ + +], function (){ + function MaximumSelectionLength (decorated, $e, options) { + this.maximumSelectionLength = options.get('maximumSelectionLength'); + + decorated.call(this, $e, options); + } + + MaximumSelectionLength.prototype.query = + function (decorated, params, callback) { + var self = this; + + this.current(function (currentData) { + var count = currentData != null ? currentData.length : 0; + if (self.maximumSelectionLength > 0 && + count >= self.maximumSelectionLength) { + self.trigger('results:message', { + message: 'maximumSelected', + args: { + maximum: self.maximumSelectionLength + } + }); + return; + } + decorated.call(self, params, callback); + }); + }; + + return MaximumSelectionLength; +}); + +S2.define('select2/dropdown',[ + 'jquery', + './utils' +], function ($, Utils) { + function Dropdown ($element, options) { + this.$element = $element; + this.options = options; + + Dropdown.__super__.constructor.call(this); + } + + Utils.Extend(Dropdown, Utils.Observable); + + Dropdown.prototype.render = function () { + var $dropdown = $( + '<span class="select2-dropdown">' + + '<span class="select2-results"></span>' + + '</span>' + ); + + $dropdown.attr('dir', this.options.get('dir')); + + this.$dropdown = $dropdown; + + return $dropdown; + }; + + Dropdown.prototype.bind = function () { + // Should be implemented in subclasses + }; + + Dropdown.prototype.position = function ($dropdown, $container) { + // Should be implmented in subclasses + }; + + Dropdown.prototype.destroy = function () { + // Remove the dropdown from the DOM + this.$dropdown.remove(); + }; + + return Dropdown; +}); + +S2.define('select2/dropdown/search',[ + 'jquery', + '../utils' +], function ($, Utils) { + function Search () { } + + Search.prototype.render = function (decorated) { + var $rendered = decorated.call(this); + + var $search = $( + '<span class="select2-search select2-search--dropdown">' + + '<input class="select2-search__field" type="search" tabindex="-1"' + + ' autocomplete="off" autocorrect="off" autocapitalize="off"' + + ' spellcheck="false" role="textbox" />' + + '</span>' + ); + + this.$searchContainer = $search; + this.$search = $search.find('input'); + + $rendered.prepend($search); + + return $rendered; + }; + + Search.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + this.$search.on('keydown', function (evt) { + self.trigger('keypress', evt); + + self._keyUpPrevented = evt.isDefaultPrevented(); + }); + + // Workaround for browsers which do not support the `input` event + // This will prevent double-triggering of events for browsers which support + // both the `keyup` and `input` events. + this.$search.on('input', function (evt) { + // Unbind the duplicated `keyup` event + $(this).off('keyup'); + }); + + this.$search.on('keyup input', function (evt) { + self.handleSearch(evt); + }); + + container.on('open', function () { + self.$search.attr('tabindex', 0); + + self.$search.focus(); + + window.setTimeout(function () { + self.$search.focus(); + }, 0); + }); + + container.on('close', function () { + self.$search.attr('tabindex', -1); + + self.$search.val(''); + }); + + container.on('focus', function () { + if (container.isOpen()) { + self.$search.focus(); + } + }); + + container.on('results:all', function (params) { + if (params.query.term == null || params.query.term === '') { + var showSearch = self.showSearch(params); + + if (showSearch) { + self.$searchContainer.removeClass('select2-search--hide'); + } else { + self.$searchContainer.addClass('select2-search--hide'); + } + } + }); + }; + + Search.prototype.handleSearch = function (evt) { + if (!this._keyUpPrevented) { + var input = this.$search.val(); + + this.trigger('query', { + term: input + }); + } + + this._keyUpPrevented = false; + }; + + Search.prototype.showSearch = function (_, params) { + return true; + }; + + return Search; +}); + +S2.define('select2/dropdown/hidePlaceholder',[ + +], function () { + function HidePlaceholder (decorated, $element, options, dataAdapter) { + this.placeholder = this.normalizePlaceholder(options.get('placeholder')); + + decorated.call(this, $element, options, dataAdapter); + } + + HidePlaceholder.prototype.append = function (decorated, data) { + data.results = this.removePlaceholder(data.results); + + decorated.call(this, data); + }; + + HidePlaceholder.prototype.normalizePlaceholder = function (_, placeholder) { + if (typeof placeholder === 'string') { + placeholder = { + id: '', + text: placeholder + }; + } + + return placeholder; + }; + + HidePlaceholder.prototype.removePlaceholder = function (_, data) { + var modifiedData = data.slice(0); + + for (var d = data.length - 1; d >= 0; d--) { + var item = data[d]; + + if (this.placeholder.id === item.id) { + modifiedData.splice(d, 1); + } + } + + return modifiedData; + }; + + return HidePlaceholder; +}); + +S2.define('select2/dropdown/infiniteScroll',[ + 'jquery' +], function ($) { + function InfiniteScroll (decorated, $element, options, dataAdapter) { + this.lastParams = {}; + + decorated.call(this, $element, options, dataAdapter); + + this.$loadingMore = this.createLoadingMore(); + this.loading = false; + } + + InfiniteScroll.prototype.append = function (decorated, data) { + this.$loadingMore.remove(); + this.loading = false; + + decorated.call(this, data); + + if (this.showLoadingMore(data)) { + this.$results.append(this.$loadingMore); + } + }; + + InfiniteScroll.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + container.on('query', function (params) { + self.lastParams = params; + self.loading = true; + }); + + container.on('query:append', function (params) { + self.lastParams = params; + self.loading = true; + }); + + this.$results.on('scroll', function () { + var isLoadMoreVisible = $.contains( + document.documentElement, + self.$loadingMore[0] + ); + + if (self.loading || !isLoadMoreVisible) { + return; + } + + var currentOffset = self.$results.offset().top + + self.$results.outerHeight(false); + var loadingMoreOffset = self.$loadingMore.offset().top + + self.$loadingMore.outerHeight(false); + + if (currentOffset + 50 >= loadingMoreOffset) { + self.loadMore(); + } + }); + }; + + InfiniteScroll.prototype.loadMore = function () { + this.loading = true; + + var params = $.extend({}, {page: 1}, this.lastParams); + + params.page++; + + this.trigger('query:append', params); + }; + + InfiniteScroll.prototype.showLoadingMore = function (_, data) { + return data.pagination && data.pagination.more; + }; + + InfiniteScroll.prototype.createLoadingMore = function () { + var $option = $( + '<li ' + + 'class="select2-results__option select2-results__option--load-more"' + + 'role="treeitem" aria-disabled="true"></li>' + ); + + var message = this.options.get('translations').get('loadingMore'); + + $option.html(message(this.lastParams)); + + return $option; + }; + + return InfiniteScroll; +}); + +S2.define('select2/dropdown/attachBody',[ + 'jquery', + '../utils' +], function ($, Utils) { + function AttachBody (decorated, $element, options) { + this.$dropdownParent = options.get('dropdownParent') || $(document.body); + + decorated.call(this, $element, options); + } + + AttachBody.prototype.bind = function (decorated, container, $container) { + var self = this; + + var setupResultsEvents = false; + + decorated.call(this, container, $container); + + container.on('open', function () { + self._showDropdown(); + self._attachPositioningHandler(container); + + if (!setupResultsEvents) { + setupResultsEvents = true; + + container.on('results:all', function () { + self._positionDropdown(); + self._resizeDropdown(); + }); + + container.on('results:append', function () { + self._positionDropdown(); + self._resizeDropdown(); + }); + } + }); + + container.on('close', function () { + self._hideDropdown(); + self._detachPositioningHandler(container); + }); + + this.$dropdownContainer.on('mousedown', function (evt) { + evt.stopPropagation(); + }); + }; + + AttachBody.prototype.destroy = function (decorated) { + decorated.call(this); + + this.$dropdownContainer.remove(); + }; + + AttachBody.prototype.position = function (decorated, $dropdown, $container) { + // Clone all of the container classes + $dropdown.attr('class', $container.attr('class')); + + $dropdown.removeClass('select2'); + $dropdown.addClass('select2-container--open'); + + $dropdown.css({ + position: 'absolute', + top: -999999 + }); + + this.$container = $container; + }; + + AttachBody.prototype.render = function (decorated) { + var $container = $('<span></span>'); + + var $dropdown = decorated.call(this); + $container.append($dropdown); + + this.$dropdownContainer = $container; + + return $container; + }; + + AttachBody.prototype._hideDropdown = function (decorated) { + this.$dropdownContainer.detach(); + }; + + AttachBody.prototype._attachPositioningHandler = + function (decorated, container) { + var self = this; + + var scrollEvent = 'scroll.select2.' + container.id; + var resizeEvent = 'resize.select2.' + container.id; + var orientationEvent = 'orientationchange.select2.' + container.id; + + var $watchers = this.$container.parents().filter(Utils.hasScroll); + $watchers.each(function () { + $(this).data('select2-scroll-position', { + x: $(this).scrollLeft(), + y: $(this).scrollTop() + }); + }); + + $watchers.on(scrollEvent, function (ev) { + var position = $(this).data('select2-scroll-position'); + $(this).scrollTop(position.y); + }); + + $(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent, + function (e) { + self._positionDropdown(); + self._resizeDropdown(); + }); + }; + + AttachBody.prototype._detachPositioningHandler = + function (decorated, container) { + var scrollEvent = 'scroll.select2.' + container.id; + var resizeEvent = 'resize.select2.' + container.id; + var orientationEvent = 'orientationchange.select2.' + container.id; + + var $watchers = this.$container.parents().filter(Utils.hasScroll); + $watchers.off(scrollEvent); + + $(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent); + }; + + AttachBody.prototype._positionDropdown = function () { + var $window = $(window); + + var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above'); + var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below'); + + var newDirection = null; + + var offset = this.$container.offset(); + + offset.bottom = offset.top + this.$container.outerHeight(false); + + var container = { + height: this.$container.outerHeight(false) + }; + + container.top = offset.top; + container.bottom = offset.top + container.height; + + var dropdown = { + height: this.$dropdown.outerHeight(false) + }; + + var viewport = { + top: $window.scrollTop(), + bottom: $window.scrollTop() + $window.height() + }; + + var enoughRoomAbove = viewport.top < (offset.top - dropdown.height); + var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height); + + var css = { + left: offset.left, + top: container.bottom + }; + + // Determine what the parent element is to use for calciulating the offset + var $offsetParent = this.$dropdownParent; + + // For statically positoned elements, we need to get the element + // that is determining the offset + if ($offsetParent.css('position') === 'static') { + $offsetParent = $offsetParent.offsetParent(); + } + + var parentOffset = $offsetParent.offset(); + + css.top -= parentOffset.top; + css.left -= parentOffset.left; + + if (!isCurrentlyAbove && !isCurrentlyBelow) { + newDirection = 'below'; + } + + if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) { + newDirection = 'above'; + } else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) { + newDirection = 'below'; + } + + if (newDirection == 'above' || + (isCurrentlyAbove && newDirection !== 'below')) { + css.top = container.top - parentOffset.top - dropdown.height; + } + + if (newDirection != null) { + this.$dropdown + .removeClass('select2-dropdown--below select2-dropdown--above') + .addClass('select2-dropdown--' + newDirection); + this.$container + .removeClass('select2-container--below select2-container--above') + .addClass('select2-container--' + newDirection); + } + + this.$dropdownContainer.css(css); + }; + + AttachBody.prototype._resizeDropdown = function () { + var css = { + width: this.$container.outerWidth(false) + 'px' + }; + + if (this.options.get('dropdownAutoWidth')) { + css.minWidth = css.width; + css.position = 'relative'; + css.width = 'auto'; + } + + this.$dropdown.css(css); + }; + + AttachBody.prototype._showDropdown = function (decorated) { + this.$dropdownContainer.appendTo(this.$dropdownParent); + + this._positionDropdown(); + this._resizeDropdown(); + }; + + return AttachBody; +}); + +S2.define('select2/dropdown/minimumResultsForSearch',[ + +], function () { + function countResults (data) { + var count = 0; + + for (var d = 0; d < data.length; d++) { + var item = data[d]; + + if (item.children) { + count += countResults(item.children); + } else { + count++; + } + } + + return count; + } + + function MinimumResultsForSearch (decorated, $element, options, dataAdapter) { + this.minimumResultsForSearch = options.get('minimumResultsForSearch'); + + if (this.minimumResultsForSearch < 0) { + this.minimumResultsForSearch = Infinity; + } + + decorated.call(this, $element, options, dataAdapter); + } + + MinimumResultsForSearch.prototype.showSearch = function (decorated, params) { + if (countResults(params.data.results) < this.minimumResultsForSearch) { + return false; + } + + return decorated.call(this, params); + }; + + return MinimumResultsForSearch; +}); + +S2.define('select2/dropdown/selectOnClose',[ + +], function () { + function SelectOnClose () { } + + SelectOnClose.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + container.on('close', function (params) { + self._handleSelectOnClose(params); + }); + }; + + SelectOnClose.prototype._handleSelectOnClose = function (_, params) { + if (params && params.originalSelect2Event != null) { + var event = params.originalSelect2Event; + + // Don't select an item if the close event was triggered from a select or + // unselect event + if (event._type === 'select' || event._type === 'unselect') { + return; + } + } + + var $highlightedResults = this.getHighlightedResults(); + + // Only select highlighted results + if ($highlightedResults.length < 1) { + return; + } + + var data = $highlightedResults.data('data'); + + // Don't re-select already selected resulte + if ( + (data.element != null && data.element.selected) || + (data.element == null && data.selected) + ) { + return; + } + + this.trigger('select', { + data: data + }); + }; + + return SelectOnClose; +}); + +S2.define('select2/dropdown/closeOnSelect',[ + +], function () { + function CloseOnSelect () { } + + CloseOnSelect.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + container.on('select', function (evt) { + self._selectTriggered(evt); + }); + + container.on('unselect', function (evt) { + self._selectTriggered(evt); + }); + }; + + CloseOnSelect.prototype._selectTriggered = function (_, evt) { + var originalEvent = evt.originalEvent; + + // Don't close if the control key is being held + if (originalEvent && originalEvent.ctrlKey) { + return; + } + + this.trigger('close', { + originalEvent: originalEvent, + originalSelect2Event: evt + }); + }; + + return CloseOnSelect; +}); + +S2.define('select2/i18n/en',[],function () { + // English + return { + errorLoading: function () { + return 'The results could not be loaded.'; + }, + inputTooLong: function (args) { + var overChars = args.input.length - args.maximum; + + var message = 'Please delete ' + overChars + ' character'; + + if (overChars != 1) { + message += 's'; + } + + return message; + }, + inputTooShort: function (args) { + var remainingChars = args.minimum - args.input.length; + + var message = 'Please enter ' + remainingChars + ' or more characters'; + + return message; + }, + loadingMore: function () { + return 'Loading more results…'; + }, + maximumSelected: function (args) { + var message = 'You can only select ' + args.maximum + ' item'; + + if (args.maximum != 1) { + message += 's'; + } + + return message; + }, + noResults: function () { + return 'No results found'; + }, + searching: function () { + return 'Searching…'; + } + }; +}); + +S2.define('select2/defaults',[ + 'jquery', + 'require', + + './results', + + './selection/single', + './selection/multiple', + './selection/placeholder', + './selection/allowClear', + './selection/search', + './selection/eventRelay', + + './utils', + './translation', + './diacritics', + + './data/select', + './data/array', + './data/ajax', + './data/tags', + './data/tokenizer', + './data/minimumInputLength', + './data/maximumInputLength', + './data/maximumSelectionLength', + + './dropdown', + './dropdown/search', + './dropdown/hidePlaceholder', + './dropdown/infiniteScroll', + './dropdown/attachBody', + './dropdown/minimumResultsForSearch', + './dropdown/selectOnClose', + './dropdown/closeOnSelect', + + './i18n/en' +], function ($, require, + + ResultsList, + + SingleSelection, MultipleSelection, Placeholder, AllowClear, + SelectionSearch, EventRelay, + + Utils, Translation, DIACRITICS, + + SelectData, ArrayData, AjaxData, Tags, Tokenizer, + MinimumInputLength, MaximumInputLength, MaximumSelectionLength, + + Dropdown, DropdownSearch, HidePlaceholder, InfiniteScroll, + AttachBody, MinimumResultsForSearch, SelectOnClose, CloseOnSelect, + + EnglishTranslation) { + function Defaults () { + this.reset(); + } + + Defaults.prototype.apply = function (options) { + options = $.extend(true, {}, this.defaults, options); + + if (options.dataAdapter == null) { + if (options.ajax != null) { + options.dataAdapter = AjaxData; + } else if (options.data != null) { + options.dataAdapter = ArrayData; + } else { + options.dataAdapter = SelectData; + } + + if (options.minimumInputLength > 0) { + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + MinimumInputLength + ); + } + + if (options.maximumInputLength > 0) { + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + MaximumInputLength + ); + } + + if (options.maximumSelectionLength > 0) { + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + MaximumSelectionLength + ); + } + + if (options.tags) { + options.dataAdapter = Utils.Decorate(options.dataAdapter, Tags); + } + + if (options.tokenSeparators != null || options.tokenizer != null) { + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + Tokenizer + ); + } + + if (options.query != null) { + var Query = require(options.amdBase + 'compat/query'); + + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + Query + ); + } + + if (options.initSelection != null) { + var InitSelection = require(options.amdBase + 'compat/initSelection'); + + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + InitSelection + ); + } + } + + if (options.resultsAdapter == null) { + options.resultsAdapter = ResultsList; + + if (options.ajax != null) { + options.resultsAdapter = Utils.Decorate( + options.resultsAdapter, + InfiniteScroll + ); + } + + if (options.placeholder != null) { + options.resultsAdapter = Utils.Decorate( + options.resultsAdapter, + HidePlaceholder + ); + } + + if (options.selectOnClose) { + options.resultsAdapter = Utils.Decorate( + options.resultsAdapter, + SelectOnClose + ); + } + } + + if (options.dropdownAdapter == null) { + if (options.multiple) { + options.dropdownAdapter = Dropdown; + } else { + var SearchableDropdown = Utils.Decorate(Dropdown, DropdownSearch); + + options.dropdownAdapter = SearchableDropdown; + } + + if (options.minimumResultsForSearch !== 0) { + options.dropdownAdapter = Utils.Decorate( + options.dropdownAdapter, + MinimumResultsForSearch + ); + } + + if (options.closeOnSelect) { + options.dropdownAdapter = Utils.Decorate( + options.dropdownAdapter, + CloseOnSelect + ); + } + + if ( + options.dropdownCssClass != null || + options.dropdownCss != null || + options.adaptDropdownCssClass != null + ) { + var DropdownCSS = require(options.amdBase + 'compat/dropdownCss'); + + options.dropdownAdapter = Utils.Decorate( + options.dropdownAdapter, + DropdownCSS + ); + } + + options.dropdownAdapter = Utils.Decorate( + options.dropdownAdapter, + AttachBody + ); + } + + if (options.selectionAdapter == null) { + if (options.multiple) { + options.selectionAdapter = MultipleSelection; + } else { + options.selectionAdapter = SingleSelection; + } + + // Add the placeholder mixin if a placeholder was specified + if (options.placeholder != null) { + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + Placeholder + ); + } + + if (options.allowClear) { + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + AllowClear + ); + } + + if (options.multiple) { + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + SelectionSearch + ); + } + + if ( + options.containerCssClass != null || + options.containerCss != null || + options.adaptContainerCssClass != null + ) { + var ContainerCSS = require(options.amdBase + 'compat/containerCss'); + + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + ContainerCSS + ); + } + + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + EventRelay + ); + } + + if (typeof options.language === 'string') { + // Check if the language is specified with a region + if (options.language.indexOf('-') > 0) { + // Extract the region information if it is included + var languageParts = options.language.split('-'); + var baseLanguage = languageParts[0]; + + options.language = [options.language, baseLanguage]; + } else { + options.language = [options.language]; + } + } + + if ($.isArray(options.language)) { + var languages = new Translation(); + options.language.push('en'); + + var languageNames = options.language; + + for (var l = 0; l < languageNames.length; l++) { + var name = languageNames[l]; + var language = {}; + + try { + // Try to load it with the original name + language = Translation.loadPath(name); + } catch (e) { + try { + // If we couldn't load it, check if it wasn't the full path + name = this.defaults.amdLanguageBase + name; + language = Translation.loadPath(name); + } catch (ex) { + // The translation could not be loaded at all. Sometimes this is + // because of a configuration problem, other times this can be + // because of how Select2 helps load all possible translation files. + if (options.debug && window.console && console.warn) { + console.warn( + 'Select2: The language file for "' + name + '" could not be ' + + 'automatically loaded. A fallback will be used instead.' + ); + } + + continue; + } + } + + languages.extend(language); + } + + options.translations = languages; + } else { + var baseTranslation = Translation.loadPath( + this.defaults.amdLanguageBase + 'en' + ); + var customTranslation = new Translation(options.language); + + customTranslation.extend(baseTranslation); + + options.translations = customTranslation; + } + + return options; + }; + + Defaults.prototype.reset = function () { + function stripDiacritics (text) { + // Used 'uni range + named function' from http://jsperf.com/diacritics/18 + function match(a) { + return DIACRITICS[a] || a; + } + + return text.replace(/[^\u0000-\u007E]/g, match); + } + + function matcher (params, data) { + // Always return the object if there is nothing to compare + if ($.trim(params.term) === '') { + return data; + } + + // Do a recursive check for options with children + if (data.children && data.children.length > 0) { + // Clone the data object if there are children + // This is required as we modify the object to remove any non-matches + var match = $.extend(true, {}, data); + + // Check each child of the option + for (var c = data.children.length - 1; c >= 0; c--) { + var child = data.children[c]; + + var matches = matcher(params, child); + + // If there wasn't a match, remove the object in the array + if (matches == null) { + match.children.splice(c, 1); + } + } + + // If any children matched, return the new object + if (match.children.length > 0) { + return match; + } + + // If there were no matching children, check just the plain object + return matcher(params, match); + } + + var original = stripDiacritics(data.text).toUpperCase(); + var term = stripDiacritics(params.term).toUpperCase(); + + // Check if the text contains the term + if (original.indexOf(term) > -1) { + return data; + } + + // If it doesn't contain the term, don't return anything + return null; + } + + this.defaults = { + amdBase: './', + amdLanguageBase: './i18n/', + closeOnSelect: true, + debug: false, + dropdownAutoWidth: false, + escapeMarkup: Utils.escapeMarkup, + language: EnglishTranslation, + matcher: matcher, + minimumInputLength: 0, + maximumInputLength: 0, + maximumSelectionLength: 0, + minimumResultsForSearch: 0, + selectOnClose: false, + sorter: function (data) { + return data; + }, + templateResult: function (result) { + return result.text; + }, + templateSelection: function (selection) { + return selection.text; + }, + theme: 'default', + width: 'resolve' + }; + }; + + Defaults.prototype.set = function (key, value) { + var camelKey = $.camelCase(key); + + var data = {}; + data[camelKey] = value; + + var convertedData = Utils._convertData(data); + + $.extend(this.defaults, convertedData); + }; + + var defaults = new Defaults(); + + return defaults; +}); + +S2.define('select2/options',[ + 'require', + 'jquery', + './defaults', + './utils' +], function (require, $, Defaults, Utils) { + function Options (options, $element) { + this.options = options; + + if ($element != null) { + this.fromElement($element); + } + + this.options = Defaults.apply(this.options); + + if ($element && $element.is('input')) { + var InputCompat = require(this.get('amdBase') + 'compat/inputData'); + + this.options.dataAdapter = Utils.Decorate( + this.options.dataAdapter, + InputCompat + ); + } + } + + Options.prototype.fromElement = function ($e) { + var excludedData = ['select2']; + + if (this.options.multiple == null) { + this.options.multiple = $e.prop('multiple'); + } + + if (this.options.disabled == null) { + this.options.disabled = $e.prop('disabled'); + } + + if (this.options.language == null) { + if ($e.prop('lang')) { + this.options.language = $e.prop('lang').toLowerCase(); + } else if ($e.closest('[lang]').prop('lang')) { + this.options.language = $e.closest('[lang]').prop('lang'); + } + } + + if (this.options.dir == null) { + if ($e.prop('dir')) { + this.options.dir = $e.prop('dir'); + } else if ($e.closest('[dir]').prop('dir')) { + this.options.dir = $e.closest('[dir]').prop('dir'); + } else { + this.options.dir = 'ltr'; + } + } + + $e.prop('disabled', this.options.disabled); + $e.prop('multiple', this.options.multiple); + + if ($e.data('select2Tags')) { + if (this.options.debug && window.console && console.warn) { + console.warn( + 'Select2: The `data-select2-tags` attribute has been changed to ' + + 'use the `data-data` and `data-tags="true"` attributes and will be ' + + 'removed in future versions of Select2.' + ); + } + + $e.data('data', $e.data('select2Tags')); + $e.data('tags', true); + } + + if ($e.data('ajaxUrl')) { + if (this.options.debug && window.console && console.warn) { + console.warn( + 'Select2: The `data-ajax-url` attribute has been changed to ' + + '`data-ajax--url` and support for the old attribute will be removed' + + ' in future versions of Select2.' + ); + } + + $e.attr('ajax--url', $e.data('ajaxUrl')); + $e.data('ajax--url', $e.data('ajaxUrl')); + } + + var dataset = {}; + + // Prefer the element's `dataset` attribute if it exists + // jQuery 1.x does not correctly handle data attributes with multiple dashes + if ($.fn.jquery && $.fn.jquery.substr(0, 2) == '1.' && $e[0].dataset) { + dataset = $.extend(true, {}, $e[0].dataset, $e.data()); + } else { + dataset = $e.data(); + } + + var data = $.extend(true, {}, dataset); + + data = Utils._convertData(data); + + for (var key in data) { + if ($.inArray(key, excludedData) > -1) { + continue; + } + + if ($.isPlainObject(this.options[key])) { + $.extend(this.options[key], data[key]); + } else { + this.options[key] = data[key]; + } + } + + return this; + }; + + Options.prototype.get = function (key) { + return this.options[key]; + }; + + Options.prototype.set = function (key, val) { + this.options[key] = val; + }; + + return Options; +}); + +S2.define('select2/core',[ + 'jquery', + './options', + './utils', + './keys' +], function ($, Options, Utils, KEYS) { + var Select2 = function ($element, options) { + if ($element.data('select2') != null) { + $element.data('select2').destroy(); + } + + this.$element = $element; + + this.id = this._generateId($element); + + options = options || {}; + + this.options = new Options(options, $element); + + Select2.__super__.constructor.call(this); + + // Set up the tabindex + + var tabindex = $element.attr('tabindex') || 0; + $element.data('old-tabindex', tabindex); + $element.attr('tabindex', '-1'); + + // Set up containers and adapters + + var DataAdapter = this.options.get('dataAdapter'); + this.dataAdapter = new DataAdapter($element, this.options); + + var $container = this.render(); + + this._placeContainer($container); + + var SelectionAdapter = this.options.get('selectionAdapter'); + this.selection = new SelectionAdapter($element, this.options); + this.$selection = this.selection.render(); + + this.selection.position(this.$selection, $container); + + var DropdownAdapter = this.options.get('dropdownAdapter'); + this.dropdown = new DropdownAdapter($element, this.options); + this.$dropdown = this.dropdown.render(); + + this.dropdown.position(this.$dropdown, $container); + + var ResultsAdapter = this.options.get('resultsAdapter'); + this.results = new ResultsAdapter($element, this.options, this.dataAdapter); + this.$results = this.results.render(); + + this.results.position(this.$results, this.$dropdown); + + // Bind events + + var self = this; + + // Bind the container to all of the adapters + this._bindAdapters(); + + // Register any DOM event handlers + this._registerDomEvents(); + + // Register any internal event handlers + this._registerDataEvents(); + this._registerSelectionEvents(); + this._registerDropdownEvents(); + this._registerResultsEvents(); + this._registerEvents(); + + // Set the initial state + this.dataAdapter.current(function (initialData) { + self.trigger('selection:update', { + data: initialData + }); + }); + + // Hide the original select + $element.addClass('select2-hidden-accessible'); + $element.attr('aria-hidden', 'true'); + + // Synchronize any monitored attributes + this._syncAttributes(); + + $element.data('select2', this); + }; + + Utils.Extend(Select2, Utils.Observable); + + Select2.prototype._generateId = function ($element) { + var id = ''; + + if ($element.attr('id') != null) { + id = $element.attr('id'); + } else if ($element.attr('name') != null) { + id = $element.attr('name') + '-' + Utils.generateChars(2); + } else { + id = Utils.generateChars(4); + } + + id = id.replace(/(:|\.|\[|\]|,)/g, ''); + id = 'select2-' + id; + + return id; + }; + + Select2.prototype._placeContainer = function ($container) { + $container.insertAfter(this.$element); + + var width = this._resolveWidth(this.$element, this.options.get('width')); + + if (width != null) { + $container.css('width', width); + } + }; + + Select2.prototype._resolveWidth = function ($element, method) { + var WIDTH = /^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i; + + if (method == 'resolve') { + var styleWidth = this._resolveWidth($element, 'style'); + + if (styleWidth != null) { + return styleWidth; + } + + return this._resolveWidth($element, 'element'); + } + + if (method == 'element') { + var elementWidth = $element.outerWidth(false); + + if (elementWidth <= 0) { + return 'auto'; + } + + return elementWidth + 'px'; + } + + if (method == 'style') { + var style = $element.attr('style'); + + if (typeof(style) !== 'string') { + return null; + } + + var attrs = style.split(';'); + + for (var i = 0, l = attrs.length; i < l; i = i + 1) { + var attr = attrs[i].replace(/\s/g, ''); + var matches = attr.match(WIDTH); + + if (matches !== null && matches.length >= 1) { + return matches[1]; + } + } + + return null; + } + + return method; + }; + + Select2.prototype._bindAdapters = function () { + this.dataAdapter.bind(this, this.$container); + this.selection.bind(this, this.$container); + + this.dropdown.bind(this, this.$container); + this.results.bind(this, this.$container); + }; + + Select2.prototype._registerDomEvents = function () { + var self = this; + + this.$element.on('change.select2', function () { + self.dataAdapter.current(function (data) { + self.trigger('selection:update', { + data: data + }); + }); + }); + + this.$element.on('focus.select2', function (evt) { + self.trigger('focus', evt); + }); + + this._syncA = Utils.bind(this._syncAttributes, this); + this._syncS = Utils.bind(this._syncSubtree, this); + + if (this.$element[0].attachEvent) { + this.$element[0].attachEvent('onpropertychange', this._syncA); + } + + var observer = window.MutationObserver || + window.WebKitMutationObserver || + window.MozMutationObserver + ; + + if (observer != null) { + this._observer = new observer(function (mutations) { + $.each(mutations, self._syncA); + $.each(mutations, self._syncS); + }); + this._observer.observe(this.$element[0], { + attributes: true, + childList: true, + subtree: false + }); + } else if (this.$element[0].addEventListener) { + this.$element[0].addEventListener( + 'DOMAttrModified', + self._syncA, + false + ); + this.$element[0].addEventListener( + 'DOMNodeInserted', + self._syncS, + false + ); + this.$element[0].addEventListener( + 'DOMNodeRemoved', + self._syncS, + false + ); + } + }; + + Select2.prototype._registerDataEvents = function () { + var self = this; + + this.dataAdapter.on('*', function (name, params) { + self.trigger(name, params); + }); + }; + + Select2.prototype._registerSelectionEvents = function () { + var self = this; + var nonRelayEvents = ['toggle', 'focus']; + + this.selection.on('toggle', function () { + self.toggleDropdown(); + }); + + this.selection.on('focus', function (params) { + self.focus(params); + }); + + this.selection.on('*', function (name, params) { + if ($.inArray(name, nonRelayEvents) !== -1) { + return; + } + + self.trigger(name, params); + }); + }; + + Select2.prototype._registerDropdownEvents = function () { + var self = this; + + this.dropdown.on('*', function (name, params) { + self.trigger(name, params); + }); + }; + + Select2.prototype._registerResultsEvents = function () { + var self = this; + + this.results.on('*', function (name, params) { + self.trigger(name, params); + }); + }; + + Select2.prototype._registerEvents = function () { + var self = this; + + this.on('open', function () { + self.$container.addClass('select2-container--open'); + }); + + this.on('close', function () { + self.$container.removeClass('select2-container--open'); + }); + + this.on('enable', function () { + self.$container.removeClass('select2-container--disabled'); + }); + + this.on('disable', function () { + self.$container.addClass('select2-container--disabled'); + }); + + this.on('blur', function () { + self.$container.removeClass('select2-container--focus'); + }); + + this.on('query', function (params) { + if (!self.isOpen()) { + self.trigger('open', {}); + } + + this.dataAdapter.query(params, function (data) { + self.trigger('results:all', { + data: data, + query: params + }); + }); + }); + + this.on('query:append', function (params) { + this.dataAdapter.query(params, function (data) { + self.trigger('results:append', { + data: data, + query: params + }); + }); + }); + + this.on('keypress', function (evt) { + var key = evt.which; + + if (self.isOpen()) { + if (key === KEYS.ESC || key === KEYS.TAB || + (key === KEYS.UP && evt.altKey)) { + self.close(); + + evt.preventDefault(); + } else if (key === KEYS.ENTER) { + self.trigger('results:select', {}); + + evt.preventDefault(); + } else if ((key === KEYS.SPACE && evt.ctrlKey)) { + self.trigger('results:toggle', {}); + + evt.preventDefault(); + } else if (key === KEYS.UP) { + self.trigger('results:previous', {}); + + evt.preventDefault(); + } else if (key === KEYS.DOWN) { + self.trigger('results:next', {}); + + evt.preventDefault(); + } + } else { + if (key === KEYS.ENTER || key === KEYS.SPACE || + (key === KEYS.DOWN && evt.altKey)) { + self.open(); + + evt.preventDefault(); + } + } + }); + }; + + Select2.prototype._syncAttributes = function () { + this.options.set('disabled', this.$element.prop('disabled')); + + if (this.options.get('disabled')) { + if (this.isOpen()) { + this.close(); + } + + this.trigger('disable', {}); + } else { + this.trigger('enable', {}); + } + }; + + Select2.prototype._syncSubtree = function (evt, mutations) { + var changed = false; + var self = this; + + // Ignore any mutation events raised for elements that aren't options or + // optgroups. This handles the case when the select element is destroyed + if ( + evt && evt.target && ( + evt.target.nodeName !== 'OPTION' && evt.target.nodeName !== 'OPTGROUP' + ) + ) { + return; + } + + if (!mutations) { + // If mutation events aren't supported, then we can only assume that the + // change affected the selections + changed = true; + } else if (mutations.addedNodes && mutations.addedNodes.length > 0) { + for (var n = 0; n < mutations.addedNodes.length; n++) { + var node = mutations.addedNodes[n]; + + if (node.selected) { + changed = true; + } + } + } else if (mutations.removedNodes && mutations.removedNodes.length > 0) { + changed = true; + } + + // Only re-pull the data if we think there is a change + if (changed) { + this.dataAdapter.current(function (currentData) { + self.trigger('selection:update', { + data: currentData + }); + }); + } + }; + + /** + * Override the trigger method to automatically trigger pre-events when + * there are events that can be prevented. + */ + Select2.prototype.trigger = function (name, args) { + var actualTrigger = Select2.__super__.trigger; + var preTriggerMap = { + 'open': 'opening', + 'close': 'closing', + 'select': 'selecting', + 'unselect': 'unselecting' + }; + + if (args === undefined) { + args = {}; + } + + if (name in preTriggerMap) { + var preTriggerName = preTriggerMap[name]; + var preTriggerArgs = { + prevented: false, + name: name, + args: args + }; + + actualTrigger.call(this, preTriggerName, preTriggerArgs); + + if (preTriggerArgs.prevented) { + args.prevented = true; + + return; + } + } + + actualTrigger.call(this, name, args); + }; + + Select2.prototype.toggleDropdown = function () { + if (this.options.get('disabled')) { + return; + } + + if (this.isOpen()) { + this.close(); + } else { + this.open(); + } + }; + + Select2.prototype.open = function () { + if (this.isOpen()) { + return; + } + + this.trigger('query', {}); + }; + + Select2.prototype.close = function () { + if (!this.isOpen()) { + return; + } + + this.trigger('close', {}); + }; + + Select2.prototype.isOpen = function () { + return this.$container.hasClass('select2-container--open'); + }; + + Select2.prototype.hasFocus = function () { + return this.$container.hasClass('select2-container--focus'); + }; + + Select2.prototype.focus = function (data) { + // No need to re-trigger focus events if we are already focused + if (this.hasFocus()) { + return; + } + + this.$container.addClass('select2-container--focus'); + this.trigger('focus', {}); + }; + + Select2.prototype.enable = function (args) { + if (this.options.get('debug') && window.console && console.warn) { + console.warn( + 'Select2: The `select2("enable")` method has been deprecated and will' + + ' be removed in later Select2 versions. Use $element.prop("disabled")' + + ' instead.' + ); + } + + if (args == null || args.length === 0) { + args = [true]; + } + + var disabled = !args[0]; + + this.$element.prop('disabled', disabled); + }; + + Select2.prototype.data = function () { + if (this.options.get('debug') && + arguments.length > 0 && window.console && console.warn) { + console.warn( + 'Select2: Data can no longer be set using `select2("data")`. You ' + + 'should consider setting the value instead using `$element.val()`.' + ); + } + + var data = []; + + this.dataAdapter.current(function (currentData) { + data = currentData; + }); + + return data; + }; + + Select2.prototype.val = function (args) { + if (this.options.get('debug') && window.console && console.warn) { + console.warn( + 'Select2: The `select2("val")` method has been deprecated and will be' + + ' removed in later Select2 versions. Use $element.val() instead.' + ); + } + + if (args == null || args.length === 0) { + return this.$element.val(); + } + + var newVal = args[0]; + + if ($.isArray(newVal)) { + newVal = $.map(newVal, function (obj) { + return obj.toString(); + }); + } + + this.$element.val(newVal).trigger('change'); + }; + + Select2.prototype.destroy = function () { + this.$container.remove(); + + if (this.$element[0].detachEvent) { + this.$element[0].detachEvent('onpropertychange', this._syncA); + } + + if (this._observer != null) { + this._observer.disconnect(); + this._observer = null; + } else if (this.$element[0].removeEventListener) { + this.$element[0] + .removeEventListener('DOMAttrModified', this._syncA, false); + this.$element[0] + .removeEventListener('DOMNodeInserted', this._syncS, false); + this.$element[0] + .removeEventListener('DOMNodeRemoved', this._syncS, false); + } + + this._syncA = null; + this._syncS = null; + + this.$element.off('.select2'); + this.$element.attr('tabindex', this.$element.data('old-tabindex')); + + this.$element.removeClass('select2-hidden-accessible'); + this.$element.attr('aria-hidden', 'false'); + this.$element.removeData('select2'); + + this.dataAdapter.destroy(); + this.selection.destroy(); + this.dropdown.destroy(); + this.results.destroy(); + + this.dataAdapter = null; + this.selection = null; + this.dropdown = null; + this.results = null; + }; + + Select2.prototype.render = function () { + var $container = $( + '<span class="select2 select2-container">' + + '<span class="selection"></span>' + + '<span class="dropdown-wrapper" aria-hidden="true"></span>' + + '</span>' + ); + + $container.attr('dir', this.options.get('dir')); + + this.$container = $container; + + this.$container.addClass('select2-container--' + this.options.get('theme')); + + $container.data('element', this.$element); + + return $container; + }; + + return Select2; +}); + +S2.define('select2/compat/utils',[ + 'jquery' +], function ($) { + function syncCssClasses ($dest, $src, adapter) { + var classes, replacements = [], adapted; + + classes = $.trim($dest.attr('class')); + + if (classes) { + classes = '' + classes; // for IE which returns object + + $(classes.split(/\s+/)).each(function () { + // Save all Select2 classes + if (this.indexOf('select2-') === 0) { + replacements.push(this); + } + }); + } + + classes = $.trim($src.attr('class')); + + if (classes) { + classes = '' + classes; // for IE which returns object + + $(classes.split(/\s+/)).each(function () { + // Only adapt non-Select2 classes + if (this.indexOf('select2-') !== 0) { + adapted = adapter(this); + + if (adapted != null) { + replacements.push(adapted); + } + } + }); + } + + $dest.attr('class', replacements.join(' ')); + } + + return { + syncCssClasses: syncCssClasses + }; +}); + +S2.define('select2/compat/containerCss',[ + 'jquery', + './utils' +], function ($, CompatUtils) { + // No-op CSS adapter that discards all classes by default + function _containerAdapter (clazz) { + return null; + } + + function ContainerCSS () { } + + ContainerCSS.prototype.render = function (decorated) { + var $container = decorated.call(this); + + var containerCssClass = this.options.get('containerCssClass') || ''; + + if ($.isFunction(containerCssClass)) { + containerCssClass = containerCssClass(this.$element); + } + + var containerCssAdapter = this.options.get('adaptContainerCssClass'); + containerCssAdapter = containerCssAdapter || _containerAdapter; + + if (containerCssClass.indexOf(':all:') !== -1) { + containerCssClass = containerCssClass.replace(':all:', ''); + + var _cssAdapter = containerCssAdapter; + + containerCssAdapter = function (clazz) { + var adapted = _cssAdapter(clazz); + + if (adapted != null) { + // Append the old one along with the adapted one + return adapted + ' ' + clazz; + } + + return clazz; + }; + } + + var containerCss = this.options.get('containerCss') || {}; + + if ($.isFunction(containerCss)) { + containerCss = containerCss(this.$element); + } + + CompatUtils.syncCssClasses($container, this.$element, containerCssAdapter); + + $container.css(containerCss); + $container.addClass(containerCssClass); + + return $container; + }; + + return ContainerCSS; +}); + +S2.define('select2/compat/dropdownCss',[ + 'jquery', + './utils' +], function ($, CompatUtils) { + // No-op CSS adapter that discards all classes by default + function _dropdownAdapter (clazz) { + return null; + } + + function DropdownCSS () { } + + DropdownCSS.prototype.render = function (decorated) { + var $dropdown = decorated.call(this); + + var dropdownCssClass = this.options.get('dropdownCssClass') || ''; + + if ($.isFunction(dropdownCssClass)) { + dropdownCssClass = dropdownCssClass(this.$element); + } + + var dropdownCssAdapter = this.options.get('adaptDropdownCssClass'); + dropdownCssAdapter = dropdownCssAdapter || _dropdownAdapter; + + if (dropdownCssClass.indexOf(':all:') !== -1) { + dropdownCssClass = dropdownCssClass.replace(':all:', ''); + + var _cssAdapter = dropdownCssAdapter; + + dropdownCssAdapter = function (clazz) { + var adapted = _cssAdapter(clazz); + + if (adapted != null) { + // Append the old one along with the adapted one + return adapted + ' ' + clazz; + } + + return clazz; + }; + } + + var dropdownCss = this.options.get('dropdownCss') || {}; + + if ($.isFunction(dropdownCss)) { + dropdownCss = dropdownCss(this.$element); + } + + CompatUtils.syncCssClasses($dropdown, this.$element, dropdownCssAdapter); + + $dropdown.css(dropdownCss); + $dropdown.addClass(dropdownCssClass); + + return $dropdown; + }; + + return DropdownCSS; +}); + +S2.define('select2/compat/initSelection',[ + 'jquery' +], function ($) { + function InitSelection (decorated, $element, options) { + if (options.get('debug') && window.console && console.warn) { + console.warn( + 'Select2: The `initSelection` option has been deprecated in favor' + + ' of a custom data adapter that overrides the `current` method. ' + + 'This method is now called multiple times instead of a single ' + + 'time when the instance is initialized. Support will be removed ' + + 'for the `initSelection` option in future versions of Select2' + ); + } + + this.initSelection = options.get('initSelection'); + this._isInitialized = false; + + decorated.call(this, $element, options); + } + + InitSelection.prototype.current = function (decorated, callback) { + var self = this; + + if (this._isInitialized) { + decorated.call(this, callback); + + return; + } + + this.initSelection.call(null, this.$element, function (data) { + self._isInitialized = true; + + if (!$.isArray(data)) { + data = [data]; + } + + callback(data); + }); + }; + + return InitSelection; +}); + +S2.define('select2/compat/inputData',[ + 'jquery' +], function ($) { + function InputData (decorated, $element, options) { + this._currentData = []; + this._valueSeparator = options.get('valueSeparator') || ','; + + if ($element.prop('type') === 'hidden') { + if (options.get('debug') && console && console.warn) { + console.warn( + 'Select2: Using a hidden input with Select2 is no longer ' + + 'supported and may stop working in the future. It is recommended ' + + 'to use a `<select>` element instead.' + ); + } + } + + decorated.call(this, $element, options); + } + + InputData.prototype.current = function (_, callback) { + function getSelected (data, selectedIds) { + var selected = []; + + if (data.selected || $.inArray(data.id, selectedIds) !== -1) { + data.selected = true; + selected.push(data); + } else { + data.selected = false; + } + + if (data.children) { + selected.push.apply(selected, getSelected(data.children, selectedIds)); + } + + return selected; + } + + var selected = []; + + for (var d = 0; d < this._currentData.length; d++) { + var data = this._currentData[d]; + + selected.push.apply( + selected, + getSelected( + data, + this.$element.val().split( + this._valueSeparator + ) + ) + ); + } + + callback(selected); + }; + + InputData.prototype.select = function (_, data) { + if (!this.options.get('multiple')) { + this.current(function (allData) { + $.map(allData, function (data) { + data.selected = false; + }); + }); + + this.$element.val(data.id); + this.$element.trigger('change'); + } else { + var value = this.$element.val(); + value += this._valueSeparator + data.id; + + this.$element.val(value); + this.$element.trigger('change'); + } + }; + + InputData.prototype.unselect = function (_, data) { + var self = this; + + data.selected = false; + + this.current(function (allData) { + var values = []; + + for (var d = 0; d < allData.length; d++) { + var item = allData[d]; + + if (data.id == item.id) { + continue; + } + + values.push(item.id); + } + + self.$element.val(values.join(self._valueSeparator)); + self.$element.trigger('change'); + }); + }; + + InputData.prototype.query = function (_, params, callback) { + var results = []; + + for (var d = 0; d < this._currentData.length; d++) { + var data = this._currentData[d]; + + var matches = this.matches(params, data); + + if (matches !== null) { + results.push(matches); + } + } + + callback({ + results: results + }); + }; + + InputData.prototype.addOptions = function (_, $options) { + var options = $.map($options, function ($option) { + return $.data($option[0], 'data'); + }); + + this._currentData.push.apply(this._currentData, options); + }; + + return InputData; +}); + +S2.define('select2/compat/matcher',[ + 'jquery' +], function ($) { + function oldMatcher (matcher) { + function wrappedMatcher (params, data) { + var match = $.extend(true, {}, data); + + if (params.term == null || $.trim(params.term) === '') { + return match; + } + + if (data.children) { + for (var c = data.children.length - 1; c >= 0; c--) { + var child = data.children[c]; + + // Check if the child object matches + // The old matcher returned a boolean true or false + var doesMatch = matcher(params.term, child.text, child); + + // If the child didn't match, pop it off + if (!doesMatch) { + match.children.splice(c, 1); + } + } + + if (match.children.length > 0) { + return match; + } + } + + if (matcher(params.term, data.text, data)) { + return match; + } + + return null; + } + + return wrappedMatcher; + } + + return oldMatcher; +}); + +S2.define('select2/compat/query',[ + +], function () { + function Query (decorated, $element, options) { + if (options.get('debug') && window.console && console.warn) { + console.warn( + 'Select2: The `query` option has been deprecated in favor of a ' + + 'custom data adapter that overrides the `query` method. Support ' + + 'will be removed for the `query` option in future versions of ' + + 'Select2.' + ); + } + + decorated.call(this, $element, options); + } + + Query.prototype.query = function (_, params, callback) { + params.callback = callback; + + var query = this.options.get('query'); + + query.call(null, params); + }; + + return Query; +}); + +S2.define('select2/dropdown/attachContainer',[ + +], function () { + function AttachContainer (decorated, $element, options) { + decorated.call(this, $element, options); + } + + AttachContainer.prototype.position = + function (decorated, $dropdown, $container) { + var $dropdownContainer = $container.find('.dropdown-wrapper'); + $dropdownContainer.append($dropdown); + + $dropdown.addClass('select2-dropdown--below'); + $container.addClass('select2-container--below'); + }; + + return AttachContainer; +}); + +S2.define('select2/dropdown/stopPropagation',[ + +], function () { + function StopPropagation () { } + + StopPropagation.prototype.bind = function (decorated, container, $container) { + decorated.call(this, container, $container); + + var stoppedEvents = [ + 'blur', + 'change', + 'click', + 'dblclick', + 'focus', + 'focusin', + 'focusout', + 'input', + 'keydown', + 'keyup', + 'keypress', + 'mousedown', + 'mouseenter', + 'mouseleave', + 'mousemove', + 'mouseover', + 'mouseup', + 'search', + 'touchend', + 'touchstart' + ]; + + this.$dropdown.on(stoppedEvents.join(' '), function (evt) { + evt.stopPropagation(); + }); + }; + + return StopPropagation; +}); + +S2.define('select2/selection/stopPropagation',[ + +], function () { + function StopPropagation () { } + + StopPropagation.prototype.bind = function (decorated, container, $container) { + decorated.call(this, container, $container); + + var stoppedEvents = [ + 'blur', + 'change', + 'click', + 'dblclick', + 'focus', + 'focusin', + 'focusout', + 'input', + 'keydown', + 'keyup', + 'keypress', + 'mousedown', + 'mouseenter', + 'mouseleave', + 'mousemove', + 'mouseover', + 'mouseup', + 'search', + 'touchend', + 'touchstart' + ]; + + this.$selection.on(stoppedEvents.join(' '), function (evt) { + evt.stopPropagation(); + }); + }; + + return StopPropagation; +}); + +/*! + * jQuery Mousewheel 3.1.13 + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license + * http://jquery.org/license + */ + +(function (factory) { + if ( typeof S2.define === 'function' && S2.define.amd ) { + // AMD. Register as an anonymous module. + S2.define('jquery-mousewheel',['jquery'], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS style for Browserify + module.exports = factory; + } else { + // Browser globals + factory(jQuery); + } +}(function ($) { + + var toFix = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'], + toBind = ( 'onwheel' in document || document.documentMode >= 9 ) ? + ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'], + slice = Array.prototype.slice, + nullLowestDeltaTimeout, lowestDelta; + + if ( $.event.fixHooks ) { + for ( var i = toFix.length; i; ) { + $.event.fixHooks[ toFix[--i] ] = $.event.mouseHooks; + } + } + + var special = $.event.special.mousewheel = { + version: '3.1.12', + + setup: function() { + if ( this.addEventListener ) { + for ( var i = toBind.length; i; ) { + this.addEventListener( toBind[--i], handler, false ); + } + } else { + this.onmousewheel = handler; + } + // Store the line height and page height for this particular element + $.data(this, 'mousewheel-line-height', special.getLineHeight(this)); + $.data(this, 'mousewheel-page-height', special.getPageHeight(this)); + }, + + teardown: function() { + if ( this.removeEventListener ) { + for ( var i = toBind.length; i; ) { + this.removeEventListener( toBind[--i], handler, false ); + } + } else { + this.onmousewheel = null; + } + // Clean up the data we added to the element + $.removeData(this, 'mousewheel-line-height'); + $.removeData(this, 'mousewheel-page-height'); + }, + + getLineHeight: function(elem) { + var $elem = $(elem), + $parent = $elem['offsetParent' in $.fn ? 'offsetParent' : 'parent'](); + if (!$parent.length) { + $parent = $('body'); + } + return parseInt($parent.css('fontSize'), 10) || parseInt($elem.css('fontSize'), 10) || 16; + }, + + getPageHeight: function(elem) { + return $(elem).height(); + }, + + settings: { + adjustOldDeltas: true, // see shouldAdjustOldDeltas() below + normalizeOffset: true // calls getBoundingClientRect for each event + } + }; + + $.fn.extend({ + mousewheel: function(fn) { + return fn ? this.bind('mousewheel', fn) : this.trigger('mousewheel'); + }, + + unmousewheel: function(fn) { + return this.unbind('mousewheel', fn); + } + }); + + + function handler(event) { + var orgEvent = event || window.event, + args = slice.call(arguments, 1), + delta = 0, + deltaX = 0, + deltaY = 0, + absDelta = 0, + offsetX = 0, + offsetY = 0; + event = $.event.fix(orgEvent); + event.type = 'mousewheel'; + + // Old school scrollwheel delta + if ( 'detail' in orgEvent ) { deltaY = orgEvent.detail * -1; } + if ( 'wheelDelta' in orgEvent ) { deltaY = orgEvent.wheelDelta; } + if ( 'wheelDeltaY' in orgEvent ) { deltaY = orgEvent.wheelDeltaY; } + if ( 'wheelDeltaX' in orgEvent ) { deltaX = orgEvent.wheelDeltaX * -1; } + + // Firefox < 17 horizontal scrolling related to DOMMouseScroll event + if ( 'axis' in orgEvent && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) { + deltaX = deltaY * -1; + deltaY = 0; + } + + // Set delta to be deltaY or deltaX if deltaY is 0 for backwards compatabilitiy + delta = deltaY === 0 ? deltaX : deltaY; + + // New school wheel delta (wheel event) + if ( 'deltaY' in orgEvent ) { + deltaY = orgEvent.deltaY * -1; + delta = deltaY; + } + if ( 'deltaX' in orgEvent ) { + deltaX = orgEvent.deltaX; + if ( deltaY === 0 ) { delta = deltaX * -1; } + } + + // No change actually happened, no reason to go any further + if ( deltaY === 0 && deltaX === 0 ) { return; } + + // Need to convert lines and pages to pixels if we aren't already in pixels + // There are three delta modes: + // * deltaMode 0 is by pixels, nothing to do + // * deltaMode 1 is by lines + // * deltaMode 2 is by pages + if ( orgEvent.deltaMode === 1 ) { + var lineHeight = $.data(this, 'mousewheel-line-height'); + delta *= lineHeight; + deltaY *= lineHeight; + deltaX *= lineHeight; + } else if ( orgEvent.deltaMode === 2 ) { + var pageHeight = $.data(this, 'mousewheel-page-height'); + delta *= pageHeight; + deltaY *= pageHeight; + deltaX *= pageHeight; + } + + // Store lowest absolute delta to normalize the delta values + absDelta = Math.max( Math.abs(deltaY), Math.abs(deltaX) ); + + if ( !lowestDelta || absDelta < lowestDelta ) { + lowestDelta = absDelta; + + // Adjust older deltas if necessary + if ( shouldAdjustOldDeltas(orgEvent, absDelta) ) { + lowestDelta /= 40; + } + } + + // Adjust older deltas if necessary + if ( shouldAdjustOldDeltas(orgEvent, absDelta) ) { + // Divide all the things by 40! + delta /= 40; + deltaX /= 40; + deltaY /= 40; + } + + // Get a whole, normalized value for the deltas + delta = Math[ delta >= 1 ? 'floor' : 'ceil' ](delta / lowestDelta); + deltaX = Math[ deltaX >= 1 ? 'floor' : 'ceil' ](deltaX / lowestDelta); + deltaY = Math[ deltaY >= 1 ? 'floor' : 'ceil' ](deltaY / lowestDelta); + + // Normalise offsetX and offsetY properties + if ( special.settings.normalizeOffset && this.getBoundingClientRect ) { + var boundingRect = this.getBoundingClientRect(); + offsetX = event.clientX - boundingRect.left; + offsetY = event.clientY - boundingRect.top; + } + + // Add information to the event object + event.deltaX = deltaX; + event.deltaY = deltaY; + event.deltaFactor = lowestDelta; + event.offsetX = offsetX; + event.offsetY = offsetY; + // Go ahead and set deltaMode to 0 since we converted to pixels + // Although this is a little odd since we overwrite the deltaX/Y + // properties with normalized deltas. + event.deltaMode = 0; + + // Add event and delta to the front of the arguments + args.unshift(event, delta, deltaX, deltaY); + + // Clearout lowestDelta after sometime to better + // handle multiple device types that give different + // a different lowestDelta + // Ex: trackpad = 3 and mouse wheel = 120 + if (nullLowestDeltaTimeout) { clearTimeout(nullLowestDeltaTimeout); } + nullLowestDeltaTimeout = setTimeout(nullLowestDelta, 200); + + return ($.event.dispatch || $.event.handle).apply(this, args); + } + + function nullLowestDelta() { + lowestDelta = null; + } + + function shouldAdjustOldDeltas(orgEvent, absDelta) { + // If this is an older event and the delta is divisable by 120, + // then we are assuming that the browser is treating this as an + // older mouse wheel event and that we should divide the deltas + // by 40 to try and get a more usable deltaFactor. + // Side note, this actually impacts the reported scroll distance + // in older browsers and can cause scrolling to be slower than native. + // Turn this off by setting $.event.special.mousewheel.settings.adjustOldDeltas to false. + return special.settings.adjustOldDeltas && orgEvent.type === 'mousewheel' && absDelta % 120 === 0; + } + +})); + +S2.define('jquery.select2',[ + 'jquery', + 'jquery-mousewheel', + + './select2/core', + './select2/defaults' +], function ($, _, Select2, Defaults) { + if ($.fn.select2 == null) { + // All methods that should return the element + var thisMethods = ['open', 'close', 'destroy']; + + $.fn.select2 = function (options) { + options = options || {}; + + if (typeof options === 'object') { + this.each(function () { + var instanceOptions = $.extend(true, {}, options); + + var instance = new Select2($(this), instanceOptions); + }); + + return this; + } else if (typeof options === 'string') { + var ret; + var args = Array.prototype.slice.call(arguments, 1); + + this.each(function () { + var instance = $(this).data('select2'); + + if (instance == null && window.console && console.error) { + console.error( + 'The select2(\'' + options + '\') method was called on an ' + + 'element that is not using Select2.' + ); + } + + ret = instance[options].apply(instance, args); + }); + + // Check if we should be returning `this` + if ($.inArray(options, thisMethods) > -1) { + return this; + } + + return ret; + } else { + throw new Error('Invalid arguments for Select2: ' + options); + } + }; + } + + if ($.fn.select2.defaults == null) { + $.fn.select2.defaults = Defaults; + } + + return Select2; +}); + + // Return the AMD loader configuration so it can be used outside of this file + return { + define: S2.define, + require: S2.require + }; +}()); + + // Autoload the jQuery bindings + // We know that all of the modules exist above this, so we're safe + var select2 = S2.require('jquery.select2'); + + // Hold the AMD module references on the jQuery function that was just loaded + // This allows Select2 to use the internal loader outside of this file, such + // as in the language files. + jQuery.fn.select2.amd = S2; + + // Return the Select2 instance for anyone who is importing it. + return select2; +})); diff --git a/django/contrib/admin/static/admin/js/vendor/select2/select2.full.min.js b/django/contrib/admin/static/admin/js/vendor/select2/select2.full.min.js new file mode 100644 index 000000000000..684edf323888 --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/select2/select2.full.min.js @@ -0,0 +1,3 @@ +/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports?require("jquery"):jQuery)}(function(a){var b=function(){if(a&&a.fn&&a.fn.select2&&a.fn.select2.amd)var b=a.fn.select2.amd;var b;return function(){if(!b||!b.requirejs){b?c=b:b={};var a,c,d;!function(b){function e(a,b){return u.call(a,b)}function f(a,b){var c,d,e,f,g,h,i,j,k,l,m,n=b&&b.split("/"),o=s.map,p=o&&o["*"]||{};if(a&&"."===a.charAt(0))if(b){for(a=a.split("/"),g=a.length-1,s.nodeIdCompat&&w.test(a[g])&&(a[g]=a[g].replace(w,"")),a=n.slice(0,n.length-1).concat(a),k=0;k<a.length;k+=1)if(m=a[k],"."===m)a.splice(k,1),k-=1;else if(".."===m){if(1===k&&(".."===a[2]||".."===a[0]))break;k>0&&(a.splice(k-1,2),k-=2)}a=a.join("/")}else 0===a.indexOf("./")&&(a=a.substring(2));if((n||p)&&o){for(c=a.split("/"),k=c.length;k>0;k-=1){if(d=c.slice(0,k).join("/"),n)for(l=n.length;l>0;l-=1)if(e=o[n.slice(0,l).join("/")],e&&(e=e[d])){f=e,h=k;break}if(f)break;!i&&p&&p[d]&&(i=p[d],j=k)}!f&&i&&(f=i,h=j),f&&(c.splice(0,h,f),a=c.join("/"))}return a}function g(a,c){return function(){var d=v.call(arguments,0);return"string"!=typeof d[0]&&1===d.length&&d.push(null),n.apply(b,d.concat([a,c]))}}function h(a){return function(b){return f(b,a)}}function i(a){return function(b){q[a]=b}}function j(a){if(e(r,a)){var c=r[a];delete r[a],t[a]=!0,m.apply(b,c)}if(!e(q,a)&&!e(t,a))throw new Error("No "+a);return q[a]}function k(a){var b,c=a?a.indexOf("!"):-1;return c>-1&&(b=a.substring(0,c),a=a.substring(c+1,a.length)),[b,a]}function l(a){return function(){return s&&s.config&&s.config[a]||{}}}var m,n,o,p,q={},r={},s={},t={},u=Object.prototype.hasOwnProperty,v=[].slice,w=/\.js$/;o=function(a,b){var c,d=k(a),e=d[0];return a=d[1],e&&(e=f(e,b),c=j(e)),e?a=c&&c.normalize?c.normalize(a,h(b)):f(a,b):(a=f(a,b),d=k(a),e=d[0],a=d[1],e&&(c=j(e))),{f:e?e+"!"+a:a,n:a,pr:e,p:c}},p={require:function(a){return g(a)},exports:function(a){var b=q[a];return"undefined"!=typeof b?b:q[a]={}},module:function(a){return{id:a,uri:"",exports:q[a],config:l(a)}}},m=function(a,c,d,f){var h,k,l,m,n,s,u=[],v=typeof d;if(f=f||a,"undefined"===v||"function"===v){for(c=!c.length&&d.length?["require","exports","module"]:c,n=0;n<c.length;n+=1)if(m=o(c[n],f),k=m.f,"require"===k)u[n]=p.require(a);else if("exports"===k)u[n]=p.exports(a),s=!0;else if("module"===k)h=u[n]=p.module(a);else if(e(q,k)||e(r,k)||e(t,k))u[n]=j(k);else{if(!m.p)throw new Error(a+" missing "+k);m.p.load(m.n,g(f,!0),i(k),{}),u[n]=q[k]}l=d?d.apply(q[a],u):void 0,a&&(h&&h.exports!==b&&h.exports!==q[a]?q[a]=h.exports:l===b&&s||(q[a]=l))}else a&&(q[a]=d)},a=c=n=function(a,c,d,e,f){if("string"==typeof a)return p[a]?p[a](c):j(o(a,c).f);if(!a.splice){if(s=a,s.deps&&n(s.deps,s.callback),!c)return;c.splice?(a=c,c=d,d=null):a=b}return c=c||function(){},"function"==typeof d&&(d=e,e=f),e?m(b,a,c,d):setTimeout(function(){m(b,a,c,d)},4),n},n.config=function(a){return n(a)},a._defined=q,d=function(a,b,c){if("string"!=typeof a)throw new Error("See almond README: incorrect module build, no module name");b.splice||(c=b,b=[]),e(q,a)||e(r,a)||(r[a]=[a,b,c])},d.amd={jQuery:!0}}(),b.requirejs=a,b.require=c,b.define=d}}(),b.define("almond",function(){}),b.define("jquery",[],function(){var b=a||$;return null==b&&console&&console.error&&console.error("Select2: An instance of jQuery or a jQuery-compatible library was not found. Make sure that you are including jQuery before Select2 on your web page."),b}),b.define("select2/utils",["jquery"],function(a){function b(a){var b=a.prototype,c=[];for(var d in b){var e=b[d];"function"==typeof e&&"constructor"!==d&&c.push(d)}return c}var c={};c.Extend=function(a,b){function c(){this.constructor=a}var d={}.hasOwnProperty;for(var e in b)d.call(b,e)&&(a[e]=b[e]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a},c.Decorate=function(a,c){function d(){var b=Array.prototype.unshift,d=c.prototype.constructor.length,e=a.prototype.constructor;d>0&&(b.call(arguments,a.prototype.constructor),e=c.prototype.constructor),e.apply(this,arguments)}function e(){this.constructor=d}var f=b(c),g=b(a);c.displayName=a.displayName,d.prototype=new e;for(var h=0;h<g.length;h++){var i=g[h];d.prototype[i]=a.prototype[i]}for(var j=(function(a){var b=function(){};a in d.prototype&&(b=d.prototype[a]);var e=c.prototype[a];return function(){var a=Array.prototype.unshift;return a.call(arguments,b),e.apply(this,arguments)}}),k=0;k<f.length;k++){var l=f[k];d.prototype[l]=j(l)}return d};var d=function(){this.listeners={}};return d.prototype.on=function(a,b){this.listeners=this.listeners||{},a in this.listeners?this.listeners[a].push(b):this.listeners[a]=[b]},d.prototype.trigger=function(a){var b=Array.prototype.slice,c=b.call(arguments,1);this.listeners=this.listeners||{},null==c&&(c=[]),0===c.length&&c.push({}),c[0]._type=a,a in this.listeners&&this.invoke(this.listeners[a],b.call(arguments,1)),"*"in this.listeners&&this.invoke(this.listeners["*"],arguments)},d.prototype.invoke=function(a,b){for(var c=0,d=a.length;d>c;c++)a[c].apply(this,b)},c.Observable=d,c.generateChars=function(a){for(var b="",c=0;a>c;c++){var d=Math.floor(36*Math.random());b+=d.toString(36)}return b},c.bind=function(a,b){return function(){a.apply(b,arguments)}},c._convertData=function(a){for(var b in a){var c=b.split("-"),d=a;if(1!==c.length){for(var e=0;e<c.length;e++){var f=c[e];f=f.substring(0,1).toLowerCase()+f.substring(1),f in d||(d[f]={}),e==c.length-1&&(d[f]=a[b]),d=d[f]}delete a[b]}}return a},c.hasScroll=function(b,c){var d=a(c),e=c.style.overflowX,f=c.style.overflowY;return e!==f||"hidden"!==f&&"visible"!==f?"scroll"===e||"scroll"===f?!0:d.innerHeight()<c.scrollHeight||d.innerWidth()<c.scrollWidth:!1},c.escapeMarkup=function(a){var b={"\\":"\","&":"&","<":"<",">":">",'"':""","'":"'","/":"/"};return"string"!=typeof a?a:String(a).replace(/[&<>"'\/\\]/g,function(a){return b[a]})},c.appendMany=function(b,c){if("1.7"===a.fn.jquery.substr(0,3)){var d=a();a.map(c,function(a){d=d.add(a)}),c=d}b.append(c)},c}),b.define("select2/results",["jquery","./utils"],function(a,b){function c(a,b,d){this.$element=a,this.data=d,this.options=b,c.__super__.constructor.call(this)}return b.Extend(c,b.Observable),c.prototype.render=function(){var b=a('<ul class="select2-results__options" role="tree"></ul>');return this.options.get("multiple")&&b.attr("aria-multiselectable","true"),this.$results=b,b},c.prototype.clear=function(){this.$results.empty()},c.prototype.displayMessage=function(b){var c=this.options.get("escapeMarkup");this.clear(),this.hideLoading();var d=a('<li role="treeitem" aria-live="assertive" class="select2-results__option"></li>'),e=this.options.get("translations").get(b.message);d.append(c(e(b.args))),d[0].className+=" select2-results__message",this.$results.append(d)},c.prototype.hideMessages=function(){this.$results.find(".select2-results__message").remove()},c.prototype.append=function(a){this.hideLoading();var b=[];if(null==a.results||0===a.results.length)return void(0===this.$results.children().length&&this.trigger("results:message",{message:"noResults"}));a.results=this.sort(a.results);for(var c=0;c<a.results.length;c++){var d=a.results[c],e=this.option(d);b.push(e)}this.$results.append(b)},c.prototype.position=function(a,b){var c=b.find(".select2-results");c.append(a)},c.prototype.sort=function(a){var b=this.options.get("sorter");return b(a)},c.prototype.highlightFirstItem=function(){var a=this.$results.find(".select2-results__option[aria-selected]"),b=a.filter("[aria-selected=true]");b.length>0?b.first().trigger("mouseenter"):a.first().trigger("mouseenter"),this.ensureHighlightVisible()},c.prototype.setClasses=function(){var b=this;this.data.current(function(c){var d=a.map(c,function(a){return a.id.toString()}),e=b.$results.find(".select2-results__option[aria-selected]");e.each(function(){var b=a(this),c=a.data(this,"data"),e=""+c.id;null!=c.element&&c.element.selected||null==c.element&&a.inArray(e,d)>-1?b.attr("aria-selected","true"):b.attr("aria-selected","false")})})},c.prototype.showLoading=function(a){this.hideLoading();var b=this.options.get("translations").get("searching"),c={disabled:!0,loading:!0,text:b(a)},d=this.option(c);d.className+=" loading-results",this.$results.prepend(d)},c.prototype.hideLoading=function(){this.$results.find(".loading-results").remove()},c.prototype.option=function(b){var c=document.createElement("li");c.className="select2-results__option";var d={role:"treeitem","aria-selected":"false"};b.disabled&&(delete d["aria-selected"],d["aria-disabled"]="true"),null==b.id&&delete d["aria-selected"],null!=b._resultId&&(c.id=b._resultId),b.title&&(c.title=b.title),b.children&&(d.role="group",d["aria-label"]=b.text,delete d["aria-selected"]);for(var e in d){var f=d[e];c.setAttribute(e,f)}if(b.children){var g=a(c),h=document.createElement("strong");h.className="select2-results__group";a(h);this.template(b,h);for(var i=[],j=0;j<b.children.length;j++){var k=b.children[j],l=this.option(k);i.push(l)}var m=a("<ul></ul>",{"class":"select2-results__options select2-results__options--nested"});m.append(i),g.append(h),g.append(m)}else this.template(b,c);return a.data(c,"data",b),c},c.prototype.bind=function(b,c){var d=this,e=b.id+"-results";this.$results.attr("id",e),b.on("results:all",function(a){d.clear(),d.append(a.data),b.isOpen()&&(d.setClasses(),d.highlightFirstItem())}),b.on("results:append",function(a){d.append(a.data),b.isOpen()&&d.setClasses()}),b.on("query",function(a){d.hideMessages(),d.showLoading(a)}),b.on("select",function(){b.isOpen()&&(d.setClasses(),d.highlightFirstItem())}),b.on("unselect",function(){b.isOpen()&&(d.setClasses(),d.highlightFirstItem())}),b.on("open",function(){d.$results.attr("aria-expanded","true"),d.$results.attr("aria-hidden","false"),d.setClasses(),d.ensureHighlightVisible()}),b.on("close",function(){d.$results.attr("aria-expanded","false"),d.$results.attr("aria-hidden","true"),d.$results.removeAttr("aria-activedescendant")}),b.on("results:toggle",function(){var a=d.getHighlightedResults();0!==a.length&&a.trigger("mouseup")}),b.on("results:select",function(){var a=d.getHighlightedResults();if(0!==a.length){var b=a.data("data");"true"==a.attr("aria-selected")?d.trigger("close",{}):d.trigger("select",{data:b})}}),b.on("results:previous",function(){var a=d.getHighlightedResults(),b=d.$results.find("[aria-selected]"),c=b.index(a);if(0!==c){var e=c-1;0===a.length&&(e=0);var f=b.eq(e);f.trigger("mouseenter");var g=d.$results.offset().top,h=f.offset().top,i=d.$results.scrollTop()+(h-g);0===e?d.$results.scrollTop(0):0>h-g&&d.$results.scrollTop(i)}}),b.on("results:next",function(){var a=d.getHighlightedResults(),b=d.$results.find("[aria-selected]"),c=b.index(a),e=c+1;if(!(e>=b.length)){var f=b.eq(e);f.trigger("mouseenter");var g=d.$results.offset().top+d.$results.outerHeight(!1),h=f.offset().top+f.outerHeight(!1),i=d.$results.scrollTop()+h-g;0===e?d.$results.scrollTop(0):h>g&&d.$results.scrollTop(i)}}),b.on("results:focus",function(a){a.element.addClass("select2-results__option--highlighted")}),b.on("results:message",function(a){d.displayMessage(a)}),a.fn.mousewheel&&this.$results.on("mousewheel",function(a){var b=d.$results.scrollTop(),c=d.$results.get(0).scrollHeight-b+a.deltaY,e=a.deltaY>0&&b-a.deltaY<=0,f=a.deltaY<0&&c<=d.$results.height();e?(d.$results.scrollTop(0),a.preventDefault(),a.stopPropagation()):f&&(d.$results.scrollTop(d.$results.get(0).scrollHeight-d.$results.height()),a.preventDefault(),a.stopPropagation())}),this.$results.on("mouseup",".select2-results__option[aria-selected]",function(b){var c=a(this),e=c.data("data");return"true"===c.attr("aria-selected")?void(d.options.get("multiple")?d.trigger("unselect",{originalEvent:b,data:e}):d.trigger("close",{})):void d.trigger("select",{originalEvent:b,data:e})}),this.$results.on("mouseenter",".select2-results__option[aria-selected]",function(b){var c=a(this).data("data");d.getHighlightedResults().removeClass("select2-results__option--highlighted"),d.trigger("results:focus",{data:c,element:a(this)})})},c.prototype.getHighlightedResults=function(){var a=this.$results.find(".select2-results__option--highlighted");return a},c.prototype.destroy=function(){this.$results.remove()},c.prototype.ensureHighlightVisible=function(){var a=this.getHighlightedResults();if(0!==a.length){var b=this.$results.find("[aria-selected]"),c=b.index(a),d=this.$results.offset().top,e=a.offset().top,f=this.$results.scrollTop()+(e-d),g=e-d;f-=2*a.outerHeight(!1),2>=c?this.$results.scrollTop(0):(g>this.$results.outerHeight()||0>g)&&this.$results.scrollTop(f)}},c.prototype.template=function(b,c){var d=this.options.get("templateResult"),e=this.options.get("escapeMarkup"),f=d(b,c);null==f?c.style.display="none":"string"==typeof f?c.innerHTML=e(f):a(c).append(f)},c}),b.define("select2/keys",[],function(){var a={BACKSPACE:8,TAB:9,ENTER:13,SHIFT:16,CTRL:17,ALT:18,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,DELETE:46};return a}),b.define("select2/selection/base",["jquery","../utils","../keys"],function(a,b,c){function d(a,b){this.$element=a,this.options=b,d.__super__.constructor.call(this)}return b.Extend(d,b.Observable),d.prototype.render=function(){var b=a('<span class="select2-selection" role="combobox" aria-haspopup="true" aria-expanded="false"></span>');return this._tabindex=0,null!=this.$element.data("old-tabindex")?this._tabindex=this.$element.data("old-tabindex"):null!=this.$element.attr("tabindex")&&(this._tabindex=this.$element.attr("tabindex")),b.attr("title",this.$element.attr("title")),b.attr("tabindex",this._tabindex),this.$selection=b,b},d.prototype.bind=function(a,b){var d=this,e=(a.id+"-container",a.id+"-results");this.container=a,this.$selection.on("focus",function(a){d.trigger("focus",a)}),this.$selection.on("blur",function(a){d._handleBlur(a)}),this.$selection.on("keydown",function(a){d.trigger("keypress",a),a.which===c.SPACE&&a.preventDefault()}),a.on("results:focus",function(a){d.$selection.attr("aria-activedescendant",a.data._resultId)}),a.on("selection:update",function(a){d.update(a.data)}),a.on("open",function(){d.$selection.attr("aria-expanded","true"),d.$selection.attr("aria-owns",e),d._attachCloseHandler(a)}),a.on("close",function(){d.$selection.attr("aria-expanded","false"),d.$selection.removeAttr("aria-activedescendant"),d.$selection.removeAttr("aria-owns"),d.$selection.focus(),d._detachCloseHandler(a)}),a.on("enable",function(){d.$selection.attr("tabindex",d._tabindex)}),a.on("disable",function(){d.$selection.attr("tabindex","-1")})},d.prototype._handleBlur=function(b){var c=this;window.setTimeout(function(){document.activeElement==c.$selection[0]||a.contains(c.$selection[0],document.activeElement)||c.trigger("blur",b)},1)},d.prototype._attachCloseHandler=function(b){a(document.body).on("mousedown.select2."+b.id,function(b){var c=a(b.target),d=c.closest(".select2"),e=a(".select2.select2-container--open");e.each(function(){var b=a(this);if(this!=d[0]){var c=b.data("element");c.select2("close")}})})},d.prototype._detachCloseHandler=function(b){a(document.body).off("mousedown.select2."+b.id)},d.prototype.position=function(a,b){var c=b.find(".selection");c.append(a)},d.prototype.destroy=function(){this._detachCloseHandler(this.container)},d.prototype.update=function(a){throw new Error("The `update` method must be defined in child classes.")},d}),b.define("select2/selection/single",["jquery","./base","../utils","../keys"],function(a,b,c,d){function e(){e.__super__.constructor.apply(this,arguments)}return c.Extend(e,b),e.prototype.render=function(){var a=e.__super__.render.call(this);return a.addClass("select2-selection--single"),a.html('<span class="select2-selection__rendered"></span><span class="select2-selection__arrow" role="presentation"><b role="presentation"></b></span>'),a},e.prototype.bind=function(a,b){var c=this;e.__super__.bind.apply(this,arguments);var d=a.id+"-container";this.$selection.find(".select2-selection__rendered").attr("id",d),this.$selection.attr("aria-labelledby",d),this.$selection.on("mousedown",function(a){1===a.which&&c.trigger("toggle",{originalEvent:a})}),this.$selection.on("focus",function(a){}),this.$selection.on("blur",function(a){}),a.on("focus",function(b){a.isOpen()||c.$selection.focus()}),a.on("selection:update",function(a){c.update(a.data)})},e.prototype.clear=function(){this.$selection.find(".select2-selection__rendered").empty()},e.prototype.display=function(a,b){var c=this.options.get("templateSelection"),d=this.options.get("escapeMarkup");return d(c(a,b))},e.prototype.selectionContainer=function(){return a("<span></span>")},e.prototype.update=function(a){if(0===a.length)return void this.clear();var b=a[0],c=this.$selection.find(".select2-selection__rendered"),d=this.display(b,c);c.empty().append(d),c.prop("title",b.title||b.text)},e}),b.define("select2/selection/multiple",["jquery","./base","../utils"],function(a,b,c){function d(a,b){d.__super__.constructor.apply(this,arguments)}return c.Extend(d,b),d.prototype.render=function(){var a=d.__super__.render.call(this);return a.addClass("select2-selection--multiple"),a.html('<ul class="select2-selection__rendered"></ul>'),a},d.prototype.bind=function(b,c){var e=this;d.__super__.bind.apply(this,arguments),this.$selection.on("click",function(a){e.trigger("toggle",{originalEvent:a})}),this.$selection.on("click",".select2-selection__choice__remove",function(b){if(!e.options.get("disabled")){var c=a(this),d=c.parent(),f=d.data("data");e.trigger("unselect",{originalEvent:b,data:f})}})},d.prototype.clear=function(){this.$selection.find(".select2-selection__rendered").empty()},d.prototype.display=function(a,b){var c=this.options.get("templateSelection"),d=this.options.get("escapeMarkup");return d(c(a,b))},d.prototype.selectionContainer=function(){var b=a('<li class="select2-selection__choice"><span class="select2-selection__choice__remove" role="presentation">×</span></li>');return b},d.prototype.update=function(a){if(this.clear(),0!==a.length){for(var b=[],d=0;d<a.length;d++){var e=a[d],f=this.selectionContainer(),g=this.display(e,f);f.append(g),f.prop("title",e.title||e.text),f.data("data",e),b.push(f)}var h=this.$selection.find(".select2-selection__rendered");c.appendMany(h,b)}},d}),b.define("select2/selection/placeholder",["../utils"],function(a){function b(a,b,c){this.placeholder=this.normalizePlaceholder(c.get("placeholder")),a.call(this,b,c)}return b.prototype.normalizePlaceholder=function(a,b){return"string"==typeof b&&(b={id:"",text:b}),b},b.prototype.createPlaceholder=function(a,b){var c=this.selectionContainer();return c.html(this.display(b)),c.addClass("select2-selection__placeholder").removeClass("select2-selection__choice"),c},b.prototype.update=function(a,b){var c=1==b.length&&b[0].id!=this.placeholder.id,d=b.length>1;if(d||c)return a.call(this,b);this.clear();var e=this.createPlaceholder(this.placeholder);this.$selection.find(".select2-selection__rendered").append(e)},b}),b.define("select2/selection/allowClear",["jquery","../keys"],function(a,b){function c(){}return c.prototype.bind=function(a,b,c){var d=this;a.call(this,b,c),null==this.placeholder&&this.options.get("debug")&&window.console&&console.error&&console.error("Select2: The `allowClear` option should be used in combination with the `placeholder` option."),this.$selection.on("mousedown",".select2-selection__clear",function(a){d._handleClear(a)}),b.on("keypress",function(a){d._handleKeyboardClear(a,b)})},c.prototype._handleClear=function(a,b){if(!this.options.get("disabled")){var c=this.$selection.find(".select2-selection__clear");if(0!==c.length){b.stopPropagation();for(var d=c.data("data"),e=0;e<d.length;e++){var f={data:d[e]};if(this.trigger("unselect",f),f.prevented)return}this.$element.val(this.placeholder.id).trigger("change"),this.trigger("toggle",{})}}},c.prototype._handleKeyboardClear=function(a,c,d){d.isOpen()||(c.which==b.DELETE||c.which==b.BACKSPACE)&&this._handleClear(c)},c.prototype.update=function(b,c){if(b.call(this,c),!(this.$selection.find(".select2-selection__placeholder").length>0||0===c.length)){var d=a('<span class="select2-selection__clear">×</span>');d.data("data",c),this.$selection.find(".select2-selection__rendered").prepend(d)}},c}),b.define("select2/selection/search",["jquery","../utils","../keys"],function(a,b,c){function d(a,b,c){a.call(this,b,c)}return d.prototype.render=function(b){var c=a('<li class="select2-search select2-search--inline"><input class="select2-search__field" type="search" tabindex="-1" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" role="textbox" aria-autocomplete="list" /></li>');this.$searchContainer=c,this.$search=c.find("input");var d=b.call(this);return this._transferTabIndex(),d},d.prototype.bind=function(a,b,d){var e=this;a.call(this,b,d),b.on("open",function(){e.$search.trigger("focus")}),b.on("close",function(){e.$search.val(""),e.$search.removeAttr("aria-activedescendant"),e.$search.trigger("focus")}),b.on("enable",function(){e.$search.prop("disabled",!1),e._transferTabIndex()}),b.on("disable",function(){e.$search.prop("disabled",!0)}),b.on("focus",function(a){e.$search.trigger("focus")}),b.on("results:focus",function(a){e.$search.attr("aria-activedescendant",a.id)}),this.$selection.on("focusin",".select2-search--inline",function(a){e.trigger("focus",a)}),this.$selection.on("focusout",".select2-search--inline",function(a){e._handleBlur(a)}),this.$selection.on("keydown",".select2-search--inline",function(a){a.stopPropagation(),e.trigger("keypress",a),e._keyUpPrevented=a.isDefaultPrevented();var b=a.which;if(b===c.BACKSPACE&&""===e.$search.val()){var d=e.$searchContainer.prev(".select2-selection__choice");if(d.length>0){var f=d.data("data");e.searchRemoveChoice(f),a.preventDefault()}}});var f=document.documentMode,g=f&&11>=f;this.$selection.on("input.searchcheck",".select2-search--inline",function(a){return g?void e.$selection.off("input.search input.searchcheck"):void e.$selection.off("keyup.search")}),this.$selection.on("keyup.search input.search",".select2-search--inline",function(a){if(g&&"input"===a.type)return void e.$selection.off("input.search input.searchcheck");var b=a.which;b!=c.SHIFT&&b!=c.CTRL&&b!=c.ALT&&b!=c.TAB&&e.handleSearch(a)})},d.prototype._transferTabIndex=function(a){this.$search.attr("tabindex",this.$selection.attr("tabindex")),this.$selection.attr("tabindex","-1")},d.prototype.createPlaceholder=function(a,b){this.$search.attr("placeholder",b.text)},d.prototype.update=function(a,b){var c=this.$search[0]==document.activeElement;this.$search.attr("placeholder",""),a.call(this,b),this.$selection.find(".select2-selection__rendered").append(this.$searchContainer),this.resizeSearch(),c&&this.$search.focus()},d.prototype.handleSearch=function(){if(this.resizeSearch(),!this._keyUpPrevented){var a=this.$search.val();this.trigger("query",{term:a})}this._keyUpPrevented=!1},d.prototype.searchRemoveChoice=function(a,b){this.trigger("unselect",{data:b}),this.$search.val(b.text),this.handleSearch()},d.prototype.resizeSearch=function(){this.$search.css("width","25px");var a="";if(""!==this.$search.attr("placeholder"))a=this.$selection.find(".select2-selection__rendered").innerWidth();else{var b=this.$search.val().length+1;a=.75*b+"em"}this.$search.css("width",a)},d}),b.define("select2/selection/eventRelay",["jquery"],function(a){function b(){}return b.prototype.bind=function(b,c,d){var e=this,f=["open","opening","close","closing","select","selecting","unselect","unselecting"],g=["opening","closing","selecting","unselecting"];b.call(this,c,d),c.on("*",function(b,c){if(-1!==a.inArray(b,f)){c=c||{};var d=a.Event("select2:"+b,{params:c});e.$element.trigger(d),-1!==a.inArray(b,g)&&(c.prevented=d.isDefaultPrevented())}})},b}),b.define("select2/translation",["jquery","require"],function(a,b){function c(a){this.dict=a||{}}return c.prototype.all=function(){return this.dict},c.prototype.get=function(a){return this.dict[a]},c.prototype.extend=function(b){this.dict=a.extend({},b.all(),this.dict)},c._cache={},c.loadPath=function(a){if(!(a in c._cache)){var d=b(a);c._cache[a]=d}return new c(c._cache[a])},c}),b.define("select2/diacritics",[],function(){var a={"Ⓐ":"A","A":"A","À":"A","Á":"A","Â":"A","Ầ":"A","Ấ":"A","Ẫ":"A","Ẩ":"A","Ã":"A","Ā":"A","Ă":"A","Ằ":"A","Ắ":"A","Ẵ":"A","Ẳ":"A","Ȧ":"A","Ǡ":"A","Ä":"A","Ǟ":"A","Ả":"A","Å":"A","Ǻ":"A","Ǎ":"A","Ȁ":"A","Ȃ":"A","Ạ":"A","Ậ":"A","Ặ":"A","Ḁ":"A","Ą":"A","Ⱥ":"A","Ɐ":"A","Ꜳ":"AA","Æ":"AE","Ǽ":"AE","Ǣ":"AE","Ꜵ":"AO","Ꜷ":"AU","Ꜹ":"AV","Ꜻ":"AV","Ꜽ":"AY","Ⓑ":"B","B":"B","Ḃ":"B","Ḅ":"B","Ḇ":"B","Ƀ":"B","Ƃ":"B","Ɓ":"B","Ⓒ":"C","C":"C","Ć":"C","Ĉ":"C","Ċ":"C","Č":"C","Ç":"C","Ḉ":"C","Ƈ":"C","Ȼ":"C","Ꜿ":"C","Ⓓ":"D","D":"D","Ḋ":"D","Ď":"D","Ḍ":"D","Ḑ":"D","Ḓ":"D","Ḏ":"D","Đ":"D","Ƌ":"D","Ɗ":"D","Ɖ":"D","Ꝺ":"D","DZ":"DZ","DŽ":"DZ","Dz":"Dz","Dž":"Dz","Ⓔ":"E","E":"E","È":"E","É":"E","Ê":"E","Ề":"E","Ế":"E","Ễ":"E","Ể":"E","Ẽ":"E","Ē":"E","Ḕ":"E","Ḗ":"E","Ĕ":"E","Ė":"E","Ë":"E","Ẻ":"E","Ě":"E","Ȅ":"E","Ȇ":"E","Ẹ":"E","Ệ":"E","Ȩ":"E","Ḝ":"E","Ę":"E","Ḙ":"E","Ḛ":"E","Ɛ":"E","Ǝ":"E","Ⓕ":"F","F":"F","Ḟ":"F","Ƒ":"F","Ꝼ":"F","Ⓖ":"G","G":"G","Ǵ":"G","Ĝ":"G","Ḡ":"G","Ğ":"G","Ġ":"G","Ǧ":"G","Ģ":"G","Ǥ":"G","Ɠ":"G","Ꞡ":"G","Ᵹ":"G","Ꝿ":"G","Ⓗ":"H","H":"H","Ĥ":"H","Ḣ":"H","Ḧ":"H","Ȟ":"H","Ḥ":"H","Ḩ":"H","Ḫ":"H","Ħ":"H","Ⱨ":"H","Ⱶ":"H","Ɥ":"H","Ⓘ":"I","I":"I","Ì":"I","Í":"I","Î":"I","Ĩ":"I","Ī":"I","Ĭ":"I","İ":"I","Ï":"I","Ḯ":"I","Ỉ":"I","Ǐ":"I","Ȉ":"I","Ȋ":"I","Ị":"I","Į":"I","Ḭ":"I","Ɨ":"I","Ⓙ":"J","J":"J","Ĵ":"J","Ɉ":"J","Ⓚ":"K","K":"K","Ḱ":"K","Ǩ":"K","Ḳ":"K","Ķ":"K","Ḵ":"K","Ƙ":"K","Ⱪ":"K","Ꝁ":"K","Ꝃ":"K","Ꝅ":"K","Ꞣ":"K","Ⓛ":"L","L":"L","Ŀ":"L","Ĺ":"L","Ľ":"L","Ḷ":"L","Ḹ":"L","Ļ":"L","Ḽ":"L","Ḻ":"L","Ł":"L","Ƚ":"L","Ɫ":"L","Ⱡ":"L","Ꝉ":"L","Ꝇ":"L","Ꞁ":"L","LJ":"LJ","Lj":"Lj","Ⓜ":"M","M":"M","Ḿ":"M","Ṁ":"M","Ṃ":"M","Ɱ":"M","Ɯ":"M","Ⓝ":"N","N":"N","Ǹ":"N","Ń":"N","Ñ":"N","Ṅ":"N","Ň":"N","Ṇ":"N","Ņ":"N","Ṋ":"N","Ṉ":"N","Ƞ":"N","Ɲ":"N","Ꞑ":"N","Ꞥ":"N","NJ":"NJ","Nj":"Nj","Ⓞ":"O","O":"O","Ò":"O","Ó":"O","Ô":"O","Ồ":"O","Ố":"O","Ỗ":"O","Ổ":"O","Õ":"O","Ṍ":"O","Ȭ":"O","Ṏ":"O","Ō":"O","Ṑ":"O","Ṓ":"O","Ŏ":"O","Ȯ":"O","Ȱ":"O","Ö":"O","Ȫ":"O","Ỏ":"O","Ő":"O","Ǒ":"O","Ȍ":"O","Ȏ":"O","Ơ":"O","Ờ":"O","Ớ":"O","Ỡ":"O","Ở":"O","Ợ":"O","Ọ":"O","Ộ":"O","Ǫ":"O","Ǭ":"O","Ø":"O","Ǿ":"O","Ɔ":"O","Ɵ":"O","Ꝋ":"O","Ꝍ":"O","Ƣ":"OI","Ꝏ":"OO","Ȣ":"OU","Ⓟ":"P","P":"P","Ṕ":"P","Ṗ":"P","Ƥ":"P","Ᵽ":"P","Ꝑ":"P","Ꝓ":"P","Ꝕ":"P","Ⓠ":"Q","Q":"Q","Ꝗ":"Q","Ꝙ":"Q","Ɋ":"Q","Ⓡ":"R","R":"R","Ŕ":"R","Ṙ":"R","Ř":"R","Ȑ":"R","Ȓ":"R","Ṛ":"R","Ṝ":"R","Ŗ":"R","Ṟ":"R","Ɍ":"R","Ɽ":"R","Ꝛ":"R","Ꞧ":"R","Ꞃ":"R","Ⓢ":"S","S":"S","ẞ":"S","Ś":"S","Ṥ":"S","Ŝ":"S","Ṡ":"S","Š":"S","Ṧ":"S","Ṣ":"S","Ṩ":"S","Ș":"S","Ş":"S","Ȿ":"S","Ꞩ":"S","Ꞅ":"S","Ⓣ":"T","T":"T","Ṫ":"T","Ť":"T","Ṭ":"T","Ț":"T","Ţ":"T","Ṱ":"T","Ṯ":"T","Ŧ":"T","Ƭ":"T","Ʈ":"T","Ⱦ":"T","Ꞇ":"T","Ꜩ":"TZ","Ⓤ":"U","U":"U","Ù":"U","Ú":"U","Û":"U","Ũ":"U","Ṹ":"U","Ū":"U","Ṻ":"U","Ŭ":"U","Ü":"U","Ǜ":"U","Ǘ":"U","Ǖ":"U","Ǚ":"U","Ủ":"U","Ů":"U","Ű":"U","Ǔ":"U","Ȕ":"U","Ȗ":"U","Ư":"U","Ừ":"U","Ứ":"U","Ữ":"U","Ử":"U","Ự":"U","Ụ":"U","Ṳ":"U","Ų":"U","Ṷ":"U","Ṵ":"U","Ʉ":"U","Ⓥ":"V","V":"V","Ṽ":"V","Ṿ":"V","Ʋ":"V","Ꝟ":"V","Ʌ":"V","Ꝡ":"VY","Ⓦ":"W","W":"W","Ẁ":"W","Ẃ":"W","Ŵ":"W","Ẇ":"W","Ẅ":"W","Ẉ":"W","Ⱳ":"W","Ⓧ":"X","X":"X","Ẋ":"X","Ẍ":"X","Ⓨ":"Y","Y":"Y","Ỳ":"Y","Ý":"Y","Ŷ":"Y","Ỹ":"Y","Ȳ":"Y","Ẏ":"Y","Ÿ":"Y","Ỷ":"Y","Ỵ":"Y","Ƴ":"Y","Ɏ":"Y","Ỿ":"Y","Ⓩ":"Z","Z":"Z","Ź":"Z","Ẑ":"Z","Ż":"Z","Ž":"Z","Ẓ":"Z","Ẕ":"Z","Ƶ":"Z","Ȥ":"Z","Ɀ":"Z","Ⱬ":"Z","Ꝣ":"Z","ⓐ":"a","a":"a","ẚ":"a","à":"a","á":"a","â":"a","ầ":"a","ấ":"a","ẫ":"a","ẩ":"a","ã":"a","ā":"a","ă":"a","ằ":"a","ắ":"a","ẵ":"a","ẳ":"a","ȧ":"a","ǡ":"a","ä":"a","ǟ":"a","ả":"a","å":"a","ǻ":"a","ǎ":"a","ȁ":"a","ȃ":"a","ạ":"a","ậ":"a","ặ":"a","ḁ":"a","ą":"a","ⱥ":"a","ɐ":"a","ꜳ":"aa","æ":"ae","ǽ":"ae","ǣ":"ae","ꜵ":"ao","ꜷ":"au","ꜹ":"av","ꜻ":"av","ꜽ":"ay","ⓑ":"b","b":"b","ḃ":"b","ḅ":"b","ḇ":"b","ƀ":"b","ƃ":"b","ɓ":"b","ⓒ":"c","c":"c","ć":"c","ĉ":"c","ċ":"c","č":"c","ç":"c","ḉ":"c","ƈ":"c","ȼ":"c","ꜿ":"c","ↄ":"c","ⓓ":"d","d":"d","ḋ":"d","ď":"d","ḍ":"d","ḑ":"d","ḓ":"d","ḏ":"d","đ":"d","ƌ":"d","ɖ":"d","ɗ":"d","ꝺ":"d","dz":"dz","dž":"dz","ⓔ":"e","e":"e","è":"e","é":"e","ê":"e","ề":"e","ế":"e","ễ":"e","ể":"e","ẽ":"e","ē":"e","ḕ":"e","ḗ":"e","ĕ":"e","ė":"e","ë":"e","ẻ":"e","ě":"e","ȅ":"e","ȇ":"e","ẹ":"e","ệ":"e","ȩ":"e","ḝ":"e","ę":"e","ḙ":"e","ḛ":"e","ɇ":"e","ɛ":"e","ǝ":"e","ⓕ":"f","f":"f","ḟ":"f","ƒ":"f","ꝼ":"f","ⓖ":"g","g":"g","ǵ":"g","ĝ":"g","ḡ":"g","ğ":"g","ġ":"g","ǧ":"g","ģ":"g","ǥ":"g","ɠ":"g","ꞡ":"g","ᵹ":"g","ꝿ":"g","ⓗ":"h","h":"h","ĥ":"h","ḣ":"h","ḧ":"h","ȟ":"h","ḥ":"h","ḩ":"h","ḫ":"h","ẖ":"h","ħ":"h","ⱨ":"h","ⱶ":"h","ɥ":"h","ƕ":"hv","ⓘ":"i","i":"i","ì":"i","í":"i","î":"i","ĩ":"i","ī":"i","ĭ":"i","ï":"i","ḯ":"i","ỉ":"i","ǐ":"i","ȉ":"i","ȋ":"i","ị":"i","į":"i","ḭ":"i","ɨ":"i","ı":"i","ⓙ":"j","j":"j","ĵ":"j","ǰ":"j","ɉ":"j","ⓚ":"k","k":"k","ḱ":"k","ǩ":"k","ḳ":"k","ķ":"k","ḵ":"k","ƙ":"k","ⱪ":"k","ꝁ":"k","ꝃ":"k","ꝅ":"k","ꞣ":"k","ⓛ":"l","l":"l","ŀ":"l","ĺ":"l","ľ":"l","ḷ":"l","ḹ":"l","ļ":"l","ḽ":"l","ḻ":"l","ſ":"l","ł":"l","ƚ":"l","ɫ":"l","ⱡ":"l","ꝉ":"l","ꞁ":"l","ꝇ":"l","lj":"lj","ⓜ":"m","m":"m","ḿ":"m","ṁ":"m","ṃ":"m","ɱ":"m","ɯ":"m","ⓝ":"n","n":"n","ǹ":"n","ń":"n","ñ":"n","ṅ":"n","ň":"n","ṇ":"n","ņ":"n","ṋ":"n","ṉ":"n","ƞ":"n","ɲ":"n","ʼn":"n","ꞑ":"n","ꞥ":"n","nj":"nj","ⓞ":"o","o":"o","ò":"o","ó":"o","ô":"o","ồ":"o","ố":"o","ỗ":"o","ổ":"o","õ":"o","ṍ":"o","ȭ":"o","ṏ":"o","ō":"o","ṑ":"o","ṓ":"o","ŏ":"o","ȯ":"o","ȱ":"o","ö":"o","ȫ":"o","ỏ":"o","ő":"o","ǒ":"o","ȍ":"o","ȏ":"o","ơ":"o","ờ":"o","ớ":"o","ỡ":"o","ở":"o","ợ":"o","ọ":"o","ộ":"o","ǫ":"o","ǭ":"o","ø":"o","ǿ":"o","ɔ":"o","ꝋ":"o","ꝍ":"o","ɵ":"o","ƣ":"oi","ȣ":"ou","ꝏ":"oo","ⓟ":"p","p":"p","ṕ":"p","ṗ":"p","ƥ":"p","ᵽ":"p","ꝑ":"p","ꝓ":"p","ꝕ":"p","ⓠ":"q","q":"q","ɋ":"q","ꝗ":"q","ꝙ":"q","ⓡ":"r","r":"r","ŕ":"r","ṙ":"r","ř":"r","ȑ":"r","ȓ":"r","ṛ":"r","ṝ":"r","ŗ":"r","ṟ":"r","ɍ":"r","ɽ":"r","ꝛ":"r","ꞧ":"r","ꞃ":"r","ⓢ":"s","s":"s","ß":"s","ś":"s","ṥ":"s","ŝ":"s","ṡ":"s","š":"s","ṧ":"s","ṣ":"s","ṩ":"s","ș":"s","ş":"s","ȿ":"s","ꞩ":"s","ꞅ":"s","ẛ":"s","ⓣ":"t","t":"t","ṫ":"t","ẗ":"t","ť":"t","ṭ":"t","ț":"t","ţ":"t","ṱ":"t","ṯ":"t","ŧ":"t","ƭ":"t","ʈ":"t","ⱦ":"t","ꞇ":"t","ꜩ":"tz","ⓤ":"u","u":"u","ù":"u","ú":"u","û":"u","ũ":"u","ṹ":"u","ū":"u","ṻ":"u","ŭ":"u","ü":"u","ǜ":"u","ǘ":"u","ǖ":"u","ǚ":"u","ủ":"u","ů":"u","ű":"u","ǔ":"u","ȕ":"u","ȗ":"u","ư":"u","ừ":"u","ứ":"u","ữ":"u","ử":"u","ự":"u","ụ":"u","ṳ":"u","ų":"u","ṷ":"u","ṵ":"u","ʉ":"u","ⓥ":"v","v":"v","ṽ":"v","ṿ":"v","ʋ":"v","ꝟ":"v","ʌ":"v","ꝡ":"vy","ⓦ":"w","w":"w","ẁ":"w","ẃ":"w","ŵ":"w","ẇ":"w","ẅ":"w","ẘ":"w","ẉ":"w","ⱳ":"w","ⓧ":"x","x":"x","ẋ":"x","ẍ":"x","ⓨ":"y","y":"y","ỳ":"y","ý":"y","ŷ":"y","ỹ":"y","ȳ":"y","ẏ":"y","ÿ":"y","ỷ":"y","ẙ":"y","ỵ":"y","ƴ":"y","ɏ":"y","ỿ":"y","ⓩ":"z","z":"z","ź":"z","ẑ":"z","ż":"z","ž":"z","ẓ":"z","ẕ":"z","ƶ":"z","ȥ":"z","ɀ":"z","ⱬ":"z","ꝣ":"z","Ά":"Α","Έ":"Ε","Ή":"Η","Ί":"Ι","Ϊ":"Ι","Ό":"Ο","Ύ":"Υ","Ϋ":"Υ","Ώ":"Ω","ά":"α","έ":"ε","ή":"η","ί":"ι","ϊ":"ι","ΐ":"ι","ό":"ο","ύ":"υ","ϋ":"υ","ΰ":"υ","ω":"ω","ς":"σ"};return a}),b.define("select2/data/base",["../utils"],function(a){function b(a,c){b.__super__.constructor.call(this)}return a.Extend(b,a.Observable),b.prototype.current=function(a){throw new Error("The `current` method must be defined in child classes.")},b.prototype.query=function(a,b){throw new Error("The `query` method must be defined in child classes.")},b.prototype.bind=function(a,b){},b.prototype.destroy=function(){},b.prototype.generateResultId=function(b,c){var d=b.id+"-result-";return d+=a.generateChars(4),d+=null!=c.id?"-"+c.id.toString():"-"+a.generateChars(4)},b}),b.define("select2/data/select",["./base","../utils","jquery"],function(a,b,c){function d(a,b){this.$element=a,this.options=b,d.__super__.constructor.call(this)}return b.Extend(d,a),d.prototype.current=function(a){var b=[],d=this;this.$element.find(":selected").each(function(){var a=c(this),e=d.item(a);b.push(e)}),a(b)},d.prototype.select=function(a){var b=this;if(a.selected=!0,c(a.element).is("option"))return a.element.selected=!0,void this.$element.trigger("change"); +if(this.$element.prop("multiple"))this.current(function(d){var e=[];a=[a],a.push.apply(a,d);for(var f=0;f<a.length;f++){var g=a[f].id;-1===c.inArray(g,e)&&e.push(g)}b.$element.val(e),b.$element.trigger("change")});else{var d=a.id;this.$element.val(d),this.$element.trigger("change")}},d.prototype.unselect=function(a){var b=this;if(this.$element.prop("multiple"))return a.selected=!1,c(a.element).is("option")?(a.element.selected=!1,void this.$element.trigger("change")):void this.current(function(d){for(var e=[],f=0;f<d.length;f++){var g=d[f].id;g!==a.id&&-1===c.inArray(g,e)&&e.push(g)}b.$element.val(e),b.$element.trigger("change")})},d.prototype.bind=function(a,b){var c=this;this.container=a,a.on("select",function(a){c.select(a.data)}),a.on("unselect",function(a){c.unselect(a.data)})},d.prototype.destroy=function(){this.$element.find("*").each(function(){c.removeData(this,"data")})},d.prototype.query=function(a,b){var d=[],e=this,f=this.$element.children();f.each(function(){var b=c(this);if(b.is("option")||b.is("optgroup")){var f=e.item(b),g=e.matches(a,f);null!==g&&d.push(g)}}),b({results:d})},d.prototype.addOptions=function(a){b.appendMany(this.$element,a)},d.prototype.option=function(a){var b;a.children?(b=document.createElement("optgroup"),b.label=a.text):(b=document.createElement("option"),void 0!==b.textContent?b.textContent=a.text:b.innerText=a.text),a.id&&(b.value=a.id),a.disabled&&(b.disabled=!0),a.selected&&(b.selected=!0),a.title&&(b.title=a.title);var d=c(b),e=this._normalizeItem(a);return e.element=b,c.data(b,"data",e),d},d.prototype.item=function(a){var b={};if(b=c.data(a[0],"data"),null!=b)return b;if(a.is("option"))b={id:a.val(),text:a.text(),disabled:a.prop("disabled"),selected:a.prop("selected"),title:a.prop("title")};else if(a.is("optgroup")){b={text:a.prop("label"),children:[],title:a.prop("title")};for(var d=a.children("option"),e=[],f=0;f<d.length;f++){var g=c(d[f]),h=this.item(g);e.push(h)}b.children=e}return b=this._normalizeItem(b),b.element=a[0],c.data(a[0],"data",b),b},d.prototype._normalizeItem=function(a){c.isPlainObject(a)||(a={id:a,text:a}),a=c.extend({},{text:""},a);var b={selected:!1,disabled:!1};return null!=a.id&&(a.id=a.id.toString()),null!=a.text&&(a.text=a.text.toString()),null==a._resultId&&a.id&&null!=this.container&&(a._resultId=this.generateResultId(this.container,a)),c.extend({},b,a)},d.prototype.matches=function(a,b){var c=this.options.get("matcher");return c(a,b)},d}),b.define("select2/data/array",["./select","../utils","jquery"],function(a,b,c){function d(a,b){var c=b.get("data")||[];d.__super__.constructor.call(this,a,b),this.addOptions(this.convertToOptions(c))}return b.Extend(d,a),d.prototype.select=function(a){var b=this.$element.find("option").filter(function(b,c){return c.value==a.id.toString()});0===b.length&&(b=this.option(a),this.addOptions(b)),d.__super__.select.call(this,a)},d.prototype.convertToOptions=function(a){function d(a){return function(){return c(this).val()==a.id}}for(var e=this,f=this.$element.find("option"),g=f.map(function(){return e.item(c(this)).id}).get(),h=[],i=0;i<a.length;i++){var j=this._normalizeItem(a[i]);if(c.inArray(j.id,g)>=0){var k=f.filter(d(j)),l=this.item(k),m=c.extend(!0,{},j,l),n=this.option(m);k.replaceWith(n)}else{var o=this.option(j);if(j.children){var p=this.convertToOptions(j.children);b.appendMany(o,p)}h.push(o)}}return h},d}),b.define("select2/data/ajax",["./array","../utils","jquery"],function(a,b,c){function d(a,b){this.ajaxOptions=this._applyDefaults(b.get("ajax")),null!=this.ajaxOptions.processResults&&(this.processResults=this.ajaxOptions.processResults),d.__super__.constructor.call(this,a,b)}return b.Extend(d,a),d.prototype._applyDefaults=function(a){var b={data:function(a){return c.extend({},a,{q:a.term})},transport:function(a,b,d){var e=c.ajax(a);return e.then(b),e.fail(d),e}};return c.extend({},b,a,!0)},d.prototype.processResults=function(a){return a},d.prototype.query=function(a,b){function d(){var d=f.transport(f,function(d){var f=e.processResults(d,a);e.options.get("debug")&&window.console&&console.error&&(f&&f.results&&c.isArray(f.results)||console.error("Select2: The AJAX results did not return an array in the `results` key of the response.")),b(f)},function(){d.status&&"0"===d.status||e.trigger("results:message",{message:"errorLoading"})});e._request=d}var e=this;null!=this._request&&(c.isFunction(this._request.abort)&&this._request.abort(),this._request=null);var f=c.extend({type:"GET"},this.ajaxOptions);"function"==typeof f.url&&(f.url=f.url.call(this.$element,a)),"function"==typeof f.data&&(f.data=f.data.call(this.$element,a)),this.ajaxOptions.delay&&null!=a.term?(this._queryTimeout&&window.clearTimeout(this._queryTimeout),this._queryTimeout=window.setTimeout(d,this.ajaxOptions.delay)):d()},d}),b.define("select2/data/tags",["jquery"],function(a){function b(b,c,d){var e=d.get("tags"),f=d.get("createTag");void 0!==f&&(this.createTag=f);var g=d.get("insertTag");if(void 0!==g&&(this.insertTag=g),b.call(this,c,d),a.isArray(e))for(var h=0;h<e.length;h++){var i=e[h],j=this._normalizeItem(i),k=this.option(j);this.$element.append(k)}}return b.prototype.query=function(a,b,c){function d(a,f){for(var g=a.results,h=0;h<g.length;h++){var i=g[h],j=null!=i.children&&!d({results:i.children},!0),k=i.text===b.term;if(k||j)return f?!1:(a.data=g,void c(a))}if(f)return!0;var l=e.createTag(b);if(null!=l){var m=e.option(l);m.attr("data-select2-tag",!0),e.addOptions([m]),e.insertTag(g,l)}a.results=g,c(a)}var e=this;return this._removeOldTags(),null==b.term||null!=b.page?void a.call(this,b,c):void a.call(this,b,d)},b.prototype.createTag=function(b,c){var d=a.trim(c.term);return""===d?null:{id:d,text:d}},b.prototype.insertTag=function(a,b,c){b.unshift(c)},b.prototype._removeOldTags=function(b){var c=(this._lastTag,this.$element.find("option[data-select2-tag]"));c.each(function(){this.selected||a(this).remove()})},b}),b.define("select2/data/tokenizer",["jquery"],function(a){function b(a,b,c){var d=c.get("tokenizer");void 0!==d&&(this.tokenizer=d),a.call(this,b,c)}return b.prototype.bind=function(a,b,c){a.call(this,b,c),this.$search=b.dropdown.$search||b.selection.$search||c.find(".select2-search__field")},b.prototype.query=function(b,c,d){function e(b){var c=g._normalizeItem(b),d=g.$element.find("option").filter(function(){return a(this).val()===c.id});if(!d.length){var e=g.option(c);e.attr("data-select2-tag",!0),g._removeOldTags(),g.addOptions([e])}f(c)}function f(a){g.trigger("select",{data:a})}var g=this;c.term=c.term||"";var h=this.tokenizer(c,this.options,e);h.term!==c.term&&(this.$search.length&&(this.$search.val(h.term),this.$search.focus()),c.term=h.term),b.call(this,c,d)},b.prototype.tokenizer=function(b,c,d,e){for(var f=d.get("tokenSeparators")||[],g=c.term,h=0,i=this.createTag||function(a){return{id:a.term,text:a.term}};h<g.length;){var j=g[h];if(-1!==a.inArray(j,f)){var k=g.substr(0,h),l=a.extend({},c,{term:k}),m=i(l);null!=m?(e(m),g=g.substr(h+1)||"",h=0):h++}else h++}return{term:g}},b}),b.define("select2/data/minimumInputLength",[],function(){function a(a,b,c){this.minimumInputLength=c.get("minimumInputLength"),a.call(this,b,c)}return a.prototype.query=function(a,b,c){return b.term=b.term||"",b.term.length<this.minimumInputLength?void this.trigger("results:message",{message:"inputTooShort",args:{minimum:this.minimumInputLength,input:b.term,params:b}}):void a.call(this,b,c)},a}),b.define("select2/data/maximumInputLength",[],function(){function a(a,b,c){this.maximumInputLength=c.get("maximumInputLength"),a.call(this,b,c)}return a.prototype.query=function(a,b,c){return b.term=b.term||"",this.maximumInputLength>0&&b.term.length>this.maximumInputLength?void this.trigger("results:message",{message:"inputTooLong",args:{maximum:this.maximumInputLength,input:b.term,params:b}}):void a.call(this,b,c)},a}),b.define("select2/data/maximumSelectionLength",[],function(){function a(a,b,c){this.maximumSelectionLength=c.get("maximumSelectionLength"),a.call(this,b,c)}return a.prototype.query=function(a,b,c){var d=this;this.current(function(e){var f=null!=e?e.length:0;return d.maximumSelectionLength>0&&f>=d.maximumSelectionLength?void d.trigger("results:message",{message:"maximumSelected",args:{maximum:d.maximumSelectionLength}}):void a.call(d,b,c)})},a}),b.define("select2/dropdown",["jquery","./utils"],function(a,b){function c(a,b){this.$element=a,this.options=b,c.__super__.constructor.call(this)}return b.Extend(c,b.Observable),c.prototype.render=function(){var b=a('<span class="select2-dropdown"><span class="select2-results"></span></span>');return b.attr("dir",this.options.get("dir")),this.$dropdown=b,b},c.prototype.bind=function(){},c.prototype.position=function(a,b){},c.prototype.destroy=function(){this.$dropdown.remove()},c}),b.define("select2/dropdown/search",["jquery","../utils"],function(a,b){function c(){}return c.prototype.render=function(b){var c=b.call(this),d=a('<span class="select2-search select2-search--dropdown"><input class="select2-search__field" type="search" tabindex="-1" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" role="textbox" /></span>');return this.$searchContainer=d,this.$search=d.find("input"),c.prepend(d),c},c.prototype.bind=function(b,c,d){var e=this;b.call(this,c,d),this.$search.on("keydown",function(a){e.trigger("keypress",a),e._keyUpPrevented=a.isDefaultPrevented()}),this.$search.on("input",function(b){a(this).off("keyup")}),this.$search.on("keyup input",function(a){e.handleSearch(a)}),c.on("open",function(){e.$search.attr("tabindex",0),e.$search.focus(),window.setTimeout(function(){e.$search.focus()},0)}),c.on("close",function(){e.$search.attr("tabindex",-1),e.$search.val("")}),c.on("focus",function(){c.isOpen()&&e.$search.focus()}),c.on("results:all",function(a){if(null==a.query.term||""===a.query.term){var b=e.showSearch(a);b?e.$searchContainer.removeClass("select2-search--hide"):e.$searchContainer.addClass("select2-search--hide")}})},c.prototype.handleSearch=function(a){if(!this._keyUpPrevented){var b=this.$search.val();this.trigger("query",{term:b})}this._keyUpPrevented=!1},c.prototype.showSearch=function(a,b){return!0},c}),b.define("select2/dropdown/hidePlaceholder",[],function(){function a(a,b,c,d){this.placeholder=this.normalizePlaceholder(c.get("placeholder")),a.call(this,b,c,d)}return a.prototype.append=function(a,b){b.results=this.removePlaceholder(b.results),a.call(this,b)},a.prototype.normalizePlaceholder=function(a,b){return"string"==typeof b&&(b={id:"",text:b}),b},a.prototype.removePlaceholder=function(a,b){for(var c=b.slice(0),d=b.length-1;d>=0;d--){var e=b[d];this.placeholder.id===e.id&&c.splice(d,1)}return c},a}),b.define("select2/dropdown/infiniteScroll",["jquery"],function(a){function b(a,b,c,d){this.lastParams={},a.call(this,b,c,d),this.$loadingMore=this.createLoadingMore(),this.loading=!1}return b.prototype.append=function(a,b){this.$loadingMore.remove(),this.loading=!1,a.call(this,b),this.showLoadingMore(b)&&this.$results.append(this.$loadingMore)},b.prototype.bind=function(b,c,d){var e=this;b.call(this,c,d),c.on("query",function(a){e.lastParams=a,e.loading=!0}),c.on("query:append",function(a){e.lastParams=a,e.loading=!0}),this.$results.on("scroll",function(){var b=a.contains(document.documentElement,e.$loadingMore[0]);if(!e.loading&&b){var c=e.$results.offset().top+e.$results.outerHeight(!1),d=e.$loadingMore.offset().top+e.$loadingMore.outerHeight(!1);c+50>=d&&e.loadMore()}})},b.prototype.loadMore=function(){this.loading=!0;var b=a.extend({},{page:1},this.lastParams);b.page++,this.trigger("query:append",b)},b.prototype.showLoadingMore=function(a,b){return b.pagination&&b.pagination.more},b.prototype.createLoadingMore=function(){var b=a('<li class="select2-results__option select2-results__option--load-more"role="treeitem" aria-disabled="true"></li>'),c=this.options.get("translations").get("loadingMore");return b.html(c(this.lastParams)),b},b}),b.define("select2/dropdown/attachBody",["jquery","../utils"],function(a,b){function c(b,c,d){this.$dropdownParent=d.get("dropdownParent")||a(document.body),b.call(this,c,d)}return c.prototype.bind=function(a,b,c){var d=this,e=!1;a.call(this,b,c),b.on("open",function(){d._showDropdown(),d._attachPositioningHandler(b),e||(e=!0,b.on("results:all",function(){d._positionDropdown(),d._resizeDropdown()}),b.on("results:append",function(){d._positionDropdown(),d._resizeDropdown()}))}),b.on("close",function(){d._hideDropdown(),d._detachPositioningHandler(b)}),this.$dropdownContainer.on("mousedown",function(a){a.stopPropagation()})},c.prototype.destroy=function(a){a.call(this),this.$dropdownContainer.remove()},c.prototype.position=function(a,b,c){b.attr("class",c.attr("class")),b.removeClass("select2"),b.addClass("select2-container--open"),b.css({position:"absolute",top:-999999}),this.$container=c},c.prototype.render=function(b){var c=a("<span></span>"),d=b.call(this);return c.append(d),this.$dropdownContainer=c,c},c.prototype._hideDropdown=function(a){this.$dropdownContainer.detach()},c.prototype._attachPositioningHandler=function(c,d){var e=this,f="scroll.select2."+d.id,g="resize.select2."+d.id,h="orientationchange.select2."+d.id,i=this.$container.parents().filter(b.hasScroll);i.each(function(){a(this).data("select2-scroll-position",{x:a(this).scrollLeft(),y:a(this).scrollTop()})}),i.on(f,function(b){var c=a(this).data("select2-scroll-position");a(this).scrollTop(c.y)}),a(window).on(f+" "+g+" "+h,function(a){e._positionDropdown(),e._resizeDropdown()})},c.prototype._detachPositioningHandler=function(c,d){var e="scroll.select2."+d.id,f="resize.select2."+d.id,g="orientationchange.select2."+d.id,h=this.$container.parents().filter(b.hasScroll);h.off(e),a(window).off(e+" "+f+" "+g)},c.prototype._positionDropdown=function(){var b=a(window),c=this.$dropdown.hasClass("select2-dropdown--above"),d=this.$dropdown.hasClass("select2-dropdown--below"),e=null,f=this.$container.offset();f.bottom=f.top+this.$container.outerHeight(!1);var g={height:this.$container.outerHeight(!1)};g.top=f.top,g.bottom=f.top+g.height;var h={height:this.$dropdown.outerHeight(!1)},i={top:b.scrollTop(),bottom:b.scrollTop()+b.height()},j=i.top<f.top-h.height,k=i.bottom>f.bottom+h.height,l={left:f.left,top:g.bottom},m=this.$dropdownParent;"static"===m.css("position")&&(m=m.offsetParent());var n=m.offset();l.top-=n.top,l.left-=n.left,c||d||(e="below"),k||!j||c?!j&&k&&c&&(e="below"):e="above",("above"==e||c&&"below"!==e)&&(l.top=g.top-n.top-h.height),null!=e&&(this.$dropdown.removeClass("select2-dropdown--below select2-dropdown--above").addClass("select2-dropdown--"+e),this.$container.removeClass("select2-container--below select2-container--above").addClass("select2-container--"+e)),this.$dropdownContainer.css(l)},c.prototype._resizeDropdown=function(){var a={width:this.$container.outerWidth(!1)+"px"};this.options.get("dropdownAutoWidth")&&(a.minWidth=a.width,a.position="relative",a.width="auto"),this.$dropdown.css(a)},c.prototype._showDropdown=function(a){this.$dropdownContainer.appendTo(this.$dropdownParent),this._positionDropdown(),this._resizeDropdown()},c}),b.define("select2/dropdown/minimumResultsForSearch",[],function(){function a(b){for(var c=0,d=0;d<b.length;d++){var e=b[d];e.children?c+=a(e.children):c++}return c}function b(a,b,c,d){this.minimumResultsForSearch=c.get("minimumResultsForSearch"),this.minimumResultsForSearch<0&&(this.minimumResultsForSearch=1/0),a.call(this,b,c,d)}return b.prototype.showSearch=function(b,c){return a(c.data.results)<this.minimumResultsForSearch?!1:b.call(this,c)},b}),b.define("select2/dropdown/selectOnClose",[],function(){function a(){}return a.prototype.bind=function(a,b,c){var d=this;a.call(this,b,c),b.on("close",function(a){d._handleSelectOnClose(a)})},a.prototype._handleSelectOnClose=function(a,b){if(b&&null!=b.originalSelect2Event){var c=b.originalSelect2Event;if("select"===c._type||"unselect"===c._type)return}var d=this.getHighlightedResults();if(!(d.length<1)){var e=d.data("data");null!=e.element&&e.element.selected||null==e.element&&e.selected||this.trigger("select",{data:e})}},a}),b.define("select2/dropdown/closeOnSelect",[],function(){function a(){}return a.prototype.bind=function(a,b,c){var d=this;a.call(this,b,c),b.on("select",function(a){d._selectTriggered(a)}),b.on("unselect",function(a){d._selectTriggered(a)})},a.prototype._selectTriggered=function(a,b){var c=b.originalEvent;c&&c.ctrlKey||this.trigger("close",{originalEvent:c,originalSelect2Event:b})},a}),b.define("select2/i18n/en",[],function(){return{errorLoading:function(){return"The results could not be loaded."},inputTooLong:function(a){var b=a.input.length-a.maximum,c="Please delete "+b+" character";return 1!=b&&(c+="s"),c},inputTooShort:function(a){var b=a.minimum-a.input.length,c="Please enter "+b+" or more characters";return c},loadingMore:function(){return"Loading more results…"},maximumSelected:function(a){var b="You can only select "+a.maximum+" item";return 1!=a.maximum&&(b+="s"),b},noResults:function(){return"No results found"},searching:function(){return"Searching…"}}}),b.define("select2/defaults",["jquery","require","./results","./selection/single","./selection/multiple","./selection/placeholder","./selection/allowClear","./selection/search","./selection/eventRelay","./utils","./translation","./diacritics","./data/select","./data/array","./data/ajax","./data/tags","./data/tokenizer","./data/minimumInputLength","./data/maximumInputLength","./data/maximumSelectionLength","./dropdown","./dropdown/search","./dropdown/hidePlaceholder","./dropdown/infiniteScroll","./dropdown/attachBody","./dropdown/minimumResultsForSearch","./dropdown/selectOnClose","./dropdown/closeOnSelect","./i18n/en"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C){function D(){this.reset()}D.prototype.apply=function(l){if(l=a.extend(!0,{},this.defaults,l),null==l.dataAdapter){if(null!=l.ajax?l.dataAdapter=o:null!=l.data?l.dataAdapter=n:l.dataAdapter=m,l.minimumInputLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,r)),l.maximumInputLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,s)),l.maximumSelectionLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,t)),l.tags&&(l.dataAdapter=j.Decorate(l.dataAdapter,p)),(null!=l.tokenSeparators||null!=l.tokenizer)&&(l.dataAdapter=j.Decorate(l.dataAdapter,q)),null!=l.query){var C=b(l.amdBase+"compat/query");l.dataAdapter=j.Decorate(l.dataAdapter,C)}if(null!=l.initSelection){var D=b(l.amdBase+"compat/initSelection");l.dataAdapter=j.Decorate(l.dataAdapter,D)}}if(null==l.resultsAdapter&&(l.resultsAdapter=c,null!=l.ajax&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,x)),null!=l.placeholder&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,w)),l.selectOnClose&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,A))),null==l.dropdownAdapter){if(l.multiple)l.dropdownAdapter=u;else{var E=j.Decorate(u,v);l.dropdownAdapter=E}if(0!==l.minimumResultsForSearch&&(l.dropdownAdapter=j.Decorate(l.dropdownAdapter,z)),l.closeOnSelect&&(l.dropdownAdapter=j.Decorate(l.dropdownAdapter,B)),null!=l.dropdownCssClass||null!=l.dropdownCss||null!=l.adaptDropdownCssClass){var F=b(l.amdBase+"compat/dropdownCss");l.dropdownAdapter=j.Decorate(l.dropdownAdapter,F)}l.dropdownAdapter=j.Decorate(l.dropdownAdapter,y)}if(null==l.selectionAdapter){if(l.multiple?l.selectionAdapter=e:l.selectionAdapter=d,null!=l.placeholder&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,f)),l.allowClear&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,g)),l.multiple&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,h)),null!=l.containerCssClass||null!=l.containerCss||null!=l.adaptContainerCssClass){var G=b(l.amdBase+"compat/containerCss");l.selectionAdapter=j.Decorate(l.selectionAdapter,G)}l.selectionAdapter=j.Decorate(l.selectionAdapter,i)}if("string"==typeof l.language)if(l.language.indexOf("-")>0){var H=l.language.split("-"),I=H[0];l.language=[l.language,I]}else l.language=[l.language];if(a.isArray(l.language)){var J=new k;l.language.push("en");for(var K=l.language,L=0;L<K.length;L++){var M=K[L],N={};try{N=k.loadPath(M)}catch(O){try{M=this.defaults.amdLanguageBase+M,N=k.loadPath(M)}catch(P){l.debug&&window.console&&console.warn&&console.warn('Select2: The language file for "'+M+'" could not be automatically loaded. A fallback will be used instead.');continue}}J.extend(N)}l.translations=J}else{var Q=k.loadPath(this.defaults.amdLanguageBase+"en"),R=new k(l.language);R.extend(Q),l.translations=R}return l},D.prototype.reset=function(){function b(a){function b(a){return l[a]||a}return a.replace(/[^\u0000-\u007E]/g,b)}function c(d,e){if(""===a.trim(d.term))return e;if(e.children&&e.children.length>0){for(var f=a.extend(!0,{},e),g=e.children.length-1;g>=0;g--){var h=e.children[g],i=c(d,h);null==i&&f.children.splice(g,1)}return f.children.length>0?f:c(d,f)}var j=b(e.text).toUpperCase(),k=b(d.term).toUpperCase();return j.indexOf(k)>-1?e:null}this.defaults={amdBase:"./",amdLanguageBase:"./i18n/",closeOnSelect:!0,debug:!1,dropdownAutoWidth:!1,escapeMarkup:j.escapeMarkup,language:C,matcher:c,minimumInputLength:0,maximumInputLength:0,maximumSelectionLength:0,minimumResultsForSearch:0,selectOnClose:!1,sorter:function(a){return a},templateResult:function(a){return a.text},templateSelection:function(a){return a.text},theme:"default",width:"resolve"}},D.prototype.set=function(b,c){var d=a.camelCase(b),e={};e[d]=c;var f=j._convertData(e);a.extend(this.defaults,f)};var E=new D;return E}),b.define("select2/options",["require","jquery","./defaults","./utils"],function(a,b,c,d){function e(b,e){if(this.options=b,null!=e&&this.fromElement(e),this.options=c.apply(this.options),e&&e.is("input")){var f=a(this.get("amdBase")+"compat/inputData");this.options.dataAdapter=d.Decorate(this.options.dataAdapter,f)}}return e.prototype.fromElement=function(a){var c=["select2"];null==this.options.multiple&&(this.options.multiple=a.prop("multiple")),null==this.options.disabled&&(this.options.disabled=a.prop("disabled")),null==this.options.language&&(a.prop("lang")?this.options.language=a.prop("lang").toLowerCase():a.closest("[lang]").prop("lang")&&(this.options.language=a.closest("[lang]").prop("lang"))),null==this.options.dir&&(a.prop("dir")?this.options.dir=a.prop("dir"):a.closest("[dir]").prop("dir")?this.options.dir=a.closest("[dir]").prop("dir"):this.options.dir="ltr"),a.prop("disabled",this.options.disabled),a.prop("multiple",this.options.multiple),a.data("select2Tags")&&(this.options.debug&&window.console&&console.warn&&console.warn('Select2: The `data-select2-tags` attribute has been changed to use the `data-data` and `data-tags="true"` attributes and will be removed in future versions of Select2.'),a.data("data",a.data("select2Tags")),a.data("tags",!0)),a.data("ajaxUrl")&&(this.options.debug&&window.console&&console.warn&&console.warn("Select2: The `data-ajax-url` attribute has been changed to `data-ajax--url` and support for the old attribute will be removed in future versions of Select2."),a.attr("ajax--url",a.data("ajaxUrl")),a.data("ajax--url",a.data("ajaxUrl")));var e={};e=b.fn.jquery&&"1."==b.fn.jquery.substr(0,2)&&a[0].dataset?b.extend(!0,{},a[0].dataset,a.data()):a.data();var f=b.extend(!0,{},e);f=d._convertData(f);for(var g in f)b.inArray(g,c)>-1||(b.isPlainObject(this.options[g])?b.extend(this.options[g],f[g]):this.options[g]=f[g]);return this},e.prototype.get=function(a){return this.options[a]},e.prototype.set=function(a,b){this.options[a]=b},e}),b.define("select2/core",["jquery","./options","./utils","./keys"],function(a,b,c,d){var e=function(a,c){null!=a.data("select2")&&a.data("select2").destroy(),this.$element=a,this.id=this._generateId(a),c=c||{},this.options=new b(c,a),e.__super__.constructor.call(this);var d=a.attr("tabindex")||0;a.data("old-tabindex",d),a.attr("tabindex","-1");var f=this.options.get("dataAdapter");this.dataAdapter=new f(a,this.options);var g=this.render();this._placeContainer(g);var h=this.options.get("selectionAdapter");this.selection=new h(a,this.options),this.$selection=this.selection.render(),this.selection.position(this.$selection,g);var i=this.options.get("dropdownAdapter");this.dropdown=new i(a,this.options),this.$dropdown=this.dropdown.render(),this.dropdown.position(this.$dropdown,g);var j=this.options.get("resultsAdapter");this.results=new j(a,this.options,this.dataAdapter),this.$results=this.results.render(),this.results.position(this.$results,this.$dropdown);var k=this;this._bindAdapters(),this._registerDomEvents(),this._registerDataEvents(),this._registerSelectionEvents(),this._registerDropdownEvents(),this._registerResultsEvents(),this._registerEvents(),this.dataAdapter.current(function(a){k.trigger("selection:update",{data:a})}),a.addClass("select2-hidden-accessible"),a.attr("aria-hidden","true"),this._syncAttributes(),a.data("select2",this)};return c.Extend(e,c.Observable),e.prototype._generateId=function(a){var b="";return b=null!=a.attr("id")?a.attr("id"):null!=a.attr("name")?a.attr("name")+"-"+c.generateChars(2):c.generateChars(4),b=b.replace(/(:|\.|\[|\]|,)/g,""),b="select2-"+b},e.prototype._placeContainer=function(a){a.insertAfter(this.$element);var b=this._resolveWidth(this.$element,this.options.get("width"));null!=b&&a.css("width",b)},e.prototype._resolveWidth=function(a,b){var c=/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;if("resolve"==b){var d=this._resolveWidth(a,"style");return null!=d?d:this._resolveWidth(a,"element")}if("element"==b){var e=a.outerWidth(!1);return 0>=e?"auto":e+"px"}if("style"==b){var f=a.attr("style");if("string"!=typeof f)return null;for(var g=f.split(";"),h=0,i=g.length;i>h;h+=1){var j=g[h].replace(/\s/g,""),k=j.match(c);if(null!==k&&k.length>=1)return k[1]}return null}return b},e.prototype._bindAdapters=function(){this.dataAdapter.bind(this,this.$container),this.selection.bind(this,this.$container),this.dropdown.bind(this,this.$container),this.results.bind(this,this.$container)},e.prototype._registerDomEvents=function(){var b=this;this.$element.on("change.select2",function(){b.dataAdapter.current(function(a){b.trigger("selection:update",{data:a})})}),this.$element.on("focus.select2",function(a){b.trigger("focus",a)}),this._syncA=c.bind(this._syncAttributes,this),this._syncS=c.bind(this._syncSubtree,this),this.$element[0].attachEvent&&this.$element[0].attachEvent("onpropertychange",this._syncA);var d=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver;null!=d?(this._observer=new d(function(c){a.each(c,b._syncA),a.each(c,b._syncS)}),this._observer.observe(this.$element[0],{attributes:!0,childList:!0,subtree:!1})):this.$element[0].addEventListener&&(this.$element[0].addEventListener("DOMAttrModified",b._syncA,!1),this.$element[0].addEventListener("DOMNodeInserted",b._syncS,!1),this.$element[0].addEventListener("DOMNodeRemoved",b._syncS,!1))},e.prototype._registerDataEvents=function(){var a=this;this.dataAdapter.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerSelectionEvents=function(){var b=this,c=["toggle","focus"];this.selection.on("toggle",function(){b.toggleDropdown()}),this.selection.on("focus",function(a){b.focus(a)}),this.selection.on("*",function(d,e){-1===a.inArray(d,c)&&b.trigger(d,e)})},e.prototype._registerDropdownEvents=function(){var a=this;this.dropdown.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerResultsEvents=function(){var a=this;this.results.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerEvents=function(){var a=this;this.on("open",function(){a.$container.addClass("select2-container--open")}),this.on("close",function(){a.$container.removeClass("select2-container--open")}),this.on("enable",function(){a.$container.removeClass("select2-container--disabled")}),this.on("disable",function(){a.$container.addClass("select2-container--disabled")}),this.on("blur",function(){a.$container.removeClass("select2-container--focus")}),this.on("query",function(b){a.isOpen()||a.trigger("open",{}),this.dataAdapter.query(b,function(c){a.trigger("results:all",{data:c,query:b})})}),this.on("query:append",function(b){this.dataAdapter.query(b,function(c){a.trigger("results:append",{data:c,query:b})})}),this.on("keypress",function(b){var c=b.which;a.isOpen()?c===d.ESC||c===d.TAB||c===d.UP&&b.altKey?(a.close(),b.preventDefault()):c===d.ENTER?(a.trigger("results:select",{}),b.preventDefault()):c===d.SPACE&&b.ctrlKey?(a.trigger("results:toggle",{}),b.preventDefault()):c===d.UP?(a.trigger("results:previous",{}),b.preventDefault()):c===d.DOWN&&(a.trigger("results:next",{}),b.preventDefault()):(c===d.ENTER||c===d.SPACE||c===d.DOWN&&b.altKey)&&(a.open(),b.preventDefault())})},e.prototype._syncAttributes=function(){this.options.set("disabled",this.$element.prop("disabled")),this.options.get("disabled")?(this.isOpen()&&this.close(),this.trigger("disable",{})):this.trigger("enable",{})},e.prototype._syncSubtree=function(a,b){var c=!1,d=this;if(!a||!a.target||"OPTION"===a.target.nodeName||"OPTGROUP"===a.target.nodeName){if(b)if(b.addedNodes&&b.addedNodes.length>0)for(var e=0;e<b.addedNodes.length;e++){var f=b.addedNodes[e];f.selected&&(c=!0)}else b.removedNodes&&b.removedNodes.length>0&&(c=!0);else c=!0;c&&this.dataAdapter.current(function(a){d.trigger("selection:update",{data:a})})}},e.prototype.trigger=function(a,b){var c=e.__super__.trigger,d={open:"opening",close:"closing",select:"selecting",unselect:"unselecting"};if(void 0===b&&(b={}),a in d){var f=d[a],g={prevented:!1,name:a,args:b};if(c.call(this,f,g),g.prevented)return void(b.prevented=!0)}c.call(this,a,b)},e.prototype.toggleDropdown=function(){this.options.get("disabled")||(this.isOpen()?this.close():this.open())},e.prototype.open=function(){this.isOpen()||this.trigger("query",{})},e.prototype.close=function(){this.isOpen()&&this.trigger("close",{})},e.prototype.isOpen=function(){return this.$container.hasClass("select2-container--open")},e.prototype.hasFocus=function(){return this.$container.hasClass("select2-container--focus")},e.prototype.focus=function(a){this.hasFocus()||(this.$container.addClass("select2-container--focus"),this.trigger("focus",{}))},e.prototype.enable=function(a){this.options.get("debug")&&window.console&&console.warn&&console.warn('Select2: The `select2("enable")` method has been deprecated and will be removed in later Select2 versions. Use $element.prop("disabled") instead.'),(null==a||0===a.length)&&(a=[!0]);var b=!a[0];this.$element.prop("disabled",b)},e.prototype.data=function(){this.options.get("debug")&&arguments.length>0&&window.console&&console.warn&&console.warn('Select2: Data can no longer be set using `select2("data")`. You should consider setting the value instead using `$element.val()`.');var a=[];return this.dataAdapter.current(function(b){a=b}),a},e.prototype.val=function(b){if(this.options.get("debug")&&window.console&&console.warn&&console.warn('Select2: The `select2("val")` method has been deprecated and will be removed in later Select2 versions. Use $element.val() instead.'),null==b||0===b.length)return this.$element.val();var c=b[0];a.isArray(c)&&(c=a.map(c,function(a){return a.toString()})),this.$element.val(c).trigger("change")},e.prototype.destroy=function(){this.$container.remove(),this.$element[0].detachEvent&&this.$element[0].detachEvent("onpropertychange",this._syncA),null!=this._observer?(this._observer.disconnect(),this._observer=null):this.$element[0].removeEventListener&&(this.$element[0].removeEventListener("DOMAttrModified",this._syncA,!1),this.$element[0].removeEventListener("DOMNodeInserted",this._syncS,!1),this.$element[0].removeEventListener("DOMNodeRemoved",this._syncS,!1)),this._syncA=null,this._syncS=null,this.$element.off(".select2"),this.$element.attr("tabindex",this.$element.data("old-tabindex")),this.$element.removeClass("select2-hidden-accessible"),this.$element.attr("aria-hidden","false"),this.$element.removeData("select2"),this.dataAdapter.destroy(),this.selection.destroy(),this.dropdown.destroy(),this.results.destroy(),this.dataAdapter=null,this.selection=null,this.dropdown=null,this.results=null; +},e.prototype.render=function(){var b=a('<span class="select2 select2-container"><span class="selection"></span><span class="dropdown-wrapper" aria-hidden="true"></span></span>');return b.attr("dir",this.options.get("dir")),this.$container=b,this.$container.addClass("select2-container--"+this.options.get("theme")),b.data("element",this.$element),b},e}),b.define("select2/compat/utils",["jquery"],function(a){function b(b,c,d){var e,f,g=[];e=a.trim(b.attr("class")),e&&(e=""+e,a(e.split(/\s+/)).each(function(){0===this.indexOf("select2-")&&g.push(this)})),e=a.trim(c.attr("class")),e&&(e=""+e,a(e.split(/\s+/)).each(function(){0!==this.indexOf("select2-")&&(f=d(this),null!=f&&g.push(f))})),b.attr("class",g.join(" "))}return{syncCssClasses:b}}),b.define("select2/compat/containerCss",["jquery","./utils"],function(a,b){function c(a){return null}function d(){}return d.prototype.render=function(d){var e=d.call(this),f=this.options.get("containerCssClass")||"";a.isFunction(f)&&(f=f(this.$element));var g=this.options.get("adaptContainerCssClass");if(g=g||c,-1!==f.indexOf(":all:")){f=f.replace(":all:","");var h=g;g=function(a){var b=h(a);return null!=b?b+" "+a:a}}var i=this.options.get("containerCss")||{};return a.isFunction(i)&&(i=i(this.$element)),b.syncCssClasses(e,this.$element,g),e.css(i),e.addClass(f),e},d}),b.define("select2/compat/dropdownCss",["jquery","./utils"],function(a,b){function c(a){return null}function d(){}return d.prototype.render=function(d){var e=d.call(this),f=this.options.get("dropdownCssClass")||"";a.isFunction(f)&&(f=f(this.$element));var g=this.options.get("adaptDropdownCssClass");if(g=g||c,-1!==f.indexOf(":all:")){f=f.replace(":all:","");var h=g;g=function(a){var b=h(a);return null!=b?b+" "+a:a}}var i=this.options.get("dropdownCss")||{};return a.isFunction(i)&&(i=i(this.$element)),b.syncCssClasses(e,this.$element,g),e.css(i),e.addClass(f),e},d}),b.define("select2/compat/initSelection",["jquery"],function(a){function b(a,b,c){c.get("debug")&&window.console&&console.warn&&console.warn("Select2: The `initSelection` option has been deprecated in favor of a custom data adapter that overrides the `current` method. This method is now called multiple times instead of a single time when the instance is initialized. Support will be removed for the `initSelection` option in future versions of Select2"),this.initSelection=c.get("initSelection"),this._isInitialized=!1,a.call(this,b,c)}return b.prototype.current=function(b,c){var d=this;return this._isInitialized?void b.call(this,c):void this.initSelection.call(null,this.$element,function(b){d._isInitialized=!0,a.isArray(b)||(b=[b]),c(b)})},b}),b.define("select2/compat/inputData",["jquery"],function(a){function b(a,b,c){this._currentData=[],this._valueSeparator=c.get("valueSeparator")||",","hidden"===b.prop("type")&&c.get("debug")&&console&&console.warn&&console.warn("Select2: Using a hidden input with Select2 is no longer supported and may stop working in the future. It is recommended to use a `<select>` element instead."),a.call(this,b,c)}return b.prototype.current=function(b,c){function d(b,c){var e=[];return b.selected||-1!==a.inArray(b.id,c)?(b.selected=!0,e.push(b)):b.selected=!1,b.children&&e.push.apply(e,d(b.children,c)),e}for(var e=[],f=0;f<this._currentData.length;f++){var g=this._currentData[f];e.push.apply(e,d(g,this.$element.val().split(this._valueSeparator)))}c(e)},b.prototype.select=function(b,c){if(this.options.get("multiple")){var d=this.$element.val();d+=this._valueSeparator+c.id,this.$element.val(d),this.$element.trigger("change")}else this.current(function(b){a.map(b,function(a){a.selected=!1})}),this.$element.val(c.id),this.$element.trigger("change")},b.prototype.unselect=function(a,b){var c=this;b.selected=!1,this.current(function(a){for(var d=[],e=0;e<a.length;e++){var f=a[e];b.id!=f.id&&d.push(f.id)}c.$element.val(d.join(c._valueSeparator)),c.$element.trigger("change")})},b.prototype.query=function(a,b,c){for(var d=[],e=0;e<this._currentData.length;e++){var f=this._currentData[e],g=this.matches(b,f);null!==g&&d.push(g)}c({results:d})},b.prototype.addOptions=function(b,c){var d=a.map(c,function(b){return a.data(b[0],"data")});this._currentData.push.apply(this._currentData,d)},b}),b.define("select2/compat/matcher",["jquery"],function(a){function b(b){function c(c,d){var e=a.extend(!0,{},d);if(null==c.term||""===a.trim(c.term))return e;if(d.children){for(var f=d.children.length-1;f>=0;f--){var g=d.children[f],h=b(c.term,g.text,g);h||e.children.splice(f,1)}if(e.children.length>0)return e}return b(c.term,d.text,d)?e:null}return c}return b}),b.define("select2/compat/query",[],function(){function a(a,b,c){c.get("debug")&&window.console&&console.warn&&console.warn("Select2: The `query` option has been deprecated in favor of a custom data adapter that overrides the `query` method. Support will be removed for the `query` option in future versions of Select2."),a.call(this,b,c)}return a.prototype.query=function(a,b,c){b.callback=c;var d=this.options.get("query");d.call(null,b)},a}),b.define("select2/dropdown/attachContainer",[],function(){function a(a,b,c){a.call(this,b,c)}return a.prototype.position=function(a,b,c){var d=c.find(".dropdown-wrapper");d.append(b),b.addClass("select2-dropdown--below"),c.addClass("select2-container--below")},a}),b.define("select2/dropdown/stopPropagation",[],function(){function a(){}return a.prototype.bind=function(a,b,c){a.call(this,b,c);var d=["blur","change","click","dblclick","focus","focusin","focusout","input","keydown","keyup","keypress","mousedown","mouseenter","mouseleave","mousemove","mouseover","mouseup","search","touchend","touchstart"];this.$dropdown.on(d.join(" "),function(a){a.stopPropagation()})},a}),b.define("select2/selection/stopPropagation",[],function(){function a(){}return a.prototype.bind=function(a,b,c){a.call(this,b,c);var d=["blur","change","click","dblclick","focus","focusin","focusout","input","keydown","keyup","keypress","mousedown","mouseenter","mouseleave","mousemove","mouseover","mouseup","search","touchend","touchstart"];this.$selection.on(d.join(" "),function(a){a.stopPropagation()})},a}),function(c){"function"==typeof b.define&&b.define.amd?b.define("jquery-mousewheel",["jquery"],c):"object"==typeof exports?module.exports=c:c(a)}(function(a){function b(b){var g=b||window.event,h=i.call(arguments,1),j=0,l=0,m=0,n=0,o=0,p=0;if(b=a.event.fix(g),b.type="mousewheel","detail"in g&&(m=-1*g.detail),"wheelDelta"in g&&(m=g.wheelDelta),"wheelDeltaY"in g&&(m=g.wheelDeltaY),"wheelDeltaX"in g&&(l=-1*g.wheelDeltaX),"axis"in g&&g.axis===g.HORIZONTAL_AXIS&&(l=-1*m,m=0),j=0===m?l:m,"deltaY"in g&&(m=-1*g.deltaY,j=m),"deltaX"in g&&(l=g.deltaX,0===m&&(j=-1*l)),0!==m||0!==l){if(1===g.deltaMode){var q=a.data(this,"mousewheel-line-height");j*=q,m*=q,l*=q}else if(2===g.deltaMode){var r=a.data(this,"mousewheel-page-height");j*=r,m*=r,l*=r}if(n=Math.max(Math.abs(m),Math.abs(l)),(!f||f>n)&&(f=n,d(g,n)&&(f/=40)),d(g,n)&&(j/=40,l/=40,m/=40),j=Math[j>=1?"floor":"ceil"](j/f),l=Math[l>=1?"floor":"ceil"](l/f),m=Math[m>=1?"floor":"ceil"](m/f),k.settings.normalizeOffset&&this.getBoundingClientRect){var s=this.getBoundingClientRect();o=b.clientX-s.left,p=b.clientY-s.top}return b.deltaX=l,b.deltaY=m,b.deltaFactor=f,b.offsetX=o,b.offsetY=p,b.deltaMode=0,h.unshift(b,j,l,m),e&&clearTimeout(e),e=setTimeout(c,200),(a.event.dispatch||a.event.handle).apply(this,h)}}function c(){f=null}function d(a,b){return k.settings.adjustOldDeltas&&"mousewheel"===a.type&&b%120===0}var e,f,g=["wheel","mousewheel","DOMMouseScroll","MozMousePixelScroll"],h="onwheel"in document||document.documentMode>=9?["wheel"]:["mousewheel","DomMouseScroll","MozMousePixelScroll"],i=Array.prototype.slice;if(a.event.fixHooks)for(var j=g.length;j;)a.event.fixHooks[g[--j]]=a.event.mouseHooks;var k=a.event.special.mousewheel={version:"3.1.12",setup:function(){if(this.addEventListener)for(var c=h.length;c;)this.addEventListener(h[--c],b,!1);else this.onmousewheel=b;a.data(this,"mousewheel-line-height",k.getLineHeight(this)),a.data(this,"mousewheel-page-height",k.getPageHeight(this))},teardown:function(){if(this.removeEventListener)for(var c=h.length;c;)this.removeEventListener(h[--c],b,!1);else this.onmousewheel=null;a.removeData(this,"mousewheel-line-height"),a.removeData(this,"mousewheel-page-height")},getLineHeight:function(b){var c=a(b),d=c["offsetParent"in a.fn?"offsetParent":"parent"]();return d.length||(d=a("body")),parseInt(d.css("fontSize"),10)||parseInt(c.css("fontSize"),10)||16},getPageHeight:function(b){return a(b).height()},settings:{adjustOldDeltas:!0,normalizeOffset:!0}};a.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})}),b.define("jquery.select2",["jquery","jquery-mousewheel","./select2/core","./select2/defaults"],function(a,b,c,d){if(null==a.fn.select2){var e=["open","close","destroy"];a.fn.select2=function(b){if(b=b||{},"object"==typeof b)return this.each(function(){var d=a.extend(!0,{},b);new c(a(this),d)}),this;if("string"==typeof b){var d,f=Array.prototype.slice.call(arguments,1);return this.each(function(){var c=a(this).data("select2");null==c&&window.console&&console.error&&console.error("The select2('"+b+"') method was called on an element that is not using Select2."),d=c[b].apply(c,f)}),a.inArray(b,e)>-1?this:d}throw new Error("Invalid arguments for Select2: "+b)}}return null==a.fn.select2.defaults&&(a.fn.select2.defaults=d),c}),{define:b.define,require:b.require}}(),c=b.require("jquery.select2");return a.fn.select2.amd=b,c}); \ No newline at end of file diff --git a/django/contrib/admin/static/admin/js/vendor/xregexp/LICENSE-XREGEXP.txt b/django/contrib/admin/static/admin/js/vendor/xregexp/LICENSE.txt similarity index 100% rename from django/contrib/admin/static/admin/js/vendor/xregexp/LICENSE-XREGEXP.txt rename to django/contrib/admin/static/admin/js/vendor/xregexp/LICENSE.txt diff --git a/django/contrib/admin/templates/admin/actions.html b/django/contrib/admin/templates/admin/actions.html index 80ffa066ed47..ef2232e13fd1 100644 --- a/django/contrib/admin/templates/admin/actions.html +++ b/django/contrib/admin/templates/admin/actions.html @@ -1,7 +1,13 @@ {% load i18n %} <div class="actions"> + {% block actions %} + {% block actions-form %} {% for field in action_form %}{% if field.label %}<label>{{ field.label }} {% endif %}{{ field }}{% if field.label %}</label>{% endif %}{% endfor %} + {% endblock %} + {% block actions-submit %} <button type="submit" class="button" title="{% trans "Run the selected action" %}" name="index" value="{{ action_index|default:0 }}">{% trans "Go" %}</button> + {% endblock %} + {% block actions-counter %} {% if actions_selection_counter %} <span class="action-counter" data-actions-icnt="{{ cl.result_list|length }}">{{ selection_note }}</span> {% if cl.result_count != cl.result_list|length %} @@ -12,4 +18,6 @@ <span class="clear"><a href="#">{% trans "Clear selection" %}</a></span> {% endif %} {% endif %} + {% endblock %} + {% endblock %} </div> 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 7a47707df92c..4b3bfb920ccc 100644 --- a/django/contrib/admin/templates/admin/auth/user/change_password.html +++ b/django/contrib/admin/templates/admin/auth/user/change_password.html @@ -5,7 +5,7 @@ {% block extrahead %}{{ block.super }} <script type="text/javascript" src="{% url 'admin:jsi18n' %}"></script> {% endblock %} -{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />{% endblock %} +{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}">{% endblock %} {% block bodyclass %}{{ block.super }} {{ opts.app_label }}-{{ opts.model_name }} change-form{% endblock %} {% if not is_popup %} {% block breadcrumbs %} @@ -20,9 +20,9 @@ {% endif %} {% block content %}<div id="content-main"> <form action="{{ form_url }}" method="post" id="{{ opts.model_name }}_form">{% csrf_token %}{% block form_top %}{% endblock %} -<input type="text" name="username" value="{{ original.get_username }}" style="display: none" /> +<input type="text" name="username" value="{{ original.get_username }}" style="display: none"> <div> -{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %} +{% if is_popup %}<input type="hidden" name="_popup" value="1">{% endif %} {% if form.errors %} <p class="errornote"> {% if form.errors.items|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %} @@ -52,7 +52,7 @@ </fieldset> <div class="submit-row"> -<input type="submit" value="{% trans 'Change password' %}" class="default" /> +<input type="submit" value="{% trans 'Change password' %}" class="default"> </div> </div> diff --git a/django/contrib/admin/templates/admin/base.html b/django/contrib/admin/templates/admin/base.html index 70e137cfe2ea..2cf5137070d2 100644 --- a/django/contrib/admin/templates/admin/base.html +++ b/django/contrib/admin/templates/admin/base.html @@ -3,11 +3,16 @@ <html lang="{{ LANGUAGE_CODE|default:"en-us" }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %}> <head> <title>{% block title %}{% endblock %} - + {% block extrastyle %}{% endblock %} -{% if LANGUAGE_BIDI %}{% endif %} +{% if LANGUAGE_BIDI %}{% endif %} {% block extrahead %}{% endblock %} -{% block blockbots %}{% endblock %} +{% block responsive %} + + + {% if LANGUAGE_BIDI %}{% endif %} +{% endblock %} +{% block blockbots %}{% endblock %} {% load i18n %} @@ -76,7 +81,7 @@ {{ content }} {% endblock %} {% block sidebar %}{% endblock %} -
+
diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html index fd0b130b2d1e..1d749f25d353 100644 --- a/django/contrib/admin/templates/admin/change_form.html +++ b/django/contrib/admin/templates/admin/change_form.html @@ -6,7 +6,7 @@ {{ media }} {% endblock %} -{% block extrastyle %}{{ block.super }}{% endblock %} +{% block extrastyle %}{{ block.super }}{% endblock %} {% block coltype %}colM{% endblock %} @@ -17,7 +17,7 @@ {% endblock %} @@ -28,19 +28,15 @@ {% if change %}{% if not is_popup %}
    {% block object-tools-items %} -
  • - {% url opts|admin_urlname:'history' original.pk|admin_urlquote as history_url %} - {% trans "History" %} -
  • - {% if has_absolute_url %}
  • {% trans "View on site" %}
  • {% endif %} + {% change_form_object_tools %} {% endblock %}
{% endif %}{% endif %} {% endblock %}
{% csrf_token %}{% block form_top %}{% endblock %}
-{% if is_popup %}{% endif %} -{% if to_field %}{% endif %} +{% if is_popup %}{% endif %} +{% if to_field %}{% endif %} {% if save_on_top %}{% block submit_buttons_top %}{% submit_row %}{% endblock %}{% endif %} {% if errors %}

diff --git a/django/contrib/admin/templates/admin/change_form_object_tools.html b/django/contrib/admin/templates/admin/change_form_object_tools.html new file mode 100644 index 000000000000..32487493a257 --- /dev/null +++ b/django/contrib/admin/templates/admin/change_form_object_tools.html @@ -0,0 +1,8 @@ +{% load i18n admin_urls %} +{% block object-tools-items %} +

  • + {% url opts|admin_urlname:'history' original.pk|admin_urlquote as history_url %} + {% trans "History" %} +
  • +{% if has_absolute_url %}
  • {% trans "View on site" %}
  • {% endif %} +{% endblock %} diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html index e0af704aa982..768e581d1ea1 100644 --- a/django/contrib/admin/templates/admin/change_list.html +++ b/django/contrib/admin/templates/admin/change_list.html @@ -3,9 +3,9 @@ {% block extrastyle %} {{ block.super }} - + {% if cl.formset %} - + {% endif %} {% if cl.formset or action_form %} @@ -42,18 +42,11 @@ {% block object-tools %} {% endblock %} - {% if cl.formset.errors %} + {% if cl.formset and cl.formset.errors %}

    {% if cl.formset.total_error_count == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %}

    @@ -61,7 +54,7 @@ {% endif %}
    {% block search %}{% search_form cl %}{% endblock %} - {% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %} + {% block date_hierarchy %}{% if cl.date_hierarchy %}{% date_hierarchy cl %}{% endif %}{% endblock %} {% block filters %} {% if cl.has_filters %} @@ -72,7 +65,7 @@

    {% trans 'Filter' %}

    {% endif %} {% endblock %} - {% csrf_token %} + {% csrf_token %} {% if cl.formset %}
    {{ cl.formset.management_form }}
    {% endif %} diff --git a/django/contrib/admin/templates/admin/change_list_object_tools.html b/django/contrib/admin/templates/admin/change_list_object_tools.html new file mode 100644 index 000000000000..5d6d4582768c --- /dev/null +++ b/django/contrib/admin/templates/admin/change_list_object_tools.html @@ -0,0 +1,12 @@ +{% load i18n admin_urls %} + +{% block object-tools-items %} + {% if has_add_permission %} +
  • + {% url cl.opts|admin_urlname:'add' as add_url %} + + {% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %} + +
  • + {% endif %} +{% endblock %} diff --git a/django/contrib/admin/templates/admin/change_list_results.html b/django/contrib/admin/templates/admin/change_list_results.html index b3d7dd01d38b..9b97b5b4f7c2 100644 --- a/django/contrib/admin/templates/admin/change_list_results.html +++ b/django/contrib/admin/templates/admin/change_list_results.html @@ -27,7 +27,7 @@ {% for result in results %} -{% if result.form.non_field_errors %} +{% if result.form and result.form.non_field_errors %} {{ result.form.non_field_errors }} {% endif %} {% for item in result %}{{ item }}{% endfor %} diff --git a/django/contrib/admin/templates/admin/date_hierarchy.html b/django/contrib/admin/templates/admin/date_hierarchy.html index 005851051cdf..65ae80013411 100644 --- a/django/contrib/admin/templates/admin/date_hierarchy.html +++ b/django/contrib/admin/templates/admin/date_hierarchy.html @@ -1,10 +1,16 @@ {% if show %}

    +{% endblock %} +{% endblock %} +
    {% endif %} diff --git a/django/contrib/admin/templates/admin/delete_confirmation.html b/django/contrib/admin/templates/admin/delete_confirmation.html index 09582c18ecd3..c28a87cd9b07 100644 --- a/django/contrib/admin/templates/admin/delete_confirmation.html +++ b/django/contrib/admin/templates/admin/delete_confirmation.html @@ -13,7 +13,7 @@ @@ -41,10 +41,10 @@

    {% trans "Objects" %}

      {{ deleted_objects|unordered_list }}
    {% csrf_token %}
    - - {% if is_popup %}{% endif %} - {% if to_field %}{% endif %} - + + {% if is_popup %}{% endif %} + {% if to_field %}{% endif %} + {% trans "No, take me back" %}
    diff --git a/django/contrib/admin/templates/admin/delete_selected_confirmation.html b/django/contrib/admin/templates/admin/delete_selected_confirmation.html index 6ae53fecd308..4d77ae33a057 100644 --- a/django/contrib/admin/templates/admin/delete_selected_confirmation.html +++ b/django/contrib/admin/templates/admin/delete_selected_confirmation.html @@ -43,11 +43,11 @@

    {% trans "Objects" %}

    {% csrf_token %}
    {% for obj in queryset %} - + {% endfor %} - - - + + + {% trans "No, take me back" %}
    diff --git a/django/contrib/admin/templates/admin/edit_inline/stacked.html b/django/contrib/admin/templates/admin/edit_inline/stacked.html index 65af259a213c..d9c27b157807 100644 --- a/django/contrib/admin/templates/admin/edit_inline/stacked.html +++ b/django/contrib/admin/templates/admin/edit_inline/stacked.html @@ -8,18 +8,18 @@

    {{ inline_admin_formset.opts.verbose_name_plural|capfirst }}

    {{ inline_admin_formset.formset.management_form }} {{ inline_admin_formset.formset.non_form_errors }} -{% for inline_admin_form in inline_admin_formset %}
    -

    {{ inline_admin_formset.opts.verbose_name|capfirst }}: {% if inline_admin_form.original %}{{ inline_admin_form.original }}{% if inline_admin_form.model_admin.show_change_link and inline_admin_form.model_admin.has_registered_model %} {% trans "Change" %}{% endif %} +{% for inline_admin_form in inline_admin_formset %}
    +

    {{ inline_admin_formset.opts.verbose_name|capfirst }}: {% if inline_admin_form.original %}{{ inline_admin_form.original }}{% if inline_admin_form.model_admin.show_change_link and inline_admin_form.model_admin.has_registered_model %} {% if inline_admin_formset.has_change_permission %}{% trans "Change" %}{% else %}{% trans "View" %}{% endif %}{% endif %} {% else %}#{{ forloop.counter }}{% endif %} {% if inline_admin_form.show_url %}{% trans "View on site" %}{% endif %} - {% if inline_admin_formset.formset.can_delete and inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}{% endif %} + {% if inline_admin_formset.formset.can_delete and inline_admin_formset.has_delete_permission and inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}{% endif %}

    {% if inline_admin_form.form.non_field_errors %}{{ inline_admin_form.form.non_field_errors }}{% endif %} {% for fieldset in inline_admin_form %} {% include "admin/includes/fieldset.html" %} {% endfor %} {% if inline_admin_form.needs_explicit_pk_field %}{{ inline_admin_form.pk_field.field }}{% endif %} - {{ inline_admin_form.fk_field.field }} + {% if inline_admin_form.fk_field %}{{ inline_admin_form.fk_field.field }}{% endif %}
    {% endfor %}

    diff --git a/django/contrib/admin/templates/admin/edit_inline/tabular.html b/django/contrib/admin/templates/admin/edit_inline/tabular.html index f04faadf2ff4..d643955dcf80 100644 --- a/django/contrib/admin/templates/admin/edit_inline/tabular.html +++ b/django/contrib/admin/templates/admin/edit_inline/tabular.html @@ -12,12 +12,12 @@

    {{ inline_admin_formset.opts.verbose_name_plural|capfirst }}

    {% for field in inline_admin_formset.fields %} {% if not field.widget.is_hidden %} - {{ field.label|capfirst }} - {% if field.help_text %} ({{ field.help_text|striptags }}){% endif %} + {{ field.label|capfirst }} + {% if field.help_text %} ({{ field.help_text|striptags }}){% endif %} {% endif %} {% endfor %} - {% if inline_admin_formset.formset.can_delete %}{% trans "Delete?" %}{% endif %} + {% if inline_admin_formset.formset.can_delete and inline_admin_formset.has_delete_permission %}{% trans "Delete?" %}{% endif %} @@ -25,23 +25,23 @@

    {{ inline_admin_formset.opts.verbose_name_plural|capfirst }}

    {% if inline_admin_form.form.non_field_errors %} {{ inline_admin_form.form.non_field_errors }} {% endif %} - {% if inline_admin_form.original or inline_admin_form.show_url %}

    {% if inline_admin_form.original %} {{ inline_admin_form.original }} - {% if inline_admin_form.model_admin.show_change_link and inline_admin_form.model_admin.has_registered_model %}{% trans "Change" %}{% endif %} + {% if inline_admin_form.model_admin.show_change_link and inline_admin_form.model_admin.has_registered_model %}{% if inline_admin_formset.has_change_permission %}{% trans "Change" %}{% else %}{% trans "View" %}{% endif %}{% endif %} {% endif %} {% if inline_admin_form.show_url %}{% trans "View on site" %}{% endif %}

    {% endif %} {% if inline_admin_form.needs_explicit_pk_field %}{{ inline_admin_form.pk_field.field }}{% endif %} - {{ inline_admin_form.fk_field.field }} + {% if inline_admin_form.fk_field %}{{ inline_admin_form.fk_field.field }}{% endif %} {% spaceless %} {% for fieldset in inline_admin_form %} {% for line in fieldset %} {% for field in line %} - {% if field.field.is_hidden %} {{ field.field }} {% endif %} + {% if not field.is_readonly and field.field.is_hidden %}{{ field.field }}{% endif %} {% endfor %} {% endfor %} {% endfor %} @@ -50,7 +50,7 @@

    {{ inline_admin_formset.opts.verbose_name_plural|capfirst }}

    {% for fieldset in inline_admin_form %} {% for line in fieldset %} {% for field in line %} - {% if not field.field.is_hidden %} + {% if field.is_readonly or not field.field.is_hidden %} {% if field.is_readonly %}

    {{ field.contents }}

    @@ -63,7 +63,7 @@

    {{ inline_admin_formset.opts.verbose_name_plural|capfirst }}

    {% endfor %} {% endfor %} {% endfor %} - {% if inline_admin_formset.formset.can_delete %} + {% if inline_admin_formset.formset.can_delete and inline_admin_formset.has_delete_permission %} {% if inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }}{% endif %} {% endif %} diff --git a/django/contrib/admin/templates/admin/includes/fieldset.html b/django/contrib/admin/templates/admin/includes/fieldset.html index fce9966664f1..218fd5a4c17c 100644 --- a/django/contrib/admin/templates/admin/includes/fieldset.html +++ b/django/contrib/admin/templates/admin/includes/fieldset.html @@ -7,7 +7,7 @@
    {% if line.fields|length_is:'1' %}{{ line.errors }}{% endif %} {% for field in line %} - + {% if not line.fields|length_is:'1' and not field.is_readonly %}{{ field.errors }}{% endif %} {% if field.is_checkbox %} {{ field.field }}{{ field.label_tag }} diff --git a/django/contrib/admin/templates/admin/index.html b/django/contrib/admin/templates/admin/index.html index 5a4b12717826..2b5001588628 100644 --- a/django/contrib/admin/templates/admin/index.html +++ b/django/contrib/admin/templates/admin/index.html @@ -1,7 +1,7 @@ {% extends "admin/base_site.html" %} {% load i18n static %} -{% block extrastyle %}{{ block.super }}{% endblock %} +{% block extrastyle %}{{ block.super }}{% endblock %} {% block coltype %}colMS{% endblock %} @@ -34,7 +34,11 @@ {% endif %} {% if model.admin_url %} + {% if model.view_only %} + {% trans 'View' %} + {% else %} {% trans 'Change' %} + {% endif %} {% else %}   {% endif %} @@ -44,7 +48,7 @@
    {% endfor %} {% else %} -

    {% trans "You don't have permission to edit anything." %}

    +

    {% trans "You don't have permission to view or edit anything." %}

    {% endif %}
    {% endblock %} @@ -67,7 +71,7 @@

    {% trans 'My actions' %}

    {% else %} {{ entry.object_repr }} {% endif %} -
    +
    {% if entry.content_type %} {% filter capfirst %}{{ entry.content_type }}{% endfilter %} {% else %} diff --git a/django/contrib/admin/templates/admin/login.html b/django/contrib/admin/templates/admin/login.html index 397eadf4a220..396be276f919 100644 --- a/django/contrib/admin/templates/admin/login.html +++ b/django/contrib/admin/templates/admin/login.html @@ -1,7 +1,7 @@ {% extends "admin/base_site.html" %} {% load i18n static %} -{% block extrastyle %}{{ block.super }} +{% block extrastyle %}{{ block.super }} {{ form.media }} {% endblock %} @@ -49,7 +49,7 @@
    {{ form.password.errors }} {{ form.password.label_tag }} {{ form.password }} - +
    {% url 'admin_password_reset' as password_reset_url %} {% if password_reset_url %} @@ -58,7 +58,7 @@
    {% endif %}
    - +
    diff --git a/django/contrib/admin/templates/admin/pagination.html b/django/contrib/admin/templates/admin/pagination.html index fc1e600805b1..bef843a44497 100644 --- a/django/contrib/admin/templates/admin/pagination.html +++ b/django/contrib/admin/templates/admin/pagination.html @@ -8,5 +8,5 @@ {% endif %} {{ cl.result_count }} {% if cl.result_count == 1 %}{{ cl.opts.verbose_name }}{% else %}{{ cl.opts.verbose_name_plural }}{% endif %} {% if show_all_url %}  {% trans 'Show all' %}{% endif %} -{% if cl.formset and cl.result_count %}{% endif %} +{% if cl.formset and cl.result_count %}{% endif %}

    diff --git a/django/contrib/admin/templates/admin/popup_response.html b/django/contrib/admin/templates/admin/popup_response.html index 6e4fac8e8d36..303960ff5d85 100644 --- a/django/contrib/admin/templates/admin/popup_response.html +++ b/django/contrib/admin/templates/admin/popup_response.html @@ -1,6 +1,6 @@ {% load i18n static %} - {% trans 'Popup closing...' %} + {% trans 'Popup closing…' %} ', + element_id, mark_safe(json_str) + ) def conditional_escape(text): @@ -82,6 +99,8 @@ def conditional_escape(text): This function relies on the __html__ convention used both by Django's SafeData class and by third-party libraries like markupsafe. """ + if isinstance(text, Promise): + text = str(text) if hasattr(text, '__html__'): return text.__html__() else: @@ -90,12 +109,12 @@ def conditional_escape(text): def format_html(format_string, *args, **kwargs): """ - Similar to str.format, but passes all arguments through conditional_escape, - and calls 'mark_safe' on the result. This function should be used instead + Similar to str.format, but pass all arguments through conditional_escape(), + and call mark_safe() on the result. This function should be used instead of str.format or % interpolation to build up small HTML fragments. """ args_safe = map(conditional_escape, args) - kwargs_safe = {k: conditional_escape(v) for (k, v) in six.iteritems(kwargs)} + kwargs_safe = {k: conditional_escape(v) for (k, v) in kwargs.items()} return mark_safe(format_string.format(*args_safe, **kwargs_safe)) @@ -114,25 +133,26 @@ def format_html_join(sep, format_string, args_generator): for u in users)) """ return mark_safe(conditional_escape(sep).join( - format_html(format_string, *tuple(args)) - for args in args_generator)) + format_html(format_string, *args) + for args in args_generator + )) @keep_lazy_text def linebreaks(value, autoescape=False): - """Converts newlines into

    and
    s.""" - value = normalize_newlines(force_text(value)) - paras = re.split('\n{2,}', value) + """Convert newlines into

    and
    s.""" + value = normalize_newlines(value) + paras = re.split('\n{2,}', str(value)) if autoescape: - paras = ['

    %s

    ' % escape(p).replace('\n', '
    ') for p in paras] + paras = ['

    %s

    ' % escape(p).replace('\n', '
    ') for p in paras] else: - paras = ['

    %s

    ' % p.replace('\n', '
    ') for p in paras] + paras = ['

    %s

    ' % p.replace('\n', '
    ') for p in paras] return '\n\n'.join(paras) class MLStripper(HTMLParser): def __init__(self): - HTMLParser.__init__(self) + super().__init__(convert_charrefs=False) self.reset() self.fed = [] @@ -154,30 +174,21 @@ def _strip_once(value): Internal tag stripping utility used by strip_tags. """ s = MLStripper() - try: - s.feed(value) - except HTMLParseError: - return value - try: - s.close() - except HTMLParseError: - return s.get_data() + s.rawdata - else: - return s.get_data() + s.feed(value) + s.close() + return s.get_data() @keep_lazy_text def strip_tags(value): - """Returns the given HTML with all tags stripped.""" + """Return the given HTML with all tags stripped.""" # Note: in typical case this loop executes _strip_once once. Loop condition # is redundant, but helps to reduce number of executions of _strip_once. - value = force_text(value) + value = str(value) while '<' in value and '>' in value: new_value = _strip_once(value) - if len(new_value) >= len(value): - # _strip_once was not able to detect more tags or length increased - # due to http://bugs.python.org/issue20288 - # (affects Python 2 < 2.7.7 and Python 3 < 3.3.5) + if value.count('<') == new_value.count('<'): + # _strip_once wasn't able to detect more tags. break value = new_value return value @@ -185,19 +196,18 @@ def strip_tags(value): @keep_lazy_text def strip_spaces_between_tags(value): - """Returns the given HTML with spaces between tags removed.""" - return re.sub(r'>\s+<', '><', force_text(value)) + """Return the given HTML with spaces between tags removed.""" + return re.sub(r'>\s+<', '><', str(value)) def smart_urlquote(url): - "Quotes a URL if it isn't already quoted." + """Quote a URL if it isn't already quoted.""" def unquote_quote(segment): - segment = unquote(force_str(segment)) + segment = unquote(segment) # Tilde is part of RFC3986 Unreserved Characters - # http://tools.ietf.org/html/rfc3986#section-2.3 - # See also http://bugs.python.org/issue16285 - segment = quote(segment, safe=RFC3986_SUBDELIMS + RFC3986_GENDELIMS + str('~')) - return force_text(segment) + # https://tools.ietf.org/html/rfc3986#section-2.3 + # See also https://bugs.python.org/issue16285 + return quote(segment, safe=RFC3986_SUBDELIMS + RFC3986_GENDELIMS + '~') # Handle IDN before quoting. try: @@ -214,7 +224,7 @@ def unquote_quote(segment): if query: # Separately unquoting key/value, so as to not mix querystring separators # included in query values. See #22267. - query_parts = [(unquote(force_str(q[0])), unquote(force_str(q[1]))) + query_parts = [(unquote(q[0]), unquote(q[1])) for q in parse_qsl(query, keep_blank_values=True)] # urlencode will take care of quoting query = urlencode(query_parts) @@ -228,45 +238,35 @@ def unquote_quote(segment): @keep_lazy_text def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False): """ - Converts any URLs in text into clickable links. + Convert any URLs in text into clickable links. Works on http://, https://, www. links, and also on links ending in one of the original seven gTLDs (.com, .edu, .gov, .int, .mil, .net, and .org). Links can have trailing punctuation (periods, commas, close-parens) and leading punctuation (opening parens) and it'll still do the right thing. - If trim_url_limit is not None, the URLs in the link text longer than this - limit will be truncated to trim_url_limit-3 characters and appended with - an ellipsis. + If trim_url_limit is not None, truncate the URLs in the link text longer + than this limit to trim_url_limit - 1 characters and append an ellipsis. - If nofollow is True, the links will get a rel="nofollow" attribute. + If nofollow is True, give the links a rel="nofollow" attribute. - If autoescape is True, the link text and URLs will be autoescaped. + If autoescape is True, autoescape the link text and URLs. """ safe_input = isinstance(text, SafeData) def trim_url(x, limit=trim_url_limit): if limit is None or len(x) <= limit: return x - return '%s...' % x[:max(0, limit - 3)] + return '%s…' % x[:max(0, limit - 1)] - def unescape(text, trail): + def unescape(text): """ - If input URL is HTML-escaped, unescape it so as we can safely feed it to - smart_urlquote. For example: + If input URL is HTML-escaped, unescape it so that it can be safely fed + to smart_urlquote. For example: http://example.com?x=1&y=<2> => http://example.com?x=1&y=<2> """ - unescaped = (text + trail).replace( - '&', '&').replace('<', '<').replace( + return text.replace('&', '&').replace('<', '<').replace( '>', '>').replace('"', '"').replace(''', "'") - if trail and unescaped.endswith(trail): - # Remove trail for unescaped if it was not consumed by unescape - unescaped = unescaped[:-len(trail)] - elif trail == ';': - # Trail was consumed by unescape (as end-of-entity marker), move it to text - text += trail - trail = '' - return text, unescaped, trail def trim_punctuation(lead, middle, trail): """ @@ -277,14 +277,6 @@ def trim_punctuation(lead, middle, trail): trimmed_something = True while trimmed_something: trimmed_something = False - - # Trim trailing punctuation. - match = TRAILING_PUNCTUATION_RE.match(middle) - if match: - middle = match.group(1) - trail = match.group(2) + trail - trimmed_something = True - # Trim wrapping punctuation. for opening, closing in WRAPPING_PUNCTUATION: if middle.startswith(opening): @@ -297,9 +289,33 @@ def trim_punctuation(lead, middle, trail): middle = middle[:-len(closing)] trail = closing + trail trimmed_something = True + # Trim trailing punctuation (after trimming wrapping punctuation, + # as encoded entities contain ';'). Unescape entites to avoid + # breaking them by removing ';'. + middle_unescaped = unescape(middle) + stripped = middle_unescaped.rstrip(TRAILING_PUNCTUATION_CHARS) + if middle_unescaped != stripped: + trail = middle[len(stripped):] + trail + middle = middle[:len(stripped) - len(middle_unescaped)] + trimmed_something = True return lead, middle, trail - words = word_split_re.split(force_text(text)) + def is_email_simple(value): + """Return True if value looks like an email address.""" + # An @ must be in the middle of the value. + if '@' not in value or value.startswith('@') or value.endswith('@'): + return False + try: + p1, p2 = value.split('@') + except ValueError: + # value contains more than one @. + return False + # Dot must be in p2 (e.g. example.com) + if '.' not in p2 or p2.startswith('.'): + return False + return True + + words = word_split_re.split(str(text)) for i, word in enumerate(words): if '.' in word or '@' in word or ':' in word: # lead: Current punctuation trimmed from the beginning of the word. @@ -313,12 +329,10 @@ def trim_punctuation(lead, middle, trail): url = None nofollow_attr = ' rel="nofollow"' if nofollow else '' if simple_url_re.match(middle): - middle, middle_unescaped, trail = unescape(middle, trail) - url = smart_urlquote(middle_unescaped) + url = smart_urlquote(unescape(middle)) elif simple_url_2_re.match(middle): - middle, middle_unescaped, trail = unescape(middle, trail) - url = smart_urlquote('http://%s' % middle_unescaped) - elif ':' not in middle and simple_email_re.match(middle): + url = smart_urlquote('http://%s' % unescape(middle)) + elif ':' not in middle and is_email_simple(middle): local, domain = middle.rsplit('@', 1) try: domain = domain.encode('idna').decode('ascii') @@ -365,22 +379,12 @@ def html_safe(klass): "can't apply @html_safe to %s because it defines " "__html__()." % klass.__name__ ) - if six.PY2: - if '__unicode__' not in klass.__dict__: - raise ValueError( - "can't apply @html_safe to %s because it doesn't " - "define __unicode__()." % klass.__name__ - ) - klass_unicode = klass.__unicode__ - klass.__unicode__ = lambda self: mark_safe(klass_unicode(self)) - klass.__html__ = lambda self: unicode(self) # NOQA: unicode undefined on PY3 - else: - if '__str__' not in klass.__dict__: - raise ValueError( - "can't apply @html_safe to %s because it doesn't " - "define __str__()." % klass.__name__ - ) - klass_str = klass.__str__ - klass.__str__ = lambda self: mark_safe(klass_str(self)) - klass.__html__ = lambda self: str(self) + if '__str__' not in klass.__dict__: + raise ValueError( + "can't apply @html_safe to %s because it doesn't " + "define __str__()." % klass.__name__ + ) + klass_str = klass.__str__ + klass.__str__ = lambda self: mark_safe(klass_str(self)) + klass.__html__ = lambda self: str(self) return klass diff --git a/django/utils/html_parser.py b/django/utils/html_parser.py deleted file mode 100644 index 89646079dba9..000000000000 --- a/django/utils/html_parser.py +++ /dev/null @@ -1,22 +0,0 @@ -from django.utils import six -from django.utils.six.moves import html_parser as _html_parser - -try: - HTMLParseError = _html_parser.HTMLParseError -except AttributeError: - # create a dummy class for Python 3.5+ where it's been removed - class HTMLParseError(Exception): - pass - -if six.PY3: - class HTMLParser(_html_parser.HTMLParser): - """Explicitly set convert_charrefs to be False. - - This silences a deprecation warning on Python 3.4, but we can't do - it at call time because Python 2.7 does not have the keyword - argument. - """ - def __init__(self, convert_charrefs=False, **kwargs): - _html_parser.HTMLParser.__init__(self, convert_charrefs=convert_charrefs, **kwargs) -else: - HTMLParser = _html_parser.HTMLParser diff --git a/django/utils/http.py b/django/utils/http.py index cba2a080956a..de1ea713685a 100644 --- a/django/utils/http.py +++ b/django/utils/http.py @@ -1,25 +1,21 @@ -from __future__ import unicode_literals - import base64 import calendar import datetime import re -import sys import unicodedata import warnings from binascii import Error as BinasciiError from email.utils import formatdate +from urllib.parse import ( + ParseResult, SplitResult, _coerce_args, _splitnetloc, _splitparams, quote, + quote_plus, scheme_chars, unquote, unquote_plus, + urlencode as original_urlencode, uses_params, +) from django.core.exceptions import TooManyFieldsSent -from django.utils import six from django.utils.datastructures import MultiValueDict -from django.utils.deprecation import RemovedInDjango21Warning -from django.utils.encoding import force_bytes, force_str, force_text +from django.utils.deprecation import RemovedInDjango30Warning from django.utils.functional import keep_lazy_text -from django.utils.six.moves.urllib.parse import ( - quote, quote_plus, unquote, unquote_plus, urlencode as original_urlencode, - urlparse, -) # based on RFC 7232, Appendix C ETAG_MATCH = re.compile(r''' @@ -42,8 +38,8 @@ RFC850_DATE = re.compile(r'^\w{6,9}, %s-%s-%s %s GMT$' % (__D, __M, __Y2, __T)) ASCTIME_DATE = re.compile(r'^\w{3} %s %s %s %s$' % (__M, __D2, __T, __Y)) -RFC3986_GENDELIMS = str(":/?#[]@") -RFC3986_SUBDELIMS = str("!$&'()*+,;=") +RFC3986_GENDELIMS = ":/?#[]@" +RFC3986_SUBDELIMS = "!$&'()*+,;=" FIELDS_MATCH = re.compile('[&;]') @@ -51,98 +47,122 @@ @keep_lazy_text def urlquote(url, safe='/'): """ - A version of Python's urllib.quote() function that can operate on unicode - strings. The url is first UTF-8 encoded before quoting. The returned string - can safely be used as part of an argument to a subsequent iri_to_uri() call - without double-quoting occurring. + A legacy compatibility wrapper to Python's urllib.parse.quote() function. + (was used for unicode handling on Python 2) """ - return force_text(quote(force_str(url), force_str(safe))) + return quote(url, safe) @keep_lazy_text def urlquote_plus(url, safe=''): """ - A version of Python's urllib.quote_plus() function that can operate on - unicode strings. The url is first UTF-8 encoded before quoting. The - returned string can safely be used as part of an argument to a subsequent - iri_to_uri() call without double-quoting occurring. + A legacy compatibility wrapper to Python's urllib.parse.quote_plus() + function. (was used for unicode handling on Python 2) """ - return force_text(quote_plus(force_str(url), force_str(safe))) + return quote_plus(url, safe) @keep_lazy_text def urlunquote(quoted_url): """ - A wrapper for Python's urllib.unquote() function that can operate on - the result of django.utils.http.urlquote(). + A legacy compatibility wrapper to Python's urllib.parse.unquote() function. + (was used for unicode handling on Python 2) """ - return force_text(unquote(force_str(quoted_url))) + return unquote(quoted_url) @keep_lazy_text def urlunquote_plus(quoted_url): """ - A wrapper for Python's urllib.unquote_plus() function that can operate on - the result of django.utils.http.urlquote_plus(). + A legacy compatibility wrapper to Python's urllib.parse.unquote_plus() + function. (was used for unicode handling on Python 2) """ - return force_text(unquote_plus(force_str(quoted_url))) + return unquote_plus(quoted_url) -def urlencode(query, doseq=0): +def urlencode(query, doseq=False): """ - A version of Python's urllib.urlencode() function that can operate on - unicode strings. The parameters are first cast to UTF-8 encoded strings and - then encoded as per normal. + A version of Python's urllib.parse.urlencode() function that can operate on + MultiValueDict and non-string values. """ if isinstance(query, MultiValueDict): query = query.lists() elif hasattr(query, 'items'): query = query.items() - return original_urlencode( - [(force_str(k), - [force_str(i) for i in v] if isinstance(v, (list, tuple)) else force_str(v)) - for k, v in query], - doseq) + query_params = [] + for key, value in query: + if value is None: + raise TypeError( + 'Cannot encode None in a query string. Did you mean to pass ' + 'an empty string or omit the value?' + ) + elif isinstance(value, (str, bytes)): + query_val = value + else: + try: + itr = iter(value) + except TypeError: + query_val = value + else: + # Consume generators and iterators, even when doseq=True, to + # work around https://bugs.python.org/issue31706. + query_val = [] + for item in itr: + if item is None: + raise TypeError( + 'Cannot encode None in a query string. Did you ' + 'mean to pass an empty string or omit the value?' + ) + elif not isinstance(item, bytes): + item = str(item) + query_val.append(item) + query_params.append((key, query_val)) + return original_urlencode(query_params, doseq) def cookie_date(epoch_seconds=None): """ - Formats the time to ensure compatibility with Netscape's cookie standard. + Format the time to ensure compatibility with Netscape's cookie standard. - Accepts a floating point number expressed in seconds since the epoch, in - UTC - such as that outputted by time.time(). If set to None, defaults to - the current time. + `epoch_seconds` is a floating point number expressed in seconds since the + epoch, in UTC - such as that outputted by time.time(). If set to None, it + defaults to the current time. - Outputs a string in the format 'Wdy, DD-Mon-YYYY HH:MM:SS GMT'. + Output a string in the format 'Wdy, DD-Mon-YYYY HH:MM:SS GMT'. """ + warnings.warn( + 'cookie_date() is deprecated in favor of http_date(), which follows ' + 'the format of the latest RFC.', + RemovedInDjango30Warning, stacklevel=2, + ) rfcdate = formatdate(epoch_seconds) return '%s-%s-%s GMT' % (rfcdate[:7], rfcdate[8:11], rfcdate[12:25]) def http_date(epoch_seconds=None): """ - Formats the time to match the RFC1123 date format as specified by HTTP + Format the time to match the RFC1123 date format as specified by HTTP RFC7231 section 7.1.1.1. - Accepts a floating point number expressed in seconds since the epoch, in - UTC - such as that outputted by time.time(). If set to None, defaults to - the current time. + `epoch_seconds` is a floating point number expressed in seconds since the + epoch, in UTC - such as that outputted by time.time(). If set to None, it + defaults to the current time. - Outputs a string in the format 'Wdy, DD Mon YYYY HH:MM:SS GMT'. + Output a string in the format 'Wdy, DD Mon YYYY HH:MM:SS GMT'. """ return formatdate(epoch_seconds, usegmt=True) def parse_http_date(date): """ - Parses a date format as specified by HTTP RFC7231 section 7.1.1.1. + Parse a date format as specified by HTTP RFC7231 section 7.1.1.1. The three formats allowed by the RFC are accepted, even if only the first one is still in widespread use. - Returns an integer expressed in seconds since the epoch, in UTC. + Return an integer expressed in seconds since the epoch, in UTC. """ - # emails.Util.parsedate does the job for RFC1123 dates; unfortunately + # email.utils.parsedate() does the job for RFC1123 dates; unfortunately # RFC7231 makes it mandatory to support RFC850 dates too. So we roll # our own RFC-compliant parsing. for regex in RFC1123_DATE, RFC850_DATE, ASCTIME_DATE: @@ -165,13 +185,13 @@ def parse_http_date(date): sec = int(m.group('sec')) result = datetime.datetime(year, month, day, hour, min, sec) return calendar.timegm(result.utctimetuple()) - except Exception: - six.reraise(ValueError, ValueError("%r is not a valid date" % date), sys.exc_info()[2]) + except Exception as exc: + raise ValueError("%r is not a valid date" % date) from exc def parse_http_date_safe(date): """ - Same as parse_http_date, but returns None if the input is invalid. + Same as parse_http_date, but return None if the input is invalid. """ try: return parse_http_date(date) @@ -183,34 +203,22 @@ def parse_http_date_safe(date): def base36_to_int(s): """ - Converts a base 36 string to an ``int``. Raises ``ValueError` if the - input won't fit into an int. + Convert a base 36 string to an int. Raise ValueError if the input won't fit + into an int. """ # To prevent overconsumption of server resources, reject any - # base36 string that is long than 13 base36 digits (13 digits + # base36 string that is longer than 13 base36 digits (13 digits # is sufficient to base36-encode any 64-bit integer) if len(s) > 13: raise ValueError("Base36 input too large") - value = int(s, 36) - # ... then do a final check that the value will fit into an int to avoid - # returning a long (#15067). The long type was removed in Python 3. - if six.PY2 and value > sys.maxint: - raise ValueError("Base36 input too large") - return value + return int(s, 36) def int_to_base36(i): - """ - Converts an integer to a base36 string - """ + """Convert an integer to a base36 string.""" char_set = '0123456789abcdefghijklmnopqrstuvwxyz' if i < 0: raise ValueError("Negative base36 conversion input.") - if six.PY2: - if not isinstance(i, six.integer_types): - raise TypeError("Non-integer base36 conversion input.") - if i > sys.maxint: - raise ValueError("Base36 conversion input too large.") if i < 36: return char_set[i] b36 = '' @@ -222,18 +230,18 @@ def int_to_base36(i): def urlsafe_base64_encode(s): """ - Encodes a bytestring in base64 for use in URLs, stripping any trailing + Encode a bytestring to a base64 string for use in URLs. Strip any trailing equal signs. """ - return base64.urlsafe_b64encode(s).rstrip(b'\n=') + return base64.urlsafe_b64encode(s).rstrip(b'\n=').decode('ascii') def urlsafe_base64_decode(s): """ - Decodes a base64 encoded string, adding back any trailing equal signs that + Decode a base64 encoded string. Add back any trailing equal signs that might have been stripped. """ - s = force_bytes(s) + s = s.encode() try: return base64.urlsafe_b64decode(s.ljust(len(s) + len(s) % 4, b'=')) except (LookupError, BinasciiError) as e: @@ -284,12 +292,12 @@ def is_same_domain(host, pattern): ) -def is_safe_url(url, host=None, allowed_hosts=None, require_https=False): +def is_safe_url(url, allowed_hosts, require_https=False): """ Return ``True`` if the url is a safe redirection (i.e. it doesn't point to a different host and uses a safe scheme). - Always returns ``False`` on an empty url. + Always return ``False`` on an empty url. If ``require_https`` is ``True``, only 'https' will be considered a valid scheme, as opposed to 'http' and 'https' with the default, ``False``. @@ -298,33 +306,74 @@ def is_safe_url(url, host=None, allowed_hosts=None, require_https=False): url = url.strip() if not url: return False - if six.PY2: - try: - url = force_text(url) - except UnicodeDecodeError: - return False if allowed_hosts is None: allowed_hosts = set() - if host: - warnings.warn( - "The host argument is deprecated, use allowed_hosts instead.", - RemovedInDjango21Warning, - stacklevel=2, - ) - # Avoid mutating the passed in allowed_hosts. - allowed_hosts = allowed_hosts | {host} + elif isinstance(allowed_hosts, str): + allowed_hosts = {allowed_hosts} # Chrome treats \ completely as / in paths but it could be part of some # basic auth credentials so we need to check both URLs. return (_is_safe_url(url, allowed_hosts, require_https=require_https) and _is_safe_url(url.replace('\\', '/'), allowed_hosts, require_https=require_https)) +# Copied from urllib.parse.urlparse() but uses fixed urlsplit() function. +def _urlparse(url, scheme='', allow_fragments=True): + """Parse a URL into 6 components: + :///;?# + Return a 6-tuple: (scheme, netloc, path, params, query, fragment). + Note that we don't break the components up in smaller bits + (e.g. netloc is a single string) and we don't expand % escapes.""" + url, scheme, _coerce_result = _coerce_args(url, scheme) + splitresult = _urlsplit(url, scheme, allow_fragments) + scheme, netloc, url, query, fragment = splitresult + if scheme in uses_params and ';' in url: + url, params = _splitparams(url) + else: + params = '' + result = ParseResult(scheme, netloc, url, params, query, fragment) + return _coerce_result(result) + + +# Copied from urllib.parse.urlsplit() with +# https://github.com/python/cpython/pull/661 applied. +def _urlsplit(url, scheme='', allow_fragments=True): + """Parse a URL into 5 components: + :///?# + Return a 5-tuple: (scheme, netloc, path, query, fragment). + Note that we don't break the components up in smaller bits + (e.g. netloc is a single string) and we don't expand % escapes.""" + url, scheme, _coerce_result = _coerce_args(url, scheme) + netloc = query = fragment = '' + i = url.find(':') + if i > 0: + for c in url[:i]: + if c not in scheme_chars: + break + else: + scheme, url = url[:i].lower(), url[i + 1:] + + if url[:2] == '//': + netloc, url = _splitnetloc(url, 2) + if (('[' in netloc and ']' not in netloc) or + (']' in netloc and '[' not in netloc)): + raise ValueError("Invalid IPv6 URL") + if allow_fragments and '#' in url: + url, fragment = url.split('#', 1) + if '?' in url: + url, query = url.split('?', 1) + v = SplitResult(scheme, netloc, url, query, fragment) + return _coerce_result(v) + + def _is_safe_url(url, allowed_hosts, require_https=False): # Chrome considers any URL with more than two slashes to be absolute, but # urlparse is not so flexible. Treat any url with three slashes as unsafe. if url.startswith('///'): return False - url_info = urlparse(url) + try: + url_info = _urlparse(url) + except ValueError: # e.g. invalid IPv6 addresses + return False # Forbid URLs like http:///example.com - with a scheme, but without a hostname. # In that URL, example.com is not the hostname but, a path component. However, # Chrome will still consider example.com to be the hostname, so we must not @@ -382,21 +431,28 @@ def limited_parse_qsl(qs, keep_blank_values=False, encoding='utf-8', for name_value in pairs: if not name_value: continue - nv = name_value.split(str('='), 1) + nv = name_value.split('=', 1) if len(nv) != 2: # Handle case of a control-name with no equal sign if keep_blank_values: nv.append('') else: continue - if len(nv[1]) or keep_blank_values: - if six.PY3: - name = nv[0].replace('+', ' ') - name = unquote(name, encoding=encoding, errors=errors) - value = nv[1].replace('+', ' ') - value = unquote(value, encoding=encoding, errors=errors) - else: - name = unquote(nv[0].replace(b'+', b' ')) - value = unquote(nv[1].replace(b'+', b' ')) + if nv[1] or keep_blank_values: + name = nv[0].replace('+', ' ') + name = unquote(name, encoding=encoding, errors=errors) + value = nv[1].replace('+', ' ') + value = unquote(value, encoding=encoding, errors=errors) r.append((name, value)) return r + + +def escape_leading_slashes(url): + """ + If redirecting to an absolute path (two leading slashes), a slash must be + escaped to prevent browsers from handling the path as schemaless and + redirecting to another host. + """ + if url.startswith('//'): + url = '/%2F{}'.format(url[2:]) + return url diff --git a/django/utils/inspect.py b/django/utils/inspect.py index 597b2c095e10..293cbdffa1e5 100644 --- a/django/utils/inspect.py +++ b/django/utils/inspect.py @@ -1,41 +1,7 @@ -from __future__ import absolute_import - import inspect -from django.utils import six - - -def getargspec(func): - if six.PY2: - return inspect.getargspec(func) - - sig = inspect.signature(func) - args = [ - p.name for p in sig.parameters.values() - if p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD - ] - varargs = [ - p.name for p in sig.parameters.values() - if p.kind == inspect.Parameter.VAR_POSITIONAL - ] - varargs = varargs[0] if varargs else None - varkw = [ - p.name for p in sig.parameters.values() - if p.kind == inspect.Parameter.VAR_KEYWORD - ] - varkw = varkw[0] if varkw else None - defaults = [ - p.default for p in sig.parameters.values() - if p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD and p.default is not p.empty - ] or None - return args, varargs, varkw, defaults - def get_func_args(func): - if six.PY2: - argspec = inspect.getargspec(func) - return argspec.args[1:] # ignore 'self' - sig = inspect.signature(func) return [ arg_name for arg_name, param in sig.parameters.items() @@ -49,20 +15,6 @@ def get_func_full_args(func): does not have a default value, omit it in the tuple. Arguments such as *args and **kwargs are also included. """ - if six.PY2: - argspec = inspect.getargspec(func) - args = argspec.args[1:] # ignore 'self' - defaults = argspec.defaults or [] - # Split args into two lists depending on whether they have default value - no_default = args[:len(args) - len(defaults)] - with_default = args[len(args) - len(defaults):] - # Join the two lists and combine it with default values - args = [(arg,) for arg in no_default] + zip(with_default, defaults) - # Add possible *args and **kwargs and prepend them with '*' or '**' - varargs = [('*' + argspec.varargs,)] if argspec.varargs else [] - kwargs = [('**' + argspec.keywords,)] if argspec.keywords else [] - return args + varargs + kwargs - sig = inspect.signature(func) args = [] for arg_name, param in sig.parameters.items(): @@ -82,20 +34,6 @@ def get_func_full_args(func): def func_accepts_kwargs(func): - if six.PY2: - # Not all callables are inspectable with getargspec, so we'll - # try a couple different ways but in the end fall back on assuming - # it is -- we don't want to prevent registration of valid but weird - # callables. - try: - argspec = inspect.getargspec(func) - except TypeError: - try: - argspec = inspect.getargspec(func.__call__) - except (TypeError, AttributeError): - argspec = None - return not argspec or argspec[2] is not None - return any( p for p in inspect.signature(func).parameters.values() if p.kind == p.VAR_KEYWORD @@ -106,26 +44,20 @@ def func_accepts_var_args(func): """ Return True if function 'func' accepts positional arguments *args. """ - if six.PY2: - return inspect.getargspec(func)[1] is not None - return any( p for p in inspect.signature(func).parameters.values() if p.kind == p.VAR_POSITIONAL ) -def func_has_no_args(func): - args = inspect.getargspec(func)[0] if six.PY2 else [ - p for p in inspect.signature(func).parameters.values() +def method_has_no_args(meth): + """Return True if a method only accepts 'self'.""" + count = len([ + p for p in inspect.signature(meth).parameters.values() if p.kind == p.POSITIONAL_OR_KEYWORD - ] - return len(args) == 1 + ]) + return count == 0 if inspect.ismethod(meth) else count == 1 def func_supports_parameter(func, parameter): - if six.PY3: - return parameter in inspect.signature(func).parameters - else: - args, varargs, varkw, defaults = inspect.getargspec(func) - return parameter in args + return parameter in inspect.signature(func).parameters diff --git a/django/utils/ipv6.py b/django/utils/ipv6.py index c41f1e2b466c..ddb8c8091d2f 100644 --- a/django/utils/ipv6.py +++ b/django/utils/ipv6.py @@ -1,23 +1,18 @@ -# This code was mostly based on ipaddr-py -# Copyright 2007 Google Inc. https://github.com/google/ipaddr-py -# Licensed under the Apache License, Version 2.0 (the "License"). -import re +import ipaddress from django.core.exceptions import ValidationError -from django.utils.six.moves import range -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ def clean_ipv6_address(ip_str, unpack_ipv4=False, error_message=_("This is not a valid IPv6 address.")): """ - Cleans an IPv6 address string. + Clean an IPv6 address string. - Validity is checked by calling is_valid_ipv6_address() - if an - invalid address is passed, ValidationError is raised. + Raise ValidationError if the address is invalid. - Replaces the longest continuous zero-sequence with "::" and - removes leading zeroes and makes sure all hextets are lowercase. + Replace the longest continuous zero-sequence with "::", remove leading + zeroes, and make sure all hextets are lowercase. Args: ip_str: A valid IPv6 address. @@ -25,250 +20,27 @@ def clean_ipv6_address(ip_str, unpack_ipv4=False, return the plain IPv4 address (default=False). error_message: An error message used in the ValidationError. - Returns: - A compressed IPv6 address, or the same value + Return a compressed IPv6 address or the same value. """ - best_doublecolon_start = -1 - best_doublecolon_len = 0 - doublecolon_start = -1 - doublecolon_len = 0 - - if not is_valid_ipv6_address(ip_str): + try: + addr = ipaddress.IPv6Address(int(ipaddress.IPv6Address(ip_str))) + except ValueError: raise ValidationError(error_message, code='invalid') - # This algorithm can only handle fully exploded - # IP strings - ip_str = _explode_shorthand_ip_string(ip_str) - - ip_str = _sanitize_ipv4_mapping(ip_str) - - # If needed, unpack the IPv4 and return straight away - # - no need in running the rest of the algorithm - if unpack_ipv4: - ipv4_unpacked = _unpack_ipv4(ip_str) - - if ipv4_unpacked: - return ipv4_unpacked - - hextets = ip_str.split(":") - - for index in range(len(hextets)): - # Remove leading zeroes - if '.' not in hextets[index]: - hextets[index] = hextets[index].lstrip('0') - if not hextets[index]: - hextets[index] = '0' - - # Determine best hextet to compress - if hextets[index] == '0': - doublecolon_len += 1 - if doublecolon_start == -1: - # Start of a sequence of zeros. - doublecolon_start = index - if doublecolon_len > best_doublecolon_len: - # This is the longest sequence of zeros so far. - best_doublecolon_len = doublecolon_len - best_doublecolon_start = doublecolon_start - else: - doublecolon_len = 0 - doublecolon_start = -1 - - # Compress the most suitable hextet - if best_doublecolon_len > 1: - best_doublecolon_end = (best_doublecolon_start + - best_doublecolon_len) - # For zeros at the end of the address. - if best_doublecolon_end == len(hextets): - hextets += [''] - hextets[best_doublecolon_start:best_doublecolon_end] = [''] - # For zeros at the beginning of the address. - if best_doublecolon_start == 0: - hextets = [''] + hextets - - result = ":".join(hextets) - - return result.lower() - - -def _sanitize_ipv4_mapping(ip_str): - """ - Sanitize IPv4 mapping in an expanded IPv6 address. - - This converts ::ffff:0a0a:0a0a to ::ffff:10.10.10.10. - If there is nothing to sanitize, returns an unchanged - string. - - Args: - ip_str: A string, the expanded IPv6 address. - - Returns: - The sanitized output string, if applicable. - """ - if not ip_str.lower().startswith('0000:0000:0000:0000:0000:ffff:'): - # not an ipv4 mapping - return ip_str - - hextets = ip_str.split(':') - - if '.' in hextets[-1]: - # already sanitized - return ip_str - - ipv4_address = "%d.%d.%d.%d" % ( - int(hextets[6][0:2], 16), - int(hextets[6][2:4], 16), - int(hextets[7][0:2], 16), - int(hextets[7][2:4], 16), - ) - - result = ':'.join(hextets[0:6]) - result += ':' + ipv4_address + if unpack_ipv4 and addr.ipv4_mapped: + return str(addr.ipv4_mapped) + elif addr.ipv4_mapped: + return '::ffff:%s' % str(addr.ipv4_mapped) - return result - - -def _unpack_ipv4(ip_str): - """ - Unpack an IPv4 address that was mapped in a compressed IPv6 address. - - This converts 0000:0000:0000:0000:0000:ffff:10.10.10.10 to 10.10.10.10. - If there is nothing to sanitize, returns None. - - Args: - ip_str: A string, the expanded IPv6 address. - - Returns: - The unpacked IPv4 address, or None if there was nothing to unpack. - """ - if not ip_str.lower().startswith('0000:0000:0000:0000:0000:ffff:'): - return None - - return ip_str.rsplit(':', 1)[1] + return str(addr) def is_valid_ipv6_address(ip_str): """ - Ensure we have a valid IPv6 address. - - Args: - ip_str: A string, the IPv6 address. - - Returns: - A boolean, True if this is a valid IPv6 address. + Return whether or not the `ip_str` string is a valid IPv6 address. """ - from django.core.validators import validate_ipv4_address - - symbols_re = re.compile(r'^[0-9a-fA-F:.]+$') - if not symbols_re.match(ip_str): - return False - - # We need to have at least one ':'. - if ':' not in ip_str: - return False - - # We can only have one '::' shortener. - if ip_str.count('::') > 1: - return False - - # '::' should be encompassed by start, digits or end. - if ':::' in ip_str: - return False - - # A single colon can neither start nor end an address. - if ((ip_str.startswith(':') and not ip_str.startswith('::')) or - (ip_str.endswith(':') and not ip_str.endswith('::'))): + try: + ipaddress.IPv6Address(ip_str) + except ValueError: return False - - # We can never have more than 7 ':' (1::2:3:4:5:6:7:8 is invalid) - if ip_str.count(':') > 7: - return False - - # If we have no concatenation, we need to have 8 fields with 7 ':'. - if '::' not in ip_str and ip_str.count(':') != 7: - # We might have an IPv4 mapped address. - if ip_str.count('.') != 3: - return False - - ip_str = _explode_shorthand_ip_string(ip_str) - - # Now that we have that all squared away, let's check that each of the - # hextets are between 0x0 and 0xFFFF. - for hextet in ip_str.split(':'): - if hextet.count('.') == 3: - # If we have an IPv4 mapped address, the IPv4 portion has to - # be at the end of the IPv6 portion. - if not ip_str.split(':')[-1] == hextet: - return False - try: - validate_ipv4_address(hextet) - except ValidationError: - return False - else: - try: - # a value error here means that we got a bad hextet, - # something like 0xzzzz - if int(hextet, 16) < 0x0 or int(hextet, 16) > 0xFFFF: - return False - except ValueError: - return False return True - - -def _explode_shorthand_ip_string(ip_str): - """ - Expand a shortened IPv6 address. - - Args: - ip_str: A string, the IPv6 address. - - Returns: - A string, the expanded IPv6 address. - """ - if not _is_shorthand_ip(ip_str): - # We've already got a longhand ip_str. - return ip_str - - new_ip = [] - hextet = ip_str.split('::') - - # If there is a ::, we need to expand it with zeroes - # to get to 8 hextets - unless there is a dot in the last hextet, - # meaning we're doing v4-mapping - if '.' in ip_str.split(':')[-1]: - fill_to = 7 - else: - fill_to = 8 - - if len(hextet) > 1: - sep = len(hextet[0].split(':')) + len(hextet[1].split(':')) - new_ip = hextet[0].split(':') - - for __ in range(fill_to - sep): - new_ip.append('0000') - new_ip += hextet[1].split(':') - - else: - new_ip = ip_str.split(':') - - # Now need to make sure every hextet is 4 lower case characters. - # If a hextet is < 4 characters, we've got missing leading 0's. - ret_ip = [] - for hextet in new_ip: - ret_ip.append(('0' * (4 - len(hextet)) + hextet).lower()) - return ':'.join(ret_ip) - - -def _is_shorthand_ip(ip_str): - """Determine if the address is shortened. - - Args: - ip_str: A string, the IPv6 address. - - Returns: - A boolean, True if the address is shortened. - """ - if ip_str.count('::') == 1: - return True - if any(len(x) < 4 for x in ip_str.split(':')): - return True - return False diff --git a/django/utils/itercompat.py b/django/utils/itercompat.py index 096f80867146..9895e3f81657 100644 --- a/django/utils/itercompat.py +++ b/django/utils/itercompat.py @@ -1,12 +1,5 @@ -""" -Providing iterator functions that are not in all version of Python we support. -Where possible, we try to use the system-native version and only fall back to -these implementations if necessary. -""" - - def is_iterable(x): - "A implementation independent way of checking for iterables" + "An implementation independent way of checking for iterables" try: iter(x) except TypeError: diff --git a/django/utils/jslex.py b/django/utils/jslex.py index bef61291888d..b2ff689f33a3 100644 --- a/django/utils/jslex.py +++ b/django/utils/jslex.py @@ -1,11 +1,9 @@ """JsLex: a lexer for Javascript""" # Originally from https://bitbucket.org/ned/jslex -from __future__ import unicode_literals - import re -class Tok(object): +class Tok: """ A specification for a token class. """ @@ -29,7 +27,7 @@ def literals(choices, prefix="", suffix=""): return "|".join(prefix + re.escape(c) + suffix for c in choices.split()) -class Lexer(object): +class Lexer: """ A generic multi-state regex-based lexer. """ @@ -52,7 +50,7 @@ def lex(self, text): """ Lexically analyze `text`. - Yields pairs (`name`, `tokentext`). + Yield pairs (`name`, `tokentext`). """ end = len(text) state = self.state @@ -181,7 +179,7 @@ class JsLexer(Lexer): } def __init__(self): - super(JsLexer, self).__init__(self.states, 'reg') + super().__init__(self.states, 'reg') def prepare_js_for_gettext(js): diff --git a/django/utils/log.py b/django/utils/log.py index 2b57c6beffe6..2de6dbbb598c 100644 --- a/django/utils/log.py +++ b/django/utils/log.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import logging import logging.config # needed when logging_config doesn't start with logging.config from copy import copy @@ -11,6 +9,8 @@ from django.utils.module_loading import import_string from django.views.debug import ExceptionReporter +request_logger = logging.getLogger('django.request') + # Default logging for Django. This sends an email to the site admins on every # HTTP 500 error. Depending on DEBUG, all other log records are either sent to # the console (DEBUG=True) or discarded (DEBUG=False) by means of the @@ -29,7 +29,8 @@ 'formatters': { 'django.server': { '()': 'django.utils.log.ServerFormatter', - 'format': '[%(server_time)s] %(message)s', + 'format': '[{server_time}] {message}', + 'style': '{', } }, 'handlers': { @@ -83,7 +84,7 @@ class AdminEmailHandler(logging.Handler): """ def __init__(self, include_html=False, email_backend=None): - logging.Handler.__init__(self) + super().__init__() self.include_html = include_html self.email_backend = email_backend @@ -161,7 +162,7 @@ def filter(self, record): class ServerFormatter(logging.Formatter): def __init__(self, *args, **kwargs): self.style = color_style() - super(ServerFormatter, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def format(self, record): msg = record.msg @@ -189,7 +190,41 @@ def format(self, record): record.server_time = self.formatTime(record, self.datefmt) record.msg = msg - return super(ServerFormatter, self).format(record) + return super().format(record) def uses_server_time(self): - return self._fmt.find('%(server_time)') >= 0 + return self._fmt.find('{server_time}') >= 0 + + +def log_response(message, *args, response=None, request=None, logger=request_logger, level=None, exc_info=None): + """ + Log errors based on HttpResponse status. + + Log 5xx responses as errors and 4xx responses as warnings (unless a level + is given as a keyword argument). The HttpResponse status_code and the + request are passed to the logger's extra parameter. + """ + # Check if the response has already been logged. Multiple requests to log + # the same response can be received in some cases, e.g., when the + # response is the result of an exception and is logged at the time the + # exception is caught so that the exc_info can be recorded. + if getattr(response, '_has_been_logged', False): + return + + if level is None: + if response.status_code >= 500: + level = 'error' + elif response.status_code >= 400: + level = 'warning' + else: + level = 'info' + + getattr(logger, level)( + message, *args, + extra={ + 'status_code': response.status_code, + 'request': request, + }, + exc_info=exc_info, + ) + response._has_been_logged = True diff --git a/django/utils/lorem_ipsum.py b/django/utils/lorem_ipsum.py index 03426c152bef..cfa675d70aba 100644 --- a/django/utils/lorem_ipsum.py +++ b/django/utils/lorem_ipsum.py @@ -2,8 +2,6 @@ Utility functions for generating "lorem ipsum" Latin text. """ -from __future__ import unicode_literals - import random COMMON_P = ( @@ -57,7 +55,7 @@ def sentence(): """ - Returns a randomly generated sentence of lorem ipsum text. + Return a randomly generated sentence of lorem ipsum text. The first word is capitalized, and the sentence ends in either a period or question mark. Commas are added at random. @@ -72,7 +70,7 @@ def sentence(): def paragraph(): """ - Returns a randomly generated paragraph of lorem ipsum text. + Return a randomly generated paragraph of lorem ipsum text. The paragraph consists of between 1 and 4 sentences, inclusive. """ @@ -81,7 +79,7 @@ def paragraph(): def paragraphs(count, common=True): """ - Returns a list of paragraphs as returned by paragraph(). + Return a list of paragraphs as returned by paragraph(). If `common` is True, then the first paragraph will be the standard 'lorem ipsum' paragraph. Otherwise, the first paragraph will be random @@ -98,7 +96,7 @@ def paragraphs(count, common=True): def words(count, common=True): """ - Returns a string of `count` lorem ipsum words separated by a single space. + Return a string of `count` lorem ipsum words separated by a single space. If `common` is True, then the first 19 words will be the standard 'lorem ipsum' words. Otherwise, all words will be selected randomly. diff --git a/django/utils/lru_cache.py b/django/utils/lru_cache.py index 543296e64815..f5187ded565e 100644 --- a/django/utils/lru_cache.py +++ b/django/utils/lru_cache.py @@ -1,172 +1,5 @@ -try: - from functools import lru_cache +from functools import lru_cache # noqa -except ImportError: - # backport of Python's 3.3 lru_cache, written by Raymond Hettinger and - # licensed under MIT license, from: - # - # Should be removed when Django only supports Python 3.2 and above. - - from collections import namedtuple - from functools import update_wrapper - from threading import RLock - - _CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"]) - - class _HashedSeq(list): - __slots__ = 'hashvalue' - - def __init__(self, tup, hash=hash): - self[:] = tup - self.hashvalue = hash(tup) - - def __hash__(self): - return self.hashvalue - - def _make_key(args, kwds, typed, - kwd_mark = (object(),), - fasttypes = {int, str, frozenset, type(None)}, - sorted=sorted, tuple=tuple, type=type, len=len): - 'Make a cache key from optionally typed positional and keyword arguments' - key = args - if kwds: - sorted_items = sorted(kwds.items()) - key += kwd_mark - for item in sorted_items: - key += item - if typed: - key += tuple(type(v) for v in args) - if kwds: - key += tuple(type(v) for k, v in sorted_items) - elif len(key) == 1 and type(key[0]) in fasttypes: - return key[0] - return _HashedSeq(key) - - def lru_cache(maxsize=100, typed=False): - """Least-recently-used cache decorator. - - If *maxsize* is set to None, the LRU features are disabled and the cache - can grow without bound. - - If *typed* is True, arguments of different types will be cached separately. - For example, f(3.0) and f(3) will be treated as distinct calls with - distinct results. - - Arguments to the cached function must be hashable. - - View the cache statistics named tuple (hits, misses, maxsize, currsize) with - f.cache_info(). Clear the cache and statistics with f.cache_clear(). - Access the underlying function with f.__wrapped__. - - See: https://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used - """ - - # Users should only access the lru_cache through its public API: - # cache_info, cache_clear, and f.__wrapped__ - # The internals of the lru_cache are encapsulated for thread safety and - # to allow the implementation to change (including a possible C version). - - def decorating_function(user_function): - - cache = dict() - stats = [0, 0] # make statistics updateable non-locally - HITS, MISSES = 0, 1 # names for the stats fields - make_key = _make_key - cache_get = cache.get # bound method to lookup key or return None - _len = len # localize the global len() function - lock = RLock() # because linkedlist updates aren't threadsafe - root = [] # root of the circular doubly linked list - root[:] = [root, root, None, None] # initialize by pointing to self - nonlocal_root = [root] # make updateable non-locally - PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 # names for the link fields - - if maxsize == 0: - - def wrapper(*args, **kwds): - # no caching, just do a statistics update after a successful call - result = user_function(*args, **kwds) - stats[MISSES] += 1 - return result - - elif maxsize is None: - - def wrapper(*args, **kwds): - # simple caching without ordering or size limit - key = make_key(args, kwds, typed) - result = cache_get(key, root) # root used here as a unique not-found sentinel - if result is not root: - stats[HITS] += 1 - return result - result = user_function(*args, **kwds) - cache[key] = result - stats[MISSES] += 1 - return result - - else: - - def wrapper(*args, **kwds): - # size limited caching that tracks accesses by recency - key = make_key(args, kwds, typed) if kwds or typed else args - with lock: - link = cache_get(key) - if link is not None: - # record recent use of the key by moving it to the front of the list - root, = nonlocal_root - link_prev, link_next, key, result = link - link_prev[NEXT] = link_next - link_next[PREV] = link_prev - last = root[PREV] - last[NEXT] = root[PREV] = link - link[PREV] = last - link[NEXT] = root - stats[HITS] += 1 - return result - result = user_function(*args, **kwds) - with lock: - root, = nonlocal_root - if key in cache: - # getting here means that this same key was added to the - # cache while the lock was released. since the link - # update is already done, we need only return the - # computed result and update the count of misses. - pass - elif _len(cache) >= maxsize: - # use the old root to store the new key and result - oldroot = root - oldroot[KEY] = key - oldroot[RESULT] = result - # empty the oldest link and make it the new root - root = nonlocal_root[0] = oldroot[NEXT] - oldkey = root[KEY] - oldvalue = root[RESULT] - root[KEY] = root[RESULT] = None - # now update the cache dictionary for the new links - del cache[oldkey] - cache[key] = oldroot - else: - # put result in a new link at the front of the list - last = root[PREV] - link = [last, root, key, result] - last[NEXT] = root[PREV] = cache[key] = link - stats[MISSES] += 1 - return result - - def cache_info(): - """Report cache statistics""" - with lock: - return _CacheInfo(stats[HITS], stats[MISSES], maxsize, len(cache)) - - def cache_clear(): - """Clear the cache and cache statistics""" - with lock: - cache.clear() - root = nonlocal_root[0] - root[:] = [root, root, None, None] - stats[:] = [0, 0] - - wrapper.__wrapped__ = user_function - wrapper.cache_info = cache_info - wrapper.cache_clear = cache_clear - return update_wrapper(wrapper, user_function) - - return decorating_function +# Deprecate or remove this module when no supported version of Django still +# supports Python 2. Until then, keep it to allow pluggable apps to support +# Python 2 and Python 3 without raising a deprecation warning. diff --git a/django/utils/module_loading.py b/django/utils/module_loading.py index 9dcbc926b9ef..38119431fa84 100644 --- a/django/utils/module_loading.py +++ b/django/utils/module_loading.py @@ -1,9 +1,7 @@ import copy import os -import sys from importlib import import_module - -from django.utils import six +from importlib.util import find_spec as importlib_find def import_string(dotted_path): @@ -13,18 +11,17 @@ def import_string(dotted_path): """ try: module_path, class_name = dotted_path.rsplit('.', 1) - except ValueError: - msg = "%s doesn't look like a module path" % dotted_path - six.reraise(ImportError, ImportError(msg), sys.exc_info()[2]) + except ValueError as err: + raise ImportError("%s doesn't look like a module path" % dotted_path) from err module = import_module(module_path) try: return getattr(module, class_name) - except AttributeError: - msg = 'Module "%s" does not define a "%s" attribute/class' % ( + except AttributeError as err: + raise ImportError('Module "%s" does not define a "%s" attribute/class' % ( module_path, class_name) - six.reraise(ImportError, ImportError(msg), sys.exc_info()[2]) + ) from err def autodiscover_modules(*args, **kwargs): @@ -63,88 +60,24 @@ def autodiscover_modules(*args, **kwargs): raise -if six.PY3: - from importlib.util import find_spec as importlib_find - - def module_has_submodule(package, module_name): - """See if 'module' is in 'package'.""" - try: - package_name = package.__name__ - package_path = package.__path__ - except AttributeError: - # package isn't a package. - return False +def module_has_submodule(package, module_name): + """See if 'module' is in 'package'.""" + try: + package_name = package.__name__ + package_path = package.__path__ + except AttributeError: + # package isn't a package. + return False - full_module_name = package_name + '.' + module_name + full_module_name = package_name + '.' + module_name + try: return importlib_find(full_module_name, package_path) is not None - -else: - import imp - - def module_has_submodule(package, module_name): - """See if 'module' is in 'package'.""" - name = ".".join([package.__name__, module_name]) - try: - # None indicates a cached miss; see mark_miss() in Python/import.c. - return sys.modules[name] is not None - except KeyError: - pass - try: - package_path = package.__path__ # No __path__, then not a package. - except AttributeError: - # Since the remainder of this function assumes that we're dealing with - # a package (module with a __path__), so if it's not, then bail here. - return False - for finder in sys.meta_path: - if finder.find_module(name, package_path): - return True - for entry in package_path: - try: - # Try the cached finder. - finder = sys.path_importer_cache[entry] - if finder is None: - # Implicit import machinery should be used. - try: - file_, _, _ = imp.find_module(module_name, [entry]) - if file_: - file_.close() - return True - except ImportError: - continue - # Else see if the finder knows of a loader. - elif finder.find_module(name): - return True - else: - continue - except KeyError: - # No cached finder, so try and make one. - for hook in sys.path_hooks: - try: - finder = hook(entry) - # XXX Could cache in sys.path_importer_cache - if finder.find_module(name): - return True - else: - # Once a finder is found, stop the search. - break - except ImportError: - # Continue the search for a finder. - continue - else: - # No finder found. - # Try the implicit import machinery if searching a directory. - if os.path.isdir(entry): - try: - file_, _, _ = imp.find_module(module_name, [entry]) - if file_: - file_.close() - return True - except ImportError: - pass - # XXX Could insert None or NullImporter - else: - # Exhausted the search, so the module cannot be found. - return False + except (ImportError, AttributeError): + # When module_name is an invalid dotted path, Python raises ImportError + # (or ModuleNotFoundError in Python 3.6+). AttributeError may be raised + # if the penultimate part of the path is not a package. + # (https://bugs.python.org/issue30436) + return False def module_dir(module): diff --git a/django/utils/numberformat.py b/django/utils/numberformat.py index ae5a3b547410..88b35fc435c9 100644 --- a/django/utils/numberformat.py +++ b/django/utils/numberformat.py @@ -1,16 +1,13 @@ -from __future__ import unicode_literals - from decimal import Decimal from django.conf import settings -from django.utils import six from django.utils.safestring import mark_safe def format(number, decimal_sep, decimal_pos=None, grouping=0, thousand_sep='', - force_grouping=False): + force_grouping=False, use_l10n=None): """ - Gets a number (as a number or string), and returns it as a string, + Get a number (as a number or string), and return it as a string, using formats defined as arguments: * decimal_sep: Decimal separator symbol (for example ".") @@ -21,18 +18,31 @@ def format(number, decimal_sep, decimal_pos=None, grouping=0, thousand_sep='', module in locale.localeconv() LC_NUMERIC grouping (e.g. (3, 2, 0)). * thousand_sep: Thousand separator symbol (for example ",") """ - use_grouping = settings.USE_L10N and settings.USE_THOUSAND_SEPARATOR + use_grouping = (use_l10n or (use_l10n is None and settings.USE_L10N)) and settings.USE_THOUSAND_SEPARATOR use_grouping = use_grouping or force_grouping use_grouping = use_grouping and grouping != 0 # Make the common case fast if isinstance(number, int) and not use_grouping and not decimal_pos: - return mark_safe(six.text_type(number)) + return mark_safe(number) # sign sign = '' if isinstance(number, Decimal): - str_number = '{:f}'.format(number) + # Format values with more than 200 digits (an arbitrary cutoff) using + # scientific notation to avoid high memory usage in {:f}'.format(). + _, digits, exponent = number.as_tuple() + if abs(exponent) + len(digits) > 200: + number = '{:e}'.format(number) + coefficient, exponent = number.split('e') + # Format the coefficient. + coefficient = format( + coefficient, decimal_sep, decimal_pos, grouping, + thousand_sep, force_grouping, use_l10n, + ) + return '{}e{}'.format(coefficient, exponent) + else: + str_number = '{:f}'.format(number) else: - str_number = six.text_type(number) + str_number = str(number) if str_number[0] == '-': sign = '-' str_number = str_number[1:] @@ -45,8 +55,7 @@ def format(number, decimal_sep, decimal_pos=None, grouping=0, thousand_sep='', int_part, dec_part = str_number, '' if decimal_pos is not None: dec_part = dec_part + ('0' * (decimal_pos - len(dec_part))) - if dec_part: - dec_part = decimal_sep + dec_part + dec_part = dec_part and decimal_sep + dec_part # grouping if use_grouping: try: diff --git a/django/utils/regex_helper.py b/django/utils/regex_helper.py index d046b71d96e1..8d55a792723c 100644 --- a/django/utils/regex_helper.py +++ b/django/utils/regex_helper.py @@ -5,14 +5,6 @@ This is not, and is not intended to be, a complete reg-exp decompiler. It should be good enough for a large class of URLS, however. """ -from __future__ import unicode_literals - -import warnings - -from django.utils import six -from django.utils.deprecation import RemovedInDjango21Warning -from django.utils.six.moves import zip - # Mapping of an escape character to a representative of that class. So, e.g., # "\w" is replaced by "x" in a reverse URL. A value of None means to ignore # this sequence. Any missing key is mapped to itself. @@ -31,28 +23,20 @@ class Choice(list): - """ - Used to represent multiple possibilities at this point in a pattern string. - We use a distinguished type, rather than a list, so that the usage in the - code is clear. - """ + """Represent multiple possibilities at this point in a pattern string.""" class Group(list): - """ - Used to represent a capturing group in the pattern string. - """ + """Represent a capturing group in the pattern string.""" class NonCapture(list): - """ - Used to represent a non-capturing group in the pattern string. - """ + """Represent a non-capturing group in the pattern string.""" def normalize(pattern): r""" - Given a reg-exp pattern, normalizes it to an iterable of forms that + Given a reg-exp pattern, normalize it to an iterable of forms that suffice for reverse matching. This does the following: (1) For any repeating sections, keeps the minimum number of occurrences @@ -133,12 +117,6 @@ def normalize(pattern): # All of these are ignorable. Walk to the end of the # group. walk_to_end(ch, pattern_iter) - elif ch in 'iLmsu#': - warnings.warn( - 'Using (?%s) in url() patterns is deprecated.' % ch, - RemovedInDjango21Warning - ) - walk_to_end(ch, pattern_iter) elif ch == ':': # Non-capturing group non_capturing_groups.append(len(result)) @@ -198,8 +176,7 @@ def normalize(pattern): if consume_next: ch, escaped = next(pattern_iter) - else: - consume_next = True + consume_next = True except StopIteration: pass except NotImplementedError: @@ -216,7 +193,7 @@ def next_char(input_iter): its class (e.g. \w -> "x"). If the escaped character is one that is skipped, it is not returned (the next character is returned instead). - Yields the next character, along with a boolean indicating whether it is a + Yield the next character, along with a boolean indicating whether it is a raw (unescaped) character or not. """ for ch in input_iter: @@ -232,8 +209,8 @@ def next_char(input_iter): def walk_to_end(ch, input_iter): """ - The iterator is currently inside a capturing group. We want to walk to the - close of this group, skipping over any nested groups and handling escaped + The iterator is currently inside a capturing group. Walk to the close of + this group, skipping over any nested groups and handling escaped parentheses correctly. """ if ch == '(': @@ -256,7 +233,7 @@ def get_quantifier(ch, input_iter): Parse a quantifier from the input, where "ch" is the first character in the quantifier. - Returns the minimum number of occurrences permitted by the quantifier and + Return the minimum number of occurrences permitted by the quantifier and either None or the next character from the input_iter if the next character is not part of the quantifier. """ @@ -290,7 +267,7 @@ def get_quantifier(ch, input_iter): def contains(source, inst): """ - Returns True if the "source" contains an instance of "inst". False, + Return True if the "source" contains an instance of "inst". False, otherwise. """ if isinstance(source, inst): @@ -304,8 +281,8 @@ def contains(source, inst): def flatten_result(source): """ - Turns the given source sequence into a list of reg-exp possibilities and - their arguments. Returns a list of strings and a list of argument lists. + Turn the given source sequence into a list of reg-exp possibilities and + their arguments. Return a list of strings and a list of argument lists. Each of the two lists will be of the same length. """ if source is None: @@ -320,7 +297,7 @@ def flatten_result(source): result_args = [[]] pos = last = 0 for pos, elt in enumerate(source): - if isinstance(elt, six.string_types): + if isinstance(elt, str): continue piece = ''.join(source[last:pos]) if isinstance(elt, Group): diff --git a/django/utils/safestring.py b/django/utils/safestring.py index 76136d0b0cc2..21d7c5503c8b 100644 --- a/django/utils/safestring.py +++ b/django/utils/safestring.py @@ -4,43 +4,14 @@ that the producer of the string has already turned characters that should not be interpreted by the HTML engine (e.g. '<') into the appropriate entities. """ -import warnings -from django.utils import six -from django.utils.deprecation import RemovedInDjango20Warning -from django.utils.functional import Promise, curry, wraps +from django.utils.functional import wraps -class EscapeData(object): - pass - - -class EscapeBytes(bytes, EscapeData): - """ - A byte string that should be HTML-escaped when output. - """ - pass - - -class EscapeText(six.text_type, EscapeData): - """ - A unicode string object that should be HTML-escaped when output. - """ - pass - - -if six.PY3: - EscapeString = EscapeText -else: - EscapeString = EscapeBytes - # backwards compatibility for Python 2 - EscapeUnicode = EscapeText - - -class SafeData(object): +class SafeData: def __html__(self): """ - Returns the html representation of a string for interoperability. + Return the html representation of a string for interoperability. This allows other template engines to understand Django's SafeData. """ @@ -51,72 +22,43 @@ class SafeBytes(bytes, SafeData): """ A bytes subclass that has been specifically marked as "safe" (requires no further escaping) for HTML output purposes. + + Kept in Django 2.0 for usage by apps supporting Python 2. Shouldn't be used + in Django anymore. """ def __add__(self, rhs): """ Concatenating a safe byte string with another safe byte string or safe - unicode string is safe. Otherwise, the result is no longer safe. + string is safe. Otherwise, the result is no longer safe. """ - t = super(SafeBytes, self).__add__(rhs) + t = super().__add__(rhs) if isinstance(rhs, SafeText): return SafeText(t) elif isinstance(rhs, SafeBytes): return SafeBytes(t) return t - def _proxy_method(self, *args, **kwargs): - """ - Wrap a call to a normal unicode method up so that we return safe - results. The method that is being wrapped is passed in the 'method' - argument. - """ - method = kwargs.pop('method') - data = method(self, *args, **kwargs) - if isinstance(data, bytes): - return SafeBytes(data) - else: - return SafeText(data) - - decode = curry(_proxy_method, method=bytes.decode) - -class SafeText(six.text_type, SafeData): +class SafeText(str, SafeData): """ - A unicode (Python 2) / str (Python 3) subclass that has been specifically - marked as "safe" for HTML output purposes. + A str subclass that has been specifically marked as "safe" for HTML output + purposes. """ def __add__(self, rhs): """ - Concatenating a safe unicode string with another safe byte string or - safe unicode string is safe. Otherwise, the result is no longer safe. + Concatenating a safe string with another safe bytestring or + safe string is safe. Otherwise, the result is no longer safe. """ - t = super(SafeText, self).__add__(rhs) + t = super().__add__(rhs) if isinstance(rhs, SafeData): return SafeText(t) return t - def _proxy_method(self, *args, **kwargs): - """ - Wrap a call to a normal unicode method up so that we return safe - results. The method that is being wrapped is passed in the 'method' - argument. - """ - method = kwargs.pop('method') - data = method(self, *args, **kwargs) - if isinstance(data, bytes): - return SafeBytes(data) - else: - return SafeText(data) - - encode = curry(_proxy_method, method=six.text_type.encode) + def __str__(self): + return self -if six.PY3: - SafeString = SafeText -else: - SafeString = SafeBytes - # backwards compatibility for Python 2 - SafeUnicode = SafeText +SafeString = SafeText def _safety_decorator(safety_marker, func): @@ -129,7 +71,7 @@ def wrapped(*args, **kwargs): def mark_safe(s): """ Explicitly mark a string as safe for (HTML) output purposes. The returned - object can be used everywhere a string or unicode object is appropriate. + object can be used everywhere a string is appropriate. If used on a method as a decorator, mark the returned data as safe. @@ -137,28 +79,6 @@ def mark_safe(s): """ if hasattr(s, '__html__'): return s - if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_bytes): - return SafeBytes(s) - if isinstance(s, (six.text_type, Promise)): - return SafeText(s) if callable(s): return _safety_decorator(mark_safe, s) - return SafeString(str(s)) - - -def mark_for_escaping(s): - """ - Explicitly mark a string as requiring HTML escaping upon output. Has no - effect on SafeData subclasses. - - Can be called multiple times on a single string (the resulting escaping is - only applied once). - """ - warnings.warn('mark_for_escaping() is deprecated.', RemovedInDjango20Warning) - if hasattr(s, '__html__') or isinstance(s, EscapeData): - return s - if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_bytes): - return EscapeBytes(s) - if isinstance(s, (six.text_type, Promise)): - return EscapeText(s) - return EscapeString(str(s)) + return SafeText(s) diff --git a/django/utils/six.py b/django/utils/six.py index 52627592b750..975fbfcf4f6d 100644 --- a/django/utils/six.py +++ b/django/utils/six.py @@ -20,8 +20,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from __future__ import absolute_import - import functools import itertools import operator @@ -57,7 +55,7 @@ MAXSIZE = int((1 << 31) - 1) else: # It's possible to have sizeof(long) != sizeof(Py_ssize_t). - class X(object): + class X: def __len__(self): return 1 << 31 @@ -83,7 +81,7 @@ def _import_module(name): return sys.modules[name] -class _LazyDescr(object): +class _LazyDescr: def __init__(self, name): self.name = name @@ -103,7 +101,7 @@ def __get__(self, obj, tp): class MovedModule(_LazyDescr): def __init__(self, name, old, new=None): - super(MovedModule, self).__init__(name) + super().__init__(name) if PY3: if new is None: new = name @@ -124,7 +122,7 @@ def __getattr__(self, attr): class _LazyModule(types.ModuleType): def __init__(self, name): - super(_LazyModule, self).__init__(name) + super().__init__(name) self.__doc__ = self.__class__.__doc__ def __dir__(self): @@ -139,7 +137,7 @@ def __dir__(self): class MovedAttribute(_LazyDescr): def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): - super(MovedAttribute, self).__init__(name) + super().__init__(name) if PY3: if new_mod is None: new_mod = name @@ -161,7 +159,7 @@ def _resolve(self): return getattr(module, self.attr) -class _SixMetaPathImporter(object): +class _SixMetaPathImporter: """ A meta path importer to import six.moves and its submodules. @@ -552,7 +550,7 @@ def create_bound_method(func, obj): def create_unbound_method(func, cls): return types.MethodType(func, None, cls) - class Iterator(object): + class Iterator: def next(self): return type(self).__next__(self) diff --git a/django/utils/synch.py b/django/utils/synch.py deleted file mode 100644 index c8ef2f07bdbe..000000000000 --- a/django/utils/synch.py +++ /dev/null @@ -1,90 +0,0 @@ -""" -Synchronization primitives: - - - reader-writer lock (preference to writers) - -(Contributed to Django by eugene@lazutkin.com) -""" - -import contextlib -import threading - - -class RWLock(object): - """ - Classic implementation of reader-writer lock with preference to writers. - - Readers can access a resource simultaneously. - Writers get an exclusive access. - - API is self-descriptive: - reader_enters() - reader_leaves() - writer_enters() - writer_leaves() - """ - def __init__(self): - self.mutex = threading.RLock() - self.can_read = threading.Semaphore(0) - self.can_write = threading.Semaphore(0) - self.active_readers = 0 - self.active_writers = 0 - self.waiting_readers = 0 - self.waiting_writers = 0 - - def reader_enters(self): - with self.mutex: - if self.active_writers == 0 and self.waiting_writers == 0: - self.active_readers += 1 - self.can_read.release() - else: - self.waiting_readers += 1 - self.can_read.acquire() - - def reader_leaves(self): - with self.mutex: - self.active_readers -= 1 - if self.active_readers == 0 and self.waiting_writers != 0: - self.active_writers += 1 - self.waiting_writers -= 1 - self.can_write.release() - - @contextlib.contextmanager - def reader(self): - self.reader_enters() - try: - yield - finally: - self.reader_leaves() - - def writer_enters(self): - with self.mutex: - if self.active_writers == 0 and self.waiting_writers == 0 and self.active_readers == 0: - self.active_writers += 1 - self.can_write.release() - else: - self.waiting_writers += 1 - self.can_write.acquire() - - def writer_leaves(self): - with self.mutex: - self.active_writers -= 1 - if self.waiting_writers != 0: - self.active_writers += 1 - self.waiting_writers -= 1 - self.can_write.release() - elif self.waiting_readers != 0: - t = self.waiting_readers - self.waiting_readers = 0 - self.active_readers += t - while t > 0: - self.can_read.release() - t -= 1 - - @contextlib.contextmanager - def writer(self): - self.writer_enters() - try: - yield - finally: - self.writer_leaves() diff --git a/django/utils/termcolors.py b/django/utils/termcolors.py index 87ed8c11870c..87fe191dccdd 100644 --- a/django/utils/termcolors.py +++ b/django/utils/termcolors.py @@ -2,8 +2,6 @@ termcolors.py """ -from django.utils import six - color_names = ('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white') foreground = {color_names[x]: '3%s' % x for x in range(8)} background = {color_names[x]: '4%s' % x for x in range(8)} @@ -14,12 +12,12 @@ def colorize(text='', opts=(), **kwargs): """ - Returns your text, enclosed in ANSI graphics codes. + Return your text, enclosed in ANSI graphics codes. Depends on the keyword arguments 'fg' and 'bg', and the contents of the opts tuple/list. - Returns the RESET code if no parameters are given. + Return the RESET code if no parameters are given. Valid colors: 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white' @@ -44,7 +42,7 @@ def colorize(text='', opts=(), **kwargs): code_list = [] if text == '' and len(opts) == 1 and opts[0] == 'reset': return '\x1b[%sm' % RESET - for k, v in six.iteritems(kwargs): + for k, v in kwargs.items(): if k == 'fg': code_list.append(foreground[v]) elif k == 'bg': @@ -59,7 +57,7 @@ def colorize(text='', opts=(), **kwargs): def make_style(opts=(), **kwargs): """ - Returns a function with default parameters for colorize() + Return a function with default parameters for colorize() Example: bold_red = make_style(opts=('bold',), fg='red') @@ -201,7 +199,7 @@ def parse_color_setting(config_string): definition['bg'] = colors[-1] # All remaining instructions are options - opts = tuple(s for s in styles if s in opt_dict.keys()) + opts = tuple(s for s in styles if s in opt_dict) if opts: definition['opts'] = opts diff --git a/django/utils/text.py b/django/utils/text.py index b0f139e03497..853436a38f3f 100644 --- a/django/utils/text.py +++ b/django/utils/text.py @@ -1,35 +1,23 @@ -from __future__ import unicode_literals - +import html.entities import re import unicodedata from gzip import GzipFile from io import BytesIO -from django.utils import six -from django.utils.encoding import force_text -from django.utils.functional import ( - SimpleLazyObject, keep_lazy, keep_lazy_text, lazy, -) -from django.utils.safestring import SafeText, mark_safe -from django.utils.six.moves import html_entities -from django.utils.translation import pgettext, ugettext as _, ugettext_lazy - -if six.PY2: - # Import force_unicode even though this module doesn't use it, because some - # people rely on it being here. - from django.utils.encoding import force_unicode # NOQA +from django.utils.functional import SimpleLazyObject, keep_lazy_text, lazy +from django.utils.translation import gettext as _, gettext_lazy, pgettext @keep_lazy_text def capfirst(x): """Capitalize the first letter of a string.""" - return x and force_text(x)[0].upper() + force_text(x)[1:] + return x and str(x)[0].upper() + str(x)[1:] # Set up regular expressions -re_words = re.compile(r'<.*?>|((?:\w[-\w]*|&.*?;)+)', re.U | re.S) -re_chars = re.compile(r'<.*?>|(.)', re.U | re.S) -re_tag = re.compile(r'<(/)?([^ ]+?)(?:(\s*/)| .*?)?>', re.S) +re_words = re.compile(r'<[^>]+?>|([^<>\s]+)', re.S) +re_chars = re.compile(r'<[^>]+?>|(.)', re.S) +re_tag = re.compile(r'<(/)?(\S+?)(?:(\s*/)|\s.*?)?>', re.S) re_newlines = re.compile(r'\r\n|\r') # Used in normalize_newlines re_camel_case = re.compile(r'(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))') @@ -40,14 +28,12 @@ def wrap(text, width): A word-wrap function that preserves existing line breaks. Expects that existing line breaks are posix newlines. - All white space is preserved except added line breaks consume the space on + Preserve all white space except added line breaks consume the space on which they break the line. - Long words are not wrapped, so the output text may have lines longer than + Don't wrap long words, thus the output text may have lines longer than ``width``. """ - text = force_text(text) - def _generator(): for line in text.splitlines(True): # True keeps trailing linebreaks max_width = min((line.endswith('\n') and width + 1 or width), width) @@ -72,14 +58,13 @@ class Truncator(SimpleLazyObject): An object used to truncate text, either by characters or words. """ def __init__(self, text): - super(Truncator, self).__init__(lambda: force_text(text)) + super().__init__(lambda: str(text)) def add_truncation_text(self, text, truncate=None): if truncate is None: truncate = pgettext( 'String to return when truncating text', - '%(truncated_text)s...') - truncate = force_text(truncate) + '%(truncated_text)s…') if '%(truncated_text)s' in truncate: return truncate % {'truncated_text': text} # The truncation text didn't contain the %(truncated_text)s string @@ -92,12 +77,11 @@ def add_truncation_text(self, text, truncate=None): def chars(self, num, truncate=None, html=False): """ - Returns the text truncated to be no longer than the specified number + Return the text truncated to be no longer than the specified number of characters. - Takes an optional argument of what should be used to notify that the - string has been truncated, defaulting to a translatable string of an - ellipsis (...). + `truncate` specifies what should be used to notify that the string has + been truncated, defaulting to a translatable string of an ellipsis. """ self._setup() length = int(num) @@ -115,9 +99,7 @@ def chars(self, num, truncate=None, html=False): return self._text_chars(length, truncate, text, truncate_len) def _text_chars(self, length, truncate, text, truncate_len): - """ - Truncates a string after a certain number of chars. - """ + """Truncate a string after a certain number of chars.""" s_len = 0 end_index = None for i, char in enumerate(text): @@ -138,9 +120,9 @@ def _text_chars(self, length, truncate, text, truncate_len): def words(self, num, truncate=None, html=False): """ - Truncates a string after a certain number of words. Takes an optional - argument of what should be used to notify that the string has been - truncated, defaulting to ellipsis (...). + Truncate a string after a certain number of words. `truncate` specifies + what should be used to notify that the string has been truncated, + defaulting to ellipsis. """ self._setup() length = int(num) @@ -150,9 +132,9 @@ def words(self, num, truncate=None, html=False): def _text_words(self, length, truncate): """ - Truncates a string after a certain number of words. + Truncate a string after a certain number of words. - Newlines in the string will be stripped. + Strip newlines in the string. """ words = self._wrapped.split() if len(words) > length: @@ -162,11 +144,11 @@ def _text_words(self, length, truncate): def _truncate_html(self, length, truncate, text, truncate_len, words): """ - Truncates HTML to a certain number of chars (not counting tags and + Truncate HTML to a certain number of chars (not counting tags and comments), or, if words is True, then to a certain number of words. - Closes opened tags if they were correctly closed in the given HTML. + Close opened tags if they were correctly closed in the given HTML. - Newlines in the HTML are preserved. + Preserve newlines in the HTML. """ if words and length <= 0: return '' @@ -236,19 +218,19 @@ def _truncate_html(self, length, truncate, text, truncate_len, words): @keep_lazy_text def get_valid_filename(s): """ - Returns the given string converted to a string that can be used for a clean - filename. Specifically, leading and trailing spaces are removed; other - spaces are converted to underscores; and anything that is not a unicode - alphanumeric, dash, underscore, or dot, is removed. + Return the given string converted to a string that can be used for a clean + filename. Remove leading and trailing spaces; convert other spaces to + underscores; and remove anything that is not an alphanumeric, dash, + underscore, or dot. >>> get_valid_filename("john's portrait in 2004.jpg") 'johns_portrait_in_2004.jpg' """ - s = force_text(s).strip().replace(' ', '_') + s = str(s).strip().replace(' ', '_') return re.sub(r'(?u)[^-\w.]', '', s) @keep_lazy_text -def get_text_list(list_, last_word=ugettext_lazy('or')): +def get_text_list(list_, last_word=gettext_lazy('or')): """ >>> get_text_list(['a', 'b', 'c', 'd']) 'a, b, c or d' @@ -261,26 +243,25 @@ def get_text_list(list_, last_word=ugettext_lazy('or')): >>> get_text_list([]) '' """ - if len(list_) == 0: + if not list_: return '' if len(list_) == 1: - return force_text(list_[0]) + return str(list_[0]) return '%s %s %s' % ( # Translators: This string is used as a separator between list elements - _(', ').join(force_text(i) for i in list_[:-1]), - force_text(last_word), force_text(list_[-1])) + _(', ').join(str(i) for i in list_[:-1]), str(last_word), str(list_[-1]) + ) @keep_lazy_text def normalize_newlines(text): - """Normalizes CRLF and CR newlines to just LF.""" - text = force_text(text) - return re_newlines.sub('\n', text) + """Normalize CRLF and CR newlines to just LF.""" + return re_newlines.sub('\n', str(text)) @keep_lazy_text def phone2numeric(phone): - """Converts a phone number with letters into its numeric equivalent.""" + """Convert a phone number with letters into its numeric equivalent.""" char2number = { 'a': '2', 'b': '2', 'c': '2', 'd': '3', 'e': '3', 'f': '3', 'g': '4', 'h': '4', 'i': '4', 'j': '5', 'k': '5', 'l': '5', 'm': '6', 'n': '6', @@ -299,26 +280,13 @@ def compress_string(s): return zbuf.getvalue() -class StreamingBuffer(object): - def __init__(self): - self.vals = [] - - def write(self, val): - self.vals.append(val) - +class StreamingBuffer(BytesIO): def read(self): - if not self.vals: - return b'' - ret = b''.join(self.vals) - self.vals = [] + ret = self.getvalue() + self.seek(0) + self.truncate() return ret - def flush(self): - return - - def close(self): - return - # Like compress_string, but for iterators of strings. def compress_sequence(sequence): @@ -362,8 +330,7 @@ def smart_split(text): >>> list(smart_split(r'A "\"funky\" style" test.')) ['A', '"\\"funky\\" style"', 'test.'] """ - text = force_text(text) - for bit in smart_split_re.finditer(text): + for bit in smart_split_re.finditer(str(text)): yield bit.group(0) @@ -376,12 +343,12 @@ def _replace_entity(match): c = int(text[1:], 16) else: c = int(text) - return six.unichr(c) + return chr(c) except ValueError: return match.group(0) else: try: - return six.unichr(html_entities.name2codepoint[text]) + return chr(html.entities.name2codepoint[text]) except (ValueError, KeyError): return match.group(0) @@ -391,7 +358,7 @@ def _replace_entity(match): @keep_lazy_text def unescape_entities(text): - return _entity_re.sub(_replace_entity, force_text(text)) + return _entity_re.sub(_replace_entity, str(text)) @keep_lazy_text @@ -415,27 +382,25 @@ def unescape_string_literal(s): return s[1:-1].replace(r'\%s' % quote, quote).replace(r'\\', '\\') -@keep_lazy(six.text_type, SafeText) +@keep_lazy_text def slugify(value, allow_unicode=False): """ Convert to ASCII if 'allow_unicode' is False. Convert spaces to hyphens. Remove characters that aren't alphanumerics, underscores, or hyphens. Convert to lowercase. Also strip leading and trailing whitespace. """ - value = force_text(value) + value = str(value) if allow_unicode: value = unicodedata.normalize('NFKC', value) - value = re.sub(r'[^\w\s-]', '', value, flags=re.U).strip().lower() - return mark_safe(re.sub(r'[-\s]+', '-', value, flags=re.U)) - value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii') + else: + value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii') value = re.sub(r'[^\w\s-]', '', value).strip().lower() - return mark_safe(re.sub(r'[-\s]+', '-', value)) + return re.sub(r'[-\s]+', '-', value) def camel_case_to_spaces(value): """ - Splits CamelCase and converts to lower case. Also strips leading and - trailing whitespace. + Split CamelCase and convert to lowercase. Strip surrounding whitespace. """ return re_camel_case.sub(r' \1', value).strip().lower() @@ -448,4 +413,4 @@ def _format_lazy(format_string, *args, **kwargs): return format_string.format(*args, **kwargs) -format_lazy = lazy(_format_lazy, six.text_type) +format_lazy = lazy(_format_lazy, str) diff --git a/django/utils/timesince.py b/django/utils/timesince.py index b0eefaf73437..23bb5c6da9df 100644 --- a/django/utils/timesince.py +++ b/django/utils/timesince.py @@ -1,44 +1,56 @@ -from __future__ import unicode_literals - import calendar import datetime from django.utils.html import avoid_wrapping from django.utils.timezone import is_aware, utc -from django.utils.translation import ugettext, ungettext_lazy +from django.utils.translation import gettext, ngettext_lazy + +TIME_STRINGS = { + 'year': ngettext_lazy('%d year', '%d years'), + 'month': ngettext_lazy('%d month', '%d months'), + 'week': ngettext_lazy('%d week', '%d weeks'), + 'day': ngettext_lazy('%d day', '%d days'), + 'hour': ngettext_lazy('%d hour', '%d hours'), + 'minute': ngettext_lazy('%d minute', '%d minutes'), +} TIMESINCE_CHUNKS = ( - (60 * 60 * 24 * 365, ungettext_lazy('%d year', '%d years')), - (60 * 60 * 24 * 30, ungettext_lazy('%d month', '%d months')), - (60 * 60 * 24 * 7, ungettext_lazy('%d week', '%d weeks')), - (60 * 60 * 24, ungettext_lazy('%d day', '%d days')), - (60 * 60, ungettext_lazy('%d hour', '%d hours')), - (60, ungettext_lazy('%d minute', '%d minutes')) + (60 * 60 * 24 * 365, 'year'), + (60 * 60 * 24 * 30, 'month'), + (60 * 60 * 24 * 7, 'week'), + (60 * 60 * 24, 'day'), + (60 * 60, 'hour'), + (60, 'minute'), ) -def timesince(d, now=None, reversed=False): +def timesince(d, now=None, reversed=False, time_strings=None): """ - Takes two datetime objects and returns the time between d and now - as a nicely formatted string, e.g. "10 minutes". If d occurs after now, - then "0 minutes" is returned. + Take two datetime objects and return the time between d and now as a nicely + formatted string, e.g. "10 minutes". If d occurs after now, return + "0 minutes". Units used are years, months, weeks, days, hours, and minutes. Seconds and microseconds are ignored. Up to two adjacent units will be displayed. For example, "2 weeks, 3 days" and "1 year, 3 months" are possible outputs, but "2 weeks, 3 hours" and "1 year, 5 days" are not. + `time_strings` is an optional dict of strings to replace the default + TIME_STRINGS dict. + Adapted from - http://web.archive.org/web/20060617175230/http://blog.natbat.co.uk/archive/2003/Jun/14/time_since + https://web.archive.org/web/20060617175230/http://blog.natbat.co.uk/archive/2003/Jun/14/time_since """ + if time_strings is None: + time_strings = TIME_STRINGS + # Convert datetime.date to datetime.datetime for comparison. if not isinstance(d, datetime.datetime): d = datetime.datetime(d.year, d.month, d.day) if now and not isinstance(now, datetime.datetime): now = datetime.datetime(now.year, now.month, now.day) - if not now: - now = datetime.datetime.now(utc if is_aware(d) else None) + now = now or datetime.datetime.now(utc if is_aware(d) else None) if reversed: d, now = now, d @@ -57,24 +69,23 @@ def timesince(d, now=None, reversed=False): since = delta.days * 24 * 60 * 60 + delta.seconds if since <= 0: # d is in the future compared to now, stop processing. - return avoid_wrapping(ugettext('0 minutes')) + return avoid_wrapping(gettext('0 minutes')) for i, (seconds, name) in enumerate(TIMESINCE_CHUNKS): count = since // seconds if count != 0: break - result = avoid_wrapping(name % count) + result = avoid_wrapping(time_strings[name] % count) if i + 1 < len(TIMESINCE_CHUNKS): # Now get the second item seconds2, name2 = TIMESINCE_CHUNKS[i + 1] count2 = (since - (seconds * count)) // seconds2 if count2 != 0: - result += ugettext(', ') + avoid_wrapping(name2 % count2) + result += gettext(', ') + avoid_wrapping(time_strings[name2] % count2) return result -def timeuntil(d, now=None): +def timeuntil(d, now=None, time_strings=None): """ - Like timesince, but returns a string measuring the time until - the given time. + Like timesince, but return a string measuring the time until the given time. """ - return timesince(d, now, reversed=True) + return timesince(d, now, reversed=True, time_strings=time_strings) diff --git a/django/utils/timezone.py b/django/utils/timezone.py index 090750793a91..58e92c1fa8df 100644 --- a/django/utils/timezone.py +++ b/django/utils/timezone.py @@ -2,14 +2,16 @@ Timezone-related classes and functions. """ -from datetime import datetime, timedelta, tzinfo +import functools +import warnings +from contextlib import ContextDecorator +from datetime import datetime, timedelta, timezone, tzinfo from threading import local import pytz from django.conf import settings -from django.utils import lru_cache, six -from django.utils.decorators import ContextDecorator +from django.utils.deprecation import RemovedInDjango31Warning __all__ = [ 'utc', 'get_fixed_timezone', @@ -36,6 +38,10 @@ class FixedOffset(tzinfo): """ def __init__(self, offset=None, name=None): + warnings.warn( + 'FixedOffset is deprecated in favor of datetime.timezone', + RemovedInDjango31Warning, stacklevel=2, + ) if offset is not None: self.__offset = timedelta(minutes=offset) if name is not None: @@ -51,28 +57,26 @@ def dst(self, dt): return ZERO +# UTC time zone as a tzinfo instance. utc = pytz.utc -"""UTC time zone as a tzinfo instance.""" def get_fixed_timezone(offset): - """ - Returns a tzinfo instance with a fixed offset from UTC. - """ + """Return a tzinfo instance with a fixed offset from UTC.""" if isinstance(offset, timedelta): - offset = offset.seconds // 60 + offset = offset.total_seconds() // 60 sign = '-' if offset < 0 else '+' hhmm = '%02d%02d' % divmod(abs(offset), 60) name = sign + hhmm - return FixedOffset(offset, name) + return timezone(timedelta(minutes=offset), name) # In order to avoid accessing settings at compile time, # wrap the logic in a function and cache the result. -@lru_cache.lru_cache() +@functools.lru_cache() def get_default_timezone(): """ - Returns the default time zone as a tzinfo instance. + Return the default time zone as a tzinfo instance. This is the time zone defined by settings.TIME_ZONE. """ @@ -81,9 +85,7 @@ def get_default_timezone(): # This function exists for consistency with get_current_timezone_name def get_default_timezone_name(): - """ - Returns the name of the default time zone. - """ + """Return the name of the default time zone.""" return _get_timezone_name(get_default_timezone()) @@ -91,29 +93,18 @@ def get_default_timezone_name(): def get_current_timezone(): - """ - Returns the currently active time zone as a tzinfo instance. - """ + """Return the currently active time zone as a tzinfo instance.""" return getattr(_active, "value", get_default_timezone()) def get_current_timezone_name(): - """ - Returns the name of the currently active time zone. - """ + """Return the name of the currently active time zone.""" return _get_timezone_name(get_current_timezone()) def _get_timezone_name(timezone): - """ - Returns the name of ``timezone``. - """ - try: - # for pytz timezones - return timezone.zone - except AttributeError: - # for regular tzinfo objects - return timezone.tzname(None) + """Return the name of ``timezone``.""" + return timezone.tzname(None) # Timezone selection functions. @@ -123,14 +114,14 @@ def _get_timezone_name(timezone): def activate(timezone): """ - Sets the time zone for the current thread. + Set the time zone for the current thread. The ``timezone`` argument must be an instance of a tzinfo subclass or a time zone name. """ if isinstance(timezone, tzinfo): _active.value = timezone - elif isinstance(timezone, six.string_types): + elif isinstance(timezone, str): _active.value = pytz.timezone(timezone) else: raise ValueError("Invalid timezone: %r" % timezone) @@ -138,7 +129,7 @@ def activate(timezone): def deactivate(): """ - Unsets the time zone for the current thread. + Unset the time zone for the current thread. Django will then use the time zone defined by settings.TIME_ZONE. """ @@ -150,8 +141,8 @@ class override(ContextDecorator): """ Temporarily set the time zone for the current thread. - This is a context manager that uses ``~django.utils.timezone.activate()`` - to set the timezone on entry, and restores the previously active timezone + This is a context manager that uses django.utils.timezone.activate() + to set the timezone on entry and restores the previously active timezone on exit. The ``timezone`` argument must be an instance of a ``tzinfo`` subclass, a @@ -179,7 +170,7 @@ def __exit__(self, exc_type, exc_value, traceback): def template_localtime(value, use_tz=None): """ - Checks if value is a datetime and converts it to local time if necessary. + Check if value is a datetime and converts it to local time if necessary. If use_tz is provided and is not None, that will force the value to be converted (or not), overriding the value of settings.USE_TZ. @@ -199,7 +190,7 @@ def template_localtime(value, use_tz=None): def localtime(value=None, timezone=None): """ - Converts an aware datetime.datetime to local time. + Convert an aware datetime.datetime to local time. Only aware datetimes are allowed. When value is omitted, it defaults to now(). @@ -214,11 +205,7 @@ def localtime(value=None, timezone=None): # Emulate the behavior of astimezone() on Python < 3.6. if is_naive(value): raise ValueError("localtime() cannot be applied to a naive datetime") - value = value.astimezone(timezone) - if hasattr(timezone, 'normalize'): - # This method is available for pytz time zones. - value = timezone.normalize(value) - return value + return value.astimezone(timezone) def localdate(value=None, timezone=None): @@ -236,7 +223,7 @@ def localdate(value=None, timezone=None): def now(): """ - Returns an aware or naive datetime.datetime, depending on settings.USE_TZ. + Return an aware or naive datetime.datetime, depending on settings.USE_TZ. """ if settings.USE_TZ: # timeit shows that datetime.now(tz=utc) is 24% slower @@ -250,10 +237,10 @@ def now(): def is_aware(value): """ - Determines if a given datetime.datetime is aware. + Determine if a given datetime.datetime is aware. The concept is defined in Python's docs: - http://docs.python.org/library/datetime.html#datetime.tzinfo + https://docs.python.org/library/datetime.html#datetime.tzinfo Assuming value.tzinfo is either None or a proper datetime.tzinfo, value.utcoffset() implements the appropriate logic. @@ -263,10 +250,10 @@ def is_aware(value): def is_naive(value): """ - Determines if a given datetime.datetime is naive. + Determine if a given datetime.datetime is naive. The concept is defined in Python's docs: - http://docs.python.org/library/datetime.html#datetime.tzinfo + https://docs.python.org/library/datetime.html#datetime.tzinfo Assuming value.tzinfo is either None or a proper datetime.tzinfo, value.utcoffset() implements the appropriate logic. @@ -275,9 +262,7 @@ def is_naive(value): def make_aware(value, timezone=None, is_dst=None): - """ - Makes a naive datetime.datetime in a given time zone aware. - """ + """Make a naive datetime.datetime in a given time zone aware.""" if timezone is None: timezone = get_current_timezone() if hasattr(timezone, 'localize'): @@ -293,16 +278,10 @@ def make_aware(value, timezone=None, is_dst=None): def make_naive(value, timezone=None): - """ - Makes an aware datetime.datetime naive in a given time zone. - """ + """Make an aware datetime.datetime naive in a given time zone.""" if timezone is None: timezone = get_current_timezone() # Emulate the behavior of astimezone() on Python < 3.6. if is_naive(value): raise ValueError("make_naive() cannot be applied to a naive datetime") - value = value.astimezone(timezone) - if hasattr(timezone, 'normalize'): - # This method is available for pytz time zones. - value = timezone.normalize(value) - return value.replace(tzinfo=None) + return value.astimezone(timezone).replace(tzinfo=None) diff --git a/django/db/migrations/topological_sort.py b/django/utils/topological_sort.py similarity index 59% rename from django/db/migrations/topological_sort.py rename to django/utils/topological_sort.py index abb5dfb71bf9..f7ce0e0d1dc1 100644 --- a/django/db/migrations/topological_sort.py +++ b/django/utils/topological_sort.py @@ -1,18 +1,23 @@ +class CyclicDependencyError(ValueError): + pass + + def topological_sort_as_sets(dependency_graph): - """Variation of Kahn's algorithm (1962) that returns sets. + """ + Variation of Kahn's algorithm (1962) that returns sets. - Takes a dependency graph as a dictionary of node => dependencies. + Take a dependency graph as a dictionary of node => dependencies. - Yields sets of items in topological order, where the first set contains + Yield sets of items in topological order, where the first set contains all nodes without dependencies, and each following set contains all nodes that may depend on the nodes only in the previously yielded sets. """ todo = dependency_graph.copy() while todo: - current = {node for node, deps in todo.items() if len(deps) == 0} + current = {node for node, deps in todo.items() if not deps} if not current: - raise ValueError('Cyclic dependency in graph: {}'.format( + raise CyclicDependencyError('Cyclic dependency in graph: {}'.format( ', '.join(repr(x) for x in todo.items()))) yield current @@ -22,10 +27,10 @@ def topological_sort_as_sets(dependency_graph): todo.items() if node not in current} -def stable_topological_sort(l, dependency_graph): +def stable_topological_sort(nodes, dependency_graph): result = [] for layer in topological_sort_as_sets(dependency_graph): - for node in l: + for node in nodes: if node in layer: result.append(node) return result diff --git a/django/utils/translation/__init__.py b/django/utils/translation/__init__.py index 6cdcaf9bf178..955a038109c1 100644 --- a/django/utils/translation/__init__.py +++ b/django/utils/translation/__init__.py @@ -1,22 +1,17 @@ """ Internationalization support. """ -from __future__ import unicode_literals - import re -import warnings +from contextlib import ContextDecorator -from django.utils import six -from django.utils.decorators import ContextDecorator -from django.utils.deprecation import RemovedInDjango21Warning -from django.utils.encoding import force_text +from django.utils.autoreload import autoreload_started, file_changed from django.utils.functional import lazy __all__ = [ 'activate', 'deactivate', 'override', 'deactivate_all', 'get_language', 'get_language_from_request', 'get_language_info', 'get_language_bidi', - 'check_for_language', 'to_locale', 'templatize', 'string_concat', + 'check_for_language', 'to_language', 'to_locale', 'templatize', 'gettext', 'gettext_lazy', 'gettext_noop', 'ugettext', 'ugettext_lazy', 'ugettext_noop', 'ngettext', 'ngettext_lazy', @@ -41,7 +36,7 @@ class TranslatorCommentWarning(SyntaxWarning): # replace the functions with their real counterparts (once we do access the # settings). -class Trans(object): +class Trans: """ The purpose of this class is to store the actual translation function upon receiving the first call to that function. After this is done, changes to @@ -58,6 +53,9 @@ def __getattr__(self, real_name): from django.conf import settings if settings.USE_I18N: from django.utils.translation import trans_real as trans + from django.utils.translation.reloader import watch_for_translation_changes, translation_file_changed + autoreload_started.connect(watch_for_translation_changes, dispatch_uid='translation_file_changed') + file_changed.connect(translation_file_changed, dispatch_uid='translation_file_changed') else: from django.utils.translation import trans_null as trans setattr(self, real_name, getattr(trans, real_name)) @@ -81,16 +79,16 @@ def gettext(message): return _trans.gettext(message) -def ngettext(singular, plural, number): - return _trans.ngettext(singular, plural, number) +# An alias since Django 2.0 +ugettext = gettext -def ugettext(message): - return _trans.ugettext(message) +def ngettext(singular, plural, number): + return _trans.ngettext(singular, plural, number) -def ungettext(singular, plural, number): - return _trans.ungettext(singular, plural, number) +# An alias since Django 2.0 +ungettext = ngettext def pgettext(context, message): @@ -101,13 +99,12 @@ def npgettext(context, singular, plural, number): return _trans.npgettext(context, singular, plural, number) -gettext_lazy = lazy(gettext, str) -ugettext_lazy = lazy(ugettext, six.text_type) -pgettext_lazy = lazy(pgettext, six.text_type) +gettext_lazy = ugettext_lazy = lazy(gettext, str) +pgettext_lazy = lazy(pgettext, str) def lazy_number(func, resultclass, number=None, **kwargs): - if isinstance(number, six.integer_types): + if isinstance(number, int): kwargs['number'] = number proxy = lazy(func, resultclass)(**kwargs) else: @@ -117,27 +114,34 @@ class NumberAwareString(resultclass): def __bool__(self): return bool(kwargs['singular']) - def __nonzero__(self): # Python 2 compatibility - return type(self).__bool__(self) + def _get_number_value(self, values): + try: + return values[number] + except KeyError: + raise KeyError( + "Your dictionary lacks key '%s\'. Please provide " + "it, because it is required to determine whether " + "string is singular or plural." % number + ) + + def _translate(self, number_value): + kwargs['number'] = number_value + return func(**kwargs) + + def format(self, *args, **kwargs): + number_value = self._get_number_value(kwargs) if kwargs and number else args[0] + return self._translate(number_value).format(*args, **kwargs) def __mod__(self, rhs): if isinstance(rhs, dict) and number: - try: - number_value = rhs[number] - except KeyError: - raise KeyError( - "Your dictionary lacks key '%s\'. Please provide " - "it, because it is required to determine whether " - "string is singular or plural." % number - ) + number_value = self._get_number_value(rhs) else: number_value = rhs - kwargs['number'] = number_value - translated = func(**kwargs) + translated = self._translate(number_value) try: translated = translated % rhs except TypeError: - # String doesn't contain a placeholder for the number + # String doesn't contain a placeholder for the number. pass return translated @@ -154,12 +158,12 @@ def ngettext_lazy(singular, plural, number=None): return lazy_number(ngettext, str, singular=singular, plural=plural, number=number) -def ungettext_lazy(singular, plural, number=None): - return lazy_number(ungettext, six.text_type, singular=singular, plural=plural, number=number) +# An alias since Django 2.0 +ungettext_lazy = ngettext_lazy def npgettext_lazy(context, singular, plural, number=None): - return lazy_number(npgettext, six.text_type, context=context, singular=singular, plural=plural, number=number) + return lazy_number(npgettext, str, context=context, singular=singular, plural=plural, number=number) def activate(language): @@ -203,8 +207,29 @@ def check_for_language(lang_code): return _trans.check_for_language(lang_code) +def to_language(locale): + """Turn a locale name (en_US) into a language name (en-us).""" + p = locale.find('_') + if p >= 0: + return locale[:p].lower() + '-' + locale[p + 1:].lower() + else: + return locale.lower() + + def to_locale(language): - return _trans.to_locale(language) + """Turn a language name (en-us) into a locale name (en_US).""" + language, _, country = language.lower().partition('-') + if not country: + return language + # A language with > 2 characters after the dash only has its first + # character after the dash capitalized; e.g. sr-latn becomes sr_Latn. + # A language with 2 characters after the dash has both characters + # capitalized; e.g. en-us becomes en_US. + country, _, tail = country.partition('-') + country = country.title() if len(country) > 2 else country.upper() + if tail: + country += '-' + tail + return language + '_' + country def get_language_from_request(request, check_path=False): @@ -215,6 +240,10 @@ def get_language_from_path(path): return _trans.get_language_from_path(path) +def get_supported_language_variant(lang_code, *, strict=False): + return _trans.get_supported_language_variant(lang_code, strict) + + def templatize(src, **kwargs): from .template import templatize return templatize(src, **kwargs) @@ -224,21 +253,6 @@ def deactivate_all(): return _trans.deactivate_all() -def _string_concat(*strings): - """ - Lazy variant of string concatenation, needed for translations that are - constructed from multiple parts. - """ - warnings.warn( - 'django.utils.translate.string_concat() is deprecated in ' - 'favor of django.utils.text.format_lazy().', - RemovedInDjango21Warning, stacklevel=2) - return ''.join(force_text(s) for s in strings) - - -string_concat = lazy(_string_concat, six.text_type) - - def get_language_info(lang_code): from django.conf.locale import LANG_INFO try: @@ -257,7 +271,7 @@ def get_language_info(lang_code): raise KeyError("Unknown language code %s and %s." % (lang_code, generic_lang_code)) if info: - info['name_translated'] = ugettext_lazy(info['name']) + info['name_translated'] = gettext_lazy(info['name']) return info diff --git a/django/utils/translation/reloader.py b/django/utils/translation/reloader.py new file mode 100644 index 000000000000..4b363f512997 --- /dev/null +++ b/django/utils/translation/reloader.py @@ -0,0 +1,28 @@ +import threading +from pathlib import Path + +from django.apps import apps + + +def watch_for_translation_changes(sender, **kwargs): + """Register file watchers for .mo files in potential locale paths.""" + from django.conf import settings + + if settings.USE_I18N: + directories = [Path('locale')] + directories.extend(Path(config.path) / 'locale' for config in apps.get_app_configs()) + directories.extend(Path(p) for p in settings.LOCALE_PATHS) + for path in directories: + sender.watch_dir(path, '**/*.mo') + + +def translation_file_changed(sender, file_path, **kwargs): + """Clear the internal translations cache if a .mo file is modified.""" + if file_path.suffix == '.mo': + import gettext + from django.utils.translation import trans_real + gettext._translations = {} + trans_real._translations = {} + trans_real._default = None + trans_real._active = threading.local() + return True diff --git a/django/utils/translation/template.py b/django/utils/translation/template.py index b8618c4dd2e9..aa849b093762 100644 --- a/django/utils/translation/template.py +++ b/django/utils/translation/template.py @@ -1,15 +1,8 @@ -from __future__ import unicode_literals - import re import warnings +from io import StringIO -from django.template.base import ( - TOKEN_BLOCK, TOKEN_COMMENT, TOKEN_TEXT, TOKEN_VAR, TRANSLATOR_COMMENT_MARK, - Lexer, -) -from django.utils import six -from django.utils.encoding import force_text -from django.utils.six import StringIO +from django.template.base import TRANSLATOR_COMMENT_MARK, Lexer, TokenType from . import TranslatorCommentWarning, trim_whitespace @@ -39,13 +32,12 @@ def blankout(src, char): constant_re = re.compile(r"""_\(((?:".*?")|(?:'.*?'))\)""") -def templatize(src, origin=None, charset='utf-8'): +def templatize(src, origin=None): """ Turn a Django template into something that is understood by xgettext. It does so by translating the Django translation tags into standard gettext function invocations. """ - src = force_text(src, charset) out = StringIO('') message_context = None intrans = False @@ -57,9 +49,8 @@ def templatize(src, origin=None, charset='utf-8'): comment = [] lineno_comment_map = {} comment_lineno_cache = None - # Adding the u prefix allows gettext to recognize the Unicode string - # (#26093). - raw_prefix = 'u' if six.PY3 else '' + # Adding the u prefix allows gettext to recognize the string (#26093). + raw_prefix = 'u' def join_tokens(tokens, trim=False): message = ''.join(tokens) @@ -69,7 +60,7 @@ def join_tokens(tokens, trim=False): for t in Lexer(src).tokenize(): if incomment: - if t.token_type == TOKEN_BLOCK and t.contents == 'endcomment': + if t.token_type == TokenType.BLOCK and t.contents == 'endcomment': content = ''.join(comment) translators_comment_start = None for lineno, line in enumerate(content.splitlines(True)): @@ -85,7 +76,7 @@ def join_tokens(tokens, trim=False): else: comment.append(t.contents) elif intrans: - if t.token_type == TOKEN_BLOCK: + if t.token_type == TokenType.BLOCK: endbmatch = endblock_re.match(t.contents) pluralmatch = plural_re.match(t.contents) if endbmatch: @@ -136,12 +127,12 @@ def join_tokens(tokens, trim=False): "Translation blocks must not include other block tags: " "%s (%sline %d)" % (t.contents, filemsg, t.lineno) ) - elif t.token_type == TOKEN_VAR: + elif t.token_type == TokenType.VAR: if inplural: plural.append('%%(%s)s' % t.contents) else: singular.append('%%(%s)s' % t.contents) - elif t.token_type == TOKEN_TEXT: + elif t.token_type == TokenType.TEXT: contents = t.contents.replace('%', '%%') if inplural: plural.append(contents) @@ -153,7 +144,7 @@ def join_tokens(tokens, trim=False): if comment_lineno_cache is not None: cur_lineno = t.lineno + t.contents.count('\n') if comment_lineno_cache == cur_lineno: - if t.token_type != TOKEN_COMMENT: + if t.token_type != TokenType.COMMENT: for c in lineno_comment_map[comment_lineno_cache]: filemsg = '' if origin: @@ -169,7 +160,7 @@ def join_tokens(tokens, trim=False): out.write('# %s' % ' | '.join(lineno_comment_map[comment_lineno_cache])) comment_lineno_cache = None - if t.token_type == TOKEN_BLOCK: + if t.token_type == TokenType.BLOCK: imatch = inline_re.match(t.contents) bmatch = block_re.match(t.contents) cmatches = constant_re.findall(t.contents) @@ -217,7 +208,7 @@ def join_tokens(tokens, trim=False): incomment = True else: out.write(blankout(t.contents, 'B')) - elif t.token_type == TOKEN_VAR: + elif t.token_type == TokenType.VAR: parts = t.contents.split('|') cmatch = constant_re.match(parts[0]) if cmatch: @@ -227,7 +218,7 @@ def join_tokens(tokens, trim=False): out.write(' %s ' % p.split(':', 1)[1]) else: out.write(blankout(p, 'F')) - elif t.token_type == TOKEN_COMMENT: + elif t.token_type == TokenType.COMMENT: if t.contents.lstrip().startswith(TRANSLATOR_COMMENT_MARK): lineno_comment_map.setdefault(t.lineno, []).append(t.contents) comment_lineno_cache = t.lineno diff --git a/django/utils/translation/trans_null.py b/django/utils/translation/trans_null.py index 21097244d3f9..a687572b6920 100644 --- a/django/utils/translation/trans_null.py +++ b/django/utils/translation/trans_null.py @@ -3,7 +3,13 @@ # settings.USE_I18N = False can use this module rather than trans_real.py. from django.conf import settings -from django.utils.encoding import force_text + + +def gettext(message): + return message + + +gettext_noop = gettext_lazy = _ = gettext def ngettext(singular, plural, number): @@ -15,16 +21,12 @@ def ngettext(singular, plural, number): ngettext_lazy = ngettext -def ungettext(singular, plural, number): - return force_text(ngettext(singular, plural, number)) - - def pgettext(context, message): - return ugettext(message) + return gettext(message) def npgettext(context, singular, plural, number): - return ungettext(singular, plural, number) + return ngettext(singular, plural, number) def activate(x): @@ -50,28 +52,16 @@ def check_for_language(x): return True -def gettext(message): - return message - - -def ugettext(message): - return force_text(gettext(message)) - - -gettext_noop = gettext_lazy = _ = gettext - - -def to_locale(language): - p = language.find('-') - if p >= 0: - return language[:p].lower() + '_' + language[p + 1:].upper() - else: - return language.lower() - - def get_language_from_request(request, check_path=False): return settings.LANGUAGE_CODE def get_language_from_path(request): return None + + +def get_supported_language_variant(lang_code, strict=False): + if lang_code == settings.LANGUAGE_CODE: + return lang_code + else: + raise LookupError(lang_code) diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py index b3da22482a48..486b2b26b608 100644 --- a/django/utils/translation/trans_real.py +++ b/django/utils/translation/trans_real.py @@ -1,6 +1,5 @@ """Translation helper functions.""" -from __future__ import unicode_literals - +import functools import gettext as gettext_module import os import re @@ -15,11 +14,9 @@ from django.core.exceptions import AppRegistryNotReady from django.core.signals import setting_changed from django.dispatch import receiver -from django.utils import lru_cache, six -from django.utils._os import upath -from django.utils.encoding import force_text from django.utils.safestring import SafeData, mark_safe -from django.utils.translation import LANGUAGE_SESSION_KEY + +from . import LANGUAGE_SESSION_KEY, to_language, to_locale # Translations are cached in a dictionary for every language. # The active translations are stored by threadid to make them thread local. @@ -60,37 +57,66 @@ def reset_cache(**kwargs): get_supported_language_variant.cache_clear() -def to_locale(language, to_lower=False): +class TranslationCatalog: """ - Turns a language name (en-us) into a locale name (en_US). If 'to_lower' is - True, the last component is lower-cased (en_us). + Simulate a dict for DjangoTranslation._catalog so as multiple catalogs + with different plural equations are kept separate. """ - p = language.find('-') - if p >= 0: - if to_lower: - return language[:p].lower() + '_' + language[p + 1:].lower() + def __init__(self, trans=None): + self._catalogs = [trans._catalog.copy()] if trans else [{}] + self._plurals = [trans.plural] if trans else [lambda n: int(n != 1)] + + def __getitem__(self, key): + for cat in self._catalogs: + try: + return cat[key] + except KeyError: + pass + raise KeyError(key) + + def __setitem__(self, key, value): + self._catalogs[0][key] = value + + def __contains__(self, key): + return any(key in cat for cat in self._catalogs) + + def items(self): + for cat in self._catalogs: + yield from cat.items() + + def keys(self): + for cat in self._catalogs: + yield from cat.keys() + + def update(self, trans): + # Merge if plural function is the same, else prepend. + for cat, plural in zip(self._catalogs, self._plurals): + if trans.plural.__code__ == plural.__code__: + cat.update(trans._catalog) + break else: - # Get correct locale for sr-latn - if len(language[p + 1:]) > 2: - return language[:p].lower() + '_' + language[p + 1].upper() + language[p + 2:].lower() - return language[:p].lower() + '_' + language[p + 1:].upper() - else: - return language.lower() + self._catalogs.insert(0, trans._catalog.copy()) + self._plurals.insert(0, trans.plural) + def get(self, key, default=None): + missing = object() + for cat in self._catalogs: + result = cat.get(key, missing) + if result is not missing: + return result + return default -def to_language(locale): - """Turns a locale name (en_US) into a language name (en-us).""" - p = locale.find('_') - if p >= 0: - return locale[:p].lower() + '-' + locale[p + 1:].lower() - else: - return locale.lower() + def plural(self, msgid, num): + for cat, plural in zip(self._catalogs, self._plurals): + tmsg = cat.get((msgid, plural(num))) + if tmsg is not None: + return tmsg + raise KeyError class DjangoTranslation(gettext_module.GNUTranslations): """ - This class sets up the GNUTranslations context with regard to output - charset. + Set up the GNUTranslations context with regard to output charset. This translation object will be constructed out of multiple GNUTranslations objects by merging their catalogs. It will construct an object for the @@ -104,7 +130,6 @@ def __init__(self, language, domain=None, localedirs=None): gettext_module.GNUTranslations.__init__(self) if domain is not None: self.domain = domain - self.set_output_charset('utf-8') # For Python 2 gettext() (#25720) self.__language = language self.__to_language = to_language(language) @@ -135,14 +160,14 @@ def __init__(self, language, domain=None, localedirs=None): self._add_fallback(localedirs) if self._catalog is None: # No catalogs found for this language, set an empty catalog. - self._catalog = {} + self._catalog = TranslationCatalog() def __repr__(self): return "" % self.__language def _new_gnu_trans(self, localedir, use_null_fallback=True): """ - Returns a mergeable gettext.GNUTranslations instance. + Return a mergeable gettext.GNUTranslations instance. A convenience wrapper. By default gettext uses 'fallback=False'. Using param `use_null_fallback` to avoid confusion with any other @@ -152,18 +177,18 @@ def _new_gnu_trans(self, localedir, use_null_fallback=True): domain=self.domain, localedir=localedir, languages=[self.__locale], - codeset='utf-8', - fallback=use_null_fallback) + fallback=use_null_fallback, + ) def _init_translation_catalog(self): - """Creates a base catalog using global django translations.""" - settingsfile = upath(sys.modules[settings.__module__].__file__) + """Create a base catalog using global django translations.""" + settingsfile = sys.modules[settings.__module__].__file__ localedir = os.path.join(os.path.dirname(settingsfile), 'locale') translation = self._new_gnu_trans(localedir) self.merge(translation) def _add_installed_apps_translations(self): - """Merges translations from each installed app.""" + """Merge translations from each installed app.""" try: app_configs = reversed(list(apps.get_app_configs())) except AppRegistryNotReady: @@ -178,13 +203,13 @@ def _add_installed_apps_translations(self): self.merge(translation) def _add_local_translations(self): - """Merges translations defined in LOCALE_PATHS.""" + """Merge translations defined in LOCALE_PATHS.""" for localedir in reversed(settings.LOCALE_PATHS): translation = self._new_gnu_trans(localedir) self.merge(translation) def _add_fallback(self, localedirs=None): - """Sets the GNUTranslations() fallback with the default language.""" + """Set the GNUTranslations() fallback with the default language.""" # Don't set a fallback for the default language or any English variant # (as it's empty, so it'll ALWAYS fall back to the default language) if self.__language == settings.LANGUAGE_CODE or self.__language.startswith('en'): @@ -206,22 +231,36 @@ def merge(self, other): # Take plural and _info from first catalog found (generally Django's). self.plural = other.plural self._info = other._info.copy() - self._catalog = other._catalog.copy() + self._catalog = TranslationCatalog(other) else: - self._catalog.update(other._catalog) + self._catalog.update(other) + if other._fallback: + self.add_fallback(other._fallback) def language(self): - """Returns the translation language.""" + """Return the translation language.""" return self.__language def to_language(self): - """Returns the translation language name.""" + """Return the translation language name.""" return self.__to_language + def ngettext(self, msgid1, msgid2, n): + try: + tmsg = self._catalog.plural(msgid1, n) + except KeyError: + if self._fallback: + return self._fallback.ngettext(msgid1, msgid2, n) + if n == 1: + tmsg = msgid1 + else: + tmsg = msgid2 + return tmsg + def translation(language): """ - Returns a translation object in the default 'django' domain. + Return a translation object in the default 'django' domain. """ global _translations if language not in _translations: @@ -231,7 +270,7 @@ def translation(language): def activate(language): """ - Fetches the translation object for a given language and installs it as the + Fetch the translation object for a given language and install it as the current translation object for the current thread. """ if not language: @@ -241,8 +280,8 @@ def activate(language): def deactivate(): """ - Deinstalls the currently active translation object so that further _ calls - will resolve against the default translation object, again. + Uninstall the active translation object so that further _() calls resolve + to the default translation object. """ if hasattr(_active, "value"): del _active.value @@ -250,7 +289,7 @@ def deactivate(): def deactivate_all(): """ - Makes the active translation object a NullTranslations() instance. This is + Make the active translation object a NullTranslations() instance. This is useful when we want delayed translations to appear as the original string for some reason. """ @@ -259,7 +298,7 @@ def deactivate_all(): def get_language(): - """Returns the currently selected language.""" + """Return the currently selected language.""" t = getattr(_active, "value", None) if t is not None: try: @@ -272,7 +311,7 @@ def get_language(): def get_language_bidi(): """ - Returns selected language's BiDi layout. + Return selected language's BiDi layout. * False = left-to-right layout * True = right-to-left layout @@ -287,7 +326,7 @@ def get_language_bidi(): def catalog(): """ - Returns the current active catalog for further processing. + Return the current active catalog for further processing. This can be used if you need to modify the catalog or want to access the whole message catalog instead of just translating one string. """ @@ -301,27 +340,25 @@ def catalog(): return _default -def do_translate(message, translation_function): +def gettext(message): """ - Translates 'message' using the given 'translation_function' name -- which - will be either gettext or ugettext. It uses the current thread to find the + Translate the 'message' string. It uses the current thread to find the translation object to use. If no current translation is activated, the message will be run through the default translation object. """ global _default - # str() is allowing a bytestring message to remain bytestring on Python 2 - eol_message = message.replace(str('\r\n'), str('\n')).replace(str('\r'), str('\n')) + eol_message = message.replace('\r\n', '\n').replace('\r', '\n') - if len(eol_message) == 0: - # Returns an empty value of the corresponding type if an empty message - # is given, instead of metadata, which is the default gettext behavior. - result = type(message)("") - else: + if eol_message: _default = _default or translation(settings.LANGUAGE_CODE) translation_object = getattr(_active, "value", _default) - result = getattr(translation_object, translation_function)(eol_message) + result = translation_object.gettext(eol_message) + else: + # Return an empty value of the corresponding type if an empty message + # is given, instead of metadata, which is the default gettext behavior. + result = type(message)('') if isinstance(message, SafeData): return mark_safe(result) @@ -329,35 +366,20 @@ def do_translate(message, translation_function): return result -def gettext(message): - """ - Returns a string of the translation of the message. - - Returns a string on Python 3 and an UTF-8-encoded bytestring on Python 2. - """ - return do_translate(message, 'gettext') - - -if six.PY3: - ugettext = gettext -else: - def ugettext(message): - return do_translate(message, 'ugettext') - - def pgettext(context, message): msg_with_ctxt = "%s%s%s" % (context, CONTEXT_SEPARATOR, message) - result = ugettext(msg_with_ctxt) + result = gettext(msg_with_ctxt) if CONTEXT_SEPARATOR in result: # Translation not found - # force unicode, because lazy version expects unicode - result = force_text(message) + result = message + elif isinstance(message, SafeData): + result = mark_safe(result) return result def gettext_noop(message): """ - Marks strings for translation but doesn't translate them now. This can be + Mark strings for translation but don't translate them now. This can be used to store strings in global variables that should stay in the base language (because they might be used externally) and will be translated later. @@ -378,49 +400,41 @@ def do_ntranslate(singular, plural, number, translation_function): def ngettext(singular, plural, number): """ - Returns a string of the translation of either the singular or plural, + Return a string of the translation of either the singular or plural, based on the number. - - Returns a string on Python 3 and an UTF-8-encoded bytestring on Python 2. """ return do_ntranslate(singular, plural, number, 'ngettext') -if six.PY3: - ungettext = ngettext -else: - def ungettext(singular, plural, number): - """ - Returns a unicode strings of the translation of either the singular or - plural, based on the number. - """ - return do_ntranslate(singular, plural, number, 'ungettext') - - def npgettext(context, singular, plural, number): msgs_with_ctxt = ("%s%s%s" % (context, CONTEXT_SEPARATOR, singular), "%s%s%s" % (context, CONTEXT_SEPARATOR, plural), number) - result = ungettext(*msgs_with_ctxt) + result = ngettext(*msgs_with_ctxt) if CONTEXT_SEPARATOR in result: # Translation not found - result = ungettext(singular, plural, number) + result = ngettext(singular, plural, number) return result def all_locale_paths(): """ - Returns a list of paths to user-provides languages files. + Return a list of paths to user-provides languages files. """ globalpath = os.path.join( - os.path.dirname(upath(sys.modules[settings.__module__].__file__)), 'locale') - return [globalpath] + list(settings.LOCALE_PATHS) + os.path.dirname(sys.modules[settings.__module__].__file__), 'locale') + app_paths = [] + for app_config in apps.get_app_configs(): + locale_path = os.path.join(app_config.path, 'locale') + if os.path.exists(locale_path): + app_paths.append(locale_path) + return [globalpath, *settings.LOCALE_PATHS, *app_paths] -@lru_cache.lru_cache(maxsize=1000) +@functools.lru_cache(maxsize=1000) def check_for_language(lang_code): """ - Checks whether there is a global language file for the given language + Check whether there is a global language file for the given language code. This is used to decide whether a user-provided language is available. @@ -431,13 +445,13 @@ def check_for_language(lang_code): # First, a quick check to make sure lang_code is well-formed (#21458) if lang_code is None or not language_code_re.search(lang_code): return False - for path in all_locale_paths(): - if gettext_module.find('django', path, [to_locale(lang_code)]) is not None: - return True - return False + return any( + gettext_module.find('django', path, [to_locale(lang_code)]) is not None + for path in all_locale_paths() + ) -@lru_cache.lru_cache() +@functools.lru_cache() def get_languages(): """ Cache of settings.LANGUAGES in an OrderedDict for easy lookups by key. @@ -445,14 +459,14 @@ def get_languages(): return OrderedDict(settings.LANGUAGES) -@lru_cache.lru_cache(maxsize=1000) +@functools.lru_cache(maxsize=1000) def get_supported_language_variant(lang_code, strict=False): """ - Returns the language-code that's listed in supported languages, possibly - selecting a more generic variant. Raises LookupError if nothing found. + Return the language code that's listed in supported languages, possibly + selecting a more generic variant. Raise LookupError if nothing is found. - If `strict` is False (the default), the function will look for an alternative - country-specific variant when the currently checked is not found. + If `strict` is False (the default), look for a country-specific variant + when neither the language code nor its generic variant is found. lru_cache should have a maxsize to prevent from memory exhaustion attacks, as the provided language codes are taken from the HTTP request. See also @@ -482,11 +496,10 @@ def get_supported_language_variant(lang_code, strict=False): def get_language_from_path(path, strict=False): """ - Returns the language-code if there is a valid language-code - found in the `path`. + Return the language code if there's a valid language code found in `path`. - If `strict` is False (the default), the function will look for an alternative - country-specific variant when the currently checked is not found. + If `strict` is False (the default), look for a country-specific variant + when neither the language code nor its generic variant is found. """ regex_match = language_code_prefix_re.match(path) if not regex_match: @@ -500,7 +513,7 @@ def get_language_from_path(path, strict=False): def get_language_from_request(request, check_path=False): """ - Analyzes the request to find what language the user wants the system to + Analyze the request to find what language the user wants the system to show. Only languages listed in settings.LANGUAGES are taken into account. If the user requests a sublanguage where we have a main language, we send out the main language. @@ -546,25 +559,26 @@ def get_language_from_request(request, check_path=False): return settings.LANGUAGE_CODE +@functools.lru_cache(maxsize=1000) def parse_accept_lang_header(lang_string): """ - Parses the lang_string, which is the body of an HTTP Accept-Language - header, and returns a list of (lang, q-value), ordered by 'q' values. + Parse the lang_string, which is the body of an HTTP Accept-Language + header, and return a tuple of (lang, q-value), ordered by 'q' values. - Any format errors in lang_string results in an empty list being returned. + Return an empty tuple if there are any format errors in lang_string. """ result = [] pieces = accept_language_re.split(lang_string.lower()) if pieces[-1]: - return [] + return () for i in range(0, len(pieces) - 1, 3): first, lang, priority = pieces[i:i + 3] if first: - return [] + return () if priority: priority = float(priority) else: priority = 1.0 result.append((lang, priority)) result.sort(key=lambda k: k[1], reverse=True) - return result + return tuple(result) diff --git a/django/utils/tree.py b/django/utils/tree.py index 3eb352935479..302cd37d5f63 100644 --- a/django/utils/tree.py +++ b/django/utils/tree.py @@ -5,10 +5,10 @@ import copy -from django.utils.encoding import force_str, force_text +from django.utils.hashable import make_hashable -class Node(object): +class Node: """ A single internal node in the tree graph. A Node should be viewed as a connection (the root) with the children being either leaf nodes or other @@ -19,25 +19,22 @@ class Node(object): default = 'DEFAULT' def __init__(self, children=None, connector=None, negated=False): - """ - Constructs a new Node. If no connector is given, the default will be - used. - """ + """Construct a new Node. If no connector is given, use the default.""" self.children = children[:] if children else [] self.connector = connector or self.default self.negated = negated - # We need this because of django.db.models.query_utils.Q. Q. __init__() is + # Required because django.db.models.query_utils.Q. Q. __init__() is # problematic, but it is a natural Node subclass in all other respects. @classmethod def _new_instance(cls, children=None, connector=None, negated=False): """ - This is called to create a new instance of this class when we need new - Nodes (or subclasses) in the internal code in this class. Normally, it - just shadows __init__(). However, subclasses with an __init__ signature - that is not an extension of Node.__init__ might need to implement this - method to allow a Node to create a new instance of them (if they have - any extra setting up to do). + Create a new instance of this class when new Nodes (or subclasses) are + needed in the internal code in this class. Normally, it just shadows + __init__(). However, subclasses with an __init__ signature that aren't + an extension of Node.__init__ might need to implement this method to + allow a Node to create a new instance of them (if they have any extra + setting up to do). """ obj = Node(children, connector, negated) obj.__class__ = cls @@ -45,52 +42,50 @@ def _new_instance(cls, children=None, connector=None, negated=False): def __str__(self): template = '(NOT (%s: %s))' if self.negated else '(%s: %s)' - return force_str(template % (self.connector, ', '.join(force_text(c) for c in self.children))) + return template % (self.connector, ', '.join(str(c) for c in self.children)) def __repr__(self): - return str("<%s: %s>") % (self.__class__.__name__, self) + return "<%s: %s>" % (self.__class__.__name__, self) def __deepcopy__(self, memodict): - """ - Utility method used by copy.deepcopy(). - """ obj = Node(connector=self.connector, negated=self.negated) obj.__class__ = self.__class__ obj.children = copy.deepcopy(self.children, memodict) return obj def __len__(self): - """ - The size of a node if the number of children it has. - """ + """Return the number of children this node has.""" return len(self.children) def __bool__(self): - """ - For truth value testing. - """ + """Return whether or not this node has children.""" return bool(self.children) - def __nonzero__(self): # Python 2 compatibility - return type(self).__bool__(self) - def __contains__(self, other): - """ - Returns True is 'other' is a direct child of this instance. - """ + """Return True if 'other' is a direct child of this instance.""" return other in self.children + def __eq__(self, other): + return ( + self.__class__ == other.__class__ and + (self.connector, self.negated) == (other.connector, other.negated) and + self.children == other.children + ) + + def __hash__(self): + return hash((self.__class__, self.connector, self.negated, *make_hashable(self.children))) + def add(self, data, conn_type, squash=True): """ - Combines this tree and the data represented by data using the + Combine this tree and the data represented by data using the connector conn_type. The combine is done by squashing the node other away if possible. This tree (self) will never be pushed to a child node of the combined tree, nor will the connector or negated properties change. - The function returns a node which can be used in place of data - regardless if the node other got squashed or not. + Return a node which can be used in place of data regardless if the + node other got squashed or not. If `squash` is False the data is prepared and added as a child to this tree without further logic. @@ -125,7 +120,5 @@ def add(self, data, conn_type, squash=True): return data def negate(self): - """ - Negate the sense of the root connector. - """ + """Negate the sense of the root connector.""" self.negated = not self.negated diff --git a/django/utils/version.py b/django/utils/version.py index f5b0f109e6bf..7d17da318fb3 100644 --- a/django/utils/version.py +++ b/django/utils/version.py @@ -1,14 +1,22 @@ -from __future__ import unicode_literals - import datetime +import functools import os import subprocess +import sys +from distutils.version import LooseVersion -from django.utils.lru_cache import lru_cache +# Private, stable API for detecting the Python version. PYXY means "Python X.Y +# or later". So that third-party apps can use these values, each constant +# should remain as long as the oldest supported Django version supports that +# Python version. +PY36 = sys.version_info >= (3, 6) +PY37 = sys.version_info >= (3, 7) +PY38 = sys.version_info >= (3, 8) +PY39 = sys.version_info >= (3, 9) def get_version(version=None): - "Returns a PEP 440-compliant version number from VERSION." + """Return a PEP 440-compliant version number from VERSION.""" version = get_complete_version(version) # Now build the two parts of the version number: @@ -28,19 +36,20 @@ def get_version(version=None): mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'rc'} sub = mapping[version[3]] + str(version[4]) - return str(main + sub) + return main + sub def get_main_version(version=None): - "Returns main version (X.Y[.Z]) from VERSION." + """Return main version (X.Y[.Z]) from VERSION.""" version = get_complete_version(version) parts = 2 if version[2] == 0 else 3 return '.'.join(str(x) for x in version[:parts]) def get_complete_version(version=None): - """Returns a tuple of the django version. If version argument is non-empty, - then checks for correctness of the tuple provided. + """ + Return a tuple of the django version. If version argument is non-empty, + check for correctness of the tuple provided. """ if version is None: from django import VERSION as version @@ -59,9 +68,9 @@ def get_docs_version(version=None): return '%d.%d' % version[:2] -@lru_cache() +@functools.lru_cache() def get_git_changeset(): - """Returns a numeric identifier of the latest git changeset. + """Return a numeric identifier of the latest git changeset. The result is the UTC timestamp of the changeset in YYYYMMDDHHMMSS format. This value isn't guaranteed to be unique, but collisions are very unlikely, @@ -79,3 +88,17 @@ def get_git_changeset(): except ValueError: return None return timestamp.strftime('%Y%m%d%H%M%S') + + +def get_version_tuple(version): + """ + Return a tuple of version numbers (e.g. (1, 2, 3)) from the version + string (e.g. '1.2.3'). + """ + loose_version = LooseVersion(version) + version_numbers = [] + for item in loose_version.version: + if not isinstance(item, int): + break + version_numbers.append(item) + return tuple(version_numbers) diff --git a/django/utils/xmlutils.py b/django/utils/xmlutils.py index f1edfb2ac98e..1a1403424319 100644 --- a/django/utils/xmlutils.py +++ b/django/utils/xmlutils.py @@ -3,6 +3,7 @@ """ import re +from collections import OrderedDict from xml.sax.saxutils import XMLGenerator @@ -23,6 +24,11 @@ def addQuickElement(self, name, contents=None, attrs=None): def characters(self, content): if content and re.search(r'[\x00-\x08\x0B-\x0C\x0E-\x1F]', content): # Fail loudly when content has control chars (unsupported in XML 1.0) - # See http://www.w3.org/International/questions/qa-controls + # See https://www.w3.org/International/questions/qa-controls raise UnserializableContentError("Control characters are not supported in XML 1.0") XMLGenerator.characters(self, content) + + def startElement(self, name, attrs): + # Sort attrs for a deterministic output. + sorted_attrs = OrderedDict(sorted(attrs.items())) if attrs else attrs + super().startElement(name, sorted_attrs) diff --git a/django/views/csrf.py b/django/views/csrf.py index 5e13e529fcf7..b1c8d252357c 100644 --- a/django/views/csrf.py +++ b/django/views/csrf.py @@ -1,7 +1,7 @@ from django.conf import settings from django.http import HttpResponseForbidden from django.template import Context, Engine, TemplateDoesNotExist, loader -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from django.utils.version import get_docs_version # We include the template inline since we need to be able to reliably display @@ -23,7 +23,7 @@ html * { padding:0; margin:0; } body * { padding:10px 20px; } body * * { padding:0; } - body { font:small sans-serif; background:#eee; } + body { font:small sans-serif; background:#eee; color:#000; } body>div { border-bottom:1px solid #ddd; } h1 { font-weight:normal; margin-bottom:.4em; } h1 span { font-size:60%; color:#666; font-weight:normal; } @@ -41,6 +41,7 @@ {% if no_referer %}

    {{ no_referer1 }}

    {{ no_referer2 }}

    +

    {{ no_referer3 }}

    {% endif %} {% if no_cookie %}

    {{ no_cookie1 }}

    @@ -119,6 +120,13 @@ def csrf_failure(request, reason="", template_name=CSRF_FAILURE_TEMPLATE_NAME): "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."), + 'no_referer3': _( + "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."), 'no_cookie': reason == REASON_NO_CSRF_COOKIE, 'no_cookie1': _( "You are seeing this message because this site requires a CSRF " diff --git a/django/views/debug.py b/django/views/debug.py index 0345859c747b..b9bae8fc1e81 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -1,34 +1,42 @@ -from __future__ import unicode_literals - +import functools import re import sys import types +from pathlib import Path from django.conf import settings from django.http import HttpResponse, HttpResponseNotFound from django.template import Context, Engine, TemplateDoesNotExist -from django.template.defaultfilters import force_escape, pprint +from django.template.defaultfilters import pprint from django.urls import Resolver404, resolve -from django.utils import lru_cache, six, timezone +from django.utils import timezone from django.utils.datastructures import MultiValueDict -from django.utils.encoding import force_bytes, force_text +from django.utils.encoding import force_text from django.utils.module_loading import import_string -from django.utils.translation import ugettext as _ +from django.utils.version import get_docs_version # Minimal Django templates engine to render the error templates -# regardless of the project's TEMPLATES setting. -DEBUG_ENGINE = Engine(debug=True) +# regardless of the project's TEMPLATES setting. Templates are +# read directly from the filesystem so that the error handler +# works even if the template loader is broken. +DEBUG_ENGINE = Engine( + debug=True, + libraries={'i18n': 'django.templatetags.i18n'}, +) HIDDEN_SETTINGS = re.compile('API|TOKEN|KEY|SECRET|PASS|SIGNATURE', flags=re.IGNORECASE) CLEANSED_SUBSTITUTE = '********************' +CURRENT_DIR = Path(__file__).parent -class CallableSettingWrapper(object): - """ Object to wrap callable appearing in settings +class CallableSettingWrapper: + """ + Object to wrap callable appearing in settings. * Not to call in the debug page (#21345). - * Not to break the debug page if the callable forbidding to set attributes (#23070). + * Not to break the debug page if the callable forbidding to set attributes + (#23070). """ def __init__(self, callable_setting): self._wrapped = callable_setting @@ -38,10 +46,9 @@ def __repr__(self): def cleanse_setting(key, value): - """Cleanse an individual setting key/value of sensitive content. - - If the value is a dictionary, recursively cleanse the keys in - that dictionary. + """ + Cleanse an individual setting key/value of sensitive content. If the value + is a dictionary, recursively cleanse the keys in that dictionary. """ try: if HIDDEN_SETTINGS.search(key): @@ -63,7 +70,10 @@ def cleanse_setting(key, value): def get_safe_settings(): - "Returns a dictionary of the settings module, with sensitive settings blurred out." + """ + Return a dictionary of the settings module with values of sensitive + settings replaced with stars (*********). + """ settings_dict = {} for k in dir(settings): if k.isupper(): @@ -79,13 +89,13 @@ def technical_500_response(request, exc_type, exc_value, tb, status_code=500): reporter = ExceptionReporter(request, exc_type, exc_value, tb) if request.is_ajax(): text = reporter.get_traceback_text() - return HttpResponse(text, status=status_code, content_type='text/plain') + return HttpResponse(text, status=status_code, content_type='text/plain; charset=utf-8') else: html = reporter.get_traceback_html() return HttpResponse(html, status=status_code, content_type='text/html') -@lru_cache.lru_cache() +@functools.lru_cache() def get_default_exception_reporter_filter(): # Instantiate the default filter for the first time and cache it. return import_string(settings.DEFAULT_EXCEPTION_REPORTER_FILTER)() @@ -96,7 +106,7 @@ def get_exception_reporter_filter(request): return getattr(request, 'exception_reporter_filter', default_filter) -class ExceptionReporterFilter(object): +class ExceptionReporterFilter: """ Base for all exception reporter filter classes. All overridable hooks contain lenient default behaviors. @@ -129,7 +139,7 @@ def is_active(self, request): def get_cleansed_multivaluedict(self, request, multivaluedict): """ - Replaces the keys in a MultiValueDict marked as sensitive with stars. + Replace the keys in a MultiValueDict marked as sensitive with stars. This mitigates leaking sensitive POST parameters if something like request.POST['nonexistent_key'] throws an exception (#21098). """ @@ -143,7 +153,7 @@ def get_cleansed_multivaluedict(self, request, multivaluedict): def get_post_parameters(self, request): """ - Replaces the values of POST parameters marked as sensitive with + Replace the values of POST parameters marked as sensitive with stars (*********). """ if request is None: @@ -154,7 +164,7 @@ def get_post_parameters(self, request): cleansed = request.POST.copy() if sensitive_post_parameters == '__ALL__': # Cleanse all parameters. - for k, v in cleansed.items(): + for k in cleansed: cleansed[k] = CLEANSED_SUBSTITUTE return cleansed else: @@ -182,7 +192,7 @@ def cleanse_special_types(self, request, value): def get_traceback_frame_variables(self, request, tb_frame): """ - Replaces the values of variables marked as sensitive with + Replace the values of variables marked as sensitive with stars (*********). """ # Loop through the frame's callers to see if the sensitive_variables @@ -203,7 +213,7 @@ def get_traceback_frame_variables(self, request, tb_frame): if self.is_active(request) and sensitive_variables: if sensitive_variables == '__ALL__': # Cleanse all variables - for name, value in tb_frame.f_locals.items(): + for name in tb_frame.f_locals: cleansed[name] = CLEANSED_SUBSTITUTE else: # Cleanse specified variables @@ -231,10 +241,8 @@ def get_traceback_frame_variables(self, request, tb_frame): return cleansed.items() -class ExceptionReporter(object): - """ - A class to organize and coordinate reporting on exceptions. - """ +class ExceptionReporter: + """Organize and coordinate reporting on exceptions.""" def __init__(self, request, exc_type, exc_value, tb, is_email=False): self.request = request self.filter = get_exception_reporter_filter(self.request) @@ -247,11 +255,6 @@ def __init__(self, request, exc_type, exc_value, tb, is_email=False): self.template_does_not_exist = False self.postmortem = None - # Handle deprecated string exceptions - if isinstance(self.exc_type, six.string_types): - self.exc_value = Exception('Deprecated String Exception: %r' % self.exc_type) - self.exc_type = type(self.exc_value) - def get_traceback_data(self): """Return a dictionary containing traceback information.""" if self.exc_type and issubclass(self.exc_type, TemplateDoesNotExist): @@ -264,13 +267,10 @@ def get_traceback_data(self): frame_vars = [] for k, v in frame['vars']: v = pprint(v) - # The force_escape filter assume unicode, make sure that works - if isinstance(v, six.binary_type): - v = v.decode('utf-8', 'replace') # don't choke on non-utf-8 input # Trim large blobs of data if len(v) > 4096: - v = '%s... ' % (v[0:4096], len(v)) - frame_vars.append((k, force_escape(v))) + v = '%s… ' % (v[0:4096], len(v)) + frame_vars.append((k, v)) frame['vars'] = frame_vars frames[i] = frame @@ -290,7 +290,7 @@ def get_traceback_data(self): user_str = None else: try: - user_str = force_text(self.request.user) + user_str = str(self.request.user) except Exception: # request.user may raise OperationalError if the database is # unavailable, for example. @@ -302,7 +302,7 @@ def get_traceback_data(self): 'frames': frames, 'request': self.request, 'user_str': user_str, - 'filtered_POST_items': self.filter.get_post_parameters(self.request).items(), + 'filtered_POST_items': list(self.filter.get_post_parameters(self.request).items()), 'settings': get_safe_settings(), 'sys_executable': sys.executable, 'sys_version_info': '%d.%d.%d' % sys.version_info[0:3], @@ -321,30 +321,32 @@ def get_traceback_data(self): if self.exc_type: c['exception_type'] = self.exc_type.__name__ if self.exc_value: - c['exception_value'] = force_text(self.exc_value, errors='replace') + c['exception_value'] = str(self.exc_value) if frames: c['lastframe'] = frames[-1] return c def get_traceback_html(self): - "Return HTML version of debug 500 HTTP error page." - t = DEBUG_ENGINE.from_string(TECHNICAL_500_TEMPLATE) + """Return HTML version of debug 500 HTTP error page.""" + with Path(CURRENT_DIR, 'templates', 'technical_500.html').open(encoding='utf-8') as fh: + t = DEBUG_ENGINE.from_string(fh.read()) c = Context(self.get_traceback_data(), use_l10n=False) return t.render(c) def get_traceback_text(self): - "Return plain text version of debug 500 HTTP error page." - t = DEBUG_ENGINE.from_string(TECHNICAL_500_TEXT_TEMPLATE) + """Return plain text version of debug 500 HTTP error page.""" + with Path(CURRENT_DIR, 'templates', 'technical_500.txt').open(encoding='utf-8') as fh: + t = DEBUG_ENGINE.from_string(fh.read()) c = Context(self.get_traceback_data(), autoescape=False, use_l10n=False) return t.render(c) def _get_lines_from_file(self, filename, lineno, context_lines, loader=None, module_name=None): """ - Returns context_lines before and after lineno from file. - Returns (pre_context_lineno, pre_context, context_line, post_context). + Return context_lines before and after lineno from file. + Return (pre_context_lineno, pre_context, context_line, post_context). """ source = None - if loader is not None and hasattr(loader, "get_source"): + if hasattr(loader, 'get_source'): try: source = loader.get_source(module_name) except ImportError: @@ -361,18 +363,18 @@ def _get_lines_from_file(self, filename, lineno, context_lines, loader=None, mod return None, [], None, [] # If we just read the source from a file, or if the loader did not - # apply tokenize.detect_encoding to decode the source into a Unicode + # apply tokenize.detect_encoding to decode the source into a # string, then we should do that ourselves. - if isinstance(source[0], six.binary_type): + if isinstance(source[0], bytes): encoding = 'ascii' for line in source[:2]: # File coding may be specified. Match pattern from PEP-263 - # (http://www.python.org/dev/peps/pep-0263/) + # (https://www.python.org/dev/peps/pep-0263/) match = re.search(br'coding[:=]\s*([-\w.]+)', line) if match: encoding = match.group(1).decode('ascii') break - source = [six.text_type(sline, encoding, 'replace') for sline in source] + source = [str(sline, encoding, 'replace') for sline in source] lower_bound = max(0, lineno - context_lines) upper_bound = lineno + context_lines @@ -401,11 +403,9 @@ def explicit_or_implicit_cause(exc_value): if not exceptions: return frames - # In case there's just one exception (always in Python 2, - # sometimes in Python 3), take the traceback from self.tb (Python 2 - # doesn't have a __traceback__ attribute on Exception) + # In case there's just one exception, take the traceback from self.tb exc_value = exceptions.pop() - tb = self.tb if six.PY2 or not exceptions else exc_value.__traceback__ + tb = self.tb if not exceptions else exc_value.__traceback__ while tb is not None: # Support for __traceback_hide__ which is used by a few libraries @@ -421,28 +421,30 @@ def explicit_or_implicit_cause(exc_value): pre_context_lineno, pre_context, context_line, post_context = self._get_lines_from_file( filename, lineno, 7, loader, module_name, ) - if pre_context_lineno is not None: - frames.append({ - 'exc_cause': explicit_or_implicit_cause(exc_value), - 'exc_cause_explicit': getattr(exc_value, '__cause__', True), - 'tb': tb, - 'type': 'django' if module_name.startswith('django.') else 'user', - 'filename': filename, - 'function': function, - 'lineno': lineno + 1, - 'vars': self.filter.get_traceback_frame_variables(self.request, tb.tb_frame), - 'id': id(tb), - 'pre_context': pre_context, - 'context_line': context_line, - 'post_context': post_context, - 'pre_context_lineno': pre_context_lineno + 1, - }) + if pre_context_lineno is None: + pre_context_lineno = lineno + pre_context = [] + context_line = '' + post_context = [] + frames.append({ + 'exc_cause': explicit_or_implicit_cause(exc_value), + 'exc_cause_explicit': getattr(exc_value, '__cause__', True), + 'tb': tb, + 'type': 'django' if module_name.startswith('django.') else 'user', + 'filename': filename, + 'function': function, + 'lineno': lineno + 1, + 'vars': self.filter.get_traceback_frame_variables(self.request, tb.tb_frame), + 'id': id(tb), + 'pre_context': pre_context, + 'context_line': context_line, + 'post_context': post_context, + 'pre_context_lineno': pre_context_lineno + 1, + }) # If the traceback for current exception is consumed, try the # other exception. - if six.PY2: - tb = tb.tb_next - elif not tb.tb_next and exceptions: + if not tb.tb_next and exceptions: exc_value = exceptions.pop() tb = exc_value.__traceback__ else: @@ -450,21 +452,9 @@ def explicit_or_implicit_cause(exc_value): return frames - def format_exception(self): - """ - Return the same data as from traceback.format_exception. - """ - import traceback - frames = self.get_traceback_frames() - tb = [(f['filename'], f['lineno'], f['function'], f['context_line']) for f in frames] - list = ['Traceback (most recent call last):\n'] - list += traceback.format_list(tb) - list += traceback.format_exception_only(self.exc_type, self.exc_value) - return list - def technical_404_response(request, exception): - "Create a technical 404 error response. The exception should be the Http404." + """Create a technical 404 error response. `exception` is the Http404.""" try: error_url = exception.args[0]['path'] except (IndexError, TypeError, KeyError): @@ -504,13 +494,14 @@ def technical_404_response(request, exception): module = obj.__module__ caller = '%s.%s' % (module, caller) - t = DEBUG_ENGINE.from_string(TECHNICAL_404_TEMPLATE) + with Path(CURRENT_DIR, 'templates', 'technical_404.html').open() as fh: + t = DEBUG_ENGINE.from_string(fh.read()) c = Context({ 'urlconf': urlconf, 'root_urlconf': settings.ROOT_URLCONF, 'request_path': error_url, 'urlpatterns': tried, - 'reason': force_bytes(exception, errors='replace'), + 'reason': str(exception), 'request': request, 'settings': get_safe_settings(), 'raising_view_name': caller, @@ -519,760 +510,11 @@ def technical_404_response(request, exception): def default_urlconf(request): - "Create an empty URLconf 404 error response." - t = DEBUG_ENGINE.from_string(DEFAULT_URLCONF_TEMPLATE) + """Create an empty URLconf 404 error response.""" + with Path(CURRENT_DIR, 'templates', 'default_urlconf.html').open() as fh: + t = DEBUG_ENGINE.from_string(fh.read()) c = Context({ - "title": _("Welcome to Django"), - "heading": _("It worked!"), - "subheading": _("Congratulations on your first Django-powered page."), - "instructions": _( - "Next, start your first app by running python manage.py startapp [app_label]." - ), - "explanation": _( - "You're seeing this message because you have DEBUG = True in your " - "Django settings file and you haven't configured any URLs. Get to work!" - ), + 'version': get_docs_version(), }) return HttpResponse(t.render(c), content_type='text/html') - - -# -# Templates are embedded in the file so that we know the error handler will -# always work even if the template loader is broken. -# - -TECHNICAL_500_TEMPLATE = (""" - - - - - - {% if exception_type %}{{ exception_type }}{% else %}Report{% endif %}""" -r"""{% if request %} at {{ request.path_info|escape }}{% endif %} - - {% if not is_email %} - - {% endif %} - - -
    -

    {% if exception_type %}{{ exception_type }}{% else %}Report{% endif %}""" - """{% if request %} at {{ request.path_info|escape }}{% endif %}

    -
    """
    - """{% if exception_value %}{{ exception_value|force_escape }}{% else %}No exception message supplied{% endif %}"""
    -"""
    - -{% if request %} - - - - - - - - -{% endif %} - - - - -{% if exception_type %} - - - - -{% endif %} -{% if exception_type and exception_value %} - - - - -{% endif %} -{% if lastframe %} - - - - -{% endif %} - - - - - - - - - - - - - - - - -
    Request Method:{{ request.META.REQUEST_METHOD }}
    Request URL:{{ request.get_raw_uri|escape }}
    Django Version:{{ django_version_info }}
    Exception Type:{{ exception_type }}
    Exception Value:
    {{ exception_value|force_escape }}
    Exception Location:{{ lastframe.filename|escape }} in {{ lastframe.function|escape }}, line {{ lastframe.lineno }}
    Python Executable:{{ sys_executable|escape }}
    Python Version:{{ sys_version_info }}
    Python Path:
    {{ sys_path|pprint }}
    Server time:{{server_time|date:"r"}}
    -
    -{% if unicode_hint %} -
    -

    Unicode error hint

    -

    The string that could not be encoded/decoded was: {{ unicode_hint|force_escape }}

    -
    -{% endif %} -{% if template_does_not_exist %} -
    -

    Template-loader postmortem

    - {% if postmortem %} -

    Django tried loading these templates, in this order:

    - {% for entry in postmortem %} -

    Using engine {{ entry.backend.name }}:

    -
      - {% if entry.tried %} - {% for attempt in entry.tried %} -
    • {{ attempt.0.loader_name }}: {{ attempt.0.name }} ({{ attempt.1 }})
    • - {% endfor %} - {% else %} -
    • This engine did not provide a list of tried templates.
    • - {% endif %} -
    - {% endfor %} - {% else %} -

    No templates were found because your 'TEMPLATES' setting is not configured.

    - {% endif %} -
    -{% endif %} -{% if template_info %} -
    -

    Error during template rendering

    -

    In template {{ template_info.name }}, error at line {{ template_info.line }}

    -

    {{ template_info.message }}

    - - {% for source_line in template_info.source_lines %} - {% if source_line.0 == template_info.line %} - - - - {% else %} - - - {% endif %} - {% endfor %} -
    {{ source_line.0 }}{{ template_info.before }}""" - """{{ template_info.during }}""" - """{{ template_info.after }}
    {{ source_line.0 }}{{ source_line.1 }}
    -
    -{% endif %} -{% if frames %} -
    -

    Traceback {% if not is_email %} - Switch to copy-and-paste view{% endif %} -

    - {% autoescape off %} -
    -
      - {% for frame in frames %} - {% ifchanged frame.exc_cause %}{% if frame.exc_cause %} -
    • - {% if frame.exc_cause_explicit %} - The above exception ({{ frame.exc_cause }}) was the direct cause of the following exception: - {% else %} - During handling of the above exception ({{ frame.exc_cause }}), another exception occurred: - {% endif %} -

    • - {% endif %}{% endifchanged %} -
    • - {{ frame.filename|escape }} in {{ frame.function|escape }} - - {% if frame.context_line %} -
      - {% if frame.pre_context and not is_email %} -
        - {% for line in frame.pre_context %} -
      1. {{ line|escape }}
      2. - {% endfor %} -
      - {% endif %} -
        -
      1. -"""            """{{ frame.context_line|escape }}
        {% if not is_email %} ...{% endif %}
      - {% if frame.post_context and not is_email %} -
        - {% for line in frame.post_context %} -
      1. {{ line|escape }}
      2. - {% endfor %} -
      - {% endif %} -
      - {% endif %} - - {% if frame.vars %} -
      - {% if is_email %} -

      Local Vars

      - {% else %} - Local vars - {% endif %} -
      - - - - - - - - - {% for var in frame.vars|dictsort:0 %} - - - - - {% endfor %} - -
      VariableValue
      {{ var.0|force_escape }}
      {{ var.1 }}
      - {% endif %} -
    • - {% endfor %} -
    -
    - {% endautoescape %} -
    -{% if not is_email %} -
    - - - - - -

    - -
    -
    -
    -{% endif %} -{% endif %} - -
    -

    Request information

    - -{% if request %} - {% if user_str %} -

    USER

    -

    {{ user_str }}

    - {% endif %} - -

    GET

    - {% if request.GET %} - - - - - - - - - {% for k, v in request_GET_items %} - - - - - {% endfor %} - -
    VariableValue
    {{ k }}
    {{ v|pprint }}
    - {% else %} -

    No GET data

    - {% endif %} - -

    POST

    - {% if filtered_POST_items %} - - - - - - - - - {% for k, v in filtered_POST_items %} - - - - - {% endfor %} - -
    VariableValue
    {{ k }}
    {{ v|pprint }}
    - {% else %} -

    No POST data

    - {% endif %} -

    FILES

    - {% if request.FILES %} - - - - - - - - - {% for k, v in request_FILES_items %} - - - - - {% endfor %} - -
    VariableValue
    {{ k }}
    {{ v|pprint }}
    - {% else %} -

    No FILES data

    - {% endif %} - - - - {% if request.COOKIES %} - - - - - - - - - {% for k, v in request_COOKIES_items %} - - - - - {% endfor %} - -
    VariableValue
    {{ k }}
    {{ v|pprint }}
    - {% else %} -

    No cookie data

    - {% endif %} - -

    META

    - - - - - - - - - {% for var in request.META.items|dictsort:0 %} - - - - - {% endfor %} - -
    VariableValue
    {{ var.0 }}
    {{ var.1|pprint }}
    -{% else %} -

    Request data not supplied

    -{% endif %} - -

    Settings

    -

    Using settings module {{ settings.SETTINGS_MODULE }}

    - - - - - - - - - {% for var in settings.items|dictsort:0 %} - - - - - {% endfor %} - -
    SettingValue
    {{ var.0 }}
    {{ var.1|pprint }}
    - -
    -{% if not is_email %} -
    -

    - You're seeing this error because you have DEBUG = True in your - Django settings file. Change that to False, and Django will - display a standard page generated by the handler for this status code. -

    -
    -{% endif %} - - -""") # NOQA - -TECHNICAL_500_TEXT_TEMPLATE = ("""""" -"""{% firstof exception_type 'Report' %}{% if request %} at {{ request.path_info }}{% endif %} -{% firstof exception_value 'No exception message supplied' %} -{% if request %} -Request Method: {{ request.META.REQUEST_METHOD }} -Request URL: {{ request.get_raw_uri }}{% endif %} -Django Version: {{ django_version_info }} -Python Executable: {{ sys_executable }} -Python Version: {{ sys_version_info }} -Python Path: {{ sys_path }} -Server time: {{server_time|date:"r"}} -Installed Applications: -{{ settings.INSTALLED_APPS|pprint }} -Installed Middleware: -{% if settings.MIDDLEWARE is not None %}{{ settings.MIDDLEWARE|pprint }}""" -"""{% else %}{{ settings.MIDDLEWARE_CLASSES|pprint }}{% endif %} -{% if template_does_not_exist %}Template loader postmortem -{% if postmortem %}Django tried loading these templates, in this order: -{% for entry in postmortem %} -Using engine {{ entry.backend.name }}: -{% if entry.tried %}{% for attempt in entry.tried %}""" -""" * {{ attempt.0.loader_name }}: {{ attempt.0.name }} ({{ attempt.1 }}) -{% endfor %}{% else %} This engine did not provide a list of tried templates. -{% endif %}{% endfor %} -{% else %}No templates were found because your 'TEMPLATES' setting is not configured. -{% endif %} -{% endif %}{% if template_info %} -Template error: -In template {{ template_info.name }}, error at line {{ template_info.line }} - {{ template_info.message }} -{% for source_line in template_info.source_lines %}""" -"{% if source_line.0 == template_info.line %}" -" {{ source_line.0 }} : {{ template_info.before }} {{ template_info.during }} {{ template_info.after }}" -"{% else %}" -" {{ source_line.0 }} : {{ source_line.1 }}" -"""{% endif %}{% endfor %}{% endif %}{% if frames %} - -Traceback:""" -"{% for frame in frames %}" -"{% ifchanged frame.exc_cause %}" -" {% if frame.exc_cause %}" """ - {% if frame.exc_cause_explicit %} - The above exception ({{ frame.exc_cause }}) was the direct cause of the following exception: - {% else %} - During handling of the above exception ({{ frame.exc_cause }}), another exception occurred: - {% endif %} - {% endif %} -{% endifchanged %} -File "{{ frame.filename }}" in {{ frame.function }} -{% if frame.context_line %} {{ frame.lineno }}. {{ frame.context_line }}{% endif %} -{% endfor %} -{% if exception_type %}Exception Type: {{ exception_type }}{% if request %} at {{ request.path_info }}{% endif %} -{% if exception_value %}Exception Value: {{ exception_value }}{% endif %}{% endif %}{% endif %} -{% if request %}Request information: -{% if user_str %}USER: {{ user_str }}{% endif %} - -GET:{% for k, v in request_GET_items %} -{{ k }} = {{ v|stringformat:"r" }}{% empty %} No GET data{% endfor %} - -POST:{% for k, v in filtered_POST_items %} -{{ k }} = {{ v|stringformat:"r" }}{% empty %} No POST data{% endfor %} - -FILES:{% for k, v in request_FILES_items %} -{{ k }} = {{ v|stringformat:"r" }}{% empty %} No FILES data{% endfor %} - -COOKIES:{% for k, v in request_COOKIES_items %} -{{ k }} = {{ v|stringformat:"r" }}{% empty %} No cookie data{% endfor %} - -META:{% for k, v in request.META.items|dictsort:0 %} -{{ k }} = {{ v|stringformat:"r" }}{% endfor %} -{% else %}Request data not supplied -{% endif %} -Settings: -Using settings module {{ settings.SETTINGS_MODULE }}{% for k, v in settings.items|dictsort:0 %} -{{ k }} = {{ v|stringformat:"r" }}{% endfor %} - -{% if not is_email %} -You're seeing this error because you have DEBUG = True in your -Django settings file. Change that to False, and Django will -display a standard page generated by the handler for this status code. -{% endif %} -""") # NOQA - -TECHNICAL_404_TEMPLATE = """ - - - - - Page not found at {{ request.path_info|escape }} - - - - -
    -

    Page not found (404)

    - - - - - - - - - - {% if raising_view_name %} - - - - - {% endif %} -
    Request Method:{{ request.META.REQUEST_METHOD }}
    Request URL:{{ request.build_absolute_uri|escape }}
    Raised by:{{ raising_view_name }}
    -
    -
    - {% if urlpatterns %} -

    - Using the URLconf defined in {{ urlconf }}, - Django tried these URL patterns, in this order: -

    -
      - {% for pattern in urlpatterns %} -
    1. - {% for pat in pattern %} - {{ pat.regex.pattern }} - {% if forloop.last and pat.name %}[name='{{ pat.name }}']{% endif %} - {% endfor %} -
    2. - {% endfor %} -
    -

    - {% if request_path %} - The current path, {{ request_path|escape }},{% else %} - The empty path{% endif %} didn't match any of these. -

    - {% else %} -

    {{ reason }}

    - {% endif %} -
    - -
    -

    - You're seeing this error because you have DEBUG = True in - your Django settings file. Change that to False, and Django - will display a standard 404 page. -

    -
    - - -""" - -DEFAULT_URLCONF_TEMPLATE = """ - - - - {{ title }} - - - - -
    -

    {{ heading }}

    -

    {{ subheading }}

    -
    - -
    -

    - {{ instructions|safe }} -

    -
    - -
    -

    - {{ explanation|safe }} -

    -
    - -""" diff --git a/django/views/decorators/cache.py b/django/views/decorators/cache.py index 07e0e70946d3..9658bd6ba257 100644 --- a/django/views/decorators/cache.py +++ b/django/views/decorators/cache.py @@ -2,12 +2,10 @@ from django.middleware.cache import CacheMiddleware from django.utils.cache import add_never_cache_headers, patch_cache_control -from django.utils.decorators import ( - available_attrs, decorator_from_middleware_with_args, -) +from django.utils.decorators import decorator_from_middleware_with_args -def cache_page(*args, **kwargs): +def cache_page(timeout, *, cache=None, key_prefix=None): """ Decorator for views that tries getting the page from the cache and populates the cache if the page isn't in the cache yet. @@ -21,24 +19,14 @@ def cache_page(*args, **kwargs): Additionally, all headers from the response's Vary header will be taken into account on caching -- just like the middleware does. """ - # We also add some asserts to give better error messages in case people are - # using other ways to call cache_page that no longer work. - if len(args) != 1 or callable(args[0]): - raise TypeError("cache_page has a single mandatory positional argument: timeout") - cache_timeout = args[0] - cache_alias = kwargs.pop('cache', None) - key_prefix = kwargs.pop('key_prefix', None) - if kwargs: - raise TypeError("cache_page has two optional keyword arguments: cache and key_prefix") - return decorator_from_middleware_with_args(CacheMiddleware)( - cache_timeout=cache_timeout, cache_alias=cache_alias, key_prefix=key_prefix + cache_timeout=timeout, cache_alias=cache, key_prefix=key_prefix ) def cache_control(**kwargs): def _cache_controller(viewfunc): - @wraps(viewfunc, assigned=available_attrs(viewfunc)) + @wraps(viewfunc) def _cache_controlled(request, *args, **kw): response = viewfunc(request, *args, **kw) patch_cache_control(response, **kwargs) @@ -49,10 +37,9 @@ def _cache_controlled(request, *args, **kw): def never_cache(view_func): """ - Decorator that adds headers to a response so that it will - never be cached. + Decorator that adds headers to a response so that it will never be cached. """ - @wraps(view_func, assigned=available_attrs(view_func)) + @wraps(view_func) def _wrapped_view_func(request, *args, **kwargs): response = view_func(request, *args, **kwargs) add_never_cache_headers(response) diff --git a/django/views/decorators/clickjacking.py b/django/views/decorators/clickjacking.py index bb63c500962b..f8fc2d2b953f 100644 --- a/django/views/decorators/clickjacking.py +++ b/django/views/decorators/clickjacking.py @@ -1,15 +1,11 @@ from functools import wraps -from django.utils.decorators import available_attrs - def xframe_options_deny(view_func): """ - Modifies a view function so its response has the X-Frame-Options HTTP + Modify a view function so its response has the X-Frame-Options HTTP header set to 'DENY' as long as the response doesn't already have that - header set. - - e.g. + header set. Usage: @xframe_options_deny def some_view(request): @@ -20,16 +16,14 @@ def wrapped_view(*args, **kwargs): if resp.get('X-Frame-Options') is None: resp['X-Frame-Options'] = 'DENY' return resp - return wraps(view_func, assigned=available_attrs(view_func))(wrapped_view) + return wraps(view_func)(wrapped_view) def xframe_options_sameorigin(view_func): """ - Modifies a view function so its response has the X-Frame-Options HTTP + Modify a view function so its response has the X-Frame-Options HTTP header set to 'SAMEORIGIN' as long as the response doesn't already have - that header set. - - e.g. + that header set. Usage: @xframe_options_sameorigin def some_view(request): @@ -40,15 +34,13 @@ def wrapped_view(*args, **kwargs): if resp.get('X-Frame-Options') is None: resp['X-Frame-Options'] = 'SAMEORIGIN' return resp - return wraps(view_func, assigned=available_attrs(view_func))(wrapped_view) + return wraps(view_func)(wrapped_view) def xframe_options_exempt(view_func): """ - Modifies a view function by setting a response variable that instructs - XFrameOptionsMiddleware to NOT set the X-Frame-Options HTTP header. - - e.g. + Modify a view function by setting a response variable that instructs + XFrameOptionsMiddleware to NOT set the X-Frame-Options HTTP header. Usage: @xframe_options_exempt def some_view(request): @@ -58,4 +50,4 @@ def wrapped_view(*args, **kwargs): resp = view_func(*args, **kwargs) resp.xframe_options_exempt = True return resp - return wraps(view_func, assigned=available_attrs(view_func))(wrapped_view) + return wraps(view_func)(wrapped_view) diff --git a/django/views/decorators/csrf.py b/django/views/decorators/csrf.py index d8c89c176ca0..19d439a55a33 100644 --- a/django/views/decorators/csrf.py +++ b/django/views/decorators/csrf.py @@ -1,7 +1,7 @@ from functools import wraps from django.middleware.csrf import CsrfViewMiddleware, get_token -from django.utils.decorators import available_attrs, decorator_from_middleware +from django.utils.decorators import decorator_from_middleware csrf_protect = decorator_from_middleware(CsrfViewMiddleware) csrf_protect.__name__ = "csrf_protect" @@ -13,8 +13,7 @@ class _EnsureCsrfToken(CsrfViewMiddleware): - # We need this to behave just like the CsrfViewMiddleware, but not reject - # requests or log warnings. + # Behave like CsrfViewMiddleware but don't reject requests or log warnings. def _reject(self, request, reason): return None @@ -33,8 +32,8 @@ def _reject(self, request, reason): return None def process_view(self, request, callback, callback_args, callback_kwargs): - retval = super(_EnsureCsrfCookie, self).process_view(request, callback, callback_args, callback_kwargs) - # Forces process_response to send the cookie + retval = super().process_view(request, callback, callback_args, callback_kwargs) + # Force process_response to send the cookie get_token(request) return retval @@ -48,13 +47,10 @@ def process_view(self, request, callback, callback_args, callback_kwargs): def csrf_exempt(view_func): - """ - Marks a view function as being exempt from the CSRF view protection. - """ - # We could just do view_func.csrf_exempt = True, but decorators - # are nicer if they don't have side-effects, so we return a new - # function. + """Mark a view function as being exempt from the CSRF view protection.""" + # view_func.csrf_exempt = True would also work, but decorators are nicer + # if they don't have side effects, so return a new function. def wrapped_view(*args, **kwargs): return view_func(*args, **kwargs) wrapped_view.csrf_exempt = True - return wraps(view_func, assigned=available_attrs(view_func))(wrapped_view) + return wraps(view_func)(wrapped_view) diff --git a/django/views/decorators/debug.py b/django/views/decorators/debug.py index 318759f052ef..42a6d3266191 100644 --- a/django/views/decorators/debug.py +++ b/django/views/decorators/debug.py @@ -5,11 +5,11 @@ def sensitive_variables(*variables): """ - Indicates which variables used in the decorated function are sensitive, so + Indicate which variables used in the decorated function are sensitive so that those variables can later be treated in a special way, for example by hiding them when logging unhandled exceptions. - Two forms are accepted: + Accept two forms: * with specified variable names: @@ -19,8 +19,8 @@ def my_function(user): credit_card = user.credit_card_number ... - * without any specified variable names, in which case it is assumed that - all variables are considered sensitive: + * without any specified variable names, in which case consider all + variables are sensitive: @sensitive_variables() def my_function() @@ -40,11 +40,11 @@ def sensitive_variables_wrapper(*func_args, **func_kwargs): def sensitive_post_parameters(*parameters): """ - Indicates which POST parameters used in the decorated view are sensitive, + Indicate which POST parameters used in the decorated view are sensitive, so that those parameters can later be treated in a special way, for example by hiding them when logging unhandled exceptions. - Two forms are accepted: + Accept two forms: * with specified parameters: @@ -54,8 +54,8 @@ def my_view(request): cc = request.POST['credit_card'] ... - * without any specified parameters, in which case it is assumed that - all parameters are considered sensitive: + * without any specified parameters, in which case consider all + variables are sensitive: @sensitive_post_parameters() def my_view(request) diff --git a/django/views/decorators/http.py b/django/views/decorators/http.py index 21b56a3202f0..673302be8336 100644 --- a/django/views/decorators/http.py +++ b/django/views/decorators/http.py @@ -2,20 +2,18 @@ Decorators for views based on HTTP headers. """ -import logging from calendar import timegm from functools import wraps from django.http import HttpResponseNotAllowed from django.middleware.http import ConditionalGetMiddleware from django.utils.cache import get_conditional_response -from django.utils.decorators import available_attrs, decorator_from_middleware +from django.utils.decorators import decorator_from_middleware from django.utils.http import http_date, quote_etag +from django.utils.log import log_response conditional_page = decorator_from_middleware(ConditionalGetMiddleware) -logger = logging.getLogger('django.request') - def require_http_methods(request_method_list): """ @@ -29,14 +27,16 @@ def my_view(request): Note that request methods should be in uppercase. """ def decorator(func): - @wraps(func, assigned=available_attrs(func)) + @wraps(func) def inner(request, *args, **kwargs): if request.method not in request_method_list: - logger.warning( + response = HttpResponseNotAllowed(request_method_list) + log_response( 'Method Not Allowed (%s): %s', request.method, request.path, - extra={'status_code': 405, 'request': request} + response=response, + request=request, ) - return HttpResponseNotAllowed(request_method_list) + return response return func(request, *args, **kwargs) return inner return decorator @@ -70,12 +70,12 @@ def condition(etag_func=None, last_modified_func=None): This decorator will either pass control to the wrapped view function or return an HTTP 304 response (unmodified) or 412 response (precondition - failed), depending upon the request method. In either case, it will add the - generated ETag and Last-Modified headers to the response if it doesn't - already have them. + failed), depending upon the request method. In either case, the decorator + will add the generated ETag and Last-Modified headers to the response if + the headers aren't already set and if the request's method is safe. """ def decorator(func): - @wraps(func, assigned=available_attrs(func)) + @wraps(func) def inner(request, *args, **kwargs): # Compute values (if any) for the requested resource. def get_last_modified(): @@ -98,11 +98,13 @@ def get_last_modified(): if response is None: response = func(request, *args, **kwargs) - # Set relevant headers on the response if they don't already exist. - if res_last_modified and not response.has_header('Last-Modified'): - response['Last-Modified'] = http_date(res_last_modified) - if res_etag and not response.has_header('ETag'): - response['ETag'] = res_etag + # Set relevant headers on the response if they don't already exist + # and if the request method is safe. + if request.method in ('GET', 'HEAD'): + if res_last_modified and not response.has_header('Last-Modified'): + response['Last-Modified'] = http_date(res_last_modified) + if res_etag: + response.setdefault('ETag', res_etag) return response diff --git a/django/views/decorators/vary.py b/django/views/decorators/vary.py index df92c65c5ce1..68b783ed3a01 100644 --- a/django/views/decorators/vary.py +++ b/django/views/decorators/vary.py @@ -1,7 +1,6 @@ from functools import wraps from django.utils.cache import patch_vary_headers -from django.utils.decorators import available_attrs def vary_on_headers(*headers): @@ -16,7 +15,7 @@ def index(request): Note that the header names are not case-sensitive. """ def decorator(func): - @wraps(func, assigned=available_attrs(func)) + @wraps(func) def inner_func(*args, **kwargs): response = func(*args, **kwargs) patch_vary_headers(response, headers) @@ -34,7 +33,7 @@ def vary_on_cookie(func): def index(request): ... """ - @wraps(func, assigned=available_attrs(func)) + @wraps(func) def inner_func(*args, **kwargs): response = func(*args, **kwargs) patch_vary_headers(response, ('Cookie',)) diff --git a/django/views/defaults.py b/django/views/defaults.py index 348837ed99d7..8bf60c9d7459 100644 --- a/django/views/defaults.py +++ b/django/views/defaults.py @@ -1,7 +1,10 @@ -from django import http +from urllib.parse import quote + +from django.http import ( + HttpResponseBadRequest, HttpResponseForbidden, HttpResponseNotFound, + HttpResponseServerError, +) from django.template import Context, Engine, TemplateDoesNotExist, loader -from django.utils import six -from django.utils.encoding import force_text from django.views.decorators.csrf import requires_csrf_token ERROR_404_TEMPLATE_NAME = '404.html' @@ -21,7 +24,8 @@ def page_not_found(request, exception, template_name=ERROR_404_TEMPLATE_NAME): Templates: :template:`404.html` Context: request_path - The path of the requested URL (e.g., '/app/pages/bad_page/') + The path of the requested URL (e.g., '/app/pages/bad_page/'). It's + quoted to prevent a content injection attack. exception The message from the exception which triggered the 404 (if one was supplied), or the exception class name @@ -34,10 +38,10 @@ def page_not_found(request, exception, template_name=ERROR_404_TEMPLATE_NAME): except (AttributeError, IndexError): pass else: - if isinstance(message, six.text_type): + if isinstance(message, str): exception_repr = message context = { - 'request_path': request.path, + 'request_path': quote(request.path), 'exception': exception_repr, } try: @@ -48,12 +52,14 @@ def page_not_found(request, exception, template_name=ERROR_404_TEMPLATE_NAME): if template_name != ERROR_404_TEMPLATE_NAME: # Reraise if it's a missing custom template. raise + # Render template (even though there are no substitutions) to allow + # inspecting the context in tests. template = Engine().from_string( '

    Not Found

    ' - '

    The requested URL {{ request_path }} was not found on this server.

    ') + '

    The requested resource was not found on this server.

    ') body = template.render(Context(context)) content_type = 'text/html' - return http.HttpResponseNotFound(body, content_type=content_type) + return HttpResponseNotFound(body, content_type=content_type) @requires_csrf_token @@ -70,8 +76,8 @@ def server_error(request, template_name=ERROR_500_TEMPLATE_NAME): if template_name != ERROR_500_TEMPLATE_NAME: # Reraise if it's a missing custom template. raise - return http.HttpResponseServerError('

    Server Error (500)

    ', content_type='text/html') - return http.HttpResponseServerError(template.render()) + return HttpResponseServerError('

    Server Error (500)

    ', content_type='text/html') + return HttpResponseServerError(template.render()) @requires_csrf_token @@ -88,9 +94,9 @@ def bad_request(request, exception, template_name=ERROR_400_TEMPLATE_NAME): if template_name != ERROR_400_TEMPLATE_NAME: # Reraise if it's a missing custom template. raise - return http.HttpResponseBadRequest('

    Bad Request (400)

    ', content_type='text/html') + return HttpResponseBadRequest('

    Bad Request (400)

    ', content_type='text/html') # No exception content is passed to the template, to not disclose any sensitive information. - return http.HttpResponseBadRequest(template.render()) + return HttpResponseBadRequest(template.render()) # This can be called when CsrfViewMiddleware.process_view has not run, @@ -113,7 +119,7 @@ def permission_denied(request, exception, template_name=ERROR_403_TEMPLATE_NAME) if template_name != ERROR_403_TEMPLATE_NAME: # Reraise if it's a missing custom template. raise - return http.HttpResponseForbidden('

    403 Forbidden

    ', content_type='text/html') - return http.HttpResponseForbidden( - template.render(request=request, context={'exception': force_text(exception)}) + return HttpResponseForbidden('

    403 Forbidden

    ', content_type='text/html') + return HttpResponseForbidden( + template.render(request=request, context={'exception': str(exception)}) ) diff --git a/django/views/generic/base.py b/django/views/generic/base.py index fc04bcc311db..5ed4f18ee110 100644 --- a/django/views/generic/base.py +++ b/django/views/generic/base.py @@ -1,31 +1,33 @@ -from __future__ import unicode_literals - import logging from functools import update_wrapper -from django import http from django.core.exceptions import ImproperlyConfigured +from django.http import ( + HttpResponse, HttpResponseGone, HttpResponseNotAllowed, + HttpResponsePermanentRedirect, HttpResponseRedirect, +) from django.template.response import TemplateResponse -from django.urls import NoReverseMatch, reverse -from django.utils import six +from django.urls import reverse from django.utils.decorators import classonlymethod logger = logging.getLogger('django.request') -class ContextMixin(object): +class ContextMixin: """ A default context mixin that passes the keyword arguments received by - get_context_data as the template context. + get_context_data() as the template context. """ + extra_context = None def get_context_data(self, **kwargs): - if 'view' not in kwargs: - kwargs['view'] = self + kwargs.setdefault('view', self) + if self.extra_context is not None: + kwargs.update(self.extra_context) return kwargs -class View(object): +class View: """ Intentionally simple parent class for all views. Only implements dispatch-by-method and simple sanity checking. @@ -40,14 +42,12 @@ def __init__(self, **kwargs): """ # Go through keyword arguments, and either save their values to our # instance, or raise an error. - for key, value in six.iteritems(kwargs): + for key, value in kwargs.items(): setattr(self, key, value) @classonlymethod def as_view(cls, **initkwargs): - """ - Main entry point for a request-response process. - """ + """Main entry point for a request-response process.""" for key in initkwargs: if key in cls.http_method_names: raise TypeError("You tried to pass in the %s method name as a " @@ -62,9 +62,12 @@ def view(request, *args, **kwargs): self = cls(**initkwargs) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get - self.request = request - self.args = args - self.kwargs = kwargs + self.setup(request, *args, **kwargs) + if not hasattr(self, 'request'): + raise AttributeError( + "%s instance has no 'request' attribute. Did you override " + "setup() and forget to call super()?" % cls.__name__ + ) return self.dispatch(request, *args, **kwargs) view.view_class = cls view.view_initkwargs = initkwargs @@ -77,6 +80,12 @@ def view(request, *args, **kwargs): update_wrapper(view, cls.dispatch, assigned=()) return view + def setup(self, request, *args, **kwargs): + """Initialize attributes shared by all view methods.""" + self.request = request + self.args = args + self.kwargs = kwargs + def dispatch(self, request, *args, **kwargs): # Try to dispatch to the right method; if a method doesn't exist, # defer to the error handler. Also defer to the error handler if the @@ -92,13 +101,11 @@ def http_method_not_allowed(self, request, *args, **kwargs): 'Method Not Allowed (%s): %s', request.method, request.path, extra={'status_code': 405, 'request': request} ) - return http.HttpResponseNotAllowed(self._allowed_methods()) + return HttpResponseNotAllowed(self._allowed_methods()) def options(self, request, *args, **kwargs): - """ - Handles responding to requests for the OPTIONS HTTP verb. - """ - response = http.HttpResponse() + """Handle responding to requests for the OPTIONS HTTP verb.""" + response = HttpResponse() response['Allow'] = ', '.join(self._allowed_methods()) response['Content-Length'] = '0' return response @@ -107,10 +114,8 @@ def _allowed_methods(self): return [m.upper() for m in self.http_method_names if hasattr(self, m)] -class TemplateResponseMixin(object): - """ - A mixin that can be used to render a template. - """ +class TemplateResponseMixin: + """A mixin that can be used to render a template.""" template_name = None template_engine = None response_class = TemplateResponse @@ -118,11 +123,10 @@ class TemplateResponseMixin(object): def render_to_response(self, context, **response_kwargs): """ - Returns a response, using the `response_class` for this - view, with a template rendered with the given context. + Return a response, using the `response_class` for this view, with a + template rendered with the given context. - If any keyword arguments are provided, they will be - passed to the constructor of the response class. + Pass response_kwargs to the constructor of the response class. """ response_kwargs.setdefault('content_type', self.content_type) return self.response_class( @@ -135,8 +139,8 @@ def render_to_response(self, context, **response_kwargs): def get_template_names(self): """ - Returns a list of template names to be used for the request. Must return - a list. May not be called if render_to_response is overridden. + Return a list of template names to be used for the request. Must return + a list. May not be called if render_to_response() is overridden. """ if self.template_name is None: raise ImproperlyConfigured( @@ -148,8 +152,7 @@ def get_template_names(self): class TemplateView(TemplateResponseMixin, ContextMixin, View): """ - A view that renders a template. This view will also pass into the context - any keyword arguments passed by the URLconf. + Render a template. Pass keyword arguments from the URLconf to the context. """ def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) @@ -157,9 +160,7 @@ def get(self, request, *args, **kwargs): class RedirectView(View): - """ - A view that provides a redirect on any GET request. - """ + """Provide a redirect on any GET request.""" permanent = False url = None pattern_name = None @@ -167,17 +168,14 @@ class RedirectView(View): def get_redirect_url(self, *args, **kwargs): """ - Return the URL redirect to. Keyword arguments from the - URL pattern match generating the redirect request - are provided as kwargs to this method. + Return the URL redirect to. Keyword arguments from the URL pattern + match generating the redirect request are provided as kwargs to this + method. """ if self.url: url = self.url % kwargs elif self.pattern_name: - try: - url = reverse(self.pattern_name, args=args, kwargs=kwargs) - except NoReverseMatch: - return None + url = reverse(self.pattern_name, args=args, kwargs=kwargs) else: return None @@ -190,15 +188,15 @@ def get(self, request, *args, **kwargs): url = self.get_redirect_url(*args, **kwargs) if url: if self.permanent: - return http.HttpResponsePermanentRedirect(url) + return HttpResponsePermanentRedirect(url) else: - return http.HttpResponseRedirect(url) + return HttpResponseRedirect(url) else: logger.warning( 'Gone: %s', request.path, extra={'status_code': 410, 'request': request} ) - return http.HttpResponseGone() + return HttpResponseGone() def head(self, request, *args, **kwargs): return self.get(request, *args, **kwargs) diff --git a/django/views/generic/dates.py b/django/views/generic/dates.py index 938c53853588..4380cd54645b 100644 --- a/django/views/generic/dates.py +++ b/django/views/generic/dates.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import datetime from django.conf import settings @@ -7,9 +5,8 @@ from django.db import models from django.http import Http404 from django.utils import timezone -from django.utils.encoding import force_str, force_text from django.utils.functional import cached_property -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from django.views.generic.base import View from django.views.generic.detail import ( BaseDetailView, SingleObjectTemplateResponseMixin, @@ -19,10 +16,8 @@ ) -class YearMixin(object): - """ - Mixin for views manipulating year-based data. - """ +class YearMixin: + """Mixin for views manipulating year-based data.""" year_format = '%Y' year = None @@ -34,9 +29,7 @@ def get_year_format(self): return self.year_format def get_year(self): - """ - Return the year for which this view should display data. - """ + """Return the year for which this view should display data.""" year = self.year if year is None: try: @@ -49,15 +42,11 @@ def get_year(self): return year def get_next_year(self, date): - """ - Get the next valid year. - """ + """Get the next valid year.""" return _get_next_prev(self, date, is_previous=False, period='year') def get_previous_year(self, date): - """ - Get the previous valid year. - """ + """Get the previous valid year.""" return _get_next_prev(self, date, is_previous=True, period='year') def _get_next_year(self, date): @@ -66,19 +55,18 @@ def _get_next_year(self, date): The interval is defined by start date <= item date < next start date. """ - return date.replace(year=date.year + 1, month=1, day=1) + try: + return date.replace(year=date.year + 1, month=1, day=1) + except ValueError: + raise Http404(_("Date out of range")) def _get_current_year(self, date): - """ - Return the start date of the current interval. - """ + """Return the start date of the current interval.""" return date.replace(month=1, day=1) -class MonthMixin(object): - """ - Mixin for views manipulating month-based data. - """ +class MonthMixin: + """Mixin for views manipulating month-based data.""" month_format = '%b' month = None @@ -90,9 +78,7 @@ def get_month_format(self): return self.month_format def get_month(self): - """ - Return the month for which this view should display data. - """ + """Return the month for which this view should display data.""" month = self.month if month is None: try: @@ -105,15 +91,11 @@ def get_month(self): return month def get_next_month(self, date): - """ - Get the next valid month. - """ + """Get the next valid month.""" return _get_next_prev(self, date, is_previous=False, period='month') def get_previous_month(self, date): - """ - Get the previous valid month. - """ + """Get the previous valid month.""" return _get_next_prev(self, date, is_previous=True, period='month') def _get_next_month(self, date): @@ -123,21 +105,20 @@ def _get_next_month(self, date): The interval is defined by start date <= item date < next start date. """ if date.month == 12: - return date.replace(year=date.year + 1, month=1, day=1) + try: + return date.replace(year=date.year + 1, month=1, day=1) + except ValueError: + raise Http404(_("Date out of range")) else: return date.replace(month=date.month + 1, day=1) def _get_current_month(self, date): - """ - Return the start date of the previous interval. - """ + """Return the start date of the previous interval.""" return date.replace(day=1) -class DayMixin(object): - """ - Mixin for views manipulating day-based data. - """ +class DayMixin: + """Mixin for views manipulating day-based data.""" day_format = '%d' day = None @@ -149,9 +130,7 @@ def get_day_format(self): return self.day_format def get_day(self): - """ - Return the day for which this view should display data. - """ + """Return the day for which this view should display data.""" day = self.day if day is None: try: @@ -164,15 +143,11 @@ def get_day(self): return day def get_next_day(self, date): - """ - Get the next valid day. - """ + """Get the next valid day.""" return _get_next_prev(self, date, is_previous=False, period='day') def get_previous_day(self, date): - """ - Get the previous valid day. - """ + """Get the previous valid day.""" return _get_next_prev(self, date, is_previous=True, period='day') def _get_next_day(self, date): @@ -184,16 +159,12 @@ def _get_next_day(self, date): return date + datetime.timedelta(days=1) def _get_current_day(self, date): - """ - Return the start date of the current interval. - """ + """Return the start date of the current interval.""" return date -class WeekMixin(object): - """ - Mixin for views manipulating week-based data. - """ +class WeekMixin: + """Mixin for views manipulating week-based data.""" week_format = '%U' week = None @@ -205,9 +176,7 @@ def get_week_format(self): return self.week_format def get_week(self): - """ - Return the week for which this view should display data - """ + """Return the week for which this view should display data.""" week = self.week if week is None: try: @@ -220,15 +189,11 @@ def get_week(self): return week def get_next_week(self, date): - """ - Get the next valid week. - """ + """Get the next valid week.""" return _get_next_prev(self, date, is_previous=False, period='week') def get_previous_week(self, date): - """ - Get the previous valid week. - """ + """Get the previous valid week.""" return _get_next_prev(self, date, is_previous=True, period='week') def _get_next_week(self, date): @@ -237,12 +202,13 @@ def _get_next_week(self, date): The interval is defined by start date <= item date < next start date. """ - return date + datetime.timedelta(days=7 - self._get_weekday(date)) + try: + return date + datetime.timedelta(days=7 - self._get_weekday(date)) + except OverflowError: + raise Http404(_("Date out of range")) def _get_current_week(self, date): - """ - Return the start date of the current interval. - """ + """Return the start date of the current interval.""" return date - datetime.timedelta(self._get_weekday(date)) def _get_weekday(self, date): @@ -260,24 +226,20 @@ def _get_weekday(self, date): raise ValueError("unknown week format: %s" % week_format) -class DateMixin(object): - """ - Mixin class for views manipulating date-based data. - """ +class DateMixin: + """Mixin class for views manipulating date-based data.""" date_field = None allow_future = False def get_date_field(self): - """ - Get the name of the date field to be used to filter by. - """ + """Get the name of the date field to be used to filter by.""" if self.date_field is None: raise ImproperlyConfigured("%s.date_field is required." % self.__class__.__name__) return self.date_field def get_allow_future(self): """ - Returns `True` if the view should be allowed to display objects from + Return `True` if the view should be allowed to display objects from the future. """ return self.allow_future @@ -305,7 +267,7 @@ def _make_date_lookup_arg(self, value): if self.uses_datetime_field: value = datetime.datetime.combine(value, datetime.time.min) if settings.USE_TZ: - value = timezone.make_aware(value, timezone.get_current_timezone()) + value = timezone.make_aware(value) return value def _make_single_date_lookup(self, date): @@ -329,28 +291,26 @@ def _make_single_date_lookup(self, date): class BaseDateListView(MultipleObjectMixin, DateMixin, View): - """ - Abstract base class for date-based views displaying a list of objects. - """ + """Abstract base class for date-based views displaying a list of objects.""" allow_empty = False date_list_period = 'year' def get(self, request, *args, **kwargs): self.date_list, self.object_list, extra_context = self.get_dated_items() - context = self.get_context_data(object_list=self.object_list, - date_list=self.date_list) - context.update(extra_context) + context = self.get_context_data( + object_list=self.object_list, + date_list=self.date_list, + **extra_context + ) return self.render_to_response(context) def get_dated_items(self): - """ - Obtain the list of dates and items. - """ + """Obtain the list of dates and items.""" raise NotImplementedError('A DateView must provide an implementation of get_dated_items()') def get_ordering(self): """ - Returns the field or fields to use for ordering the queryset; uses the + Return the field or fields to use for ordering the queryset; use the date field by default. """ return '-%s' % self.get_date_field() if self.ordering is None else self.ordering @@ -373,17 +333,18 @@ def get_dated_queryset(self, **lookup): if not allow_empty: # When pagination is enabled, it's better to do a cheap query # than to load the unpaginated queryset in memory. - is_empty = len(qs) == 0 if paginate_by is None else not qs.exists() + is_empty = not qs if paginate_by is None else not qs.exists() if is_empty: raise Http404(_("No %(verbose_name_plural)s available") % { - 'verbose_name_plural': force_text(qs.model._meta.verbose_name_plural) + 'verbose_name_plural': qs.model._meta.verbose_name_plural, }) return qs def get_date_list_period(self): """ - Get the aggregation period for the list of dates: 'year', 'month', or 'day'. + Get the aggregation period for the list of dates: 'year', 'month', or + 'day'. """ return self.date_list_period @@ -402,25 +363,23 @@ def get_date_list(self, queryset, date_type=None, ordering='ASC'): else: date_list = queryset.dates(date_field, date_type, ordering) if date_list is not None and not date_list and not allow_empty: - name = force_text(queryset.model._meta.verbose_name_plural) - raise Http404(_("No %(verbose_name_plural)s available") % - {'verbose_name_plural': name}) + raise Http404( + _("No %(verbose_name_plural)s available") % { + 'verbose_name_plural': queryset.model._meta.verbose_name_plural, + } + ) return date_list class BaseArchiveIndexView(BaseDateListView): """ - Base class for archives of date-based items. - - Requires a response mixin. + Base class for archives of date-based items. Requires a response mixin. """ context_object_name = 'latest' def get_dated_items(self): - """ - Return (date_list, items, extra_context) for this request. - """ + """Return (date_list, items, extra_context) for this request.""" qs = self.get_dated_queryset() date_list = self.get_date_list(qs, ordering='DESC') @@ -431,23 +390,17 @@ def get_dated_items(self): class ArchiveIndexView(MultipleObjectTemplateResponseMixin, BaseArchiveIndexView): - """ - Top-level archive of date-based items. - """ + """Top-level archive of date-based items.""" template_name_suffix = '_archive' class BaseYearArchiveView(YearMixin, BaseDateListView): - """ - List of objects published in a given year. - """ + """List of objects published in a given year.""" date_list_period = 'month' make_object_list = False def get_dated_items(self): - """ - Return (date_list, items, extra_context) for this request. - """ + """Return (date_list, items, extra_context) for this request.""" year = self.get_year() date_field = self.get_date_field() @@ -483,22 +436,16 @@ def get_make_object_list(self): class YearArchiveView(MultipleObjectTemplateResponseMixin, BaseYearArchiveView): - """ - List of objects published in a given year. - """ + """List of objects published in a given year.""" template_name_suffix = '_archive_year' class BaseMonthArchiveView(YearMixin, MonthMixin, BaseDateListView): - """ - List of objects published in a given month. - """ + """List of objects published in a given month.""" date_list_period = 'day' def get_dated_items(self): - """ - Return (date_list, items, extra_context) for this request. - """ + """Return (date_list, items, extra_context) for this request.""" year = self.get_year() month = self.get_month() @@ -524,30 +471,28 @@ def get_dated_items(self): class MonthArchiveView(MultipleObjectTemplateResponseMixin, BaseMonthArchiveView): - """ - List of objects published in a given month. - """ + """List of objects published in a given month.""" template_name_suffix = '_archive_month' class BaseWeekArchiveView(YearMixin, WeekMixin, BaseDateListView): - """ - List of objects published in a given week. - """ + """List of objects published in a given week.""" def get_dated_items(self): - """ - Return (date_list, items, extra_context) for this request. - """ + """Return (date_list, items, extra_context) for this request.""" year = self.get_year() week = self.get_week() date_field = self.get_date_field() week_format = self.get_week_format() - week_start = { - '%W': '1', - '%U': '0', - }[week_format] + week_choices = {'%W': '1', '%U': '0'} + try: + week_start = week_choices[week_format] + except KeyError: + raise ValueError('Unknown week format %r. Choices are: %s' % ( + week_format, + ', '.join(sorted(week_choices)), + )) date = _date_from_string(year, self.get_year_format(), week_start, '%w', week, week_format) @@ -569,20 +514,14 @@ def get_dated_items(self): class WeekArchiveView(MultipleObjectTemplateResponseMixin, BaseWeekArchiveView): - """ - List of objects published in a given week. - """ + """List of objects published in a given week.""" template_name_suffix = '_archive_week' class BaseDayArchiveView(YearMixin, MonthMixin, DayMixin, BaseDateListView): - """ - List of objects published on a given day. - """ + """List of objects published on a given day.""" def get_dated_items(self): - """ - Return (date_list, items, extra_context) for this request. - """ + """Return (date_list, items, extra_context) for this request.""" year = self.get_year() month = self.get_month() day = self.get_day() @@ -611,28 +550,20 @@ def _get_dated_items(self, date): class DayArchiveView(MultipleObjectTemplateResponseMixin, BaseDayArchiveView): - """ - List of objects published on a given day. - """ + """List of objects published on a given day.""" template_name_suffix = "_archive_day" class BaseTodayArchiveView(BaseDayArchiveView): - """ - List of objects published today. - """ + """List of objects published today.""" def get_dated_items(self): - """ - Return (date_list, items, extra_context) for this request. - """ + """Return (date_list, items, extra_context) for this request.""" return self._get_dated_items(datetime.date.today()) class TodayArchiveView(MultipleObjectTemplateResponseMixin, BaseTodayArchiveView): - """ - List of objects published today. - """ + """List of objects published today.""" template_name_suffix = "_archive_day" @@ -642,9 +573,7 @@ class BaseDateDetailView(YearMixin, MonthMixin, DayMixin, DateMixin, BaseDetailV standard DetailView by accepting a year/month/day in the URL. """ def get_object(self, queryset=None): - """ - Get the object this request displays. - """ + """Get the object this request displays.""" year = self.get_year() month = self.get_month() day = self.get_day() @@ -670,7 +599,7 @@ def get_object(self, queryset=None): lookup_kwargs = self._make_single_date_lookup(date) qs = qs.filter(**lookup_kwargs) - return super(BaseDetailView, self).get_object(queryset=qs) + return super().get_object(queryset=qs) class DateDetailView(SingleObjectTemplateResponseMixin, BaseDateDetailView): @@ -683,13 +612,13 @@ class DateDetailView(SingleObjectTemplateResponseMixin, BaseDateDetailView): def _date_from_string(year, year_format, month='', month_format='', day='', day_format='', delim='__'): """ - Helper: get a datetime.date object given a format string and a year, - month, and day (only year is mandatory). Raise a 404 for an invalid date. + Get a datetime.date object given a format string and a year, month, and day + (only year is mandatory). Raise a 404 for an invalid date. """ - format = delim.join((year_format, month_format, day_format)) - datestr = delim.join((year, month, day)) + format = year_format + delim + month_format + delim + day_format + datestr = str(year) + delim + str(month) + delim + str(day) try: - return datetime.datetime.strptime(force_str(datestr), format).date() + return datetime.datetime.strptime(datestr, format).date() except ValueError: raise Http404(_("Invalid date string '%(datestr)s' given format '%(format)s'") % { 'datestr': datestr, @@ -699,9 +628,9 @@ def _date_from_string(year, year_format, month='', month_format='', day='', day_ def _get_next_prev(generic_view, date, is_previous, period): """ - Helper: Get the next or the previous valid date. The idea is to allow - links on month/day views to never be 404s by never providing a date - that'll be invalid for the given view. + Get the next or the previous valid date. The idea is to allow links on + month/day views to never be 404s by never providing a date that'll be + invalid for the given view. This is a bit complicated since it handles different intervals of time, hence the coupling to generic_view. @@ -788,9 +717,7 @@ def _get_next_prev(generic_view, date, is_previous, period): def timezone_today(): - """ - Return the current date in the current time zone. - """ + """Return the current date in the current time zone.""" if settings.USE_TZ: return timezone.localdate() else: diff --git a/django/views/generic/detail.py b/django/views/generic/detail.py index 2b67ef5e5ceb..1922114598a6 100644 --- a/django/views/generic/detail.py +++ b/django/views/generic/detail.py @@ -1,15 +1,13 @@ -from __future__ import unicode_literals - from django.core.exceptions import ImproperlyConfigured from django.db import models from django.http import Http404 -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from django.views.generic.base import ContextMixin, TemplateResponseMixin, View class SingleObjectMixin(ContextMixin): """ - Provides the ability to retrieve a single object for further manipulation. + Provide the ability to retrieve a single object for further manipulation. """ model = None queryset = None @@ -21,10 +19,10 @@ class SingleObjectMixin(ContextMixin): def get_object(self, queryset=None): """ - Returns the object the view is displaying. + Return the object the view is displaying. - By default this requires `self.queryset` and a `pk` or `slug` argument - in the URLconf, but subclasses can override this to return any object. + Require `self.queryset` and a `pk` or `slug` argument in the URLconf. + Subclasses can override this to return any object. """ # Use a custom queryset if provided; this is required for subclasses # like DateDetailView @@ -44,9 +42,10 @@ def get_object(self, queryset=None): # If none of those are defined, it's an error. if pk is None and slug is None: - raise AttributeError("Generic detail view %s must be called with " - "either an object pk or a slug." - % self.__class__.__name__) + raise AttributeError( + "Generic detail view %s must be called with either an object " + "pk or a slug in the URLconf." % self.__class__.__name__ + ) try: # Get the single item from the filtered queryset @@ -60,8 +59,8 @@ def get_queryset(self): """ Return the `QuerySet` that will be used to look up the object. - Note that this method is called by the default implementation of - `get_object` and may not be called if `get_object` is overridden. + This method is called by the default implementation of get_object() and + may not be called if get_object() is overridden. """ if self.queryset is None: if self.model: @@ -77,15 +76,11 @@ def get_queryset(self): return self.queryset.all() def get_slug_field(self): - """ - Get the name of a slug field to be used to look up by slug. - """ + """Get the name of a slug field to be used to look up by slug.""" return self.slug_field def get_context_object_name(self, obj): - """ - Get the name to use for the object. - """ + """Get the name to use for the object.""" if self.context_object_name: return self.context_object_name elif isinstance(obj, models.Model): @@ -94,9 +89,7 @@ def get_context_object_name(self, obj): return None def get_context_data(self, **kwargs): - """ - Insert the single object into the context dict. - """ + """Insert the single object into the context dict.""" context = {} if self.object: context['object'] = self.object @@ -104,13 +97,11 @@ def get_context_data(self, **kwargs): if context_object_name: context[context_object_name] = self.object context.update(kwargs) - return super(SingleObjectMixin, self).get_context_data(**context) + return super().get_context_data(**context) class BaseDetailView(SingleObjectMixin, View): - """ - A base view for displaying a single object - """ + """A base view for displaying a single object.""" def get(self, request, *args, **kwargs): self.object = self.get_object() context = self.get_context_data(object=self.object) @@ -124,7 +115,7 @@ class SingleObjectTemplateResponseMixin(TemplateResponseMixin): def get_template_names(self): """ Return a list of template names to be used for the request. May not be - called if render_to_response is overridden. Returns the following list: + called if render_to_response() is overridden. Return the following list: * the value of ``template_name`` on the view (if provided) * the contents of the ``template_name_field`` field on the @@ -132,7 +123,7 @@ def get_template_names(self): * ``/.html`` """ try: - names = super(SingleObjectTemplateResponseMixin, self).get_template_names() + names = super().get_template_names() except ImproperlyConfigured: # If template_name isn't specified, it's not a problem -- # we just start with an empty list. @@ -155,7 +146,7 @@ def get_template_names(self): object_meta.model_name, self.template_name_suffix )) - elif hasattr(self, 'model') and self.model is not None and issubclass(self.model, models.Model): + elif getattr(self, 'model', None) is not None and issubclass(self.model, models.Model): names.append("%s/%s%s.html" % ( self.model._meta.app_label, self.model._meta.model_name, diff --git a/django/views/generic/edit.py b/django/views/generic/edit.py index 9b5bd3873690..ccfef9cbcdf1 100644 --- a/django/views/generic/edit.py +++ b/django/views/generic/edit.py @@ -1,7 +1,6 @@ from django.core.exceptions import ImproperlyConfigured from django.forms import models as model_forms from django.http import HttpResponseRedirect -from django.utils.encoding import force_text from django.views.generic.base import ContextMixin, TemplateResponseMixin, View from django.views.generic.detail import ( BaseDetailView, SingleObjectMixin, SingleObjectTemplateResponseMixin, @@ -9,45 +8,32 @@ class FormMixin(ContextMixin): - """ - A mixin that provides a way to show and handle a form in a request. - """ - + """Provide a way to show and handle a form in a request.""" initial = {} form_class = None success_url = None prefix = None def get_initial(self): - """ - Returns the initial data to use for forms on this view. - """ + """Return the initial data to use for forms on this view.""" return self.initial.copy() def get_prefix(self): - """ - Returns the prefix to use for forms on this view - """ + """Return the prefix to use for forms.""" return self.prefix def get_form_class(self): - """ - Returns the form class to use in this view - """ + """Return the form class to use.""" return self.form_class def get_form(self, form_class=None): - """ - Returns an instance of the form to be used in this view. - """ + """Return an instance of the form to be used in this view.""" if form_class is None: form_class = self.get_form_class() return form_class(**self.get_form_kwargs()) def get_form_kwargs(self): - """ - Returns the keyword arguments for instantiating the form. - """ + """Return the keyword arguments for instantiating the form.""" kwargs = { 'initial': self.get_initial(), 'prefix': self.get_prefix(), @@ -61,49 +47,32 @@ def get_form_kwargs(self): return kwargs def get_success_url(self): - """ - Returns the supplied success URL. - """ - if self.success_url: - # Forcing possible reverse_lazy evaluation - url = force_text(self.success_url) - else: - raise ImproperlyConfigured( - "No URL to redirect to. Provide a success_url.") - return url + """Return the URL to redirect to after processing a valid form.""" + if not self.success_url: + raise ImproperlyConfigured("No URL to redirect to. Provide a success_url.") + return str(self.success_url) # success_url may be lazy def form_valid(self, form): - """ - If the form is valid, redirect to the supplied URL. - """ + """If the form is valid, redirect to the supplied URL.""" return HttpResponseRedirect(self.get_success_url()) def form_invalid(self, form): - """ - If the form is invalid, re-render the context data with the - data-filled form and errors. - """ + """If the form is invalid, render the invalid form.""" return self.render_to_response(self.get_context_data(form=form)) def get_context_data(self, **kwargs): - """ - Insert the form into the context dict. - """ + """Insert the form into the context dict.""" if 'form' not in kwargs: kwargs['form'] = self.get_form() - return super(FormMixin, self).get_context_data(**kwargs) + return super().get_context_data(**kwargs) class ModelFormMixin(FormMixin, SingleObjectMixin): - """ - A mixin that provides a way to show and handle a modelform in a request. - """ + """Provide a way to show and handle a ModelForm in a request.""" fields = None def get_form_class(self): - """ - Returns the form class to use in this view. - """ + """Return the form class to use in this view.""" if self.fields is not None and self.form_class: raise ImproperlyConfigured( "Specifying both 'fields' and 'form_class' is not permitted." @@ -114,7 +83,7 @@ def get_form_class(self): if self.model is not None: # If a model has been explicitly provided, use it model = self.model - elif hasattr(self, 'object') and self.object is not None: + elif getattr(self, 'object', None) is not None: # If this view is operating on a single object, use # the class of that object model = self.object.__class__ @@ -132,18 +101,14 @@ def get_form_class(self): return model_forms.modelform_factory(model, fields=self.fields) def get_form_kwargs(self): - """ - Returns the keyword arguments for instantiating the form. - """ - kwargs = super(ModelFormMixin, self).get_form_kwargs() + """Return the keyword arguments for instantiating the form.""" + kwargs = super().get_form_kwargs() if hasattr(self, 'object'): kwargs.update({'instance': self.object}) return kwargs def get_success_url(self): - """ - Returns the supplied URL. - """ + """Return the URL to redirect to after processing a valid form.""" if self.success_url: url = self.success_url.format(**self.object.__dict__) else: @@ -156,27 +121,21 @@ def get_success_url(self): return url def form_valid(self, form): - """ - If the form is valid, save the associated model. - """ + """If the form is valid, save the associated model.""" self.object = form.save() - return super(ModelFormMixin, self).form_valid(form) + return super().form_valid(form) class ProcessFormView(View): - """ - A mixin that renders a form on GET and processes it on POST. - """ + """Render a form on GET and processes it on POST.""" def get(self, request, *args, **kwargs): - """ - Handles GET requests and instantiates a blank version of the form. - """ + """Handle GET requests: instantiate a blank version of the form.""" return self.render_to_response(self.get_context_data()) def post(self, request, *args, **kwargs): """ - Handles POST requests, instantiating a form instance with the passed - POST variables and then checked for validity. + Handle POST requests: instantiate a form instance with the passed + POST variables and then check if it's valid. """ form = self.get_form() if form.is_valid(): @@ -191,36 +150,31 @@ def put(self, *args, **kwargs): class BaseFormView(FormMixin, ProcessFormView): - """ - A base view for displaying a form. - """ + """A base view for displaying a form.""" class FormView(TemplateResponseMixin, BaseFormView): - """ - A view for displaying a form, and rendering a template response. - """ + """A view for displaying a form and rendering a template response.""" class BaseCreateView(ModelFormMixin, ProcessFormView): """ - Base view for creating an new object instance. + Base view for creating a new object instance. Using this base class requires subclassing to provide a response mixin. """ def get(self, request, *args, **kwargs): self.object = None - return super(BaseCreateView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def post(self, request, *args, **kwargs): self.object = None - return super(BaseCreateView, self).post(request, *args, **kwargs) + return super().post(request, *args, **kwargs) class CreateView(SingleObjectTemplateResponseMixin, BaseCreateView): """ - View for creating a new object instance, - with a response rendered by template. + View for creating a new object, with a response rendered by a template. """ template_name_suffix = '_form' @@ -233,31 +187,26 @@ class BaseUpdateView(ModelFormMixin, ProcessFormView): """ def get(self, request, *args, **kwargs): self.object = self.get_object() - return super(BaseUpdateView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def post(self, request, *args, **kwargs): self.object = self.get_object() - return super(BaseUpdateView, self).post(request, *args, **kwargs) + return super().post(request, *args, **kwargs) class UpdateView(SingleObjectTemplateResponseMixin, BaseUpdateView): - """ - View for updating an object, - with a response rendered by template. - """ + """View for updating an object, with a response rendered by a template.""" template_name_suffix = '_form' -class DeletionMixin(object): - """ - A mixin providing the ability to delete objects - """ +class DeletionMixin: + """Provide the ability to delete objects.""" success_url = None def delete(self, request, *args, **kwargs): """ - Calls the delete() method on the fetched object and then - redirects to the success URL. + Call the delete() method on the fetched object and then redirect to the + success URL. """ self.object = self.get_object() success_url = self.get_success_url() @@ -286,7 +235,7 @@ class BaseDeleteView(DeletionMixin, BaseDetailView): class DeleteView(SingleObjectTemplateResponseMixin, BaseDeleteView): """ - View for deleting an object retrieved with `self.get_object()`, - with a response rendered by template. + View for deleting an object retrieved with self.get_object(), with a + response rendered by a template. """ template_name_suffix = '_confirm_delete' diff --git a/django/views/generic/list.py b/django/views/generic/list.py index 2277ffd4d3ec..00e5e5df2b87 100644 --- a/django/views/generic/list.py +++ b/django/views/generic/list.py @@ -1,18 +1,13 @@ -from __future__ import unicode_literals - from django.core.exceptions import ImproperlyConfigured from django.core.paginator import InvalidPage, Paginator from django.db.models.query import QuerySet from django.http import Http404 -from django.utils import six -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from django.views.generic.base import ContextMixin, TemplateResponseMixin, View class MultipleObjectMixin(ContextMixin): - """ - A mixin for views manipulating multiple objects. - """ + """A mixin for views manipulating multiple objects.""" allow_empty = True queryset = None model = None @@ -46,22 +41,18 @@ def get_queryset(self): ) ordering = self.get_ordering() if ordering: - if isinstance(ordering, six.string_types): + if isinstance(ordering, str): ordering = (ordering,) queryset = queryset.order_by(*ordering) return queryset def get_ordering(self): - """ - Return the field or fields to use for ordering the queryset. - """ + """Return the field or fields to use for ordering the queryset.""" return self.ordering def paginate_queryset(self, queryset, page_size): - """ - Paginate the queryset, if needed. - """ + """Paginate the queryset, if needed.""" paginator = self.get_paginator( queryset, page_size, orphans=self.get_paginate_orphans(), allow_empty_first_page=self.get_allow_empty()) @@ -91,31 +82,27 @@ def get_paginate_by(self, queryset): def get_paginator(self, queryset, per_page, orphans=0, allow_empty_first_page=True, **kwargs): - """ - Return an instance of the paginator for this view. - """ + """Return an instance of the paginator for this view.""" return self.paginator_class( queryset, per_page, orphans=orphans, allow_empty_first_page=allow_empty_first_page, **kwargs) def get_paginate_orphans(self): """ - Returns the maximum number of orphans extend the last page by when + Return the maximum number of orphans extend the last page by when paginating. """ return self.paginate_orphans def get_allow_empty(self): """ - Returns ``True`` if the view should display empty lists, and ``False`` + Return ``True`` if the view should display empty lists and ``False`` if a 404 should be raised instead. """ return self.allow_empty def get_context_object_name(self, object_list): - """ - Get the name of the item to be used in the context. - """ + """Get the name of the item to be used in the context.""" if self.context_object_name: return self.context_object_name elif hasattr(object_list, 'model'): @@ -123,11 +110,9 @@ def get_context_object_name(self, object_list): else: return None - def get_context_data(self, **kwargs): - """ - Get the context for this view. - """ - queryset = kwargs.pop('object_list', self.object_list) + def get_context_data(self, *, object_list=None, **kwargs): + """Get the context for this view.""" + queryset = object_list if object_list is not None else self.object_list page_size = self.get_paginate_by(queryset) context_object_name = self.get_context_object_name(queryset) if page_size: @@ -148,13 +133,11 @@ def get_context_data(self, **kwargs): if context_object_name is not None: context[context_object_name] = queryset context.update(kwargs) - return super(MultipleObjectMixin, self).get_context_data(**context) + return super().get_context_data(**context) class BaseListView(MultipleObjectMixin, View): - """ - A base view for displaying a list of objects. - """ + """A base view for displaying a list of objects.""" def get(self, request, *args, **kwargs): self.object_list = self.get_queryset() allow_empty = self.get_allow_empty() @@ -166,7 +149,7 @@ def get(self, request, *args, **kwargs): if self.get_paginate_by(self.object_list) is not None and hasattr(self.object_list, 'exists'): is_empty = not self.object_list.exists() else: - is_empty = len(self.object_list) == 0 + is_empty = not self.object_list if is_empty: raise Http404(_("Empty list and '%(class_name)s.allow_empty' is False.") % { 'class_name': self.__class__.__name__, @@ -176,9 +159,7 @@ def get(self, request, *args, **kwargs): class MultipleObjectTemplateResponseMixin(TemplateResponseMixin): - """ - Mixin for responding with a template and list of objects. - """ + """Mixin for responding with a template and list of objects.""" template_name_suffix = '_list' def get_template_names(self): @@ -187,7 +168,7 @@ def get_template_names(self): a list. May not be called if render_to_response is overridden. """ try: - names = super(MultipleObjectTemplateResponseMixin, self).get_template_names() + names = super().get_template_names() except ImproperlyConfigured: # If template_name isn't specified, it's not a problem -- # we just start with an empty list. @@ -200,7 +181,13 @@ def get_template_names(self): if hasattr(self.object_list, 'model'): opts = self.object_list.model._meta names.append("%s/%s%s.html" % (opts.app_label, opts.model_name, self.template_name_suffix)) - + elif not names: + raise ImproperlyConfigured( + "%(cls)s requires either a 'template_name' attribute " + "or a get_queryset() method that returns a QuerySet." % { + 'cls': self.__class__.__name__, + } + ) return names diff --git a/django/views/i18n.py b/django/views/i18n.py index c3a813b0fe9b..f684da23dc84 100644 --- a/django/views/i18n.py +++ b/django/views/i18n.py @@ -1,34 +1,29 @@ -import importlib import itertools import json import os -import warnings +import re +from urllib.parse import unquote -from django import http from django.apps import apps from django.conf import settings +from django.http import HttpResponse, HttpResponseRedirect, JsonResponse from django.template import Context, Engine from django.urls import translate_url -from django.utils import six -from django.utils._os import upath -from django.utils.deprecation import RemovedInDjango20Warning -from django.utils.encoding import force_text from django.utils.formats import get_format -from django.utils.http import is_safe_url, urlunquote +from django.utils.http import is_safe_url from django.utils.translation import ( - LANGUAGE_SESSION_KEY, check_for_language, get_language, to_locale, + LANGUAGE_SESSION_KEY, check_for_language, get_language, ) from django.utils.translation.trans_real import DjangoTranslation from django.views.generic import View -DEFAULT_PACKAGES = ['django.conf'] LANGUAGE_QUERY_PARAMETER = 'language' def set_language(request): """ - Redirect to a given url while setting the chosen language in the - session or cookie. The url and the language code need to be + Redirect to a given URL while setting the chosen language in the session + (if enabled) and in a cookie. The URL and the language code need to be specified in the request parameters. Since this view changes how the user will see the rest of the site, it must @@ -40,34 +35,30 @@ def set_language(request): if ((next or not request.is_ajax()) and not is_safe_url(url=next, allowed_hosts={request.get_host()}, require_https=request.is_secure())): next = request.META.get('HTTP_REFERER') - if next: - next = urlunquote(next) # HTTP_REFERER may be encoded. + next = next and unquote(next) # HTTP_REFERER may be encoded. if not is_safe_url(url=next, allowed_hosts={request.get_host()}, require_https=request.is_secure()): next = '/' - response = http.HttpResponseRedirect(next) if next else http.HttpResponse(status=204) + response = HttpResponseRedirect(next) if next else HttpResponse(status=204) if request.method == 'POST': lang_code = request.POST.get(LANGUAGE_QUERY_PARAMETER) if lang_code and check_for_language(lang_code): if next: next_trans = translate_url(next, lang_code) if next_trans != next: - response = http.HttpResponseRedirect(next_trans) + response = HttpResponseRedirect(next_trans) if hasattr(request, 'session'): request.session[LANGUAGE_SESSION_KEY] = lang_code - else: - response.set_cookie( - settings.LANGUAGE_COOKIE_NAME, lang_code, - max_age=settings.LANGUAGE_COOKIE_AGE, - path=settings.LANGUAGE_COOKIE_PATH, - domain=settings.LANGUAGE_COOKIE_DOMAIN, - ) + response.set_cookie( + settings.LANGUAGE_COOKIE_NAME, lang_code, + max_age=settings.LANGUAGE_COOKIE_AGE, + path=settings.LANGUAGE_COOKIE_PATH, + domain=settings.LANGUAGE_COOKIE_DOMAIN, + ) return response def get_formats(): - """ - Returns all formats strings required for i18n to work - """ + """Return all formats strings required for i18n to work.""" FORMAT_SETTINGS = ( 'DATE_FORMAT', 'DATETIME_FORMAT', 'TIME_FORMAT', 'YEAR_MONTH_FORMAT', 'MONTH_DAY_FORMAT', 'SHORT_DATE_FORMAT', @@ -75,16 +66,7 @@ def get_formats(): 'THOUSAND_SEPARATOR', 'NUMBER_GROUPING', 'DATE_INPUT_FORMATS', 'TIME_INPUT_FORMATS', 'DATETIME_INPUT_FORMATS' ) - result = {} - for attr in FORMAT_SETTINGS: - result[attr] = get_format(attr) - formats = {} - for k, v in result.items(): - if isinstance(v, (six.string_types, int)): - formats[k] = force_text(v) - elif isinstance(v, (tuple, list)): - formats[k] = [force_text(value) for value in v] - return formats + return {attr: get_format(attr) for attr in FORMAT_SETTINGS} js_catalog_template = r""" @@ -131,7 +113,7 @@ def get_formats(): if (typeof(value) == 'undefined') { return (count == 1) ? singular : plural; } else { - return value[django.pluralidx(count)]; + return value.constructor === Array ? value[django.pluralidx(count)] : value; } }; @@ -193,154 +175,11 @@ def get_formats(): """ -def render_javascript_catalog(catalog=None, plural=None): - template = Engine().from_string(js_catalog_template) - - def indent(s): - return s.replace('\n', '\n ') - - context = Context({ - 'catalog_str': indent(json.dumps( - catalog, sort_keys=True, indent=2)) if catalog else None, - 'formats_str': indent(json.dumps( - get_formats(), sort_keys=True, indent=2)), - 'plural': plural, - }) - - return http.HttpResponse(template.render(context), 'text/javascript') - - -def get_javascript_catalog(locale, domain, packages): - app_configs = apps.get_app_configs() - allowable_packages = set(app_config.name for app_config in app_configs) - allowable_packages.update(DEFAULT_PACKAGES) - packages = [p for p in packages if p in allowable_packages] - paths = [] - # paths of requested packages - for package in packages: - p = importlib.import_module(package) - path = os.path.join(os.path.dirname(upath(p.__file__)), 'locale') - paths.append(path) - - trans = DjangoTranslation(locale, domain=domain, localedirs=paths) - trans_cat = trans._catalog - - plural = None - if '' in trans_cat: - for line in trans_cat[''].split('\n'): - if line.startswith('Plural-Forms:'): - plural = line.split(':', 1)[1].strip() - if plural is not None: - # this should actually be a compiled function of a typical plural-form: - # 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; - plural = [el.strip() for el in plural.split(';') if el.strip().startswith('plural=')][0].split('=', 1)[1] - - pdict = {} - maxcnts = {} - catalog = {} - trans_fallback_cat = trans._fallback._catalog if trans._fallback else {} - for key, value in itertools.chain(six.iteritems(trans_cat), six.iteritems(trans_fallback_cat)): - if key == '' or key in catalog: - continue - if isinstance(key, six.string_types): - catalog[key] = value - elif isinstance(key, tuple): - msgid = key[0] - cnt = key[1] - maxcnts[msgid] = max(cnt, maxcnts.get(msgid, 0)) - pdict.setdefault(msgid, {})[cnt] = value - else: - raise TypeError(key) - for k, v in pdict.items(): - catalog[k] = [v.get(i, '') for i in range(maxcnts[k] + 1)] - - return catalog, plural - - -def _get_locale(request): - language = request.GET.get(LANGUAGE_QUERY_PARAMETER) - if not (language and check_for_language(language)): - language = get_language() - return to_locale(language) - - -def _parse_packages(packages): - if packages is None: - packages = list(DEFAULT_PACKAGES) - elif isinstance(packages, six.string_types): - packages = packages.split('+') - return packages - - -def null_javascript_catalog(request, domain=None, packages=None): - """ - Returns "identity" versions of the JavaScript i18n functions -- i.e., - versions that don't actually do anything. - """ - return render_javascript_catalog() - - -def javascript_catalog(request, domain='djangojs', packages=None): - """ - Returns the selected language catalog as a javascript library. - - Receives the list of packages to check for translations in the - packages parameter either from an infodict or as a +-delimited - string from the request. Default is 'django.conf'. - - Additionally you can override the gettext domain for this view, - but usually you don't want to do that, as JavaScript messages - go to the djangojs domain. But this might be needed if you - deliver your JavaScript source from Django templates. - """ - warnings.warn( - "The javascript_catalog() view is deprecated in favor of the " - "JavaScriptCatalog view.", RemovedInDjango20Warning, stacklevel=2 - ) - locale = _get_locale(request) - packages = _parse_packages(packages) - catalog, plural = get_javascript_catalog(locale, domain, packages) - return render_javascript_catalog(catalog, plural) - - -def json_catalog(request, domain='djangojs', packages=None): - """ - Return the selected language catalog as a JSON object. - - Receives the same parameters as javascript_catalog(), but returns - a response with a JSON object of the following format: - - { - "catalog": { - # Translations catalog - }, - "formats": { - # Language formats for date, time, etc. - }, - "plural": '...' # Expression for plural forms, or null. - } - """ - warnings.warn( - "The json_catalog() view is deprecated in favor of the " - "JSONCatalog view.", RemovedInDjango20Warning, stacklevel=2 - ) - locale = _get_locale(request) - packages = _parse_packages(packages) - catalog, plural = get_javascript_catalog(locale, domain, packages) - data = { - 'catalog': catalog, - 'formats': get_formats(), - 'plural': plural, - } - return http.JsonResponse(data) - - class JavaScriptCatalog(View): """ Return the selected language catalog as a JavaScript library. - Receives the list of packages to check for translations in the `packages` + Receive the list of packages to check for translations in the `packages` kwarg either from the extra dictionary passed to the url() function or as a plus-sign delimited string from the request. Default is 'django.conf'. @@ -364,17 +203,41 @@ def get(self, request, *args, **kwargs): return self.render_to_response(context) def get_paths(self, packages): - allowable_packages = dict((app_config.name, app_config) for app_config in apps.get_app_configs()) + allowable_packages = {app_config.name: app_config for app_config in apps.get_app_configs()} app_configs = [allowable_packages[p] for p in packages if p in allowable_packages] + if len(app_configs) < len(packages): + excluded = [p for p in packages if p not in allowable_packages] + raise ValueError( + 'Invalid package(s) provided to JavaScriptCatalog: %s' % ','.join(excluded) + ) # paths of requested packages return [os.path.join(app.path, 'locale') for app in app_configs] - def get_plural(self): - plural = None + @property + def _num_plurals(self): + """ + Return the number of plurals for this catalog language, or 2 if no + plural string is available. + """ + match = re.search(r'nplurals=\s*(\d+)', self._plural_string or '') + if match: + return int(match.groups()[0]) + return 2 + + @property + def _plural_string(self): + """ + Return the plural string (including nplurals) for this catalog language, + or None if no plural string is available. + """ if '' in self.translation._catalog: for line in self.translation._catalog[''].split('\n'): if line.startswith('Plural-Forms:'): - plural = line.split(':', 1)[1].strip() + return line.split(':', 1)[1].strip() + return None + + def get_plural(self): + plural = self._plural_string if plural is not None: # This should be a compiled function of a typical plural-form: # Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : @@ -384,24 +247,24 @@ def get_plural(self): def get_catalog(self): pdict = {} - maxcnts = {} + num_plurals = self._num_plurals catalog = {} trans_cat = self.translation._catalog trans_fallback_cat = self.translation._fallback._catalog if self.translation._fallback else {} - for key, value in itertools.chain(six.iteritems(trans_cat), six.iteritems(trans_fallback_cat)): - if key == '' or key in catalog: + seen_keys = set() + for key, value in itertools.chain(trans_cat.items(), trans_fallback_cat.items()): + if key == '' or key in seen_keys: continue - if isinstance(key, six.string_types): + if isinstance(key, str): catalog[key] = value elif isinstance(key, tuple): - msgid = key[0] - cnt = key[1] - maxcnts[msgid] = max(cnt, maxcnts.get(msgid, 0)) + msgid, cnt = key pdict.setdefault(msgid, {})[cnt] = value else: raise TypeError(key) + seen_keys.add(key) for k, v in pdict.items(): - catalog[k] = [v.get(i, '') for i in range(maxcnts[k] + 1)] + catalog[k] = [v.get(i, '') for i in range(num_plurals)] return catalog def get_context_data(self, **kwargs): @@ -421,14 +284,14 @@ def indent(s): ) if context['catalog'] else None context['formats_str'] = indent(json.dumps(context['formats'], sort_keys=True, indent=2)) - return http.HttpResponse(template.render(Context(context)), 'text/javascript') + return HttpResponse(template.render(Context(context)), 'text/javascript; charset="utf-8"') class JSONCatalog(JavaScriptCatalog): """ Return the selected language catalog as a JSON object. - Receives the same parameters as JavaScriptCatalog and returns a response + Receive the same parameters as JavaScriptCatalog and return a response with a JSON object of the following format: { @@ -442,4 +305,4 @@ class JSONCatalog(JavaScriptCatalog): } """ def render_to_response(self, context, **response_kwargs): - return http.JsonResponse(context) + return JsonResponse(context) diff --git a/django/views/static.py b/django/views/static.py index 2af26621f392..90bad8db7ea2 100644 --- a/django/views/static.py +++ b/django/views/static.py @@ -2,22 +2,18 @@ Views and functions for serving static files. These are only to be used during development, and SHOULD NOT be used in a production setting. """ -from __future__ import unicode_literals - import mimetypes -import os import posixpath import re -import stat +from pathlib import Path from django.http import ( FileResponse, Http404, HttpResponse, HttpResponseNotModified, - HttpResponseRedirect, ) from django.template import Context, Engine, TemplateDoesNotExist, loader +from django.utils._os import safe_join from django.utils.http import http_date, parse_http_date -from django.utils.six.moves.urllib.parse import unquote -from django.utils.translation import ugettext as _, ugettext_lazy +from django.utils.translation import gettext as _, gettext_lazy def serve(request, path, document_root=None, show_indexes=False): @@ -36,39 +32,23 @@ def serve(request, path, document_root=None, show_indexes=False): but if you'd like to override it, you can create a template called ``static/directory_index.html``. """ - path = posixpath.normpath(unquote(path)) - path = path.lstrip('/') - newpath = '' - for part in path.split('/'): - if not part: - # Strip empty path components. - continue - drive, part = os.path.splitdrive(part) - head, part = os.path.split(part) - if part in (os.curdir, os.pardir): - # Strip '.' and '..' in path. - continue - newpath = os.path.join(newpath, part).replace('\\', '/') - if newpath and path != newpath: - return HttpResponseRedirect(newpath) - fullpath = os.path.join(document_root, newpath) - if os.path.isdir(fullpath): + path = posixpath.normpath(path).lstrip('/') + fullpath = Path(safe_join(document_root, path)) + if fullpath.is_dir(): if show_indexes: - return directory_index(newpath, fullpath) + return directory_index(path, fullpath) raise Http404(_("Directory indexes are not allowed here.")) - if not os.path.exists(fullpath): + if not fullpath.exists(): raise Http404(_('"%(path)s" does not exist') % {'path': fullpath}) # Respect the If-Modified-Since header. - statobj = os.stat(fullpath) + statobj = fullpath.stat() if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'), statobj.st_mtime, statobj.st_size): return HttpResponseNotModified() - content_type, encoding = mimetypes.guess_type(fullpath) + content_type, encoding = mimetypes.guess_type(str(fullpath)) content_type = content_type or 'application/octet-stream' - response = FileResponse(open(fullpath, 'rb'), content_type=content_type) + response = FileResponse(fullpath.open('rb'), content_type=content_type) response["Last-Modified"] = http_date(statobj.st_mtime) - if stat.S_ISREG(statobj.st_mode): - response["Content-Length"] = statobj.st_size if encoding: response["Content-Encoding"] = encoding return response @@ -79,9 +59,9 @@ def serve(request, path, document_root=None, show_indexes=False): - - - + + + {% blocktrans %}Index of {{ directory }}{% endblocktrans %} @@ -97,7 +77,7 @@ def serve(request, path, document_root=None, show_indexes=False): """ -template_translatable = ugettext_lazy("Index of %(directory)s") +template_translatable = gettext_lazy("Index of %(directory)s") def directory_index(path, fullpath): @@ -108,13 +88,17 @@ def directory_index(path, fullpath): ]) except TemplateDoesNotExist: t = Engine(libraries={'i18n': 'django.templatetags.i18n'}).from_string(DEFAULT_DIRECTORY_INDEX_TEMPLATE) + c = Context() + else: + c = {} files = [] - for f in os.listdir(fullpath): - if not f.startswith('.'): - if os.path.isdir(os.path.join(fullpath, f)): - f += '/' - files.append(f) - c = Context({ + for f in fullpath.iterdir(): + if not f.name.startswith('.'): + url = str(f.relative_to(fullpath)) + if f.is_dir(): + url += '/' + files.append(url) + c.update({ 'directory': path + '/', 'file_list': files, }) diff --git a/django/views/templates/default_urlconf.html b/django/views/templates/default_urlconf.html new file mode 100644 index 000000000000..2a2a0377c0cc --- /dev/null +++ b/django/views/templates/default_urlconf.html @@ -0,0 +1,414 @@ +{% load i18n %} + + + + + {% trans "Django: the Web framework for perfectionists with deadlines." %} + + + + + +
    + +
    +

    {% blocktrans %}View release notes for Django {{ version }}{% endblocktrans %}

    +
    +
    +
    +
    + + + + + + + + + + + + + + + + +
    +

    {% trans "The install worked successfully! Congratulations!" %}

    +

    {% blocktrans %}You are seeing this page because DEBUG=True is in your settings file and you have not configured any URLs.{% endblocktrans %}

    +
    + + + diff --git a/django/views/templates/technical_404.html b/django/views/templates/technical_404.html new file mode 100644 index 000000000000..bc0858bba2b8 --- /dev/null +++ b/django/views/templates/technical_404.html @@ -0,0 +1,79 @@ + + + + + Page not found at {{ request.path_info }} + + + + +
    +

    Page not found (404)

    + + + + + + + + + + {% if raising_view_name %} + + + + + {% endif %} +
    Request Method:{{ request.META.REQUEST_METHOD }}
    Request URL:{{ request.build_absolute_uri }}
    Raised by:{{ raising_view_name }}
    +
    +
    + {% if urlpatterns %} +

    + Using the URLconf defined in {{ urlconf }}, + Django tried these URL patterns, in this order: +

    +
      + {% for pattern in urlpatterns %} +
    1. + {% for pat in pattern %} + {{ pat.pattern }} + {% if forloop.last and pat.name %}[name='{{ pat.name }}']{% endif %} + {% endfor %} +
    2. + {% endfor %} +
    +

    + {% if request_path %} + The current path, {{ request_path }},{% else %} + The empty path{% endif %} didn't match any of these. +

    + {% else %} +

    {{ reason }}

    + {% endif %} +
    + +
    +

    + You're seeing this error because you have DEBUG = True in + your Django settings file. Change that to False, and Django + will display a standard 404 page. +

    +
    + + diff --git a/django/views/templates/technical_500.html b/django/views/templates/technical_500.html new file mode 100644 index 000000000000..b03e1ada8c0f --- /dev/null +++ b/django/views/templates/technical_500.html @@ -0,0 +1,484 @@ + + + + + + {% if exception_type %}{{ exception_type }}{% else %}Report{% endif %} + {% if request %} at {{ request.path_info }}{% endif %} + + {% if not is_email %} + + {% endif %} + + +
    +

    {% if exception_type %}{{ exception_type }}{% else %}Report{% endif %} + {% if request %} at {{ request.path_info }}{% endif %}

    +
    {% if exception_value %}{{ exception_value|force_escape }}{% else %}No exception message supplied{% endif %}
    + +{% if request %} + + + + + + + + +{% endif %} + + + + +{% if exception_type %} + + + + +{% endif %} +{% if exception_type and exception_value %} + + + + +{% endif %} +{% if lastframe %} + + + + +{% endif %} + + + + + + + + + + + + + + + + +
    Request Method:{{ request.META.REQUEST_METHOD }}
    Request URL:{{ request.get_raw_uri }}
    Django Version:{{ django_version_info }}
    Exception Type:{{ exception_type }}
    Exception Value:
    {{ exception_value|force_escape }}
    Exception Location:{{ lastframe.filename }} in {{ lastframe.function }}, line {{ lastframe.lineno }}
    Python Executable:{{ sys_executable }}
    Python Version:{{ sys_version_info }}
    Python Path:
    {{ sys_path|pprint }}
    Server time:{{server_time|date:"r"}}
    +
    +{% if unicode_hint %} +
    +

    Unicode error hint

    +

    The string that could not be encoded/decoded was: {{ unicode_hint }}

    +
    +{% endif %} +{% if template_does_not_exist %} +
    +

    Template-loader postmortem

    + {% if postmortem %} +

    Django tried loading these templates, in this order:

    + {% for entry in postmortem %} +

    Using engine {{ entry.backend.name }}:

    +
      + {% if entry.tried %} + {% for attempt in entry.tried %} +
    • {{ attempt.0.loader_name }}: {{ attempt.0.name }} ({{ attempt.1 }})
    • + {% endfor %} + {% else %} +
    • This engine did not provide a list of tried templates.
    • + {% endif %} +
    + {% endfor %} + {% else %} +

    No templates were found because your 'TEMPLATES' setting is not configured.

    + {% endif %} +
    +{% endif %} +{% if template_info %} +
    +

    Error during template rendering

    +

    In template {{ template_info.name }}, error at line {{ template_info.line }}

    +

    {{ template_info.message }}

    + + {% for source_line in template_info.source_lines %} + {% if source_line.0 == template_info.line %} + + + + {% else %} + + + {% endif %} + {% endfor %} +
    {{ source_line.0 }}{{ template_info.before }}{{ template_info.during }}{{ template_info.after }}
    {{ source_line.0 }}{{ source_line.1 }}
    +
    +{% endif %} +{% if frames %} +
    +

    Traceback {% if not is_email %} + Switch to copy-and-paste view{% endif %} +

    +
    +
      + {% for frame in frames %} + {% ifchanged frame.exc_cause %}{% if frame.exc_cause %} +
    • + {% if frame.exc_cause_explicit %} + The above exception ({{ frame.exc_cause|force_escape }}) was the direct cause of the following exception: + {% else %} + During handling of the above exception ({{ frame.exc_cause|force_escape }}), another exception occurred: + {% endif %} +

    • + {% endif %}{% endifchanged %} +
    • + {{ frame.filename }} in {{ frame.function }} + + {% if frame.context_line %} +
      + {% if frame.pre_context and not is_email %} +
        + {% for line in frame.pre_context %} +
      1. {{ line }}
      2. + {% endfor %} +
      + {% endif %} +
        +
      1. {{ frame.context_line }}
        {% if not is_email %} {% endif %}
      2. +
      + {% if frame.post_context and not is_email %} +
        + {% for line in frame.post_context %} +
      1. {{ line }}
      2. + {% endfor %} +
      + {% endif %} +
      + {% endif %} + + {% if frame.vars %} +
      + {% if is_email %} +

      Local Vars

      + {% else %} + Local vars + {% endif %} +
      + + + + + + + + + {% for var in frame.vars|dictsort:0 %} + + + + + {% endfor %} + +
      VariableValue
      {{ var.0 }}
      {{ var.1 }}
      + {% endif %} +
    • + {% endfor %} +
    +
    +
    +{% if not is_email %} +
    + + + + + +

    + +
    +
    +
    +{% endif %} +{% endif %} + +
    +

    Request information

    + +{% if request %} + {% if user_str %} +

    USER

    +

    {{ user_str }}

    + {% endif %} + +

    GET

    + {% if request.GET %} + + + + + + + + + {% for k, v in request_GET_items %} + + + + + {% endfor %} + +
    VariableValue
    {{ k }}
    {{ v|pprint }}
    + {% else %} +

    No GET data

    + {% endif %} + +

    POST

    + {% if filtered_POST_items %} + + + + + + + + + {% for k, v in filtered_POST_items %} + + + + + {% endfor %} + +
    VariableValue
    {{ k }}
    {{ v|pprint }}
    + {% else %} +

    No POST data

    + {% endif %} +

    FILES

    + {% if request.FILES %} + + + + + + + + + {% for k, v in request_FILES_items %} + + + + + {% endfor %} + +
    VariableValue
    {{ k }}
    {{ v|pprint }}
    + {% else %} +

    No FILES data

    + {% endif %} + + + + {% if request.COOKIES %} + + + + + + + + + {% for k, v in request_COOKIES_items %} + + + + + {% endfor %} + +
    VariableValue
    {{ k }}
    {{ v|pprint }}
    + {% else %} +

    No cookie data

    + {% endif %} + +

    META

    + + + + + + + + + {% for var in request.META.items|dictsort:0 %} + + + + + {% endfor %} + +
    VariableValue
    {{ var.0 }}
    {{ var.1|pprint }}
    +{% else %} +

    Request data not supplied

    +{% endif %} + +

    Settings

    +

    Using settings module {{ settings.SETTINGS_MODULE }}

    + + + + + + + + + {% for var in settings.items|dictsort:0 %} + + + + + {% endfor %} + +
    SettingValue
    {{ var.0 }}
    {{ var.1|pprint }}
    + +
    +{% if not is_email %} +
    +

    + You're seeing this error because you have DEBUG = True in your + Django settings file. Change that to False, and Django will + display a standard page generated by the handler for this status code. +

    +
    +{% endif %} + + diff --git a/django/views/templates/technical_500.txt b/django/views/templates/technical_500.txt new file mode 100644 index 000000000000..1777051906f4 --- /dev/null +++ b/django/views/templates/technical_500.txt @@ -0,0 +1,66 @@ +{% firstof exception_type 'Report' %}{% if request %} at {{ request.path_info }}{% endif %} +{% firstof exception_value 'No exception message supplied' %} +{% if request %} +Request Method: {{ request.META.REQUEST_METHOD }} +Request URL: {{ request.get_raw_uri }}{% endif %} +Django Version: {{ django_version_info }} +Python Executable: {{ sys_executable }} +Python Version: {{ sys_version_info }} +Python Path: {{ sys_path }} +Server time: {{server_time|date:"r"}} +Installed Applications: +{{ settings.INSTALLED_APPS|pprint }} +Installed Middleware: +{{ settings.MIDDLEWARE|pprint }} +{% if template_does_not_exist %}Template loader postmortem +{% if postmortem %}Django tried loading these templates, in this order: +{% for entry in postmortem %} +Using engine {{ entry.backend.name }}: +{% if entry.tried %}{% for attempt in entry.tried %} * {{ attempt.0.loader_name }}: {{ attempt.0.name }} ({{ attempt.1 }}) +{% endfor %}{% else %} This engine did not provide a list of tried templates. +{% endif %}{% endfor %} +{% else %}No templates were found because your 'TEMPLATES' setting is not configured. +{% endif %} +{% endif %}{% if template_info %} +Template error: +In template {{ template_info.name }}, error at line {{ template_info.line }} + {{ template_info.message }} +{% for source_line in template_info.source_lines %}{% if source_line.0 == template_info.line %} {{ source_line.0 }} : {{ template_info.before }} {{ template_info.during }} {{ template_info.after }}{% else %} {{ source_line.0 }} : {{ source_line.1 }}{% endif %}{% endfor %}{% endif %}{% if frames %} + +Traceback: +{% for frame in frames %}{% ifchanged frame.exc_cause %}{% if frame.exc_cause %} +{% if frame.exc_cause_explicit %}The above exception ({{ frame.exc_cause }}) was the direct cause of the following exception:{% else %}During handling of the above exception ({{ frame.exc_cause }}), another exception occurred:{% endif %} +{% endif %}{% endifchanged %} +File "{{ frame.filename }}" in {{ frame.function }} +{% if frame.context_line %} {{ frame.lineno }}. {{ frame.context_line }}{% endif %} +{% endfor %} +{% if exception_type %}Exception Type: {{ exception_type }}{% if request %} at {{ request.path_info }}{% endif %} +{% if exception_value %}Exception Value: {{ exception_value }}{% endif %}{% endif %}{% endif %} +{% if request %}Request information: +{% if user_str %}USER: {{ user_str }}{% endif %} + +GET:{% for k, v in request_GET_items %} +{{ k }} = {{ v|stringformat:"r" }}{% empty %} No GET data{% endfor %} + +POST:{% for k, v in filtered_POST_items %} +{{ k }} = {{ v|stringformat:"r" }}{% empty %} No POST data{% endfor %} + +FILES:{% for k, v in request_FILES_items %} +{{ k }} = {{ v|stringformat:"r" }}{% empty %} No FILES data{% endfor %} + +COOKIES:{% for k, v in request_COOKIES_items %} +{{ k }} = {{ v|stringformat:"r" }}{% empty %} No cookie data{% endfor %} + +META:{% for k, v in request.META.items|dictsort:0 %} +{{ k }} = {{ v|stringformat:"r" }}{% endfor %} +{% else %}Request data not supplied +{% endif %} +Settings: +Using settings module {{ settings.SETTINGS_MODULE }}{% for k, v in settings.items|dictsort:0 %} +{{ k }} = {{ v|stringformat:"r" }}{% endfor %} + +{% if not is_email %} +You're seeing this error because you have DEBUG = True in your +Django settings file. Change that to False, and Django will +display a standard page generated by the handler for this status code. +{% endif %} diff --git a/docs/Makefile b/docs/Makefile index f57db99699f6..39f84ec0e3ff 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -9,10 +9,14 @@ PAPER ?= BUILDDIR ?= _build LANGUAGE ?= +# Convert something like "en_US" to "en", because Sphinx does not recognize +# underscores. Country codes should be passed using a dash, e.g. "pt-BR". +LANGUAGEOPT = $(firstword $(subst _, ,$(LANGUAGE))) + # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -n -d $(BUILDDIR)/doctrees -D language=$(LANGUAGE) $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +ALLSPHINXOPTS = -n -d $(BUILDDIR)/doctrees -D language=$(LANGUAGEOPT) $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . diff --git a/docs/_ext/cve_role.py b/docs/_ext/cve_role.py deleted file mode 100644 index 254d3e679fed..000000000000 --- a/docs/_ext/cve_role.py +++ /dev/null @@ -1,27 +0,0 @@ -""" -An interpreted text role to link docs to CVE issues. To use: :cve:`XXXXX` -""" -from docutils import nodes, utils -from docutils.parsers.rst import roles - - -def cve_role(name, rawtext, text, lineno, inliner, options=None, content=None): - if options is None: - options = {} - - url_pattern = inliner.document.settings.env.app.config.cve_url - if url_pattern is None: - msg = inliner.reporter.warning("cve not configured: please configure cve_url in conf.py") - prb = inliner.problematic(rawtext, rawtext, msg) - return [prb], [msg] - - url = url_pattern % text - roles.set_classes(options) - node = nodes.reference(rawtext, utils.unescape('CVE-%s' % text), refuri=url, **options) - return [node], [] - - -def setup(app): - app.add_config_value('cve_url', None, 'env') - app.add_role('cve', cve_role) - return {'parallel_read_safe': True} diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py index a5d545f46b67..cc40c40cd8d6 100644 --- a/docs/_ext/djangodocs.py +++ b/docs/_ext/djangodocs.py @@ -6,15 +6,18 @@ import re from docutils import nodes -from docutils.parsers.rst import directives +from docutils.parsers.rst import Directive +from docutils.statemachine import ViewList from sphinx import addnodes from sphinx.builders.html import StandaloneHTMLBuilder +from sphinx.directives.code import CodeBlock from sphinx.domains.std import Cmdoption -from sphinx.util.compat import Directive +from sphinx.errors import ExtensionError +from sphinx.util import logging from sphinx.util.console import bold -from sphinx.util.nodes import set_source_info -from sphinx.writers.html import SmartyPantsHTMLTranslator +from sphinx.writers.html import HTMLTranslator +logger = logging.getLogger(__name__) # RE for option descriptions without a '--' prefix simple_option_desc_re = re.compile( r'([-_a-zA-Z0-9]+)(\s*.*?)(?=,\s+(?:/|-|--)|$)') @@ -41,7 +44,7 @@ def setup(app): rolename="lookup", indextemplate="pair: %s; field lookup type", ) - app.add_description_unit( + app.add_object_type( directivename="django-admin", rolename="djadmin", indextemplate="pair: %s; django-admin command", @@ -52,147 +55,19 @@ def setup(app): app.add_directive('versionadded', VersionDirective) app.add_directive('versionchanged', VersionDirective) app.add_builder(DjangoStandaloneHTMLBuilder) - - # register the snippet directive - app.add_directive('snippet', SnippetWithFilename) - # register a node for snippet directive so that the xml parser - # knows how to handle the enter/exit parsing event - app.add_node(snippet_with_filename, - html=(visit_snippet, depart_snippet_literal), - latex=(visit_snippet_latex, depart_snippet_latex), - man=(visit_snippet_literal, depart_snippet_literal), - text=(visit_snippet_literal, depart_snippet_literal), - texinfo=(visit_snippet_literal, depart_snippet_literal)) app.set_translator('djangohtml', DjangoHTMLTranslator) app.set_translator('json', DjangoHTMLTranslator) - return {'parallel_read_safe': True} - - -class snippet_with_filename(nodes.literal_block): - """ - Subclass the literal_block to override the visit/depart event handlers - """ - pass - - -def visit_snippet_literal(self, node): - """ - default literal block handler - """ - self.visit_literal_block(node) - - -def depart_snippet_literal(self, node): - """ - default literal block handler - """ - self.depart_literal_block(node) - - -def visit_snippet(self, node): - """ - HTML document generator visit handler - """ - lang = self.highlightlang - linenos = node.rawsource.count('\n') >= self.highlightlinenothreshold - 1 - fname = node['filename'] - highlight_args = node.get('highlight_args', {}) - if 'language' in node: - # code-block directives - lang = node['language'] - highlight_args['force'] = True - if 'linenos' in node: - linenos = node['linenos'] - - def warner(msg): - self.builder.warn(msg, (self.builder.current_docname, node.line)) - - highlighted = self.highlighter.highlight_block(node.rawsource, lang, - warn=warner, - linenos=linenos, - **highlight_args) - starttag = self.starttag(node, 'div', suffix='', - CLASS='highlight-%s snippet' % lang) - self.body.append(starttag) - self.body.append('
    %s
    \n''' % (fname,)) - self.body.append(highlighted) - self.body.append('\n') - raise nodes.SkipNode - - -def visit_snippet_latex(self, node): - """ - Latex document generator visit handler - """ - code = node.rawsource.rstrip('\n') - - lang = self.hlsettingstack[-1][0] - linenos = code.count('\n') >= self.hlsettingstack[-1][1] - 1 - fname = node['filename'] - highlight_args = node.get('highlight_args', {}) - if 'language' in node: - # code-block directives - lang = node['language'] - highlight_args['force'] = True - if 'linenos' in node: - linenos = node['linenos'] - - def warner(msg): - self.builder.warn(msg, (self.curfilestack[-1], node.line)) - - hlcode = self.highlighter.highlight_block(code, lang, warn=warner, - linenos=linenos, - **highlight_args) - - self.body.append( - '\n{\\colorbox[rgb]{0.9,0.9,0.9}' - '{\\makebox[\\textwidth][l]' - '{\\small\\texttt{%s}}}}\n' % ( - # Some filenames have '_', which is special in latex. - fname.replace('_', r'\_'), - ) + app.add_node( + ConsoleNode, + html=(visit_console_html, None), + latex=(visit_console_dummy, depart_console_dummy), + man=(visit_console_dummy, depart_console_dummy), + text=(visit_console_dummy, depart_console_dummy), + texinfo=(visit_console_dummy, depart_console_dummy), ) - - if self.table: - hlcode = hlcode.replace('\\begin{Verbatim}', - '\\begin{OriginalVerbatim}') - self.table.has_problematic = True - self.table.has_verbatim = True - - hlcode = hlcode.rstrip()[:-14] # strip \end{Verbatim} - hlcode = hlcode.rstrip() + '\n' - self.body.append('\n' + hlcode + '\\end{%sVerbatim}\n' % - (self.table and 'Original' or '')) - - # Prevent rawsource from appearing in output a second time. - raise nodes.SkipNode - - -def depart_snippet_latex(self, node): - """ - Latex document generator depart handler. - """ - pass - - -class SnippetWithFilename(Directive): - """ - The 'snippet' directive that allows to add the filename (optional) - of a code snippet in the document. This is modeled after CodeBlock. - """ - has_content = True - optional_arguments = 1 - option_spec = {'filename': directives.unchanged_required} - - def run(self): - code = '\n'.join(self.content) - - literal = snippet_with_filename(code, code) - if self.arguments: - literal['language'] = self.arguments[0] - literal['filename'] = self.options['filename'] - set_source_info(self, literal) - return [literal] + app.add_directive('console', ConsoleDirective) + app.connect('html-page-context', html_page_context_hook) + return {'parallel_read_safe': True} class VersionDirective(Directive): @@ -222,11 +97,15 @@ def run(self): node['type'] = self.name if self.content: self.state.nested_parse(self.content, self.content_offset, node) - env.note_versionchange(node['type'], node['version'], node, self.lineno) + try: + env.get_domain('changeset').note_changeset(node) + except ExtensionError: + # Sphinx < 1.8: Domain 'changeset' is not registered + env.note_versionchange(node['type'], node['version'], node, self.lineno) return ret -class DjangoHTMLTranslator(SmartyPantsHTMLTranslator): +class DjangoHTMLTranslator(HTMLTranslator): """ Django-specific reST to HTML tweaks. """ @@ -247,8 +126,7 @@ def visit_desc_parameterlist(self, node): self.first_param = 1 self.optional_param_level = 0 self.param_separator = node.child_text_separator - self.required_params_left = sum([isinstance(c, addnodes.desc_parameter) - for c in node.children]) + self.required_params_left = sum(isinstance(c, addnodes.desc_parameter) for c in node.children) def depart_desc_parameterlist(self, node): self.body.append(')') @@ -287,7 +165,7 @@ def visit_section(self, node): old_ids = node.get('ids', []) node['ids'] = ['s-' + i for i in old_ids] node['ids'].extend(old_ids) - SmartyPantsHTMLTranslator.visit_section(self, node) + super().visit_section(node) node['ids'] = old_ids @@ -307,8 +185,8 @@ class DjangoStandaloneHTMLBuilder(StandaloneHTMLBuilder): name = 'djangohtml' def finish(self): - super(DjangoStandaloneHTMLBuilder, self).finish() - self.info(bold("writing templatebuiltins.js...")) + super().finish() + logger.info(bold("writing templatebuiltins.js...")) xrefs = self.env.domaindata["std"]["objects"] templatebuiltins = { "ttags": [ @@ -325,3 +203,171 @@ def finish(self): fp.write('var django_template_builtins = ') json.dump(templatebuiltins, fp) fp.write(';\n') + + +class ConsoleNode(nodes.literal_block): + """ + Custom node to override the visit/depart event handlers at registration + time. Wrap a literal_block object and defer to it. + """ + tagname = 'ConsoleNode' + + def __init__(self, litblk_obj): + self.wrapped = litblk_obj + + def __getattr__(self, attr): + if attr == 'wrapped': + return self.__dict__.wrapped + return getattr(self.wrapped, attr) + + +def visit_console_dummy(self, node): + """Defer to the corresponding parent's handler.""" + self.visit_literal_block(node) + + +def depart_console_dummy(self, node): + """Defer to the corresponding parent's handler.""" + self.depart_literal_block(node) + + +def visit_console_html(self, node): + """Generate HTML for the console directive.""" + if self.builder.name in ('djangohtml', 'json') and node['win_console_text']: + # Put a mark on the document object signaling the fact the directive + # has been used on it. + self.document._console_directive_used_flag = True + uid = node['uid'] + self.body.append('''\ +
    + + + + +
    \n''' % {'id': uid}) + try: + self.visit_literal_block(node) + except nodes.SkipNode: + pass + self.body.append('
    \n') + + self.body.append('
    \n' % {'id': uid}) + win_text = node['win_console_text'] + highlight_args = {'force': True} + linenos = node.get('linenos', False) + + def warner(msg): + self.builder.warn(msg, (self.builder.current_docname, node.line)) + + highlighted = self.highlighter.highlight_block( + win_text, 'doscon', warn=warner, linenos=linenos, **highlight_args + ) + self.body.append(highlighted) + self.body.append('
    \n') + self.body.append('
    \n') + raise nodes.SkipNode + else: + self.visit_literal_block(node) + + +class ConsoleDirective(CodeBlock): + """ + A reStructuredText directive which renders a two-tab code block in which + the second tab shows a Windows command line equivalent of the usual + Unix-oriented examples. + """ + required_arguments = 0 + # The 'doscon' Pygments formatter needs a prompt like this. '>' alone + # won't do it because then it simply paints the whole command line as a + # grey comment with no highlighting at all. + WIN_PROMPT = r'...\> ' + + def run(self): + + def args_to_win(cmdline): + changed = False + out = [] + for token in cmdline.split(): + if token[:2] == './': + token = token[2:] + changed = True + elif token[:2] == '~/': + token = '%HOMEPATH%\\' + token[2:] + changed = True + elif token == 'make': + token = 'make.bat' + changed = True + if '://' not in token and 'git' not in cmdline: + out.append(token.replace('/', '\\')) + changed = True + else: + out.append(token) + if changed: + return ' '.join(out) + return cmdline + + def cmdline_to_win(line): + if line.startswith('# '): + return 'REM ' + args_to_win(line[2:]) + if line.startswith('$ # '): + return 'REM ' + args_to_win(line[4:]) + if line.startswith('$ ./manage.py'): + return 'manage.py ' + args_to_win(line[13:]) + if line.startswith('$ manage.py'): + return 'manage.py ' + args_to_win(line[11:]) + if line.startswith('$ ./runtests.py'): + return 'runtests.py ' + args_to_win(line[15:]) + if line.startswith('$ ./'): + return args_to_win(line[4:]) + if line.startswith('$ python3'): + return 'py ' + args_to_win(line[9:]) + if line.startswith('$ python'): + return 'py ' + args_to_win(line[8:]) + if line.startswith('$ '): + return args_to_win(line[2:]) + return None + + def code_block_to_win(content): + bchanged = False + lines = [] + for line in content: + modline = cmdline_to_win(line) + if modline is None: + lines.append(line) + else: + lines.append(self.WIN_PROMPT + modline) + bchanged = True + if bchanged: + return ViewList(lines) + return None + + env = self.state.document.settings.env + self.arguments = ['console'] + lit_blk_obj = super().run()[0] + + # Only do work when the djangohtml HTML Sphinx builder is being used, + # invoke the default behavior for the rest. + if env.app.builder.name not in ('djangohtml', 'json'): + return [lit_blk_obj] + + lit_blk_obj['uid'] = '%s' % env.new_serialno('console') + # Only add the tabbed UI if there is actually a Windows-specific + # version of the CLI example. + win_content = code_block_to_win(self.content) + if win_content is None: + lit_blk_obj['win_console_text'] = None + else: + self.content = win_content + lit_blk_obj['win_console_text'] = super().run()[0].rawsource + + # Replace the literal_node object returned by Sphinx's CodeBlock with + # the ConsoleNode wrapper. + return [ConsoleNode(lit_blk_obj)] + + +def html_page_context_hook(app, pagename, templatename, context, doctree): + # Put a bool on the context used to render the template. It's used to + # control inclusion of console-tabs.css and activation of the JavaScript. + # This way it's include only from HTML files rendered from reST files where + # the ConsoleDirective is used. + context['include_console_assets'] = getattr(doctree, '_console_directive_used_flag', False) diff --git a/docs/_ext/ticket_role.py b/docs/_ext/ticket_role.py deleted file mode 100644 index 809b4239b2a7..000000000000 --- a/docs/_ext/ticket_role.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -An interpreted text role to link docs to Trac tickets. - -To use: :ticket:`XXXXX` - -Based on code from psycopg2 by Daniele Varrazzo. -""" -from docutils import nodes, utils -from docutils.parsers.rst import roles - - -def ticket_role(name, rawtext, text, lineno, inliner, options=None, content=None): - if options is None: - options = {} - try: - num = int(text.replace('#', '')) - except ValueError: - msg = inliner.reporter.error( - "ticket number must be... a number, got '%s'" % text) - prb = inliner.problematic(rawtext, rawtext, msg) - return [prb], [msg] - - url_pattern = inliner.document.settings.env.app.config.ticket_url - if url_pattern is None: - msg = inliner.reporter.warning( - "ticket not configured: please configure ticket_url in conf.py") - prb = inliner.problematic(rawtext, rawtext, msg) - return [prb], [msg] - - url = url_pattern % num - roles.set_classes(options) - node = nodes.reference(rawtext, '#' + utils.unescape(text), refuri=url, **options) - return [node], [] - - -def setup(app): - app.add_config_value('ticket_url', None, 'env') - app.add_role('ticket', ticket_role) - return {'parallel_read_safe': True} diff --git a/docs/_theme/djangodocs-epub/static/epub.css b/docs/_theme/djangodocs-epub/static/epub.css index b74af5e9bf0d..7db68b53fb1a 100644 --- a/docs/_theme/djangodocs-epub/static/epub.css +++ b/docs/_theme/djangodocs-epub/static/epub.css @@ -29,15 +29,14 @@ pre { } /* Header for some code blocks. */ -.snippet-filename { +.code-block-caption { background-color: #393939; color: white; margin: 0; padding: 0.5em; font: bold 90% monospace; } -.snippet-filename + .highlight > pre, -.snippet-filename + pre { +.literal-block-wrapper pre { margin-top: 0; } diff --git a/docs/_theme/djangodocs/layout.html b/docs/_theme/djangodocs/layout.html index f90030852c75..acf83a47cdaf 100644 --- a/docs/_theme/djangodocs/layout.html +++ b/docs/_theme/djangodocs/layout.html @@ -53,8 +53,27 @@ }); }); })(jQuery); +{%- if include_console_assets -%} +(function($) { + $(document).ready(function() { + $(".c-tab-unix").on("click", function() { + $("section.c-content-unix").show(); + $("section.c-content-win").hide(); + $(".c-tab-unix").prop("checked", true); + }); + $(".c-tab-win").on("click", function() { + $("section.c-content-win").show(); + $("section.c-content-unix").hide(); + $(".c-tab-win").prop("checked", true); + }); + }); +})(jQuery); +{%- endif -%} {% endif %} +{%- if include_console_assets -%} + +{%- endif -%} {% endblock %} {% block document %} diff --git a/docs/_theme/djangodocs/static/console-tabs.css b/docs/_theme/djangodocs/static/console-tabs.css new file mode 100644 index 000000000000..c13ec7b1ac48 --- /dev/null +++ b/docs/_theme/djangodocs/static/console-tabs.css @@ -0,0 +1,46 @@ +@import url("{{ pathto('_static/fontawesome/css/fa-brands.min.css', 1) }}"); + +.console-block { + text-align: right; +} + +.console-block *:before, +.console-block *:after { + box-sizing: border-box; +} + +.console-block > section { + display: none; + text-align: left; +} + +.console-block > input.c-tab-unix, +.console-block > input.c-tab-win { + display: none; +} + +.console-block > label { + display: inline-block; + padding: 4px 8px; + font-weight: normal; + text-align: center; + color: #bbb; + border: 1px solid transparent; + font-family: fontawesome; +} + +.console-block > input:checked + label { + color: #555; + border: 1px solid #ddd; + border-top: 2px solid #ab5603; + border-bottom: 1px solid #fff; +} + +.console-block > .c-tab-unix:checked ~ .c-content-unix, +.console-block > .c-tab-win:checked ~ .c-content-win { + display: block; +} + +.console-block pre { + margin-top: 0px; +} diff --git a/docs/_theme/djangodocs/static/djangodocs.css b/docs/_theme/djangodocs/static/djangodocs.css index 22fce2a15807..1a397f44c0f6 100644 --- a/docs/_theme/djangodocs/static/djangodocs.css +++ b/docs/_theme/djangodocs/static/djangodocs.css @@ -43,11 +43,12 @@ div.nav { margin: 0; font-size: 11px; text-align: right; color: #487858;} /*** basic styles ***/ dd { margin-left:15px; } -h1,h2,h3,h4 { margin-top:1em; font-family:"Trebuchet MS",sans-serif; font-weight:normal; } +h1,h2,h3,h4,h5 { margin-top:1em; font-family:"Trebuchet MS",sans-serif; font-weight:normal; } h1 { font-size:218%; margin-top:0.6em; margin-bottom:.4em; line-height:1.1em; } h2 { font-size:175%; margin-bottom:.6em; line-height:1.2em; color:#092e20; } h3 { font-size:150%; font-weight:bold; margin-bottom:.2em; color:#487858; } h4 { font-size:125%; font-weight:bold; margin-top:1.5em; margin-bottom:3px; } +h5 { font-size:110%; font-weight:bold; margin-top:1em; margin-bottom:3px; } div.figure { text-align: center; } div.figure p.caption { font-size:1em; margin-top:0; margin-bottom:1.5em; color: #555;} hr { color:#ccc; background-color:#ccc; height:1px; border:0; } @@ -100,9 +101,8 @@ pre { font-size:small; background:#E0FFB8; border:1px solid #94da3a; border-widt dt .literal, table .literal { background:none; } #bd a.reference { text-decoration: none; } #bd a.reference tt.literal { border-bottom: 1px #234f32 dotted; } -div.snippet-filename { color: white; background-color: #234F32; margin: 0; padding: 2px 5px; width: 100%; font-family: monospace; font-size: small; line-height: 1.3em; } -div.snippet-filename + div.highlight > pre { margin-top: 0; } -div.snippet-filename + pre { margin-top: 0; } +div.code-block-caption { color: white; background-color: #234F32; margin: 0; padding: 2px 5px; width: 100%; font-family: monospace; font-size: small; line-height: 1.3em; } +div.literal-block-wrapper pre { margin-top: 0; } /* Restore colors of pygments hyperlinked code */ #bd .highlight .k a:link, #bd .highlight .k a:visited { color: #000000; text-decoration: none; border-bottom: 1px dotted #000000; } diff --git a/docs/_theme/djangodocs/static/fontawesome/LICENSE.txt b/docs/_theme/djangodocs/static/fontawesome/LICENSE.txt new file mode 100644 index 000000000000..28c1c4bc730d --- /dev/null +++ b/docs/_theme/djangodocs/static/fontawesome/LICENSE.txt @@ -0,0 +1,34 @@ +Font Awesome Free License +------------------------- + +Font Awesome Free is free, open source, and GPL friendly. You can use it for +commercial projects, open source projects, or really almost whatever you want. +Full Font Awesome Free license: https://fontawesome.com/license. + +# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/) +In the Font Awesome Free download, the CC BY 4.0 license applies to all icons +packaged as SVG and JS file types. + +# Fonts: SIL OFL 1.1 License (https://scripts.sil.org/OFL) +In the Font Awesome Free download, the SIL OLF license applies to all icons +packaged as web and desktop font files. + +# Code: MIT License (https://opensource.org/licenses/MIT) +In the Font Awesome Free download, the MIT license applies to all non-font and +non-icon files. + +# Attribution +Attribution is required by MIT, SIL OLF, and CC BY licenses. Downloaded Font +Awesome Free files already contain embedded comments with sufficient +attribution, so you shouldn't need to do anything additional when using these +files normally. + +We've kept attribution comments terse, so we ask that you do not actively work +to remove them from files, especially code. They're a great way for folks to +learn about Font Awesome. + +# Brand Icons +All brand icons are trademarks of their respective owners. The use of these +trademarks does not indicate endorsement of the trademark holder by Font +Awesome, nor vice versa. **Please do not use brand logos for any purpose except +to represent the company, product, or service to which they refer.** diff --git a/docs/_theme/djangodocs/static/fontawesome/README.md b/docs/_theme/djangodocs/static/fontawesome/README.md new file mode 100644 index 000000000000..72a373e34829 --- /dev/null +++ b/docs/_theme/djangodocs/static/fontawesome/README.md @@ -0,0 +1,7 @@ +# Font Awesome 5.0.4 + +Thanks for downloading Font Awesome! We're so excited you're here. + +Our documentation is available online. Just head here: + +https://fontawesome.com diff --git a/docs/_theme/djangodocs/static/fontawesome/css/fa-brands.min.css b/docs/_theme/djangodocs/static/fontawesome/css/fa-brands.min.css new file mode 100644 index 000000000000..244a01ef9833 --- /dev/null +++ b/docs/_theme/djangodocs/static/fontawesome/css/fa-brands.min.css @@ -0,0 +1,5 @@ +/*! + * Font Awesome Free 5.0.4 by @fontawesome - http://fontawesome.com + * License - http://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +@font-face{font-family:Font Awesome\ 5 Brands;font-style:normal;font-weight:400;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:Font Awesome\ 5 Brands} \ No newline at end of file diff --git a/docs/_theme/djangodocs/static/fontawesome/webfonts/fa-brands-400.eot b/docs/_theme/djangodocs/static/fontawesome/webfonts/fa-brands-400.eot new file mode 100644 index 000000000000..45f12a121fd3 Binary files /dev/null and b/docs/_theme/djangodocs/static/fontawesome/webfonts/fa-brands-400.eot differ diff --git a/docs/_theme/djangodocs/static/fontawesome/webfonts/fa-brands-400.svg b/docs/_theme/djangodocs/static/fontawesome/webfonts/fa-brands-400.svg new file mode 100644 index 000000000000..2f26609a1ab8 --- /dev/null +++ b/docs/_theme/djangodocs/static/fontawesome/webfonts/fa-brands-400.svg @@ -0,0 +1,996 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/_theme/djangodocs/static/fontawesome/webfonts/fa-brands-400.ttf b/docs/_theme/djangodocs/static/fontawesome/webfonts/fa-brands-400.ttf new file mode 100644 index 000000000000..ed62125e4043 Binary files /dev/null and b/docs/_theme/djangodocs/static/fontawesome/webfonts/fa-brands-400.ttf differ diff --git a/docs/_theme/djangodocs/static/fontawesome/webfonts/fa-brands-400.woff b/docs/_theme/djangodocs/static/fontawesome/webfonts/fa-brands-400.woff new file mode 100644 index 000000000000..dc90ab137a7f Binary files /dev/null and b/docs/_theme/djangodocs/static/fontawesome/webfonts/fa-brands-400.woff differ diff --git a/docs/_theme/djangodocs/static/fontawesome/webfonts/fa-brands-400.woff2 b/docs/_theme/djangodocs/static/fontawesome/webfonts/fa-brands-400.woff2 new file mode 100644 index 000000000000..d14f86eb8622 Binary files /dev/null and b/docs/_theme/djangodocs/static/fontawesome/webfonts/fa-brands-400.woff2 differ diff --git a/docs/conf.py b/docs/conf.py index 5cba4e50bae8..11d637cb5c09 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Django documentation build configuration file, created by # sphinx-quickstart on Thu Mar 27 09:06:53 2008. # @@ -11,8 +9,6 @@ # All configuration values have a default; values that are commented out # serve to show the default. -from __future__ import unicode_literals - import sys from os.path import abspath, dirname, join @@ -36,16 +32,15 @@ # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -needs_sphinx = '1.3' # Actually 1.3.4, but micro versions aren't supported here. +needs_sphinx = '1.6.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ "djangodocs", + 'sphinx.ext.extlinks', "sphinx.ext.intersphinx", "sphinx.ext.viewcode", - "ticket_role", - "cve_role", ] # Spelling check needs an additional module that is not installed by default. @@ -81,7 +76,7 @@ # built documents. # # The short X.Y version. -version = '1.11' +version = '2.2' # The full version, including alpha/beta/rc tags. try: from django import VERSION, get_version @@ -97,7 +92,15 @@ def django_release(): release = django_release() # The "development version" of Django -django_next_version = '1.11' +django_next_version = '3.0' + +extlinks = { + 'commit': ('https://github.com/django/django/commit/%s', ''), + 'cve': ('https://nvd.nist.gov/view/vuln/detail?vulnId=%s', 'CVE-'), + # A file or directory. GitHub redirects from blob to tree if needed. + 'source': ('https://github.com/django/django/blob/stable/' + version + '.x/%s', ''), + 'ticket': ('https://code.djangoproject.com/ticket/%s', '#'), +} # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -114,7 +117,7 @@ def django_release(): # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ['_build', '_theme'] # The reST default role (used for this markup: `text`) to use for all documents. # default_role = None @@ -137,9 +140,7 @@ def django_release(): # branch, which is located at this URL. intersphinx_mapping = { 'python': ('https://docs.python.org/3/', None), - 'sphinx': ('http://sphinx-doc.org/', None), - 'six': ('https://pythonhosted.org/six/', None), - 'formtools': ('https://django-formtools.readthedocs.io/en/latest/', None), + 'sphinx': ('http://www.sphinx-doc.org/en/master/', None), 'psycopg2': ('http://initd.org/psycopg/docs/', None), } @@ -188,10 +189,6 @@ def django_release(): # using the given strftime format. html_last_updated_fmt = '%b %d, %Y' -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -html_use_smartypants = True - # Content template for the index page. # html_index = '' @@ -292,7 +289,7 @@ def django_release(): 'Utility script for the Django Web framework', ['Django Software Foundation'], 1 -), ] +)] # -- Options for Texinfo output ------------------------------------------------ @@ -373,7 +370,3 @@ def django_release(): # If false, no index is generated. # epub_use_index = True - -# -- custom extension options -------------------------------------------------- -cve_url = 'https://web.nvd.nist.gov/view/vuln/detail?vulnId=%s' -ticket_url = 'https://code.djangoproject.com/ticket/%s' diff --git a/docs/faq/admin.txt b/docs/faq/admin.txt index 26ad08374fa0..f1c6f61ebab1 100644 --- a/docs/faq/admin.txt +++ b/docs/faq/admin.txt @@ -62,7 +62,7 @@ Some objects aren't appearing in the admin. Inconsistent row counts may be caused by missing foreign key values or a foreign key field incorrectly set to :attr:`null=False `. If you have a record with a -:class:`~django.db.models.ForeignKey` pointing to a non-existent object and +:class:`~django.db.models.ForeignKey` pointing to a nonexistent object and that foreign key is included is :attr:`~django.contrib.admin.ModelAdmin.list_display`, the record will not be shown in the admin changelist because the Django model is declaring an diff --git a/docs/faq/general.txt b/docs/faq/general.txt index f7043c5f3c3f..9dc4d1b0d42d 100644 --- a/docs/faq/general.txt +++ b/docs/faq/general.txt @@ -42,7 +42,7 @@ Django is pronounced **JANG**-oh. Rhymes with FANG-oh. The "D" is silent. We've also recorded an `audio clip of the pronunciation`_. .. _Django Reinhardt: https://en.wikipedia.org/wiki/Django_Reinhardt -.. _audio clip of the pronunciation: http://red-bean.com/~adrian/django_pronunciation.mp3 +.. _audio clip of the pronunciation: https://www.red-bean.com/~adrian/django_pronunciation.mp3 Is Django stable? ================= @@ -69,7 +69,21 @@ Who's behind this? Django was originally developed at World Online, the Web department of a newspaper in Lawrence, Kansas, USA. Django's now run by an international -:doc:`team of volunteers `. +`team of volunteers `_. + +How is Django licensed? +======================= + +Django is distributed under :source:`the 3-clause BSD license `. This +is an open source license granting broad permissions to modify and redistribute +Django. + +Why does Django include Python's license file? +============================================== + +Django includes code from the Python standard library. Python is distributed +under a permissive open source license. :source:`A copy of the Python license +` is included with Django for compliance with Python's terms. Which sites use Django? ======================= @@ -122,9 +136,8 @@ to add a given feature to Django. Why did you write all of Django from scratch, instead of using other Python libraries? ====================================================================================== -When Django was originally written a couple of years ago, Adrian and Simon -spent quite a bit of time exploring the various Python Web frameworks -available. +When Django was originally written, Adrian and Simon spent quite a bit of time +exploring the various Python Web frameworks available. In our opinion, none of them were completely up to snuff. @@ -169,15 +182,13 @@ The Django docs are available in the ``docs`` directory of each Django tarball release. These docs are in reST (reStructuredText) format, and each text file corresponds to a Web page on the official Django site. -Because the documentation is `stored in revision control`_, you can browse -documentation changes just like you can browse code changes. +Because the documentation is :source:`stored in revision control `, you +can browse documentation changes just like you can browse code changes. Technically, the docs on Django's site are generated from the latest development versions of those reST documents, so the docs on the Django site may offer more information than the docs that come with the latest Django release. -.. _stored in revision control: https://github.com/django/django/tree/master/docs/ - How do I cite Django? ===================== @@ -204,4 +215,4 @@ If you need a name, just use "Django", without any tagline. If you need a publication date, use the year of release of the version you're referencing (e.g., 2013 for v1.5) -.. _APA style: http://www.apastyle.org +.. _APA style: https://www.apastyle.org diff --git a/docs/faq/index.txt b/docs/faq/index.txt index ffa5a7d16447..d7ef5f69aeb8 100644 --- a/docs/faq/index.txt +++ b/docs/faq/index.txt @@ -12,4 +12,4 @@ Django FAQ models admin contributing - troubleshooting \ No newline at end of file + troubleshooting diff --git a/docs/faq/install.txt b/docs/faq/install.txt index ea27b11db802..5f322bc7e639 100644 --- a/docs/faq/install.txt +++ b/docs/faq/install.txt @@ -30,13 +30,13 @@ popular alternatives. If you want to use Django with a database, which is probably the case, you'll also need a database engine. PostgreSQL_ is recommended, because we're -PostgreSQL fans, and MySQL_, `SQLite 3`_, and Oracle_ are also supported. +PostgreSQL fans, and MySQL_, `SQLite`_, and Oracle_ are also supported. .. _Python: https://www.python.org/ .. _PostgreSQL: https://www.postgresql.org/ .. _MySQL: https://www.mysql.com/ -.. _`SQLite 3`: https://www.sqlite.org/ -.. _Oracle: http://www.oracle.com/ +.. _`SQLite`: https://www.sqlite.org/ +.. _Oracle: https://www.oracle.com/ .. _faq-python-version-support: @@ -46,10 +46,10 @@ What Python version can I use with Django? ============== =============== Django version Python versions ============== =============== -1.8 2.7, 3.2 (until the end of 2016), 3.3, 3.4, 3.5 -1.9, 1.10 2.7, 3.4, 3.5 -1.11 2.7, 3.4, 3.5, 3.6 -2.0 3.5+ +1.11 2.7, 3.4, 3.5, 3.6, 3.7 (added in 1.11.17) +2.0 3.4, 3.5, 3.6, 3.7 +2.1 3.5, 3.6, 3.7 +2.2 3.5, 3.6, 3.7, 3.8 (added in 2.2.8) ============== =============== For each version of Python, only the latest micro release (A.B.C) is officially @@ -65,18 +65,11 @@ is the last version to support Python 3.3. What Python version should I use with Django? ============================================= -As of Django 1.6, Python 3 support is considered stable and you can safely use -it in production. See also :doc:`/topics/python3`. However, the community is -still in the process of migrating third-party packages and applications to -Python 3. - -If you're starting a new project, and the dependencies you plan to use work on -Python 3, you should use Python 3. If they don't, consider contributing to the -porting efforts, or stick to Python 2. +Python 3 is recommended. Django 1.11 is the last version to support Python 2.7. +Support for Python 2.7 and Django 1.11 ends in 2020. Since newer versions of Python are often faster, have more features, and are -better supported, all else being equal, we recommend that you use the latest -2.x.y or 3.x.y release. +better supported, the latest version of Python 3 is recommended. You don't lose anything in Django by using an older release, but you don't take advantage of the improvements and optimizations in newer Python releases. diff --git a/docs/faq/models.txt b/docs/faq/models.txt index c6a7f28e239d..2aa211e17734 100644 --- a/docs/faq/models.txt +++ b/docs/faq/models.txt @@ -68,13 +68,12 @@ Does Django support NoSQL databases? ==================================== NoSQL databases are not officially supported by Django itself. There are, -however, a number of side project and forks which allow NoSQL functionality in -Django, like `Django non-rel`_. +however, a number of side projects and forks which allow NoSQL functionality in +Django. -You can also take a look on `the wiki page`_ which discusses some alternatives. +You can take a look on `the wiki page`_ which discusses some projects. -.. _`Django non-rel`: http://django-nonrel.org/ -.. _`the wiki page`: https://code.djangoproject.com/wiki/NoSqlSupport +.. _the wiki page: https://code.djangoproject.com/wiki/NoSqlSupport How do I add database-specific options to my CREATE TABLE statements, such as specifying MyISAM as the table type? ================================================================================================================== diff --git a/docs/faq/troubleshooting.txt b/docs/faq/troubleshooting.txt index 1b2efd552fdf..f90d0e8e6ee2 100644 --- a/docs/faq/troubleshooting.txt +++ b/docs/faq/troubleshooting.txt @@ -11,12 +11,12 @@ Problems running ``django-admin`` ================================= "command not found: `django-admin`" ------------------------------------- +----------------------------------- :doc:`django-admin ` should be on your system path if you -installed Django via ``python setup.py``. If it's not on your path, you can -find it in ``site-packages/django/bin``, where ``site-packages`` is a directory -within your Python installation. Consider symlinking to :doc:`django-admin +installed Django via ``pip``. If it's not on your path, you can find it in +``site-packages/django/bin``, where ``site-packages`` is a directory within +your Python installation. Consider symlinking to :doc:`django-admin ` from some place on your path, such as :file:`/usr/local/bin`. @@ -24,12 +24,12 @@ If ``django-admin`` doesn't work but ``django-admin.py`` does, you're probably using a version of Django that doesn't match the version of this documentation. ``django-admin`` is new in Django 1.7. -Mac OS X permissions --------------------- +macOS permissions +----------------- -If you're using Mac OS X, you may see the message "permission denied" when +If you're using macOS, you may see the message "permission denied" when you try to run ``django-admin``. This is because, on Unix-based systems like -OS X, a file must be marked as "executable" before it can be run as a program. +macOS, a file must be marked as "executable" before it can be run as a program. To do this, open Terminal.app and navigate (using the ``cd`` command) to the directory where :doc:`django-admin ` is installed, then run the command ``sudo chmod +x django-admin``. @@ -55,15 +55,6 @@ pitfalls producing this error: case, please refer to your system documentation to learn how you can change this to a UTF-8 locale. -* You created raw bytestrings, which is easy to do on Python 2:: - - my_string = 'café' - - Either use the ``u''`` prefix or even better, add the - ``from __future__ import unicode_literals`` line at the top of your file - so that your code will be compatible with Python 3.2 which doesn't support - the ``u''`` prefix. - Related resources: * :doc:`Unicode in Django ` diff --git a/docs/howto/auth-remote-user.txt b/docs/howto/auth-remote-user.txt index b695fbd6478c..fc16647098fb 100644 --- a/docs/howto/auth-remote-user.txt +++ b/docs/howto/auth-remote-user.txt @@ -13,7 +13,7 @@ Windows Authentication or Apache and `mod_authnz_ldap`_, `CAS`_, `Cosign`_, .. _CAS: https://www.apereo.org/projects/cas .. _Cosign: http://weblogin.org .. _WebAuth: https://www.stanford.edu/services/webauth/ -.. _mod_auth_sspi: http://sourceforge.net/projects/mod-auth-sspi +.. _mod_auth_sspi: https://sourceforge.net/projects/mod-auth-sspi When the Web server takes care of authentication it typically sets the ``REMOTE_USER`` environment variable for use in the underlying application. In @@ -75,10 +75,6 @@ regardless of ``AUTHENTICATION_BACKENDS``. :class:`~django.contrib.auth.backends.AllowAllUsersRemoteUserBackend` if you want to allow them to. - .. versionchanged:: 1.10 - - In older versions, inactive users weren't rejected as described above. - If your authentication mechanism uses a custom HTTP header and not ``REMOTE_USER``, you can subclass ``RemoteUserMiddleware`` and set the ``header`` attribute to the desired ``request.META`` key. For example:: diff --git a/docs/howto/custom-lookups.txt b/docs/howto/custom-lookups.txt index 0b1cd717b9ef..1e73d329e791 100644 --- a/docs/howto/custom-lookups.txt +++ b/docs/howto/custom-lookups.txt @@ -109,7 +109,7 @@ or where it did not exceed a certain amount functionality which is possible in a database backend independent manner, and without duplicating functionality already in Django. -We will start by writing a ``AbsoluteValue`` transformer. This will use the SQL +We will start by writing an ``AbsoluteValue`` transformer. This will use the SQL function ``ABS()`` to transform the value before comparison:: from django.db.models import Transform @@ -138,6 +138,21 @@ SQL:: Note that in case there is no other lookup specified, Django interprets ``change__abs=27`` as ``change__abs__exact=27``. +This also allows the result to be used in ``ORDER BY`` and ``DISTINCT ON`` +clauses. For example ``Experiment.objects.order_by('change__abs')`` generates:: + + SELECT ... ORDER BY ABS("experiments"."change") ASC + +And on databases that support distinct on fields (such as PostgreSQL), +``Experiment.objects.distinct('change__abs')`` generates:: + + SELECT ... DISTINCT ON ABS("experiments"."change") + +.. versionchanged:: 2.1 + + Ordering and distinct support as described in the last two paragraphs was + added. + When looking for which lookups are allowable after the ``Transform`` has been applied, Django uses the ``output_field`` attribute. We didn't need to specify this here as it didn't change, but supposing we were applying ``AbsoluteValue`` @@ -257,10 +272,10 @@ operator. (Note that in reality almost all databases support both, including all the official databases supported by Django). We can change the behavior on a specific backend by creating a subclass of -``NotEqual`` with a ``as_mysql`` method:: +``NotEqual`` with an ``as_mysql`` method:: class MySQLNotEqual(NotEqual): - def as_mysql(self, compiler, connection): + def as_mysql(self, compiler, connection, **extra_context): lhs, lhs_params = self.process_lhs(compiler, connection) rhs, rhs_params = self.process_rhs(compiler, connection) params = lhs_params + rhs_params @@ -294,7 +309,7 @@ would override ``get_lookup`` with something like:: pass else: return get_coordinate_lookup(dimension) - return super(CoordinatesField, self).get_lookup(lookup_name) + return super().get_lookup(lookup_name) You would then define ``get_coordinate_lookup`` appropriately to return a ``Lookup`` subclass which handles the relevant value of ``dimension``. diff --git a/docs/howto/custom-management-commands.txt b/docs/howto/custom-management-commands.txt index e6482a0da823..7524463fb57b 100644 --- a/docs/howto/custom-management-commands.txt +++ b/docs/howto/custom-management-commands.txt @@ -18,18 +18,12 @@ directory whose name doesn't begin with an underscore. For example:: __init__.py models.py management/ - __init__.py commands/ - __init__.py _private.py closepoll.py tests.py views.py -On Python 2, be sure to include ``__init__.py`` files in both the -``management`` and ``management/commands`` directories as done above or your -command will not be detected. - In this example, the ``closepoll`` command will be made available to any project that includes the ``polls`` application in :setting:`INSTALLED_APPS`. @@ -55,10 +49,10 @@ look like this:: help = 'Closes the specified poll for voting' def add_arguments(self, parser): - parser.add_argument('poll_id', nargs='+', type=int) + parser.add_argument('poll_ids', nargs='+', type=int) def handle(self, *args, **options): - for poll_id in options['poll_id']: + for poll_id in options['poll_ids']: try: poll = Poll.objects.get(pk=poll_id) except Poll.DoesNotExist: @@ -83,12 +77,12 @@ look like this:: self.stdout.write("Unterminated line", ending='') The new custom command can be called using ``python manage.py closepoll -``. +``. The ``handle()`` method takes one or more ``poll_ids`` and sets ``poll.opened`` to ``False`` for each one. If the user referenced any nonexistent polls, a :exc:`CommandError` is raised. The ``poll.opened`` attribute does not exist in -the :doc:`tutorial` and was added to +the :doc:`tutorial` and was added to ``polls.models.Question`` for this example. .. _custom-commands-options: @@ -103,14 +97,12 @@ options can be added in the :meth:`~BaseCommand.add_arguments` method like this: class Command(BaseCommand): def add_arguments(self, parser): # Positional arguments - parser.add_argument('poll_id', nargs='+', type=int) + parser.add_argument('poll_ids', nargs='+', type=int) # Named (optional) arguments parser.add_argument( '--delete', action='store_true', - dest='delete', - default=False, help='Delete poll instead of closing it', ) @@ -133,52 +125,30 @@ such as :option:`--verbosity` and :option:`--traceback`. Management commands and locales =============================== -By default, the :meth:`BaseCommand.execute` method deactivates translations -because some commands shipped with Django perform several tasks (for example, -user-facing content rendering and database population) that require a -project-neutral string language. +By default, management commands are executed with the current active locale. -If, for some reason, your custom management command needs to use a fixed locale, -you should manually activate and deactivate it in your -:meth:`~BaseCommand.handle` method using the functions provided by the I18N -support code:: +If, for some reason, your custom management command must run without an active +locale (for example, to prevent translated content from being inserted into +the database), deactivate translations using the ``@no_translations`` +decorator on your :meth:`~BaseCommand.handle` method:: - from django.core.management.base import BaseCommand, CommandError - from django.utils import translation + from django.core.management.base import BaseCommand, no_translations class Command(BaseCommand): ... + @no_translations def handle(self, *args, **options): - - # Activate a fixed locale, e.g. Russian - translation.activate('ru') - - # Or you can activate the LANGUAGE_CODE # chosen in the settings: - from django.conf import settings - translation.activate(settings.LANGUAGE_CODE) - - # Your command logic here ... - translation.deactivate() - -Another need might be that your command simply should use the locale set in -settings and Django should be kept from deactivating it. You can achieve -it by using the :data:`BaseCommand.leave_locale_alone` option. +Since translation deactivation requires access to configured settings, the +decorator can't be used for commands that work without configured settings. -When working on the scenarios described above though, take into account that -system management commands typically have to be very careful about running in -non-uniform locales, so you might need to: +.. versionchanged:: 2.1 -* Make sure the :setting:`USE_I18N` setting is always ``True`` when running - the command (this is a good example of the potential problems stemming - from a dynamic runtime environment that Django commands avoid offhand by - deactivating translations). - -* Review the code of your command and the code it calls for behavioral - differences when locales are changed and evaluate its impact on - predictable behavior of your command. + The ``@no_translations`` decorator is new. In older versions, translations + are deactivated before running a command unless the command's + ``leave_locale_alone`` attribute (now removed) is set to ``True``. Testing ======= @@ -186,6 +156,24 @@ Testing Information on how to test custom management commands can be found in the :ref:`testing docs `. +Overriding commands +=================== + +Django registers the built-in commands and then searches for commands in +:setting:`INSTALLED_APPS` in reverse. During the search, if a command name +duplicates an already registered command, the newly discovered command +overrides the first. + +In other words, to override a command, the new command must have the same name +and its app must be before the overridden command's app in +:setting:`INSTALLED_APPS`. + +Management commands from third-party apps that have been unintentionally +overridden can be made available under a new name by creating a new command in +one of your project's apps (ordered before the third-party app in +:setting:`INSTALLED_APPS`) which imports the ``Command`` of the overridden +command. + Command objects =============== @@ -227,8 +215,6 @@ All attributes can be set in your derived class and can be used in .. attribute:: BaseCommand.requires_migrations_checks - .. versionadded:: 1.10 - A boolean; if ``True``, the command prints a warning if the set of migrations on disk don't match the migrations in the database. A warning doesn't prevent the command from executing. Default value is ``False``. @@ -238,21 +224,6 @@ All attributes can be set in your derived class and can be used in A boolean; if ``True``, the entire Django project will be checked for potential problems prior to executing the command. Default value is ``True``. -.. attribute:: BaseCommand.leave_locale_alone - - A boolean indicating whether the locale set in settings should be preserved - during the execution of the command instead of being forcibly set to 'en-us'. - - Default value is ``False``. - - Make sure you know what you are doing if you decide to change the value of - this option in your custom command if it creates database content that - is locale-sensitive and such content shouldn't contain any translations - (like it happens e.g. with :mod:`django.contrib.auth` permissions) as - making the locale differ from the de facto default 'en-us' might cause - unintended effects. See the `Management commands and locales`_ section - above for further details. - .. attribute:: BaseCommand.style An instance attribute that helps create colored output when writing to @@ -280,9 +251,22 @@ the :meth:`~BaseCommand.handle` method must be implemented. class Command(BaseCommand): def __init__(self, *args, **kwargs): - super(Command, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # ... +.. method:: BaseCommand.create_parser(prog_name, subcommand, **kwargs) + + Returns a ``CommandParser`` instance, which is an + :class:`~argparse.ArgumentParser` subclass with a few customizations for + Django. + + You can customize the instance by overriding this method and calling + ``super()`` with ``kwargs`` of :class:`~argparse.ArgumentParser` parameters. + + .. versionchanged:: 2.2 + + ``kwargs`` was added. + .. method:: BaseCommand.add_arguments(parser) Entry point to add parser arguments to handle command line arguments passed @@ -311,7 +295,7 @@ the :meth:`~BaseCommand.handle` method must be implemented. The actual logic of the command. Subclasses must implement this method. - It may return a Unicode string which will be printed to ``stdout`` (wrapped + It may return a string which will be printed to ``stdout`` (wrapped by ``BEGIN;`` and ``COMMIT;`` if :attr:`output_transaction` is ``True``). .. method:: BaseCommand.check(app_configs=None, tags=None, display_num_errors=False) diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt index 3c71f8f004ed..56d5c03718b0 100644 --- a/docs/howto/custom-model-fields.txt +++ b/docs/howto/custom-model-fields.txt @@ -19,7 +19,7 @@ only the common types, such as ``VARCHAR`` and ``INTEGER``. For more obscure column types, such as geographic polygons or even user-created types such as `PostgreSQL custom types`_, you can define your own Django ``Field`` subclasses. -.. _PostgreSQL custom types: https://www.postgresql.org/docs/current/static/sql-createtype.html +.. _PostgreSQL custom types: https://www.postgresql.org/docs/current/sql-createtype.html Alternatively, you may have a complex Python object that can somehow be serialized to fit into a standard database column type. This is another case @@ -36,7 +36,7 @@ You only need to know that 52 cards are dealt out equally to four players, who are traditionally called *north*, *east*, *south* and *west*. Our class looks something like this:: - class Hand(object): + class Hand: """A hand of cards (bridge style)""" def __init__(self, north, east, south, west): @@ -168,7 +168,7 @@ behave like any existing field, so we'll subclass directly from def __init__(self, *args, **kwargs): kwargs['max_length'] = 104 - super(HandField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) Our ``HandField`` accepts most of the standard field options (see the list below), but we ensure it has a fixed length, since it only needs to hold 52 @@ -231,9 +231,10 @@ Field deconstruction -------------------- The counterpoint to writing your ``__init__()`` method is writing the -``deconstruct()`` method. This method tells Django how to take an instance -of your new field and reduce it to a serialized form - in particular, what -arguments to pass to ``__init__()`` to re-create it. +:meth:`~.Field.deconstruct` method. It's used during :doc:`model migrations +` to tell Django how to take an instance of your new field +and reduce it to a serialized form - in particular, what arguments to pass to +``__init__()`` to re-create it. If you haven't added any extra options on top of the field you inherited from, then there's no need to write a new ``deconstruct()`` method. If, however, @@ -262,15 +263,17 @@ we can drop it from the keyword arguments for readability:: def __init__(self, *args, **kwargs): kwargs['max_length'] = 104 - super(HandField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def deconstruct(self): - name, path, args, kwargs = super(HandField, self).deconstruct() + name, path, args, kwargs = super().deconstruct() del kwargs["max_length"] return name, path, args, kwargs -If you add a new keyword argument, you need to write code to put its value -into ``kwargs`` yourself:: +If you add a new keyword argument, you need to write code in ``deconstruct()`` +that puts its value into ``kwargs`` yourself. You should also omit the value +from ``kwargs`` when it isn't necessary to reconstruct the state of the field, +such as when the default value is being used:: from django.db import models @@ -279,10 +282,10 @@ into ``kwargs`` yourself:: def __init__(self, separator=",", *args, **kwargs): self.separator = separator - super(CommaSepField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def deconstruct(self): - name, path, args, kwargs = super(CommaSepField, self).deconstruct() + name, path, args, kwargs = super().deconstruct() # Only include kwarg if it's not the default if self.separator != ",": kwargs['separator'] = self.separator @@ -435,7 +438,7 @@ time -- i.e., when the class is instantiated. To do that, just implement class BetterCharField(models.Field): def __init__(self, max_length, *args, **kwargs): self.max_length = max_length - super(BetterCharField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def db_type(self, connection): return 'char(%s)' % self.max_length @@ -464,10 +467,6 @@ need the foreign keys that point to that field to use the same data type:: def rel_db_type(self, connection): return 'integer UNSIGNED' -.. versionadded:: 1.10 - - The :meth:`~Field.rel_db_type` method was added. - .. _converting-values-to-python-objects: Converting values to Python objects @@ -502,7 +501,7 @@ instances:: from django.core.exceptions import ValidationError from django.db import models - from django.utils.translation import ugettext_lazy as _ + from django.utils.translation import gettext_lazy as _ def parse_hand(hand_string): """Takes a string of cards and splits into a full hand.""" @@ -516,7 +515,7 @@ instances:: class HandField(models.Field): # ... - def from_db_value(self, value, expression, connection, context): + def from_db_value(self, value, expression, connection): if value is None: return value return parse_hand(value) @@ -580,7 +579,7 @@ For example, Django uses the following method for its :class:`BinaryField`:: def get_db_prep_value(self, value, connection, prepared=False): - value = super(BinaryField, self).get_db_prep_value(value, connection, prepared) + value = super().get_db_prep_value(value, connection, prepared) if value is not None: return connection.Database.Binary(value) return value @@ -637,7 +636,7 @@ as:: # while letting the caller override them. defaults = {'form_class': MyFormField} defaults.update(kwargs) - return super(HandField, self).formfield(**defaults) + return super().formfield(**defaults) This assumes we've imported a ``MyFormField`` field class (which has its own default widget). This document doesn't cover the details of writing custom form @@ -683,8 +682,8 @@ Converting field data for serialization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To customize how the values are serialized by a serializer, you can override -:meth:`~Field.value_to_string`. Using ``value_from_object()`` is the best way -to get the field's value prior to serialization. For example, since our +:meth:`~Field.value_to_string`. Using :meth:`~Field.value_from_object` is the +best way to get the field's value prior to serialization. For example, since ``HandField`` uses strings for its data storage anyway, we can reuse some existing conversion code:: @@ -696,26 +695,24 @@ existing conversion code:: return self.get_prep_value(value) Some general advice --------------------- +------------------- Writing a custom field can be a tricky process, particularly if you're doing complex conversions between your Python types and your database and serialization formats. Here are a couple of tips to make things go more smoothly: -1. Look at the existing Django fields (in +#. Look at the existing Django fields (in :file:`django/db/models/fields/__init__.py`) for inspiration. Try to find a field that's similar to what you want and extend it a little bit, instead of creating an entirely new field from scratch. -2. Put a ``__str__()`` (``__unicode__()`` on Python 2) method on the class you're - wrapping up as a field. There are a lot of places where the default - behavior of the field code is to call - :func:`~django.utils.encoding.force_text` on the value. (In our - examples in this document, ``value`` would be a ``Hand`` instance, not a - ``HandField``). So if your ``__str__()`` method (``__unicode__()`` on - Python 2) automatically converts to the string form of your Python object, - you can save yourself a lot of work. +#. Put a ``__str__()`` method on the class you're wrapping up as a field. There + are a lot of places where the default behavior of the field code is to call + ``str()`` on the value. (In our examples in this document, ``value`` would + be a ``Hand`` instance, not a ``HandField``). So if your ``__str__()`` + method automatically converts to the string form of your Python object, you + can save yourself a lot of work. Writing a ``FileField`` subclass ================================ @@ -737,17 +734,17 @@ told to use it. To do so, simply assign the new ``File`` subclass to the special ``attr_class`` attribute of the ``FileField`` subclass. A few suggestions ------------------- +----------------- In addition to the above details, there are a few guidelines which can greatly improve the efficiency and readability of the field's code. -1. The source for Django's own ``ImageField`` (in +#. The source for Django's own ``ImageField`` (in ``django/db/models/fields/files.py``) is a great example of how to subclass ``FileField`` to support a particular type of file, as it incorporates all of the techniques described above. -2. Cache file attributes wherever possible. Since files may be stored in +#. Cache file attributes wherever possible. Since files may be stored in remote storage systems, retrieving them may cost extra time, or even money, that isn't always necessary. Once a file is retrieved to obtain some data about its content, cache as much of that data as possible to diff --git a/docs/howto/custom-template-tags.txt b/docs/howto/custom-template-tags.txt index 53ea433137e6..24a2e59f1c7f 100644 --- a/docs/howto/custom-template-tags.txt +++ b/docs/howto/custom-template-tags.txt @@ -188,23 +188,24 @@ Filters and auto-escaping ------------------------- When writing a custom filter, give some thought to how the filter will interact -with Django's auto-escaping behavior. Note that three types of strings can be +with Django's auto-escaping behavior. Note that two types of strings can be passed around inside the template code: -* **Raw strings** are the native Python ``str`` or ``unicode`` types. On - output, they're escaped if auto-escaping is in effect and presented - unchanged, otherwise. +* **Raw strings** are the native Python strings. On output, they're escaped if + auto-escaping is in effect and presented unchanged, otherwise. * **Safe strings** are strings that have been marked safe from further escaping at output time. Any necessary escaping has already been done. They're commonly used for output that contains raw HTML that is intended to be interpreted as-is on the client side. - Internally, these strings are of type ``SafeBytes`` or ``SafeText``. - They share a common base class of ``SafeData``, so you can test - for them using code like:: + Internally, these strings are of type + :class:`~django.utils.safestring.SafeText`. You can test for them + using code like:: - if isinstance(value, SafeData): + from django.utils.safestring import SafeText + + if isinstance(value, SafeText): # Do something with the "safe" string. ... @@ -229,9 +230,8 @@ Template filter code falls into one of two situations: The reason ``is_safe`` is necessary is because there are plenty of normal string operations that will turn a ``SafeData`` object back into - a normal ``str`` or ``unicode`` object and, rather than try to catch - them all, which would be very difficult, Django repairs the damage after - the filter has completed. + a normal ``str`` object and, rather than try to catch them all, which would + be very difficult, Django repairs the damage after the filter has completed. For example, suppose you have a filter that adds the string ``xx`` to the end of any input. Since this introduces no dangerous HTML characters @@ -490,7 +490,7 @@ you see fit: .. code-block:: html+django - {% get_current_time "%Y-%m-%d %I:%M %p" as the_time %} + {% current_time "%Y-%m-%d %I:%M %p" as the_time %}

    The time is {{ the_time }}.

    .. _howto-custom-template-tags-inclusion-tags: @@ -629,35 +629,6 @@ positional arguments. For example: {% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %} -Assignment tags ---------------- - -.. method:: django.template.Library.assignment_tag() - -.. deprecated:: 1.9 - - ``simple_tag`` can now store results in a template variable and should - be used instead. - -To ease the creation of tags setting a variable in the context, Django provides -a helper function, ``assignment_tag``. This function works the same way as -:meth:`~django.template.Library.simple_tag` except that it stores the tag's -result in a specified context variable instead of directly outputting it. - -Our earlier ``current_time`` function could thus be written like this:: - - @register.assignment_tag - def get_current_time(format_string): - return datetime.datetime.now().strftime(format_string) - -You may then store the result in a template variable using the ``as`` argument -followed by the variable name, and output it yourself where you see fit: - -.. code-block:: html+django - - {% get_current_time "%Y-%m-%d %I:%M %p" as the_time %} -

    The time is {{ the_time }}.

    - Advanced custom template tags ----------------------------- @@ -871,13 +842,13 @@ A naive implementation of ``CycleNode`` might look something like this:: But, suppose we have two templates rendering the template snippet from above at the same time: -1. Thread 1 performs its first loop iteration, ``CycleNode.render()`` +#. Thread 1 performs its first loop iteration, ``CycleNode.render()`` returns 'row1' -2. Thread 2 performs its first loop iteration, ``CycleNode.render()`` +#. Thread 2 performs its first loop iteration, ``CycleNode.render()`` returns 'row2' -3. Thread 1 performs its second loop iteration, ``CycleNode.render()`` +#. Thread 1 performs its second loop iteration, ``CycleNode.render()`` returns 'row1' -4. Thread 2 performs its second loop iteration, ``CycleNode.render()`` +#. Thread 2 performs its second loop iteration, ``CycleNode.render()`` returns 'row2' The CycleNode is iterating, but it's iterating globally. As far as Thread 1 @@ -921,7 +892,7 @@ Registering the tag ------------------- Finally, register the tag with your module's ``Library`` instance, as explained -in :ref:`writing custom template filters` +in :ref:`writing custom template tags` above. Example:: register.tag('current_time', do_current_time) @@ -1043,7 +1014,7 @@ Here's how you'd use this new version of the tag: .. code-block:: html+django - {% current_time "%Y-%M-%d %I:%M %p" %}

    The time is {{ current_time }}.

    + {% current_time "%Y-%m-%d %I:%M %p" %}

    The time is {{ current_time }}.

    .. admonition:: Variable scope in context @@ -1061,7 +1032,7 @@ like so: .. code-block:: html+django - {% current_time "%Y-%M-%d %I:%M %p" as my_current_time %} + {% current_time "%Y-%m-%d %I:%M %p" as my_current_time %}

    The current time is {{ my_current_time }}.

    To do that, you'll need to refactor both the compilation function and ``Node`` diff --git a/docs/howto/deployment/checklist.txt b/docs/howto/deployment/checklist.txt index 8d7c27b04f8f..c31bcac62a9b 100644 --- a/docs/howto/deployment/checklist.txt +++ b/docs/howto/deployment/checklist.txt @@ -154,6 +154,16 @@ server never attempts to interpret them. For instance, if a user uploads a Now is a good time to check your backup strategy for these files. +:setting:`FILE_UPLOAD_PERMISSIONS` +---------------------------------- + +With the default file upload settings, files smaller than +:setting:`FILE_UPLOAD_MAX_MEMORY_SIZE` may be stored with a different mode +than larger files as described in :setting:`FILE_UPLOAD_PERMISSIONS`. + +Setting :setting:`FILE_UPLOAD_PERMISSIONS` ensures all files are uploaded with +the same permissions. + HTTPS ===== @@ -234,7 +244,7 @@ See :doc:`/howto/error-reporting` for details on error reporting by email. Consider using an error monitoring system such as Sentry_ before your inbox is flooded by reports. Sentry can also aggregate logs. - .. _Sentry: https://docs.getsentry.com/ + .. _Sentry: https://docs.sentry.io/ Customize the default error views --------------------------------- @@ -242,27 +252,6 @@ Customize the default error views Django includes default views and templates for several HTTP error codes. You may want to override the default templates by creating the following templates in your root template directory: ``404.html``, ``500.html``, ``403.html``, and -``400.html``. The default views should suffice for 99% of Web applications, but -if you desire to customize them, see these instructions which also contain -details about the default templates: - -* :ref:`http_not_found_view` -* :ref:`http_internal_server_error_view` -* :ref:`http_forbidden_view` -* :ref:`http_bad_request_view` - -Python Options -============== - -It's strongly recommended that you invoke the Python process running your -Django application using the `-R`_ option or with the :envvar:`PYTHONHASHSEED` -environment variable set to ``random``. This option is enabled by default -starting with Python 3.3. - -These options help protect your site from denial-of-service (DoS) -attacks triggered by carefully crafted inputs. Such an attack can -drastically increase CPU usage by causing worst-case performance when -creating ``dict`` instances. See `oCERT advisory #2011-003 -`_ for more information. - -.. _-r: https://docs.python.org/2/using/cmdline.html#cmdoption-R +``400.html``. The :ref:`default error views ` that use these +templates should suffice for 99% of Web applications, but you can +:ref:`customize them ` as well. diff --git a/docs/howto/deployment/index.txt b/docs/howto/deployment/index.txt index 8ffda2cf63bf..96eb8200ec78 100644 --- a/docs/howto/deployment/index.txt +++ b/docs/howto/deployment/index.txt @@ -7,9 +7,11 @@ those tools are of no use if you can't easily deploy your sites. Since Django's inception, ease of deployment has been a major goal. .. toctree:: - :maxdepth: 1 + :maxdepth: 2 wsgi/index + ../static-files/deployment + ../error-reporting checklist If you're new to deploying Django and/or Python, we'd recommend you try diff --git a/docs/howto/deployment/wsgi/apache-auth.txt b/docs/howto/deployment/wsgi/apache-auth.txt index 5f134209f09e..912d11830e0e 100644 --- a/docs/howto/deployment/wsgi/apache-auth.txt +++ b/docs/howto/deployment/wsgi/apache-auth.txt @@ -22,7 +22,7 @@ version >= 2.2 and mod_wsgi >= 2.0. For example, you could: a 'name' field. You can also specify your own custom mod_wsgi auth handler if your custom cannot conform to these requirements. -.. _Subversion: http://subversion.apache.org/ +.. _Subversion: https://subversion.apache.org/ .. _mod_dav: https://httpd.apache.org/docs/2.2/mod/mod_dav.html Authentication with ``mod_wsgi`` diff --git a/docs/howto/deployment/wsgi/gunicorn.txt b/docs/howto/deployment/wsgi/gunicorn.txt index 4cde1257da1a..11451d59a49e 100644 --- a/docs/howto/deployment/wsgi/gunicorn.txt +++ b/docs/howto/deployment/wsgi/gunicorn.txt @@ -7,7 +7,7 @@ How to use Django with Gunicorn Gunicorn_ ('Green Unicorn') is a pure-Python WSGI server for UNIX. It has no dependencies and is easy to install and use. -.. _Gunicorn: http://gunicorn.org/ +.. _Gunicorn: https://gunicorn.org/ Installing Gunicorn =================== @@ -15,7 +15,7 @@ Installing Gunicorn Installing gunicorn is as easy as ``pip install gunicorn``. For more details, see the `gunicorn documentation`_. -.. _gunicorn documentation: http://docs.gunicorn.org/en/latest/install.html +.. _gunicorn documentation: https://docs.gunicorn.org/en/latest/install.html Running Django in Gunicorn as a generic WSGI application ======================================================== @@ -34,4 +34,4 @@ that is to run this command from the same directory as your ``manage.py`` file. See Gunicorn's `deployment documentation`_ for additional tips. -.. _deployment documentation: http://docs.gunicorn.org/en/latest/deploy.html +.. _deployment documentation: https://docs.gunicorn.org/en/latest/deploy.html diff --git a/docs/howto/deployment/wsgi/index.txt b/docs/howto/deployment/wsgi/index.txt index 214ebc54a4c1..ffc0fbe09ac0 100644 --- a/docs/howto/deployment/wsgi/index.txt +++ b/docs/howto/deployment/wsgi/index.txt @@ -5,7 +5,7 @@ How to deploy with WSGI Django's primary deployment platform is WSGI_, the Python standard for web servers and applications. -.. _WSGI: http://www.wsgi.org +.. _WSGI: https://wsgi.readthedocs.io/en/latest/ Django's :djadmin:`startproject` management command sets up a simple default WSGI configuration for you, which you can tweak as needed for your project, @@ -16,10 +16,10 @@ Django includes getting-started documentation for the following WSGI servers: .. toctree:: :maxdepth: 1 - modwsgi - apache-auth gunicorn uwsgi + modwsgi + apache-auth The ``application`` object ========================== @@ -81,10 +81,3 @@ application that later delegates to the Django WSGI application, if you want to combine a Django application with a WSGI application of another framework. .. _`WSGI middleware`: https://www.python.org/dev/peps/pep-3333/#middleware-components-that-play-both-sides - -.. note:: - - Some third-party WSGI middleware do not call ``close`` on the response - object after handling a request. In those cases the - :data:`~django.core.signals.request_finished` signal isn't sent. This can - result in idle connections to database and memcache servers. diff --git a/docs/howto/deployment/wsgi/modwsgi.txt b/docs/howto/deployment/wsgi/modwsgi.txt index 9b1b042308fb..c2e74d3bae30 100644 --- a/docs/howto/deployment/wsgi/modwsgi.txt +++ b/docs/howto/deployment/wsgi/modwsgi.txt @@ -6,17 +6,17 @@ Deploying Django with Apache_ and `mod_wsgi`_ is a tried and tested way to get Django into production. .. _Apache: https://httpd.apache.org/ -.. _mod_wsgi: http://www.modwsgi.org/ +.. _mod_wsgi: https://modwsgi.readthedocs.io/en/develop/ mod_wsgi is an Apache module which can host any Python WSGI_ application, including Django. Django will work with any version of Apache which supports mod_wsgi. -.. _WSGI: http://www.wsgi.org +.. _WSGI: https://wsgi.readthedocs.io/en/latest/ -The `official mod_wsgi documentation`_ is fantastic; it's your source for all -the details about how to use mod_wsgi. You'll probably want to start with the -`installation and configuration documentation`_. +The `official mod_wsgi documentation`_ is your source for all the details about +how to use mod_wsgi. You'll probably want to start with the `installation and +configuration documentation`_. .. _official mod_wsgi documentation: https://modwsgi.readthedocs.io/ .. _installation and configuration documentation: https://modwsgi.readthedocs.io/en/develop/installation.html @@ -34,6 +34,7 @@ also add the line ``Order deny,allow`` above it. .. code-block:: apache WSGIScriptAlias / /path/to/mysite.com/mysite/wsgi.py + WSGIPythonHome /path/to/venv WSGIPythonPath /path/to/mysite.com @@ -49,6 +50,10 @@ your project package (``mysite`` in this example). This tells Apache to serve any request below the given URL using the WSGI application defined in that file. +If you install your project's Python dependencies inside a `virtualenv`_, add +the path to the virtualenv using ``WSGIPythonHome``. See the `mod_wsgi +virtualenv guide`_ for more details. + The ``WSGIPythonPath`` line ensures that your project package is available for import on the Python path; in other words, that ``import mysite`` works. @@ -61,6 +66,9 @@ for you; otherwise, you'll need to create it. See the :doc:`WSGI overview documentation` for the default contents you should put in this file, and what else you can add to it. +.. _virtualenv: https://virtualenv.pypa.io/ +.. _mod_wsgi virtualenv guide: https://modwsgi.readthedocs.io/en/develop/user-guides/virtual-environments.html + .. warning:: If multiple Django sites are run in a single mod_wsgi process, all of them @@ -90,26 +98,6 @@ should put in this file, and what else you can add to it. See the :ref:`unicode-files` section of the Unicode reference guide for details. -Using a ``virtualenv`` -====================== - -If you install your project's Python dependencies inside a `virtualenv`_, -you'll need to add the path to this virtualenv's ``site-packages`` directory to -your Python path as well. To do this, add an additional path to your -``WSGIPythonPath`` directive, with multiple paths separated by a colon (``:``) -if using a UNIX-like system, or a semicolon (``;``) if using Windows. If any -part of a directory path contains a space character, the complete argument -string to ``WSGIPythonPath`` must be quoted: - -.. code-block:: apache - - WSGIPythonPath /path/to/mysite.com:/path/to/your/venv/lib/python3.X/site-packages - -Make sure you give the correct path to your virtualenv, and replace -``python3.X`` with the correct Python version (e.g. ``python3.4``). - -.. _virtualenv: https://virtualenv.pypa.io/ - .. _daemon-mode: Using ``mod_wsgi`` daemon mode @@ -125,7 +113,7 @@ use ``WSGIPythonPath``; instead you should use the ``python-path`` option to .. code-block:: apache - WSGIDaemonProcess example.com python-path=/path/to/mysite.com:/path/to/venv/lib/python2.7/site-packages + WSGIDaemonProcess example.com python-home=/path/to/venv python-path=/path/to/mysite.com WSGIProcessGroup example.com If you want to serve your project in a subdirectory @@ -191,7 +179,7 @@ If you are using a version of Apache older than 2.4, replace ``Require all granted`` with ``Allow from all`` and also add the line ``Order deny,allow`` above it. -.. _Nginx: http://wiki.nginx.org/Main +.. _Nginx: https://nginx.org/en/ .. _Apache: https://httpd.apache.org/ .. More details on configuring a mod_wsgi site to serve static files can be found diff --git a/docs/howto/deployment/wsgi/uwsgi.txt b/docs/howto/deployment/wsgi/uwsgi.txt index b026a31d4a81..a02c12f26c34 100644 --- a/docs/howto/deployment/wsgi/uwsgi.txt +++ b/docs/howto/deployment/wsgi/uwsgi.txt @@ -7,7 +7,7 @@ How to use Django with uWSGI uWSGI_ is a fast, self-healing and developer/sysadmin-friendly application container server coded in pure C. -.. _uWSGI: https://projects.unbit.it/uwsgi/ +.. _uWSGI: https://uwsgi-docs.readthedocs.io/ .. seealso:: @@ -34,32 +34,19 @@ command. For example: .. _installation procedures: https://uwsgi-docs.readthedocs.io/en/latest/Install.html -.. warning:: - - Some distributions, including Debian and Ubuntu, ship an outdated version - of uWSGI that does not conform to the WSGI specification. Versions prior to - 1.2.6 do not call ``close`` on the response object after handling a - request. In those cases the :data:`~django.core.signals.request_finished` - signal isn't sent. This can result in idle connections to database and - memcache servers. - uWSGI model ----------- uWSGI operates on a client-server model. Your Web server (e.g., nginx, Apache) communicates with a `django-uwsgi` "worker" process to serve dynamic content. -See uWSGI's `background documentation`_ for more detail. - -.. _background documentation: https://projects.unbit.it/uwsgi/wiki/Background Configuring and starting the uWSGI server for Django ---------------------------------------------------- uWSGI supports multiple ways to configure the process. See uWSGI's -`configuration documentation`_ and `examples`_. +`configuration documentation`_. .. _configuration documentation: https://uwsgi.readthedocs.io/en/latest/Configuration.html -.. _examples: https://projects.unbit.it/uwsgi/wiki/Example Here's an example command to start a uWSGI server:: diff --git a/docs/howto/error-reporting.txt b/docs/howto/error-reporting.txt index 7928d193d5a0..0edd9f929047 100644 --- a/docs/howto/error-reporting.txt +++ b/docs/howto/error-reporting.txt @@ -20,10 +20,11 @@ Server errors When :setting:`DEBUG` is ``False``, Django will email the users listed in the :setting:`ADMINS` setting whenever your code raises an unhandled exception and -results in an internal server error (HTTP status code 500). This gives the -administrators immediate notification of any errors. The :setting:`ADMINS` will -get a description of the error, a complete Python traceback, and details about -the HTTP request that caused the error. +results in an internal server error (strictly speaking, for any response with +an HTTP status code of 500 or greater). This gives the administrators immediate +notification of any errors. The :setting:`ADMINS` will get a description of the +error, a complete Python traceback, and details about the HTTP request that +caused the error. .. note:: @@ -120,10 +121,10 @@ Filtering error reports .. warning:: Filtering sensitive data is a hard problem, and it's nearly impossible to - guarantee that sensitive won't leak into an error report. Therefore, error - reports should only be available to trusted team members and you should - avoid transmitting error reports unencrypted over the Internet (such as - through email). + guarantee that sensitive data won't leak into an error report. Therefore, + error reports should only be available to trusted team members and you + should avoid transmitting error reports unencrypted over the Internet + (such as through email). Filtering sensitive information ------------------------------- diff --git a/docs/howto/index.txt b/docs/howto/index.txt index 89e319281015..1bd3e757923c 100644 --- a/docs/howto/index.txt +++ b/docs/howto/index.txt @@ -24,6 +24,7 @@ you quickly accomplish common tasks. legacy-databases outputting-csv outputting-pdf + overriding-templates static-files/index static-files/deployment windows diff --git a/docs/howto/initial-data.txt b/docs/howto/initial-data.txt index 5945073889fe..4ec2b0bae3b2 100644 --- a/docs/howto/initial-data.txt +++ b/docs/howto/initial-data.txt @@ -3,13 +3,24 @@ Providing initial data for models ================================= It's sometimes useful to pre-populate your database with hard-coded data when -you're first setting up an app. You can provide initial data with fixtures or -migrations. +you're first setting up an app. You can provide initial data with migrations or +fixtures. + +Providing initial data with migrations +====================================== + +If you want to automatically load initial data for an app, create a +:ref:`data migration `. Migrations are run when setting up the +test database, so the data will be available there, subject to :ref:`some +limitations `. .. _initial-data-via-fixtures: -Providing initial data with fixtures -==================================== +Providing data with fixtures +============================ + +You can also provide data using fixtures, however, this data isn't loaded +automatically, except if you use :attr:`.TransactionTestCase.fixtures`. A fixture is a collection of data that Django knows how to import into a database. The most straightforward way of creating a fixture if you've already @@ -19,7 +30,7 @@ Or, you can write fixtures by hand; fixtures can be written as JSON, XML or YAML ` has more details about each of these supported :ref:`serialization formats `. -.. _PyYAML: http://www.pyyaml.org/ +.. _PyYAML: https://pyyaml.org/ As an example, though, here's what a fixture for a simple ``Person`` model might look like in JSON: @@ -47,7 +58,7 @@ look like in JSON: And here's that same fixture as YAML: -.. code-block:: none +.. code-block:: yaml - model: myapp.person pk: 1 @@ -84,11 +95,3 @@ directories. Fixtures are also used by the :ref:`testing framework ` to help set up a consistent test environment. - -Providing initial data with migrations -====================================== - -If you want to automatically load initial data for an app, don't use fixtures. -Instead, create a migration for your application with -:class:`~django.db.migrations.operations.RunPython` or -:class:`~django.db.migrations.operations.RunSQL` operations. diff --git a/docs/howto/jython.txt b/docs/howto/jython.txt index 607bbbdd1b5e..9685ba2bb03f 100644 --- a/docs/howto/jython.txt +++ b/docs/howto/jython.txt @@ -1,74 +1,14 @@ -======================== -Running Django on Jython -======================== +================ +Django on Jython +================ .. index:: Jython, Java, JVM Jython_ is an implementation of Python that runs on the Java platform (JVM). -This document will get you up and running with Django on top of Jython. - -Installing Jython -================= - -Django works with Jython versions 2.7b2 and higher. See the Jython_ website for -download and installation instructions. .. _jython: http://www.jython.org/ -Creating a servlet container -============================ - -If you just want to experiment with Django, skip ahead to the next section; -Django includes a lightweight Web server you can use for testing, so you won't -need to set up anything else until you're ready to deploy Django in production. - -If you want to use Django on a production site, use a Java servlet container, -such as `Apache Tomcat`_. Full JavaEE applications servers such as `GlassFish`_ -or `JBoss`_ are also OK, if you need the extra features they include. - -.. _`Apache Tomcat`: https://tomcat.apache.org/ -.. _GlassFish: https://glassfish.java.net/ -.. _JBoss: https://www.jboss.org/ - -Installing Django -================= - -The next step is to install Django itself. This is exactly the same as -installing Django on standard Python, so see -:ref:`removing-old-versions-of-django` and :ref:`install-django-code` for -instructions. - -Installing Jython platform support libraries -============================================ - -The `django-jython`_ project contains database backends and management commands -for Django/Jython development. Note that the builtin Django backends won't work -on top of Jython. - -.. _`django-jython`: https://github.com/beachmachine/django-jython - -To install it, follow the `installation instructions`_ detailed on the project -website. Also, read the `database backends`_ documentation there. - -.. _`installation instructions`: https://pythonhosted.org/django-jython/quickstart.html#install -.. _`database backends`: https://pythonhosted.org/django-jython/database-backends.html - -Differences with Django on Jython -================================= - -.. index:: JYTHONPATH - -At this point, Django on Jython should behave nearly identically to Django -running on standard Python. However, are a few differences to keep in mind: - -* Remember to use the ``jython`` command instead of ``python``. The - documentation uses ``python`` for consistency, but if you're using Jython - you'll want to mentally replace ``python`` with ``jython`` every time it - occurs. - -* Similarly, you'll need to use the ``JYTHONPATH`` environment variable - instead of ``PYTHONPATH``. - -* Any part of Django that requires `Pillow`_ will not work. - -.. _Pillow: https://pillow.readthedocs.io/en/latest/ +If you want to use Jython (which supports only Python 2.7 at the time of this +writing), you must use Django 1.11.x (the last series to support Python 2). +Jython support may be readded to Django if a Python 3 compatible Jython is +released. diff --git a/docs/howto/outputting-csv.txt b/docs/howto/outputting-csv.txt index 26a0c4eb1f8b..c29e9bc2a6f2 100644 --- a/docs/howto/outputting-csv.txt +++ b/docs/howto/outputting-csv.txt @@ -47,32 +47,12 @@ mention: bill. * For each row in your CSV file, call ``writer.writerow``, passing it an - iterable object such as a list or tuple. + :term:`iterable`. * The CSV module takes care of quoting for you, so you don't have to worry about escaping strings with quotes or commas in them. Just pass ``writerow()`` your raw strings, and it'll do the right thing. -.. admonition:: Handling Unicode on Python 2 - - Python 2's :mod:`csv` module does not support Unicode input. Since Django - uses Unicode internally this means strings read from sources such as - :class:`~django.http.HttpRequest` are potentially problematic. There are a - few options for handling this: - - * Manually encode all Unicode objects to a compatible encoding. - - * Use the ``UnicodeWriter`` class provided in the `csv module's examples - section`_. - - * Use the `python-unicodecsv module`_, which aims to be a drop-in - replacement for :mod:`csv` that gracefully handles Unicode. - - For more information, see the Python documentation of the :mod:`csv` module. - - .. _`csv module's examples section`: https://docs.python.org/2/library/csv.html#examples - .. _`python-unicodecsv module`: https://github.com/jdunck/python-unicodecsv - .. _streaming-csv-files: Streaming large CSV files @@ -89,10 +69,9 @@ the assembly and transmission of a large CSV file:: import csv - from django.utils.six.moves import range from django.http import StreamingHttpResponse - class Echo(object): + class Echo: """An object that implements just the write method of the file-like interface. """ @@ -126,7 +105,7 @@ template output the commas in a :ttag:`for` loop. Here's an example, which generates the same CSV file as above:: from django.http import HttpResponse - from django.template import loader, Context + from django.template import loader def some_view(request): # Create the HttpResponse object with the appropriate CSV header. @@ -141,9 +120,7 @@ Here's an example, which generates the same CSV file as above:: ) t = loader.get_template('my_template_name.txt') - c = Context({ - 'data': csv_data, - }) + c = {'data': csv_data} response.write(t.render(c)) return response diff --git a/docs/howto/outputting-pdf.txt b/docs/howto/outputting-pdf.txt index bfcea02f972c..986a5b252c57 100644 --- a/docs/howto/outputting-pdf.txt +++ b/docs/howto/outputting-pdf.txt @@ -14,7 +14,7 @@ For example, Django was used at kusports.com_ to generate customized, printer-friendly NCAA tournament brackets, as PDF files, for people participating in a March Madness contest. -.. _ReportLab: http://www.reportlab.com/opensource/ +.. _ReportLab: https://www.reportlab.com/opensource/ .. _kusports.com: http://www.kusports.com/ Install ReportLab @@ -24,7 +24,7 @@ The ReportLab library is `available on PyPI`_. A `user guide`_ (not coincidentally, a PDF file) is also available for download. You can install ReportLab with ``pip``: -.. code-block:: console +.. console:: $ pip install reportlab @@ -34,28 +34,28 @@ Test your installation by importing it in the Python interactive interpreter:: If that command doesn't raise any errors, the installation worked. -.. _available on PyPI: https://pypi.python.org/pypi/reportlab -.. _user guide: http://www.reportlab.com/docs/reportlab-userguide.pdf +.. _available on PyPI: https://pypi.org/project/reportlab/ +.. _user guide: https://www.reportlab.com/docs/reportlab-userguide.pdf Write your view =============== The key to generating PDFs dynamically with Django is that the ReportLab API -acts on file-like objects, and Django's :class:`~django.http.HttpResponse` -objects are file-like objects. +acts on file-like objects, and Django's :class:`~django.http.FileResponse` +objects accept file-like objects. Here's a "Hello World" example:: + import io + from django.http import FileResponse from reportlab.pdfgen import canvas - from django.http import HttpResponse def some_view(request): - # Create the HttpResponse object with the appropriate PDF headers. - response = HttpResponse(content_type='application/pdf') - response['Content-Disposition'] = 'attachment; filename="somefilename.pdf"' + # Create a file-like buffer to receive PDF data. + buffer = io.BytesIO() - # Create the PDF object, using the response object as its "file." - p = canvas.Canvas(response) + # Create the PDF object, using the buffer as its "file." + p = canvas.Canvas(buffer) # Draw things on the PDF. Here's where the PDF generation happens. # See the ReportLab documentation for the full list of functionality. @@ -64,37 +64,36 @@ Here's a "Hello World" example:: # Close the PDF object cleanly, and we're done. p.showPage() p.save() - return response + + # FileResponse sets the Content-Disposition header so that browsers + # present the option to save the file. + buffer.seek(0) + return FileResponse(buffer, as_attachment=True, filename='hello.pdf') The code and comments should be self-explanatory, but a few things deserve a mention: -* The response gets a special MIME type, :mimetype:`application/pdf`. This - tells browsers that the document is a PDF file, rather than an HTML file. - If you leave this off, browsers will probably interpret the output as - HTML, which would result in ugly, scary gobbledygook in the browser - window. - -* The response gets an additional ``Content-Disposition`` header, which - contains the name of the PDF file. This filename is arbitrary: Call it - whatever you want. It'll be used by browsers in the "Save as..." dialog, etc. +* The response will automatically set the MIME type :mimetype:`application/pdf` + based on the filename extension. This tells browsers that the document is a + PDF file, rather than an HTML file or a generic `application/octet-stream` + binary content. -* The ``Content-Disposition`` header starts with ``'attachment; '`` in this - example. This forces Web browsers to pop-up a dialog box - prompting/confirming how to handle the document even if a default is set - on the machine. If you leave off ``'attachment;'``, browsers will handle - the PDF using whatever program/plugin they've been configured to use for - PDFs. Here's what that code would look like:: +* When ``as_attachment=True`` is passed to ``FileResponse``, it sets the + appropriate ``Content-Disposition`` header and that tells Web browsers to + pop-up a dialog box prompting/confirming how to handle the document even if a + default is set on the machine. If the ``as_attachment`` parameter is omitted, + browsers will handle the PDF using whatever program/plugin they've been + configured to use for PDFs. - response['Content-Disposition'] = 'filename="somefilename.pdf"' +* You can provide an arbitrary ``filename`` parameter. It'll be used by browsers + in the "Save as..." dialog. -* Hooking into the ReportLab API is easy: Just pass ``response`` as the - first argument to ``canvas.Canvas``. The ``Canvas`` class expects a - file-like object, and :class:`~django.http.HttpResponse` objects fit the - bill. +* Hooking into the ReportLab API is easy: The same buffer passed as the first + argument to ``canvas.Canvas`` can be fed to the + :class:`~django.http.FileResponse` class. * Note that all subsequent PDF-generation methods are called on the PDF - object (in this case, ``p``) -- not on ``response``. + object (in this case, ``p``) -- not on ``buffer``. * Finally, it's important to call ``showPage()`` and ``save()`` on the PDF file. @@ -105,42 +104,6 @@ mention: with building PDF-generating Django views that are accessed by many people at the same time. -Complex PDFs -============ - -If you're creating a complex PDF document with ReportLab, consider using the -:mod:`io` library as a temporary holding place for your PDF file. This -library provides a file-like object interface that is particularly efficient. -Here's the above "Hello World" example rewritten to use :mod:`io`:: - - from io import BytesIO - from reportlab.pdfgen import canvas - from django.http import HttpResponse - - def some_view(request): - # Create the HttpResponse object with the appropriate PDF headers. - response = HttpResponse(content_type='application/pdf') - response['Content-Disposition'] = 'attachment; filename="somefilename.pdf"' - - buffer = BytesIO() - - # Create the PDF object, using the BytesIO object as its "file." - p = canvas.Canvas(buffer) - - # Draw things on the PDF. Here's where the PDF generation happens. - # See the ReportLab documentation for the full list of functionality. - p.drawString(100, 100, "Hello world.") - - # Close the PDF object cleanly. - p.showPage() - p.save() - - # Get the value of the BytesIO buffer and write it to the response. - pdf = buffer.getvalue() - buffer.close() - response.write(pdf) - return response - Other formats ============= diff --git a/docs/howto/overriding-templates.txt b/docs/howto/overriding-templates.txt new file mode 100644 index 000000000000..e7c65dd35416 --- /dev/null +++ b/docs/howto/overriding-templates.txt @@ -0,0 +1,99 @@ +==================== +Overriding templates +==================== + +In your project, you might want to override a template in another Django +application, whether it be a third-party application or a contrib application +such as ``django.contrib.admin``. You can either put template overrides in your +project's templates directory or in an application's templates directory. + +If you have app and project templates directories that both contain overrides, +the default Django template loader will try to load the template from the +project-level directory first. In other words, :setting:`DIRS ` +is searched before :setting:`APP_DIRS `. + +.. seealso:: + + Read :ref:`overriding-built-in-widget-templates` if you're looking to + do that. + +Overriding from the project's templates directory +================================================= + +First, we'll explore overriding templates by creating replacement templates in +your project's templates directory. + +Let's say you're trying to override the templates for a third-party application +called ``blog``, which provides the templates ``blog/post.html`` and +``blog/list.html``. The relevant settings for your project would look like:: + + import os + + BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + INSTALLED_APPS = [ + ..., + 'blog', + ..., + ] + + TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [os.path.join(BASE_DIR, 'templates')], + 'APP_DIRS': True, + ... + }, + ] + +The :setting:`TEMPLATES` setting and ``BASE_DIR`` will already exist if you +created your project using the default project template. The setting that needs +to be modified is :setting:`DIRS`. + +These settings assume you have a ``templates`` directory in the root of your +project. To override the templates for the ``blog`` app, create a folder +in the ``templates`` directory, and add the template files to that folder: + +.. code-block:: none + + templates/ + blog/ + list.html + post.html + +The template loader first looks for templates in the ``DIRS`` directory. When +the views in the ``blog`` app ask for the ``blog/post.html`` and +``blog/list.html`` templates, the loader will return the files you just created. + +Overriding from an app's template directory +=========================================== + +Since you're overriding templates located outside of one of your project's +apps, it's more common to use the first method and put template overrides in a +project's templates folder. If you prefer, however, it's also possible to put +the overrides in an app's template directory. + +First, make sure your template settings are checking inside app directories:: + + TEMPLATES = [ + { + ..., + 'APP_DIRS': True, + ... + }, + ] + +If you want to put the template overrides in an app called ``myapp`` and the +templates to override are named ``blog/list.html`` and ``blog/post.html``, +then your directory structure will look like: + +.. code-block:: none + + myapp/ + templates/ + blog/ + list.html + post.html + +With :setting:`APP_DIRS` set to ``True``, the template +loader will look in the app's templates directory and find the templates. diff --git a/docs/howto/static-files/deployment.txt b/docs/howto/static-files/deployment.txt index 6c116f816f99..a9777c0f1bf1 100644 --- a/docs/howto/static-files/deployment.txt +++ b/docs/howto/static-files/deployment.txt @@ -38,30 +38,7 @@ serving your site, the process may look something like: :ref:`how to do this with Apache and mod_wsgi `. You'll probably want to automate this process, especially if you've got -multiple web servers. There's any number of ways to do this automation, but -one option that many Django developers enjoy is `Fabric -`_. - -Below, and in the following sections, we'll show off a few example fabfiles -(i.e. Fabric scripts) that automate these file deployment options. The syntax -of a fabfile is fairly straightforward but won't be covered here; consult -`Fabric's documentation `_, for a complete -explanation of the syntax. - -So, a fabfile to deploy static files to a couple of web servers might look -something like:: - - from fabric.api import * - - # Hosts to deploy onto - env.hosts = ['www1.example.com', 'www2.example.com'] - - # Where your project code lives on the server - env.project_root = '/home/www/myproject' - - def deploy_static(): - with cd(env.project_root): - run('./manage.py collectstatic -v0 --noinput') +multiple web servers. Serving static files from a dedicated server -------------------------------------------- @@ -73,7 +50,7 @@ type of web server -- faster but less full-featured. Some common choices are: * Nginx_ * A stripped-down version of Apache_ -.. _Nginx: http://wiki.nginx.org/Main +.. _Nginx: https://nginx.org/en/ .. _Apache: https://httpd.apache.org/ Configuring these servers is out of scope of this document; check each @@ -89,26 +66,6 @@ the deployment strategy to look something like: common choice for this step since it only needs to transfer the bits of static files that have changed. -Here's how this might look in a fabfile:: - - from fabric.api import * - from fabric.contrib import project - - # Where the static files get collected locally. Your STATIC_ROOT setting. - env.local_static_root = '/path/to/static' - - # Where the static files should go remotely - env.remote_static_root = '/home/www/static.example.com' - - @roles('static') - def deploy_static(): - local('./manage.py collectstatic') - project.rsync_project( - remote_dir=env.remote_static_root, - local_dir=env.local_static_root, - delete=True, - ) - .. _staticfiles-from-cdn: Serving static files from a cloud service or CDN @@ -142,8 +99,8 @@ as changing your :setting:`STATICFILES_STORAGE` setting. For details on how you'd write one of these backends, see :doc:`/howto/custom-file-storage`. There are 3rd party apps available that provide storage backends for many common file storage APIs. A good starting -point is the `overview at djangopackages.com -`_. +point is the `overview at djangopackages.org +`_. Learn more ========== diff --git a/docs/howto/static-files/index.txt b/docs/howto/static-files/index.txt index 8244b685ce96..c21c75a8d1de 100644 --- a/docs/howto/static-files/index.txt +++ b/docs/howto/static-files/index.txt @@ -11,28 +11,24 @@ This page describes how you can serve these static files. Configuring static files ======================== -1. Make sure that ``django.contrib.staticfiles`` is included in your +#. Make sure that ``django.contrib.staticfiles`` is included in your :setting:`INSTALLED_APPS`. -2. In your settings file, define :setting:`STATIC_URL`, for example:: +#. In your settings file, define :setting:`STATIC_URL`, for example:: STATIC_URL = '/static/' -3. In your templates, either hardcode the url like - ``/static/my_app/example.jpg`` or, preferably, use the :ttag:`static` - template tag to build the URL for the given relative path by using the - configured :setting:`STATICFILES_STORAGE` storage (this makes it much easier - when you want to switch to a content delivery network (CDN) for serving - static files). +#. In your templates, use the :ttag:`static` template tag to build the URL for + the given relative path using the configured :setting:`STATICFILES_STORAGE`. .. _staticfiles-in-templates: .. code-block:: html+django {% load static %} - My image + My image -4. Store your static files in a folder called ``static`` in your app. For +#. Store your static files in a folder called ``static`` in your app. For example ``my_app/static/my_app/example.jpg``. .. admonition:: Serving the files @@ -84,7 +80,7 @@ If you use :mod:`django.contrib.staticfiles` as explained above, :djadmin:`runserver` will do this automatically when :setting:`DEBUG` is set to ``True``. If you don't have ``django.contrib.staticfiles`` in :setting:`INSTALLED_APPS`, you can still manually serve static files using the -:func:`django.contrib.staticfiles.views.serve` view. +:func:`django.views.static.serve` view. This is not suitable for production use! For some common deployment strategies, see :doc:`/howto/static-files/deployment`. @@ -115,8 +111,7 @@ Serving files uploaded by a user during development =================================================== During development, you can serve user-uploaded media files from -:setting:`MEDIA_ROOT` using the :func:`django.contrib.staticfiles.views.serve` -view. +:setting:`MEDIA_ROOT` using the :func:`django.views.static.serve` view. This is not suitable for production use! For some common deployment strategies, see :doc:`/howto/static-files/deployment`. @@ -164,19 +159,19 @@ Deployment :mod:`django.contrib.staticfiles` provides a convenience management command for gathering static files in a single directory so you can serve them easily. -1. Set the :setting:`STATIC_ROOT` setting to the directory from which you'd +#. Set the :setting:`STATIC_ROOT` setting to the directory from which you'd like to serve these files, for example:: STATIC_ROOT = "/var/www/example.com/static/" -2. Run the :djadmin:`collectstatic` management command:: +#. Run the :djadmin:`collectstatic` management command:: $ python manage.py collectstatic This will copy all files from your static folders into the :setting:`STATIC_ROOT` directory. -3. Use a web server of your choice to serve the +#. Use a web server of your choice to serve the files. :doc:`/howto/static-files/deployment` covers some common deployment strategies for static files. diff --git a/docs/howto/upgrade-version.txt b/docs/howto/upgrade-version.txt index 9f433094b151..d5bfeff3eb52 100644 --- a/docs/howto/upgrade-version.txt +++ b/docs/howto/upgrade-version.txt @@ -8,7 +8,7 @@ version has several benefits: * New features and improvements are added. * Bugs are fixed. * Older version of Django will eventually no longer receive security updates. - (see :ref:`backwards-compatibility-policy`). + (see :ref:`supported-versions-policy`). * Upgrading as each new Django release is available makes future upgrades less painful by keeping your code base up to date. @@ -33,6 +33,14 @@ the new Django version(s): Pay particular attention to backwards incompatible changes to get a clear idea of what will be needed for a successful upgrade. +If you're upgrading through more than one feature version (e.g. A.B to A.B+2), +it's usually easier to upgrade through each feature release incrementally +(A.B to A.B+1 to A.B+2) rather than to make all the changes for each feature +release at once. For each feature release, use the latest patch release (A.B.C). + +The same incremental upgrade approach is recommended when upgrading from one +LTS to the next. + Dependencies ============ @@ -51,12 +59,12 @@ warnings before upgrading ensures that you're informed about areas of the code that need altering. In Python, deprecation warnings are silenced by default. You must turn them on -using the ``-Wall`` Python command line option or the :envvar:`PYTHONWARNINGS` +using the ``-Wa`` Python command line option or the :envvar:`PYTHONWARNINGS` environment variable. For example, to show warnings while running tests: -.. code-block:: console +.. console:: - $ python -Wall manage.py test + $ python -Wa manage.py test If you're not using the Django test runner, you may need to also ensure that any console output is not captured which would hide deprecation warnings. For @@ -64,7 +72,7 @@ example, if you use `py.test`: .. code-block:: console - $ PYTHONWARNINGS=all py.test tests --capture=no + $ PYTHONWARNINGS=always py.test tests --capture=no Resolve any deprecation warnings with your current version of Django before continuing the upgrade process. @@ -81,19 +89,12 @@ Once you're ready, it is time to :doc:`install the new Django version `. If you are using virtualenv_ and it is a major upgrade, you might want to set up a new environment with all the dependencies first. -Exactly which steps you will need to take depends on your installation process. -The most convenient way is to use pip_ with the ``--upgrade`` or ``-U`` flag: +If you installed Django with pip_, you can use the ``--upgrade`` or ``-U`` flag: -.. code-block:: console +.. console:: $ pip install -U Django -pip_ also automatically uninstalls the previous version of Django. - -If you use some other installation process, you might have to manually -:ref:`uninstall the old Django version ` and -should look at the complete installation instructions. - .. _pip: https://pip.pypa.io/ .. _virtualenv: https://virtualenv.pypa.io/ @@ -105,9 +106,9 @@ When the new environment is set up, :doc:`run the full test suite on deprecation warnings on so they're shown in the test output (you can also use the flag if you test your app manually using ``manage.py runserver``): -.. code-block:: console +.. console:: - $ python -Wall manage.py test + $ python -Wa manage.py test After you have run the tests, fix any failures. While you have the release notes fresh in your mind, it may also be a good time to take advantage of new diff --git a/docs/howto/windows.txt b/docs/howto/windows.txt index 536db09ffb18..c37c23eb4934 100644 --- a/docs/howto/windows.txt +++ b/docs/howto/windows.txt @@ -40,7 +40,7 @@ To install pip on your machine, go to https://pip.pypa.io/en/latest/installing/, and follow the ``Installing with get-pip.py`` instructions. -.. _pip: https://pypi.python.org/pypi/pip +.. _pip: https://pypi.org/project/pip/ Install ``virtualenv`` and ``virtualenvwrapper`` ================================================ @@ -62,8 +62,8 @@ command prompt, you'll need to activate the environment again using:: workon myproject -.. _virtualenv: https://pypi.python.org/pypi/virtualenv -.. _virtualenvwrapper: https://pypi.python.org/pypi/virtualenvwrapper-win +.. _virtualenv: https://pypi.org/project/virtualenv/ +.. _virtualenvwrapper: https://pypi.org/project/virtualenvwrapper-win/ Install Django ============== diff --git a/docs/howto/writing-migrations.txt b/docs/howto/writing-migrations.txt index f850e927ebcc..084513b312a0 100644 --- a/docs/howto/writing-migrations.txt +++ b/docs/howto/writing-migrations.txt @@ -22,7 +22,7 @@ attribute:: from django.db import migrations def forwards(apps, schema_editor): - if not schema_editor.connection.alias == 'default': + if schema_editor.connection.alias != 'default': return # Your migration code goes here @@ -39,10 +39,10 @@ attribute:: You can also provide hints that will be passed to the :meth:`allow_migrate()` method of database routers as ``**hints``: -.. snippet:: - :filename: myapp/dbrouters.py +.. code-block:: python + :caption: myapp/dbrouters.py - class MyRouter(object): + class MyRouter: def allow_migrate(self, db, app_label, model_name=None, **hints): if 'target_db' in hints: @@ -94,20 +94,16 @@ the respective field according to your needs. give them meaningful names in the examples below. * Copy the ``AddField`` operation from the auto-generated migration (the first - of the three new files) to the last migration and change ``AddField`` to - ``AlterField``. For example: + of the three new files) to the last migration, change ``AddField`` to + ``AlterField``, and add imports of ``uuid`` and ``models``. For example: - .. snippet:: - :filename: 0006_remove_uuid_null.py + .. code-block:: python + :caption: 0006_remove_uuid_null.py - # -*- coding: utf-8 -*- # Generated by Django A.B on YYYY-MM-DD HH:MM - from __future__ import unicode_literals - from django.db import migrations, models import uuid - class Migration(migrations.Migration): dependencies = [ @@ -125,8 +121,8 @@ the respective field according to your needs. * Edit the first migration file. The generated migration class should look similar to this: - .. snippet:: - :filename: 0004_add_uuid_field.py + .. code-block:: python + :caption: 0004_add_uuid_field.py class Migration(migrations.Migration): @@ -149,23 +145,21 @@ the respective field according to your needs. * In the first empty migration file, add a :class:`~django.db.migrations.operations.RunPython` or :class:`~django.db.migrations.operations.RunSQL` operation to generate a - unique value (UUID in the example) for each existing row. For example: + unique value (UUID in the example) for each existing row. Also add an import + of ``uuid``. For example: - .. snippet:: - :filename: 0005_populate_uuid_values.py + .. code-block:: python + :caption: 0005_populate_uuid_values.py - # -*- coding: utf-8 -*- # Generated by Django A.B on YYYY-MM-DD HH:MM - from __future__ import unicode_literals - - from django.db import migrations, models + from django.db import migrations import uuid def gen_uuid(apps, schema_editor): MyModel = apps.get_model('myapp', 'MyModel') for row in MyModel.objects.all(): row.uuid = uuid.uuid4() - row.save() + row.save(update_fields=['uuid']) class Migration(migrations.Migration): @@ -189,8 +183,6 @@ the respective field according to your needs. Non-atomic migrations ~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 1.10 - On databases that support DDL transactions (SQLite and PostgreSQL), migrations will run inside a transaction by default. For use cases such as performing data migrations on large tables, you may want to prevent a migration from running in @@ -229,7 +221,10 @@ smaller batches:: ] The ``atomic`` attribute doesn't have an effect on databases that don't support -DDL transactions (e.g. MySQL, Oracle). +DDL transactions (e.g. MySQL, Oracle). (MySQL's `atomic DDL statement support +`_ refers to individual +statements rather than multiple statements wrapped in a transaction that can be +rolled back.) Controlling the order of migrations =================================== @@ -287,8 +282,8 @@ project anywhere without first installing and then uninstalling the old app. Here's a sample migration: -.. snippet:: - :filename: myapp/migrations/0124_move_old_app_to_new_app.py +.. code-block:: python + :caption: myapp/migrations/0124_move_old_app_to_new_app.py from django.apps import apps as global_apps from django.db import migrations diff --git a/docs/index.txt b/docs/index.txt index ab38fa02f907..6139c3e9b889 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -17,15 +17,13 @@ Having trouble? We'd like to help! * Search for information in the archives of the |django-users| mailing list, or `post a question`_. -* Ask a question in the `#django IRC channel`_, or search the `IRC logs`_ to see - if it's been asked before. +* Ask a question in the `#django IRC channel`_. * Report bugs with Django in our `ticket tracker`_. .. _archives: https://groups.google.com/group/django-users/ .. _post a question: https://groups.google.com/d/forum/django-users .. _#django IRC channel: irc://irc.freenode.net/django -.. _IRC logs: http://django-irc-logs.com/ .. _ticket tracker: https://code.djangoproject.com/ How the documentation is organized @@ -87,7 +85,7 @@ manipulating the data of your Web application. Learn more about it below: :doc:`Model class ` * **QuerySets:** - :doc:`Executing queries ` | + :doc:`Making queries ` | :doc:`QuerySet method reference ` | :doc:`Lookup expressions ` @@ -229,7 +227,8 @@ testing of Django applications: :doc:`Overview ` | :doc:`WSGI servers ` | :doc:`Deploying static files ` | - :doc:`Tracking code errors by email ` + :doc:`Tracking code errors by email ` | + :doc:`Deployment checklist ` The admin ========= @@ -275,15 +274,6 @@ more efficiently - faster, and using fewer system resources. * :doc:`Performance and optimization overview ` -Python compatibility -==================== - -Django aims to be compatible with multiple different flavors and versions of -Python: - -* :doc:`Jython support ` -* :doc:`Python 3 compatibility ` - Geographic framework ==================== @@ -339,8 +329,6 @@ you can contribute: :doc:`How to get involved ` | :doc:`The release process ` | :doc:`Team organization ` | - :doc:`Meet the team ` | - :doc:`Current roles ` | :doc:`The Django source code repository ` | :doc:`Security policies ` | :doc:`Mailing lists ` diff --git a/docs/internals/contributing/committing-code.txt b/docs/internals/contributing/committing-code.txt index d42fdb8940bf..d0513e2dd4d1 100644 --- a/docs/internals/contributing/committing-code.txt +++ b/docs/internals/contributing/committing-code.txt @@ -2,10 +2,9 @@ Committing code =============== -This section is addressed to the :ref:`committers` and to anyone interested in -knowing how code gets committed into Django core. If you're a community member -who wants to contribute code to Django, have a look at -:doc:`writing-code/working-with-git` instead. +This section is addressed to the committers and to anyone interested in knowing +how code gets committed into Django. If you're a community member who wants to +contribute code to Django, look at :doc:`writing-code/working-with-git` instead. .. _handling-pull-requests: @@ -21,11 +20,9 @@ best pull requests possible. In practice however, committers - who will likely be more familiar with the commit guidelines - may decide to bring a commit up to standard themselves. -.. note:: - - Before merging, but after reviewing, have Jenkins test the pull request by - commenting "buildbot, test this please" on the PR. - See our `Jenkins wiki page`_ for more details. +You may want to have Jenkins test the pull request with one of the pull request +builders that doesn't run automatically, such as Oracle or Selenium. See the +`Jenkins wiki page`_ for instructions. .. _Jenkins wiki page: https://code.djangoproject.com/wiki/Jenkins @@ -42,7 +39,7 @@ At this point, you can work on the code. Use ``git rebase -i`` and ``git commit --amend`` to make sure the commits have the expected level of quality. Once you're ready: -.. code-block:: console +.. console:: $ # Pull in the latest changes from master. $ git checkout master @@ -62,13 +59,9 @@ Once you're ready: $ # Delete the pull request branch. $ git branch -d pr/xxxx -For changes on your own branches, force push to your fork after rebasing on -master but before merging and pushing to upstream. This allows the commit -hashes on master and your branch to match which automatically closes the pull -request. Since you can't push to other contributors' branches, comment on the -pull request "Merged in XXXXXXX" (replacing with the commit hash) after you -merge it. Trac checks for this message format to indicate on the ticket page -whether or not a pull request is merged. +Force push to the branch after rebasing on master but before merging and +pushing to upstream. This allows the commit hashes on master and the branch to +match which automatically closes the pull request. If a pull request doesn't need to be merged as multiple commits, you can use GitHub's "Squash and merge" button on the website. Edit the commit message as @@ -132,9 +125,11 @@ Django's Git repository: The commit message should be in lines of 72 chars maximum. There should be a subject line, separated by a blank line and then paragraphs of 72 char lines. The limits are soft. For the subject line, shorter is better. In the - body of the commit message more detail is better than less:: + body of the commit message more detail is better than less: + + .. code-block:: none - Fixed #18307 -- Added git workflow guidelines + Fixed #18307 -- Added git workflow guidelines. Refactored the Django's documentation to remove mentions of SVN specific tasks. Added guidelines of how to use Git, GitHub, and @@ -154,8 +149,7 @@ Django's Git repository: commit. This goes a *long way* in helping everyone follow your changes. * Separate bug fixes from feature changes. Bugfixes may need to be backported - to the stable branch, according to the :ref:`backwards-compatibility policy - `. + to the stable branch, according to :ref:`supported-versions-policy`. * If your commit closes a ticket in the Django `ticket tracker`_, begin your commit message with the text "Fixed #xxxxx", where "xxxxx" is the @@ -164,10 +158,6 @@ Django's Git repository: format will automatically close the referenced ticket and post a comment to it with the full commit message. - If your commit closes a ticket and is in a branch, use the branch name - first, then the "Fixed #xxxxx." For example: - "[1.4.x] Fixed #123 -- Added whizbang feature." - For the curious, we're using a `Trac plugin`_ for this. .. note:: @@ -184,13 +174,17 @@ Django's Git repository: is the number of the ticket your commit references. This will automatically post a comment to the appropriate ticket. -* Write commit messages for backports using this pattern:: +* Write commit messages for backports using this pattern: + + .. code-block:: none [] Fixed -- Backport of from . - For example:: + For example: + + .. code-block:: none [1.3.x] Fixed #17028 -- Changed diveintopython.org -> diveintopython.net. @@ -200,6 +194,14 @@ Django's Git repository: `_ to automate this. + If the commit fixes a regression, include this in the commit message: + + .. code-block:: none + + Regression in 6ecccad711b52f9273b1acb07a57d3f806e93928. + + (use the commit hash where the regression was introduced). + Reverting commits ================= diff --git a/docs/internals/contributing/index.txt b/docs/internals/contributing/index.txt index 2614df9fd65f..9a1e5d64d7f5 100644 --- a/docs/internals/contributing/index.txt +++ b/docs/internals/contributing/index.txt @@ -6,6 +6,11 @@ Django is a community that lives on its volunteers. As it keeps growing, we always need more people to help others. As soon as you learn Django, you can contribute in many ways: +* Join the `Django forum`_. This forum is a place for discussing the Django + framework and applications and projects that use it. This is also a good + place to ask and answer any questions related to installing, using, or + contributing to Django. + * Join the |django-users| mailing list and answer questions. This mailing list has a huge audience, and we really want to maintain a friendly and helpful atmosphere. If you're new to the Django community, @@ -32,7 +37,8 @@ development: * :doc:`Report bugs ` in our `ticket tracker`_. * Join the |django-developers| mailing list and share your ideas for how - to improve Django. We're always open to suggestions. + to improve Django. We're always open to suggestions. You can also interact + on the `#django-dev IRC channel`_. * :doc:`Submit patches ` for new and/or fixed behavior. If you're looking for an easy way to start contributing @@ -63,7 +69,9 @@ Browse the following sections to find out how: .. _posting guidelines: https://code.djangoproject.com/wiki/UsingTheMailingList .. _#django IRC channel: irc://irc.freenode.net/django +.. _#django-dev IRC channel: irc://irc.freenode.net/django-dev .. _community page: https://www.djangoproject.com/community/ +.. _Django forum: https://forum.djangoproject.com/ .. _register it here: https://www.djangoproject.com/community/add/blogs/ .. _ticket tracker: https://code.djangoproject.com/ .. _easy pickings: https://code.djangoproject.com/query?status=!closed&easy=1 diff --git a/docs/internals/contributing/localizing.txt b/docs/internals/contributing/localizing.txt index e3df902549e6..1c9e1ec2f363 100644 --- a/docs/internals/contributing/localizing.txt +++ b/docs/internals/contributing/localizing.txt @@ -46,11 +46,12 @@ translating or add a language that isn't yet translated, here's what to do: `Transifex User Guide`_. Translations from Transifex are only integrated into the Django repository at -the time of a new :term:`feature release`. We try to update them a second time -during one of the following :term:`patch release`\s, but that depends on the -translation manager's availability. So don't miss the string freeze period -(between the release candidate and the feature release) to take the opportunity -to complete and fix the translations for your language! +the time of a new :term:`feature release `. We try to update +them a second time during one of the following :term:`patch release +`\s, but that depends on the translation manager's availability. +So don't miss the string freeze period (between the release candidate and the +feature release) to take the opportunity to complete and fix the translations +for your language! Formats ======= @@ -70,7 +71,7 @@ Django source tree, as for any code change: .. _Transifex: https://www.transifex.com/ .. _Django project page: https://www.transifex.com/django/django/ -.. _Transifex User Guide: http://docs.transifex.com/ +.. _Transifex User Guide: https://docs.transifex.com/ .. _translating-documentation: diff --git a/docs/internals/contributing/new-contributors.txt b/docs/internals/contributing/new-contributors.txt index 90fa97cf287c..de63137286d4 100644 --- a/docs/internals/contributing/new-contributors.txt +++ b/docs/internals/contributing/new-contributors.txt @@ -15,13 +15,6 @@ First steps Start with these easy tasks to discover Django's development process. -* **Sign the Contributor License Agreement** - - The code that you write belongs to you or your employer. If your - contribution is more than one or two lines of code, you need to sign the - `CLA`_. See the `Contributor License Agreement FAQ`_ for a more thorough - explanation. - * **Triage tickets** If an `unreviewed ticket`_ reports a bug, try and reproduce it. If you @@ -63,6 +56,13 @@ Start with these easy tasks to discover Django's development process. .. _reports page: https://code.djangoproject.com/wiki/Reports +* **Sign the Contributor License Agreement** + + The code that you write belongs to you or your employer. If your + contribution is more than one or two lines of code, you need to sign the + `CLA`_. See the `Contributor License Agreement FAQ`_ for a more thorough + explanation. + .. _CLA: https://www.djangoproject.com/foundation/cla/ .. _Contributor License Agreement FAQ: https://www.djangoproject.com/foundation/cla/faq/ .. _unreviewed ticket: https://code.djangoproject.com/query?status=!closed&stage=Unreviewed diff --git a/docs/internals/contributing/triaging-tickets.txt b/docs/internals/contributing/triaging-tickets.txt index 3f1c7768fc25..043ad9315447 100644 --- a/docs/internals/contributing/triaging-tickets.txt +++ b/docs/internals/contributing/triaging-tickets.txt @@ -404,8 +404,6 @@ the ticket database: but your input is still valuable. .. _Trac: https://code.djangoproject.com/ -.. _i18n branch: https://code.djangoproject.com/browser/django/branches/i18n -.. _`tags/releases`: https://code.djangoproject.com/browser/django/tags/releases .. _`easy pickings`: https://code.djangoproject.com/query?status=!closed&easy=1 .. _`creating an account on Trac`: https://www.djangoproject.com/accounts/register/ .. _password reset page: https://www.djangoproject.com/accounts/password/reset/ diff --git a/docs/internals/contributing/writing-code/coding-style.txt b/docs/internals/contributing/writing-code/coding-style.txt index fe0a21c5a57c..fc516859bc58 100644 --- a/docs/internals/contributing/writing-code/coding-style.txt +++ b/docs/internals/contributing/writing-code/coding-style.txt @@ -33,24 +33,43 @@ Python style * Use four spaces for indentation. +* Use four space hanging indentation rather than vertical alignment:: + + raise AttributeError( + 'Here is a multiline error message ' + 'shortened for clarity.' + ) + + Instead of:: + + raise AttributeError('Here is a multiline error message ' + 'shortened for clarity.') + + This makes better use of space and avoids having to realign strings if the + length of the first line changes. + +* Use single quotes for strings, or a double quote if the string contains a + single quote. Don't waste time doing unrelated refactoring of existing code + to conform to this style. + +* Avoid use of "we" in comments, e.g. "Loop over" rather than "We loop over". + * Use underscores, not camelCase, for variable, function and method names (i.e. ``poll.get_unique_voters()``, not ``poll.getUniqueVoters()``). * Use ``InitialCaps`` for class names (or for factory functions that return classes). -* In docstrings, follow :pep:`257`. For example:: +* In docstrings, follow the style of existing docstrings and :pep:`257`. - def foo(): - """ - Calculate something and return the result. - """ - ... - -* In tests, use :meth:`~django.test.SimpleTestCase.assertRaisesMessage` instead - of :meth:`~unittest.TestCase.assertRaises` so you can check the exception - message. Use :meth:`~unittest.TestCase.assertRaisesRegex` only if you need - regular expression matching. +* In tests, use + :meth:`~django.test.SimpleTestCase.assertRaisesMessage` and + :meth:`~django.test.SimpleTestCase.assertWarnsMessage` + instead of :meth:`~unittest.TestCase.assertRaises` and + :meth:`~unittest.TestCase.assertWarns` so you can check the + exception or warning message. Use :meth:`~unittest.TestCase.assertRaisesRegex` + and :meth:`~unittest.TestCase.assertWarnsRegex` only if you need regular + expression matching. * In test docstrings, state the expected behavior that each test demonstrates. Don't include preambles such as "Tests that" or "Ensures that". @@ -75,7 +94,7 @@ Imports Quick start: - .. code-block:: console + .. console:: $ pip install isort $ isort -rc . @@ -93,7 +112,7 @@ Imports imports for other Django components and relative imports for local components. * On each line, alphabetize the items with the upper case items grouped before - the lower case items. + the lowercase items. * Break long lines using parentheses and indent continuation lines by 4 spaces. Include a trailing comma after the last import and put the closing @@ -104,8 +123,8 @@ Imports For example (comments are for explanatory purposes only): - .. snippet:: - :filename: django/contrib/admin/example.py + .. code-block:: python + :caption: django/contrib/admin/example.py # future from __future__ import unicode_literals @@ -136,7 +155,7 @@ Imports CONSTANT = 'foo' - class Example(object): + class Example: # ... * Use convenience imports whenever available. For example, do this:: @@ -228,10 +247,6 @@ Model style first_name = models.CharField(max_length=20) last_name = models.CharField(max_length=40) -* If you define a ``__str__`` method (previously ``__unicode__`` before Python 3 - was supported), decorate the model class with - :func:`~django.utils.encoding.python_2_unicode_compatible`. - * The order of model inner classes and standard methods should be as follows (noting that these are not all required): @@ -243,17 +258,17 @@ Model style * ``def get_absolute_url()`` * Any custom methods -* If ``choices`` is defined for a given model field, define each choice as - a tuple of tuples, with an all-uppercase name as a class attribute on the - model. Example:: +* If ``choices`` is defined for a given model field, define each choice as a + list of tuples, with an all-uppercase name as a class attribute on the model. + Example:: class MyModel(models.Model): DIRECTION_UP = 'U' DIRECTION_DOWN = 'D' - DIRECTION_CHOICES = ( + DIRECTION_CHOICES = [ (DIRECTION_UP, 'Up'), (DIRECTION_DOWN, 'Down'), - ) + ] Use of ``django.conf.settings`` =============================== @@ -320,5 +335,5 @@ JavaScript style For details about the JavaScript code style used by Django, see :doc:`javascript`. -.. _editorconfig: http://editorconfig.org/ -.. _flake8: https://pypi.python.org/pypi/flake8 +.. _editorconfig: https://editorconfig.org/ +.. _flake8: https://pypi.org/project/flake8/ diff --git a/docs/internals/contributing/writing-code/javascript.txt b/docs/internals/contributing/writing-code/javascript.txt index 3d10c48f0039..941fa085e8a7 100644 --- a/docs/internals/contributing/writing-code/javascript.txt +++ b/docs/internals/contributing/writing-code/javascript.txt @@ -53,7 +53,7 @@ To simplify the process of providing optimized JavaScript code, Django includes a handy Python script which should be used to create a "minified" version. To run it: -.. code-block:: console +.. console:: $ pip install closure $ python django/contrib/admin/bin/compress.py @@ -81,32 +81,32 @@ Django's JavaScript tests use `QUnit`_. Here is an example test module: .. code-block:: javascript - module('magicTricks', { + QUnit.module('magicTricks', { beforeEach: function() { var $ = django.jQuery; $('#qunit-fixture').append(''); } }); - test('removeOnClick removes button on click', function(assert) { + QUnit.test('removeOnClick removes button on click', function(assert) { var $ = django.jQuery; removeOnClick('.button'); - assert.equal($('.button').length === 1); + assert.equal($('.button').length, 1); $('.button').click(); - assert.equal($('.button').length === 0); + assert.equal($('.button').length, 0); }); - test('copyOnClick adds button on click', function(assert) { + QUnit.test('copyOnClick adds button on click', function(assert) { var $ = django.jQuery; copyOnClick('.button'); - assert.equal($('.button').length === 1); + assert.equal($('.button').length, 1); $('.button').click(); - assert.equal($('.button').length === 2); + assert.equal($('.button').length, 2); }); Please consult the QUnit documentation for information on the types of -`assertions supported by QUnit `_. +`assertions supported by QUnit `_. Running tests ------------- @@ -122,8 +122,8 @@ browser. To measure code coverage when running the tests, you need to view that file over HTTP. To view code coverage: -* Execute ``python -m http.server`` (or ``python -m SimpleHTTPServer`` on - Python 2) from the root directory (not from inside ``js_tests``). +* Execute ``python -m http.server`` from the root directory (not from inside + ``js_tests``). * Open http://localhost:8000/js_tests/tests.html in your web browser. Testing from the command line @@ -134,19 +134,19 @@ To run the tests from the command line, you need to have `Node.js`_ installed. After installing `Node.js`, install the JavaScript test dependencies by running the following from the root of your Django checkout: -.. code-block:: console +.. console:: $ npm install Then run the tests with: -.. code-block:: console +.. console:: $ npm test .. _Closure Compiler: https://developers.google.com/closure/compiler/ -.. _EditorConfig: http://editorconfig.org/ +.. _EditorConfig: https://editorconfig.org/ .. _Java: https://www.java.com -.. _jshint: http://jshint.com/ +.. _jshint: https://jshint.com/ .. _node.js: https://nodejs.org/ .. _qunit: https://qunitjs.com/ diff --git a/docs/internals/contributing/writing-code/submitting-patches.txt b/docs/internals/contributing/writing-code/submitting-patches.txt index cbda36075d8d..455374b3476a 100644 --- a/docs/internals/contributing/writing-code/submitting-patches.txt +++ b/docs/internals/contributing/writing-code/submitting-patches.txt @@ -175,7 +175,7 @@ the first release of Django that deprecates a feature (``A.B``) should raise a will be removed) when the deprecated feature is invoked. Assuming we have good test coverage, these warnings are converted to errors when :ref:`running the test suite ` with warnings enabled: -``python -Wall runtests.py``. Thus, when adding a ``RemovedInDjangoXXWarning`` +``python -Wa runtests.py``. Thus, when adding a ``RemovedInDjangoXXWarning`` you need to eliminate or silence any warnings generated when running the tests. The first step is to remove any use of the deprecated behavior by Django itself. @@ -201,20 +201,15 @@ level: class MyDeprecatedTests(unittest.TestCase): ... -You can also add a test for the deprecation warning. You'll have to disable the -"warning as error" behavior in your test by doing:: +You can also add a test for the deprecation warning:: - import warnings + from django.utils.deprecation import RemovedInDjangoXXWarning def test_foo_deprecation_warning(self): - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter('always') # prevent warnings from appearing as errors + msg = 'Expected deprecation message' + with self.assertWarnsMessage(RemovedInDjangoXXWarning, msg): # invoke deprecated behavior - self.assertEqual(len(warns), 1) - msg = str(warns[0].message) - self.assertEqual(msg, 'Expected deprecation message') - Finally, there are a couple of updates to Django's documentation to make: #) If the existing feature is documented, mark it deprecated in documentation @@ -229,8 +224,8 @@ Finally, there are a couple of updates to Django's documentation to make: under the appropriate version describing what code will be removed. Once you have completed these steps, you are finished with the deprecation. -In each :term:`feature release`, all ``RemovedInDjangoXXWarning``\s matching -the new version are removed. +In each :term:`feature release `, all +``RemovedInDjangoXXWarning``\s matching the new version are removed. JavaScript patches ================== @@ -273,6 +268,10 @@ Bugs * Is there a proper regression test (the test should fail before the fix is applied)? +* If it's a bug that :ref:`qualifies for a backport ` + to the stable version of Django, is there a release note in + ``docs/releases/A.B.C.txt``? Bug fixes that will be applied only to the + master branch don't need a release note. New Features ------------ diff --git a/docs/internals/contributing/writing-code/unit-tests.txt b/docs/internals/contributing/writing-code/unit-tests.txt index 1bed50389827..16f2939e9f04 100644 --- a/docs/internals/contributing/writing-code/unit-tests.txt +++ b/docs/internals/contributing/writing-code/unit-tests.txt @@ -28,10 +28,10 @@ how to do that, read our :doc:`contributing tutorial `. Next, clone your fork, install some requirements, and run the tests:: - $ git clone git@github.com:YourGitHubName/django.git django-repo + $ git clone https://github.com/YourGitHubName/django.git django-repo $ cd django-repo/tests $ pip install -e .. - $ pip install -r requirements/py3.txt # Python 2: py2.txt + $ pip install -r requirements/py3.txt $ ./runtests.py Installing the requirements will likely require some operating system packages @@ -39,8 +39,7 @@ that your computer doesn't have installed. You can usually figure out which package to install by doing a Web search for the last line or so of the error message. Try adding your operating system to the search query if needed. -If you have trouble installing the requirements, you can skip that step, except -on Python 2, where you must ``pip install mock``. See +If you have trouble installing the requirements, you can skip that step. See :ref:`running-unit-tests-dependencies` for details on installing the optional test dependencies. If you don't have an optional dependency installed, the tests that require it will be skipped. @@ -61,7 +60,7 @@ Having problems? See :ref:`troubleshooting-unit-tests` for some common issues. Running tests using ``tox`` --------------------------- -`Tox `_ is a tool for running tests in different +`Tox `_ is a tool for running tests in different virtual environments. Django includes a basic ``tox.ini`` that automates some checks that our build server performs on pull requests. To run the unit tests and other checks (such as :ref:`import sorting `, the @@ -75,9 +74,8 @@ command from any place in the Django source tree:: By default, ``tox`` runs the test suite with the bundled test settings file for SQLite, ``flake8``, ``isort``, and the documentation spelling checker. In addition to the system dependencies noted elsewhere in this documentation, -the commands ``python2`` and ``python3`` must be on your path and linked to -the appropriate versions of Python. A list of default environments can be seen -as follows:: +the command ``python3`` must be on your path and linked to the appropriate +version of Python. A list of default environments can be seen as follows:: $ tox -l py3 @@ -125,6 +123,15 @@ necessary for the majority of patches. To run the JavaScript tests using This command runs ``npm install`` to ensure test requirements are up to date and then runs ``npm test``. +Running tests using ``django-docker-box`` +----------------------------------------- + +`django-docker-box`_ allows you to run the Django's test suite across all +supported databases and python versions. See the `django-docker-box`_ project +page for installation and usage instructions. + +.. _django-docker-box: https://github.com/django/django-docker-box + .. _running-unit-tests-settings: Using another ``settings`` module @@ -225,25 +232,25 @@ dependencies: * argon2-cffi_ 16.1.0+ * bcrypt_ * docutils_ -* enum34_ (Python 2 only) * geoip2_ * jinja2_ 2.7+ * numpy_ * Pillow_ * PyYAML_ * pytz_ (required) +* pywatchman_ * setuptools_ * memcached_, plus a :ref:`supported Python binding ` -* mock_ (for Python 2) * gettext_ (:ref:`gettext_on_windows`) * selenium_ -* sqlparse_ +* sqlparse_ 0.2.2+ (required) +* tblib_ 1.5.0+ You can find these dependencies in `pip requirements files`_ inside the ``tests/requirements`` directory of the Django source tree and install them like so:: - $ pip install -r tests/requirements/py3.txt # Python 2: py2.txt + $ pip install -r tests/requirements/py3.txt If you encounter an error during the installation, your system might be missing a dependency for one or more of the Python packages. Consult the failing @@ -262,23 +269,27 @@ and install the Geospatial libraries`. Each of these dependencies is optional. If you're missing any of them, the associated tests will be skipped. -.. _argon2-cffi: https://pypi.python.org/pypi/argon2_cffi -.. _bcrypt: https://pypi.python.org/pypi/bcrypt -.. _docutils: https://pypi.python.org/pypi/docutils -.. _enum34: https://pypi.python.org/pypi/enum34 -.. _geoip2: https://pypi.python.org/pypi/geoip2 -.. _jinja2: https://pypi.python.org/pypi/jinja2 -.. _numpy: https://pypi.python.org/pypi/numpy -.. _Pillow: https://pypi.python.org/pypi/Pillow/ -.. _PyYAML: http://pyyaml.org/wiki/PyYAML -.. _pytz: https://pypi.python.org/pypi/pytz/ -.. _setuptools: https://pypi.python.org/pypi/setuptools/ -.. _memcached: http://memcached.org/ -.. _mock: https://pypi.python.org/pypi/mock +To run some of the autoreload tests, you'll need to install the Watchman_ +service. + +.. _argon2-cffi: https://pypi.org/project/argon2_cffi/ +.. _bcrypt: https://pypi.org/project/bcrypt/ +.. _docutils: https://pypi.org/project/docutils/ +.. _geoip2: https://pypi.org/project/geoip2/ +.. _jinja2: https://pypi.org/project/jinja2/ +.. _numpy: https://pypi.org/project/numpy/ +.. _Pillow: https://pypi.org/project/Pillow/ +.. _PyYAML: https://pyyaml.org/wiki/PyYAML +.. _pytz: https://pypi.org/project/pytz/ +.. _pywatchman: https://pypi.org/project/pywatchman/ +.. _setuptools: https://pypi.org/project/setuptools/ +.. _memcached: https://memcached.org/ .. _gettext: https://www.gnu.org/software/gettext/manual/gettext.html -.. _selenium: https://pypi.python.org/pypi/selenium -.. _sqlparse: https://pypi.python.org/pypi/sqlparse -.. _pip requirements files: https://pip.pypa.io/en/latest/user_guide.html#requirements-files +.. _selenium: https://pypi.org/project/selenium/ +.. _sqlparse: https://pypi.org/project/sqlparse/ +.. _pip requirements files: https://pip.pypa.io/en/latest/user_guide/#requirements-files +.. _tblib: https://pypi.org/project/tblib/ +.. _Watchman: https://facebook.github.io/watchman/ Code coverage ------------- @@ -315,6 +326,27 @@ in ``tests/auth_tests``. Troubleshooting =============== +Test suite hangs or shows failures on ``master`` branch +------------------------------------------------------- + +Ensure you have the latest point release of a :ref:`supported Python version +`, since there are often bugs in earlier versions +that may cause the test suite to fail or hang. + +On **macOS** (High Sierra and newer versions), you might see this message +logged, after which the tests hang:: + + objc[42074]: +[__NSPlaceholderDate initialize] may have been in progress in + another thread when fork() was called. + +To avoid this set a ``OBJC_DISABLE_INITIALIZE_FORK_SAFETY`` environment +variable, for example:: + + $ OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES ./runtests.py + +Or add ``export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES`` to your shell's +startup file (e.g. ``~/.profile``). + Many test failures with ``UnicodeEncodeError`` ---------------------------------------------- @@ -402,12 +434,12 @@ You can also use the ``DJANGO_TEST_PROCESSES`` environment variable for this purpose. Tips for writing tests ----------------------- +====================== .. highlight:: python Isolating model registration -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +---------------------------- To avoid polluting the global :attr:`~django.apps.apps` registry and prevent unnecessary table creation, models defined in a test method should be bound to @@ -428,8 +460,6 @@ a temporary ``Apps`` instance:: .. function:: django.test.utils.isolate_apps(*app_labels, attr_name=None, kwarg_name=None) -.. versionadded:: 1.10 - Since this pattern involves a lot of boilerplate, Django provides the :func:`~django.test.utils.isolate_apps` decorator. It's used like this:: @@ -454,8 +484,8 @@ Since this pattern involves a lot of boilerplate, Django provides the :func:`~django.test.utils.isolate_apps` instances are correctly installed, you should pass the set of targeted ``app_label`` as arguments: - .. snippet:: - :filename: tests/app_label/tests.py + .. code-block:: python + :caption: tests/app_label/tests.py from django.db import models from django.test import SimpleTestCase diff --git a/docs/internals/contributing/writing-code/working-with-git.txt b/docs/internals/contributing/writing-code/working-with-git.txt index 85ca6dfc8526..60ba1345a480 100644 --- a/docs/internals/contributing/writing-code/working-with-git.txt +++ b/docs/internals/contributing/writing-code/working-with-git.txt @@ -18,7 +18,7 @@ Installing Git ============== Django uses `Git`_ for its source control. You can `download -`_ Git, but it's often easier to install with +`_ Git, but it's often easier to install with your operating system's package manager. Django's `Git repository`_ is hosted on `GitHub`_, and it is recommended @@ -34,7 +34,7 @@ Note that ``user.name`` should be your real name, not your GitHub nick. GitHub should know the email you use in the ``user.email`` field, as this will be used to associate your commits with your GitHub account. -.. _Git: http://git-scm.com/ +.. _Git: https://git-scm.com/ .. _Git repository: https://github.com/django/django/ .. _GitHub: https://github.com/ @@ -45,7 +45,7 @@ When you have created your GitHub account, with the nick "GitHub_nick", and `forked Django's repository `__, create a local copy of your fork:: - git clone git@github.com:GitHub_nick/django.git + git clone https://github.com/GitHub_nick/django.git This will create a new directory "django", containing a clone of your GitHub repository. The rest of the git commands on this page need to be run within the diff --git a/docs/internals/contributing/writing-documentation.txt b/docs/internals/contributing/writing-documentation.txt index e19b5854fdf6..76a5b31c2120 100644 --- a/docs/internals/contributing/writing-documentation.txt +++ b/docs/internals/contributing/writing-documentation.txt @@ -45,16 +45,20 @@ documentation is transformed into HTML, PDF, and any other output format. __ http://sphinx-doc.org/ __ http://docutils.sourceforge.net/ -To actually build the documentation locally, you'll currently need to install -Sphinx -- ``pip install Sphinx`` should do the trick. +To build the documentation locally, install Sphinx: -Then, building the HTML is easy; just ``make html`` (or ``make.bat html`` on -Windows) from the ``docs`` directory. +.. console:: + + $ pip install Sphinx + +Then from the ``docs`` directory, build the HTML: + +.. console:: + + $ make html To get started contributing, you'll want to read the :ref:`reStructuredText -Primer `. After that, you'll want to read about the -:ref:`Sphinx-specific markup ` that's used to manage -metadata, indexing, and cross-references. +reference `. Your locally-built documentation will be themed differently than the documentation at `docs.djangoproject.com `_. @@ -125,6 +129,12 @@ Instead of: * his or hers... use theirs. * himself or herself... use themselves. +Try to avoid using words that minimize the difficulty involved in a task or +operation, such as "easily", "simply", "just", "merely", "straightforward", and +so on. People's experience may not match your expectations, and they may become +frustrated when they do not find a step as "straightforward" or "simple" as it +is implied to be. + Commonly used terms =================== @@ -222,11 +232,15 @@ documentation: Five ^^^^ +* Use :rst:role:`:rfc:` to reference RFC and and try to link to the + relevant section if possible. For example, use ``:rfc:`2324#section-2.3.2``` + or ``:rfc:`Custom link text <2324#section-2.3.2>```. + Django-specific markup ====================== -Besides the :ref:`Sphinx built-in markup `, Django's -docs defines some extra description units: +Besides :ref:`Sphinx's built-in markup `, Django's docs +define some extra description units: * Settings:: @@ -269,6 +283,50 @@ docs defines some extra description units: :ticket:`12345` +Django's documentation uses a custom ``console`` directive for documenting +command-line examples involving ``django-admin.py``, ``manage.py``, ``python``, +etc.). In the HTML documentation, it renders a two-tab UI, with one tab showing +a Unix-style command prompt and a second tab showing a Windows prompt. + +For example, you can replace this fragment:: + + use this command: + + .. code-block:: console + + $ python manage.py shell + +with this one:: + + use this command: + + .. console:: + + $ python manage.py shell + +Notice two things: + +* You usually will replace occurrences of the ``.. code-block:: console`` + directive. +* You don't need to change the actual content of the code example. You still + write it assuming a Unix-y environment (i.e. a ``'$'`` prompt symbol, + ``'/'`` as filesystem path components separator, etc.) + +The example above will render a code example block with two tabs. The first +one will show: + +.. code-block:: console + + $ python manage.py shell + +(No changes from what ``.. code-block:: console`` would have rendered). + +The second one will show: + +.. code-block:: doscon + + ...\> py manage.py shell + .. _documenting-new-features: Documenting new features @@ -337,7 +395,7 @@ AdvanceCOMP's ``advpng``: .. code-block:: console - $ cd docs/ + $ cd docs $ optipng -o7 -zm1-9 -i0 -strip all `find . -type f -not -path "./_build/*" -name "*.png"` $ advpng -z4 `find . -type f -not -path "./_build/*" -name "*.png"` @@ -426,11 +484,11 @@ Spelling check Before you commit your docs, it's a good idea to run the spelling checker. You'll need to install a couple packages first: -* `pyenchant `_ (which requires - `enchant `_) +* `pyenchant `_ (which requires + `enchant `_) * `sphinxcontrib-spelling - `_ + `_ Then from the ``docs`` directory, run ``make spelling``. Wrong words (if any) along with the file and line number where they occur will be saved to diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 42db85127f18..087458fa5e99 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -7,6 +7,71 @@ in a backward incompatible way, following their deprecation, as per the :ref:`deprecation policy `. More details about each item can often be found in the release notes of two versions prior. +.. _deprecation-removed-in-3.1: + +3.1 +--- + +See the :ref:`Django 2.2 release notes ` for more +details on these changes. + +* ``django.utils.timezone.FixedOffset`` will be removed. + +* ``django.core.paginator.QuerySetPaginator`` will be removed. + +* A model's ``Meta.ordering`` will no longer affect ``GROUP BY`` queries. + +* ``django.contrib.postgres.fields.FloatRangeField`` and + ``django.contrib.postgres.forms.FloatRangeField`` will be removed. + +* The ``FILE_CHARSET`` setting will be removed. + +* ``django.contrib.staticfiles.storage.CachedStaticFilesStorage`` will be + removed. + +* ``RemoteUserBackend.configure_user()`` will require ``request`` as the first + positional argument. + +* Support for ``SimpleTestCase.allow_database_queries`` and + ``TransactionTestCase.multi_db`` will be removed. + +.. _deprecation-removed-in-3.0: + +3.0 +--- + +See the :ref:`Django 2.0 release notes` for more +details on these changes. + +* The ``django.db.backends.postgresql_psycopg2`` module will be removed. + +* ``django.shortcuts.render_to_response()`` will be removed. + +* The ``DEFAULT_CONTENT_TYPE`` setting will be removed. + +* ``HttpRequest.xreadlines()`` will be removed. + +* Support for the ``context`` argument of ``Field.from_db_value()`` and + ``Expression.convert_value()`` will be removed. + +* The ``field_name`` keyword argument of ``QuerySet.earliest()`` and + ``latest()`` will be removed. + +See the :ref:`Django 2.1 release notes ` for more +details on these changes. + +* ``django.contrib.gis.db.models.functions.ForceRHR`` will be removed. + +* ``django.utils.http.cookie_date()`` will be removed. + +* The ``staticfiles`` and ``admin_static`` template tag libraries will be + removed. + +* ``django.contrib.staticfiles.templatetags.static()`` will be removed. + +* The shim to allow ``InlineModelAdmin.has_add_permission()`` to be defined + without an ``obj`` argument will be removed. + .. _deprecation-removed-in-2.1: 2.1 @@ -38,8 +103,8 @@ details on these changes. * ``DatabaseIntrospection.get_indexes()`` will be removed. -* The ``authenticate()`` method of authentication backends will require a - ``request`` argument. +* The ``authenticate()`` method of authentication backends will require + ``request`` as the first positional argument. * The ``django.db.models.permalink()`` decorator will be removed. @@ -48,8 +113,8 @@ details on these changes. * The ``Model._meta.has_auto_field`` attribute will be removed. -* Support for regular expression groups with ``iLmsu#`` in ``url()`` will be - removed. +* ``url()``'s support for inline flags in regular expression groups (``(?i)``, + ``(?L)``, ``(?m)``, ``(?s)``, and ``(?u)``) will be removed. * Support for ``Widget.render()`` methods without the ``renderer`` argument will be removed. @@ -125,7 +190,7 @@ details on these changes. ``django.utils.feedgenerator.RssFeed`` will be removed in favor of ``content_type``. -* The ``app_name`` argument to :func:`~django.conf.urls.include()` will be +* The ``app_name`` argument to ``django.conf.urls.include()`` will be removed. * Support for passing a 3-tuple as the first argument to ``include()`` will @@ -571,7 +636,7 @@ details on these changes. * The ``SEND_BROKEN_LINK_EMAILS`` setting will be removed. Add the :class:`django.middleware.common.BrokenLinkEmailsMiddleware` middleware to - your :setting:`MIDDLEWARE_CLASSES` setting instead. + your ``MIDDLEWARE_CLASSES`` setting instead. * ``django.middleware.doc.XViewMiddleware`` will be removed. Use ``django.contrib.admindocs.middleware.XViewMiddleware`` instead. @@ -595,9 +660,6 @@ details on these changes. * Support for the Python Imaging Library (PIL) module will be removed, as it no longer appears to be actively maintained & does not work on Python 3. - You are advised to install `Pillow`_, which should be used instead. - - .. _`Pillow`: https://pypi.python.org/pypi/Pillow * The following private APIs will be removed: @@ -692,7 +754,7 @@ details on these changes. remove calls to this method, and instead ensure that their auth related views are CSRF protected, which ensures that cookies are enabled. -* The version of :func:`django.contrib.auth.views.password_reset_confirm` that +* The version of ``django.contrib.auth.views.password_reset_confirm()`` that supports base36 encoded user IDs (``django.contrib.auth.views.password_reset_confirm_uidb36``) will be removed. If your site has been running Django 1.6 for more than @@ -764,10 +826,9 @@ details on these changes. ``django.contrib.gis.utils`` will be removed. * ``django.conf.urls.defaults`` will be removed. The functions - :func:`~django.conf.urls.include`, ``patterns()`` and - :func:`~django.conf.urls.url` plus :data:`~django.conf.urls.handler404`, - :data:`~django.conf.urls.handler500`, are now available through - :mod:`django.conf.urls` . + ``include()``, ``patterns()``, and ``url()``, plus + :data:`~django.conf.urls.handler404` and :data:`~django.conf.urls.handler500` + are now available through ``django.conf.urls``. * The functions ``setup_environ()`` and ``execute_manager()`` will be removed from :mod:`django.core.management`. This also means that the old (pre-1.4) @@ -807,10 +868,9 @@ details on these changes. used instead. * The ``django.test.simple.DjangoTestRunner`` will be removed. - Instead use a unittest-native class. The features of the + Instead use a ``unittest``-native class. The features of the ``django.test.simple.DjangoTestRunner`` (including fail-fast and - Ctrl-C test termination) can currently be provided by the unittest-native - :class:`~unittest.TextTestRunner`. + Ctrl-C test termination) can be provided by :class:`unittest.TextTestRunner`. * The undocumented function ``django.contrib.formtools.utils.security_hash`` will be removed, diff --git a/docs/internals/git.txt b/docs/internals/git.txt index 0e35544eb48c..f71fcf251ae6 100644 --- a/docs/internals/git.txt +++ b/docs/internals/git.txt @@ -39,21 +39,18 @@ The Git repository includes several `branches`_: They are also used for bugfix and security releases which occur as necessary after the initial release of a feature version. -* ``soc20XX/`` branches were used by students who worked on Django - during the 2009 and 2010 Google Summer of Code programs. - -* ``attic/`` branches were used to develop major or experimental new - features without affecting the rest of Django's code. - The Git repository also contains `tags`_. These are the exact revisions from which packaged Django releases were produced, since version 1.0. +A number of tags also exist under the ``archive/`` prefix for :ref:`archived +work`. + The source code for the `Djangoproject.com `_ website can be found at `github.com/django/djangoproject.com `_. -.. _Git: http://git-scm.com/ -.. _documentation: http://git-scm.com/documentation +.. _Git: https://git-scm.com/ +.. _documentation: https://git-scm.com/documentation .. _branches: https://github.com/django/django/branches .. _tags: https://github.com/django/django/tags @@ -81,18 +78,11 @@ over to :doc:`the documentation for contributing to Django `, which covers things like the preferred coding style and how to generate and submit a patch. -Other branches -============== - -Django uses branches to prepare for releases of Django. - -In the past when Django was hosted on Subversion, branches were also used for -feature development. Now Django is hosted on Git and feature development is -done on contributor's forks, but the Subversion feature branches remain in Git -for historical reference. - Stable branches ---------------- +=============== + +Django uses branches to prepare for releases of Django. Each major release +series has its own stable branch. These branches can be found in the repository as ``stable/A.B.x`` branches and will be created right after the first alpha is tagged. @@ -101,14 +91,14 @@ For example, immediately after *Django 1.5 alpha 1* was tagged, the branch ``stable/1.5.x`` was created and all further work on preparing the code for the final 1.5 release was done there. -These branches also provide limited bugfix support for the most recent released -version of Django and security support for the two most recently-released -versions of Django. +These branches also provide bugfix and security support as described in +:ref:`supported-versions-policy`. For example, after the release of Django 1.5, the branch ``stable/1.5.x`` receives only fixes for security and critical stability bugs, which are eventually released as Django 1.5.1 and so on, ``stable/1.4.x`` receives only -security fixes, and ``stable/1.3.x`` no longer receives any updates. +security and data loss fixes, and ``stable/1.3.x`` no longer receives any +updates. .. admonition:: Historical information @@ -117,18 +107,29 @@ security fixes, and ``stable/1.3.x`` no longer receives any updates. Previously, these branches weren't created until right after the releases and the stabilization work occurred on the main repository branch. Thus, - no new features development work for the next release of Django could be + no new feature development work for the next release of Django could be committed until the final release happened. For example, shortly after the release of Django 1.3 the branch ``stable/1.3.x`` was created. Official support for that release has expired, and so it no longer receives direct maintenance from the Django project. - However, that and all other similarly named branches continue to exist and + However, that and all other similarly named branches continue to exist, and interested community members have occasionally used them to provide unofficial support for old Django releases. -Feature-development branches ----------------------------- +Tags +==== + +Each Django release is tagged and signed by the releaser. + +The tags can be found on GitHub's `tags`_ page. + +.. _tags: https://github.com/django/django/tags + +.. _archived-feature-development-work: + +Archived feature-development work +--------------------------------- .. admonition:: Historical information @@ -142,17 +143,19 @@ Feature-development branches Feature-development branches tend by their nature to be temporary. Some produce successful features which are merged back into Django's master to -become part of an official release, but others do not; in either case there +become part of an official release, but others do not; in either case, there comes a time when the branch is no longer being actively worked on by any developer. At this point the branch is considered closed. -Unfortunately, Django used to be maintained with the Subversion revision -control system, that has no standard way of indicating this. As a workaround, -branches of Django which are closed and no longer maintained were moved into -``attic``. +Django used to be maintained with the Subversion revision control system, that +has no standard way of indicating this. As a workaround, branches of Django +which are closed and no longer maintained were moved into ``attic``. + +A number of tags exist under the ``archive/`` prefix to maintain a reference to +this and other work of historical interest. -For reference, the following are branches whose code eventually became -part of Django itself, and so are no longer separately maintained: +The following tags under the ``archive/attic/`` prefix reference the tip of +branches whose code eventually became part of Django itself: * ``boulder-oracle-sprint``: Added support for Oracle databases to Django's object-relational mapper. This has been part of Django @@ -192,31 +195,9 @@ part of Django itself, and so are no longer separately maintained: Unicode-based strings in most places within Django and Django applications. This became part of Django as of the 1.0 release. -When Django moved from SVN to Git, the information about branch merges wasn't -preserved in the source code repository. This means that the ``master`` branch -of Django doesn't contain merge commits for the above branches. - -However, this information is `available as a grafts file`_. You can restore it -by putting the following lines in ``.git/info/grafts`` in your local clone:: - - ac64e91a0cadc57f4bc5cd5d66955832320ca7a1 553a20075e6991e7a60baee51ea68c8adc520d9a 0cb8e31823b2e9f05c4ae868c19f5f38e78a5f2e - 79e68c225b926302ebb29c808dda8afa49856f5c d0f57e7c7385a112cb9e19d314352fc5ed5b0747 aa239e3e5405933af6a29dac3cf587b59a099927 - 5cf8f684237ab5addaf3549b2347c3adf107c0a7 cb45fd0ae20597306cd1f877efc99d9bd7cbee98 e27211a0deae2f1d402537f0ebb64ad4ccf6a4da - f69cf70ed813a8cd7e1f963a14ae39103e8d5265 d5dbeaa9be359a4c794885c2e9f1b5a7e5e51fb8 d2fcbcf9d76d5bb8a661ee73dae976c74183098b - aab3a418ac9293bb4abd7670f65d930cb0426d58 4ea7a11659b8a0ab07b0d2e847975f7324664f10 adf4b9311d5d64a2bdd58da50271c121ea22e397 - ff60c5f9de3e8690d1e86f3e9e3f7248a15397c8 7ef212af149540aa2da577a960d0d87029fd1514 45b4288bb66a3cda401b45901e85b645674c3988 - 9dda4abee1225db7a7b195b84c915fdd141a7260 4fe5c9b7ee09dc25921918a6dbb7605edb374bc9 3a7c14b583621272d4ef53061287b619ce3c290d - a19ed8aea395e8e07164ff7d85bd7dff2f24edca dc375fb0f3b7fbae740e8cfcd791b8bccb8a4e66 42ea7a5ce8aece67d16c6610a49560c1493d4653 - 9c52d56f6f8a9cdafb231adf9f4110473099c9b5 c91a30f00fd182faf8ca5c03cd7dbcf8b735b458 4a5c5c78f2ecd4ed8859cd5ac773ff3a01bccf96 - 953badbea5a04159adbfa970f5805c0232b6a401 4c958b15b250866b70ded7d82aa532f1e57f96ae 5664a678b29ab04cad425c15b2792f4519f43928 - 471596fc1afcb9c6258d317c619eaf5fd394e797 4e89105d64bb9e04c409139a41e9c7aac263df4c 3e9035a9625c8a8a5e88361133e87ce455c4fc13 - 9233d0426537615e06b78d28010d17d5a66adf44 6632739e94c6c38b4c5a86cf5c80c48ae50ac49f 18e151bc3f8a85f2766d64262902a9fcad44d937 - -.. _available as a grafts file: https://github.com/ramiro/django-git-grafts - -Additionally, the following branches are closed, but their code was -never merged into Django and the features they aimed to implement -were never finished: +Additionally, the following tags under the ``archive/attic/`` prefix reference +the tips of branches that were closed, but whose code was never merged into +Django, and the features they aimed to implement were never finished: * ``full-history`` @@ -234,17 +215,7 @@ were never finished: * ``sqlalchemy`` -All of the above-mentioned branches now reside in ``attic``. - -Finally, the repository contains ``soc2009/xxx`` and ``soc2010/xxx`` feature -branches, used for Google Summer of Code projects. - -Tags -==== - -Each Django release is tagged and signed by a :ref:`releaser -`. - -The tags can be found on GitHub's `tags`_ page. - -.. _tags: https://github.com/django/django/tags +Finally, under the ``archive/`` prefix, the repository contains +``soc20XX/`` tags referencing the tip of branches that were used by +students who worked on Django during the 2009 and 2010 Google Summer of Code +programs. diff --git a/docs/internals/howto-release-django.txt b/docs/internals/howto-release-django.txt index 6a91069abce3..730196223d30 100644 --- a/docs/internals/howto-release-django.txt +++ b/docs/internals/howto-release-django.txt @@ -62,8 +62,8 @@ You'll need a few things before getting started: * Access to Django's record on PyPI. Create a file with your credentials: - .. snippet:: - :filename: ~/.pypirc + .. code-block:: ini + :caption: ~/.pypirc [pypi] username:YourUsername @@ -78,8 +78,8 @@ You'll need a few things before getting started: * If this is a security release, access to the pre-notification distribution list. -If this is your first release, you'll need to coordinate with James and/or -Jacob to get all these things lined up. +If this is your first release, you'll need to coordinate with another releaser +to get all these things lined up. Pre-release tasks ================= @@ -89,14 +89,13 @@ This stuff starts about a week before the release; most of it can be done any time leading up to the actual release: #. If this is a security release, send out pre-notification **one week** before - the release. We maintain a list of who gets these pre-notification emails in - the private ``django-core`` repository. Send the mail to - ``security@djangoproject.com`` and BCC the pre-notification recipients. - This email should be signed by the key you'll use for the release and - should include `CVE IDs `_ (requested with - Vendor: djangoproject, Product: django) and patches for each issue being - fixed. Also, :ref:`notify django-announce ` of the - upcoming security release. + the release. The template for that email and a list of the recipients are in + the private ``django-security`` GitHub wiki. BCC the pre-notification + recipients. Sign the email with the key you'll use for the release and + include `CVE IDs `_ (requested with Vendor: + djangoproject, Product: django) and patches for each issue being fixed. + Also, :ref:`notify django-announce ` of the upcoming + security release. #. As the release approaches, watch Trac to make sure no release blockers are left for the upcoming release. @@ -155,7 +154,7 @@ OK, this is the fun part, where we actually push out a release! #. Check `Jenkins`__ is green for the version(s) you're putting out. You probably shouldn't issue a release until it's green. - __ http://djangoci.com + __ https://djangoci.com #. A release always begins from a release branch, so you should make sure you're on a stable branch and up-to-date. For example:: @@ -164,14 +163,14 @@ OK, this is the fun part, where we actually push out a release! $ git pull #. If this is a security release, merge the appropriate patches from - ``django-private``. Rebase these patches as necessary to make each one a + ``django-security``. Rebase these patches as necessary to make each one a simple commit on the release branch rather than a merge commit. To ensure this, merge them with the ``--ff-only`` flag; for example:: $ git checkout stable/1.5.x $ git merge --ff-only security/1.5.x - (This assumes ``security/1.5.x`` is a branch in the ``django-private`` repo + (This assumes ``security/1.5.x`` is a branch in the ``django-security`` repo containing the necessary security patches for the next release in the 1.5 series.) @@ -180,9 +179,7 @@ OK, this is the fun part, where we actually push out a release! checkout security/1.5.x; git rebase stable/1.5.x``) and then switch back and do the merge. Make sure the commit message for each security fix explains that the commit is a security fix and that an announcement will follow - (`example security commit`__). - - __ https://github.com/django/django/commit/3ef4bbf495cc6c061789132e3d50a8231a89406b + (:commit:`example security commit `). #. For a feature release, remove the ``UNDER DEVELOPMENT`` header at the top of the release notes and add the release date on the next line. For a @@ -303,13 +300,13 @@ Now you're ready to actually put the release out there. To do this: $ pip install https://www.djangoproject.com/m/releases/$MAJOR_VERSION/Django-$RELEASE_VERSION.tar.gz $ deactivate $ mktmpenv - $ pip install https://www.djangoproject.com/m/releases/$MAJOR_VERSION/Django-$RELEASE_VERSION-py2.py3-none-any.whl + $ pip install https://www.djangoproject.com/m/releases/$MAJOR_VERSION/Django-$RELEASE_VERSION-py3-none-any.whl $ deactivate This just tests that the tarballs are available (i.e. redirects are up) and that they install correctly, but it'll catch silly mistakes. - __ https://pypi.python.org/pypi/virtualenvwrapper + __ https://pypi.org/project/virtualenvwrapper/ #. Ask a few people on IRC to verify the checksums by visiting the checksums file (e.g. https://www.djangoproject.com/m/pgp/Django-1.5b1.checksum.txt) @@ -338,6 +335,12 @@ Now you're ready to actually put the release out there. To do this: database (this will automatically flip it to ``False`` for all others); you can do this using the site's admin. + Create new ``DocumentRelease`` objects for each language that has an entry + for the previous release. Update djangoproject.com's `robots.docs.txt`__ + file by copying entries from the previous release. + + __ https://github.com/django/djangoproject.com/blob/master/djangoproject/static/robots.docs.txt + #. Post the release announcement to the |django-announce|, |django-developers|, and |django-users| mailing lists. This should include a link to the announcement blog post. If this is a security release, also include diff --git a/docs/internals/index.txt b/docs/internals/index.txt index e9c32fe30448..b76696abb59e 100644 --- a/docs/internals/index.txt +++ b/docs/internals/index.txt @@ -11,8 +11,6 @@ you'd like to help improve Django or learn about how Django is managed. contributing/index mailing-lists organization - team - roles security release-process deprecation diff --git a/docs/internals/mailing-lists.txt b/docs/internals/mailing-lists.txt index c39d3e3cc56a..d5b9ab5f9ced 100644 --- a/docs/internals/mailing-lists.txt +++ b/docs/internals/mailing-lists.txt @@ -59,6 +59,10 @@ the Django Project. The discussion about the development of Django itself takes place here. +Before asking a question about how to contribute, read +:doc:`/internals/contributing/index`. Many frequently asked questions are +answered there. + .. note:: Please make use of diff --git a/docs/internals/organization.txt b/docs/internals/organization.txt index 5982621757c3..b2d399255f66 100644 --- a/docs/internals/organization.txt +++ b/docs/internals/organization.txt @@ -74,9 +74,8 @@ need them through the same process. Membership ---------- -The core team finds its origins with the :ref:`four people -` who created Django. It has grown to :ref:`a few dozen -people ` by co-opting volunteers who demonstrate: +`Django team members `_ +demonstrate: - a good grasp of the philosophy of the Django Project - a solid track record of being constructive and helpful @@ -162,20 +161,19 @@ capacity on non-technical decisions. Membership ---------- -The technical board is an elected group of five committers. They're expected -to be experienced but there's no formal seniority requirement. Its current -composition is published :ref:`here `. +`The technical board`_ is an elected group of five committers. They're expected +to be experienced but there's no formal seniority requirement. A new board is elected after each feature release of Django. The election process is managed by a returns officer nominated by the outgoing technical board. The election process works as follows: -1. Candidates advertise their application for the technical board to the team. +#. Candidates advertise their application for the technical board to the team. They must be committers already. There's no term limit for technical board members. -2. Each team member can vote for zero to five people among the candidates. +#. Each team member can vote for zero to five people among the candidates. Candidates are ranked by the total number of votes they received. In case of a tie, the person who joined the core team earlier wins. @@ -183,6 +181,8 @@ board. The election process works as follows: Both the application and the voting period last between one and two weeks, at the outgoing board's discretion. +.. _the technical board: https://www.djangoproject.com/foundation/teams/#technical-board-team + Changing the organization ========================= diff --git a/docs/internals/release-process.txt b/docs/internals/release-process.txt index ad891c6225f8..ce600395eb71 100644 --- a/docs/internals/release-process.txt +++ b/docs/internals/release-process.txt @@ -59,11 +59,13 @@ security purposes, please see :doc:`our security policies `. .. _the download page: https://www.djangoproject.com/download/ +.. _internal-release-cadence: + Release cadence =============== Starting with Django 2.0, version numbers will use a loose form of `semantic -versioning `_ such that each version following an LTS will +versioning `_ such that each version following an LTS will bump to the next "dot zero" version. For example: 2.0, 2.1, 2.2 (LTS), 3.0, 3.1, 3.2 (LTS), etc. @@ -114,7 +116,7 @@ A more generic example: LTS to LTS upgrades). * Z.0: Drop deprecation shims added in Y.0 and Y.1. -.. _backwards-compatibility-policy: +.. _supported-versions-policy: Supported versions ================== @@ -137,7 +139,7 @@ page for the current state of support for each version. * Crashing bugs. - * Major functionality bugs in newly-introduced features. + * Major functionality bugs in new features of the latest stable release. * Regressions from older versions of Django. diff --git a/docs/internals/roles.txt b/docs/internals/roles.txt deleted file mode 100644 index aabd11dbb750..000000000000 --- a/docs/internals/roles.txt +++ /dev/null @@ -1,70 +0,0 @@ -===== -Roles -===== - -.. _technical-board-list: - -Technical board -=============== - -The technical board for the 1.11 release cycle is: - -* James Bennett -* Andrew Godwin -* Russell Keith-Magee -* Carl Meyer -* Marc Tamlyn - -.. _committers: - -Committers -========== - -Most :ref:`core team ` members have commit access. They're called -"committers" or "core developers". - -Being part of the core team is a pre-requisite for having commit access. - -.. _security-team-list: - -Security team -============= - -The security team is responsible for :doc:`Django's security policies -`. It handles private reports of security issues. - -The current security team members are: - -- Florian Apolloner -- James Bennett -- Tim Graham -- Adrian Holovaty -- Markus Holtermann -- Paul McMillan -- Carl Meyer - -.. _releasers-list: - -Releasers -========= - -Releasers take care of :doc:`building Django releases -`. - -The current releasers are: - -- James Bennett -- Jacob Kaplan-Moss -- Tim Graham - -Ops team -======== - -The ops team maintains Django's infrastructure like the Django Project server, -Trac instance, and continuous integration infrastructure. - -- Florian Apolloner -- Tim Graham -- Markus Holtermann -- Jannis Leidel -- Tobias McNulty diff --git a/docs/internals/security.txt b/docs/internals/security.txt index 148418e0fed9..8fb9583b63a7 100644 --- a/docs/internals/security.txt +++ b/docs/internals/security.txt @@ -23,9 +23,8 @@ publicly reported in this fashion. Instead, if you believe you've found something in Django which has security implications, please send a description of the issue via email to -``security@djangoproject.com``. Mail sent to that address reaches a -:ref:`subset of the core team `, who can forward security -issues into the private team's mailing list for broader discussion if needed. +``security@djangoproject.com``. Mail sent to that address reaches the `security +team `_. Once you've submitted an issue via email, you should receive an acknowledgment from a member of the security team within 48 hours, and depending on the @@ -94,6 +93,7 @@ triaging our announcement and upgrade Django as needed. Severity levels are: * Cross site scripting (XSS) * Cross site request forgery (CSRF) +* Denial-of-service attacks * Broken authentication **Low**: @@ -106,7 +106,7 @@ triaging our announcement and upgrade Django as needed. Severity levels are: Second, we notify a list of :ref:`people and organizations `, primarily composed of operating-system vendors and other distributors of Django. This email is signed with the PGP key of someone -from :ref:`Django's release team ` and consists of: +from `Django's release team`_ and consists of: * A full description of the issue and the affected versions of Django. @@ -119,21 +119,21 @@ from :ref:`Django's release team ` and consists of: On the day of disclosure, we will take the following steps: -1. Apply the relevant patch(es) to Django's codebase. +#. Apply the relevant patch(es) to Django's codebase. -2. Issue the relevant release(s), by placing new packages on `the +#. Issue the relevant release(s), by placing new packages on `the Python Package Index`_ and on the Django website, and tagging the new release(s) in Django's git repository. -3. Post a public entry on `the official Django development blog`_, +#. Post a public entry on `the official Django development blog`_, describing the issue and its resolution in detail, pointing to the relevant patches and new releases, and crediting the reporter of the issue (if the reporter wishes to be publicly identified). -4. Post a notice to the |django-announce| and oss-security@lists.openwall.com +#. Post a notice to the |django-announce| and oss-security@lists.openwall.com mailing lists that links to the blog post. -.. _the Python Package Index: https://pypi.python.org/pypi +.. _the Python Package Index: https://pypi.org/ .. _the official Django development blog: https://www.djangoproject.com/weblog/ If a reported issue is believed to be particularly time-sensitive -- @@ -150,6 +150,8 @@ theirs. The Django team also maintains an :doc:`archive of security issues disclosed in Django`. +.. _Django's release team: https://www.djangoproject.com/foundation/teams/#releasers-team + .. _security-notifications: Who receives advance notification diff --git a/docs/internals/team.txt b/docs/internals/team.txt deleted file mode 100644 index e5f5c96d742b..000000000000 --- a/docs/internals/team.txt +++ /dev/null @@ -1,738 +0,0 @@ -=========== -Django team -=========== - -.. _original-team-list: - -The original team -================= - -Django originally started at World Online, the Web department of the `Lawrence -Journal-World`_ of Lawrence, Kansas, USA. - -`Adrian Holovaty`_ - Adrian is a Web developer with a background in journalism. He's known in - journalism circles as one of the pioneers of "journalism via computer - programming", and in technical circles as "the guy who invented Django." - - He was lead developer at World Online for 2.5 years, during which time - Django was developed and implemented on World Online's sites. He was the - leader and founder of EveryBlock_, a "news feed for your block." He now - develops Soundslice_. - - Adrian lives in Chicago, USA. - -`Simon Willison`_ - Simon is a well-respected Web developer from England. He had a one-year - internship at World Online, during which time he and Adrian developed Django - from scratch. The most enthusiastic Brit you'll ever meet, he's passionate - about best practices in Web development and maintains a well-read - `web-development blog`_. - - Simon lives in Brighton, England. - -`Jacob Kaplan-Moss`_ - Jacob is Director of Platform Security at Heroku_. He worked at World - Online for four years, where he helped open source Django and found - the Django Software Foundation. Jacob lives on a hobby farm outside of - Lawrence where he spends his weekends playing with dirt and power tools. - -`Wilson Miner`_ - Wilson's design-fu is what makes Django look so nice. He created the design - that was used for nearly the first ten years on the Django Project website, - as well as the current design for Django's acclaimed admin interface. Wilson - was the designer for EveryBlock and Rdio_. He now designs for Facebook. - - Wilson lives in San Francisco, USA. - -.. _lawrence journal-world: http://ljworld.com/ -.. _adrian holovaty: http://holovaty.com/ -.. _everyblock: https://everyblock.com/ -.. _soundslice: https://www.soundslice.com/ -.. _simon willison: http://simonwillison.net/ -.. _web-development blog: `simon willison`_ -.. _jacob kaplan-moss: https://jacobian.org/ -.. _revolution systems: http://revsys.com/ -.. _wilson miner: http://wilsonminer.com/ -.. _heroku: https://heroku.com/ -.. _Rdio: http://rdio.com - -.. _core-team-list: - -The current team -================ - -These are the folks who have a long history of contributions, a solid track -record of being helpful on the mailing lists, and a proven desire to dedicate -serious time to Django. In return, they've been invited to join the :ref:`core -team `. - -`Luke Plant`_ - At University Luke studied physics and Materials Science and also - met `Michael Meeks`_ who introduced him to Linux and Open Source, - re-igniting an interest in programming. Since then he has - contributed to a number of Open Source projects and worked - professionally as a developer. - - Luke has contributed many excellent improvements to Django, - including database-level improvements, the CSRF middleware and - many unit tests. - - Luke currently works for a church in Bradford, UK, and part-time - as a freelance developer. - - .. _luke plant: http://lukeplant.me.uk/ - .. _michael meeks: https://en.wikipedia.org/wiki/Michael_Meeks_(software) - -`Russell Keith-Magee`_ - Russell studied physics as an undergraduate, and studied neural networks for - his PhD. His first job was with a startup in the defense industry developing - simulation frameworks. Over time, mostly through work with Django, he's - become more involved in Web development. - - Russell has helped with several major aspects of Django, including a - couple major internal refactorings, creation of the test system, and more. - - Russell lives in the most isolated capital city in the world — Perth, - Australia. - - .. _russell keith-magee: http://cecinestpasun.com/ - -`James Bennett`_ - James has been one of Django's release managers, and also - contributes to the documentation and provide the occasional - bugfix. - - James came to Web development from philosophy when he discovered - that programmers get to argue just as much while collecting much - better pay. He lives in San Mateo, California and previously - worked at World Online and Mozilla; currently, he's part of the - Web engineering team at `Clover`_. - - He `keeps a blog`_, and enjoys fine port and talking to his car. - - .. _james bennett: http://b-list.org/ - .. _Clover: https://www.cloverhealth.com/ - .. _keeps a blog: `james bennett`_ - -Justin Bronn - Justin Bronn is a computer scientist and attorney specializing - in legal topics related to intellectual property and spatial law. - - In 2007, Justin began developing ``django.contrib.gis`` in a branch, - a.k.a. GeoDjango_, which was merged in time for Django 1.0. While - implementing GeoDjango, Justin obtained a deep knowledge of Django's - internals including the ORM, the admin, and Oracle support. - - Justin lives in Houston, TX. - - .. _GeoDjango: http://geodjango.org/ - -Karen Tracey - Karen has a background in distributed operating systems (graduate school), - communications software (industry) and crossword puzzle construction - (freelance). The last of these brought her to Django, in late 2006, when - she set out to put a Web front-end on her crossword puzzle database. - That done, she stuck around in the community answering questions, debugging - problems, etc. -- because coding puzzles are as much fun as word puzzles. - - Karen lives in Apex, NC, USA. - -`Jannis Leidel`_ - Jannis graduated in media design from `Bauhaus-University Weimar`_, - is the author of a number of pluggable Django apps and likes to - contribute to Open Source projects like virtualenv_ and pip_. - - He has worked on Django's auth, admin and staticfiles apps as well as - the form, core, internationalization and test systems. He currently works - at Mozilla_. - - Jannis lives in Berlin, Germany. - - .. _Jannis Leidel: https://jezdez.com/ - .. _Bauhaus-University Weimar: http://www.uni-weimar.de/ - .. _virtualenv: https://virtualenv.pypa.io/ - .. _pip: https://pip.pypa.io/ - .. _Mozilla: https://www.mozilla.org/ - -`Andrew Godwin`_ - Andrew is a freelance Python developer and tinkerer, and has been - developing against Django since 2007. He graduated from Oxford University - with a degree in Computer Science, and has become most well known - in the Django community for his work on South, the schema migrations - library. - - Andrew lives in San Francisco, CA, USA. - - .. _Andrew Godwin: https://www.aeracode.org/ - -`Carl Meyer`_ - Carl has been a Django user since 2007 (long enough to remember - queryset-refactor, but not magic-removal), and builds web apps at OddBird_. - He became a Django contributor by accident, because fixing bugs is more - interesting than working around them. - - Carl lives in Rapid City, SD, USA. - - .. _Carl Meyer: http://www.oddbird.net/ - .. _OddBird: http://www.oddbird.net/ - -Ramiro Morales - Ramiro has been reading Django source code and submitting patches since - mid-2006 after researching for a Python Web tool with matching awesomeness - and being pointed to it by an old ninja. - - A software developer in the electronic transactions industry, he is a - living proof of the fact that anyone with enough enthusiasm can contribute - to Django, learning a lot and having fun in the process. - - Ramiro lives in Córdoba, Argentina. - -`Chris Beaven`_ - Chris has been submitting patches and suggesting crazy ideas for Django - since early 2006. An advocate for community involvement and a long-term - triager, he is still often found answering questions in the `#django` IRC - channel. - - Chris lives in Napier, New Zealand (adding to the pool of Oceanic core - developers). He works remotely as a developer for `Lincoln Loop`_. - - .. _Chris Beaven: http://smileychris.com/ - .. _Lincoln Loop: https://lincolnloop.com/ - -Honza Král - Honza first discovered Django in 2006 and started using it right away, - first for school and personal projects and later in his full-time job. He - contributed various patches and fixes mostly to the newforms library, - newforms admin and, through participation in the Google Summer of Code - project, assisted in creating the :ref:`model validation - ` functionality. - - He is currently working for `Whiskey Media`_ in San Francisco developing - awesome sites running on pure Django. - - .. _Whiskey Media: http://www.whiskeymedia.com/ - -Tim Graham - When exploring Web frameworks for an independent study project in the fall - of 2008, Tim discovered Django and was lured to it by the documentation. - He enjoys contributing to the docs because they're awesome. - - Tim works as a software engineer and lives in Philadelphia, PA, USA. - -Paul McMillan - Paul found Django in 2008 while looking for a more - structured approach to web programming. He stuck around after - figuring out that the developers of Django had already invented - many of the wheels he needed. His passion for breaking (and then - fixing) things led to his current role working to maintain and - improve the security of Django. - -`Julien Phalip`_ - Julien has a background in software engineering and human-computer - interaction. As a Web developer, he enjoys tinkering with the backend as - much as designing and coding user interfaces. Julien discovered Django in - 2007 while doing his PhD in Computing Sciences. Since then he has - contributed patches to various components of the framework, in particular - the admin. Julien was a co-founder of the `Interaction Consortium`_. He - now works at Odopod_, a digital agency based in San Francisco, CA, USA. - - .. _Julien Phalip: http://julienphalip.com - .. _Interaction Consortium: http://interaction.net.au - .. _Odopod: http://odopod.com - -`Aymeric Augustin`_ - Aymeric is an engineer with a background in mathematics and computer - science. He chose Django because he believes that software should be simple, - explicit and tested. His perfectionist tendencies quickly led him to triage - tickets and contribute patches. - - Aymeric has a pragmatic approach to software engineering, can't live without - a continuous integration server, and likes proving that Django is a good - choice for enterprise software. - - .. _Aymeric Augustin: https://myks.org/ - -`Claude Paroz`_ - Claude is a former teacher who fell in love with free software at the - beginning of the 21st century. He's now working as freelancer in Web - development in his native Switzerland. He has found in Django a perfect - match for his needs of a stable, clean, documented and well-maintained Web - framework. - - He's also helping the GNOME Translation Project as maintainer of the - Django-based `l10n.gnome.org`_. - - .. _Claude Paroz: http://www.2xlibre.net - .. _l10n.gnome.org: https://l10n.gnome.org - -Anssi Kääriäinen - Anssi works as a Senior Backend Developer at `Holvi`, a Finnish financial - technology company building digital banking solutions for freelancers and - small businesses. What makes `Holvi` an exciting challenge for Anssi is - using technology to create something real people care for and actually want - to use. In his daily work, he's building a full stack bank with Django. - - Anssi works mostly on ORM related issues and enjoys making the ORM solve - complex query problems. - -Florian Apolloner - Florian is currently studying Physics at the `Graz University of Technology`_. - Soon after he started using Django he joined the `Ubuntuusers webteam`_ to - work on *Inyoka*, the software powering the whole Ubuntuusers site. - - For the time being he lives in Graz, Austria (not Australia ;)). - - .. _Graz University of Technology: http://tugraz.at/ - .. _Ubuntuusers webteam: https://wiki.ubuntuusers.de/ubuntuusers/Webteam - -Jeremy Dunck - Jeremy was rescued from corporate IT drudgery by Free Software and, in part, - Django. Many of Jeremy's interests center around access to information. - - Jeremy was the lead developer of Pegasus News, one of the first uses of - Django outside World Online, and has since joined Votizen, a startup intent - on reducing the influence of money in politics. - - He serves as DSF Secretary, organizes and helps organize sprints, cares - about the health and equity of the Django community. He has gone an - embarrassingly long time without a working blog. - - Jeremy lives in Mountain View, CA, USA. - -`Bryan Veloso`_ - Bryan found Django 0.96 through a fellow designer who was evangelizing - its use. It was his first foray outside of the land that was PHP-based - templating. Although he has only ever used Django for personal projects, - it is the very reason he considers himself a designer/developer - hybrid and is working to further design within the Django community. - - Bryan works as a designer at GitHub by day, and masquerades as a `vlogger`_ - and `shoutcaster`_ in the after-hours. Bryan lives in Los Angeles, CA, USA. - - .. _bryan veloso: http://avalonstar.com/ - .. _vlogger: https://youtube.com/bryanveloso/ - .. _shoutcaster: http://twitch.tv/vlogalonstar/ - -`Simon Charette`_ - Simon is a mathematics student who discovered Django while searching for a - replacement framework to an in-house PHP entity. Since that faithful day - Django has been a big part of his life. So far, he's been involved in some - ORM and forms API fixes. - - Apart from contributing to multiple open source projects he spends most of - his spare-time playing `Ultimate Frisbee`_ and working part-time - at this awesome place called `Reptiletech`_. - - Simon lives in Montréal, Québec, Canada. - - .. _Simon Charette: https://github.com/charettes - .. _Ultimate Frisbee: http://www.montrealultimate.ca - .. _Reptiletech: https://www.reptiletech.com - -Donald Stufft - Donald found Python and Django in 2007 while trying to find a language, - and web framework that he really enjoyed using after many years of PHP. He - fell in love with the beauty of Python and the way Django made tasks simple - and easy. His contributions to Django focus primarily on ensuring that it - is and remains a secure web framework. - - Donald currently works at `Nebula Inc`_ as a Software Engineer for their - security team and lives in the Greater Philadelphia Area. - - .. _Nebula Inc: https://www.nebula.com/ - -Marc Tamlyn - Marc started life on the web using Django 1.2 back in 2010, and has never - looked back. He was involved with rewriting the class-based view - documentation at DjangoCon EU 2012, and also helped to develop `CCBV`_, an - additional class-based view reference tool. - - Marc is currently a full-time parent, part-time developer, and lives in - Oxford, UK. - - .. _CCBV: https://ccbv.co.uk/ - -Shai Berger - Shai started working with Python back in 1998, and with Django just - before 1.0. He is a Free Software enthusiast, but life happens, and - he was driven by consulting gigs to contribute to the Oracle and - SQL Server backends of South, and then the Oracle backend of Django - itself. Finally, he joined core to help maintain the Oracle backend. - - Shai works for `Platonix`_, a small consulting company he started - with a few friends in 1996, and lives near Tel Aviv, Israel. - - .. _Platonix: http://tech.platonix.com - -Baptiste Mispelon - Baptiste discovered Django around the 1.2 version and promptly switched away - from his homegrown PHP framework. He started getting more involved in the - project after attending DjangoCon EU 2012, mostly by triaging tickets and - submitting small patches. - - Baptiste currently lives in Budapest, Hungary and works for `M2BPO`_, - a small French company providing services to architects. - - .. _M2BPO: https://www.m2bpo.fr - -Daniele Procida - Daniele unexpectedly became a Django developer on 29th April 2009. Since - then he has relied daily on Django's documentation, which has been a - constant companion to him. More recently he has been able to contribute - back to the project by helping improve the documentation itself. - - He is the author of `Arkestra`_ and `Don't be afraid to commit`_. He lives - in Cardiff, Wales, and works for `Divio`_. - - .. _Divio: https://divio.ch/ - .. _Arkestra: http://arkestra-project.org/ - .. _Don\'t be afraid to commit: https://dont-be-afraid-to-commit.readthedocs.io - -`Erik Romijn`_ - Erik started using Django in the days of 1.2. His largest contribution to Django was - ``GenericIPAddressField``, and he has worked on all sorts of patches since. - While developing with Django, he always keeps a little list of even the slightest - Django frustrations, to tackle them at a later time and prevent other developers - from having to deal with the same issues. - - Erik is an independent app maker, mostly developing web and mobile apps, as - `Solid Links`_. He also enjoys helping ordinary developers to build safer web apps, - for which Django is already a great start, and developed `Erik's Pony Checkup`_ with - that goal in mind. Erik lives in Amsterdam, The Netherlands. - - .. _Erik Romijn: http://erik.io/ - .. _Solid Links: https://solidlinks.nl/ - .. _Erik's Pony Checkup: https://ponycheckup.com/ - -`Loïc Bistuer`_ - Loïc studied telecommunications engineering and works as an independent - software developer and consultant. - - He discovered Django in 2008 shortly before the 1.0 release and has been - hooked ever since. He contributes mostly to Django's ORM and Form - components. His main contributions include advanced query prefetching, - streamlining QuerySet and Manager to improve query reusability, and a - significant refactor of forms error handling. - - Loïc is originally from the South of France and currently lives in - Bangkok, Thailand. - - .. _Loïc Bistuer: https://github.com/loic - -`Michael Manfre`_ - Michael started running Django on Windows against a Microsoft SQL Server - (MSSQL) database in 2008. He quickly became the maintainer of the - ``django-mssql`` database backend. Much of his involvement with Django - relates to the ORM, the private 3rd party database API, and using Django on - Windows. - - Michael lives in Cary, NC, USA. - - .. _Michael Manfre: http://manfre.net - -`Collin Anderson`_ - Collin found Django in November 2006. He was in awe of the admin and ORM - and was amazed that the documentation was teaching him best web practices - like redirecting after a successful POST request. Why had he never learned - this before? No one knows to this day. - - He enjoys helping people on the |django-users| mailing list and making - Django simple and easy for newcomers. - - Collin lives in South Bend, IN, USA where he uses Django to `increase - unity`_. - - .. _Collin Anderson: https://github.com/collinanderson - .. _increase unity: http://onetencommunications.com/about/ - -`Tom Christie`_ - Tom has background in speech recognition, networking, and web development. - He has a particular interest in Web API design and is the original author - of `Django REST framework`_. - - Tom lives in the seaside city of Brighton, UK. - - .. _Tom Christie: https://twitter.com/_tomchristie - .. _Django REST framework: http://django-rest-framework.org - -`Curtis Maloney`_ - Curtis is a self-taught programmer from Melbourne, Australia, who eschews - specialization. Upon finding Django when it was first open sourced, he - realized it was possible to enjoy web development. - - He spends a lot of time helping people on the `#django` IRC channel, and - has authored and released a number of `smaller Django apps`_. - - .. _Curtis Maloney: http://musings.tinbrain.net/blog/ - .. _smaller Django apps: https://github.com/funkybob/ - -`Markus Holtermann`_ - Markus is a senior backend developer at `LaterPay`_ in Munich. He studied - Computer Science at the `Technical University of Berlin`_. He started - working with Django in 2010 when he joined the `ubuntuusers.de`_ web team - to work on *Inyoka*. Markus made his first contribution to the Django - project during DjangoCon Europe 2013 in Warsaw. He was the web team leader - for the `EuroPython 2014 website`_ and started regular contributions to - Django after that. - - Markus lives in Berlin, Germany. - - .. _Markus Holtermann: https://github.com/MarkusH - .. _LaterPay: https://www.laterpay.net/ - .. _Technical University of Berlin: http://www.tu-berlin.de/ - .. _ubuntuusers.de: https://ubuntuusers.de/ - .. _EuroPython 2014 website: https://ep2014.europython.eu/ - -`Josh Smeaton`_ - Josh was given the opportunity to work on a new Django app around version - 1.1 after working with a homegrown PHP reporting framework. The simplicity - of the ORM and the power of the Admin were extremely liberating. - - Still being involved with custom reporting applications, he decided to try - his hand at improving the ORM support for analytics. His contributions - focus on giving more power to users of the ORM. - - Josh lives in Melbourne, Australia where he heads up development for a SaaS - telecommunications company. - - .. _Josh Smeaton: https://github.com/jarshwah - -`Preston Timmons`_ - Preston is a software developer with a background in mathematics. He enjoys - Django because it enables consistent, simple, and tested systems to be - built that even new programmers can quickly dive into. Preston lives in - Dallas, TX. - - .. _Preston Timmons: https://github.com/prestontimmons - -`Tomek Paczkowski`_ - Tomek started using Django in 2007 as a tool for quickly dealing with - university projects. Since then, he worked with various technologies - like Ruby on Rails, JavaScript and Android but always returned to - Python and Django. - - Tomek loves the Django community. He organized multiple Django - sprints, co-organized `DjangoCon Europe 2013`_ and has mentored at many - `Django Girls`_ events. - - Originally from Poland, Tomek currently lives in London, where he - works at Squirrel_. - - .. _Tomek Paczkowski: https://hauru.eu - .. _DjangoCon Europe 2013: http://love.djangocircus.com - .. _Django Girls: https://djangogirls.org - .. _Squirrel: https://squirrel.me - -`Ola Sitarska`_ - Ola started working with Django in 2009, when she discovered the power of - the Django admin and quickly fell in love with the beauty of Python. - - She co-organized `DjangoCon Europe 2013`_ in Warsaw and co-authored the - `Django Girls Tutorial`_, the most beginner friendly Django tutorial out - there. Together with Ola Sendecka, she started `Django Girls`_, a community - and series of Django workshops for women who've never programmed before. - - In 2015, she became a Django Software Foundation board member. Ola was also - a part of the team responsible for shipping the djangoproject.com redesign. - - Originally from Poland, Ola currently lives in London, where she - works with friends at `Potato`_. - - .. _Ola Sitarska: http://ola.sitarska.com/ - .. _DjangoCon Europe 2013: http://love.djangocircus.com - .. _Django Girls Tutorial: http://tutorial.djangogirls.org - .. _Django Girls: https://djangogirls.org - .. _Potato: https://p.ota.to - -`Ola Sendecka`_ - Ola started her adventure with Django when writing a university project in - 2009. After that she attended her first DjangoCon Europe which - defined her future life as a Django professional. - - Since then she has been an active community member. Ola co-organized a number of - Django sprints and conferences including: - `DjangoCon Europe 2013`_, `Django Under the Hood 2015`_ and - `DjangoCon Europe 2016`_. Together with Ola Sitarska she founded - `Django Girls`_, co-authored the `Django Girls Tutorial`_ and - is a member of the Django Girls Foundation. She is also an author of - the `Coding is for Girls`_ YouTube series teaching programming and Django - to beginners. - - Originally from Poland, Ola currently lives in London, where she works for `Potato`_. - - .. _Ola Sendecka: https://twitter.com/asendecka - .. _DjangoCon Europe 2013: http://love.djangocircus.com - .. _Django Under the Hood 2015: http://www.djangounderthehood.com/ - .. _DjangoCon Europe 2016: http://2016.djangocon.eu - .. _Django Girls: https://djangogirls.org - .. _Django Girls Tutorial: http://tutorial.djangogirls.org - .. _Coding is for Girls: https://www.youtube.com/channel/UC0hNd2uW8jTR5K3KBzRuG2A - .. _Potato: https://p.ota.to - -`Adam Johnson`_ - Adam started working with Django in 2012 on his first non-PHP website and - has never looked back. He has since been pushing for it to be more friendly - with MySQL / MariaDB, and now helps organize the London Django Meetup. - - Adam hails from Cheltenham, UK and currently resides in London where he - works for `Time Out`_. - - .. _Adam Johnson: https://adamj.eu/ - .. _Time Out: https://www.timeout.com/ - -`Lucie Daeye`_ - Lucie studied Geography and Korean Studies at university. She discovered - Django during the very first `Django Girls`_ in 2014: she coded her first - first web app for her PhD research, loved it, and decided to switch career - to programming. - - Since then, she's organized two `Django Girls workshops in Paris`_ and - coached at numerous events. She started working for Django Girls in - September 2015 and is now their `Awesomeness Ambassador`_. - - .. _Lucie Daeye: https://justhiding.org/ - .. _Django Girls workshops in Paris: https://djangogirls.org/paris/ - .. _Awesomeness Ambassador: https://github.com/DjangoGirls/wiki/blob/master/general/ambassador.md - -Past team members -================= - -Georg "Hugo" Bauer - Georg created Django's internationalization system, managed i18n - contributions and made a ton of excellent tweaks, feature additions and bug - fixes. - -Robert Wittams - Robert was responsible for the *first* refactoring of Django's admin - application to allow for easier reuse and has made a ton of - excellent tweaks, feature additions and bug fixes. - -`Alex Gaynor`_ - Alex was involved in many parts of Django, he contributed to the ORM, - forms, admin, amongst others; he is most known for his work on - multiple-database support in Django. - - Alex lives in Washington, DC, USA. - - .. _Alex Gaynor: https://alexgaynor.net - -`Simon Meers`_ - Simon discovered Django 0.96 during his Computer Science PhD research and - has been developing with it full-time ever since. His core code - contributions are mostly in Django's admin application. - - Simon works as a freelance developer based in Wollongong, Australia. - - .. _Simon Meers: http://simonmeers.com/ - -`Gabriel Hurley`_ - Gabriel has been working with Django since 2008, shortly after the 1.0 - release. Convinced by his business partner that Python and Django were the - right direction for the company, he couldn't have been more happy with the - decision. His contributions range across many areas in Django, but years of - copy-editing and an eye for detail lead him to be particularly at home - while working on Django's documentation. - - Gabriel works as a developer in the SF Bay Area, CA, USA. - - .. _gabriel hurley: http://strikeawe.com/ - -Malcolm Tredinnick - Malcolm originally wanted to be a mathematician and somehow ended up a - software developer. He contributed to many Open Source projects, served on - the board of the GNOME foundation, and was a great chess player. - - Malcolm was deeply involved in many part of Django - most notably, the - ORM, but many other internals bear his fingerprints. Django’s support for - unicode and autoescaping in templates can both be almost entirely - attributed to Malcolm. - - He was an International Man of Mystery and lived in Sydney, Australia. - - *Malcolm passed away on March 17, 2013.* - -`Preston Holmes`_ - Preston is a recovering neuroscientist who originally discovered Django as - part of a sweeping move to Python from a grab bag of half a dozen - languages. He was drawn to Django's balance of practical batteries included - philosophy, care and thought in code design, and strong open source - community. Currently working in the rent-your-infra space (aka Cloud), he - is always looking for opportunities to volunteer for community oriented - education projects, such as for kids and scientists (e.g. Software - Carpentry). - - Preston lives with his family and animal menagerie in Santa Barbara, CA, USA. - - .. _Preston Holmes: http://www.ptone.com/ - -`Idan Gazit`_ - As a self-professed design geek, Idan was initially attracted to Django - sometime between magic-removal and queryset-refactor. Formally trained - as a software engineer, Idan straddles the worlds of design and code, - jack of two trades and master of none. He is passionate about usability - and finding novel ways to extract meaning from data, and is a longtime - photographer_. - - Idan is currently hacking on all things data and visualization at Heroku_. - - .. _Idan Gazit: http://gazit.me - .. _photographer: https://flickr.com/photos/idangazit - -Matt Boersma - Matt helped with Django's Oracle support. - -Ian Kelly - Ian also helped with Oracle support. - -Joseph Kocherhans - Joseph was the director of lead development at EveryBlock and previously - developed at the Lawrence Journal-World. He often disappears for several - days into the woods, attempts to teach himself computational linguistics, - and annoys his neighbors with his Charango_ playing. - - Joseph's first contribution to Django was a series of improvements to the - authorization system leading up to support for pluggable authorization. - Since then, he's worked on the new forms system, its use in the admin, and - many other smaller improvements. - - Joseph lives in Chicago, USA. - - .. _charango: https://en.wikipedia.org/wiki/Charango - -`Gary Wilson`_ - Gary starting contributing patches to Django in 2006 while developing Web - applications for `The University of Texas`_ (UT). Since, he has made - contributions to the email and forms systems, as well as many other - improvements and code cleanups throughout the code base. - - Gary lives in Austin, Texas, USA. - - .. _Gary Wilson: http://thegarywilson.com/ - .. _The University of Texas: https://www.utexas.edu/ - -`Brian Rosner`_ - Brian enjoys learning more about programming languages and system - architectures and contributing to open source projects. - - He helped immensely in getting Django's "newforms-admin" branch finished - in time for Django 1.0. - - Brian lives in Denver, Colorado, USA. - - .. _brian rosner: http://brosner.com/ - -`James Tauber`_ - James is the lead developer of Pinax_ and the CEO and founder of - Eldarion_. He has been doing open source software since 1993, Python - since 1998 and Django since 2006. He serves on the board of the Python - Software Foundation and is currently on a leave of absence from a PhD in - linguistics. - - James currently lives in Boston, MA, USA but originally hails from - Perth, Western Australia where he attended the same high school as - Russell Keith-Magee. - - .. _James Tauber: http://jtauber.com/ - .. _eldarion: http://eldarion.com/ - .. _pinax: http://pinaxproject.com/ diff --git a/docs/intro/_images/admin01.png b/docs/intro/_images/admin01.png index 35257e4b3127..a1a0dc9619e3 100644 Binary files a/docs/intro/_images/admin01.png and b/docs/intro/_images/admin01.png differ diff --git a/docs/intro/contributing.txt b/docs/intro/contributing.txt index 75414410f226..eb00190ad764 100644 --- a/docs/intro/contributing.txt +++ b/docs/intro/contributing.txt @@ -43,7 +43,7 @@ so that it can be of use to the widest audience. to |django-developers| or drop by `#django-dev on irc.freenode.net`__ to chat with other Django users who might be able to help. -__ http://www.diveintopython3.net/ +__ https://diveinto.org/python3/table-of-contents.html __ irc://irc.freenode.net/django-dev What does this tutorial cover? @@ -54,12 +54,12 @@ By the end of this tutorial, you should have a basic understanding of both the tools and the processes involved. Specifically, we'll be covering the following: * Installing Git. -* How to download a development copy of Django. +* Downloading a copy of Django's development version. * Running Django's test suite. * Writing a test for your patch. * Writing the code for your patch. * Testing your patch. -* Generating a patch file for your changes. +* Submitting a pull request. * Where to look for more information. Once you're done with the tutorial, you can look through the rest of @@ -70,8 +70,8 @@ probably got the answers. .. admonition:: Python 3 required! - This tutorial assumes you are using Python 3. Get the latest version at - `Python's download page `_ or with your + The current version of Django doesn't support Python 2.7. Get Python 3 at + `Python's download page `_ or with your operating system's package manager. .. admonition:: For Windows users @@ -96,16 +96,10 @@ To check whether or not you have Git installed, enter ``git`` into the command line. If you get messages saying that this command could not be found, you'll have to download and install it, see `Git's download page`__. -.. admonition:: For Windows users - - When installing Git on Windows, it is recommended that you pick the - "Git Bash" option so that Git runs in its own shell. This tutorial assumes - that's how you have installed it. - If you're not that familiar with Git, you can always find out more about its commands (once it's installed) by typing ``git help`` into the command line. -__ http://git-scm.com/download +__ https://git-scm.com/download Getting a copy of Django's development version ============================================== @@ -117,56 +111,34 @@ where you'll want your local copy of Django to live. Download the Django source code repository using the following command: -.. code-block:: console +.. console:: - $ git clone git@github.com:YourGitHubName/django.git + $ git clone https://github.com/YourGitHubName/django.git -Now that you have a local copy of Django, you can install it just like you would -install any package using ``pip``. The most convenient way to do so is by using -a *virtual environment* (or virtualenv) which is a feature built into Python -that allows you to keep a separate directory of installed packages for each of -your projects so that they don't interfere with each other. +.. admonition:: Low bandwidth connection? -It's a good idea to keep all your virtualenvs in one place, for example in -``.virtualenvs/`` in your home directory. Create it if it doesn't exist yet: + You can add the ``--depth 1`` argument to ``git clone`` to skip downloading + all of Django's commit history, which reduces data transfer from ~250 MB + to ~70 MB. -.. code-block:: console +Now that you have a local copy of Django, you can install it just like you would +install any package using ``pip``. The most convenient way to do so is by using +a *virtual environment*, which is a feature built into Python that allows you +to keep a separate directory of installed packages for each of your projects so +that they don't interfere with each other. - $ mkdir ~/.virtualenvs +It's a good idea to keep all your virtual environments in one place, for +example in ``.virtualenvs/`` in your home directory. -Now create a new virtualenv by running: +Create a new virtual environment by running: -.. code-block:: console +.. console:: $ python3 -m venv ~/.virtualenvs/djangodev The path is where the new environment will be saved on your computer. -.. admonition:: For Windows users - - Using the built-in ``venv`` module will not work if you are also using the - Git Bash shell on Windows, since activation scripts are only created for the - system shell (``.bat``) and PowerShell (``.ps1``). Use the ``virtualenv`` - package instead: - - .. code-block:: none - - $ pip install virtualenv - $ virtualenv ~/.virtualenvs/djangodev - -.. admonition:: For Ubuntu users - - On some versions of Ubuntu the above command might fail. Use the - ``virtualenv`` package instead, first making sure you have ``pip3``: - - .. code-block:: console - - $ sudo apt-get install python3-pip - $ # Prefix the next command with sudo if it gives a permission denied error - $ pip3 install virtualenv - $ virtualenv --python=`which python3` ~/.virtualenvs/djangodev - -The final step in setting up your virtualenv is to activate it: +The final step in setting up your virtual environment is to activate it: .. code-block:: console @@ -180,60 +152,44 @@ If the ``source`` command is not available, you can try using a dot instead: .. admonition:: For Windows users - To activate your virtualenv on Windows, run: + To activate your virtual environment on Windows, run: - .. code-block:: none + .. code-block:: doscon - $ source ~/virtualenvs/djangodev/Scripts/activate + ...\> %HOMEPATH%\.virtualenvs\djangodev\Scripts\activate.bat -You have to activate the virtualenv whenever you open a new terminal window. -virtualenvwrapper__ is a useful tool for making this more convenient. +You have to activate the virtual environment whenever you open a new +terminal window. virtualenvwrapper__ is a useful tool for making this +more convenient. __ https://virtualenvwrapper.readthedocs.io/en/latest/ -Anything you install through ``pip`` from now on will be installed in your new -virtualenv, isolated from other environments and system-wide packages. Also, the -name of the currently activated virtualenv is displayed on the command line to -help you keep track of which one you are using. Go ahead and install the -previously cloned copy of Django: +The name of the currently activated virtual environment is displayed on the +command line to help you keep track of which one you are using. Anything you +install through ``pip`` while this name is displayed will be installed in that +virtual environment, isolated from other environments and system-wide packages. -.. code-block:: console +.. _intro-contributing-install-local-copy: - $ pip install -e /path/to/your/local/clone/django/ +Go ahead and install the previously cloned copy of Django: -The installed version of Django is now pointing at your local copy. You will -immediately see any changes you make to it, which is of great help when writing -your first patch. +.. console:: -Rolling back to a previous revision of Django -============================================= - -For this tutorial, we'll be using ticket :ticket:`24788` as a case study, so -we'll rewind Django's version history in git to before that ticket's patch was -applied. This will allow us to go through all of the steps involved in writing -that patch from scratch, including running Django's test suite. - -**Keep in mind that while we'll be using an older revision of Django's trunk -for the purposes of the tutorial below, you should always use the current -development revision of Django when working on your own patch for a ticket!** - -.. note:: - - The patch for this ticket was written by Paweł Marczewski, and it was - applied to Django as `commit 4df7e8483b2679fc1cba3410f08960bac6f51115`__. - Consequently, we'll be using the revision of Django just prior to that, - `commit 4ccfc4439a7add24f8db4ef3960d02ef8ae09887`__. - -__ https://github.com/django/django/commit/4df7e8483b2679fc1cba3410f08960bac6f51115 -__ https://github.com/django/django/commit/4ccfc4439a7add24f8db4ef3960d02ef8ae09887 + $ pip install -e /path/to/your/local/clone/django/ -Navigate into Django's root directory (that's the one that contains ``django``, -``docs``, ``tests``, ``AUTHORS``, etc.). You can then check out the older -revision of Django that we'll be using in the tutorial below: +The installed version of Django is now pointing at your local copy by installing +in editable mode. You will immediately see any changes you make to it, which is +of great help when writing your first patch. -.. code-block:: console +Creating projects with a local copy of Django +--------------------------------------------- - $ git checkout 4ccfc4439a7add24f8db4ef3960d02ef8ae09887 +It may be helpful to test your local changes with a Django project. First you +have to create a new virtual environment, :ref:`install the previously cloned +local copy of Django in editable mode `, +and create a new Django project outside of your local copy of Django. You will +immediately see any changes you make to Django in your new project, which is +of great help when writing your first patch. Running Django's test suite for the first time ============================================== @@ -242,14 +198,14 @@ When contributing to Django it's very important that your code changes don't introduce bugs into other areas of Django. One way to check that Django still works after you make your changes is by running Django's test suite. If all the tests still pass, then you can be reasonably sure that your changes -haven't completely broken Django. If you've never run Django's test suite -before, it's a good idea to run it once beforehand just to get familiar with -what its output is supposed to look like. +work and haven't broken other parts Django. If you've never run Django's test +suite before, it's a good idea to run it once beforehand to get familiar with +its output. -Before running the test suite, install its dependencies by first ``cd``-ing -into the Django ``tests/`` directory and then running: +Before running the test suite, install its dependencies by ``cd``-ing into the +Django ``tests/`` directory and then running: -.. code-block:: console +.. console:: $ pip install -r requirements/py3.txt @@ -258,22 +214,21 @@ a dependency for one or more of the Python packages. Consult the failing package's documentation or search the Web with the error message that you encounter. -Now we are ready to run the test suite. If you're using GNU/Linux, Mac OS X or +Now we are ready to run the test suite. If you're using GNU/Linux, macOS, or some other flavor of Unix, run: -.. code-block:: console +.. console:: $ ./runtests.py -Now sit back and relax. Django's entire test suite has over 9,600 different -tests, so it can take anywhere from 5 to 15 minutes to run, depending on the -speed of your computer. +Now sit back and relax. Django's entire test suite has thousands of tests, and +it takes at least a few minutes run, depending on the speed of your computer. While Django's test suite is running, you'll see a stream of characters -representing the status of each test as it's run. ``E`` indicates that an error -was raised during a test, and ``F`` indicates that a test's assertions failed. -Both of these are considered to be test failures. Meanwhile, ``x`` and ``s`` -indicated expected failures and skipped tests, respectively. Dots indicate +representing the status of each test as it completes. ``E`` indicates that an +error was raised during a test, and ``F`` indicates that a test's assertions +failed. Both of these are considered to be test failures. Meanwhile, ``x`` and +``s`` indicated expected failures and skipped tests, respectively. Dots indicate passing tests. Skipped tests are typically due to missing external libraries required to run @@ -288,18 +243,16 @@ Once the tests complete, you should be greeted with a message informing you whether the test suite passed or failed. Since you haven't yet made any changes to Django's code, the entire test suite **should** pass. If you get failures or errors make sure you've followed all of the previous steps properly. See -:ref:`running-unit-tests` for more information. If you're using Python 3.5+, -there will be a couple failures related to deprecation warnings that you can -ignore. These failures have since been fixed in Django. +:ref:`running-unit-tests` for more information. -Note that the latest Django trunk may not always be stable. When developing -against trunk, you can check `Django's continuous integration builds`__ to +Note that the latest Django master may not always be stable. When developing +against master, you can check `Django's continuous integration builds`__ to determine if the failures are specific to your machine or if they are also present in Django's official builds. If you click to view a particular build, you can view the "Configuration Matrix" which shows failures broken down by Python version and database backend. -__ http://djangoci.com +__ https://djangoci.com .. note:: @@ -308,6 +261,32 @@ __ http://djangoci.com :ref:`run the tests using a different database `. +Working on a feature +==================== + +For this tutorial, we'll work on a "fake ticket" as a case study. Here are the +imaginary details: + +.. admonition:: Ticket #99999 -- Allow making toast + + Django should provide a function ``django.shortcuts.make_toast()`` that + returns ``'toast'``. + +We'll now implement this feature and associated tests. + +Creating a branch for your patch +================================ + +Before making any changes, create a new branch for the ticket: + +.. console:: + + $ git checkout -b ticket_99999 + +You can choose any name that you want for the branch, "ticket_99999" is an +example. All changes made in this branch will be specific to the ticket and +won't affect the main copy of the code that we cloned earlier. + Writing some tests for your ticket ================================== @@ -333,42 +312,25 @@ Now for our hands-on example. __ https://en.wikipedia.org/wiki/Test-driven_development -Writing some tests for ticket #24788 ------------------------------------- - -Ticket :ticket:`24788` proposes a small feature addition: the ability to -specify the class level attribute ``prefix`` on Form classes, so that:: - - […] forms which ship with apps could effectively namespace themselves such - that N overlapping form fields could be POSTed at once and resolved to the - correct form. +Writing a test for ticket #99999 +-------------------------------- -In order to resolve this ticket, we'll add a ``prefix`` attribute to the -``BaseForm`` class. When creating instances of this class, passing a prefix to -the ``__init__()`` method will still set that prefix on the created instance. -But not passing a prefix (or passing ``None``) will use the class-level prefix. -Before we make those changes though, we're going to write a couple tests to -verify that our modification functions correctly and continues to function -correctly in the future. +In order to resolve this ticket, we'll add a ``make_toast()`` function to the +top-level ``django`` module. First we are going to write a test that tries to +use the function and check that its output looks correct. -Navigate to Django's ``tests/forms_tests/tests/`` folder and open the -``test_forms.py`` file. Add the following code on line 1674 right before the -``test_forms_with_null_boolean`` function:: +Navigate to Django's ``tests/shortcuts/`` folder and create a new file +``test_make_toast.py``. Add the following code:: - def test_class_prefix(self): - # Prefix can be also specified at the class level. - class Person(Form): - first_name = CharField() - prefix = 'foo' + from django.shortcuts import make_toast + from django.test import SimpleTestCase - p = Person() - self.assertEqual(p.prefix, 'foo') - p = Person(prefix='bar') - self.assertEqual(p.prefix, 'bar') + class MakeToastTests(SimpleTestCase): + def test_make_toast(self): + self.assertEqual(make_toast(), 'toast') -This new test checks that setting a class level prefix works as expected, and -that passing a ``prefix`` parameter when creating an instance still works too. +This test checks that the ``make_toast()`` returns ``'toast'``. .. admonition:: But this testing thing looks kinda hard... @@ -383,72 +345,48 @@ that passing a ``prefix`` parameter when creating an instance still works too. * After reading those, if you want something a little meatier to sink your teeth into, there's always the Python :mod:`unittest` documentation. -__ http://www.diveintopython.net/unit_testing/index.html +__ https://www.diveinto.org/python3/unit-testing.html Running your new test --------------------- -Remember that we haven't actually made any modifications to ``BaseForm`` yet, -so our tests are going to fail. Let's run all the tests in the ``forms_tests`` -folder to make sure that's really what happens. From the command line, ``cd`` -into the Django ``tests/`` directory and run: +Since we haven't made any modifications to ``django.shortcuts`` yet, our test +should fail. Let's run all the tests in the ``shortcuts`` folder to make sure +that's really what happens. ``cd`` to the Django ``tests/`` directory and run: -.. code-block:: console +.. console:: - $ ./runtests.py forms_tests + $ ./runtests.py shortcuts If the tests ran correctly, you should see one failure corresponding to the test -method we added. If all of the tests passed, then you'll want to make sure that -you added the new test shown above to the appropriate folder and class. - -Writing the code for your ticket -================================ - -Next we'll be adding the functionality described in ticket :ticket:`24788` to -Django. - -Writing the code for ticket #24788 ----------------------------------- - -Navigate to the ``django/django/forms/`` folder and open the ``forms.py`` file. -Find the ``BaseForm`` class on line 72 and add the ``prefix`` class attribute -right after the ``field_order`` attribute:: +method we added, with this error:: - class BaseForm(object): - # This is the main implementation of all the Form logic. Note that this - # class is different than Form. See the comments by the Form class for - # more information. Any improvements to the form API should be made to - # *this* class, not to the Form class. - field_order = None - prefix = None + ImportError: cannot import name 'make_toast' from 'django.shortcuts' -Verifying your test now passes ------------------------------- +If all of the tests passed, then you'll want to make sure that you added the +new test shown above to the appropriate folder and file name. -Once you're done modifying Django, we need to make sure that the tests we wrote -earlier pass, so we can see whether the code we wrote above is working -correctly. To run the tests in the ``forms_tests`` folder, ``cd`` into the -Django ``tests/`` directory and run: +Writing the code for your ticket +================================ -.. code-block:: console +Next we'll be adding the ``make_toast()`` function. - $ ./runtests.py forms_tests +Navigate to the ``django/`` folder and open the ``shortcuts.py`` file. At the +bottom, add:: -Oops, good thing we wrote those tests! You should still see one failure with -the following exception:: + def make_toast(): + return 'toast' - AssertionError: None != 'foo' +Now we need to make sure that the test we wrote earlier passes, so we can see +whether the code we added is working correctly. Again, navigate to the Django +``tests/`` directory and run: -We forgot to add the conditional statement in the ``__init__`` method. Go ahead -and change ``self.prefix = prefix`` that is now on line 87 of -``django/forms/forms.py``, adding a conditional statement:: +.. console:: - if prefix is not None: - self.prefix = prefix + $ ./runtests.py shortcuts -Re-run the tests and everything should pass. If it doesn't, make sure you -correctly modified the ``BaseForm`` class as shown above and copied the new test -correctly. +Everything should pass. If it doesn't, make sure you correctly added the +function to the correct file. Running Django's test suite for the second time =============================================== @@ -462,34 +400,33 @@ help identify many bugs and regressions that might otherwise go unnoticed. To run the entire Django test suite, ``cd`` into the Django ``tests/`` directory and run: -.. code-block:: console +.. console:: $ ./runtests.py -As long as you don't see any failures, you're good to go. - Writing Documentation ===================== -This is a new feature, so it should be documented. Add the following section on -line 1068 (at the end of the file) of ``django/docs/ref/forms/api.txt``:: - - The prefix can also be specified on the form class:: +This is a new feature, so it should be documented. Open the file +``docs/topics/http/shortcuts.txt`` and add the following at the end of the +file:: - >>> class PersonForm(forms.Form): - ... ... - ... prefix = 'person' + ``make_toast()`` + ================ - .. versionadded:: 1.9 + .. versionadded:: 2.2 - The ability to specify ``prefix`` on the form class was added. + Returns ``'toast'``. Since this new feature will be in an upcoming release it is also added to the -release notes for Django 1.9, on line 164 under the "Forms" section in the file -``docs/releases/1.9.txt``:: +release notes for the next version of Django. Open the release notes for the +latest version in ``docs/releases/``, which at time of writing is ``2.2.txt``. +Add a note under the "Minor Features" header:: + + :mod:`django.shortcuts` + ~~~~~~~~~~~~~~~~~~~~~~~ - * A form prefix can be specified inside a form class, not only when - instantiating a form. See :ref:`form-prefix` for details. + * The new :func:`django.shortcuts.make_toast` function returns ``'toast'``. For more information on writing documentation, including an explanation of what the ``versionadded`` bit is all about, see @@ -497,129 +434,133 @@ the ``versionadded`` bit is all about, see an explanation of how to build a copy of the documentation locally, so you can preview the HTML that will be generated. -Generating a patch for your changes -=================================== - -Now it's time to generate a patch file that can be uploaded to Trac or applied -to another copy of Django. To get a look at the content of your patch, run the -following command: +Previewing your changes +======================= -.. code-block:: console +Now it's time to go through all the changes made in our patch. To stage all the +changes ready for commit, run: - $ git diff +.. console:: -This will display the differences between your current copy of Django (with -your changes) and the revision that you initially checked out earlier in the -tutorial. + $ git add --all -Once you're done looking at the patch, hit the ``q`` key to exit back to the -command line. If the patch's content looked okay, you can run the following -command to save the patch file to your current working directory: +Then display the differences between your current copy of Django (with your +changes) and the revision that you initially checked out earlier in the +tutorial with: -.. code-block:: console +.. console:: - $ git diff > 24788.diff + $ git diff --cached -You should now have a file in the root Django directory called ``24788.diff``. -This patch file contains all your changes and should look this: +Use the arrow keys to move up and down. .. code-block:: diff - diff --git a/django/forms/forms.py b/django/forms/forms.py - index 509709f..d1370de 100644 - --- a/django/forms/forms.py - +++ b/django/forms/forms.py - @@ -75,6 +75,7 @@ class BaseForm(object): - # information. Any improvements to the form API should be made to *this* - # class, not to the Form class. - field_order = None - + prefix = None - - def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, - initial=None, error_class=ErrorList, label_suffix=None, - @@ -83,7 +84,8 @@ class BaseForm(object): - self.data = data or {} - self.files = files or {} - self.auto_id = auto_id - - self.prefix = prefix - + if prefix is not None: - + self.prefix = prefix - self.initial = initial or {} - self.error_class = error_class - # Translators: This is the default suffix added to form field labels - diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt - index 3bc39cd..008170d 100644 - --- a/docs/ref/forms/api.txt - +++ b/docs/ref/forms/api.txt - @@ -1065,3 +1065,13 @@ You can put several Django forms inside one ``
    `` tag. To give each - >>> print(father.as_ul()) -
  • -
  • + diff --git a/django/shortcuts.py b/django/shortcuts.py + index 7ab1df0e9d..8dde9e28d9 100644 + --- a/django/shortcuts.py + +++ b/django/shortcuts.py + @@ -156,3 +156,7 @@ def resolve_url(to, *args, **kwargs): + + # Finally, fall back and assume it's a URL + return to + + + + + +def make_toast(): + + return 'toast' + diff --git a/docs/releases/2.2.txt b/docs/releases/2.2.txt + index 7d85d30c4a..81518187b3 100644 + --- a/docs/releases/2.2.txt + +++ b/docs/releases/2.2.txt + @@ -40,6 +40,11 @@ database constraints. Constraints are added to models using the + Minor features + -------------- + + +:mod:`django.shortcuts` + +~~~~~~~~~~~~~~~~~~~~~~~ + - +The prefix can also be specified on the form class:: + +* The new :func:`django.shortcuts.make_toast` function returns ``'toast'``. + - + >>> class PersonForm(forms.Form): - + ... ... - + ... prefix = 'person' + :mod:`django.contrib.admin` + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + diff --git a/docs/topics/http/shortcuts.txt b/docs/topics/http/shortcuts.txt + index 7b3a3a2c00..711bf6bb6d 100644 + --- a/docs/topics/http/shortcuts.txt + +++ b/docs/topics/http/shortcuts.txt + @@ -271,3 +271,12 @@ This example is equivalent to:: + my_objects = list(MyModel.objects.filter(published=True)) + if not my_objects: + raise Http404("No MyModel matches the given query.") + - +.. versionadded:: 1.9 + +``make_toast()`` + +================ + - + The ability to specify ``prefix`` on the form class was added. - diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt - index 5b58f79..f9bb9de 100644 - --- a/docs/releases/1.9.txt - +++ b/docs/releases/1.9.txt - @@ -161,6 +161,9 @@ Forms - :attr:`~django.forms.Form.field_order` attribute, the ``field_order`` - constructor argument , or the :meth:`~django.forms.Form.order_fields` method. - - +* A form prefix can be specified inside a form class, not only when - + instantiating a form. See :ref:`form-prefix` for details. + +.. function:: make_toast() + - Generic Views - ^^^^^^^^^^^^^ - - diff --git a/tests/forms_tests/tests/test_forms.py b/tests/forms_tests/tests/test_forms.py - index 690f205..e07fae2 100644 - --- a/tests/forms_tests/tests/test_forms.py - +++ b/tests/forms_tests/tests/test_forms.py - @@ -1671,6 +1671,18 @@ class FormsTestCase(SimpleTestCase): - self.assertEqual(p.cleaned_data['last_name'], 'Lennon') - self.assertEqual(p.cleaned_data['birthday'], datetime.date(1940, 10, 9)) - - + def test_class_prefix(self): - + # Prefix can be also specified at the class level. - + class Person(Form): - + first_name = CharField() - + prefix = 'foo' + +.. versionadded:: 2.2 + - + p = Person() - + self.assertEqual(p.prefix, 'foo') + +Returns ``'toast'``. + diff --git a/tests/shortcuts/test_make_toast.py b/tests/shortcuts/test_make_toast.py + new file mode 100644 + index 0000000000..6f4c627b6e + --- /dev/null + +++ b/tests/shortcuts/test_make_toast.py + @@ -0,0 +1,7 @@ + +from django.shortcuts import make_toast + +from django.test import SimpleTestCase + - + p = Person(prefix='bar') - + self.assertEqual(p.prefix, 'bar') + - def test_forms_with_null_boolean(self): - # NullBooleanField is a bit of a special case because its presentation (widget) - # is different than its data. This is handled transparently, though. + +class MakeToastTests(SimpleTestCase): + + def test_make_toast(self): + + self.assertEqual(make_toast(), 'toast') -So what do I do next? -===================== +When you're done previewing the patch, hit the ``q`` key to return to the +command line. If the patch's content looked okay, it's time to commit the +changes. -Congratulations, you've generated your very first Django patch! Now that you've -got that under your belt, you can put those skills to good use by helping to -improve Django's codebase. Generating patches and attaching them to Trac -tickets is useful, however, since we are using git - adopting a more :doc:`git -oriented workflow ` is -recommended. +Committing the changes in the patch +=================================== -Since we never committed our changes locally, perform the following to get your -git branch back to a good starting point: +To commit the changes: -.. code-block:: console +.. console:: + + $ git commit + +This opens up a text editor to type the commit message. Follow the :ref:`commit +message guidelines ` and write a message like: + +.. code-block:: text + + Fixed #99999 -- Added a shortcut function to make toast. + +Pushing the commit and making a pull request +============================================ + +After committing the patch, send it to your fork on GitHub (substitute +"ticket_99999" with the name of your branch if it's different): + +.. console:: + + $ git push origin ticket_99999 + +You can create a pull request by visiting the `Django GitHub page +`_. You'll see your branch under "Your +recently pushed branches". Click "Compare & pull request" next to it. + +Please don't do it for this tutorial, but on the next page that displays a +preview of the patch, you would click "Create pull request". + +Next steps +========== + +Congratulations, you've learned how to make a pull request to Django! Details +of more advanced techniques you may need are in +:doc:`/internals/contributing/writing-code/working-with-git`. - $ git reset --hard HEAD - $ git checkout master +Now you can put those skills to good use by helping to improve Django's +codebase. More information for new contributors ------------------------------------- @@ -665,13 +606,12 @@ __ https://code.djangoproject.com/query?status=new&status=reopened&has_patch=0&e __ https://code.djangoproject.com/query?status=new&status=reopened&needs_better_patch=1&easy=1&col=id&col=summary&col=status&col=owner&col=type&col=milestone&order=priority __ https://code.djangoproject.com/query?status=new&status=reopened&needs_tests=1&easy=1&col=id&col=summary&col=status&col=owner&col=type&col=milestone&order=priority -What's next? ------------- +What's next after creating a pull request? +------------------------------------------ After a ticket has a patch, it needs to be reviewed by a second set of eyes. -After uploading a patch or submitting a pull request, be sure to update the -ticket metadata by setting the flags on the ticket to say "has patch", -"doesn't need tests", etc, so others can find it for review. Contributing -doesn't necessarily always mean writing a patch from scratch. Reviewing -existing patches is also a very helpful contribution. See -:doc:`/internals/contributing/triaging-tickets` for details. +After submitting a pull request, update the ticket metadata by setting the +flags on the ticket to say "has patch", "doesn't need tests", etc, so others +can find it for review. Contributing doesn't necessarily always mean writing a +patch from scratch. Reviewing existing patches is also a very helpful +contribution. See :doc:`/internals/contributing/triaging-tickets` for details. diff --git a/docs/intro/index.txt b/docs/intro/index.txt index ab4ae21469d0..1be0facb2234 100644 --- a/docs/intro/index.txt +++ b/docs/intro/index.txt @@ -36,5 +36,5 @@ place: read this material to quickly get up and running. .. _python: https://python.org/ .. _list of Python resources for non-programmers: https://wiki.python.org/moin/BeginnersGuide/NonProgrammers - .. _Dive Into Python: http://www.diveintopython3.net/ + .. _Dive Into Python: https://diveinto.org/python3/table-of-contents.html .. _books about Python: https://wiki.python.org/moin/PythonBooks diff --git a/docs/intro/install.txt b/docs/intro/install.txt index 7a338ee858a8..a750189f85bd 100644 --- a/docs/intro/install.txt +++ b/docs/intro/install.txt @@ -16,20 +16,13 @@ database called SQLite_ so you won't need to set up a database just yet. .. _sqlite: https://sqlite.org/ -Get the latest version of Python at https://www.python.org/download/ or with +Get the latest version of Python at https://www.python.org/downloads/ or with your operating system's package manager. -.. admonition:: Django on Jython - - If you use Jython_ (a Python implementation for the Java platform), you'll - need to follow a few additional steps. See :doc:`/howto/jython` for details. - -.. _jython: http://www.jython.org/ - You can verify that Python is installed by typing ``python`` from your shell; you should see something like:: - Python 3.4.x + Python 3.x.y [GCC 4.x] on linux Type "help", "copyright", "credits" or "license" for more information. >>> @@ -41,13 +34,6 @@ This step is only necessary if you'd like to work with a "large" database engine like PostgreSQL, MySQL, or Oracle. To install such a database, consult the :ref:`database installation information `. -Remove any old versions of Django -================================= - -If you are upgrading your installation of Django from a previous version, you -will need to :ref:`uninstall the old Django version before installing the new -version `. - Install Django ============== diff --git a/docs/intro/overview.txt b/docs/intro/overview.txt index 011d1695a06b..47af40533c76 100644 --- a/docs/intro/overview.txt +++ b/docs/intro/overview.txt @@ -25,15 +25,15 @@ The :doc:`data-model syntax ` offers many rich ways of representing your models -- so far, it's been solving many years' worth of database-schema problems. Here's a quick example: -.. snippet:: - :filename: mysite/news/models.py +.. code-block:: python + :caption: mysite/news/models.py from django.db import models class Reporter(models.Model): full_name = models.CharField(max_length=70) - def __str__(self): # __unicode__ on Python 2 + def __str__(self): return self.full_name class Article(models.Model): @@ -42,22 +42,24 @@ database-schema problems. Here's a quick example: content = models.TextField() reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE) - def __str__(self): # __unicode__ on Python 2 + def __str__(self): return self.headline Install it ========== -Next, run the Django command-line utility to create the database tables +Next, run the Django command-line utilities to create the database tables automatically: -.. code-block:: console +.. console:: + $ python manage.py makemigrations $ python manage.py migrate -The :djadmin:`migrate` command looks at all your available models and creates -tables in your database for whichever tables don't already exist, as well as -optionally providing :doc:`much richer schema control `. +The :djadmin:`makemigrations` command looks at all your available models and +creates migrations for whichever tables don't already exist. :djadmin:`migrate` +runs the migrations and creates tables in your database, as well as optionally +providing :doc:`much richer schema control `. Enjoy the free API ================== @@ -69,7 +71,7 @@ necessary: .. code-block:: python # Import the models we created from our "news" app - >>> from news.models import Reporter, Article + >>> from news.models import Article, Reporter # No reporters are in the system yet. >>> Reporter.objects.all() @@ -145,8 +147,8 @@ production ready :doc:`administrative interface ` -- a website that lets authenticated users add, change and delete objects. It's as easy as registering your model in the admin site: -.. snippet:: - :filename: mysite/news/models.py +.. code-block:: python + :caption: mysite/news/models.py from django.db import models @@ -156,8 +158,8 @@ as easy as registering your model in the admin site: content = models.TextField() reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE) -.. snippet:: - :filename: mysite/news/admin.py +.. code-block:: python + :caption: mysite/news/admin.py from django.contrib import admin @@ -188,34 +190,33 @@ to decouple URLs from Python code. Here's what a URLconf might look like for the ``Reporter``/``Article`` example above: -.. snippet:: - :filename: mysite/news/urls.py +.. code-block:: python + :caption: mysite/news/urls.py - from django.conf.urls import url + from django.urls import path from . import views urlpatterns = [ - url(r'^articles/([0-9]{4})/$', views.year_archive), - url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive), - url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail), + path('articles//', views.year_archive), + path('articles///', views.month_archive), + path('articles////', views.article_detail), ] -The code above maps URLs, as simple :ref:`regular expressions `, -to the location of Python callback functions ("views"). The regular expressions -use parenthesis to "capture" values from the URLs. When a user requests a page, -Django runs through each pattern, in order, and stops at the first one that -matches the requested URL. (If none of them matches, Django calls a -special-case 404 view.) This is blazingly fast, because the regular expressions -are compiled at load time. +The code above maps URL paths to Python callback functions ("views"). The path +strings use parameter tags to "capture" values from the URLs. When a user +requests a page, Django runs through each path, in order, and stops at the +first one that matches the requested URL. (If none of them matches, Django +calls a special-case 404 view.) This is blazingly fast, because the paths are +compiled into regular expressions at load time. -Once one of the regexes matches, Django imports and calls the given view, which -is a simple Python function. Each view gets passed a request object -- -which contains request metadata -- and the values captured in the regex. +Once one of the URL patterns matches, Django calls the given view, which is a +Python function. Each view gets passed a request object -- which contains +request metadata -- and the values captured in the pattern. For example, if a user requested the URL "/articles/2005/05/39323/", Django would call the function ``news.views.article_detail(request, -'2005', '05', '39323')``. +year=2005, month=5, pk=39323)``. Write your views ================ @@ -229,8 +230,8 @@ Generally, a view retrieves data according to the parameters, loads a template and renders the template with the retrieved data. Here's an example view for ``year_archive`` from above: -.. snippet:: - :filename: mysite/news/views.py +.. code-block:: python + :caption: mysite/news/views.py from django.shortcuts import render @@ -258,8 +259,8 @@ in the first directory, it checks the second, and so on. Let's say the ``news/year_archive.html`` template was found. Here's what that might look like: -.. snippet:: html+django - :filename: mysite/news/templates/news/year_archive.html +.. code-block:: html+django + :caption: mysite/news/templates/news/year_archive.html {% extends "base.html" %} @@ -299,8 +300,8 @@ in templates: each template has to define only what's unique to that template. Here's what the "base.html" template, including the use of :doc:`static files `, might look like: -.. snippet:: html+django - :filename: mysite/templates/base.html +.. code-block:: html+django + :caption: mysite/templates/base.html {% load static %} @@ -308,7 +309,7 @@ Here's what the "base.html" template, including the use of :doc:`static files {% block title %}{% endblock %} - Logo + Logo {% block content %}{% endblock %} @@ -342,8 +343,8 @@ features: * A :doc:`syndication framework ` that makes creating RSS and Atom feeds as easy as writing a small Python class. -* More sexy automatically-generated admin features -- this overview barely - scratched the surface. +* More attractive automatically-generated admin features -- this overview + barely scratched the surface. The next obvious steps are for you to `download Django`_, read :doc:`the tutorial ` and join `the community`_. Thanks for your diff --git a/docs/intro/reusable-apps.txt b/docs/intro/reusable-apps.txt index 69017660477c..dda315b9ad79 100644 --- a/docs/intro/reusable-apps.txt +++ b/docs/intro/reusable-apps.txt @@ -17,17 +17,16 @@ Python and Django projects share common problems. Wouldn't it be great if we could save some of this repeated work? Reusability is the way of life in Python. `The Python Package Index (PyPI) -`_ has a vast range of packages you can use in -your own Python programs. Check out `Django Packages -`_ for existing reusable apps you could -incorporate in your project. Django itself is also just a Python package. This -means that you can take existing Python packages or Django apps and compose -them into your own web project. You only need to write the parts that make -your project unique. +`_ has a vast range of packages you can use in your own +Python programs. Check out `Django Packages `_ for +existing reusable apps you could incorporate in your project. Django itself is +also just a Python package. This means that you can take existing Python +packages or Django apps and compose them into your own web project. You only +need to write the parts that make your project unique. Let's say you were starting a new project that needed a polls app like the one we've been working on. How do you make this app reusable? Luckily, you're well -on the way already. In :doc:`Tutorial 3 `, we saw how we +on the way already. In :doc:`Tutorial 1 `, we saw how we could decouple polls from the project-level URLconf using an ``include``. In this tutorial, we'll take further steps to make the app easy to use in new projects and ready to publish for others to install and use. @@ -111,8 +110,8 @@ two packages now. If you need help, you can refer to :ref:`how to install Django with pip`. You can install ``setuptools`` the same way. -.. _setuptools: https://pypi.python.org/pypi/setuptools -.. _pip: https://pypi.python.org/pypi/pip +.. _setuptools: https://pypi.org/project/setuptools/ +.. _pip: https://pypi.org/project/pip/ Packaging your app ================== @@ -121,7 +120,7 @@ Python *packaging* refers to preparing your app in a specific format that can be easily installed and used. Django itself is packaged very much like this. For a small app like polls, this process isn't too difficult. -1. First, create a parent directory for ``polls``, outside of your Django +#. First, create a parent directory for ``polls``, outside of your Django project. Call this directory ``django-polls``. .. admonition:: Choosing a name for your app @@ -138,12 +137,12 @@ this. For a small app like polls, this process isn't too difficult. `, for example ``auth``, ``admin``, or ``messages``. -2. Move the ``polls`` directory into the ``django-polls`` directory. +#. Move the ``polls`` directory into the ``django-polls`` directory. -3. Create a file ``django-polls/README.rst`` with the following contents: +#. Create a file ``django-polls/README.rst`` with the following contents: - .. snippet:: - :filename: django-polls/README.rst + .. code-block:: rst + :caption: django-polls/README.rst ===== Polls @@ -166,7 +165,7 @@ this. For a small app like polls, this process isn't too difficult. 2. Include the polls URLconf in your project urls.py like this:: - url(r'^polls/', include('polls.urls')), + path('polls/', include('polls.urls')), 3. Run `python manage.py migrate` to create the polls models. @@ -175,76 +174,75 @@ this. For a small app like polls, this process isn't too difficult. 5. Visit http://127.0.0.1:8000/polls/ to participate in the poll. -4. Create a ``django-polls/LICENSE`` file. Choosing a license is beyond the +#. Create a ``django-polls/LICENSE`` file. Choosing a license is beyond the scope of this tutorial, but suffice it to say that code released publicly without a license is *useless*. Django and many Django-compatible apps are distributed under the BSD license; however, you're free to pick your own license. Just be aware that your licensing choice will affect who is able to use your code. -5. Next we'll create a ``setup.py`` file which provides details about how to - build and install the app. A full explanation of this file is beyond the - scope of this tutorial, but the `setuptools docs - `_ have a good - explanation. Create a file ``django-polls/setup.py`` with the following - contents: - - .. snippet:: - :filename: django-polls/setup.py - - import os - from setuptools import find_packages, setup - - with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme: - README = readme.read() - - # allow setup.py to be run from any path - os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) - - setup( - name='django-polls', - version='0.1', - packages=find_packages(), - include_package_data=True, - license='BSD License', # example license - description='A simple Django app to conduct Web-based polls.', - long_description=README, - url='https://www.example.com/', - author='Your Name', - author_email='yourname@example.com', - classifiers=[ - 'Environment :: Web Environment', - 'Framework :: Django', - 'Framework :: Django :: X.Y', # replace "X.Y" as appropriate - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', # example license - 'Operating System :: OS Independent', - 'Programming Language :: Python', - # Replace these appropriately if you are stuck on Python 2. - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Topic :: Internet :: WWW/HTTP', - 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', - ], - ) - -6. Only Python modules and packages are included in the package by default. To +#. Next we'll create ``setup.cfg`` and ``setup.py`` files which detail how to + build and install the app. A full explanation of these files is beyond the + scope of this tutorial, but the `setuptools documentation + `_ has a good explanation. + Create the files ``django-polls/setup.cfg`` and ``django-polls/setup.py`` + with the following contents: + + .. code-block:: ini + :caption: django-polls/setup.cfg + + [metadata] + name = django-polls + version = 0.1 + description = A Django app to conduct Web-based polls. + long_description = file: README.rst + url = https://www.example.com/ + author = Your Name + author_email = yourname@example.com + license = BSD-3-Clause # Example license + classifiers = + Environment :: Web Environment + Framework :: Django + Framework :: Django :: X.Y # Replace "X.Y" as appropriate + Intended Audience :: Developers + License :: OSI Approved :: BSD License + Operating System :: OS Independent + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Topic :: Internet :: WWW/HTTP + Topic :: Internet :: WWW/HTTP :: Dynamic Content + + [options] + include_package_data = true + packages = find: + + .. code-block:: python + :caption: django-polls/setup.py + + from setuptools import setup + + setup() + +#. Only Python modules and packages are included in the package by default. To include additional files, we'll need to create a ``MANIFEST.in`` file. The setuptools docs referred to in the previous step discuss this file in more details. To include the templates, the ``README.rst`` and our ``LICENSE`` file, create a file ``django-polls/MANIFEST.in`` with the following contents: - .. snippet:: - :filename: django-polls/MANIFEST.in + .. code-block:: text + :caption: django-polls/MANIFEST.in include LICENSE include README.rst recursive-include polls/static * recursive-include polls/templates * -7. It's optional, but recommended, to include detailed documentation with your +#. It's optional, but recommended, to include detailed documentation with your app. Create an empty directory ``django-polls/docs`` for future documentation. Add an additional line to ``django-polls/MANIFEST.in``:: @@ -254,12 +252,13 @@ this. For a small app like polls, this process isn't too difficult. you add some files to it. Many Django apps also provide their documentation online through sites like `readthedocs.org `_. -8. Try building your package with ``python setup.py sdist`` (run from inside +#. Try building your package with ``python setup.py sdist`` (run from inside ``django-polls``). This creates a directory called ``dist`` and builds your new package, ``django-polls-0.1.tar.gz``. For more information on packaging, see Python's `Tutorial on Packaging and -Distributing Projects `_. +Distributing Projects +`_. Using your own package ====================== @@ -279,20 +278,18 @@ working. We'll now fix this by installing our new ``django-polls`` package. tools that run as that user, so ``virtualenv`` is a more robust solution (see below). -1. To install the package, use pip (you already :ref:`installed it +#. To install the package, use pip (you already :ref:`installed it `, right?):: pip install --user django-polls/dist/django-polls-0.1.tar.gz -2. With luck, your Django project should now work correctly again. Run the +#. With luck, your Django project should now work correctly again. Run the server again to confirm this. -3. To uninstall the package, use pip:: +#. To uninstall the package, use pip:: pip uninstall django-polls -.. _pip: https://pypi.python.org/pypi/pip - Publishing your app =================== @@ -305,7 +302,7 @@ the world! If this wasn't just an example, you could now: * Post the package on a public repository, such as `the Python Package Index (PyPI)`_. `packaging.python.org `_ has `a good - tutorial `_ + tutorial `_ for doing this. Installing Python packages with virtualenv diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index 10738fa91f13..56de527fac3e 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -13,21 +13,22 @@ It'll consist of two parts: * An admin site that lets you add, change, and delete polls. We'll assume you have :doc:`Django installed ` already. You can -tell Django is installed and which version by running the following command: +tell Django is installed and which version by running the following command +in a shell prompt (indicated by the $ prefix): -.. code-block:: console +.. console:: $ python -m django --version If Django is installed, you should see the version of your installation. If it isn't, you'll get an error telling "No module named django". -This tutorial is written for Django |version| and Python 3.4 or later. If the -Django version doesn't match, you can refer to the tutorial for your version -of Django by using the version switcher at the bottom right corner of this -page, or update Django to the newest version. If you are still using Python -2.7, you will need to adjust the code samples slightly, as described in -comments. +This tutorial is written for Django |version|, which supports Python 3.5 and +later. If the Django version doesn't match, you can refer to the tutorial for +your version of Django by using the version switcher at the bottom right corner +of this page, or update Django to the newest version. If you're using an older +version of Python, check :ref:`faq-python-version-support` to find a compatible +version of Django. See :doc:`How to install Django ` for advice on how to remove older versions of Django and install a newer one. @@ -51,7 +52,7 @@ application-specific settings. From the command line, ``cd`` into a directory where you'd like to store your code, then run the following command: -.. code-block:: console +.. console:: $ django-admin startproject mysite @@ -122,7 +123,7 @@ The development server Let's verify your Django project works. Change into the outer :file:`mysite` directory, if you haven't already, and run the following commands: -.. code-block:: console +.. console:: $ python manage.py runserver @@ -156,7 +157,7 @@ production environment. It's intended only for use while developing. (We're in the business of making Web frameworks, not Web servers.) Now that the server's running, visit http://127.0.0.1:8000/ with your Web -browser. You'll see a "Welcome to Django" page, in pleasant, light-blue pastel. +browser. You'll see a "Congratulations!" page, with a rocket taking off. It worked! .. admonition:: Changing the port @@ -168,20 +169,21 @@ It worked! it as a command-line argument. For instance, this command starts the server on port 8080: - .. code-block:: console + .. console:: $ python manage.py runserver 8080 - If you want to change the server's IP, pass it along with the port. So to - listen on all public IPs (useful if you want to show off your work on other - computers on your network), use: + If you want to change the server's IP, pass it along with the port. For + example, to listen on all available public IPs (which is useful if you are + running Vagrant or want to show off your work on other computers on the + network), use: - .. code-block:: console + .. console:: - $ python manage.py runserver 0.0.0.0:8000 + $ python manage.py runserver 0:8000 - Full docs for the development server can be found in the - :djadmin:`runserver` reference. + **0** is a shortcut for **0.0.0.0**. Full docs for the development server + can be found in the :djadmin:`runserver` reference. .. admonition:: Automatic reloading of :djadmin:`runserver` @@ -217,7 +219,7 @@ submodule of ``mysite``. To create your app, make sure you're in the same directory as :file:`manage.py` and type this command: -.. code-block:: console +.. console:: $ python manage.py startapp polls @@ -241,8 +243,8 @@ Write your first view Let's write the first view. Open the file ``polls/views.py`` and put the following Python code in it: -.. snippet:: - :filename: polls/views.py +.. code-block:: python + :caption: polls/views.py from django.http import HttpResponse @@ -269,62 +271,52 @@ Your app directory should now look like:: In the ``polls/urls.py`` file include the following code: -.. snippet:: - :filename: polls/urls.py +.. code-block:: python + :caption: polls/urls.py - from django.conf.urls import url + from django.urls import path from . import views urlpatterns = [ - url(r'^$', views.index, name='index'), + path('', views.index, name='index'), ] The next step is to point the root URLconf at the ``polls.urls`` module. In -``mysite/urls.py``, add an import for ``django.conf.urls.include`` and insert -an :func:`~django.conf.urls.include` in the ``urlpatterns`` list, so you have: +``mysite/urls.py``, add an import for ``django.urls.include`` and insert an +:func:`~django.urls.include` in the ``urlpatterns`` list, so you have: -.. snippet:: - :filename: mysite/urls.py +.. code-block:: python + :caption: mysite/urls.py - from django.conf.urls import include, url from django.contrib import admin + from django.urls import include, path urlpatterns = [ - url(r'^polls/', include('polls.urls')), - url(r'^admin/', admin.site.urls), + path('polls/', include('polls.urls')), + path('admin/', admin.site.urls), ] -The :func:`~django.conf.urls.include` function allows referencing other -URLconfs. Note that the regular expressions for the -:func:`~django.conf.urls.include` function doesn't have a ``$`` (end-of-string -match character) but rather a trailing slash. Whenever Django encounters -:func:`~django.conf.urls.include`, it chops off whatever part of the URL -matched up to that point and sends the remaining string to the included URLconf -for further processing. +The :func:`~django.urls.include` function allows referencing other URLconfs. +Whenever Django encounters :func:`~django.urls.include`, it chops off whatever +part of the URL matched up to that point and sends the remaining string to the +included URLconf for further processing. -The idea behind :func:`~django.conf.urls.include` is to make it easy to +The idea behind :func:`~django.urls.include` is to make it easy to plug-and-play URLs. Since polls are in their own URLconf (``polls/urls.py``), they can be placed under "/polls/", or under "/fun_polls/", or under "/content/polls/", or any other path root, and the app will still work. -.. admonition:: When to use :func:`~django.conf.urls.include()` +.. admonition:: When to use :func:`~django.urls.include()` You should always use ``include()`` when you include other URL patterns. ``admin.site.urls`` is the only exception to this. -.. admonition:: Doesn't match what you see? +You have now wired an ``index`` view into the URLconf. Verify it's working with +the following command: - If you're seeing ``include(admin.site.urls)`` instead of just - ``admin.site.urls``, you're probably using a version of Django that - doesn't match this tutorial version. You'll want to either switch to the - older tutorial or the newer Django version. - -You have now wired an ``index`` view into the URLconf. Lets verify it's -working, run the following command: - -.. code-block:: console +.. console:: $ python manage.py runserver @@ -332,56 +324,44 @@ Go to http://localhost:8000/polls/ in your browser, and you should see the text "*Hello, world. You're at the polls index.*", which you defined in the ``index`` view. -The :func:`~django.conf.urls.url` function is passed four arguments, two -required: ``regex`` and ``view``, and two optional: ``kwargs``, and ``name``. -At this point, it's worth reviewing what these arguments are for. - -:func:`~django.conf.urls.url` argument: regex ---------------------------------------------- +.. admonition:: Page not found? -The term "regex" is a commonly used short form meaning "regular expression", -which is a syntax for matching patterns in strings, or in this case, url -patterns. Django starts at the first regular expression and makes its way down -the list, comparing the requested URL against each regular expression until it -finds one that matches. + If you get an error page here, check that you're going to + http://localhost:8000/polls/ and not http://localhost:8000/. -Note that these regular expressions do not search GET and POST parameters, or -the domain name. For example, in a request to -``https://www.example.com/myapp/``, the URLconf will look for ``myapp/``. In a -request to ``https://www.example.com/myapp/?page=3``, the URLconf will also -look for ``myapp/``. +The :func:`~django.urls.path` function is passed four arguments, two required: +``route`` and ``view``, and two optional: ``kwargs``, and ``name``. +At this point, it's worth reviewing what these arguments are for. -If you need help with regular expressions, see `Wikipedia's entry`_ and the -documentation of the :mod:`re` module. Also, the O'Reilly book "Mastering -Regular Expressions" by Jeffrey Friedl is fantastic. In practice, however, -you don't need to be an expert on regular expressions, as you really only need -to know how to capture simple patterns. In fact, complex regexes can have poor -lookup performance, so you probably shouldn't rely on the full power of regexes. +:func:`~django.urls.path` argument: ``route`` +--------------------------------------------- -Finally, a performance note: these regular expressions are compiled the first -time the URLconf module is loaded. They're super fast (as long as the lookups -aren't too complex as noted above). +``route`` is a string that contains a URL pattern. When processing a request, +Django starts at the first pattern in ``urlpatterns`` and makes its way down +the list, comparing the requested URL against each pattern until it finds one +that matches. -.. _Wikipedia's entry: https://en.wikipedia.org/wiki/Regular_expression +Patterns don't search GET and POST parameters, or the domain name. For example, +in a request to ``https://www.example.com/myapp/``, the URLconf will look for +``myapp/``. In a request to ``https://www.example.com/myapp/?page=3``, the +URLconf will also look for ``myapp/``. -:func:`~django.conf.urls.url` argument: view +:func:`~django.urls.path` argument: ``view`` -------------------------------------------- -When Django finds a regular expression match, Django calls the specified view -function, with an :class:`~django.http.HttpRequest` object as the first -argument and any “captured” values from the regular expression as other -arguments. If the regex uses simple captures, values are passed as positional -arguments; if it uses named captures, values are passed as keyword arguments. -We'll give an example of this in a bit. +When Django finds a matching pattern, it calls the specified view function with +an :class:`~django.http.HttpRequest` object as the first argument and any +"captured" values from the route as keyword arguments. We'll give an example +of this in a bit. -:func:`~django.conf.urls.url` argument: kwargs +:func:`~django.urls.path` argument: ``kwargs`` ---------------------------------------------- Arbitrary keyword arguments can be passed in a dictionary to the target view. We aren't going to use this feature of Django in the tutorial. -:func:`~django.conf.urls.url` argument: name ---------------------------------------------- +:func:`~django.urls.path` argument: ``name`` +-------------------------------------------- Naming your URL lets you refer to it unambiguously from elsewhere in Django, especially from within templates. This powerful feature allows you to make diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt index bd7e9c18cafc..01bee32a6ca3 100644 --- a/docs/intro/tutorial02.txt +++ b/docs/intro/tutorial02.txt @@ -85,7 +85,7 @@ Some of these applications make use of at least one database table, though, so we need to create the tables in the database before we can use them. To do that, run the following command: -.. code-block:: console +.. console:: $ python manage.py migrate @@ -135,8 +135,8 @@ with a ``Question``. These concepts are represented by simple Python classes. Edit the :file:`polls/models.py` file so it looks like this: -.. snippet:: - :filename: polls/models.py +.. code-block:: python + :caption: polls/models.py from django.db import models @@ -211,8 +211,8 @@ is ``'polls.apps.PollsConfig'``. Edit the :file:`mysite/settings.py` file and add that dotted path to the :setting:`INSTALLED_APPS` setting. It'll look like this: -.. snippet:: - :filename: mysite/settings.py +.. code-block:: python + :caption: mysite/settings.py INSTALLED_APPS = [ 'polls.apps.PollsConfig', @@ -226,7 +226,7 @@ this: Now Django knows to include the ``polls`` app. Let's run another command: -.. code-block:: console +.. console:: $ python manage.py makemigrations polls @@ -256,7 +256,7 @@ schema automatically - that's called :djadmin:`migrate`, and we'll come to it in moment - but first, let's see what SQL that migration would run. The :djadmin:`sqlmigrate` command takes migration names and returns their SQL: -.. code-block:: console +.. console:: $ python manage.py sqlmigrate polls 0001 @@ -332,7 +332,7 @@ your project without making migrations or touching the database. Now, run :djadmin:`migrate` again to create those model tables in your database: -.. code-block:: console +.. console:: $ python manage.py migrate Operations to perform: @@ -362,7 +362,7 @@ but for now, remember the three-step guide to making model changes: The reason that there are separate commands to make and apply migrations is because you'll commit migrations to your version control system and ship them with your app; they not only make your development easier, they're also -useable by other developers and in production. +usable by other developers and in production. Read the :doc:`django-admin documentation ` for full information on what the ``manage.py`` utility can do. @@ -373,7 +373,7 @@ Playing with the API Now, let's hop into the interactive Python shell and play around with the free API Django gives you. To invoke the Python shell, use this command: -.. code-block:: console +.. console:: $ python manage.py shell @@ -381,31 +381,9 @@ We're using this instead of simply typing "python", because :file:`manage.py` sets the ``DJANGO_SETTINGS_MODULE`` environment variable, which gives Django the Python import path to your :file:`mysite/settings.py` file. -.. admonition:: Bypassing manage.py - - If you'd rather not use :file:`manage.py`, no problem. Just set the - :envvar:`DJANGO_SETTINGS_MODULE` environment variable to - ``mysite.settings``, start a plain Python shell, and set up Django: - - .. code-block:: pycon - - >>> import django - >>> django.setup() - - If this raises an :exc:`AttributeError`, you're probably using - a version of Django that doesn't match this tutorial version. You'll want - to either switch to the older tutorial or the newer Django version. - - You must run ``python`` from the same directory :file:`manage.py` is in, - or ensure that directory is on the Python path, so that ``import mysite`` - works. - - For more information on all of this, see the :doc:`django-admin - documentation `. - Once you're in the shell, explore the :doc:`database API `:: - >>> from polls.models import Question, Choice # Import the model classes we just wrote. + >>> from polls.models import Choice, Question # Import the model classes we just wrote. # No questions are in the system yet. >>> Question.objects.all() @@ -421,10 +399,7 @@ Once you're in the shell, explore the :doc:`database API `:: # Save the object into the database. You have to call save() explicitly. >>> q.save() - # Now it has an ID. Note that this might say "1L" instead of "1", depending - # on which database you're using. That's no biggie; it just means your - # database backend prefers to return integers as Python long integer - # objects. + # Now it has an ID. >>> q.id 1 @@ -440,27 +415,24 @@ Once you're in the shell, explore the :doc:`database API `:: # objects.all() displays all the questions in the database. >>> Question.objects.all() - ]> + ]> -Wait a minute. ```` is, utterly, an unhelpful representation -of this object. Let's fix that by editing the ``Question`` model (in the -``polls/models.py`` file) and adding a +Wait a minute. ```` isn't a helpful +representation of this object. Let's fix that by editing the ``Question`` model +(in the ``polls/models.py`` file) and adding a :meth:`~django.db.models.Model.__str__` method to both ``Question`` and ``Choice``: -.. snippet:: - :filename: polls/models.py +.. code-block:: python + :caption: polls/models.py from django.db import models - from django.utils.encoding import python_2_unicode_compatible - @python_2_unicode_compatible # only if you need to support Python 2 class Question(models.Model): # ... def __str__(self): return self.question_text - @python_2_unicode_compatible # only if you need to support Python 2 class Choice(models.Model): # ... def __str__(self): @@ -471,11 +443,12 @@ models, not only for your own convenience when dealing with the interactive prompt, but also because objects' representations are used throughout Django's automatically-generated admin. -Note these are normal Python methods. Let's add a custom method, just for -demonstration: +.. _tutorial02-import-timezone: + +Let's also add a custom method to this model: -.. snippet:: - :filename: polls/models.py +.. code-block:: python + :caption: polls/models.py import datetime @@ -497,7 +470,7 @@ the :doc:`time zone support docs `. Save these changes and start a new Python interactive shell by running ``python manage.py shell`` again:: - >>> from polls.models import Question, Choice + >>> from polls.models import Choice, Question # Make sure our __str__() addition worked. >>> Question.objects.all() @@ -603,7 +576,7 @@ Creating an admin user First we'll need to create a user who can login to the admin site. Run the following command: -.. code-block:: console +.. console:: $ python manage.py createsuperuser @@ -636,7 +609,7 @@ server and explore it. If the server is not running start it like so: -.. code-block:: console +.. console:: $ python manage.py runserver @@ -672,8 +645,8 @@ Just one thing to do: we need to tell the admin that ``Question`` objects have an admin interface. To do this, open the :file:`polls/admin.py` file, and edit it to look like this: -.. snippet:: - :filename: polls/admin.py +.. code-block:: python + :caption: polls/admin.py from django.contrib import admin diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt index 2d1104d3d756..1a9d21c3c5f3 100644 --- a/docs/intro/tutorial03.txt +++ b/docs/intro/tutorial03.txt @@ -53,10 +53,10 @@ A URL pattern is simply the general form of a URL - for example: ``/newsarchive///``. To get from a URL to a view, Django uses what are known as 'URLconfs'. A -URLconf maps URL patterns (described as regular expressions) to views. +URLconf maps URL patterns to views. This tutorial provides basic instruction in the use of URLconfs, and you can -refer to :mod:`django.urls` for more information. +refer to :doc:`/topics/http/urls` for more information. Writing more views ================== @@ -64,8 +64,8 @@ Writing more views Now let's add a few more views to ``polls/views.py``. These views are slightly different, because they take an argument: -.. snippet:: - :filename: polls/views.py +.. code-block:: python + :caption: polls/views.py def detail(request, question_id): return HttpResponse("You're looking at question %s." % question_id) @@ -78,24 +78,24 @@ slightly different, because they take an argument: return HttpResponse("You're voting on question %s." % question_id) Wire these new views into the ``polls.urls`` module by adding the following -:func:`~django.conf.urls.url` calls: +:func:`~django.urls.path` calls: -.. snippet:: - :filename: polls/urls.py +.. code-block:: python + :caption: polls/urls.py - from django.conf.urls import url + from django.urls import path from . import views urlpatterns = [ # ex: /polls/ - url(r'^$', views.index, name='index'), + path('', views.index, name='index'), # ex: /polls/5/ - url(r'^(?P[0-9]+)/$', views.detail, name='detail'), + path('/', views.detail, name='detail'), # ex: /polls/5/results/ - url(r'^(?P[0-9]+)/results/$', views.results, name='results'), + path('/results/', views.results, name='results'), # ex: /polls/5/vote/ - url(r'^(?P[0-9]+)/vote/$', views.vote, name='vote'), + path('/vote/', views.vote, name='vote'), ] Take a look in your browser, at "/polls/34/". It'll run the ``detail()`` @@ -106,26 +106,24 @@ placeholder results and voting pages. When somebody requests a page from your website -- say, "/polls/34/", Django will load the ``mysite.urls`` Python module because it's pointed to by the :setting:`ROOT_URLCONF` setting. It finds the variable named ``urlpatterns`` -and traverses the regular expressions in order. After finding the match at -``'^polls/'``, it strips off the matching text (``"polls/"``) and sends the -remaining text -- ``"34/"`` -- to the 'polls.urls' URLconf for further -processing. There it matches ``r'^(?P[0-9]+)/$'``, resulting in a -call to the ``detail()`` view like so:: +and traverses the patterns in order. After finding the match at ``'polls/'``, +it strips off the matching text (``"polls/"``) and sends the remaining text -- +``"34/"`` -- to the 'polls.urls' URLconf for further processing. There it +matches ``'/'``, resulting in a call to the ``detail()`` view +like so:: - detail(request=, question_id='34') + detail(request=, question_id=34) -The ``question_id='34'`` part comes from ``(?P[0-9]+)``. Using parentheses -around a pattern "captures" the text matched by that pattern and sends it as an -argument to the view function; ``?P`` defines the name that will -be used to identify the matched pattern; and ``[0-9]+`` is a regular expression to -match a sequence of digits (i.e., a number). +The ``question_id=34`` part comes from ````. Using angle +brackets "captures" part of the URL and sends it as a keyword argument to the +view function. The ``:question_id>`` part of the string defines the name that +will be used to identify the matched pattern, and the ```. Here's one stab at a new ``index()`` view, which displays the latest 5 poll questions in the system, separated by commas, according to publication date: -.. snippet:: - :filename: polls/views.py +.. code-block:: python + :caption: polls/views.py from django.http import HttpResponse @@ -198,8 +196,8 @@ Django simply as ``polls/index.html``. Put the following code in that template: -.. snippet:: html+django - :filename: polls/templates/polls/index.html +.. code-block:: html+django + :caption: polls/templates/polls/index.html {% if latest_question_list %}
      @@ -213,8 +211,8 @@ Put the following code in that template: Now let's update our ``index`` view in ``polls/views.py`` to use the template: -.. snippet:: - :filename: polls/views.py +.. code-block:: python + :caption: polls/views.py from django.http import HttpResponse from django.template import loader @@ -246,8 +244,8 @@ It's a very common idiom to load a template, fill a context and return an template. Django provides a shortcut. Here's the full ``index()`` view, rewritten: -.. snippet:: - :filename: polls/views.py +.. code-block:: python + :caption: polls/views.py from django.shortcuts import render @@ -275,8 +273,8 @@ Raising a 404 error Now, let's tackle the question detail view -- the page that displays the question text for a given poll. Here's the view: -.. snippet:: - :filename: polls/views.py +.. code-block:: python + :caption: polls/views.py from django.http import Http404 from django.shortcuts import render @@ -297,8 +295,8 @@ We'll discuss what you could put in that ``polls/detail.html`` template a bit later, but if you'd like to quickly get the above example working, a file containing just: -.. snippet:: html+django - :filename: polls/templates/polls/detail.html +.. code-block:: html+django + :caption: polls/templates/polls/detail.html {{ question }} @@ -311,8 +309,8 @@ It's a very common idiom to use :meth:`~django.db.models.query.QuerySet.get` and raise :exc:`~django.http.Http404` if the object doesn't exist. Django provides a shortcut. Here's the ``detail()`` view, rewritten: -.. snippet:: - :filename: polls/views.py +.. code-block:: python + :caption: polls/views.py from django.shortcuts import get_object_or_404, render @@ -353,8 +351,8 @@ Back to the ``detail()`` view for our poll application. Given the context variable ``question``, here's what the ``polls/detail.html`` template might look like: -.. snippet:: html+django - :filename: polls/templates/polls/detail.html +.. code-block:: html+django + :caption: polls/templates/polls/detail.html

      {{ question.question_text }}

        @@ -388,7 +386,7 @@ template, the link was partially hardcoded like this: The problem with this hardcoded, tightly-coupled approach is that it becomes challenging to change URLs on projects with a lot of templates. However, since -you defined the name argument in the :func:`~django.conf.urls.url` functions in +you defined the name argument in the :func:`~django.urls.path` functions in the ``polls.urls`` module, you can remove a reliance on specific URL paths defined in your url configurations by using the ``{% url %}`` template tag: @@ -402,7 +400,7 @@ defined below:: ... # the 'name' value as called by the {% url %} template tag - url(r'^(?P[0-9]+)/$', views.detail, name='detail'), + path('/', views.detail, name='detail'), ... If you want to change the URL of the polls detail view to something else, @@ -411,11 +409,11 @@ template (or templates) you would change it in ``polls/urls.py``:: ... # added the word 'specifics' - url(r'^specifics/(?P[0-9]+)/$', views.detail, name='detail'), + path('specifics//', views.detail, name='detail'), ... Namespacing URL names -====================== +===================== The tutorial project has just one app, ``polls``. In real Django projects, there might be five, ten, twenty apps or more. How does Django differentiate @@ -427,32 +425,32 @@ make it so that Django knows which app view to create for a url when using the The answer is to add namespaces to your URLconf. In the ``polls/urls.py`` file, go ahead and add an ``app_name`` to set the application namespace: -.. snippet:: - :filename: polls/urls.py +.. code-block:: python + :caption: polls/urls.py - from django.conf.urls import url + from django.urls import path from . import views app_name = 'polls' urlpatterns = [ - url(r'^$', views.index, name='index'), - url(r'^(?P[0-9]+)/$', views.detail, name='detail'), - url(r'^(?P[0-9]+)/results/$', views.results, name='results'), - url(r'^(?P[0-9]+)/vote/$', views.vote, name='vote'), + path('', views.index, name='index'), + path('/', views.detail, name='detail'), + path('/results/', views.results, name='results'), + path('/vote/', views.vote, name='vote'), ] Now change your ``polls/index.html`` template from: -.. snippet:: html+django - :filename: polls/templates/polls/index.html +.. code-block:: html+django + :caption: polls/templates/polls/index.html
      • {{ question.question_text }}
      • to point at the namespaced detail view: -.. snippet:: html+django - :filename: polls/templates/polls/index.html +.. code-block:: html+django + :caption: polls/templates/polls/index.html
      • {{ question.question_text }}
      • diff --git a/docs/intro/tutorial04.txt b/docs/intro/tutorial04.txt index f320476548ec..6bfe506d7060 100644 --- a/docs/intro/tutorial04.txt +++ b/docs/intro/tutorial04.txt @@ -12,8 +12,8 @@ Write a simple form Let's update our poll detail template ("polls/detail.html") from the last tutorial, so that the template contains an HTML ```` element: -.. snippet:: html+django - :filename: polls/templates/polls/detail.html +.. code-block:: html+django + :caption: polls/templates/polls/detail.html

        {{ question.question_text }}

        @@ -22,10 +22,10 @@ tutorial, so that the template contains an HTML ```` element: {% csrf_token %} {% for choice in question.choice_set.all %} - -
        + +
        {% endfor %} - + A quick rundown: @@ -58,19 +58,19 @@ Now, let's create a Django view that handles the submitted data and does something with it. Remember, in :doc:`Tutorial 3 `, we created a URLconf for the polls application that includes this line: -.. snippet:: - :filename: polls/urls.py +.. code-block:: python + :caption: polls/urls.py - url(r'^(?P[0-9]+)/vote/$', views.vote, name='vote'), + path('/vote/', views.vote, name='vote'), We also created a dummy implementation of the ``vote()`` function. Let's create a real version. Add the following to ``polls/views.py``: -.. snippet:: - :filename: polls/views.py +.. code-block:: python + :caption: polls/views.py + from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, render - from django.http import HttpResponseRedirect, HttpResponse from django.urls import reverse from .models import Choice, Question @@ -146,8 +146,8 @@ response documentation `. After somebody votes in a question, the ``vote()`` view redirects to the results page for the question. Let's write that view: -.. snippet:: - :filename: polls/views.py +.. code-block:: python + :caption: polls/views.py from django.shortcuts import get_object_or_404, render @@ -162,8 +162,8 @@ redundancy later. Now, create a ``polls/results.html`` template: -.. snippet:: html+django - :filename: polls/templates/polls/results.html +.. code-block:: html+django + :caption: polls/templates/polls/results.html

        {{ question.question_text }}

        @@ -211,11 +211,11 @@ Let's convert our poll app to use the generic views system, so we can delete a bunch of our own code. We'll just have to take a few steps to make the conversion. We will: -1. Convert the URLconf. +#. Convert the URLconf. -2. Delete some of the old, unneeded views. +#. Delete some of the old, unneeded views. -3. Introduce new views based on Django's generic views. +#. Introduce new views based on Django's generic views. Read on for details. @@ -234,23 +234,23 @@ Amend URLconf First, open the ``polls/urls.py`` URLconf and change it like so: -.. snippet:: - :filename: polls/urls.py +.. code-block:: python + :caption: polls/urls.py - from django.conf.urls import url + from django.urls import path from . import views app_name = 'polls' urlpatterns = [ - url(r'^$', views.IndexView.as_view(), name='index'), - url(r'^(?P[0-9]+)/$', views.DetailView.as_view(), name='detail'), - url(r'^(?P[0-9]+)/results/$', views.ResultsView.as_view(), name='results'), - url(r'^(?P[0-9]+)/vote/$', views.vote, name='vote'), + path('', views.IndexView.as_view(), name='index'), + path('/', views.DetailView.as_view(), name='detail'), + path('/results/', views.ResultsView.as_view(), name='results'), + path('/vote/', views.vote, name='vote'), ] -Note that the name of the matched pattern in the regexes of the second and third -patterns has changed from ```` to ````. +Note that the name of the matched pattern in the path strings of the second and +third patterns has changed from ```` to ````. Amend views ----------- @@ -259,11 +259,11 @@ Next, we're going to remove our old ``index``, ``detail``, and ``results`` views and use Django's generic views instead. To do so, open the ``polls/views.py`` file and change it like so: -.. snippet:: - :filename: polls/views.py +.. code-block:: python + :caption: polls/views.py - from django.shortcuts import get_object_or_404, render from django.http import HttpResponseRedirect + from django.shortcuts import get_object_or_404, render from django.urls import reverse from django.views import generic diff --git a/docs/intro/tutorial05.txt b/docs/intro/tutorial05.txt index c6e2827b6b89..b488fabba52a 100644 --- a/docs/intro/tutorial05.txt +++ b/docs/intro/tutorial05.txt @@ -134,8 +134,14 @@ right away: the ``Question.was_published_recently()`` method returns ``True`` if the ``Question`` was published within the last day (which is correct) but also if the ``Question``’s ``pub_date`` field is in the future (which certainly isn't). -To check if the bug really exists, using the Admin create a question whose date -lies in the future and check the method using the :djadmin:`shell`:: +Confirm the bug by using the :djadmin:`shell` to check the method on a question +whose date lies in the future: + +.. console:: + + $ python manage.py shell + +.. code-block:: pycon >>> import datetime >>> from django.utils import timezone @@ -160,37 +166,38 @@ whose name begins with ``test``. Put the following in the ``tests.py`` file in the ``polls`` application: -.. snippet:: - :filename: polls/tests.py +.. code-block:: python + :caption: polls/tests.py import datetime - from django.utils import timezone from django.test import TestCase + from django.utils import timezone from .models import Question - class QuestionMethodTests(TestCase): + class QuestionModelTests(TestCase): def test_was_published_recently_with_future_question(self): """ - was_published_recently() should return False for questions whose - pub_date is in the future. + was_published_recently() returns False for questions whose pub_date + is in the future. """ time = timezone.now() + datetime.timedelta(days=30) future_question = Question(pub_date=time) self.assertIs(future_question.was_published_recently(), False) -What we have done here is created a :class:`django.test.TestCase` subclass -with a method that creates a ``Question`` instance with a ``pub_date`` in the -future. We then check the output of ``was_published_recently()`` - which -*ought* to be False. +Here we have created a :class:`django.test.TestCase` subclass with a method that +creates a ``Question`` instance with a ``pub_date`` in the future. We then check +the output of ``was_published_recently()`` - which *ought* to be False. Running tests ------------- -In the terminal, we can run our test:: +In the terminal, we can run our test: + +.. console:: $ python manage.py test polls @@ -200,7 +207,7 @@ and you'll see something like:: System check identified no issues (0 silenced). F ====================================================================== - FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionMethodTests) + FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/path/to/mysite/polls/tests.py", line 16, in test_was_published_recently_with_future_question @@ -213,9 +220,16 @@ and you'll see something like:: FAILED (failures=1) Destroying test database for alias 'default'... +.. admonition:: Different error? + + If instead you're getting a ``NameError`` here, you may have missed a step + in :ref:`Part 2 ` where we added imports of + ``datetime`` and ``timezone`` to ``polls/models.py``. Copy the imports from + that section, and try running your tests again. + What happened is this: -* ``python manage.py test polls`` looked for tests in the ``polls`` application +* ``manage.py test polls`` looked for tests in the ``polls`` application * it found a subclass of the :class:`django.test.TestCase` class @@ -241,8 +255,8 @@ return ``False`` if its ``pub_date`` is in the future. Amend the method in ``models.py``, so that it will only return ``True`` if the date is also in the past: -.. snippet:: - :filename: polls/models.py +.. code-block:: python + :caption: polls/models.py def was_published_recently(self): now = timezone.now() @@ -277,24 +291,24 @@ introduced another. Add two more test methods to the same class, to test the behavior of the method more comprehensively: -.. snippet:: - :filename: polls/tests.py +.. code-block:: python + :caption: polls/tests.py def test_was_published_recently_with_old_question(self): """ - was_published_recently() should return False for questions whose - pub_date is older than 1 day. + was_published_recently() returns False for questions whose pub_date + is older than 1 day. """ - time = timezone.now() - datetime.timedelta(days=30) + time = timezone.now() - datetime.timedelta(days=1, seconds=1) old_question = Question(pub_date=time) self.assertIs(old_question.was_published_recently(), False) def test_was_published_recently_with_recent_question(self): """ - was_published_recently() should return True for questions whose - pub_date is within the last day. + was_published_recently() returns True for questions whose pub_date + is within the last day. """ - time = timezone.now() - datetime.timedelta(hours=1) + time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59) recent_question = Question(pub_date=time) self.assertIs(recent_question.was_published_recently(), True) @@ -335,7 +349,13 @@ or even in the :djadmin:`shell`. We will start again with the :djadmin:`shell`, where we need to do a couple of things that won't be necessary in ``tests.py``. The first is to set up the test -environment in the :djadmin:`shell`:: +environment in the :djadmin:`shell`: + +.. console:: + + $ python manage.py shell + +.. code-block:: pycon >>> from django.test.utils import setup_test_environment >>> setup_test_environment() @@ -361,7 +381,10 @@ With that ready, we can ask the client to do some work for us:: >>> # get a response from '/' >>> response = client.get('/') - >>> # we should expect a 404 from that address + Not Found: / + >>> # we should expect a 404 from that address; if you instead see an + >>> # "Invalid HTTP_HOST header" error and a 400 response, you probably + >>> # omitted the setup_test_environment() call described earlier. >>> response.status_code 404 >>> # on the other hand we should expect to find something at '/polls/' @@ -372,8 +395,6 @@ With that ready, we can ask the client to do some work for us:: 200 >>> response.content b'\n \n\n' - >>> # If the following doesn't work, you probably omitted the call to - >>> # setup_test_environment() described above >>> response.context['latest_question_list'] ]> @@ -386,8 +407,8 @@ The list of polls shows polls that aren't published yet (i.e. those that have a In :doc:`Tutorial 4 ` we introduced a class-based view, based on :class:`~django.views.generic.list.ListView`: -.. snippet:: - :filename: polls/views.py +.. code-block:: python + :caption: polls/views.py class IndexView(generic.ListView): template_name = 'polls/index.html' @@ -401,15 +422,15 @@ We need to amend the ``get_queryset()`` method and change it so that it also checks the date by comparing it with ``timezone.now()``. First we need to add an import: -.. snippet:: - :filename: polls/views.py +.. code-block:: python + :caption: polls/views.py from django.utils import timezone and then we must amend the ``get_queryset`` method like so: -.. snippet:: - :filename: polls/views.py +.. code-block:: python + :caption: polls/views.py def get_queryset(self): """ @@ -427,29 +448,29 @@ is, earlier than or equal to - ``timezone.now``. Testing our new view -------------------- -Now you can satisfy yourself that this behaves as expected by firing up the -runserver, loading the site in your browser, creating ``Questions`` with dates -in the past and future, and checking that only those that have been published -are listed. You don't want to have to do that *every single time you make any -change that might affect this* - so let's also create a test, based on our -:djadmin:`shell` session above. +Now you can satisfy yourself that this behaves as expected by firing up +``runserver``, loading the site in your browser, creating ``Questions`` with +dates in the past and future, and checking that only those that have been +published are listed. You don't want to have to do that *every single time you +make any change that might affect this* - so let's also create a test, based on +our :djadmin:`shell` session above. Add the following to ``polls/tests.py``: -.. snippet:: - :filename: polls/tests.py +.. code-block:: python + :caption: polls/tests.py from django.urls import reverse and we'll create a shortcut function to create questions as well as a new test class: -.. snippet:: - :filename: polls/tests.py +.. code-block:: python + :caption: polls/tests.py def create_question(question_text, days): """ - Creates a question with the given `question_text` and published the + Create a question with the given `question_text` and published the given number of `days` offset to now (negative for questions published in the past, positive for questions that have yet to be published). """ @@ -457,19 +478,19 @@ class: return Question.objects.create(question_text=question_text, pub_date=time) - class QuestionViewTests(TestCase): - def test_index_view_with_no_questions(self): + class QuestionIndexViewTests(TestCase): + def test_no_questions(self): """ - If no questions exist, an appropriate message should be displayed. + If no questions exist, an appropriate message is displayed. """ response = self.client.get(reverse('polls:index')) self.assertEqual(response.status_code, 200) self.assertContains(response, "No polls are available.") self.assertQuerysetEqual(response.context['latest_question_list'], []) - def test_index_view_with_a_past_question(self): + def test_past_question(self): """ - Questions with a pub_date in the past should be displayed on the + Questions with a pub_date in the past are displayed on the index page. """ create_question(question_text="Past question.", days=-30) @@ -479,9 +500,9 @@ class: [''] ) - def test_index_view_with_a_future_question(self): + def test_future_question(self): """ - Questions with a pub_date in the future should not be displayed on + Questions with a pub_date in the future aren't displayed on the index page. """ create_question(question_text="Future question.", days=30) @@ -489,10 +510,10 @@ class: self.assertContains(response, "No polls are available.") self.assertQuerysetEqual(response.context['latest_question_list'], []) - def test_index_view_with_future_question_and_past_question(self): + def test_future_question_and_past_question(self): """ Even if both past and future questions exist, only past questions - should be displayed. + are displayed. """ create_question(question_text="Past question.", days=-30) create_question(question_text="Future question.", days=30) @@ -502,7 +523,7 @@ class: [''] ) - def test_index_view_with_two_past_questions(self): + def test_two_past_questions(self): """ The questions index page may display multiple questions. """ @@ -520,20 +541,19 @@ Let's look at some of these more closely. First is a question shortcut function, ``create_question``, to take some repetition out of the process of creating questions. -``test_index_view_with_no_questions`` doesn't create any questions, but checks -the message: "No polls are available." and verifies the ``latest_question_list`` -is empty. Note that the :class:`django.test.TestCase` class provides some -additional assertion methods. In these examples, we use +``test_no_questions`` doesn't create any questions, but checks the message: +"No polls are available." and verifies the ``latest_question_list`` is empty. +Note that the :class:`django.test.TestCase` class provides some additional +assertion methods. In these examples, we use :meth:`~django.test.SimpleTestCase.assertContains()` and :meth:`~django.test.TransactionTestCase.assertQuerysetEqual()`. -In ``test_index_view_with_a_past_question``, we create a question and verify that it -appears in the list. +In ``test_past_question``, we create a question and verify that it appears in +the list. -In ``test_index_view_with_a_future_question``, we create a question with a -``pub_date`` in the future. The database is reset for each test method, so the -first question is no longer there, and so again the index shouldn't have any -questions in it. +In ``test_future_question``, we create a question with a ``pub_date`` in the +future. The database is reset for each test method, so the first question is no +longer there, and so again the index shouldn't have any questions in it. And so on. In effect, we are using the tests to tell a story of admin input and user experience on the site, and checking that at every state and for every @@ -546,8 +566,8 @@ What we have works well; however, even though future questions don't appear in the *index*, users can still reach them if they know or guess the right URL. So we need to add a similar constraint to ``DetailView``: -.. snippet:: - :filename: polls/views.py +.. code-block:: python + :caption: polls/views.py class DetailView(generic.DetailView): ... @@ -561,24 +581,24 @@ And of course, we will add some tests, to check that a ``Question`` whose ``pub_date`` is in the past can be displayed, and that one with a ``pub_date`` in the future is not: -.. snippet:: - :filename: polls/tests.py +.. code-block:: python + :caption: polls/tests.py - class QuestionIndexDetailTests(TestCase): - def test_detail_view_with_a_future_question(self): + class QuestionDetailViewTests(TestCase): + def test_future_question(self): """ - The detail view of a question with a pub_date in the future should - return a 404 not found. + The detail view of a question with a pub_date in the future + returns a 404 not found. """ future_question = create_question(question_text='Future question.', days=5) url = reverse('polls:detail', args=(future_question.id,)) response = self.client.get(url) self.assertEqual(response.status_code, 404) - def test_detail_view_with_a_past_question(self): + def test_past_question(self): """ - The detail view of a question with a pub_date in the past should - display the question's text. + The detail view of a question with a pub_date in the past + displays the question's text. """ past_question = create_question(question_text='Past Question.', days=-5) url = reverse('polls:detail', args=(past_question.id,)) diff --git a/docs/intro/tutorial06.txt b/docs/intro/tutorial06.txt index 0d3dd2331542..e8b1d02764aa 100644 --- a/docs/intro/tutorial06.txt +++ b/docs/intro/tutorial06.txt @@ -56,8 +56,8 @@ reference the path for templates. Put the following code in that stylesheet (``polls/static/polls/style.css``): -.. snippet:: css - :filename: polls/static/polls/style.css +.. code-block:: css + :caption: polls/static/polls/style.css li a { color: green; @@ -65,18 +65,26 @@ Put the following code in that stylesheet (``polls/static/polls/style.css``): Next, add the following at the top of ``polls/templates/polls/index.html``: -.. snippet:: html+django - :filename: polls/templates/polls/index.html +.. code-block:: html+django + :caption: polls/templates/polls/index.html {% load static %} - + The ``{% static %}`` template tag generates the absolute URL of static files. -That's all you need to do for development. Reload -``http://localhost:8000/polls/`` and you should see that the question links are -green (Django style!) which means that your stylesheet was properly loaded. +That's all you need to do for development. + +Start the server (or restart it if it's already running): + +.. console:: + + $ python manage.py runserver + +Reload ``http://localhost:8000/polls/`` and you should see that the question +links are green (Django style!) which means that your stylesheet was properly +loaded. Adding a background-image ========================= @@ -88,15 +96,15 @@ called ``background.gif``. In other words, put your image in Then, add to your stylesheet (``polls/static/polls/style.css``): -.. snippet:: css - :filename: polls/static/polls/style.css +.. code-block:: css + :caption: polls/static/polls/style.css body { - background: white url("images/background.gif") no-repeat right bottom; + background: white url("images/background.gif") no-repeat; } Reload ``http://localhost:8000/polls/`` and you should see the background -loaded in the bottom right of the screen. +loaded in the top left of the screen. .. warning:: diff --git a/docs/intro/tutorial07.txt b/docs/intro/tutorial07.txt index 7dd0f42e22c8..001c6ec9988c 100644 --- a/docs/intro/tutorial07.txt +++ b/docs/intro/tutorial07.txt @@ -18,8 +18,8 @@ Django the options you want when you register the object. Let's see how this works by reordering the fields on the edit form. Replace the ``admin.site.register(Question)`` line with: -.. snippet:: - :filename: polls/admin.py +.. code-block:: python + :caption: polls/admin.py from django.contrib import admin @@ -47,8 +47,8 @@ of fields, choosing an intuitive order is an important usability detail. And speaking of forms with dozens of fields, you might want to split the form up into fieldsets: -.. snippet:: - :filename: polls/admin.py +.. code-block:: python + :caption: polls/admin.py from django.contrib import admin @@ -81,8 +81,8 @@ Yet. There are two ways to solve this problem. The first is to register ``Choice`` with the admin just as we did with ``Question``. That's easy: -.. snippet:: - :filename: polls/admin.py +.. code-block:: python + :caption: polls/admin.py from django.contrib import admin @@ -115,8 +115,8 @@ It'd be better if you could add a bunch of Choices directly when you create the Remove the ``register()`` call for the ``Choice`` model. Then, edit the ``Question`` registration code to read: -.. snippet:: - :filename: polls/admin.py +.. code-block:: python + :caption: polls/admin.py from django.contrib import admin @@ -162,8 +162,8 @@ fields for entering related ``Choice`` objects. For that reason, Django offers a tabular way of displaying inline related objects; you just need to change the ``ChoiceInline`` declaration to read: -.. snippet:: - :filename: polls/admin.py +.. code-block:: python + :caption: polls/admin.py class ChoiceInline(admin.TabularInline): #... @@ -194,8 +194,8 @@ more helpful if we could display individual fields. To do that, use the tuple of field names to display, as columns, on the change list page for the object: -.. snippet:: - :filename: polls/admin.py +.. code-block:: python + :caption: polls/admin.py class QuestionAdmin(admin.ModelAdmin): # ... @@ -204,8 +204,8 @@ object: Just for good measure, let's also include the ``was_published_recently()`` method from :doc:`Tutorial 2 `: -.. snippet:: - :filename: polls/admin.py +.. code-block:: python + :caption: polls/admin.py class QuestionAdmin(admin.ModelAdmin): # ... @@ -226,8 +226,8 @@ representation of the output. You can improve that by giving that method (in :file:`polls/models.py`) a few attributes, as follows: -.. snippet:: - :filename: polls/models.py +.. code-block:: python + :caption: polls/models.py class Question(models.Model): # ... @@ -301,8 +301,8 @@ keeping your templates within the project is a good convention to follow. Open your settings file (:file:`mysite/settings.py`, remember) and add a :setting:`DIRS ` option in the :setting:`TEMPLATES` setting: -.. snippet:: - :filename: mysite/settings.py +.. code-block:: python + :caption: mysite/settings.py TEMPLATES = [ { @@ -328,8 +328,8 @@ when loading Django templates; it's a search path. Just like the static files, we *could* have all our templates together, in one big templates directory, and it would work perfectly well. However, templates that belong to a particular application should be placed in that - application’s template directory (e.g. ``polls/templates``) rather than the - project’s (``templates``). We'll discuss in more detail in the + application's template directory (e.g. ``polls/templates``) rather than the + project's (``templates``). We'll discuss in more detail in the :doc:`reusable apps tutorial ` *why* we do this. Now create a directory called ``admin`` inside ``templates``, and copy the @@ -342,7 +342,7 @@ template directory in the source code of Django itself If you have difficulty finding where the Django source files are located on your system, run the following command: - .. code-block:: console + .. console:: $ python -c "import django; print(django.__path__)" diff --git a/docs/intro/whatsnext.txt b/docs/intro/whatsnext.txt index f3bd5bce2b39..7d3346a12a70 100644 --- a/docs/intro/whatsnext.txt +++ b/docs/intro/whatsnext.txt @@ -97,10 +97,8 @@ reasons: Django APIs or behaviors change. Django's documentation is kept in the same source control system as its code. It -lives in the `docs`_ directory of our Git repository. Each document online is a -separate text file in the repository. - -.. _docs: https://github.com/django/django/tree/master/docs +lives in the :source:`docs` directory of our Git repository. Each document +online is a separate text file in the repository. Where to get it =============== @@ -116,8 +114,8 @@ https://docs.djangoproject.com/en/dev/. These HTML pages are generated automatically from the text files in source control. That means they reflect the "latest and greatest" in Django -- they include the very latest corrections and additions, and they discuss the latest Django features, which may only be -available to users of the Django development version. (See "Differences between -versions" below.) +available to users of the Django development version. (See +:ref:`differences-between-doc-versions` below.) We encourage you to help improve the docs by submitting changes, corrections and suggestions in the `ticket system`_. The Django developers actively monitor the @@ -137,11 +135,11 @@ In plain text For offline reading, or just for convenience, you can read the Django documentation in plain text. -If you're using an official release of Django, note that the zipped package -(tarball) of the code includes a ``docs/`` directory, which contains all the -documentation for that release. +If you're using an official release of Django, the zipped package (tarball) of +the code includes a ``docs/`` directory, which contains all the documentation +for that release. -If you're using the development version of Django (aka "trunk"), note that the +If you're using the development version of Django (aka the master branch), the ``docs/`` directory contains all of the documentation. You can update your Git checkout to get the latest changes. @@ -150,7 +148,7 @@ Unix ``grep`` utility to search for a phrase in all of the documentation. For example, this will show you each mention of the phrase "max_length" in any Django document: -.. code-block:: console +.. console:: $ grep -r max_length /path/to/django/docs/ @@ -163,7 +161,7 @@ You can get a local copy of the HTML documentation following a few easy steps: plain text to HTML. You'll need to install Sphinx by either downloading and installing the package from the Sphinx website, or with ``pip``: - .. code-block:: console + .. console:: $ pip install Sphinx @@ -194,34 +192,32 @@ __ https://www.gnu.org/software/make/ Differences between versions ============================ -As previously mentioned, the text documentation in our Git repository -contains the "latest and greatest" changes and additions. These changes often -include documentation of new features added in the Django development version --- the Git ("trunk") version of Django. For that reason, it's worth -pointing out our policy on keeping straight the documentation for various -versions of the framework. +The text documentation in the master branch of the Git repository contains the +"latest and greatest" changes and additions. These changes include +documentation of new features targeted for Django's next :term:`feature +release `. For that reason, it's worth pointing out our policy +to highlight recent changes and additions to Django. We follow this policy: -* The primary documentation on djangoproject.com is an HTML version of the - latest docs in Git. These docs always correspond to the latest - official Django release, plus whatever features we've added/changed in - the framework *since* the latest release. +* The development documentation at https://docs.djangoproject.com/en/dev/ is + from the master branch. These docs correspond to the latest feature release, + plus whatever features have been added/changed in the framework since then. -* As we add features to Django's development version, we try to update the +* As we add features to Django's development version, we update the documentation in the same Git commit transaction. * To distinguish feature changes/additions in the docs, we use the phrase: - "New in version X.Y", being X.Y the next release version (hence, the one - being developed). + "New in Django Development version" for the version of Django that hasn't + been released yet, or "New in version X.Y" for released versions. * Documentation fixes and improvements may be backported to the last release branch, at the discretion of the committer, however, once a version of - Django is :ref:`no longer supported`, that - version of the docs won't get any further updates. + Django is :ref:`no longer supported`, that version + of the docs won't get any further updates. * The `main documentation Web page`_ includes links to documentation for - all previous versions. Be sure you are using the version of the docs + previous versions. Be sure you are using the version of the docs corresponding to the version of Django you are using! .. _main documentation Web page: https://docs.djangoproject.com/en/dev/ diff --git a/docs/man/django-admin.1 b/docs/man/django-admin.1 index 3f07cc0e7af9..5b7e4d06bc59 100644 --- a/docs/man/django-admin.1 +++ b/docs/man/django-admin.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "DJANGO-ADMIN" "1" "September 23, 2015" "1.9" "Django" +.TH "DJANGO-ADMIN" "1" "November 04, 2019" "2.2" "Django" .SH NAME django-admin \- Utility script for the Django Web framework . @@ -34,18 +34,10 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] \fBdjango\-admin\fP is Django\(aqs command\-line utility for administrative tasks. This document outlines all it can do. .sp -In addition, \fBmanage.py\fP is automatically created in each Django project. -\fBmanage.py\fP is a thin wrapper around \fBdjango\-admin\fP that takes care of -several things for you before delegating to \fBdjango\-admin\fP: -.INDENT 0.0 -.IP \(bu 2 -It puts your project\(aqs package on \fBsys.path\fP\&. -.IP \(bu 2 -It sets the \fBDJANGO_SETTINGS_MODULE\fP environment variable so that -it points to your project\(aqs \fBsettings.py\fP file. -.IP \(bu 2 -It calls \fBdjango.setup()\fP to initialize various internals of Django. -.UNINDENT +In addition, \fBmanage.py\fP is automatically created in each Django project. It +does the same thing as \fBdjango\-admin\fP but also sets the +\fBDJANGO_SETTINGS_MODULE\fP environment variable so that it points to your +project\(aqs \fBsettings.py\fP file. .sp The \fBdjango\-admin\fP script should be on your system path if you installed Django via its \fBsetup.py\fP utility. If it\(aqs not on your path, you can find it @@ -66,9 +58,6 @@ option. The command\-line examples throughout this document use \fBdjango\-admin\fP to be consistent, but any example can use \fBmanage.py\fP or \fBpython \-m django\fP just as well. -.sp -\fBpython \-m django\fP was added. - .SH USAGE .INDENT 0.0 .INDENT 3.5 @@ -113,7 +102,7 @@ contains the string \fB\(aqmysite.blog\(aq\fP, the app name is \fBblog\fP\&. .sp Run \fBdjango\-admin version\fP to display the current Django version. .sp -The output follows the schema described in \fI\%PEP 386\fP: +The output follows the schema described in \fI\%PEP 440\fP: .INDENT 0.0 .INDENT 3.5 .sp @@ -128,32 +117,26 @@ The output follows the schema described in \fI\%PEP 386\fP: .UNINDENT .SS Displaying debug output .sp -Use \fI\%\-\-verbosity\fP to specify the amount of notification and debug information -that \fBdjango\-admin\fP should print to the console. For more details, see the -documentation for the \fI\%\-\-verbosity\fP option. +Use \fI\%\-\-verbosity\fP to specify the amount of notification and debug +information that \fBdjango\-admin\fP prints to the console. .SH AVAILABLE COMMANDS -.SS check +.SS \fBcheck\fP .INDENT 0.0 .TP -.B django\-admin check +.B django\-admin check [app_label [app_label ...]] .UNINDENT .sp -Uses the \fBsystem check framework\fP to inspect -the entire Django project for common problems. -.sp -The system check framework will confirm that there aren\(aqt any problems with -your installed models or your admin registrations. It will also provide warnings -of common compatibility problems introduced by upgrading Django to a new version. -Custom checks may be introduced by other libraries and applications. +Uses the system check framework to inspect the entire +Django project for common problems. .sp -By default, all apps will be checked. You can check a subset of apps by providing -a list of app labels as arguments: +By default, all apps will be checked. You can check a subset of apps by +providing a list of app labels as arguments: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C -python manage.py check auth admin myapp +django\-admin check auth admin myapp .ft P .fi .UNINDENT @@ -162,19 +145,19 @@ python manage.py check auth admin myapp If you do not specify any app, all apps will be checked. .INDENT 0.0 .TP -.B \-\-tag +.B \-\-tag TAGS, \-t TAGS .UNINDENT .sp -The \fBsystem check framework\fP performs many different -types of checks. These check types are categorized with tags. You can use these tags -to restrict the checks performed to just those in a particular category. For example, -to perform only security and compatibility checks, you would run: +The system check framework performs many different types of checks that are +categorized with tags\&. You can use these +tags to restrict the checks performed to just those in a particular category. +For example, to perform only models and compatibility checks, run: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C -python manage.py check \-\-tag security \-\-tag compatibility +django\-admin check \-\-tag models \-\-tag compatibility .ft P .fi .UNINDENT @@ -184,16 +167,13 @@ python manage.py check \-\-tag security \-\-tag compatibility .B \-\-list\-tags .UNINDENT .sp -List all available tags. +Lists all available tags. .INDENT 0.0 .TP .B \-\-deploy .UNINDENT .sp - -.sp -The \fB\-\-deploy\fP option activates some additional checks that are only relevant -in a deployment setting. +Activates some additional checks that are only relevant in a deployment setting. .sp You can use this option in your local development environment, but since your local development settings module may not have many of your production settings, @@ -205,7 +185,7 @@ or by passing the \fB\-\-settings\fP option: .sp .nf .ft C -python manage.py check \-\-deploy \-\-settings=production_settings +django\-admin check \-\-deploy \-\-settings=production_settings .ft P .fi .UNINDENT @@ -214,31 +194,40 @@ python manage.py check \-\-deploy \-\-settings=production_settings Or you could run it directly on a production or staging deployment to verify that the correct settings are in use (omitting \fB\-\-settings\fP). You could even make it part of your integration test suite. -.SS compilemessages +.INDENT 0.0 +.TP +.B \-\-fail\-level {CRITICAL,ERROR,WARNING,INFO,DEBUG} +.UNINDENT +.sp +Specifies the message level that will cause the command to exit with a non\-zero +status. Default is \fBERROR\fP\&. +.SS \fBcompilemessages\fP .INDENT 0.0 .TP .B django\-admin compilemessages .UNINDENT .sp -Compiles .po files created by \fI\%makemessages\fP to .mo files for use with -the builtin gettext support. See \fB/topics/i18n/index\fP\&. +Compiles \fB\&.po\fP files created by \fI\%makemessages\fP to \fB\&.mo\fP files for +use with the built\-in gettext support. See /topics/i18n/index\&. +.INDENT 0.0 +.TP +.B \-\-locale LOCALE, \-l LOCALE +.UNINDENT .sp -Use the \fI\%\-\-locale\fP option (or its shorter version \fB\-l\fP) to -specify the locale(s) to process. If not provided, all locales are processed. +Specifies the locale(s) to process. If not provided, all locales are processed. +.INDENT 0.0 +.TP +.B \-\-exclude EXCLUDE, \-x EXCLUDE +.UNINDENT .sp -Use the \fI\%\-\-exclude\fP option (or its shorter version \fB\-x\fP) to -specify the locale(s) to exclude from processing. If not provided, no locales +Specifies the locale(s) to exclude from processing. If not provided, no locales are excluded. +.INDENT 0.0 +.TP +.B \-\-use\-fuzzy, \-f +.UNINDENT .sp -You can pass \fB\-\-use\-fuzzy\fP option (or \fB\-f\fP) to include fuzzy translations -into compiled files. -.sp -\fBcompilemessages\fP now matches the operation of \fI\%makemessages\fP, -scanning the project tree for \fB\&.po\fP files to compile. - -.sp -Added \fB\-\-exclude\fP and \fB\-\-use\-fuzzy\fP options. - +Includes fuzzy translations into compiled files. .sp Example usage: .INDENT 0.0 @@ -258,34 +247,38 @@ django\-admin compilemessages \-x pt_BR \-x fr .fi .UNINDENT .UNINDENT -.SS createcachetable +.SS \fBcreatecachetable\fP .INDENT 0.0 .TP .B django\-admin createcachetable .UNINDENT .sp Creates the cache tables for use with the database cache backend using the -information from your settings file. See \fB/topics/cache\fP for more +information from your settings file. See /topics/cache for more information. +.INDENT 0.0 +.TP +.B \-\-database DATABASE +.UNINDENT .sp -The \fI\%\-\-database\fP option can be used to specify the database -onto which the cache table will be installed, but since this information is -pulled from your settings by default, it\(aqs typically not needed. -.sp -The \fI\%\-\-dry\-run\fP option will print the SQL that would be run without -actually running it, so you can customize it or use the migrations framework. +Specifies the database in which the cache table(s) will be created. Defaults to +\fBdefault\fP\&. +.INDENT 0.0 +.TP +.B \-\-dry\-run +.UNINDENT .sp -The \fB\-\-dry\-run\fP option was added. - -.SS dbshell +Prints the SQL that would be run without actually running it, so you can +customize it or use the migrations framework. +.SS \fBdbshell\fP .INDENT 0.0 .TP .B django\-admin dbshell .UNINDENT .sp Runs the command\-line client for the database engine specified in your -\fBENGINE\fP setting, with the connection parameters specified in your -\fBUSER\fP, \fBPASSWORD\fP, etc., settings. +\fBENGINE\fP setting, with the connection parameters +specified in your \fBUSER\fP, \fBPASSWORD\fP, etc., settings. .INDENT 0.0 .IP \(bu 2 For PostgreSQL, this runs the \fBpsql\fP command\-line client. @@ -301,29 +294,53 @@ This command assumes the programs are on your \fBPATH\fP so that a simple call t the program name (\fBpsql\fP, \fBmysql\fP, \fBsqlite3\fP, \fBsqlplus\fP) will find the program in the right place. There\(aqs no way to specify the location of the program manually. +.INDENT 0.0 +.TP +.B \-\-database DATABASE +.UNINDENT .sp -The \fI\%\-\-database\fP option can be used to specify the database -onto which to open a shell. -.SS diffsettings +Specifies the database onto which to open a shell. Defaults to \fBdefault\fP\&. +.SS \fBdiffsettings\fP .INDENT 0.0 .TP .B django\-admin diffsettings .UNINDENT .sp Displays differences between the current settings file and Django\(aqs default -settings. +settings (or another settings file specified by \fI\%\-\-default\fP). .sp Settings that don\(aqt appear in the defaults are followed by \fB"###"\fP\&. For example, the default settings don\(aqt define \fBROOT_URLCONF\fP, so \fBROOT_URLCONF\fP is followed by \fB"###"\fP in the output of \fBdiffsettings\fP\&. +.INDENT 0.0 +.TP +.B \-\-all +.UNINDENT +.sp +Displays all settings, even if they have Django\(aqs default value. Such settings +are prefixed by \fB"###"\fP\&. +.INDENT 0.0 +.TP +.B \-\-default MODULE +.UNINDENT +.sp +The settings module to compare the current settings against. Leave empty to +compare against Django\(aqs default settings. +.INDENT 0.0 +.TP +.B \-\-output {hash,unified} +.UNINDENT .sp -The \fI\%\-\-all\fP option may be provided to display all settings, even -if they have Django\(aqs default value. Such settings are prefixed by \fB"###"\fP\&. -.SS dumpdata +Specifies the output format. Available values are \fBhash\fP and \fBunified\fP\&. +\fBhash\fP is the default mode that displays the output that\(aqs described above. +\fBunified\fP displays the output similar to \fBdiff \-u\fP\&. Default settings are +prefixed with a minus sign, followed by the changed setting prefixed with a +plus sign. +.SS \fBdumpdata\fP .INDENT 0.0 .TP -.B django\-admin dumpdata +.B django\-admin dumpdata [app_label[.ModelName] [app_label[.ModelName] ...]] .UNINDENT .sp Outputs to standard output all data in the database associated with the named @@ -337,78 +354,92 @@ Note that \fBdumpdata\fP uses the default manager on the model for selecting the records to dump. If you\(aqre using a custom manager as the default manager and it filters some of the available records, not all of the objects will be dumped. +.INDENT 0.0 +.TP +.B \-\-all, \-a +.UNINDENT .sp -The \fI\%\-\-all\fP option may be provided to specify that -\fBdumpdata\fP should use Django\(aqs base manager, dumping records which -might otherwise be filtered or modified by a custom manager. +Uses Django\(aqs base manager, dumping records which might otherwise be filtered +or modified by a custom manager. .INDENT 0.0 .TP -.B \-\-format +.B \-\-format FORMAT .UNINDENT .sp -By default, \fBdumpdata\fP will format its output in JSON, but you can use the -\fB\-\-format\fP option to specify another format. Currently supported formats -are listed in serialization\-formats\&. +Specifies the serialization format of the output. Defaults to JSON. Supported +formats are listed in serialization\-formats\&. .INDENT 0.0 .TP -.B \-\-indent +.B \-\-indent INDENT .UNINDENT .sp -By default, \fBdumpdata\fP will output all data on a single line. This isn\(aqt -easy for humans to read, so you can use the \fB\-\-indent\fP option to -pretty\-print the output with a number of indentation spaces. +Specifies the number of indentation spaces to use in the output. Defaults to +\fBNone\fP which displays all data on single line. +.INDENT 0.0 +.TP +.B \-\-exclude EXCLUDE, \-e EXCLUDE +.UNINDENT .sp -The \fI\%\-\-exclude\fP option may be provided to prevent specific -applications or models (specified as in the form of \fBapp_label.ModelName\fP) -from being dumped. If you specify a model name to \fBdumpdata\fP, the dumped +Prevents specific applications or models (specified in the form of +\fBapp_label.ModelName\fP) from being dumped. If you specify a model name, the output will be restricted to that model, rather than the entire application. You can also mix application names and model names. .sp -The \fI\%\-\-database\fP option can be used to specify the database -from which data will be dumped. +If you want to exclude multiple applications, pass \fB\-\-exclude\fP more than +once: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +django\-admin dumpdata \-\-exclude=auth \-\-exclude=contenttypes +.ft P +.fi +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-database DATABASE +.UNINDENT +.sp +Specifies the database from which data will be dumped. Defaults to \fBdefault\fP\&. .INDENT 0.0 .TP .B \-\-natural\-foreign .UNINDENT .sp -When this option is specified, Django will use the \fBnatural_key()\fP model -method to serialize any foreign key and many\-to\-many relationship to objects of -the type that defines the method. If you are dumping \fBcontrib.auth\fP -\fBPermission\fP objects or \fBcontrib.contenttypes\fP \fBContentType\fP objects, you -should probably be using this flag. See the natural keys documentation for more details on this -and the next option. +Uses the \fBnatural_key()\fP model method to serialize any foreign key and +many\-to\-many relationship to objects of the type that defines the method. If +you\(aqre dumping \fBcontrib.auth\fP \fBPermission\fP objects or +\fBcontrib.contenttypes\fP \fBContentType\fP objects, you should probably use this +flag. See the natural keys +documentation for more details on this and the next option. .INDENT 0.0 .TP .B \-\-natural\-primary .UNINDENT .sp -When this option is specified, Django will not provide the primary key in the -serialized data of this object since it can be calculated during -deserialization. +Omits the primary key in the serialized data of this object since it can be +calculated during deserialization. .INDENT 0.0 .TP -.B \-\-pks +.B \-\-pks PRIMARY_KEYS .UNINDENT .sp -By default, \fBdumpdata\fP will output all the records of the model, but -you can use the \fB\-\-pks\fP option to specify a comma separated list of -primary keys on which to filter. This is only available when dumping -one model. +Outputs only the objects specified by a comma separated list of primary keys. +This is only available when dumping one model. By default, all the records of +the model are output. .INDENT 0.0 .TP -.B \-\-output +.B \-\-output OUTPUT, \-o OUTPUT .UNINDENT .sp - +Specifies a file to write the serialized data to. By default, the data goes to +standard output. .sp -By default \fBdumpdata\fP will output all the serialized data to standard output. -This option allows you to specify the file to which the data is to be written. -When this option is set and the verbosity is greater than 0 (the default), a +When this option is set and \fB\-\-verbosity\fP is greater than 0 (the default), a progress bar is shown in the terminal. -.sp -The progress bar in the terminal was added. - -.SS flush +.SS \fBflush\fP .INDENT 0.0 .TP .B django\-admin flush @@ -419,22 +450,33 @@ handlers. The table of which migrations have been applied is not cleared. .sp If you would rather start from an empty database and re\-run all migrations, you should drop and recreate the database and then run \fI\%migrate\fP instead. +.INDENT 0.0 +.TP +.B \-\-noinput, \-\-no\-input +.UNINDENT .sp -The \fI\%\-\-noinput\fP option may be provided to suppress all user -prompts. +Suppresses all user prompts. +.INDENT 0.0 +.TP +.B \-\-database DATABASE +.UNINDENT .sp -The \fI\%\-\-database\fP option may be used to specify the database -to flush. -.SS inspectdb +Specifies the database to flush. Defaults to \fBdefault\fP\&. +.SS \fBinspectdb\fP .INDENT 0.0 .TP -.B django\-admin inspectdb +.B django\-admin inspectdb [table [table ...]] .UNINDENT .sp Introspects the database tables in the database pointed\-to by the \fBNAME\fP setting and outputs a Django model module (a \fBmodels.py\fP file) to standard output. .sp +You may choose what tables or views to inspect by passing their names as +arguments. If no arguments are provided, models are created for views only if +the \fI\%\-\-include\-views\fP option is used. Models for partition tables are +created on PostgreSQL if the \fI\%\-\-include\-partitions\fP option is used. +.sp Use this if you have a legacy database with which you\(aqd like to use Django. The script will inspect the database and create a model for each table within it. @@ -447,7 +489,9 @@ output: If \fBinspectdb\fP cannot map a column\(aqs type to a model field type, it\(aqll use \fBTextField\fP and will insert the Python comment \fB\(aqThis field type is a guess.\(aq\fP next to the field in the generated -model. +model. The recognized fields may depend on apps listed in +\fBINSTALLED_APPS\fP\&. For example, \fBdjango.contrib.postgres\fP adds +recognition for several PostgreSQL\-specific field types. .IP \(bu 2 If the database column name is a Python reserved word (such as \fB\(aqpass\(aq\fP, \fB\(aqclass\(aq\fP or \fB\(aqfor\(aq\fP), \fBinspectdb\fP will append @@ -464,13 +508,6 @@ you run it, you\(aqll want to look over the generated models yourself to make customizations. In particular, you\(aqll need to rearrange models\(aq order, so that models that refer to other models are ordered properly. .sp -Primary keys are automatically introspected for PostgreSQL, MySQL and -SQLite, in which case Django puts in the \fBprimary_key=True\fP where -needed. -.sp -\fBinspectdb\fP works with PostgreSQL, MySQL and SQLite. Foreign\-key detection -only works in PostgreSQL and with certain types of MySQL tables. -.sp Django doesn\(aqt create database defaults when a \fBdefault\fP is specified on a model field. Similarly, database defaults aren\(aqt translated to model field defaults or @@ -482,36 +519,93 @@ modification, and deletion. If you do want to allow Django to manage the table\(aqs lifecycle, you\(aqll need to change the \fBmanaged\fP option to \fBTrue\fP (or simply remove it because \fBTrue\fP is its default value). +.SS Database\-specific notes +.SS Oracle +.INDENT 0.0 +.IP \(bu 2 +Models are created for materialized views if \fI\%\-\-include\-views\fP is +used. +.UNINDENT +.SS PostgreSQL +.INDENT 0.0 +.IP \(bu 2 +Models are created for foreign tables. +.IP \(bu 2 +Models are created for materialized views if +\fI\%\-\-include\-views\fP is used. +.IP \(bu 2 +Models are created for partition tables if +\fI\%\-\-include\-partitions\fP is used. +.UNINDENT +.sp +Support for foreign tables and materialized views was added. + +.INDENT 0.0 +.TP +.B \-\-database DATABASE +.UNINDENT .sp -The \fI\%\-\-database\fP option may be used to specify the -database to introspect. -.SS loaddata +Specifies the database to introspect. Defaults to \fBdefault\fP\&. .INDENT 0.0 .TP -.B django\-admin loaddata +.B \-\-include\-partitions +.UNINDENT +.sp + +.sp +If this option is provided, models are also created for partitions. +.sp +Only support for PostgreSQL is implemented. +.INDENT 0.0 +.TP +.B \-\-include\-views +.UNINDENT +.sp + +.sp +If this option is provided, models are also created for database views. +.SS \fBloaddata\fP +.INDENT 0.0 +.TP +.B django\-admin loaddata fixture [fixture ...] .UNINDENT .sp Searches for and loads the contents of the named fixture into the database. +.INDENT 0.0 +.TP +.B \-\-database DATABASE +.UNINDENT .sp -The \fI\%\-\-database\fP option can be used to specify the database -onto which the data will be loaded. +Specifies the database into which the data will be loaded. Defaults to +\fBdefault\fP\&. .INDENT 0.0 .TP -.B \-\-ignorenonexistent +.B \-\-ignorenonexistent, \-i .UNINDENT .sp -The \fI\%\-\-ignorenonexistent\fP option can be used to ignore fields and -models that may have been removed since the fixture was originally generated. +Ignores fields and models that may have been removed since the fixture was +originally generated. .INDENT 0.0 .TP -.B \-\-app +.B \-\-app APP_LABEL .UNINDENT .sp -The \fI\%\-\-app\fP option can be used to specify a single app to look -for fixtures in rather than looking through all apps. +Specifies a single app to look for fixtures in rather than looking in all apps. +.INDENT 0.0 +.TP +.B \-\-format FORMAT +.UNINDENT .sp -\fB\-\-ignorenonexistent\fP also ignores non\-existent models. - +Specifies the serialization format (e.g., +\fBjson\fP or \fBxml\fP) for fixtures \fI\%read from stdin\fP\&. +.INDENT 0.0 +.TP +.B \-\-exclude EXCLUDE, \-e EXCLUDE +.UNINDENT +.sp +Excludes loading the fixtures from the given applications and/or models (in the +form of \fBapp_label\fP or \fBapp_label.ModelName\fP). Use the option multiple +times to exclude more than one app or model. .SS What\(aqs a "fixture"? .sp A \fIfixture\fP is a collection of files that contain the serialized contents of @@ -689,7 +783,38 @@ For example, if your \fBDATABASES\fP setting has a \(aqmaster\(aq database defined, name the fixture \fBmydata.master.json\fP or \fBmydata.master.json.gz\fP and the fixture will only be loaded when you specify you want to load data into the \fBmaster\fP database. -.SS makemessages +.SS Loading fixtures from \fBstdin\fP +.sp +You can use a dash as the fixture name to load input from \fBsys.stdin\fP\&. For +example: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +django\-admin loaddata \-\-format=json \- +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +When reading from \fBstdin\fP, the \fI\%\-\-format\fP option +is required to specify the serialization format +of the input (e.g., \fBjson\fP or \fBxml\fP). +.sp +Loading from \fBstdin\fP is useful with standard input and output redirections. +For example: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +django\-admin dumpdata \-\-format=json \-\-database=test app_label.ModelName | django\-admin loaddata \-\-format=json \-\-database=prod \- +.ft P +.fi +.UNINDENT +.UNINDENT +.SS \fBmakemessages\fP .INDENT 0.0 .TP .B django\-admin makemessages @@ -701,32 +826,23 @@ conf/locale (in the Django tree) or locale (for project and application) directory. After making changes to the messages files you need to compile them with \fI\%compilemessages\fP for use with the builtin gettext support. See the i18n documentation for details. +.sp +This command doesn\(aqt require configured settings. However, when settings aren\(aqt +configured, the command can\(aqt ignore the \fBMEDIA_ROOT\fP and +\fBSTATIC_ROOT\fP directories or include \fBLOCALE_PATHS\fP\&. .INDENT 0.0 .TP -.B \-\-all +.B \-\-all, \-a .UNINDENT .sp -Use the \fB\-\-all\fP or \fB\-a\fP option to update the message files for all -available languages. -.sp -Example usage: -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -django\-admin makemessages \-\-all -.ft P -.fi -.UNINDENT -.UNINDENT +Updates the message files for all available languages. .INDENT 0.0 .TP -.B \-\-extension +.B \-\-extension EXTENSIONS, \-e EXTENSIONS .UNINDENT .sp -Use the \fB\-\-extension\fP or \fB\-e\fP option to specify a list of file extensions -to examine (default: ".html", ".txt"). +Specifies a list of file extensions to examine (default: \fBhtml\fP, \fBtxt\fP, +\fBpy\fP or \fBjs\fP if \fI\%\-\-domain\fP is \fBjs\fP). .sp Example usage: .INDENT 0.0 @@ -740,7 +856,8 @@ django\-admin makemessages \-\-locale=de \-\-extension xhtml .UNINDENT .UNINDENT .sp -Separate multiple extensions with commas or use \-e or \-\-extension multiple times: +Separate multiple extensions with commas or use \fB\-e\fP or \fB\-\-extension\fP +multiple times: .INDENT 0.0 .INDENT 3.5 .sp @@ -751,14 +868,18 @@ django\-admin makemessages \-\-locale=de \-\-extension=html,txt \-\-extension xm .fi .UNINDENT .UNINDENT +.INDENT 0.0 +.TP +.B \-\-locale LOCALE, \-l LOCALE +.UNINDENT .sp -Use the \fI\%\-\-locale\fP option (or its shorter version \fB\-l\fP) to -specify the locale(s) to process. -.sp - +Specifies the locale(s) to process. +.INDENT 0.0 +.TP +.B \-\-exclude EXCLUDE, \-x EXCLUDE +.UNINDENT .sp -Use the \fI\%\-\-exclude\fP option (or its shorter version \fB\-x\fP) to -specify the locale(s) to exclude from processing. If not provided, no locales +Specifies the locale(s) to exclude from processing. If not provided, no locales are excluded. .sp Example usage: @@ -781,11 +902,10 @@ django\-admin makemessages \-x pt_BR \-x fr .UNINDENT .INDENT 0.0 .TP -.B \-\-domain +.B \-\-domain DOMAIN, \-d DOMAIN .UNINDENT .sp -Use the \fB\-\-domain\fP or \fB\-d\fP option to change the domain of the messages files. -Currently supported: +Specifies the domain of the messages files. Supported options are: .INDENT 0.0 .IP \(bu 2 \fBdjango\fP for all \fB*.py\fP, \fB*.html\fP and \fB*.txt\fP files (default) @@ -794,11 +914,10 @@ Currently supported: .UNINDENT .INDENT 0.0 .TP -.B \-\-symlinks +.B \-\-symlinks, \-s .UNINDENT .sp -Use the \fB\-\-symlinks\fP or \fB\-s\fP option to follow symlinks to directories when -looking for new translation strings. +Follows symlinks to directories when looking for new translation strings. .sp Example usage: .INDENT 0.0 @@ -813,13 +932,13 @@ django\-admin makemessages \-\-locale=de \-\-symlinks .UNINDENT .INDENT 0.0 .TP -.B \-\-ignore +.B \-\-ignore PATTERN, \-i PATTERN .UNINDENT .sp -Use the \fB\-\-ignore\fP or \fB\-i\fP option to ignore files or directories matching -the given \fI\%glob\fP\-style pattern. Use multiple times to ignore more. +Ignores files or directories matching the given \fI\%glob\fP\-style pattern. Use +multiple times to ignore more. .sp -These patterns are used by default: \fB\(aqCVS\(aq\fP, \fB\(aq.*\(aq\fP, \fB\(aq*~\(aq\fP, \fB\(aq*.pyc\(aq\fP +These patterns are used by default: \fB\(aqCVS\(aq\fP, \fB\(aq.*\(aq\fP, \fB\(aq*~\(aq\fP, \fB\(aq*.pyc\(aq\fP\&. .sp Example usage: .INDENT 0.0 @@ -837,31 +956,47 @@ django\-admin makemessages \-\-locale=en_US \-\-ignore=apps/* \-\-ignore=secret/ .B \-\-no\-default\-ignore .UNINDENT .sp -Use the \fB\-\-no\-default\-ignore\fP option to disable the default values of -\fI\%\-\-ignore\fP\&. +Disables the default values of \fB\-\-ignore\fP\&. .INDENT 0.0 .TP .B \-\-no\-wrap .UNINDENT .sp -Use the \fB\-\-no\-wrap\fP option to disable breaking long message lines into -several lines in language files. +Disables breaking long message lines into several lines in language files. .INDENT 0.0 .TP .B \-\-no\-location .UNINDENT .sp -Use the \fB\-\-no\-location\fP option to suppress writing \(aq\fB#: filename:line\fP’ -comment lines in language files. Note that using this option makes it harder -for technically skilled translators to understand each message\(aqs context. +Suppresses writing \(aq\fB#: filename:line\fP’ comment lines in language files. +Using this option makes it harder for technically skilled translators to +understand each message\(aqs context. +.INDENT 0.0 +.TP +.B \-\-add\-location [{full,file,never}] +.UNINDENT +.sp +Controls \fB#: filename:line\fP comment lines in language files. If the option +is: +.INDENT 0.0 +.IP \(bu 2 +\fBfull\fP (the default if not given): the lines include both file name and +line number. +.IP \(bu 2 +\fBfile\fP: the line number is omitted. +.IP \(bu 2 +\fBnever\fP: the lines are suppressed (same as \fI\%\-\-no\-location\fP). +.UNINDENT +.sp +Requires \fBgettext\fP 0.19 or newer. .INDENT 0.0 .TP .B \-\-keep\-pot .UNINDENT .sp -Use the \fB\-\-keep\-pot\fP option to prevent Django from deleting the temporary -.pot files it generates before creating the .po file. This is useful for -debugging errors which may prevent the final language files from being created. +Prevents deleting the temporary \fB\&.pot\fP files generated before creating the +\fB\&.po\fP file. This is useful for debugging errors which may prevent the final +language files from being created. .sp \fBSEE ALSO:\fP .INDENT 0.0 @@ -870,77 +1005,83 @@ See customizing\-makemessages for instructions on how to customize the keywords that \fI\%makemessages\fP passes to \fBxgettext\fP\&. .UNINDENT .UNINDENT -.SS makemigrations [] +.SS \fBmakemigrations\fP .INDENT 0.0 .TP -.B django\-admin makemigrations +.B django\-admin makemigrations [app_label [app_label ...]] .UNINDENT .sp Creates new migrations based on the changes detected to your models. Migrations, their relationship with apps and more are covered in depth in -\fBthe migrations documentation\fP\&. +the migrations documentation\&. .sp Providing one or more app names as arguments will limit the migrations created to the app(s) specified and any dependencies needed (the table at the other end of a \fBForeignKey\fP, for example). .sp - +To add migrations to an app that doesn\(aqt have a \fBmigrations\fP directory, run +\fBmakemigrations\fP with the app\(aqs \fBapp_label\fP\&. +.INDENT 0.0 +.TP +.B \-\-noinput, \-\-no\-input +.UNINDENT .sp -The \fB\-\-noinput\fP option may be provided to suppress all user prompts. If a suppressed -prompt cannot be resolved automatically, the command will exit with error code 3. +Suppresses all user prompts. If a suppressed prompt cannot be resolved +automatically, the command will exit with error code 3. .INDENT 0.0 .TP .B \-\-empty .UNINDENT .sp -The \fB\-\-empty\fP option will cause \fBmakemigrations\fP to output an empty -migration for the specified apps, for manual editing. This option is only -for advanced users and should not be used unless you are familiar with -the migration format, migration operations, and the dependencies between -your migrations. +Outputs an empty migration for the specified apps, for manual editing. This is +for advanced users and should not be used unless you are familiar with the +migration format, migration operations, and the dependencies between your +migrations. .INDENT 0.0 .TP .B \-\-dry\-run .UNINDENT .sp -The \fB\-\-dry\-run\fP option shows what migrations would be made without -actually writing any migrations files to disk. Using this option along with -\fB\-\-verbosity 3\fP will also show the complete migrations files that would be -written. +Shows what migrations would be made without actually writing any migrations +files to disk. Using this option along with \fB\-\-verbosity 3\fP will also show +the complete migrations files that would be written. .INDENT 0.0 .TP .B \-\-merge .UNINDENT .sp -The \fB\-\-merge\fP option enables fixing of migration conflicts. +Enables fixing of migration conflicts. .INDENT 0.0 .TP -.B \-\-name, \-n +.B \-\-name NAME, \-n NAME .UNINDENT .sp - -.sp -The \fB\-\-name\fP option allows you to give the migration(s) a custom name instead -of a generated one. +Allows naming the generated migration(s) instead of using a generated name. The +name must be a valid Python \fI\%identifier\fP\&. .INDENT 0.0 .TP -.B \-\-exit, \-e +.B \-\-no\-header .UNINDENT .sp .sp -The \fB\-\-exit\fP option will cause \fBmakemigrations\fP to exit with error code 1 -when no migrations are created (or would have been created, if combined with -\fB\-\-dry\-run\fP). -.SS migrate [ []] +Generate migration files without Django version and timestamp header. .INDENT 0.0 .TP -.B django\-admin migrate +.B \-\-check +.UNINDENT +.sp +Makes \fBmakemigrations\fP exit with a non\-zero status when model changes without +migrations are detected. +.SS \fBmigrate\fP +.INDENT 0.0 +.TP +.B django\-admin migrate [app_label] [migration_name] .UNINDENT .sp Synchronizes the database state with the current set of models and migrations. Migrations, their relationship with apps and more are covered in depth in -\fBthe migrations documentation\fP\&. +the migrations documentation\&. .sp The behavior of this command changes depending on the arguments provided: .INDENT 0.0 @@ -954,20 +1095,33 @@ to dependencies. \fB \fP: Brings the database schema to a state where the named migration is applied, but no later migrations in the same app are applied. This may involve unapplying migrations if you have previously -migrated past the named migration. Use the name \fBzero\fP to unapply all +migrated past the named migration. You can use a prefix of the migration +name, e.g. \fB0001\fP, as long as it\(aqs unique for the given app name. Use the +name \fBzero\fP to migrate all the way back i.e. to revert all applied migrations for an app. .UNINDENT .sp -The \fI\%\-\-database\fP option can be used to specify the database to -migrate. +\fBWARNING:\fP +.INDENT 0.0 +.INDENT 3.5 +When unapplying migrations, all dependent migrations will also be +unapplied, regardless of \fB\fP\&. You can use \fB\-\-plan\fP to check +which migrations will be unapplied. +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-database DATABASE +.UNINDENT +.sp +Specifies the database to migrate. Defaults to \fBdefault\fP\&. .INDENT 0.0 .TP .B \-\-fake .UNINDENT .sp -The \fB\-\-fake\fP option tells Django to mark the migrations as having been -applied or unapplied, but without actually running the SQL to change your -database schema. +Marks the migrations up to the target one (following the rules above) as +applied, but without actually running the SQL to change your database schema. .sp This is intended for advanced users to manipulate the current migration state directly if they\(aqre manually applying changes; @@ -979,11 +1133,9 @@ run correctly. .B \-\-fake\-initial .UNINDENT .sp - -.sp -The \fB\-\-fake\-initial\fP option can be used to allow Django to skip an app\(aqs -initial migration if all database tables with the names of all models created -by all \fBCreateModel\fP operations in that +Allows Django to skip an app\(aqs initial migration if all database tables with +the names of all models created by all +\fBCreateModel\fP operations in that migration already exist. This option is intended for use when first running migrations against a database that preexisted the use of migrations. This option does not, however, check for matching database schema beyond matching @@ -991,22 +1143,32 @@ table names and so is only safe to use if you are confident that your existing schema matches what is recorded in your initial migration. .INDENT 0.0 .TP -.B \-\-run\-syncdb +.B \-\-plan .UNINDENT .sp .sp -The \fB\-\-run\-syncdb\fP option allows creating tables for apps without migrations. -While this isn\(aqt recommended, the migrations framework is sometimes too slow -on large projects with hundreds of models. -.sp -Deprecated since version 1.8: The \fB\-\-list\fP option has been moved to the \fI\%showmigrations\fP +Shows the migration operations that will be performed for the given \fBmigrate\fP command. - -.SS runserver [port or address:port] .INDENT 0.0 .TP -.B django\-admin runserver +.B \-\-run\-syncdb +.UNINDENT +.sp +Allows creating tables for apps without migrations. While this isn\(aqt +recommended, the migrations framework is sometimes too slow on large projects +with hundreds of models. +.INDENT 0.0 +.TP +.B \-\-noinput, \-\-no\-input +.UNINDENT +.sp +Suppresses all user prompts. An example prompt is asking about removing stale +content types. +.SS \fBrunserver\fP +.INDENT 0.0 +.TP +.B django\-admin runserver [addrport] .UNINDENT .sp Starts a lightweight development Web server on the local machine. By default, @@ -1031,11 +1193,32 @@ needed. You don\(aqt need to restart the server for code changes to take effect. However, some actions like adding files don\(aqt trigger a restart, so you\(aqll have to restart the server in these cases. .sp -If you are using Linux and install \fI\%pyinotify\fP, kernel signals will be used to -autoreload the server (rather than polling file modification timestamps each -second). This offers better scaling to large projects, reduction in response -time to code modification, more robust change detection, and battery usage -reduction. +If you\(aqre using Linux or MacOS and install both \fI\%pywatchman\fP and the +\fI\%Watchman\fP service, kernel signals will be used to autoreload the server +(rather than polling file modification timestamps each second). This offers +better performance on large projects, reduced response time after code changes, +more robust change detection, and a reduction in power usage. +.INDENT 0.0 +.INDENT 3.5 +.IP "Large directories with many files may cause performance issues" +.sp +When using Watchman with a project that includes large non\-Python +directories like \fBnode_modules\fP, it\(aqs advisable to ignore this directory +for optimal performance. See the \fI\%watchman documentation\fP for information +on how to do this. +.UNINDENT +.UNINDENT +.INDENT 0.0 +.INDENT 3.5 +.IP "Watchman timeout" +.sp +The default timeout of \fBWatchman\fP client is 5 seconds. You can change it +by setting the \fBDJANGO_WATCHMAN_TIMEOUT\fP environment variable. +.UNINDENT +.UNINDENT +.sp +Watchman support replaced support for \fIpyinotify\fP\&. + .sp When you start the server, and each time you change Python code while the server is running, the system check framework will check your entire Django @@ -1055,60 +1238,34 @@ You can provide an IPv6 address surrounded by brackets .sp A hostname containing ASCII\-only characters can also be used. .sp -If the \fBstaticfiles\fP contrib app is enabled +If the staticfiles contrib app is enabled (default in new projects) the \fI\%runserver\fP command will be overridden with its own runserver command. .sp -If \fI\%migrate\fP was not previously executed, the table that stores the -history of migrations is created at first run of \fBrunserver\fP\&. +Logging of each request and response of the server is sent to the +django\-server\-logger logger. .INDENT 0.0 .TP .B \-\-noreload .UNINDENT .sp -Use the \fB\-\-noreload\fP option to disable the use of the auto\-reloader. This -means any Python code changes you make while the server is running will \fInot\fP -take effect if the particular Python modules have already been loaded into -memory. -.sp -Example usage: -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -django\-admin runserver \-\-noreload -.ft P -.fi -.UNINDENT -.UNINDENT +Disables the auto\-reloader. This means any Python code changes you make while +the server is running will \fInot\fP take effect if the particular Python modules +have already been loaded into memory. .INDENT 0.0 .TP .B \-\-nothreading .UNINDENT .sp -The development server is multithreaded by default. Use the \fB\-\-nothreading\fP -option to disable the use of threading in the development server. +Disables use of threading in the development server. The server is +multithreaded by default. .INDENT 0.0 .TP .B \-\-ipv6, \-6 .UNINDENT .sp -Use the \fB\-\-ipv6\fP (or shorter \fB\-6\fP) option to tell Django to use IPv6 for -the development server. This changes the default IP address from +Uses IPv6 for the development server. This changes the default IP address from \fB127.0.0.1\fP to \fB::1\fP\&. -.sp -Example usage: -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -django\-admin runserver \-\-ipv6 -.ft P -.fi -.UNINDENT -.UNINDENT .SS Examples of using different ports and addresses .sp Port 8000 on IP address \fB127.0.0.1\fP: @@ -1223,14 +1380,12 @@ django\-admin runserver \-6 localhost:8000 By default, the development server doesn\(aqt serve any static files for your site (such as CSS files, images, things under \fBMEDIA_URL\fP and so forth). If you want to configure Django to serve static media, read -\fB/howto/static\-files/index\fP\&. -.SS sendtestemail +/howto/static\-files/index\&. +.SS \fBsendtestemail\fP .INDENT 0.0 .TP -.B django\-admin sendtestemail +.B django\-admin sendtestemail [email [email ...]] .UNINDENT -.sp - .sp Sends a test email (to confirm email sending through Django is working) to the recipient(s) specified. For example: @@ -1244,120 +1399,154 @@ django\-admin sendtestemail foo@example.com bar@example.com .fi .UNINDENT .UNINDENT +.sp +There are a couple of options, and you may use any combination of them +together: .INDENT 0.0 .TP .B \-\-managers .UNINDENT .sp -Use the \fB\-\-managers\fP option to mail the email addresses specified in -\fBMANAGERS\fP using \fBmail_managers()\fP\&. +Mails the email addresses specified in \fBMANAGERS\fP using +\fBmail_managers()\fP\&. .INDENT 0.0 .TP .B \-\-admins .UNINDENT .sp -Use the \fB\-\-admins\fP option to mail the email addresses specified in -\fBADMINS\fP using \fBmail_admins()\fP\&. -.sp -Note that you may use any combination of these options together. -.SS shell +Mails the email addresses specified in \fBADMINS\fP using +\fBmail_admins()\fP\&. +.SS \fBshell\fP .INDENT 0.0 .TP .B django\-admin shell .UNINDENT .sp Starts the Python interactive interpreter. +.INDENT 0.0 +.TP +.B \-\-interface {ipython,bpython,python}, \-i {ipython,bpython,python} +.UNINDENT +.sp +Specifies the shell to use. By default, Django will use \fI\%IPython\fP or \fI\%bpython\fP if +either is installed. If both are installed, specify which one you want like so: .sp -Django will use \fI\%IPython\fP or \fI\%bpython\fP if either is installed. If you have a -rich shell installed but want to force use of the "plain" Python interpreter, -use the \fB\-\-plain\fP option, like so: +IPython: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C -django\-admin shell \-\-plain +django\-admin shell \-i ipython .ft P .fi .UNINDENT .UNINDENT .sp -If you would like to specify either IPython or bpython as your interpreter if -you have both installed you can specify an alternative interpreter interface -with the \fB\-i\fP or \fB\-\-interface\fP options like so: +bpython: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +django\-admin shell \-i bpython +.ft P +.fi +.UNINDENT +.UNINDENT .sp -IPython: +If you have a "rich" shell installed but want to force use of the "plain" +Python interpreter, use \fBpython\fP as the interface name, like so: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C -django\-admin shell \-i ipython -django\-admin shell \-\-interface ipython +django\-admin shell \-i python .ft P .fi .UNINDENT .UNINDENT +.INDENT 0.0 +.TP +.B \-\-nostartup +.UNINDENT .sp -bpython: +Disables reading the startup script for the "plain" Python interpreter. By +default, the script pointed to by the \fI\%PYTHONSTARTUP\fP environment +variable or the \fB~/.pythonrc.py\fP script is read. +.INDENT 0.0 +.TP +.B \-\-command COMMAND, \-c COMMAND +.UNINDENT +.sp +Lets you pass a command as a string to execute it as Django, like so: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C -django\-admin shell \-i bpython -django\-admin shell \-\-interface bpython +django\-admin shell \-\-command="import django; print(django.__version__)" .ft P .fi .UNINDENT .UNINDENT .sp -When the "plain" Python interactive interpreter starts (be it because -\fB\-\-plain\fP was specified or because no other interactive interface is -available) it reads the script pointed to by the \fI\%PYTHONSTARTUP\fP -environment variable and the \fB~/.pythonrc.py\fP script. If you don\(aqt wish this -behavior you can use the \fB\-\-no\-startup\fP option. e.g.: +You can also pass code in on standard input to execute it. For example: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C -django\-admin shell \-\-plain \-\-no\-startup +$ django\-admin shell < import django +> print(django.__version__) +> EOF .ft P .fi .UNINDENT .UNINDENT -.SS showmigrations [ []] +.sp +On Windows, the REPL is output due to implementation limits of +\fI\%select.select()\fP on that platform. +.SS \fBshowmigrations\fP .INDENT 0.0 .TP -.B django\-admin showmigrations +.B django\-admin showmigrations [app_label [app_label ...]] .UNINDENT .sp - -.sp -Shows all migrations in a project. +Shows all migrations in a project. You can choose from one of two formats: .INDENT 0.0 .TP .B \-\-list, \-l .UNINDENT .sp -The \fB\-\-list\fP option lists all of the apps Django knows about, the -migrations available for each app, and whether or not each migration is -applied (marked by an \fB[X]\fP next to the migration name). +Lists all of the apps Django knows about, the migrations available for each +app, and whether or not each migration is applied (marked by an \fB[X]\fP next to +the migration name). .sp Apps without migrations are also listed, but have \fB(no migrations)\fP printed under them. +.sp +This is the default output format. .INDENT 0.0 .TP .B \-\-plan, \-p .UNINDENT .sp -The \fB\-\-plan\fP option shows the migration plan Django will follow to apply -migrations. Any supplied app labels are ignored because the plan might go -beyond those apps. Same as \fB\-\-list\fP, applied migrations are marked by an -\fB[X]\fP\&. For a verbosity of 2 and above, all dependencies of a migration will -also be shown. -.SS sqlflush +Shows the migration plan Django will follow to apply migrations. Like +\fB\-\-list\fP, applied migrations are marked by an \fB[X]\fP\&. For a \fB\-\-verbosity\fP +of 2 and above, all dependencies of a migration will also be shown. +.sp +\fBapp_label\fPs arguments limit the output, however, dependencies of provided +apps may also be included. +.INDENT 0.0 +.TP +.B \-\-database DATABASE +.UNINDENT +.sp +Specifies the database to examine. Defaults to \fBdefault\fP\&. +.SS \fBsqlflush\fP .INDENT 0.0 .TP .B django\-admin sqlflush @@ -1365,13 +1554,16 @@ also be shown. .sp Prints the SQL statements that would be executed for the \fI\%flush\fP command. +.INDENT 0.0 +.TP +.B \-\-database DATABASE +.UNINDENT .sp -The \fI\%\-\-database\fP option can be used to specify the database for -which to print the SQL. -.SS sqlmigrate +Specifies the database for which to print the SQL. Defaults to \fBdefault\fP\&. +.SS \fBsqlmigrate\fP .INDENT 0.0 .TP -.B django\-admin sqlmigrate +.B django\-admin sqlmigrate app_label migration_name .UNINDENT .sp Prints the SQL for the named migration. This requires an active database @@ -1379,26 +1571,23 @@ connection, which it will use to resolve constraint names; this means you must generate the SQL against a copy of the database you wish to later apply it on. .sp Note that \fBsqlmigrate\fP doesn\(aqt colorize its output. -.sp -The \fI\%\-\-database\fP option can be used to specify the database for -which to generate the SQL. .INDENT 0.0 .TP .B \-\-backwards .UNINDENT .sp -By default, the SQL created is for running the migration in the forwards -direction. Pass \fB\-\-backwards\fP to generate the SQL for -unapplying the migration instead. +Generates the SQL for unapplying the migration. By default, the SQL created is +for running the migration in the forwards direction. +.INDENT 0.0 +.TP +.B \-\-database DATABASE +.UNINDENT .sp -To increase the readability of the overall SQL output the SQL code -generated for each migration operation is preceded by the operation\(aqs -description. - -.SS sqlsequencereset +Specifies the database for which to generate the SQL. Defaults to \fBdefault\fP\&. +.SS \fBsqlsequencereset\fP .INDENT 0.0 .TP -.B django\-admin sqlsequencereset +.B django\-admin sqlsequencereset app_label [app_label ...] .UNINDENT .sp Prints the SQL statements for resetting sequences for the given app name(s). @@ -1408,21 +1597,22 @@ number for automatically incremented fields. .sp Use this command to generate SQL which will fix cases where a sequence is out of sync with its automatically incremented field data. +.INDENT 0.0 +.TP +.B \-\-database DATABASE +.UNINDENT .sp -The \fI\%\-\-database\fP option can be used to specify the database for -which to print the SQL. -.SS squashmigrations [] +Specifies the database for which to print the SQL. Defaults to \fBdefault\fP\&. +.SS \fBsquashmigrations\fP .INDENT 0.0 .TP -.B django\-admin squashmigrations +.B django\-admin squashmigrations app_label [start_migration_name] migration_name .UNINDENT .sp Squashes the migrations for \fBapp_label\fP up to and including \fBmigration_name\fP down into fewer migrations, if possible. The resulting squashed migrations can live alongside the unsquashed ones safely. For more information, please read migration\-squashing\&. -.sp - .sp When \fBstart_migration_name\fP is given, Django will only include migrations starting from and including this migration. This helps to mitigate the @@ -1433,24 +1623,44 @@ squashing limitation of \fBRunPython\fP and .B \-\-no\-optimize .UNINDENT .sp -By default, Django will try to optimize the operations in your migrations -to reduce the size of the resulting file. Pass \fB\-\-no\-optimize\fP if this -process is failing for you or creating incorrect migrations, though please -also file a Django bug report about the behavior, as optimization is meant -to be safe. -.SS startapp [destination] +Disables the optimizer when generating a squashed migration. By default, Django +will try to optimize the operations in your migrations to reduce the size of +the resulting file. Use this option if this process is failing or creating +incorrect migrations, though please also file a Django bug report about the +behavior, as optimization is meant to be safe. +.INDENT 0.0 +.TP +.B \-\-noinput, \-\-no\-input +.UNINDENT +.sp +Suppresses all user prompts. .INDENT 0.0 .TP -.B django\-admin startapp +.B \-\-squashed\-name SQUASHED_NAME +.UNINDENT +.sp +Sets the name of the squashed migration. When omitted, the name is based on the +first and last migration, with \fB_squashed_\fP in between. +.INDENT 0.0 +.TP +.B \-\-no\-header +.UNINDENT +.sp + +.sp +Generate squashed migration file without Django version and timestamp header. +.SS \fBstartapp\fP +.INDENT 0.0 +.TP +.B django\-admin startapp name [directory] .UNINDENT .sp Creates a Django app directory structure for the given app name in the current directory or the given destination. .sp -By default the directory created contains a \fBmodels.py\fP file and other app -template files. (See the \fI\%source\fP for more details.) If only the app -name is given, the app directory will be created in the current working -directory. +By default, \fI\%the new directory\fP contains a +\fBmodels.py\fP file and other app template files. If only the app name is given, +the app directory will be created in the current working directory. .sp If the optional destination is provided, Django will use that existing directory rather than creating a new one. You can use \(aq.\(aq to denote the current @@ -1469,11 +1679,10 @@ django\-admin startapp myapp /Users/jezdez/Code/myapp .UNINDENT .INDENT 0.0 .TP -.B \-\-template +.B \-\-template TEMPLATE .UNINDENT .sp -With the \fB\-\-template\fP option, you can use a custom app template by providing -either the path to a directory with the app template file, or a path to a +Provides the path to a directory with a custom app template file or a path to a compressed file (\fB\&.tar.gz\fP, \fB\&.tar.bz2\fP, \fB\&.tgz\fP, \fB\&.tbz\fP, \fB\&.zip\fP) containing the app template files. .sp @@ -1506,11 +1715,24 @@ django\-admin startapp \-\-template=https://github.com/githubuser/django\-app\-t .fi .UNINDENT .UNINDENT +.INDENT 0.0 +.TP +.B \-\-extension EXTENSIONS, \-e EXTENSIONS +.UNINDENT +.sp +Specifies which file extensions in the app template should be rendered with the +template engine. Defaults to \fBpy\fP\&. +.INDENT 0.0 +.TP +.B \-\-name FILES, \-n FILES +.UNINDENT .sp -When Django copies the app template files, it also renders certain files -through the template engine: the files whose extensions match the -\fB\-\-extension\fP option (\fBpy\fP by default) and the files whose names are passed -with the \fB\-\-name\fP option. The \fBtemplate context\fP used is: +Specifies which files in the app template (in addition to those matching +\fB\-\-extension\fP) should be rendered with the template engine. Defaults to an +empty list. +.sp +The \fBtemplate context\fP used for all matching +files is: .INDENT 0.0 .IP \(bu 2 Any option passed to the \fBstartapp\fP command (among the command\(aqs supported @@ -1523,10 +1745,9 @@ options) \fBcamel_case_app_name\fP \-\- the app name in camel case format .IP \(bu 2 \fBdocs_version\fP \-\- the version of the documentation: \fB\(aqdev\(aq\fP or \fB\(aq1.x\(aq\fP +.IP \(bu 2 +\fBdjango_version\fP \-\- the version of Django, e.g. \fB\(aq2.0.3\(aq\fP .UNINDENT -.sp -\fBcamel_case_app_name\fP was added. - .sp \fBWARNING:\fP .INDENT 0.0 @@ -1538,21 +1759,26 @@ contains a docstring explaining a particular feature related to template rendering, it might result in an incorrect example. .sp To work around this problem, you can use the \fBtemplatetag\fP -templatetag to "escape" the various parts of the template syntax. +template tag to "escape" the various parts of the template syntax. +.sp +In addition, to allow Python template files that contain Django template +language syntax while also preventing packaging systems from trying to +byte\-compile invalid \fB*.py\fP files, template files ending with \fB\&.py\-tpl\fP +will be renamed to \fB\&.py\fP\&. .UNINDENT .UNINDENT -.SS startproject [destination] +.SS \fBstartproject\fP .INDENT 0.0 .TP -.B django\-admin startproject +.B django\-admin startproject name [directory] .UNINDENT .sp Creates a Django project directory structure for the given project name in the current directory or the given destination. .sp -By default, the new directory contains \fBmanage.py\fP and a project package -(containing a \fBsettings.py\fP and other files). See the \fI\%template source\fP for -details. +By default, \fI\%the new directory\fP contains +\fBmanage.py\fP and a project package (containing a \fBsettings.py\fP and other +files). .sp If only the project name is given, both the project directory and project package will be named \fB\fP and the project directory @@ -1573,46 +1799,30 @@ django\-admin startproject myproject /Users/jezdez/Code/myproject_repo .fi .UNINDENT .UNINDENT -.sp -As with the \fI\%startapp\fP command, the \fB\-\-template\fP option lets you -specify a directory, file path or URL of a custom project template. See the -\fI\%startapp\fP documentation for details of supported project template -formats. -.sp -For example, this would look for a project template in the given directory -when creating the \fBmyproject\fP project: .INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -django\-admin startproject \-\-template=/Users/jezdez/Code/my_project_template myproject -.ft P -.fi -.UNINDENT +.TP +.B \-\-template TEMPLATE .UNINDENT .sp -Django will also accept URLs (\fBhttp\fP, \fBhttps\fP, \fBftp\fP) to compressed -archives with the project template files, downloading and extracting them on the -fly. -.sp -For example, taking advantage of GitHub\(aqs feature to expose repositories as -zip files, you can use a URL like: +Specifies a directory, file path, or URL of a custom project template. See the +\fI\%startapp \-\-template\fP documentation for examples and usage. .INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -django\-admin startproject \-\-template=https://github.com/githubuser/django\-project\-template/archive/master.zip myproject -.ft P -.fi +.TP +.B \-\-extension EXTENSIONS, \-e EXTENSIONS .UNINDENT +.sp +Specifies which file extensions in the project template should be rendered with +the template engine. Defaults to \fBpy\fP\&. +.INDENT 0.0 +.TP +.B \-\-name FILES, \-n FILES .UNINDENT .sp -When Django copies the project template files, it also renders certain files -through the template engine: the files whose extensions match the -\fB\-\-extension\fP option (\fBpy\fP by default) and the files whose names are passed -with the \fB\-\-name\fP option. The \fBtemplate context\fP used is: +Specifies which files in the project template (in addition to those matching +\fB\-\-extension\fP) should be rendered with the template engine. Defaults to an +empty list. +.sp +The \fBtemplate context\fP used is: .INDENT 0.0 .IP \(bu 2 Any option passed to the \fBstartproject\fP command (among the command\(aqs @@ -1625,87 +1835,85 @@ supported options) \fBsecret_key\fP \-\- a random key for the \fBSECRET_KEY\fP setting .IP \(bu 2 \fBdocs_version\fP \-\- the version of the documentation: \fB\(aqdev\(aq\fP or \fB\(aq1.x\(aq\fP +.IP \(bu 2 +\fBdjango_version\fP \-\- the version of Django, e.g. \fB\(aq2.0.3\(aq\fP .UNINDENT .sp Please also see the \fI\%rendering warning\fP as mentioned for \fI\%startapp\fP\&. -.SS test +.SS \fBtest\fP .INDENT 0.0 .TP -.B django\-admin test +.B django\-admin test [test_label [test_label ...]] .UNINDENT .sp -Runs tests for all installed models. See \fB/topics/testing/index\fP for more +Runs tests for all installed apps. See /topics/testing/index for more information. .INDENT 0.0 .TP .B \-\-failfast .UNINDENT .sp -The \fB\-\-failfast\fP option can be used to stop running tests and report the -failure immediately after a test fails. +Stops running tests and reports the failure immediately after a test fails. .INDENT 0.0 .TP -.B \-\-testrunner +.B \-\-testrunner TESTRUNNER .UNINDENT .sp -The \fB\-\-testrunner\fP option can be used to control the test runner class that -is used to execute tests. If this value is provided, it overrides the value -provided by the \fBTEST_RUNNER\fP setting. +Controls the test runner class that is used to execute tests. This value +overrides the value provided by the \fBTEST_RUNNER\fP setting. .INDENT 0.0 .TP -.B \-\-liveserver +.B \-\-noinput, \-\-no\-input .UNINDENT .sp -The \fB\-\-liveserver\fP option can be used to override the default address where -the live server (used with \fBLiveServerTestCase\fP) is -expected to run from. The default value is \fBlocalhost:8081\-8179\fP\&. +Suppresses all user prompts. A typical prompt is a warning about deleting an +existing test database. +.SS Test runner options .sp -In earlier versions, the default value was \fBlocalhost:8081\fP\&. - +The \fBtest\fP command receives options on behalf of the specified +\fI\%\-\-testrunner\fP\&. These are the options of the default test runner: +\fBDiscoverRunner\fP\&. .INDENT 0.0 .TP -.B \-\-keepdb +.B \-\-keepdb, \-k .UNINDENT .sp - -.sp -The \fB\-\-keepdb\fP option can be used to preserve the test database between test -runs. This has the advantage of skipping both the create and destroy actions -which can greatly decrease the time to run tests, especially those in a large -test suite. If the test database does not exist, it will be created on the first -run and then preserved for each subsequent run. Any unapplied migrations will also -be applied to the test database before running the test suite. +Preserves the test database between test runs. This has the advantage of +skipping both the create and destroy actions which can greatly decrease the +time to run tests, especially those in a large test suite. If the test database +does not exist, it will be created on the first run and then preserved for each +subsequent run. Any unapplied migrations will also be applied to the test +database before running the test suite. .INDENT 0.0 .TP -.B \-\-reverse +.B \-\-reverse, \-r .UNINDENT .sp - -.sp -The \fB\-\-reverse\fP option can be used to sort test cases in the opposite order. -This may help in debugging the side effects of tests that aren\(aqt properly -isolated. Grouping by test class is preserved when using -this option. +Sorts test cases in the opposite execution order. This may help in debugging +the side effects of tests that aren\(aqt properly isolated. Grouping by test +class is preserved when using this option. .INDENT 0.0 .TP -.B \-\-debug\-sql +.B \-\-debug\-mode .UNINDENT .sp - -.sp -The \fB\-\-debug\-sql\fP option can be used to enable SQL logging for failing tests. If \fI\%\-\-verbosity\fP is \fB2\fP, -then queries in passing tests are also output. +Sets the \fBDEBUG\fP setting to \fBTrue\fP prior to running tests. This may +help troubleshoot test failures. .INDENT 0.0 .TP -.B \-\-parallel +.B \-\-debug\-sql, \-d .UNINDENT .sp - +Enables SQL logging for failing tests. If +\fB\-\-verbosity\fP is \fB2\fP, then queries in passing tests are also output. +.INDENT 0.0 +.TP +.B \-\-parallel [N] +.UNINDENT .sp -The \fB\-\-parallel\fP option can be used to run tests in parallel in separate -processes. Since modern processors have multiple cores, this allows running -tests significantly faster. +Runs tests in separate parallel processes. Since modern processors have +multiple cores, this allows running tests significantly faster. .sp By default \fB\-\-parallel\fP runs one process per core according to \fI\%multiprocessing.cpu_count()\fP\&. You can adjust the number of processes @@ -1720,6 +1928,15 @@ Each process gets its own database. You must ensure that different test cases don\(aqt access the same resources. For instance, test cases that touch the filesystem should create a temporary directory for their own use. .sp +\fBNOTE:\fP +.INDENT 0.0 +.INDENT 3.5 +If you have test classes that cannot be run in parallel, you can use +\fBSerializeMixin\fP to run them sequentially. See Enforce running test +classes sequentially\&. +.UNINDENT +.UNINDENT +.sp This option requires the third\-party \fBtblib\fP package to display tracebacks correctly: .INDENT 0.0 @@ -1736,6 +1953,10 @@ $ pip install tblib This feature isn\(aqt available on Windows. It doesn\(aqt work with the Oracle database backend either. .sp +If you want to use \fI\%pdb\fP while debugging tests, you must disable parallel +execution (\fB\-\-parallel=1\fP). You\(aqll see something like \fBbdb.BdbQuit\fP if you +don\(aqt. +.sp \fBWARNING:\fP .INDENT 0.0 .INDENT 3.5 @@ -1749,10 +1970,24 @@ in order to exchange them between processes. See \fI\%What can be pickled and unpickled?\fP for details. .UNINDENT .UNINDENT -.SS testserver .INDENT 0.0 .TP -.B django\-admin testserver +.B \-\-tag TAGS +.UNINDENT +.sp +Runs only tests marked with the specified tags\&. +May be specified multiple times and combined with \fI\%test \-\-exclude\-tag\fP\&. +.INDENT 0.0 +.TP +.B \-\-exclude\-tag EXCLUDE_TAGS +.UNINDENT +.sp +Excludes tests marked with the specified tags\&. +May be specified multiple times and combined with \fI\%test \-\-tag\fP\&. +.SS \fBtestserver\fP +.INDENT 0.0 +.TP +.B django\-admin testserver [fixture [fixture ...]] .UNINDENT .sp Runs a Django development server (as in \fI\%runserver\fP) using data from @@ -1785,7 +2020,7 @@ this newly created test database instead of your production database. This is useful in a number of ways: .INDENT 0.0 .IP \(bu 2 -When you\(aqre writing \fBunit tests\fP of how your views +When you\(aqre writing unit tests of how your views act with certain fixture data, you can use \fBtestserver\fP to interact with the views in a Web browser, manually. .IP \(bu 2 @@ -1803,13 +2038,12 @@ source code (as \fI\%runserver\fP does). It does, however, detect changes to templates. .INDENT 0.0 .TP -.B \-\-addrport [port number or ipaddr:port] +.B \-\-addrport ADDRPORT .UNINDENT .sp -Use \fB\-\-addrport\fP to specify a different port, or IP address and port, from -the default of \fB127.0.0.1:8000\fP\&. This value follows exactly the same format and -serves exactly the same function as the argument to the \fI\%runserver\fP -command. +Specifies a different port, or IP address and port, from the default of +\fB127.0.0.1:8000\fP\&. This value follows exactly the same format and serves +exactly the same function as the argument to the \fI\%runserver\fP command. .sp Examples: .sp @@ -1841,31 +2075,38 @@ django\-admin testserver \-\-addrport 1.2.3.4:7000 test .fi .UNINDENT .UNINDENT +.INDENT 0.0 +.TP +.B \-\-noinput, \-\-no\-input +.UNINDENT .sp -The \fI\%\-\-noinput\fP option may be provided to suppress all user -prompts. +Suppresses all user prompts. A typical prompt is a warning about deleting an +existing test database. .SH COMMANDS PROVIDED BY APPLICATIONS .sp Some commands are only available when the \fBdjango.contrib\fP application that -\fBimplements\fP them has been +implements them has been \fBenabled\fP\&. This section describes them grouped by their application. .SS \fBdjango.contrib.auth\fP -.SS changepassword +.SS \fBchangepassword\fP .INDENT 0.0 .TP -.B django\-admin changepassword +.B django\-admin changepassword [] .UNINDENT .sp -This command is only available if Django\(aqs \fBauthentication system\fP (\fBdjango.contrib.auth\fP) is installed. +This command is only available if Django\(aqs authentication system (\fBdjango.contrib.auth\fP) is installed. .sp Allows changing a user\(aqs password. It prompts you to enter a new password twice for the given user. If the entries are identical, this immediately becomes the new password. If you do not supply a user, the command will attempt to change the password whose username matches the current user. +.INDENT 0.0 +.TP +.B \-\-database DATABASE +.UNINDENT .sp -Use the \fB\-\-database\fP option to specify the database to query for the user. If -it\(aqs not supplied, Django will use the \fBdefault\fP database. +Specifies the database to query for the user. Defaults to \fBdefault\fP\&. .sp Example usage: .INDENT 0.0 @@ -1878,13 +2119,13 @@ django\-admin changepassword ringo .fi .UNINDENT .UNINDENT -.SS createsuperuser +.SS \fBcreatesuperuser\fP .INDENT 0.0 .TP .B django\-admin createsuperuser .UNINDENT .sp -This command is only available if Django\(aqs \fBauthentication system\fP (\fBdjango.contrib.auth\fP) is installed. +This command is only available if Django\(aqs authentication system (\fBdjango.contrib.auth\fP) is installed. .sp Creates a superuser account (a user who has all permissions). This is useful if you need to create an initial superuser account or if you need to @@ -1896,22 +2137,30 @@ will be set, and the superuser account will not be able to log in until a password has been manually set for it. .INDENT 0.0 .TP -.B \-\-username +.B \-\-noinput, \-\-no\-input .UNINDENT +.sp +Suppresses all user prompts. If a suppressed prompt cannot be resolved +automatically, the command will exit with error code 1. .INDENT 0.0 .TP -.B \-\-email +.B \-\-username USERNAME +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-email EMAIL .UNINDENT .sp The username and email address for the new account can be supplied by using the \fB\-\-username\fP and \fB\-\-email\fP arguments on the command line. If either of those is not supplied, \fBcreatesuperuser\fP will prompt for it when running interactively. +.INDENT 0.0 +.TP +.B \-\-database DATABASE +.UNINDENT .sp -Use the \fB\-\-database\fP option to specify the database into which the superuser -object will be saved. -.sp - +Specifies the database into which the superuser object will be saved. .sp You can subclass the management command and override \fBget_input_data()\fP if you want to customize data input and validation. Consult the source code for @@ -1920,16 +2169,35 @@ it could be useful if you have a \fBForeignKey\fP in \fBREQUIRED_FIELDS\fP and want to allow creating an instance instead of entering the primary key of an existing instance. +.SS \fBdjango.contrib.contenttypes\fP +.SS \fBremove_stale_contenttypes\fP +.INDENT 0.0 +.TP +.B django\-admin remove_stale_contenttypes +.UNINDENT +.sp +This command is only available if Django\(aqs contenttypes app (\fBdjango.contrib.contenttypes\fP) is installed. +.sp +Deletes stale content types (from deleted models) in your database. Any objects +that depend on the deleted content types will also be deleted. A list of +deleted objects will be displayed before you confirm it\(aqs okay to proceed with +the deletion. +.INDENT 0.0 +.TP +.B \-\-database DATABASE +.UNINDENT +.sp +Specifies the database to use. Defaults to \fBdefault\fP\&. .SS \fBdjango.contrib.gis\fP -.SS ogrinspect +.SS \fBogrinspect\fP .sp -This command is only available if \fBGeoDjango\fP +This command is only available if GeoDjango (\fBdjango.contrib.gis\fP) is installed. .sp Please refer to its \fBdescription\fP in the GeoDjango documentation. .SS \fBdjango.contrib.sessions\fP -.SS clearsessions +.SS \fBclearsessions\fP .INDENT 0.0 .TP .B django\-admin clearsessions @@ -1937,33 +2205,40 @@ documentation. .sp Can be run as a cron job or directly to clean out expired sessions. .SS \fBdjango.contrib.sitemaps\fP -.SS ping_google +.SS \fBping_google\fP .sp -This command is only available if the \fBSitemaps framework\fP (\fBdjango.contrib.sitemaps\fP) is installed. +This command is only available if the Sitemaps framework (\fBdjango.contrib.sitemaps\fP) is installed. .sp Please refer to its \fBdescription\fP in the Sitemaps documentation. .SS \fBdjango.contrib.staticfiles\fP -.SS collectstatic +.SS \fBcollectstatic\fP .sp -This command is only available if the \fBstatic files application\fP (\fBdjango.contrib.staticfiles\fP) is installed. +This command is only available if the static files application (\fBdjango.contrib.staticfiles\fP) is installed. .sp Please refer to its \fBdescription\fP in the -\fBstaticfiles\fP documentation. -.SS findstatic +staticfiles documentation. +.SS \fBfindstatic\fP .sp -This command is only available if the \fBstatic files application\fP (\fBdjango.contrib.staticfiles\fP) is installed. +This command is only available if the static files application (\fBdjango.contrib.staticfiles\fP) is installed. .sp -Please refer to its \fBdescription\fP in the \fBstaticfiles\fP documentation. +Please refer to its \fBdescription\fP in the staticfiles documentation. .SH DEFAULT OPTIONS .sp Although some commands may allow their own custom options, every command allows for the following options: .INDENT 0.0 .TP -.B \-\-pythonpath +.B \-\-pythonpath PYTHONPATH .UNINDENT .sp +Adds the given filesystem path to the Python \fI\%import search path\fP\&. If this +isn\(aqt provided, \fBdjango\-admin\fP will use the \fBPYTHONPATH\fP environment +variable. +.sp +This option is unnecessary in \fBmanage.py\fP, because it takes care of setting +the Python path for you. +.sp Example usage: .INDENT 0.0 .INDENT 3.5 @@ -1975,18 +2250,18 @@ django\-admin migrate \-\-pythonpath=\(aq/home/djangoprojects/myproject\(aq .fi .UNINDENT .UNINDENT -.sp -Adds the given filesystem path to the Python \fI\%import search path\fP\&. If this -isn\(aqt provided, \fBdjango\-admin\fP will use the \fBPYTHONPATH\fP environment -variable. -.sp -Note that this option is unnecessary in \fBmanage.py\fP, because it takes care of -setting the Python path for you. .INDENT 0.0 .TP -.B \-\-settings +.B \-\-settings SETTINGS .UNINDENT .sp +Specifies the settings module to use. The settings module should be in Python +package syntax, e.g. \fBmysite.settings\fP\&. If this isn\(aqt provided, +\fBdjango\-admin\fP will use the \fBDJANGO_SETTINGS_MODULE\fP environment variable. +.sp +This option is unnecessary in \fBmanage.py\fP, because it uses +\fBsettings.py\fP from the current project by default. +.sp Example usage: .INDENT 0.0 .INDENT 3.5 @@ -1998,19 +2273,15 @@ django\-admin migrate \-\-settings=mysite.settings .fi .UNINDENT .UNINDENT -.sp -Explicitly specifies the settings module to use. The settings module should be -in Python package syntax, e.g. \fBmysite.settings\fP\&. If this isn\(aqt provided, -\fBdjango\-admin\fP will use the \fBDJANGO_SETTINGS_MODULE\fP environment -variable. -.sp -Note that this option is unnecessary in \fBmanage.py\fP, because it uses -\fBsettings.py\fP from the current project by default. .INDENT 0.0 .TP .B \-\-traceback .UNINDENT .sp +Displays a full stack trace when a \fBCommandError\fP +is raised. By default, \fBdjango\-admin\fP will show a simple error message when a +\fBCommandError\fP occurs and a full stack trace for any other exception. +.sp Example usage: .INDENT 0.0 .INDENT 3.5 @@ -2022,30 +2293,13 @@ django\-admin migrate \-\-traceback .fi .UNINDENT .UNINDENT -.sp -By default, \fBdjango\-admin\fP will show a simple error message whenever a -\fBCommandError\fP occurs, but a full stack trace -for any other exception. If you specify \fB\-\-traceback\fP, \fBdjango\-admin\fP -will also output a full stack trace when a \fBCommandError\fP is raised. .INDENT 0.0 .TP -.B \-\-verbosity +.B \-\-verbosity {0,1,2,3}, \-v {0,1,2,3} .UNINDENT .sp -Example usage: -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -django\-admin migrate \-\-verbosity 2 -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -Use \fB\-\-verbosity\fP to specify the amount of notification and debug information -that \fBdjango\-admin\fP should print to the console. +Specifies the amount of notification and debug information that a command +should print to the console. .INDENT 0.0 .IP \(bu 2 \fB0\fP means no output. @@ -2056,10 +2310,6 @@ that \fBdjango\-admin\fP should print to the console. .IP \(bu 2 \fB3\fP means \fIvery\fP verbose output. .UNINDENT -.INDENT 0.0 -.TP -.B \-\-no\-color -.UNINDENT .sp Example usage: .INDENT 0.0 @@ -2067,96 +2317,48 @@ Example usage: .sp .nf .ft C -django\-admin runserver \-\-no\-color -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -By default, \fBdjango\-admin\fP will format the output to be colorized. For -example, errors will be printed to the console in red and SQL statements will -be syntax highlighted. To prevent this and have a plain text output, pass the -\fB\-\-no\-color\fP option when running your command. -.SH COMMON OPTIONS -.sp -The following options are not available on every command, but they are common -to a number of commands. -.INDENT 0.0 -.TP -.B \-\-database -.UNINDENT -.sp -Used to specify the database on which a command will operate. If not -specified, this option will default to an alias of \fBdefault\fP\&. -.sp -For example, to dump data from the database with the alias \fBmaster\fP: -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -django\-admin dumpdata \-\-database=master +django\-admin migrate \-\-verbosity 2 .ft P .fi .UNINDENT .UNINDENT .INDENT 0.0 .TP -.B \-\-exclude +.B \-\-no\-color .UNINDENT .sp -Exclude a specific application from the applications whose contents is -output. For example, to specifically exclude the \fBauth\fP application from -the output of dumpdata, you would call: -.INDENT 0.0 -.INDENT 3.5 +Disables colorized command output. Some commands format their output to be +colorized. For example, errors will be printed to the console in red and SQL +statements will be syntax highlighted. .sp -.nf -.ft C -django\-admin dumpdata \-\-exclude=auth -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -If you want to exclude multiple applications, use multiple \fB\-\-exclude\fP -directives: +Example usage: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C -django\-admin dumpdata \-\-exclude=auth \-\-exclude=contenttypes +django\-admin runserver \-\-no\-color .ft P .fi .UNINDENT .UNINDENT .INDENT 0.0 .TP -.B \-\-locale +.B \-\-force\-color .UNINDENT .sp -Use the \fB\-\-locale\fP or \fB\-l\fP option to specify the locale to process. -If not provided all locales are processed. -.INDENT 0.0 -.TP -.B \-\-noinput -.UNINDENT -.sp -Use the \fB\-\-noinput\fP option to suppress all user prompting, such as "Are -you sure?" confirmation messages. This is useful if \fBdjango\-admin\fP is -being executed as an unattended, automated script. You can use \fB\-\-no\-input\fP -as an alias for this option. -.sp -The \fB\-\-no\-input\fP alias was added. +.sp +Forces colorization of the command output if it would otherwise be disabled +as discussed in \fI\%Syntax coloring\fP\&. For example, you may want to pipe +colored output to another command. .SH EXTRA NICETIES .SS Syntax coloring .sp The \fBdjango\-admin\fP / \fBmanage.py\fP commands will use pretty color\-coded output if your terminal supports ANSI\-colored output. It won\(aqt use the color codes if you\(aqre piping the command\(aqs output to -another program. +another program unless the \fI\%\-\-force\-color\fP option is used. .sp Under Windows, the native console doesn\(aqt support ANSI escape sequences so by default there is no color output. But you can install the \fI\%ANSICON\fP @@ -2199,6 +2401,10 @@ number of roles in which color is used: .IP \(bu 2 \fBnotice\fP \- A minor error. .IP \(bu 2 +\fBsuccess\fP \- A success. +.IP \(bu 2 +\fBwarning\fP \- A warning. +.IP \(bu 2 \fBsql_field\fP \- The name of a model field in SQL. .IP \(bu 2 \fBsql_coltype\fP \- The type of a model field in SQL. @@ -2220,6 +2426,10 @@ number of roles in which color is used: \fBhttp_bad_request\fP \- A 4XX HTTP Bad Request server response other than 404. .IP \(bu 2 \fBhttp_server_error\fP \- A 5XX HTTP Server Error response. +.IP \(bu 2 +\fBmigrate_heading\fP \- A heading in a migrations management command. +.IP \(bu 2 +\fBmigrate_label\fP \- A migration name. .UNINDENT .sp Each of these roles can be assigned a specific foreground and @@ -2309,7 +2519,7 @@ overridden as specified. .SS Bash completion .sp If you use the Bash shell, consider installing the Django bash completion -script, which lives in \fBextras/django_bash_completion\fP in the Django +script, which lives in \fBextras/django_bash_completion\fP in the Django source distribution. It enables tab\-completion of \fBdjango\-admin\fP and \fBmanage.py\fP commands, so you can, for instance... .INDENT 0.0 @@ -2322,7 +2532,7 @@ Type \fBsql\fP, then [TAB], to see all available options whose names start with \fBsql\fP\&. .UNINDENT .sp -See \fB/howto/custom\-management\-commands\fP for how to add customized actions. +See /howto/custom\-management\-commands for how to add customized actions. .INDENT 0.0 .TP .B django.core.management.call_command(name, *args, **options) @@ -2332,13 +2542,19 @@ To call a management command from code use \fBcall_command\fP\&. .INDENT 0.0 .TP .B \fBname\fP -the name of the command to call. +the name of the command to call or a command object. Passing the name is +preferred unless the object is required for testing. .TP .B \fB*args\fP -a list of arguments accepted by the command. +a list of arguments accepted by the command. Arguments are passed to the +argument parser, so you can use the same style as you would on the command +line. For example, \fBcall_command(\(aqflush\(aq, \(aq\-\-verbosity=0\(aq)\fP\&. .TP .B \fB**options\fP -named options accepted on the command\-line. +named options accepted on the command\-line. Options are passed to the command +without triggering the argument parser, which means you\(aqll need to pass the +correct type. For example, \fBcall_command(\(aqflush\(aq, verbosity=0)\fP (zero must +be an integer rather than a string). .UNINDENT .sp Examples: @@ -2348,8 +2564,11 @@ Examples: .nf .ft C from django.core import management +from django.core.management.commands import loaddata + management.call_command(\(aqflush\(aq, verbosity=0, interactive=False) management.call_command(\(aqloaddata\(aq, \(aqtest_data\(aq, verbosity=0) +management.call_command(loaddata.Command(), \(aqtest_data\(aq, verbosity=0) .ft P .fi .UNINDENT @@ -2378,11 +2597,12 @@ management.call_command(\(aqdumpdata\(aq, use_natural_foreign_keys=True) .UNINDENT .UNINDENT .sp -The first syntax is now supported thanks to management commands using the -\fI\%argparse\fP module. For the second syntax, Django previously passed -the option name as\-is to the command, now it is always using the \fBdest\fP -variable name (which may or may not be the same as the option name). - +Some command options have different names when using \fBcall_command()\fP instead +of \fBdjango\-admin\fP or \fBmanage.py\fP\&. For example, \fBdjango\-admin +createsuperuser \-\-no\-input\fP translates to \fBcall_command(\(aqcreatesuperuser\(aq, +interactive=False)\fP\&. To find what keyword argument name to use for +\fBcall_command()\fP, check the command\(aqs source code for the \fBdest\fP argument +passed to \fBparser.add_argument()\fP\&. .sp Command options which take multiple options are passed a list: .INDENT 0.0 @@ -2395,6 +2615,9 @@ management.call_command(\(aqdumpdata\(aq, exclude=[\(aqcontenttypes\(aq, \(aqaut .fi .UNINDENT .UNINDENT +.sp +The return value of the \fBcall_command()\fP function is the same as the return +value of the \fBhandle()\fP method of the command. .SH OUTPUT REDIRECTION .sp Note that you can redirect standard output and error streams as all commands @@ -2404,7 +2627,7 @@ support the \fBstdout\fP and \fBstderr\fP options. For example, you could write: .sp .nf .ft C -with open(\(aq/path/to/command_output\(aq) as f: +with open(\(aq/path/to/command_output\(aq, \(aqw\(aq) as f: management.call_command(\(aqdumpdata\(aq, stdout=f) .ft P .fi diff --git a/docs/misc/api-stability.txt b/docs/misc/api-stability.txt index e18b3bb3135f..d59e557cc13d 100644 --- a/docs/misc/api-stability.txt +++ b/docs/misc/api-stability.txt @@ -2,12 +2,21 @@ API stability ============= -Django promises API stability and forwards-compatibility since version 1.0. In -a nutshell, this means that code you develop against a version of Django will -continue to work with future releases. You may need to make minor changes when -upgrading the version of Django your project uses: see the "Backwards -incompatible changes" section of the :doc:`release note ` for -the version or versions to which you are upgrading. +Django is committed to API stability and forwards-compatibility. In a nutshell, +this means that code you develop against a version of Django will continue to +work with future releases. You may need to make minor changes when upgrading +the version of Django your project uses: see the "Backwards incompatible +changes" section of the :doc:`release note ` for the version +or versions to which you are upgrading. + +At the same time as making API stability a very high priority, Django is also +committed to continual improvement, along with aiming for "one way to do it" +(eventually) in the APIs we provide. This means that when we discover clearly +superior ways to do things, we will deprecate and eventually remove the old +ways. Our aim is to provide a modern, dependable web framework of the highest +quality that encourages best practices in all projects that use it. By using +incremental improvements, we try to avoid both stagnation and large breaking +upgrades. What "stable" means =================== @@ -29,8 +38,8 @@ In this context, stable means: See :ref:`official-releases` for more details on how Django's version numbering scheme works, and how features will be deprecated. -- We'll only break backwards compatibility of these APIs if a bug or - security hole makes it completely unavoidable. +- We'll only break backwards compatibility of these APIs without a deprecation + process if a bug or security hole makes it completely unavoidable. Stable APIs =========== diff --git a/docs/misc/design-philosophies.txt b/docs/misc/design-philosophies.txt index 60e375718e60..8c0cac554ef0 100644 --- a/docs/misc/design-philosophies.txt +++ b/docs/misc/design-philosophies.txt @@ -27,7 +27,7 @@ template system a programmer uses. Although Django comes with a full stack for convenience, the pieces of the stack are independent of another wherever possible. -.. _`loose coupling and tight cohesion`: http://c2.com/cgi/wiki?CouplingAndCohesion +.. _`loose coupling and tight cohesion`: http://wiki.c2.com/?CouplingAndCohesion .. _less-code: @@ -66,7 +66,7 @@ as possible. The `discussion of DRY on the Portland Pattern Repository`__ - __ http://c2.com/cgi/wiki?DontRepeatYourself + __ http://wiki.c2.com/?DontRepeatYourself .. _explicit-is-better-than-implicit: @@ -110,7 +110,7 @@ it (its human-readable name, options like default ordering, etc.) are defined in the model class; all the information needed to understand a given model should be stored *in* the model. -.. _`Active Record`: http://www.martinfowler.com/eaaCatalog/activeRecord.html +.. _`Active Record`: https://www.martinfowler.com/eaaCatalog/activeRecord.html Database API ============ diff --git a/docs/ref/applications.txt b/docs/ref/applications.txt index c3a06ab85543..e735fce951e2 100644 --- a/docs/ref/applications.txt +++ b/docs/ref/applications.txt @@ -13,7 +13,7 @@ This registry is simply called :attr:`~django.apps.apps` and it's available in >>> from django.apps import apps >>> apps.get_app_config('admin').verbose_name - 'Admin' + 'Administration' Projects and applications ========================= @@ -192,7 +192,7 @@ Configurable attributes .. attribute:: AppConfig.path Filesystem path to the application directory, e.g. - ``'/usr/lib/python3.4/dist-packages/django/contrib/admin'``. + ``'/usr/lib/pythonX.Y/dist-packages/django/contrib/admin'``. In most cases, Django can automatically detect and set this, but you can also provide an explicit override as a class attribute on your @@ -206,12 +206,12 @@ Read-only attributes .. attribute:: AppConfig.module Root module for the application, e.g. ````. + 'django/contrib/admin/__init__.py'>``. .. attribute:: AppConfig.models_module Module containing the models, e.g. ````. + from 'django/contrib/admin/models.py'>``. It may be ``None`` if the application doesn't contain a ``models`` module. Note that the database related signals such as @@ -240,10 +240,6 @@ Methods ``require_ready`` argument is set to ``False``. ``require_ready`` behaves exactly as in :meth:`apps.get_model()`. - .. versionadded:: 1.11 - - The ``require_ready`` keyword argument was added. - .. method:: AppConfig.ready() Subclasses can override this method to perform initialization tasks such @@ -295,23 +291,22 @@ Methods .. _namespace package: -Namespace packages as apps (Python 3.3+) ----------------------------------------- +Namespace packages as apps +-------------------------- -Python versions 3.3 and later support Python packages without an -``__init__.py`` file. These packages are known as "namespace packages" and may -be spread across multiple directories at different locations on ``sys.path`` -(see :pep:`420`). +Python packages without an ``__init__.py`` file are known as "namespace +packages" and may be spread across multiple directories at different locations +on ``sys.path`` (see :pep:`420`). Django applications require a single base filesystem path where Django (depending on configuration) will search for templates, static assets, etc. Thus, namespace packages may only be Django applications if one of the following is true: -1. The namespace package actually has only a single location (i.e. is not +#. The namespace package actually has only a single location (i.e. is not spread across more than one directory.) -2. The :class:`~django.apps.AppConfig` class used to configure the application +#. The :class:`~django.apps.AppConfig` class used to configure the application has a :attr:`~django.apps.AppConfig.path` class attribute, which is the absolute directory path Django will use as the single base path for the application. @@ -374,10 +369,6 @@ Application registry best to leave ``require_ready`` to the default value of ``True`` whenever possible. - .. versionadded:: 1.11 - - The ``require_ready`` keyword argument was added. - .. _app-loading-process: Initialization process @@ -401,10 +392,6 @@ application registry. :setting:`FORCE_SCRIPT_NAME` if defined, or ``/`` otherwise. * Initializing the application registry. - .. versionchanged:: 1.10 - - The ability to set the URL resolver script prefix is new. - This function is called automatically: * When running an HTTP server via Django's WSGI support. @@ -462,10 +449,10 @@ Here are some common problems that you may encounter during initialization: importing an application configuration or a models module triggers code that depends on the app registry. - For example, :func:`~django.utils.translation.ugettext()` uses the app + For example, :func:`~django.utils.translation.gettext()` uses the app registry to look up translation catalogs in applications. To translate at - import time, you need :func:`~django.utils.translation.ugettext_lazy()` - instead. (Using :func:`~django.utils.translation.ugettext()` would be a bug, + import time, you need :func:`~django.utils.translation.gettext_lazy()` + instead. (Using :func:`~django.utils.translation.gettext()` would be a bug, because the translation would happen at import time, rather than at each request depending on the active language.) diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt index d5d0bd4e0f78..b1e48b2c1b9e 100644 --- a/docs/ref/checks.txt +++ b/docs/ref/checks.txt @@ -15,7 +15,7 @@ API reference ============= ``CheckMessage`` ------------------ +---------------- .. class:: CheckMessage(level, msg, hint=None, obj=None, id=None) @@ -45,9 +45,9 @@ Constructor arguments are: ``obj`` Optional. An object providing context for the message (for example, the model where the problem was discovered). The object should be a model, - field, or manager or any other object that defines ``__str__`` method (on - Python 2 you need to define ``__unicode__`` method). The method is used - while reporting all messages and its result precedes the message. + field, or manager or any other object that defines a ``__str__()`` method. + The method is used while reporting all messages and its result precedes the + message. ``id`` Optional string. A unique identifier for the issue. Identifiers should @@ -83,7 +83,9 @@ Django's system checks are organized using the following tags: * ``models``: Checks of model, field, and manager definitions. * ``security``: Checks security related configuration. * ``signals``: Checks on signal declarations and handler registrations. +* ``staticfiles``: Checks :mod:`django.contrib.staticfiles` configuration. * ``templates``: Checks template related configuration. +* ``translation``: Checks translation related configuration. * ``urls``: Checks URL configuration. Some checks may be registered with multiple tags. @@ -94,28 +96,12 @@ Core system checks Backwards compatibility ----------------------- -The following checks are performed to warn the user of any potential problems -that might occur as a result of a version upgrade. - -* **1_6.W001**: Some project unit tests may not execute as expected. *This - check was removed in Django 1.8 due to false positives*. -* **1_6.W002**: ``BooleanField`` does not have a default value. *This - check was removed in Django 1.8 due to false positives*. -* **1_7.W001**: Django 1.7 changed the global defaults for the - ``MIDDLEWARE_CLASSES.`` - ``django.contrib.sessions.middleware.SessionMiddleware``, - ``django.contrib.auth.middleware.AuthenticationMiddleware``, and - ``django.contrib.messages.middleware.MessageMiddleware`` were removed from - the defaults. If your project needs these middleware then you should - configure this setting. *This check was removed in Django 1.9*. -* **1_8.W001**: The standalone ``TEMPLATE_*`` settings were deprecated in - Django 1.8 and the :setting:`TEMPLATES` dictionary takes precedence. You must - put the values of the following settings into your defaults ``TEMPLATES`` - dict: ``TEMPLATE_DIRS``, ``TEMPLATE_CONTEXT_PROCESSORS``, ``TEMPLATE_DEBUG``, - ``TEMPLATE_LOADERS``, ``TEMPLATE_STRING_IF_INVALID``. -* **1_10.W001**: The ``MIDDLEWARE_CLASSES`` setting is deprecated in Django - 1.10 and the :setting:`MIDDLEWARE` setting takes precedence. Since you've - set ``MIDDLEWARE``, the value of ``MIDDLEWARE_CLASSES`` is ignored. +Compatibility checks warn of potential problems that might occur after +upgrading Django. + +* **2_0.W001**: Your URL pattern ```` has a ``route`` that contains + ``(?P<``, begins with a ``^``, or ends with a ``$``. This was likely an + oversight when migrating from ``url()`` to :func:`~django.urls.path`. Caches ------ @@ -151,11 +137,14 @@ Model fields human readable name)`` tuples. * **fields.E006**: ``db_index`` must be ``None``, ``True`` or ``False``. * **fields.E007**: Primary keys must not have ``null=True``. +* **fields.E008**: All ``validators`` must be callable. * **fields.E100**: ``AutoField``\s must set primary_key=True. -* **fields.E110**: ``BooleanField``\s do not accept null values. +* **fields.E110**: ``BooleanField``\s do not accept null values. *This check + appeared before support for null values was added in Django 2.1.* * **fields.E120**: ``CharField``\s must define a ``max_length`` attribute. * **fields.E121**: ``max_length`` must be a positive integer. -* **fields.W122**: ``max_length`` is ignored when used with ``IntegerField``. +* **fields.W122**: ``max_length`` is ignored when used with + ````. * **fields.E130**: ``DecimalField``\s must define a ``decimal_places`` attribute. * **fields.E131**: ``decimal_places`` must be a non-negative integer. * **fields.E132**: ``DecimalField``\s must define a ``max_digits`` attribute. @@ -168,13 +157,20 @@ Model fields * **fields.E160**: The options ``auto_now``, ``auto_now_add``, and ``default`` are mutually exclusive. Only one of these options may be present. * **fields.W161**: Fixed default value provided. +* **fields.W162**: ```` does not support a database index on + ```` columns. * **fields.E900**: ``IPAddressField`` has been removed except for support in historical migrations. * **fields.W900**: ``IPAddressField`` has been deprecated. Support for it (except in historical migrations) will be removed in Django 1.9. *This check appeared in Django 1.7 and 1.8*. * **fields.W901**: ``CommaSeparatedIntegerField`` has been deprecated. Support - for it (except in historical migrations) will be removed in Django 2.0. + for it (except in historical migrations) will be removed in Django 2.0. *This + check appeared in Django 1.10 and 1.11*. +* **fields.E901**: ``CommaSeparatedIntegerField`` is removed except for support + in historical migrations. +* **fields.W902**: ``FloatRangeField`` is deprecated and will be removed in + Django 3.1. File fields ~~~~~~~~~~~ @@ -260,8 +256,8 @@ Models * **models.E001**: ```` is not of the form ``app_label.app_name``. * **models.E002**: ```` references ````, which has not been installed, or is abstract. -* **models.E003**: The model has two many-to-many relations through the - intermediate model ``.``. +* **models.E003**: The model has two identical many-to-many relations through + the intermediate model ``.``. * **models.E004**: ``id`` can only be used as a field name if the field also sets ``primary_key=True``. * **models.E005**: The field ```` from parent model ```` @@ -274,16 +270,16 @@ Models * **models.E009**: All ``index_together`` elements must be lists or tuples. * **models.E010**: ``unique_together`` must be a list or tuple. * **models.E011**: All ``unique_together`` elements must be lists or tuples. -* **models.E012**: ``index_together/unique_together`` refers to the - non-existent field ````. -* **models.E013**: ``index_together/unique_together`` refers to a +* **models.E012**: ``indexes/index_together/unique_together`` refers to the + nonexistent field ````. +* **models.E013**: ``indexes/index_together/unique_together`` refers to a ``ManyToManyField`` ````, but ``ManyToManyField``\s are not supported for that option. * **models.E014**: ``ordering`` must be a tuple or list (even if you want to order by only one field). -* **models.E015**: ``ordering`` refers to the non-existent field +* **models.E015**: ``ordering`` refers to the nonexistent field ````. -* **models.E016**: ``index_together/unique_together`` refers to field +* **models.E016**: ``indexes/index_together/unique_together`` refers to field ```` which is not local to model ````. * **models.E017**: Proxy model ```` contains model fields. * **models.E018**: Autogenerated column name too long for field ````. @@ -301,6 +297,15 @@ Models underscore as it collides with the query lookup syntax. * **models.E024**: The model name ```` cannot contain double underscores as it collides with the query lookup syntax. +* **models.E025**: The property ```` clashes with a related + field accessor. +* **models.E026**: The model cannot have more than one field with + ``primary_key=True``. +* **models.W027**: ```` does not support check constraints. +* **models.E028**: ``db_table`` ```` is used by multiple models: + ````. +* **models.W035**: ``db_table`` ```` is used by multiple models: + ````. Security -------- @@ -320,19 +325,19 @@ The following checks are run if you use the :option:`check --deploy` option: * **security.W001**: You do not have :class:`django.middleware.security.SecurityMiddleware` in your - :setting:`MIDDLEWARE`/:setting:`MIDDLEWARE_CLASSES` so the :setting:`SECURE_HSTS_SECONDS`, + :setting:`MIDDLEWARE` so the :setting:`SECURE_HSTS_SECONDS`, :setting:`SECURE_CONTENT_TYPE_NOSNIFF`, :setting:`SECURE_BROWSER_XSS_FILTER`, and :setting:`SECURE_SSL_REDIRECT` settings will have no effect. * **security.W002**: You do not have :class:`django.middleware.clickjacking.XFrameOptionsMiddleware` in your - :setting:`MIDDLEWARE`/:setting:`MIDDLEWARE_CLASSES`, so your pages will not be served with an + :setting:`MIDDLEWARE`, so your pages will not be served with an ``'x-frame-options'`` header. Unless there is a good reason for your site to be served in a frame, you should consider enabling this header to help prevent clickjacking attacks. * **security.W003**: You don't appear to be using Django's built-in cross-site request forgery protection via the middleware (:class:`django.middleware.csrf.CsrfViewMiddleware` is not in your - :setting:`MIDDLEWARE`/:setting:`MIDDLEWARE_CLASSES`). Enabling the middleware is the safest + :setting:`MIDDLEWARE`). Enabling the middleware is the safest approach to ensure you don't leave any holes. * **security.W004**: You have not set a value for the :setting:`SECURE_HSTS_SECONDS` setting. If your entire site is served only @@ -347,11 +352,11 @@ The following checks are run if you use the :option:`check --deploy` option: your domain should be served exclusively via SSL. * **security.W006**: Your :setting:`SECURE_CONTENT_TYPE_NOSNIFF` setting is not set to ``True``, so your pages will not be served with an - ``'x-content-type-options: nosniff'`` header. You should consider enabling + ``'X-Content-Type-Options: nosniff'`` header. You should consider enabling this header to prevent the browser from identifying content types incorrectly. * **security.W007**: Your :setting:`SECURE_BROWSER_XSS_FILTER` setting is not set to ``True``, so your pages will not be served with an - ``'x-xss-protection: 1; mode=block'`` header. You should consider enabling + ``'X-XSS-Protection: 1; mode=block'`` header. You should consider enabling this header to activate the browser's XSS filtering and help prevent XSS attacks. * **security.W008**: Your :setting:`SECURE_SSL_REDIRECT` setting is not set to @@ -369,10 +374,9 @@ The following checks are run if you use the :option:`check --deploy` option: sessions. * **security.W011**: You have :class:`django.contrib.sessions.middleware.SessionMiddleware` in your - :setting:`MIDDLEWARE`/:setting:`MIDDLEWARE_CLASSES`, but you have not set - :setting:`SESSION_COOKIE_SECURE` to ``True``. Using a secure-only session - cookie makes it more difficult for network traffic sniffers to hijack user - sessions. + :setting:`MIDDLEWARE`, but you have not set :setting:`SESSION_COOKIE_SECURE` + to ``True``. Using a secure-only session cookie makes it more difficult for + network traffic sniffers to hijack user sessions. * **security.W012**: :setting:`SESSION_COOKIE_SECURE` is not set to ``True``. Using a secure-only session cookie makes it more difficult for network traffic sniffers to hijack user sessions. @@ -383,10 +387,9 @@ The following checks are run if you use the :option:`check --deploy` option: sessions. * **security.W014**: You have :class:`django.contrib.sessions.middleware.SessionMiddleware` in your - :setting:`MIDDLEWARE`/:setting:`MIDDLEWARE_CLASSES`, but you have not set - :setting:`SESSION_COOKIE_HTTPONLY` to ``True``. Using an ``HttpOnly`` session - cookie makes it more difficult for cross-site scripting attacks to hijack user - sessions. + :setting:`MIDDLEWARE`, but you have not set :setting:`SESSION_COOKIE_HTTPONLY` + to ``True``. Using an ``HttpOnly`` session cookie makes it more difficult for + cross-site scripting attacks to hijack user sessions. * **security.W015**: :setting:`SESSION_COOKIE_HTTPONLY` is not set to ``True``. Using an ``HttpOnly`` session cookie makes it more difficult for cross-site scripting attacks to hijack user sessions. @@ -402,7 +405,7 @@ The following checks are run if you use the :option:`check --deploy` option: deployment. * **security.W019**: You have :class:`django.middleware.clickjacking.XFrameOptionsMiddleware` in your - :setting:`MIDDLEWARE`/:setting:`MIDDLEWARE_CLASSES`, but :setting:`X_FRAME_OPTIONS` is not set to + :setting:`MIDDLEWARE`, but :setting:`X_FRAME_OPTIONS` is not set to ``'DENY'``. The default is ``'SAMEORIGIN'``, but unless there is a good reason for your site to serve other parts of itself in a frame, you should change it to ``'DENY'``. @@ -431,28 +434,40 @@ configured: :setting:`OPTIONS ` must be a string but got: ``{value}`` (``{type}``). +Translation +----------- + +The following checks are performed on your translation configuration: + +* **translation.E001**: You have provided an invalid value for the + :setting:`LANGUAGE_CODE` setting. + URLs ---- The following checks are performed on your URL configuration: * **urls.W001**: Your URL pattern ```` uses - :func:`~django.conf.urls.include` with a ``regex`` ending with a - ``$``. Remove the dollar from the ``regex`` to avoid problems - including URLs. -* **urls.W002**: Your URL pattern ```` has a ``regex`` - beginning with a ``/``. Remove this slash as it is unnecessary. - If this pattern is targeted in an :func:`~django.conf.urls.include`, ensure - the :func:`~django.conf.urls.include` pattern has a trailing ``/``. + :func:`~django.urls.include` with a ``route`` ending with a ``$``. Remove the + dollar from the ``route`` to avoid problems including URLs. +* **urls.W002**: Your URL pattern ```` has a ``route`` beginning with + a ``/``. Remove this slash as it is unnecessary. If this pattern is targeted + in an :func:`~django.urls.include`, ensure the :func:`~django.urls.include` + pattern has a trailing ``/``. * **urls.W003**: Your URL pattern ```` has a ``name`` including a ``:``. Remove the colon, to avoid ambiguous namespace references. * **urls.E004**: Your URL pattern ```` is invalid. Ensure that - ``urlpatterns`` is a list of :func:`~django.conf.urls.url()` instances. + ``urlpatterns`` is a list of :func:`~django.urls.path` and/or + :func:`~django.urls.re_path` instances. * **urls.W005**: URL namespace ```` isn't unique. You may not be able to reverse all URLs in this namespace. * **urls.E006**: The :setting:`MEDIA_URL`/ :setting:`STATIC_URL` setting must end with a slash. +* **urls.E007**: The custom ``handlerXXX`` view ``'path.to.view'`` does not + take the correct number of arguments (…). +* **urls.E008**: The custom ``handlerXXX`` view ``'path.to.view'`` could not be + imported. ``contrib`` app checks ====================== @@ -520,6 +535,15 @@ with the admin site: * **admin.E034**: The value of ``readonly_fields`` must be a list or tuple. * **admin.E035**: The value of ``readonly_fields[n]`` is not a callable, an attribute of ````, or an attribute of ````. +* **admin.E036**: The value of ``autocomplete_fields`` must be a list or tuple. +* **admin.E037**: The value of ``autocomplete_fields[n]`` refers to + ````, which is not an attribute of ````. +* **admin.E038**: The value of ``autocomplete_fields[n]`` must be a foreign + key or a many-to-many field. +* **admin.E039**: An admin for model ```` has to be registered to be + referenced by ``.autocomplete_fields``. +* **admin.E040**: ```` must define ``search_fields``, because + it's referenced by ``.autocomplete_fields``. ``ModelAdmin`` ~~~~~~~~~~~~~~ @@ -576,6 +600,10 @@ with the admin site: which does not refer to a Field. * **admin.E128**: The value of ``date_hierarchy`` must be a ``DateField`` or ``DateTimeField``. +* **admin.E129**: ```` must define a ``has__permission()`` + method for the ```` action. +* **admin.E130**: ``__name__`` attributes of actions defined in + ```` must be unique. ``InlineModelAdmin`` ~~~~~~~~~~~~~~~~~~~~ @@ -618,7 +646,26 @@ The following checks are performed on the default * **admin.E401**: :mod:`django.contrib.contenttypes` must be in :setting:`INSTALLED_APPS` in order to use the admin application. * **admin.E402**: :mod:`django.contrib.auth.context_processors.auth` - must be in :setting:`TEMPLATES` in order to use the admin application. + must be enabled in :class:`~django.template.backends.django.DjangoTemplates` + (:setting:`TEMPLATES`) if using the default auth backend in order to use the + admin application. +* **admin.E403**: A :class:`django.template.backends.django.DjangoTemplates` + instance must be configured in :setting:`TEMPLATES` in order to use the + admin application. +* **admin.E404**: ``django.contrib.messages.context_processors.messages`` + must be enabled in :class:`~django.template.backends.django.DjangoTemplates` + (:setting:`TEMPLATES`) in order to use the admin application. +* **admin.E405**: :mod:`django.contrib.auth` must be in + :setting:`INSTALLED_APPS` in order to use the admin application. +* **admin.E406**: :mod:`django.contrib.messages` must be in + :setting:`INSTALLED_APPS` in order to use the admin application. +* **admin.E408**: + :class:`django.contrib.auth.middleware.AuthenticationMiddleware` must be in + :setting:`MIDDLEWARE` in order to use the admin application. +* **admin.E409**: :class:`django.contrib.messages.middleware.MessageMiddleware` + must be in :setting:`MIDDLEWARE` in order to use the admin application. +* **admin.E410**: :class:`django.contrib.sessions.middleware.SessionMiddleware` + must be in :setting:`MIDDLEWARE` in order to use the admin application. ``auth`` -------- @@ -655,12 +702,24 @@ The following checks are performed when a model contains a :class:`~django.contrib.contenttypes.fields.GenericRelation`: * **contenttypes.E001**: The ``GenericForeignKey`` object ID references the - non-existent field ````. + nonexistent field ````. * **contenttypes.E002**: The ``GenericForeignKey`` content type references the - non-existent field ````. + nonexistent field ````. * **contenttypes.E003**: ```` is not a ``ForeignKey``. * **contenttypes.E004**: ```` is not a ``ForeignKey`` to ``contenttypes.ContentType``. +* **contenttypes.E005**: Model names must be at most 100 characters. + +``postgres`` +------------ + +The following checks are performed on :mod:`django.contrib.postgres` model +fields: + +* **postgres.E001**: Base field for array has errors: ... +* **postgres.E002**: Base field for array cannot be a related field. +* **postgres.E003**: ```` default should be a callable instead of an + instance so that it's not shared between all field instances. ``sites`` --------- @@ -672,3 +731,16 @@ The following checks are performed on any model using a ````. * **sites.E002**: ``CurrentSiteManager`` cannot use ```` as it is not a foreign key or a many-to-many field. + +``staticfiles`` +--------------- + +The following checks verify that :mod:`django.contrib.staticfiles` is correctly +configured: + +* **staticfiles.E001**: The :setting:`STATICFILES_DIRS` setting is not a tuple + or list. +* **staticfiles.E002**: The :setting:`STATICFILES_DIRS` setting should not + contain the :setting:`STATIC_ROOT` setting. +* **staticfiles.E003**: The prefix ```` in the + :setting:`STATICFILES_DIRS` setting must not end with a slash. diff --git a/docs/ref/class-based-views/base.txt b/docs/ref/class-based-views/base.txt index 80904ef76f53..85e3c8bd4b76 100644 --- a/docs/ref/class-based-views/base.txt +++ b/docs/ref/class-based-views/base.txt @@ -22,15 +22,12 @@ MRO is an acronym for Method Resolution Order. this base class. It isn't strictly a generic view and thus can also be imported from ``django.views``. - .. versionchanged:: 1.10 - - The ability to import from ``django.views`` was added. - **Method Flowchart** - 1. :meth:`dispatch()` - 2. :meth:`http_method_not_allowed()` - 3. :meth:`options()` + #. :meth:`setup()` + #. :meth:`dispatch()` + #. :meth:`http_method_not_allowed()` + #. :meth:`options()` **Example views.py**:: @@ -44,12 +41,12 @@ MRO is an acronym for Method Resolution Order. **Example urls.py**:: - from django.conf.urls import url + from django.urls import path from myapp.views import MyView urlpatterns = [ - url(r'^mine/$', MyView.as_view(), name='my-view'), + path('mine/', MyView.as_view(), name='my-view'), ] **Attributes** @@ -73,6 +70,24 @@ MRO is an acronym for Method Resolution Order. The returned view has ``view_class`` and ``view_initkwargs`` attributes. + When the view is called during the request/response cycle, the + :meth:`setup` method assigns the :class:`~django.http.HttpRequest` to + the view's ``request`` attribute, and any positional and/or keyword + arguments :ref:`captured from the URL pattern + ` to the ``args`` and ``kwargs`` + attributes, respectively. Then :meth:`dispatch` is called. + + .. method:: setup(request, *args, **kwargs) + + .. versionadded:: 2.2 + + Initializes view instance attributes: ``self.request``, ``self.args``, + and ``self.kwargs`` prior to :meth:`dispatch`. + + Overriding this method allows mixins to setup instance attributes for + reuse in child classes. When overriding this method, you must call + ``super()``. + .. method:: dispatch(request, *args, **kwargs) The ``view`` part of the view -- the method that accepts a ``request`` @@ -120,9 +135,10 @@ MRO is an acronym for Method Resolution Order. **Method Flowchart** - 1. :meth:`~django.views.generic.base.View.dispatch()` - 2. :meth:`~django.views.generic.base.View.http_method_not_allowed()` - 3. :meth:`~django.views.generic.base.ContextMixin.get_context_data()` + #. :meth:`~django.views.generic.base.View.setup()` + #. :meth:`~django.views.generic.base.View.dispatch()` + #. :meth:`~django.views.generic.base.View.http_method_not_allowed()` + #. :meth:`~django.views.generic.base.ContextMixin.get_context_data()` **Example views.py**:: @@ -135,24 +151,27 @@ MRO is an acronym for Method Resolution Order. template_name = "home.html" def get_context_data(self, **kwargs): - context = super(HomePageView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context['latest_articles'] = Article.objects.all()[:5] return context **Example urls.py**:: - from django.conf.urls import url + from django.urls import path from myapp.views import HomePageView urlpatterns = [ - url(r'^$', HomePageView.as_view(), name='home'), + path('', HomePageView.as_view(), name='home'), ] **Context** * Populated (through :class:`~django.views.generic.base.ContextMixin`) with the keyword arguments captured from the URL pattern that served the view. + * You can also add context using the + :attr:`~django.views.generic.base.ContextMixin.extra_context` keyword + argument for :meth:`~django.views.generic.base.View.as_view`. ``RedirectView`` ================ @@ -178,9 +197,10 @@ MRO is an acronym for Method Resolution Order. **Method Flowchart** - 1. :meth:`~django.views.generic.base.View.dispatch()` - 2. :meth:`~django.views.generic.base.View.http_method_not_allowed()` - 3. :meth:`get_redirect_url()` + #. :meth:`~django.views.generic.base.View.setup()` + #. :meth:`~django.views.generic.base.View.dispatch()` + #. :meth:`~django.views.generic.base.View.http_method_not_allowed()` + #. :meth:`get_redirect_url()` **Example views.py**:: @@ -198,19 +218,19 @@ MRO is an acronym for Method Resolution Order. def get_redirect_url(self, *args, **kwargs): article = get_object_or_404(Article, pk=kwargs['pk']) article.update_counter() - return super(ArticleCounterRedirectView, self).get_redirect_url(*args, **kwargs) + return super().get_redirect_url(*args, **kwargs) **Example urls.py**:: - from django.conf.urls import url + from django.urls import path from django.views.generic.base import RedirectView from article.views import ArticleCounterRedirectView, ArticleDetail urlpatterns = [ - url(r'^counter/(?P[0-9]+)/$', ArticleCounterRedirectView.as_view(), name='article-counter'), - url(r'^details/(?P[0-9]+)/$', ArticleDetail.as_view(), name='article-detail'), - url(r'^go-to-django/$', RedirectView.as_view(url='https://djangoproject.com'), name='go-to-django'), + path('counter//', ArticleCounterRedirectView.as_view(), name='article-counter'), + path('details//', ArticleDetail.as_view(), name='article-detail'), + path('go-to-django/', RedirectView.as_view(url='https://djangoproject.com'), name='go-to-django'), ] **Attributes** diff --git a/docs/ref/class-based-views/flattened-index.txt b/docs/ref/class-based-views/flattened-index.txt index 25ac1465002e..6e85e0b3b807 100644 --- a/docs/ref/class-based-views/flattened-index.txt +++ b/docs/ref/class-based-views/flattened-index.txt @@ -31,6 +31,7 @@ Simple generic views * :meth:`~django.views.generic.base.View.dispatch` * ``head()`` * :meth:`~django.views.generic.base.View.http_method_not_allowed` +* :meth:`~django.views.generic.base.View.setup` ``TemplateView`` ---------------- @@ -40,6 +41,7 @@ Simple generic views **Attributes** (with optional accessor): * :attr:`~django.views.generic.base.TemplateResponseMixin.content_type` +* :attr:`~django.views.generic.base.ContextMixin.extra_context` * :attr:`~django.views.generic.base.View.http_method_names` * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`] * :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine` @@ -54,6 +56,7 @@ Simple generic views * ``head()`` * :meth:`~django.views.generic.base.View.http_method_not_allowed` * :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` +* :meth:`~django.views.generic.base.View.setup` ``RedirectView`` ---------------- @@ -79,6 +82,7 @@ Simple generic views * ``options()`` * ``post()`` * ``put()`` +* :meth:`~django.views.generic.base.View.setup` Detail Views ============ @@ -92,6 +96,7 @@ Detail Views * :attr:`~django.views.generic.base.TemplateResponseMixin.content_type` * :attr:`~django.views.generic.detail.SingleObjectMixin.context_object_name` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_context_object_name`] +* :attr:`~django.views.generic.base.ContextMixin.extra_context` * :attr:`~django.views.generic.base.View.http_method_names` * :attr:`~django.views.generic.detail.SingleObjectMixin.model` * :attr:`~django.views.generic.detail.SingleObjectMixin.pk_url_kwarg` @@ -114,6 +119,7 @@ Detail Views * ``head()`` * :meth:`~django.views.generic.base.View.http_method_not_allowed` * :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` +* :meth:`~django.views.generic.base.View.setup` List Views ========== @@ -128,6 +134,7 @@ List Views * :attr:`~django.views.generic.list.MultipleObjectMixin.allow_empty` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_allow_empty`] * :attr:`~django.views.generic.base.TemplateResponseMixin.content_type` * :attr:`~django.views.generic.list.MultipleObjectMixin.context_object_name` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_context_object_name`] +* :attr:`~django.views.generic.base.ContextMixin.extra_context` * :attr:`~django.views.generic.base.View.http_method_names` * :attr:`~django.views.generic.list.MultipleObjectMixin.model` * :attr:`~django.views.generic.list.MultipleObjectMixin.ordering` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_ordering`] @@ -151,6 +158,7 @@ List Views * :meth:`~django.views.generic.base.View.http_method_not_allowed` * :meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset` * :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` +* :meth:`~django.views.generic.base.View.setup` Editing views ============= @@ -163,6 +171,7 @@ Editing views **Attributes** (with optional accessor): * :attr:`~django.views.generic.base.TemplateResponseMixin.content_type` +* :attr:`~django.views.generic.base.ContextMixin.extra_context` * :attr:`~django.views.generic.edit.FormMixin.form_class` [:meth:`~django.views.generic.edit.FormMixin.get_form_class`] * :attr:`~django.views.generic.base.View.http_method_names` * :attr:`~django.views.generic.edit.FormMixin.initial` [:meth:`~django.views.generic.edit.FormMixin.get_initial`] @@ -185,6 +194,7 @@ Editing views * :meth:`~django.views.generic.base.View.http_method_not_allowed` * :meth:`~django.views.generic.edit.ProcessFormView.post` * :meth:`~django.views.generic.edit.ProcessFormView.put` +* :meth:`~django.views.generic.base.View.setup` ``CreateView`` -------------- @@ -195,6 +205,7 @@ Editing views * :attr:`~django.views.generic.base.TemplateResponseMixin.content_type` * :attr:`~django.views.generic.detail.SingleObjectMixin.context_object_name` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_context_object_name`] +* :attr:`~django.views.generic.base.ContextMixin.extra_context` * :attr:`~django.views.generic.edit.ModelFormMixin.fields` * :attr:`~django.views.generic.edit.FormMixin.form_class` [:meth:`~django.views.generic.edit.ModelFormMixin.get_form_class`] * :attr:`~django.views.generic.base.View.http_method_names` @@ -228,6 +239,7 @@ Editing views * :meth:`~django.views.generic.edit.ProcessFormView.post` * ``put()`` * :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` +* :meth:`~django.views.generic.base.View.setup` ``UpdateView`` -------------- @@ -238,6 +250,7 @@ Editing views * :attr:`~django.views.generic.base.TemplateResponseMixin.content_type` * :attr:`~django.views.generic.detail.SingleObjectMixin.context_object_name` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_context_object_name`] +* :attr:`~django.views.generic.base.ContextMixin.extra_context` * :attr:`~django.views.generic.edit.ModelFormMixin.fields` * :attr:`~django.views.generic.edit.FormMixin.form_class` [:meth:`~django.views.generic.edit.ModelFormMixin.get_form_class`] * :attr:`~django.views.generic.base.View.http_method_names` @@ -271,6 +284,7 @@ Editing views * :meth:`~django.views.generic.edit.ProcessFormView.post` * ``put()`` * :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` +* :meth:`~django.views.generic.base.View.setup` ``DeleteView`` -------------- @@ -281,6 +295,7 @@ Editing views * :attr:`~django.views.generic.base.TemplateResponseMixin.content_type` * :attr:`~django.views.generic.detail.SingleObjectMixin.context_object_name` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_context_object_name`] +* :attr:`~django.views.generic.base.ContextMixin.extra_context` * :attr:`~django.views.generic.base.View.http_method_names` * :attr:`~django.views.generic.detail.SingleObjectMixin.model` * :attr:`~django.views.generic.detail.SingleObjectMixin.pk_url_kwarg` @@ -306,6 +321,7 @@ Editing views * :meth:`~django.views.generic.base.View.http_method_not_allowed` * ``post()`` * :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` +* :meth:`~django.views.generic.base.View.setup` Date-based views ================ @@ -322,6 +338,7 @@ Date-based views * :attr:`~django.views.generic.base.TemplateResponseMixin.content_type` * :attr:`~django.views.generic.list.MultipleObjectMixin.context_object_name` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_context_object_name`] * :attr:`~django.views.generic.dates.DateMixin.date_field` [:meth:`~django.views.generic.dates.DateMixin.get_date_field`] +* :attr:`~django.views.generic.base.ContextMixin.extra_context` * :attr:`~django.views.generic.base.View.http_method_names` * :attr:`~django.views.generic.list.MultipleObjectMixin.model` * :attr:`~django.views.generic.list.MultipleObjectMixin.ordering` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_ordering`] @@ -348,6 +365,7 @@ Date-based views * :meth:`~django.views.generic.base.View.http_method_not_allowed` * :meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset` * :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` +* :meth:`~django.views.generic.base.View.setup` ``YearArchiveView`` ------------------- @@ -361,6 +379,7 @@ Date-based views * :attr:`~django.views.generic.base.TemplateResponseMixin.content_type` * :attr:`~django.views.generic.list.MultipleObjectMixin.context_object_name` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_context_object_name`] * :attr:`~django.views.generic.dates.DateMixin.date_field` [:meth:`~django.views.generic.dates.DateMixin.get_date_field`] +* :attr:`~django.views.generic.base.ContextMixin.extra_context` * :attr:`~django.views.generic.base.View.http_method_names` * :attr:`~django.views.generic.dates.YearArchiveView.make_object_list` [:meth:`~django.views.generic.dates.YearArchiveView.get_make_object_list`] * :attr:`~django.views.generic.list.MultipleObjectMixin.model` @@ -390,6 +409,7 @@ Date-based views * :meth:`~django.views.generic.base.View.http_method_not_allowed` * :meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset` * :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` +* :meth:`~django.views.generic.base.View.setup` ``MonthArchiveView`` -------------------- @@ -403,6 +423,7 @@ Date-based views * :attr:`~django.views.generic.base.TemplateResponseMixin.content_type` * :attr:`~django.views.generic.list.MultipleObjectMixin.context_object_name` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_context_object_name`] * :attr:`~django.views.generic.dates.DateMixin.date_field` [:meth:`~django.views.generic.dates.DateMixin.get_date_field`] +* :attr:`~django.views.generic.base.ContextMixin.extra_context` * :attr:`~django.views.generic.base.View.http_method_names` * :attr:`~django.views.generic.list.MultipleObjectMixin.model` * :attr:`~django.views.generic.dates.MonthMixin.month` [:meth:`~django.views.generic.dates.MonthMixin.get_month`] @@ -435,6 +456,7 @@ Date-based views * :meth:`~django.views.generic.base.View.http_method_not_allowed` * :meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset` * :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` +* :meth:`~django.views.generic.base.View.setup` ``WeekArchiveView`` ------------------- @@ -448,6 +470,7 @@ Date-based views * :attr:`~django.views.generic.base.TemplateResponseMixin.content_type` * :attr:`~django.views.generic.list.MultipleObjectMixin.context_object_name` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_context_object_name`] * :attr:`~django.views.generic.dates.DateMixin.date_field` [:meth:`~django.views.generic.dates.DateMixin.get_date_field`] +* :attr:`~django.views.generic.base.ContextMixin.extra_context` * :attr:`~django.views.generic.base.View.http_method_names` * :attr:`~django.views.generic.list.MultipleObjectMixin.model` * :attr:`~django.views.generic.list.MultipleObjectMixin.ordering` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_ordering`] @@ -478,6 +501,7 @@ Date-based views * :meth:`~django.views.generic.base.View.http_method_not_allowed` * :meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset` * :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` +* :meth:`~django.views.generic.base.View.setup` ``DayArchiveView`` ------------------ @@ -493,6 +517,7 @@ Date-based views * :attr:`~django.views.generic.dates.DateMixin.date_field` [:meth:`~django.views.generic.dates.DateMixin.get_date_field`] * :attr:`~django.views.generic.dates.DayMixin.day` [:meth:`~django.views.generic.dates.DayMixin.get_day`] * :attr:`~django.views.generic.dates.DayMixin.day_format` [:meth:`~django.views.generic.dates.DayMixin.get_day_format`] +* :attr:`~django.views.generic.base.ContextMixin.extra_context` * :attr:`~django.views.generic.base.View.http_method_names` * :attr:`~django.views.generic.list.MultipleObjectMixin.model` * :attr:`~django.views.generic.dates.MonthMixin.month` [:meth:`~django.views.generic.dates.MonthMixin.get_month`] @@ -527,6 +552,7 @@ Date-based views * :meth:`~django.views.generic.base.View.http_method_not_allowed` * :meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset` * :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` +* :meth:`~django.views.generic.base.View.setup` ``TodayArchiveView`` -------------------- @@ -542,6 +568,7 @@ Date-based views * :attr:`~django.views.generic.dates.DateMixin.date_field` [:meth:`~django.views.generic.dates.DateMixin.get_date_field`] * :attr:`~django.views.generic.dates.DayMixin.day` [:meth:`~django.views.generic.dates.DayMixin.get_day`] * :attr:`~django.views.generic.dates.DayMixin.day_format` [:meth:`~django.views.generic.dates.DayMixin.get_day_format`] +* :attr:`~django.views.generic.base.ContextMixin.extra_context` * :attr:`~django.views.generic.base.View.http_method_names` * :attr:`~django.views.generic.list.MultipleObjectMixin.model` * :attr:`~django.views.generic.dates.MonthMixin.month` [:meth:`~django.views.generic.dates.MonthMixin.get_month`] @@ -576,6 +603,7 @@ Date-based views * :meth:`~django.views.generic.base.View.http_method_not_allowed` * :meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset` * :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` +* :meth:`~django.views.generic.base.View.setup` ``DateDetailView`` ------------------ @@ -590,6 +618,7 @@ Date-based views * :attr:`~django.views.generic.dates.DateMixin.date_field` [:meth:`~django.views.generic.dates.DateMixin.get_date_field`] * :attr:`~django.views.generic.dates.DayMixin.day` [:meth:`~django.views.generic.dates.DayMixin.get_day`] * :attr:`~django.views.generic.dates.DayMixin.day_format` [:meth:`~django.views.generic.dates.DayMixin.get_day_format`] +* :attr:`~django.views.generic.base.ContextMixin.extra_context` * :attr:`~django.views.generic.base.View.http_method_names` * :attr:`~django.views.generic.detail.SingleObjectMixin.model` * :attr:`~django.views.generic.dates.MonthMixin.month` [:meth:`~django.views.generic.dates.MonthMixin.get_month`] @@ -620,3 +649,4 @@ Date-based views * ``head()`` * :meth:`~django.views.generic.base.View.http_method_not_allowed` * :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` +* :meth:`~django.views.generic.base.View.setup` diff --git a/docs/ref/class-based-views/generic-date-based.txt b/docs/ref/class-based-views/generic-date-based.txt index 7b50d099ba85..a42896c05861 100644 --- a/docs/ref/class-based-views/generic-date-based.txt +++ b/docs/ref/class-based-views/generic-date-based.txt @@ -63,15 +63,15 @@ views for displaying drilldown pages for date-based data. **Example myapp/urls.py**:: - from django.conf.urls import url + from django.urls import path from django.views.generic.dates import ArchiveIndexView from myapp.models import Article urlpatterns = [ - url(r'^archive/$', - ArchiveIndexView.as_view(model=Article, date_field="pub_date"), - name="article_archive"), + path('archive/', + ArchiveIndexView.as_view(model=Article, date_field="pub_date"), + name="article_archive"), ] **Example myapp/article_archive.html**: @@ -162,14 +162,14 @@ views for displaying drilldown pages for date-based data. **Example myapp/urls.py**:: - from django.conf.urls import url + from django.urls import path from myapp.views import ArticleYearArchiveView urlpatterns = [ - url(r'^(?P[0-9]{4})/$', - ArticleYearArchiveView.as_view(), - name="article_year_archive"), + path('/', + ArticleYearArchiveView.as_view(), + name="article_year_archive"), ] **Example myapp/article_archive_year.html**: @@ -254,19 +254,19 @@ views for displaying drilldown pages for date-based data. **Example myapp/urls.py**:: - from django.conf.urls import url + from django.urls import path from myapp.views import ArticleMonthArchiveView urlpatterns = [ - # Example: /2012/aug/ - url(r'^(?P[0-9]{4})/(?P[-\w]+)/$', - ArticleMonthArchiveView.as_view(), - name="archive_month"), # Example: /2012/08/ - url(r'^(?P[0-9]{4})/(?P[0-9]+)/$', - ArticleMonthArchiveView.as_view(month_format='%m'), - name="archive_month_numeric"), + path('//', + ArticleMonthArchiveView.as_view(month_format='%m'), + name="archive_month_numeric"), + # Example: /2012/aug/ + path('//', + ArticleMonthArchiveView.as_view(), + name="archive_month"), ] **Example myapp/article_archive_month.html**: @@ -356,15 +356,15 @@ views for displaying drilldown pages for date-based data. **Example myapp/urls.py**:: - from django.conf.urls import url + from django.urls import path from myapp.views import ArticleWeekArchiveView urlpatterns = [ # Example: /2012/week/23/ - url(r'^(?P[0-9]{4})/week/(?P[0-9]+)/$', - ArticleWeekArchiveView.as_view(), - name="archive_week"), + path('/week//', + ArticleWeekArchiveView.as_view(), + name="archive_week"), ] **Example myapp/article_archive_week.html**: @@ -468,15 +468,15 @@ views for displaying drilldown pages for date-based data. **Example myapp/urls.py**:: - from django.conf.urls import url + from django.urls import path from myapp.views import ArticleDayArchiveView urlpatterns = [ # Example: /2012/nov/10/ - url(r'^(?P[0-9]{4})/(?P[-\w]+)/(?P[0-9]+)/$', - ArticleDayArchiveView.as_view(), - name="archive_day"), + path('///', + ArticleDayArchiveView.as_view(), + name="archive_day"), ] **Example myapp/article_archive_day.html**: @@ -541,14 +541,14 @@ views for displaying drilldown pages for date-based data. **Example myapp/urls.py**:: - from django.conf.urls import url + from django.urls import path from myapp.views import ArticleTodayArchiveView urlpatterns = [ - url(r'^today/$', - ArticleTodayArchiveView.as_view(), - name="archive_today"), + path('today/', + ArticleTodayArchiveView.as_view(), + name="archive_today"), ] .. admonition:: Where is the example template for ``TodayArchiveView``? @@ -591,13 +591,13 @@ views for displaying drilldown pages for date-based data. **Example myapp/urls.py**:: - from django.conf.urls import url + from django.urls import path from django.views.generic.dates import DateDetailView urlpatterns = [ - url(r'^(?P[0-9]{4})/(?P[-\w]+)/(?P[0-9]+)/(?P[0-9]+)/$', - DateDetailView.as_view(model=Article, date_field="pub_date"), - name="archive_date_detail"), + path('////', + DateDetailView.as_view(model=Article, date_field="pub_date"), + name="archive_date_detail"), ] **Example myapp/article_detail.html**: diff --git a/docs/ref/class-based-views/generic-display.txt b/docs/ref/class-based-views/generic-display.txt index b97d3674cd37..ac1e4c39cae0 100644 --- a/docs/ref/class-based-views/generic-display.txt +++ b/docs/ref/class-based-views/generic-display.txt @@ -25,21 +25,22 @@ many projects they are typically the most commonly used views. **Method Flowchart** - 1. :meth:`~django.views.generic.base.View.dispatch()` - 2. :meth:`~django.views.generic.base.View.http_method_not_allowed()` - 3. :meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names()` - 4. :meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field()` - 5. :meth:`~django.views.generic.detail.SingleObjectMixin.get_queryset()` - 6. :meth:`~django.views.generic.detail.SingleObjectMixin.get_object()` - 7. :meth:`~django.views.generic.detail.SingleObjectMixin.get_context_object_name()` - 8. :meth:`~django.views.generic.detail.SingleObjectMixin.get_context_data()` - 9. ``get()`` - 10. :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response()` + #. :meth:`~django.views.generic.base.View.setup()` + #. :meth:`~django.views.generic.base.View.dispatch()` + #. :meth:`~django.views.generic.base.View.http_method_not_allowed()` + #. :meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names()` + #. :meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field()` + #. :meth:`~django.views.generic.detail.SingleObjectMixin.get_queryset()` + #. :meth:`~django.views.generic.detail.SingleObjectMixin.get_object()` + #. :meth:`~django.views.generic.detail.SingleObjectMixin.get_context_object_name()` + #. :meth:`~django.views.generic.detail.SingleObjectMixin.get_context_data()` + #. ``get()`` + #. :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response()` **Example myapp/views.py**:: - from django.views.generic.detail import DetailView from django.utils import timezone + from django.views.generic.detail import DetailView from articles.models import Article @@ -48,18 +49,18 @@ many projects they are typically the most commonly used views. model = Article def get_context_data(self, **kwargs): - context = super(ArticleDetailView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context['now'] = timezone.now() return context **Example myapp/urls.py**:: - from django.conf.urls import url + from django.urls import path from article.views import ArticleDetailView urlpatterns = [ - url(r'^(?P[-\w]+)/$', ArticleDetailView.as_view(), name='article-detail'), + path('/', ArticleDetailView.as_view(), name='article-detail'), ] **Example myapp/article_detail.html**: @@ -95,40 +96,41 @@ many projects they are typically the most commonly used views. **Method Flowchart** - 1. :meth:`~django.views.generic.base.View.dispatch()` - 2. :meth:`~django.views.generic.base.View.http_method_not_allowed()` - 3. :meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names()` - 4. :meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset()` - 5. :meth:`~django.views.generic.list.MultipleObjectMixin.get_context_object_name()` - 6. :meth:`~django.views.generic.list.MultipleObjectMixin.get_context_data()` - 7. ``get()`` - 8. :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response()` - + #. :meth:`~django.views.generic.base.View.setup()` + #. :meth:`~django.views.generic.base.View.dispatch()` + #. :meth:`~django.views.generic.base.View.http_method_not_allowed()` + #. :meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names()` + #. :meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset()` + #. :meth:`~django.views.generic.list.MultipleObjectMixin.get_context_object_name()` + #. :meth:`~django.views.generic.list.MultipleObjectMixin.get_context_data()` + #. ``get()`` + #. :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response()` **Example views.py**:: - from django.views.generic.list import ListView from django.utils import timezone + from django.views.generic.list import ListView from articles.models import Article class ArticleListView(ListView): model = Article + paginate_by = 100 # if pagination is desired def get_context_data(self, **kwargs): - context = super(ArticleListView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context['now'] = timezone.now() return context **Example myapp/urls.py**:: - from django.conf.urls import url + from django.urls import path from article.views import ArticleListView urlpatterns = [ - url(r'^$', ArticleListView.as_view(), name='article-list'), + path('', ArticleListView.as_view(), name='article-list'), ] **Example myapp/article_list.html**: @@ -144,6 +146,10 @@ many projects they are typically the most commonly used views. {% endfor %}
      + If you're using pagination, you can adapt the :ref:`example template from + the pagination docs `. Change instances of + ``contacts`` in that example template to ``page_obj``. + .. class:: django.views.generic.list.BaseListView A base view for displaying a list of objects. It is not intended to be used diff --git a/docs/ref/class-based-views/generic-editing.txt b/docs/ref/class-based-views/generic-editing.txt index 43dd349bffa9..0d5aebc9837e 100644 --- a/docs/ref/class-based-views/generic-editing.txt +++ b/docs/ref/class-based-views/generic-editing.txt @@ -10,13 +10,19 @@ editing content: * :class:`django.views.generic.edit.UpdateView` * :class:`django.views.generic.edit.DeleteView` +.. seealso:: + + The :doc:`messages framework ` contains + :class:`~django.contrib.messages.views.SuccessMessageMixin`, which + facilitates presenting messages about successful form submissions. + .. note:: Some of the examples on this page assume that an ``Author`` model has been defined as follows in ``myapp/models.py``:: - from django.urls import reverse from django.db import models + from django.urls import reverse class Author(models.Model): name = models.CharField(max_length=200) @@ -68,15 +74,15 @@ editing content: # This method is called when valid form data has been POSTed. # It should return an HttpResponse. form.send_email() - return super(ContactView, self).form_valid(form) + return super().form_valid(form) **Example myapp/contact.html**: .. code-block:: html+django -
      {% csrf_token %} + {% csrf_token %} {{ form.as_p }} - +
      @@ -130,9 +136,9 @@ editing content: .. code-block:: html+django -
      {% csrf_token %} + {% csrf_token %} {{ form.as_p }} - +
      ``UpdateView`` @@ -187,9 +193,9 @@ editing content: .. code-block:: html+django -
      {% csrf_token %} + {% csrf_token %} {{ form.as_p }} - +
      ``DeleteView`` @@ -226,8 +232,8 @@ editing content: **Example myapp/views.py**:: - from django.views.generic.edit import DeleteView from django.urls import reverse_lazy + from django.views.generic.edit import DeleteView from myapp.models import Author class AuthorDelete(DeleteView): @@ -238,7 +244,7 @@ editing content: .. code-block:: html+django -
      {% csrf_token %} + {% csrf_token %}

      Are you sure you want to delete "{{ object }}"?

      - +
      diff --git a/docs/ref/class-based-views/index.txt b/docs/ref/class-based-views/index.txt index 56708495625c..518700038512 100644 --- a/docs/ref/class-based-views/index.txt +++ b/docs/ref/class-based-views/index.txt @@ -26,7 +26,7 @@ A class-based view is deployed into a URL pattern using the :meth:`~django.views.generic.base.View.as_view()` classmethod:: urlpatterns = [ - url(r'^view/$', MyView.as_view(size=42)), + path('view/', MyView.as_view(size=42)), ] .. admonition:: Thread safety with view arguments @@ -51,7 +51,7 @@ used by themselves or inherited from. They may not provide all the capabilities required for projects, in which case there are Mixins which extend what base views can do. -Django’s generic views are built off of those base views, and were developed +Django's generic views are built off of those base views, and were developed as a shortcut for common usage patterns such as displaying the details of an object. They take certain common idioms and patterns found in view development and abstract them so that you can quickly write common views of diff --git a/docs/ref/class-based-views/mixins-editing.txt b/docs/ref/class-based-views/mixins-editing.txt index 80757ab5bfcc..9cde8b9834e3 100644 --- a/docs/ref/class-based-views/mixins-editing.txt +++ b/docs/ref/class-based-views/mixins-editing.txt @@ -173,7 +173,7 @@ The following mixins are used to construct Django's editing views: redirects to :meth:`~django.views.generic.edit.FormMixin.get_success_url`. - .. method:: form_invalid() + .. method:: form_invalid(form) Renders a response, providing the invalid form as context. @@ -232,6 +232,11 @@ The following mixins are used to construct Django's editing views: could use ``success_url="/parent/{parent_id}/"`` to redirect to a URL composed out of the ``parent_id`` field on a model. + .. method:: delete(request, *args, **kwargs) + + Retrieves the target object and calls its ``delete()`` method, then + redirects to the success URL. + .. method:: get_success_url() Returns the url to redirect to when the nominated object has been diff --git a/docs/ref/class-based-views/mixins-multiple-object.txt b/docs/ref/class-based-views/mixins-multiple-object.txt index 58260391a8a0..8f6fcb8d4862 100644 --- a/docs/ref/class-based-views/mixins-multiple-object.txt +++ b/docs/ref/class-based-views/mixins-multiple-object.txt @@ -15,7 +15,7 @@ Multiple object mixins * Use the ``page`` parameter in the URLconf. For example, this is what your URLconf might look like:: - url(r'^objects/page(?P[0-9]+)/$', PaginatedView.as_view()), + path('objects/page/', PaginatedView.as_view()), * Pass the page number via the ``page`` query-string parameter. For example, a URL would look like this:: diff --git a/docs/ref/class-based-views/mixins-simple.txt b/docs/ref/class-based-views/mixins-simple.txt index b039967c46ff..2d1bbdeb57f1 100644 --- a/docs/ref/class-based-views/mixins-simple.txt +++ b/docs/ref/class-based-views/mixins-simple.txt @@ -7,6 +7,17 @@ Simple mixins .. class:: django.views.generic.base.ContextMixin + **Attributes** + + .. attribute:: extra_context + + A dictionary to include in the context. This is a convenient way of + specifying some simple context in + :meth:`~django.views.generic.base.View.as_view`. Example usage:: + + from django.views.generic import TemplateView + TemplateView.as_view(extra_context={'title': 'Custom Title'}) + **Methods** .. method:: get_context_data(**kwargs) @@ -15,7 +26,7 @@ Simple mixins arguments provided will make up the returned context. Example usage:: def get_context_data(self, **kwargs): - context = super(RandomNumberView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context['number'] = random.randrange(1, 100) return context diff --git a/docs/ref/class-based-views/mixins-single-object.txt b/docs/ref/class-based-views/mixins-single-object.txt index 9100e4a104a0..2801b9964bf1 100644 --- a/docs/ref/class-based-views/mixins-single-object.txt +++ b/docs/ref/class-based-views/mixins-single-object.txt @@ -100,7 +100,7 @@ Single object mixins .. method:: get_context_data(**kwargs) - Returns context data for displaying the list of objects. + Returns context data for displaying the object. The base implementation of this method requires that the ``self.object`` attribute be set by the view (even if ``None``). Be sure to do this if diff --git a/docs/ref/clickjacking.txt b/docs/ref/clickjacking.txt index 2f72e18d2f15..f00f3c3628d5 100644 --- a/docs/ref/clickjacking.txt +++ b/docs/ref/clickjacking.txt @@ -35,14 +35,14 @@ load the resource in a frame if the request originated from the same site. If the header is set to ``DENY`` then the browser will block the resource from loading in a frame no matter which site made the request. -.. _X-Frame-Options: https://developer.mozilla.org/en-US/docs/Web/HTTP/X-Frame-Options +.. _X-Frame-Options: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options Django provides a few simple ways to include this header in responses from your site: -1. A simple middleware that sets the header in all responses. +#. A simple middleware that sets the header in all responses. -2. A set of view decorators that can be used to override the middleware or to +#. A set of view decorators that can be used to override the middleware or to only set the header for certain views. The ``X-Frame-Options`` HTTP header will only be set by the middleware or view @@ -84,6 +84,11 @@ that tells the middleware not to set the header:: def ok_to_load_in_a_frame(request): return HttpResponse("This page is safe to load in a frame on any site.") +.. note:: + + If you want to submit a form or access a session cookie within a frame or + iframe, you may need to modify the :setting:`CSRF_COOKIE_SAMESITE` or + :setting:`SESSION_COOKIE_SAMESITE` settings. Setting ``X-Frame-Options`` per view ------------------------------------ @@ -127,5 +132,5 @@ See also A `complete list`_ of browsers supporting ``X-Frame-Options``. -.. _complete list: https://developer.mozilla.org/en-US/docs/Web/HTTP/X-Frame-Options#Browser_compatibility +.. _complete list: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options#Browser_compatibility .. _other clickjacking prevention techniques: https://en.wikipedia.org/wiki/Clickjacking#Prevention diff --git a/docs/ref/contrib/admin/actions.txt b/docs/ref/contrib/admin/actions.txt index e98d8d2aef2b..edf196360f42 100644 --- a/docs/ref/contrib/admin/actions.txt +++ b/docs/ref/contrib/admin/actions.txt @@ -27,8 +27,9 @@ models. For example, here's the user module from Django's built-in has an important caveat: your model's ``delete()`` method will not be called. - If you wish to override this behavior, simply write a custom action which - accomplishes deletion in your preferred manner -- for example, by calling + If you wish to override this behavior, you can override + :meth:`.ModelAdmin.delete_queryset` or write a custom action which does + deletion in your preferred manner -- for example, by calling ``Model.delete()`` for each of the selected items. For more background on bulk deletion, see the documentation on :ref:`object @@ -46,18 +47,18 @@ simple news application with an ``Article`` model:: from django.db import models - STATUS_CHOICES = ( + STATUS_CHOICES = [ ('d', 'Draft'), ('p', 'Published'), ('w', 'Withdrawn'), - ) + ] class Article(models.Model): title = models.CharField(max_length=100) body = models.TextField() status = models.CharField(max_length=1, choices=STATUS_CHOICES) - def __str__(self): # __unicode__ on Python 2 + def __str__(self): return self.title A common task we might perform with a model like this is to update an @@ -219,8 +220,8 @@ example, you might write a simple export function that uses Django's :doc:`serialization functions ` to dump some selected objects as JSON:: - from django.http import HttpResponse from django.core import serializers + from django.http import HttpResponse def export_as_json(modeladmin, request, queryset): response = HttpResponse(content_type="application/json") @@ -237,14 +238,16 @@ you'd want to let the user choose a format, and possibly a list of fields to include in the export. The best thing to do would be to write a small action that simply redirects to your custom export view:: - from django.contrib import admin from django.contrib.contenttypes.models import ContentType from django.http import HttpResponseRedirect def export_selected_objects(modeladmin, request, queryset): - selected = request.POST.getlist(admin.ACTION_CHECKBOX_NAME) + selected = queryset.values_list('pk', flat=True) ct = ContentType.objects.get_for_model(queryset.model) - return HttpResponseRedirect("/export/?ct=%s&ids=%s" % (ct.pk, ",".join(selected))) + return HttpResponseRedirect('/export/?ct=%s&ids=%s' % ( + ct.pk, + ','.join(str(pk) for pk in selected), + )) As you can see, the action is the simple part; all the complex logic would belong in your export view. This would need to deal with objects of any type, @@ -342,17 +345,64 @@ Conditionally enabling or disabling actions This returns a dictionary of actions allowed. The keys are action names, and the values are ``(function, name, short_description)`` tuples. - Most of the time you'll use this method to conditionally remove actions from - the list gathered by the superclass. For example, if I only wanted users - whose names begin with 'J' to be able to delete objects in bulk, I could do - the following:: + For example, if you only want users whose names begin with 'J' to be able + to delete objects in bulk:: class MyModelAdmin(admin.ModelAdmin): ... def get_actions(self, request): - actions = super(MyModelAdmin, self).get_actions(request) + actions = super().get_actions(request) if request.user.username[0].upper() != 'J': if 'delete_selected' in actions: del actions['delete_selected'] return actions + +.. _admin-action-permissions: + +Setting permissions for actions +------------------------------- + +.. versionadded:: 2.1 + +Actions may limit their availability to users with specific permissions by +setting an ``allowed_permissions`` attribute on the action function:: + + def make_published(modeladmin, request, queryset): + queryset.update(status='p') + make_published.allowed_permissions = ('change',) + +The ``make_published()`` action will only be available to users that pass the +:meth:`.ModelAdmin.has_change_permission` check. + +If ``allowed_permissions`` has more than one permission, the action will be +available as long as the user passes at least one of the checks. + +Available values for ``allowed_permissions`` and the corresponding method +checks are: + +- ``'add'``: :meth:`.ModelAdmin.has_add_permission` +- ``'change'``: :meth:`.ModelAdmin.has_change_permission` +- ``'delete'``: :meth:`.ModelAdmin.has_delete_permission` +- ``'view'``: :meth:`.ModelAdmin.has_view_permission` + +You can specify any other value as long as you implement a corresponding +``has__permission(self, request)`` method on the ``ModelAdmin``. + +For example:: + + from django.contrib import admin + from django.contrib.auth import get_permission_codename + + class ArticleAdmin(admin.ModelAdmin): + actions = ['make_published'] + + def make_published(self, request, queryset): + queryset.update(status='p') + make_published.allowed_permissions = ('publish',) + + def has_publish_permission(self, request): + """Does the user have the publish permission?""" + opts = self.opts + codename = get_permission_codename('publish', opts) + return request.user.has_perm('%s.%s' % (opts.app_label, codename)) diff --git a/docs/ref/contrib/admin/admindocs.txt b/docs/ref/contrib/admin/admindocs.txt index 461813f9851d..7779fe822aad 100644 --- a/docs/ref/contrib/admin/admindocs.txt +++ b/docs/ref/contrib/admin/admindocs.txt @@ -19,9 +19,9 @@ To activate the :mod:`~django.contrib.admindocs`, you will need to do the following: * Add :mod:`django.contrib.admindocs` to your :setting:`INSTALLED_APPS`. -* Add ``url(r'^admin/doc/', include('django.contrib.admindocs.urls'))`` to +* Add ``path('admin/doc/', include('django.contrib.admindocs.urls'))`` to your ``urlpatterns``. Make sure it's included *before* the - ``r'^admin/'`` entry, so that requests to ``/admin/doc/`` don't get + ``'admin/'`` entry, so that requests to ``/admin/doc/`` don't get handled by the latter entry. * Install the docutils Python module (http://docutils.sf.net/). * **Optional:** Using the admindocs bookmarklets requires @@ -51,9 +51,13 @@ Model reference =============== The **models** section of the ``admindocs`` page describes each model in the -system along with all the fields and methods available on it. Relationships -to other models appear as hyperlinks. Descriptions are pulled from ``help_text`` -attributes on fields or from docstrings on model methods. +system along with all the fields, properties, and methods available on it. +Relationships to other models appear as hyperlinks. Descriptions are pulled +from ``help_text`` attributes on fields or from docstrings on model methods. + +.. versionchanged:: 2.2 + + Older versions don't display model properties. A model with useful documentation might look like this:: diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 40acb7dd2966..7ee8fa2db417 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -25,41 +25,39 @@ Overview The admin is enabled in the default project template used by :djadmin:`startproject`. -For reference, here are the requirements: +If you're not using the default project template, here are the requirements: -1. Add ``'django.contrib.admin'`` to your :setting:`INSTALLED_APPS` setting. +#. Add ``'django.contrib.admin'`` and its dependencies - + :mod:`django.contrib.auth`, :mod:`django.contrib.contenttypes`, + :mod:`django.contrib.messages`, and :mod:`django.contrib.sessions` - to your + :setting:`INSTALLED_APPS` setting. -2. The admin has four dependencies - :mod:`django.contrib.auth`, - :mod:`django.contrib.contenttypes`, - :mod:`django.contrib.messages` and - :mod:`django.contrib.sessions`. If these applications are not - in your :setting:`INSTALLED_APPS` list, add them. +#. Configure a :class:`~django.template.backends.django.DjangoTemplates` + backend in your :setting:`TEMPLATES` setting with + ``django.contrib.auth.context_processors.auth`` and + ``django.contrib.messages.context_processors.messages`` in + the ``'context_processors'`` option of :setting:`OPTIONS + `. -3. Add ``django.contrib.auth.context_processors.auth`` and - ``django.contrib.messages.context_processors.messages`` to - the ``'context_processors'`` option of the ``DjangoTemplates`` backend - defined in your :setting:`TEMPLATES` as well as +#. If you've customized the :setting:`MIDDLEWARE` setting, :class:`django.contrib.auth.middleware.AuthenticationMiddleware` and - :class:`django.contrib.messages.middleware.MessageMiddleware` to - :setting:`MIDDLEWARE`. These are all active by default, so you only need to - do this if you've manually tweaked the settings. + :class:`django.contrib.messages.middleware.MessageMiddleware` must be + included. -4. Determine which of your application's models should be editable in the - admin interface. +5. :ref:`Hook the admin's URLs into your URLconf + `. -5. For each of those models, optionally create a ``ModelAdmin`` class that - encapsulates the customized admin functionality and options for that - particular model. +After you've taken these steps, you'll be able to use the admin site by +visiting the URL you hooked it into (``/admin/``, by default). -6. Instantiate an ``AdminSite`` and tell it about each of your models and - ``ModelAdmin`` classes. +If you need to create a user to login with, use the :djadmin:`createsuperuser` +command. By default, logging in to the admin requires that the user has the +:attr:`~.User.is_superuser` or :attr:`~.User.is_staff` attribute set to +``True``. -7. Hook the ``AdminSite`` instance into your URLconf. - -After you've taken these steps, you'll be able to use your Django admin site -by visiting the URL you hooked it into (``/admin/``, by default). If you need -to create a user to login with, you can use the :djadmin:`createsuperuser` -command. +Finally, determine which of your application's models should be editable in the +admin interface. For each of those models, register them with the admin as +described in :class:`ModelAdmin`. Other topics ------------ @@ -123,12 +121,12 @@ The ``register`` decorator class AuthorAdmin(admin.ModelAdmin): pass - It is given one or more model classes to register with the ``ModelAdmin`` - and an optional keyword argument ``site`` if you are not using the default - ``AdminSite``:: + It's given one or more model classes to register with the ``ModelAdmin``. + If you're using a custom :class:`AdminSite`, pass it using the ``site`` keyword + argument:: from django.contrib import admin - from .models import Author, Reader, Editor + from .models import Author, Editor, Reader from myproject.admin_site import custom_admin_site @admin.register(Author, Reader, Editor, site=custom_admin_site) @@ -137,10 +135,8 @@ The ``register`` decorator You can't use this decorator if you have to reference your model admin class in its ``__init__()`` method, e.g. - ``super(PersonAdmin, self).__init__(*args, **kwargs)``. If you are using - Python 3 and don't have to worry about supporting Python 2, you can - use ``super().__init__(*args, **kwargs)`` . Otherwise, you'll have to use - ``admin.site.register()`` instead of this decorator. + ``super(PersonAdmin, self).__init__(*args, **kwargs)``. You can use + ``super().__init__(*args, **kwargs)``. Discovery of admin files ------------------------ @@ -159,6 +155,15 @@ application and imports it. This class works like :class:`~django.contrib.admin.apps.AdminConfig`, except it doesn't call :func:`~django.contrib.admin.autodiscover()`. + .. attribute:: default_site + + .. versionadded:: 2.1 + + A dotted import path to the default admin site's class or to a callable + that returns a site instance. Defaults to + ``'django.contrib.admin.sites.AdminSite'``. See + :ref:`overriding-default-admin-site` for usage. + .. function:: autodiscover This function attempts to import an ``admin`` module in each installed @@ -222,10 +227,6 @@ subclass:: e.g. if all the dates are in one month, it'll show the day-level drill-down only. - .. versionchanged:: 1.11 - - The ability to reference fields on related models was added. - .. note:: ``date_hierarchy`` uses :meth:`QuerySet.datetimes() @@ -305,9 +306,9 @@ subclass:: For more complex layout needs, see the :attr:`~ModelAdmin.fieldsets` option. - The ``fields`` option, unlike :attr:`~ModelAdmin.list_display`, may only - contain names of fields on the model or the form specified by - :attr:`~ModelAdmin.form`. It may contain callables only if they are listed + The ``fields`` option accepts the same types of values as + :attr:`~ModelAdmin.list_display`, except that callables aren't accepted. + Names of model and model admin methods will only be used if they're listed in :attr:`~ModelAdmin.readonly_fields`. To display multiple fields on the same line, wrap those fields in their own @@ -419,7 +420,7 @@ subclass:: Note that this value is *not* HTML-escaped when it's displayed in the admin interface. This lets you include HTML if you so desire. Alternatively you can use plain text and - ``django.utils.html.escape()`` to escape any HTML special + :func:`django.utils.html.escape` to escape any HTML special characters. .. attribute:: ModelAdmin.filter_horizontal @@ -499,12 +500,12 @@ subclass:: that we'd like to use for large text fields instead of the default ``

      -

      +

      -

      +

      Note that each form field has an ID attribute set to ``id_``, which is referenced by the accompanying label tag. This is important in ensuring that diff --git a/docs/topics/forms/media.txt b/docs/topics/forms/media.txt index adbd2c377ed5..398a4538b136 100644 --- a/docs/topics/forms/media.txt +++ b/docs/topics/forms/media.txt @@ -71,7 +71,7 @@ can be retrieved through this property:: >>> w = CalendarWidget() >>> print(w.media) - + @@ -114,9 +114,9 @@ requirements:: If this last CSS definition were to be rendered, it would become the following HTML:: - - - + + + ``js`` ------ @@ -145,8 +145,8 @@ example above:: >>> w = FancyCalendarWidget() >>> print(w.media) - - + + @@ -165,7 +165,7 @@ an ``extend=False`` declaration to the ``Media`` declaration:: >>> w = FancyCalendarWidget() >>> print(w.media) - + If you require even more control over inheritance, define your assets using a @@ -228,7 +228,7 @@ was ``None``:: >>> w = CalendarWidget() >>> print(w.media) - + @@ -236,23 +236,19 @@ But if :setting:`STATIC_URL` is ``'http://static.example.com/'``:: >>> w = CalendarWidget() >>> print(w.media) - + Or if :mod:`~django.contrib.staticfiles` is configured using the -`~django.contib.staticfiles.ManifestStaticFilesStorage`:: +:class:`~django.contrib.staticfiles.storage.ManifestStaticFilesStorage`:: >>> w = CalendarWidget() >>> print(w.media) - + -.. versionchanged:: 1.10 - - Older versions didn't serve assets using :mod:`django.contrib.staticfiles`. - ``Media`` objects ================= @@ -272,12 +268,12 @@ operator to filter out a medium of interest. For example:: >>> w = CalendarWidget() >>> print(w.media) - + >>> print(w.media['css']) - + When you use the subscript operator, the value that is returned is a new ``Media`` object -- but one that only contains the media of interest. @@ -304,11 +300,41 @@ specified by both:: >>> w1 = CalendarWidget() >>> w2 = OtherWidget() >>> print(w1.media + w2.media) - + +.. _form-media-asset-order: + +Order of assets +--------------- + +The order in which assets are inserted into the DOM is often important. For +example, you may have a script that depends on jQuery. Therefore, combining +``Media`` objects attempts to preserve the relative order in which assets are +defined in each ``Media`` class. + +For example:: + + >>> from django import forms + >>> class CalendarWidget(forms.TextInput): + ... class Media: + ... js = ('jQuery.js', 'calendar.js', 'noConflict.js') + >>> class TimeWidget(forms.TextInput): + ... class Media: + ... js = ('jQuery.js', 'time.js', 'noConflict.js') + >>> w1 = CalendarWidget() + >>> w2 = TimeWidget() + >>> print(w1.media + w2.media) + + + + + +Combining ``Media`` objects with assets in a conflicting order results in a +``MediaOrderConflictWarning``. + ``Media`` on Forms ================== @@ -330,7 +356,7 @@ are part of the form:: >>> f = ContactForm() >>> f.media - + @@ -350,8 +376,8 @@ form:: >>> f = ContactForm() >>> f.media - - + + diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index bc0c12401408..b9ecc14026f8 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -2,9 +2,6 @@ Creating forms from models ========================== -.. module:: django.forms.models - :synopsis: ModelForm and ModelFormset. - .. currentmodule:: django.forms ``ModelForm`` @@ -62,7 +59,14 @@ Model field Form field ``min_value`` set to -9223372036854775808 and ``max_value`` set to 9223372036854775807. -:class:`BooleanField` :class:`~django.forms.BooleanField` +:class:`BinaryField` :class:`~django.forms.CharField`, if + :attr:`~.Field.editable` is set to + ``True`` on the model field, otherwise not + represented in the form. + +:class:`BooleanField` :class:`~django.forms.BooleanField`, or + :class:`~django.forms.NullBooleanField` if + ``null=True``. :class:`CharField` :class:`~django.forms.CharField` with ``max_length`` set to the model field's @@ -70,14 +74,14 @@ Model field Form field :attr:`~django.forms.CharField.empty_value` set to ``None`` if ``null=True``. -:class:`CommaSeparatedIntegerField` :class:`~django.forms.CharField` - :class:`DateField` :class:`~django.forms.DateField` :class:`DateTimeField` :class:`~django.forms.DateTimeField` :class:`DecimalField` :class:`~django.forms.DecimalField` +:class:`DurationField` :class:`~django.forms.DurationField` + :class:`EmailField` :class:`~django.forms.EmailField` :class:`FileField` :class:`~django.forms.FileField` @@ -89,7 +93,7 @@ Model field Form field :class:`ForeignKey` :class:`~django.forms.ModelChoiceField` (see below) -``ImageField`` :class:`~django.forms.ImageField` +:class:`ImageField` :class:`~django.forms.ImageField` :class:`IntegerField` :class:`~django.forms.IntegerField` @@ -116,6 +120,8 @@ Model field Form field :class:`TimeField` :class:`~django.forms.TimeField` :class:`URLField` :class:`~django.forms.URLField` + +:class:`UUIDField` :class:`~django.forms.UUIDField` =================================== ================================================== .. currentmodule:: django.forms @@ -160,18 +166,18 @@ Consider this set of models:: from django.db import models from django.forms import ModelForm - TITLE_CHOICES = ( + TITLE_CHOICES = [ ('MR', 'Mr.'), ('MRS', 'Mrs.'), ('MS', 'Ms.'), - ) + ] class Author(models.Model): name = models.CharField(max_length=100) title = models.CharField(max_length=3, choices=TITLE_CHOICES) birth_date = models.DateField(blank=True, null=True) - def __str__(self): # __unicode__ on Python 2 + def __str__(self): return self.name class Book(models.Model): @@ -290,8 +296,8 @@ You can override the error messages from ``NON_FIELD_ERRORS`` raised by model validation by adding the :data:`~django.core.exceptions.NON_FIELD_ERRORS` key to the ``error_messages`` dictionary of the ``ModelForm``’s inner ``Meta`` class:: - from django.forms import ModelForm from django.core.exceptions import NON_FIELD_ERRORS + from django.forms import ModelForm class ArticleForm(ModelForm): class Meta: @@ -337,22 +343,14 @@ doesn't validate -- i.e., if ``form.errors`` evaluates to ``True``. If an optional field doesn't appear in the form's data, the resulting model instance uses the model field :attr:`~django.db.models.Field.default`, if there is one, for that field. This behavior doesn't apply to fields that use -:class:`~django.forms.CheckboxInput` and -:class:`~django.forms.CheckboxSelectMultiple` (or any custom widget whose +:class:`~django.forms.CheckboxInput`, +:class:`~django.forms.CheckboxSelectMultiple`, or +:class:`~django.forms.SelectMultiple` (or any custom widget whose :meth:`~django.forms.Widget.value_omitted_from_data` method always returns -``False``) since an unchecked checkbox doesn't appear in the data of an HTML -form submission. Use a custom form field or widget if you're designing an API -and want the default fallback for a :class:`~django.db.models.BooleanField`. - -.. versionchanged:: 1.10.1 - - Older versions don't have the exception for - :class:`~django.forms.CheckboxInput` which means that unchecked checkboxes - receive a value of ``True`` if that's the model field default. - -.. versionchanged:: 1.10.2 - - The :meth:`~django.forms.Widget.value_omitted_from_data` method was added. +``False``) since an unchecked checkbox and unselected `` - + + + .. note:: @@ -791,6 +791,12 @@ with the ``Author`` model. It works just like a regular formset:: means that a model formset is just an extension of a basic formset that knows how to interact with a particular model. +.. note:: + + When using :ref:`multi-table inheritance `, forms + generated by a formset factory will contain a parent link field (by default + ``_ptr``) instead of an ``id`` field. + Changing the queryset --------------------- @@ -809,7 +815,7 @@ Alternatively, you can create a subclass that sets ``self.queryset`` in class BaseAuthorFormSet(BaseModelFormSet): def __init__(self, *args, **kwargs): - super(BaseAuthorFormSet, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.queryset = Author.objects.filter(name__startswith='O') Then, pass your ``BaseAuthorFormSet`` class to the factory function:: @@ -880,8 +886,10 @@ As with regular formsets, it's possible to :ref:`specify initial data parameter when instantiating the model formset class returned by :func:`~django.forms.models.modelformset_factory`. However, with model formsets, the initial values only apply to extra forms, those that aren't -attached to an existing model instance. If the extra forms with initial data -aren't changed by the user, they won't be validated or saved. +attached to an existing model instance. If the length of ``initial`` exceeds +the number of extra forms, the excess initial data is ignored. If the extra +forms with initial data aren't changed by the user, they won't be validated or +saved. .. _saving-objects-in-the-formset: @@ -952,9 +960,9 @@ extra forms displayed. Also, ``extra=0`` doesn't prevent creation of new model instances as you can :ref:`add additional forms with JavaScript ` -or just send additional POST data. Formsets `don't yet provide functionality -`_ for an "edit only" view that -prevents creation of new instances. +or just send additional POST data. Formsets :ticket:`don't yet provide +functionality <26142>` for an "edit only" view that prevents creation of new +instances. If the value of ``max_num`` is greater than the number of existing related objects, up to ``extra`` additional blank forms will be added to the formset, @@ -964,10 +972,10 @@ so long as the total number of forms does not exceed ``max_num``:: >>> formset = AuthorFormSet(queryset=Author.objects.order_by('name')) >>> for form in formset: ... print(form.as_table()) - - - - + + + + A ``max_num`` value of ``None`` (the default) puts a high limit on the number of forms displayed (1000). In practice this is equivalent to no limit. @@ -1014,7 +1022,7 @@ class's ``clean`` method:: class MyModelFormSet(BaseModelFormSet): def clean(self): - super(MyModelFormSet, self).clean() + super().clean() # example custom validation across forms in the formset for form in self.forms: # your custom formset validation @@ -1030,7 +1038,7 @@ to modify a value in ``ModelFormSet.clean()`` you must modify class MyModelFormSet(BaseModelFormSet): def clean(self): - super(MyModelFormSet, self).clean() + super().clean() for form in self.forms: name = form.cleaned_data['name'].upper() @@ -1074,14 +1082,14 @@ There are three ways to render a formset in a Django template. First, you can let the formset do most of the work:: -
      + {{ formset }}
      Second, you can manually render the formset, but let the form deal with itself:: -
      + {{ formset.management_form }} {% for form in formset %} {{ form }} @@ -1094,7 +1102,7 @@ form as shown above. See the :ref:`management form documentation Third, you can manually render each field:: - + {{ formset.management_form }} {% for form in formset %} {% for field in form %} @@ -1107,7 +1115,7 @@ If you opt to use this third method and you don't iterate over the fields with a ``{% for %}`` loop, you'll need to render the primary key field. For example, if you were rendering the ``name`` and ``age`` fields of a model:: - + {{ formset.management_form }} {% for form in formset %} {{ form.id }} @@ -1153,6 +1161,10 @@ a particular author, you could do this:: >>> author = Author.objects.get(name='Mike Royko') >>> formset = BookFormSet(instance=author) +``BookFormSet``'s :ref:`prefix ` is ``'book_set'`` +(``_set`` ). If ``Book``'s ``ForeignKey`` to ``Author`` has a +:attr:`~django.db.models.ForeignKey.related_name`, that's used instead. + .. note:: :func:`~django.forms.models.inlineformset_factory` uses @@ -1176,7 +1188,7 @@ For example, if you want to override ``clean()``:: class CustomInlineFormSet(BaseInlineFormSet): def clean(self): - super(CustomInlineFormSet, self).clean() + super().clean() # example custom validation across forms in the formset for form in self.forms: # your custom formset validation diff --git a/docs/topics/http/decorators.txt b/docs/topics/http/decorators.txt index 55676c488826..b5145919abcd 100644 --- a/docs/topics/http/decorators.txt +++ b/docs/topics/http/decorators.txt @@ -7,6 +7,9 @@ View decorators Django provides several decorators that can be applied to views to support various HTTP features. +See :ref:`decorating-class-based-views` for how to use these decorators with +class-based views. + Allowed HTTP methods ==================== diff --git a/docs/topics/http/file-uploads.txt b/docs/topics/http/file-uploads.txt index 75ac1de45292..21a6f06853ef 100644 --- a/docs/topics/http/file-uploads.txt +++ b/docs/topics/http/file-uploads.txt @@ -21,8 +21,8 @@ Basic file uploads Consider a simple form containing a :class:`~django.forms.FileField`: -.. snippet:: - :filename: forms.py +.. code-block:: python + :caption: forms.py from django import forms @@ -46,8 +46,8 @@ Most of the time, you'll simply pass the file data from ``request`` into the form as described in :ref:`binding-uploaded-files`. This would look something like: -.. snippet:: - :filename: views.py +.. code-block:: python + :caption: views.py from django.http import HttpResponseRedirect from django.shortcuts import render @@ -133,8 +133,8 @@ Uploading multiple files If you want to upload multiple files using one form field, set the ``multiple`` HTML attribute of field's widget: -.. snippet:: - :filename: forms.py +.. code-block:: python + :caption: forms.py from django import forms @@ -145,8 +145,8 @@ Then override the ``post`` method of your :class:`~django.views.generic.edit.FormView` subclass to handle multiple file uploads: -.. snippet:: - :filename: views.py +.. code-block:: python + :caption: views.py from django.views.generic.edit import FormView from .forms import FileFieldForm @@ -190,8 +190,6 @@ data on the fly, render progress bars, and even send data to another storage location directly without storing it locally. See :ref:`custom_upload_handlers` for details on how you can customize or completely replace upload behavior. -.. _modifying_upload_handlers_on_the_fly: - Where uploaded data is stored ----------------------------- @@ -216,6 +214,8 @@ Changing upload handler behavior There are a few settings which control Django's file upload behavior. See :ref:`File Upload Settings ` for details. +.. _modifying_upload_handlers_on_the_fly: + Modifying upload handlers on the fly ------------------------------------ diff --git a/docs/topics/http/middleware.txt b/docs/topics/http/middleware.txt index 3ead0208382b..3b96eae4e82d 100644 --- a/docs/topics/http/middleware.txt +++ b/docs/topics/http/middleware.txt @@ -16,16 +16,6 @@ how to write your own middleware. Django ships with some built-in middleware you can use right out of the box. They're documented in the :doc:`built-in middleware reference `. -.. versionchanged:: 1.10 - - A new style of middleware was introduced for use with the new - :setting:`MIDDLEWARE` setting. If you're using the old - :setting:`MIDDLEWARE_CLASSES` setting, you'll need to :ref:`adapt old, - custom middleware ` before using the new setting. - This document describes new-style middleware. Refer to this page in older - versions of the documentation for a description of how old-style middleware - works. - Writing your own middleware =========================== @@ -53,7 +43,7 @@ A middleware can be written as a function that looks like this:: Or it can be written as a class whose instances are callable, like this:: - class SimpleMiddleware(object): + class SimpleMiddleware: def __init__(self, get_response): self.get_response = get_response # One-time configuration and initialization. @@ -96,15 +86,6 @@ caveats: * Unlike the ``__call__()`` method which is called once per request, ``__init__()`` is called only *once*, when the Web server starts. -.. versionchanged:: 1.10 - - In older versions, ``__init__()`` wasn't called until the Web server - responded to its first request. - - In older versions, ``__init__()`` didn't accept any arguments. To allow - your middleware to be used in Django 1.9 and earlier, make ``get_response`` - an optional argument (``get_response=None``). - Marking middleware as unused ---------------------------- @@ -311,7 +292,7 @@ Upgrading pre-Django 1.10-style middleware Django provides ``django.utils.deprecation.MiddlewareMixin`` to ease creating middleware classes that are compatible with both :setting:`MIDDLEWARE` and the -old :setting:`MIDDLEWARE_CLASSES`. All middleware classes included with Django +old ``MIDDLEWARE_CLASSES``. All middleware classes included with Django are compatible with both settings. The mixin provides an ``__init__()`` method that accepts an optional @@ -325,7 +306,7 @@ The ``__call__()`` method: #. Calls ``self.process_response(request, response)`` (if defined). #. Returns the response. -If used with :setting:`MIDDLEWARE_CLASSES`, the ``__call__()`` method will +If used with ``MIDDLEWARE_CLASSES``, the ``__call__()`` method will never be used; Django calls ``process_request()`` and ``process_response()`` directly. @@ -336,9 +317,9 @@ even beneficial to the existing middleware. In a few cases, a middleware class may need some changes to adjust to the new semantics. These are the behavioral differences between using :setting:`MIDDLEWARE` and -:setting:`MIDDLEWARE_CLASSES`: +``MIDDLEWARE_CLASSES``: -1. Under :setting:`MIDDLEWARE_CLASSES`, every middleware will always have its +#. Under ``MIDDLEWARE_CLASSES``, every middleware will always have its ``process_response`` method called, even if an earlier middleware short-circuited by returning a response from its ``process_request`` method. Under :setting:`MIDDLEWARE`, middleware behaves more like an onion: @@ -347,7 +328,7 @@ These are the behavioral differences between using :setting:`MIDDLEWARE` and that middleware and the ones before it in :setting:`MIDDLEWARE` will see the response. -2. Under :setting:`MIDDLEWARE_CLASSES`, ``process_exception`` is applied to +#. Under ``MIDDLEWARE_CLASSES``, ``process_exception`` is applied to exceptions raised from a middleware ``process_request`` method. Under :setting:`MIDDLEWARE`, ``process_exception`` applies only to exceptions raised from the view (or from the ``render`` method of a @@ -355,7 +336,7 @@ These are the behavioral differences between using :setting:`MIDDLEWARE` and a middleware are converted to the appropriate HTTP response and then passed to the next middleware. -3. Under :setting:`MIDDLEWARE_CLASSES`, if a ``process_response`` method raises +#. Under ``MIDDLEWARE_CLASSES``, if a ``process_response`` method raises an exception, the ``process_response`` methods of all earlier middleware are skipped and a ``500 Internal Server Error`` HTTP response is always returned (even if the exception raised was e.g. an diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index 39c64f525da5..f0311f6fa177 100644 --- a/docs/topics/http/sessions.txt +++ b/docs/topics/http/sessions.txt @@ -167,7 +167,7 @@ and the :setting:`SECRET_KEY` setting. .. _`common limit of 4096 bytes`: https://tools.ietf.org/html/rfc2965#section-5.3 .. _`replay attacks`: https://en.wikipedia.org/wiki/Replay_attack -.. _`speed of your site`: http://yuiblog.com/blog/2007/03/01/performance-research-part-3/ +.. _`speed of your site`: https://yuiblog.com/blog/2007/03/01/performance-research-part-3/ Using sessions in views ======================= @@ -629,6 +629,7 @@ behavior: * :setting:`SESSION_COOKIE_HTTPONLY` * :setting:`SESSION_COOKIE_NAME` * :setting:`SESSION_COOKIE_PATH` +* :setting:`SESSION_COOKIE_SAMESITE` * :setting:`SESSION_COOKIE_SECURE` * :setting:`SESSION_ENGINE` * :setting:`SESSION_EXPIRE_AT_BROWSER_CLOSE` @@ -650,10 +651,10 @@ session for their account. If the attacker has control over ``bad.example.com``, they can use it to send their session key to you since a subdomain is permitted to set cookies on ``*.example.com``. When you visit ``good.example.com``, you'll be logged in as the attacker and might inadvertently enter your -sensitive personal data (e.g. credit card info) into the attackers account. +sensitive personal data (e.g. credit card info) into the attacker's account. Another possible attack would be if ``good.example.com`` sets its -:setting:`SESSION_COOKIE_DOMAIN` to ``".example.com"`` which would cause +:setting:`SESSION_COOKIE_DOMAIN` to ``"example.com"`` which would cause session cookies from that site to be sent to ``bad.example.com``. Technical details @@ -810,7 +811,7 @@ to query the database for all active sessions for an account):: return CustomSession def create_model_instance(self, data): - obj = super(SessionStore, self).create_model_instance(data) + obj = super().create_model_instance(data) try: account_id = int(data.get('_auth_user_id')) except (ValueError, TypeError): diff --git a/docs/topics/http/shortcuts.txt b/docs/topics/http/shortcuts.txt index 705de11b690a..8448e4a8adc1 100644 --- a/docs/topics/http/shortcuts.txt +++ b/docs/topics/http/shortcuts.txt @@ -86,14 +86,16 @@ This example is equivalent to:: .. function:: render_to_response(template_name, context=None, content_type=None, status=None, using=None) + .. deprecated:: 2.0 + This function preceded the introduction of :func:`render` and works similarly except that it doesn't make the ``request`` available in the - response. It's not recommended and is likely to be deprecated in the future. + response. ``redirect()`` ============== -.. function:: redirect(to, permanent=False, *args, **kwargs) +.. function:: redirect(to, *args, permanent=False, **kwargs) Returns an :class:`~django.http.HttpResponseRedirect` to the appropriate URL for the arguments passed. @@ -117,7 +119,7 @@ Examples You can use the :func:`redirect` function in a number of ways. -1. By passing some object; that object's +#. By passing some object; that object's :meth:`~django.db.models.Model.get_absolute_url` method will be called to figure out the redirect URL:: @@ -125,10 +127,10 @@ You can use the :func:`redirect` function in a number of ways. def my_view(request): ... - object = MyModel.objects.get(...) - return redirect(object) + obj = MyModel.objects.get(...) + return redirect(obj) -2. By passing the name of a view and optionally some positional or +#. By passing the name of a view and optionally some positional or keyword arguments; the URL will be reverse resolved using the :func:`~django.urls.reverse` method:: @@ -136,7 +138,7 @@ You can use the :func:`redirect` function in a number of ways. ... return redirect('some-view-name', foo='bar') -3. By passing a hardcoded URL to redirect to:: +#. By passing a hardcoded URL to redirect to:: def my_view(request): ... @@ -154,8 +156,8 @@ will be returned:: def my_view(request): ... - object = MyModel.objects.get(...) - return redirect(object, permanent=True) + obj = MyModel.objects.get(...) + return redirect(obj, permanent=True) ``get_object_or_404()`` ======================= @@ -188,7 +190,7 @@ The following example gets the object with the primary key of 1 from from django.shortcuts import get_object_or_404 def my_view(request): - my_object = get_object_or_404(MyModel, pk=1) + obj = get_object_or_404(MyModel, pk=1) This example is equivalent to:: @@ -196,7 +198,7 @@ This example is equivalent to:: def my_view(request): try: - my_object = MyModel.objects.get(pk=1) + obj = MyModel.objects.get(pk=1) except MyModel.DoesNotExist: raise Http404("No MyModel matches the given query.") diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index 59b3b698f11b..ed7257d84736 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -6,21 +6,17 @@ A clean, elegant URL scheme is an important detail in a high-quality Web application. Django lets you design URLs however you want, with no framework limitations. -There's no ``.php`` or ``.cgi`` required, and certainly none of that -``0,2097,1-1-1928,00`` nonsense. - See `Cool URIs don't change`_, by World Wide Web creator Tim Berners-Lee, for excellent arguments on why URLs should be clean and usable. -.. _Cool URIs don't change: http://www.w3.org/Provider/Style/URI +.. _Cool URIs don't change: https://www.w3.org/Provider/Style/URI Overview ======== To design URLs for an app, you create a Python module informally called a **URLconf** (URL configuration). This module is pure Python code and is a -simple mapping between URL patterns (simple regular expressions) to Python -functions (your views). +mapping between URL path expressions to Python functions (your views). This mapping can be as short or as long as needed. It can reference other mappings. And, because it's pure Python code, it can be constructed @@ -38,32 +34,33 @@ How Django processes a request When a user requests a page from your Django-powered site, this is the algorithm the system follows to determine which Python code to execute: -1. Django determines the root URLconf module to use. Ordinarily, +#. Django determines the root URLconf module to use. Ordinarily, this is the value of the :setting:`ROOT_URLCONF` setting, but if the incoming ``HttpRequest`` object has a :attr:`~django.http.HttpRequest.urlconf` attribute (set by middleware), its value will be used in place of the :setting:`ROOT_URLCONF` setting. -2. Django loads that Python module and looks for the variable - ``urlpatterns``. This should be a Python list of :func:`django.conf.urls.url` - instances. +#. Django loads that Python module and looks for the variable + ``urlpatterns``. This should be a :term:`sequence` of + :func:`django.urls.path` and/or :func:`django.urls.re_path` instances. -3. Django runs through each URL pattern, in order, and stops at the first +#. Django runs through each URL pattern, in order, and stops at the first one that matches the requested URL. -4. Once one of the regexes matches, Django imports and calls the given view, - which is a simple Python function (or a :doc:`class-based view +#. Once one of the URL patterns matches, Django imports and calls the given + view, which is a simple Python function (or a :doc:`class-based view `). The view gets passed the following arguments: * An instance of :class:`~django.http.HttpRequest`. - * If the matched regular expression returned no named groups, then the + * If the matched URL pattern returned no named groups, then the matches from the regular expression are provided as positional arguments. - * The keyword arguments are made up of any named groups matched by the - regular expression, overridden by any arguments specified in the optional - ``kwargs`` argument to :func:`django.conf.urls.url`. + * The keyword arguments are made up of any named parts matched by the + path expression, overridden by any arguments specified in the optional + ``kwargs`` argument to :func:`django.urls.path` or + :func:`django.urls.re_path`. -5. If no regex matches, or if an exception is raised during any +#. If no URL pattern matches, or if an exception is raised during any point in this process, Django invokes an appropriate error-handling view. See `Error handling`_ below. @@ -72,36 +69,33 @@ Example Here's a sample URLconf:: - from django.conf.urls import url + from django.urls import path from . import views urlpatterns = [ - url(r'^articles/2003/$', views.special_case_2003), - url(r'^articles/([0-9]{4})/$', views.year_archive), - url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive), - url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail), + path('articles/2003/', views.special_case_2003), + path('articles//', views.year_archive), + path('articles///', views.month_archive), + path('articles////', views.article_detail), ] Notes: -* To capture a value from the URL, just put parenthesis around it. +* To capture a value from the URL, use angle brackets. -* There's no need to add a leading slash, because every URL has that. For - example, it's ``^articles``, not ``^/articles``. +* Captured values can optionally include a converter type. For example, use + ```` to capture an integer parameter. If a converter isn't included, + any string, excluding a ``/`` character, is matched. -* The ``'r'`` in front of each regular expression string is optional but - recommended. It tells Python that a string is "raw" -- that nothing in - the string should be escaped. See `Dive Into Python's explanation`_. +* There's no need to add a leading slash, because every URL has that. For + example, it's ``articles``, not ``/articles``. Example requests: * A request to ``/articles/2005/03/`` would match the third entry in the list. Django would call the function - ``views.month_archive(request, '2005', '03')``. - -* ``/articles/2005/3/`` would not match any URL patterns, because the - third entry in the list requires two digits for the month. + ``views.month_archive(request, year=2005, month=3)``. * ``/articles/2003/`` would match the first pattern in the list, not the second one, because the patterns are tested in order, and the first one @@ -112,66 +106,166 @@ Example requests: * ``/articles/2003`` would not match any of these patterns, because each pattern requires that the URL end with a slash. -* ``/articles/2003/03/03/`` would match the final pattern. Django would call - the function ``views.article_detail(request, '2003', '03', '03')``. +* ``/articles/2003/03/building-a-django-site/`` would match the final + pattern. Django would call the function + ``views.article_detail(request, year=2003, month=3, slug="building-a-django-site")``. + +Path converters +=============== + +The following path converters are available by default: + +* ``str`` - Matches any non-empty string, excluding the path separator, ``'/'``. + This is the default if a converter isn't included in the expression. + +* ``int`` - Matches zero or any positive integer. Returns an `int`. + +* ``slug`` - Matches any slug string consisting of ASCII letters or numbers, + plus the hyphen and underscore characters. For example, + ``building-your-1st-django-site``. + +* ``uuid`` - Matches a formatted UUID. To prevent multiple URLs from mapping to + the same page, dashes must be included and letters must be lowercase. For + example, ``075194d3-6885-417e-a8a8-6c931e272f00``. Returns a + :class:`~uuid.UUID` instance. + +* ``path`` - Matches any non-empty string, including the path separator, + ``'/'``. This allows you to match against a complete URL path rather than + just a segment of a URL path as with ``str``. + +.. _registering-custom-path-converters: + +Registering custom path converters +================================== + +For more complex matching requirements, you can define your own path converters. + +A converter is a class that includes the following: + +* A ``regex`` class attribute, as a string. + +* A ``to_python(self, value)`` method, which handles converting the matched + string into the type that should be passed to the view function. It should + raise ``ValueError`` if it can't convert the given value. A ``ValueError`` is + interpreted as no match and as a consequence a 404 response is sent to the + user. + +* A ``to_url(self, value)`` method, which handles converting the Python type + into a string to be used in the URL. + +For example:: + + class FourDigitYearConverter: + regex = '[0-9]{4}' + + def to_python(self, value): + return int(value) + + def to_url(self, value): + return '%04d' % value + +Register custom converter classes in your URLconf using +:func:`~django.urls.register_converter`:: + + from django.urls import path, register_converter -.. _Dive Into Python's explanation: http://www.diveintopython.net/regular_expressions/street_addresses.html#re.matching.2.3 + from . import converters, views -Named groups -============ + register_converter(converters.FourDigitYearConverter, 'yyyy') -The above example used simple, *non-named* regular-expression groups (via -parenthesis) to capture bits of the URL and pass them as *positional* arguments -to a view. In more advanced usage, it's possible to use *named* -regular-expression groups to capture URL bits and pass them as *keyword* -arguments to a view. + urlpatterns = [ + path('articles/2003/', views.special_case_2003), + path('articles//', views.year_archive), + ... + ] + +Using regular expressions +========================= -In Python regular expressions, the syntax for named regular-expression groups +If the paths and converters syntax isn't sufficient for defining your URL +patterns, you can also use regular expressions. To do so, use +:func:`~django.urls.re_path` instead of :func:`~django.urls.path`. + +In Python regular expressions, the syntax for named regular expression groups is ``(?Ppattern)``, where ``name`` is the name of the group and ``pattern`` is some pattern to match. -Here's the above example URLconf, rewritten to use named groups:: +Here's the example URLconf from earlier, rewritten using regular expressions:: - from django.conf.urls import url + from django.urls import path, re_path from . import views urlpatterns = [ - url(r'^articles/2003/$', views.special_case_2003), - url(r'^articles/(?P[0-9]{4})/$', views.year_archive), - url(r'^articles/(?P[0-9]{4})/(?P[0-9]{2})/$', views.month_archive), - url(r'^articles/(?P[0-9]{4})/(?P[0-9]{2})/(?P[0-9]{2})/$', views.article_detail), + path('articles/2003/', views.special_case_2003), + re_path(r'^articles/(?P[0-9]{4})/$', views.year_archive), + re_path(r'^articles/(?P[0-9]{4})/(?P[0-9]{2})/$', views.month_archive), + re_path(r'^articles/(?P[0-9]{4})/(?P[0-9]{2})/(?P[\w-]+)/$', views.article_detail), ] -This accomplishes exactly the same thing as the previous example, with one -subtle difference: The captured values are passed to view functions as keyword -arguments rather than positional arguments. For example: +This accomplishes roughly the same thing as the previous example, except: -* A request to ``/articles/2005/03/`` would call the function - ``views.month_archive(request, year='2005', month='03')``, instead - of ``views.month_archive(request, '2005', '03')``. +* The exact URLs that will match are slightly more constrained. For example, + the year 10000 will no longer match since the year integers are constrained + to be exactly four digits long. -* A request to ``/articles/2003/03/03/`` would call the function - ``views.article_detail(request, year='2003', month='03', day='03')``. +* Each captured argument is sent to the view as a string, regardless of what + sort of match the regular expression makes. -In practice, this means your URLconfs are slightly more explicit and less prone -to argument-order bugs -- and you can reorder the arguments in your views' -function definitions. Of course, these benefits come at the cost of brevity; -some developers find the named-group syntax ugly and too verbose. +When switching from using :func:`~django.urls.path` to +:func:`~django.urls.re_path` or vice versa, it's particularly important to be +aware that the type of the view arguments may change, and so you may need to +adapt your views. -The matching/grouping algorithm -------------------------------- +Using unnamed regular expression groups +--------------------------------------- -Here's the algorithm the URLconf parser follows, with respect to named groups -vs. non-named groups in a regular expression: +As well as the named group syntax, e.g. ``(?P[0-9]{4})``, you can +also use the shorter unnamed group, e.g. ``([0-9]{4})``. -1. If there are any named arguments, it will use those, ignoring non-named - arguments. +This usage isn't particularly recommended as it makes it easier to accidentally +introduce errors between the intended meaning of a match and the arguments +of the view. -2. Otherwise, it will pass all non-named arguments as positional arguments. +In either case, using only one style within a given regex is recommended. When +both styles are mixed, any unnamed groups are ignored and only named groups are +passed to the view function. -In both cases, any extra keyword arguments that have been given as per `Passing -extra options to view functions`_ (below) will also be passed to the view. +Nested arguments +---------------- + +Regular expressions allow nested arguments, and Django will resolve them and +pass them to the view. When reversing, Django will try to fill in all outer +captured arguments, ignoring any nested captured arguments. Consider the +following URL patterns which optionally take a page argument:: + + from django.urls import re_path + + urlpatterns = [ + re_path(r'^blog/(page-(\d+)/)?$', blog_articles), # bad + re_path(r'^comments/(?:page-(?P\d+)/)?$', comments), # good + ] + +Both patterns use nested arguments and will resolve: for example, +``blog/page-2/`` will result in a match to ``blog_articles`` with two +positional arguments: ``page-2/`` and ``2``. The second pattern for +``comments`` will match ``comments/page-2/`` with keyword argument +``page_number`` set to 2. The outer argument in this case is a non-capturing +argument ``(?:...)``. + +The ``blog_articles`` view needs the outermost captured argument to be reversed, +``page-2/`` or no arguments in this case, while ``comments`` can be reversed +with either no arguments or a value for ``page_number``. + +Nested captured arguments create a strong coupling between the view arguments +and the URL as illustrated by ``blog_articles``: the view receives part of the +URL (``page-2/``) instead of only the value the view is interested in. This +coupling is even more pronounced when reversing, since to reverse the view we +need to pass the piece of URL instead of the page number. + +As a rule of thumb, only capture the values the view needs to work with and +use non-capturing arguments when the regular expression needs an argument but +the view ignores it. What the URLconf searches against ================================= @@ -189,18 +283,6 @@ The URLconf doesn't look at the request method. In other words, all request methods -- ``POST``, ``GET``, ``HEAD``, etc. -- will be routed to the same function for the same URL. -Captured arguments are always strings -===================================== - -Each captured argument is sent to the view as a plain Python string, regardless -of what sort of match the regular expression makes. For example, in this -URLconf line:: - - url(r'^articles/(?P[0-9]{4})/$', views.year_archive), - -...the ``year`` argument passed to ``views.year_archive()`` will be a string, - not an integer, even though the ``[0-9]{4}`` will only match integer strings. - Specifying defaults for view arguments ====================================== @@ -208,25 +290,25 @@ A convenient trick is to specify default parameters for your views' arguments. Here's an example URLconf and view:: # URLconf - from django.conf.urls import url + from django.urls import path from . import views urlpatterns = [ - url(r'^blog/$', views.page), - url(r'^blog/page(?P[0-9]+)/$', views.page), + path('blog/', views.page), + path('blog/page/', views.page), ] # View (in blog/views.py) - def page(request, num="1"): + def page(request, num=1): # Output the appropriate page of blog entries, according to num. ... In the above example, both URL patterns point to the same view -- ``views.page`` -- but the first pattern doesn't capture anything from the URL. If the first pattern matches, the ``page()`` function will use its -default argument for ``num``, ``"1"``. If the second pattern matches, -``page()`` will use whatever ``num`` value was captured by the regex. +default argument for ``num``, ``1``. If the second pattern matches, +``page()`` will use whatever ``num`` value was captured. Performance =========== @@ -237,14 +319,14 @@ accessed. This makes the system blazingly fast. Syntax of the ``urlpatterns`` variable ====================================== -``urlpatterns`` should be a Python list of :func:`~django.conf.urls.url` -instances. +``urlpatterns`` should be a :term:`sequence` of :func:`~django.urls.path` +and/or :func:`~django.urls.re_path` instances. Error handling ============== -When Django can't find a regex matching the requested URL, or when an -exception is raised, Django will invoke an error-handling view. +When Django can't find a match for the requested URL, or when an exception is +raised, Django invokes an error-handling view. The views to use for these cases are specified by four variables. Their default values should suffice for most projects, but further customization is @@ -277,39 +359,37 @@ essentially "roots" a set of URLs below other ones. For example, here's an excerpt of the URLconf for the `Django website`_ itself. It includes a number of other URLconfs:: - from django.conf.urls import include, url + from django.urls import include, path urlpatterns = [ # ... snip ... - url(r'^community/', include('django_website.aggregator.urls')), - url(r'^contact/', include('django_website.contact.urls')), + path('community/', include('aggregator.urls')), + path('contact/', include('contact.urls')), # ... snip ... ] -Note that the regular expressions in this example don't have a ``$`` -(end-of-string match character) but do include a trailing slash. Whenever -Django encounters ``include()`` (:func:`django.conf.urls.include()`), it chops -off whatever part of the URL matched up to that point and sends the remaining +Whenever Django encounters :func:`~django.urls.include()`, it chops off +whatever part of the URL matched up to that point and sends the remaining string to the included URLconf for further processing. Another possibility is to include additional URL patterns by using a list of -:func:`~django.conf.urls.url` instances. For example, consider this URLconf:: +:func:`~django.urls.path` instances. For example, consider this URLconf:: - from django.conf.urls import include, url + from django.urls import include, path from apps.main import views as main_views from credit import views as credit_views extra_patterns = [ - url(r'^reports/$', credit_views.report), - url(r'^reports/(?P[0-9]+)/$', credit_views.report), - url(r'^charge/$', credit_views.charge), + path('reports/', credit_views.report), + path('reports//', credit_views.report), + path('charge/', credit_views.charge), ] urlpatterns = [ - url(r'^$', main_views.homepage), - url(r'^help/', include('apps.help.urls')), - url(r'^credit/', include(extra_patterns)), + path('', main_views.homepage), + path('help/', include('apps.help.urls')), + path('credit/', include(extra_patterns)), ] In this example, the ``/credit/reports/`` URL will be handled by the @@ -318,28 +398,28 @@ In this example, the ``/credit/reports/`` URL will be handled by the This can be used to remove redundancy from URLconfs where a single pattern prefix is used repeatedly. For example, consider this URLconf:: - from django.conf.urls import url + from django.urls import path from . import views urlpatterns = [ - url(r'^(?P[\w-]+)-(?P\w+)/history/$', views.history), - url(r'^(?P[\w-]+)-(?P\w+)/edit/$', views.edit), - url(r'^(?P[\w-]+)-(?P\w+)/discuss/$', views.discuss), - url(r'^(?P[\w-]+)-(?P\w+)/permissions/$', views.permissions), + path('-/history/', views.history), + path('-/edit/', views.edit), + path('-/discuss/', views.discuss), + path('-/permissions/', views.permissions), ] We can improve this by stating the common path prefix only once and grouping the suffixes that differ:: - from django.conf.urls import include, url + from django.urls import include, path from . import views urlpatterns = [ - url(r'^(?P[\w-]+)-(?P\w+)/', include([ - url(r'^history/$', views.history), - url(r'^edit/$', views.edit), - url(r'^discuss/$', views.discuss), - url(r'^permissions/$', views.permissions), + path('-/', include([ + path('history/', views.history), + path('edit/', views.edit), + path('discuss/', views.discuss), + path('permissions/', views.permissions), ])), ] @@ -352,60 +432,24 @@ An included URLconf receives any captured parameters from parent URLconfs, so the following example is valid:: # In settings/urls/main.py - from django.conf.urls import include, url + from django.urls import include, path urlpatterns = [ - url(r'^(?P\w+)/blog/', include('foo.urls.blog')), + path('/blog/', include('foo.urls.blog')), ] # In foo/urls/blog.py - from django.conf.urls import url + from django.urls import path from . import views urlpatterns = [ - url(r'^$', views.blog.index), - url(r'^archive/$', views.blog.archive), + path('', views.blog.index), + path('archive/', views.blog.archive), ] In the above example, the captured ``"username"`` variable is passed to the included URLconf, as expected. -Nested arguments -================ - -Regular expressions allow nested arguments, and Django will resolve them and -pass them to the view. When reversing, Django will try to fill in all outer -captured arguments, ignoring any nested captured arguments. Consider the -following URL patterns which optionally take a page argument:: - - from django.conf.urls import url - - urlpatterns = [ - url(r'blog/(page-(\d+)/)?$', blog_articles), # bad - url(r'comments/(?:page-(?P\d+)/)?$', comments), # good - ] - -Both patterns use nested arguments and will resolve: for example, -``blog/page-2/`` will result in a match to ``blog_articles`` with two -positional arguments: ``page-2/`` and ``2``. The second pattern for -``comments`` will match ``comments/page-2/`` with keyword argument -``page_number`` set to 2. The outer argument in this case is a non-capturing -argument ``(?:...)``. - -The ``blog_articles`` view needs the outermost captured argument to be reversed, -``page-2/`` or no arguments in this case, while ``comments`` can be reversed -with either no arguments or a value for ``page_number``. - -Nested captured arguments create a strong coupling between the view arguments -and the URL as illustrated by ``blog_articles``: the view receives part of the -URL (``page-2/``) instead of only the value the view is interested in. This -coupling is even more pronounced when reversing, since to reverse the view we -need to pass the piece of URL instead of the page number. - -As a rule of thumb, only capture the values the view needs to work with and -use non-capturing arguments when the regular expression needs an argument but -the view ignores it. - .. _views-extra-options: Passing extra options to view functions @@ -414,21 +458,21 @@ Passing extra options to view functions URLconfs have a hook that lets you pass extra arguments to your view functions, as a Python dictionary. -The :func:`django.conf.urls.url` function can take an optional third argument +The :func:`~django.urls.path` function can take an optional third argument which should be a dictionary of extra keyword arguments to pass to the view function. For example:: - from django.conf.urls import url + from django.urls import path from . import views urlpatterns = [ - url(r'^blog/(?P[0-9]{4})/$', views.year_archive, {'foo': 'bar'}), + path('blog//', views.year_archive, {'foo': 'bar'}), ] In this example, for a request to ``/blog/2005/``, Django will call -``views.year_archive(request, year='2005', foo='bar')``. +``views.year_archive(request, year=2005, foo='bar')``. This technique is used in the :doc:`syndication framework ` to pass metadata and @@ -444,46 +488,45 @@ options to views. Passing extra options to ``include()`` -------------------------------------- -Similarly, you can pass extra options to :func:`~django.conf.urls.include`. -When you pass extra options to ``include()``, *each* line in the included -URLconf will be passed the extra options. +Similarly, you can pass extra options to :func:`~django.urls.include` and +each line in the included URLconf will be passed the extra options. For example, these two URLconf sets are functionally identical: Set one:: # main.py - from django.conf.urls import include, url + from django.urls import include, path urlpatterns = [ - url(r'^blog/', include('inner'), {'blogid': 3}), + path('blog/', include('inner'), {'blog_id': 3}), ] # inner.py - from django.conf.urls import url + from django.urls import path from mysite import views urlpatterns = [ - url(r'^archive/$', views.archive), - url(r'^about/$', views.about), + path('archive/', views.archive), + path('about/', views.about), ] Set two:: # main.py - from django.conf.urls import include, url + from django.urls import include, path from mysite import views urlpatterns = [ - url(r'^blog/', include('inner')), + path('blog/', include('inner')), ] # inner.py - from django.conf.urls import url + from django.urls import path urlpatterns = [ - url(r'^archive/$', views.archive, {'blogid': 3}), - url(r'^about/$', views.about, {'blogid': 3}), + path('archive/', views.archive, {'blog_id': 3}), + path('about/', views.about, {'blog_id': 3}), ] Note that extra options will *always* be passed to *every* line in the included @@ -543,18 +586,18 @@ Examples Consider again this URLconf entry:: - from django.conf.urls import url + from django.urls import path from . import views urlpatterns = [ #... - url(r'^articles/([0-9]{4})/$', views.year_archive, name='news-year-archive'), + path('articles//', views.year_archive, name='news-year-archive'), #... ] According to this design, the URL for the archive corresponding to year *nnnn* -is ``/articles/nnnn/``. +is ``/articles//``. You can obtain these in template code by using: @@ -570,8 +613,8 @@ You can obtain these in template code by using: Or in Python code:: - from django.urls import reverse from django.http import HttpResponseRedirect + from django.urls import reverse def redirect_to_year(request): # ... @@ -597,15 +640,28 @@ In order to perform URL reversing, you'll need to use **named URL patterns** as done in the examples above. The string used for the URL name can contain any characters you like. You are not restricted to valid Python names. -When you name your URL patterns, make sure you use names that are unlikely -to clash with any other application's choice of names. If you call your URL -pattern ``comment``, and another application does the same thing, there's -no guarantee which URL will be inserted into your template when you use -this name. +When naming URL patterns, choose names that are unlikely to clash with other +applications' choice of names. If you call your URL pattern ``comment`` +and another application does the same thing, the URL that +:func:`~django.urls.reverse()` finds depends on whichever pattern is last in +your project's ``urlpatterns`` list. Putting a prefix on your URL names, perhaps derived from the application -name, will decrease the chances of collision. We recommend something like -``myapp-comment`` instead of ``comment``. +name (such as ``myapp-comment`` instead of ``comment``), decreases the chance +of collision. + +You can deliberately choose the *same URL name* as another application if you +want to override a view. For example, a common use case is to override the +:class:`~django.contrib.auth.views.LoginView`. Parts of Django and most +third-party apps assume that this view has a URL pattern with the name +``login``. If you have a custom login view and give its URL the name ``login``, +:func:`~django.urls.reverse()` will find your custom view as long as it's in +``urlpatterns`` after ``django.contrib.auth.urls`` is included (if that's +included at all). + +You may also use the same name for multiple URL patterns if they differ in +their arguments. In addition to the URL name, :func:`~django.urls.reverse()` +matches the number of arguments and the names of the keyword arguments. .. _topics-http-defining-url-namespaces: @@ -664,11 +720,11 @@ Reversing namespaced URLs When given a namespaced URL (e.g. ``'polls:index'``) to resolve, Django splits the fully qualified name into parts and then tries the following lookup: -1. First, Django looks for a matching :term:`application namespace` (in this +#. First, Django looks for a matching :term:`application namespace` (in this example, ``'polls'``). This will yield a list of instances of that application. -2. If there is a current application defined, Django finds and returns the URL +#. If there is a current application defined, Django finds and returns the URL resolver for that instance. The current application can be specified with the ``current_app`` argument to the :func:`~django.urls.reverse()` function. @@ -679,15 +735,15 @@ the fully qualified name into parts and then tries the following lookup: setting the current application on the :attr:`request.current_app ` attribute. -3. If there is no current application. Django looks for a default +#. If there is no current application, Django looks for a default application instance. The default application instance is the instance that has an :term:`instance namespace` matching the :term:`application namespace` (in this example, an instance of ``polls`` called ``'polls'``). -4. If there is no default application instance, Django will pick the last +#. If there is no default application instance, Django will pick the last deployed instance of the application, whatever its instance name may be. -5. If the provided namespace doesn't match an :term:`application namespace` in +#. If the provided namespace doesn't match an :term:`application namespace` in step 1, Django will attempt a direct lookup of the namespace as an :term:`instance namespace`. @@ -704,27 +760,27 @@ and one called ``'publisher-polls'``. Assume we have enhanced that application so that it takes the instance namespace into consideration when creating and displaying polls. -.. snippet:: - :filename: urls.py +.. code-block:: python + :caption: urls.py - from django.conf.urls import include, url + from django.urls import include, path urlpatterns = [ - url(r'^author-polls/', include('polls.urls', namespace='author-polls')), - url(r'^publisher-polls/', include('polls.urls', namespace='publisher-polls')), + path('author-polls/', include('polls.urls', namespace='author-polls')), + path('publisher-polls/', include('polls.urls', namespace='publisher-polls')), ] -.. snippet:: - :filename: polls/urls.py +.. code-block:: python + :caption: polls/urls.py - from django.conf.urls import url + from django.urls import path from . import views app_name = 'polls' urlpatterns = [ - url(r'^$', views.IndexView.as_view(), name='index'), - url(r'^(?P\d+)/$', views.DetailView.as_view(), name='detail'), + path('', views.IndexView.as_view(), name='index'), + path('/', views.DetailView.as_view(), name='detail'), ... ] @@ -770,60 +826,61 @@ Application namespaces of included URLconfs can be specified in two ways. Firstly, you can set an ``app_name`` attribute in the included URLconf module, at the same level as the ``urlpatterns`` attribute. You have to pass the actual -module, or a string reference to the module, to -:func:`~django.conf.urls.include`, not the list of ``urlpatterns`` itself. +module, or a string reference to the module, to :func:`~django.urls.include`, +not the list of ``urlpatterns`` itself. -.. snippet:: - :filename: polls/urls.py +.. code-block:: python + :caption: polls/urls.py - from django.conf.urls import url + from django.urls import path from . import views app_name = 'polls' urlpatterns = [ - url(r'^$', views.IndexView.as_view(), name='index'), - url(r'^(?P\d+)/$', views.DetailView.as_view(), name='detail'), + path('', views.IndexView.as_view(), name='index'), + path('/', views.DetailView.as_view(), name='detail'), ... ] -.. snippet:: - :filename: urls.py +.. code-block:: python + :caption: urls.py - from django.conf.urls import include, url + from django.urls import include, path urlpatterns = [ - url(r'^polls/', include('polls.urls')), + path('polls/', include('polls.urls')), ] The URLs defined in ``polls.urls`` will have an application namespace ``polls``. Secondly, you can include an object that contains embedded namespace data. If -you ``include()`` a list of :func:`~django.conf.urls.url` instances, -the URLs contained in that object will be added to the global namespace. -However, you can also ``include()`` a 2-tuple containing:: +you ``include()`` a list of :func:`~django.urls.path` or +:func:`~django.urls.re_path` instances, the URLs contained in that object +will be added to the global namespace. However, you can also ``include()`` a +2-tuple containing:: - (, ) + (, ) For example:: - from django.conf.urls import include, url + from django.urls import include, path from . import views polls_patterns = ([ - url(r'^$', views.IndexView.as_view(), name='index'), - url(r'^(?P\d+)/$', views.DetailView.as_view(), name='detail'), + path('', views.IndexView.as_view(), name='index'), + path('/', views.DetailView.as_view(), name='detail'), ], 'polls') urlpatterns = [ - url(r'^polls/', include(polls_patterns)), + path('polls/', include(polls_patterns)), ] This will include the nominated URL patterns into the given application namespace. The instance namespace can be specified using the ``namespace`` argument to -:func:`~django.conf.urls.include`. If the instance namespace is not specified, +:func:`~django.urls.include`. If the instance namespace is not specified, it will default to the included URLconf's application namespace. This means it will also be the default instance for that namespace. diff --git a/docs/topics/http/views.txt b/docs/topics/http/views.txt index 3c7c5e50182e..baacd233b5f5 100644 --- a/docs/topics/http/views.txt +++ b/docs/topics/http/views.txt @@ -166,3 +166,39 @@ The :func:`~django.views.defaults.bad_request` view is overridden by Use the :setting:`CSRF_FAILURE_VIEW` setting to override the CSRF error view. + +Testing custom error views +-------------------------- + +To test the response of a custom error handler, raise the appropriate exception +in a test view. For example:: + + from django.core.exceptions import PermissionDenied + from django.http import HttpResponse + from django.test import SimpleTestCase, override_settings + from django.urls import path + + + def response_error_handler(request, exception=None): + return HttpResponse('Error handler content', status=403) + + + def permission_denied_view(request): + raise PermissionDenied + + + urlpatterns = [ + path('403/', permission_denied_view), + ] + + handler403 = response_error_handler + + + # ROOT_URLCONF must specify the module that contains handler403 = ... + @override_settings(ROOT_URLCONF=__name__) + class CustomErrorHandlerTests(SimpleTestCase): + + def test_handler_renders_template_response(self): + response = self.client.get('/403/') + # Make assertions on the response here. For example: + self.assertContains(response, 'Error handler content', status_code=403) diff --git a/docs/topics/i18n/formatting.txt b/docs/topics/i18n/formatting.txt index 248d6b0d23a9..4a0ce6d3e843 100644 --- a/docs/topics/i18n/formatting.txt +++ b/docs/topics/i18n/formatting.txt @@ -177,8 +177,6 @@ To customize the English formats, a structure like this would be needed:: where :file:`formats.py` contains custom format definitions. For example:: - from __future__ import unicode_literals - THOUSAND_SEPARATOR = '\xa0' to use a non-breaking space (Unicode ``00A0``) as a thousand separator, diff --git a/docs/topics/i18n/index.txt b/docs/topics/i18n/index.txt index 366ff97bb9e6..5aad6590335d 100644 --- a/docs/topics/i18n/index.txt +++ b/docs/topics/i18n/index.txt @@ -48,7 +48,7 @@ here's a simplified definition: More details can be found in the `W3C Web Internationalization FAQ`_, the `Wikipedia article`_ or the `GNU gettext documentation`_. -.. _W3C Web Internationalization FAQ: http://www.w3.org/International/questions/qa-i18n +.. _W3C Web Internationalization FAQ: https://www.w3.org/International/questions/qa-i18n .. _GNU gettext documentation: https://www.gnu.org/software/gettext/manual/gettext.html#Concepts .. _Wikipedia article: https://en.wikipedia.org/wiki/Internationalization_and_localization @@ -67,14 +67,14 @@ Here are some other terms that will help us to handle a common language: A locale name, either a language specification of the form ``ll`` or a combined language and country specification of the form ``ll_CC``. Examples: ``it``, ``de_AT``, ``es``, ``pt_BR``. The language part is - always in lower case and the country part in upper case. The separator - is an underscore. + always in lowercase and the country part in upper case. The separator is + an underscore. language code Represents the name of a language. Browsers send the names of the languages they accept in the ``Accept-Language`` HTTP header using this format. Examples: ``it``, ``de-at``, ``es``, ``pt-br``. Language codes - are generally represented in lower-case, but the HTTP ``Accept-Language`` + are generally represented in lowercase, but the HTTP ``Accept-Language`` header is case-insensitive. The separator is a dash. message file diff --git a/docs/topics/i18n/timezones.txt b/docs/topics/i18n/timezones.txt index ecabefb421b8..1c3c2bac2882 100644 --- a/docs/topics/i18n/timezones.txt +++ b/docs/topics/i18n/timezones.txt @@ -29,10 +29,6 @@ Time zone support is disabled by default. To enable it, set :setting:`USE_TZ = True ` in your settings file. Time zone support uses pytz_, which is installed when you install Django. -.. versionchanged:: 1.11 - - Older versions don't require ``pytz`` or install it automatically. - .. note:: The default :file:`settings.py` file created by :djadmin:`django-admin @@ -173,15 +169,18 @@ Add the following middleware to :setting:`MIDDLEWARE`:: import pytz from django.utils import timezone - from django.utils.deprecation import MiddlewareMixin - class TimezoneMiddleware(MiddlewareMixin): - def process_request(self, request): + class TimezoneMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): tzname = request.session.get('django_timezone') if tzname: timezone.activate(pytz.timezone(tzname)) else: timezone.deactivate() + return self.get_response(request) Create a view that can set the current timezone:: @@ -208,7 +207,7 @@ Include a form in ``template.html`` that will ``POST`` to this view: {% endfor %} - + .. _time-zones-in-forms: @@ -466,17 +465,12 @@ FAQ Setup ----- -1. **I don't need multiple time zones. Should I enable time zone support?** +#. **I don't need multiple time zones. Should I enable time zone support?** Yes. When time zone support is enabled, Django uses a more accurate model of local time. This shields you from subtle and unreproducible bugs around Daylight Saving Time (DST) transitions. - In this regard, time zones are comparable to ``unicode`` in Python. At first - it's hard. You get encoding and decoding errors. Then you learn the rules. - And some problems disappear -- you never get mangled output again when your - application receives non-ASCII input. - When you enable time zone support, you'll encounter some errors because you're using naive datetimes where Django expects aware datetimes. Such errors show up when running tests and they're easy to fix. You'll quickly @@ -490,7 +484,7 @@ Setup For these reasons, time zone support is enabled by default in new projects, and you should keep it unless you have a very good reason not to. -2. **I've enabled time zone support. Am I safe?** +#. **I've enabled time zone support. Am I safe?** Maybe. You're better protected from DST-related bugs, but you can still shoot yourself in the foot by carelessly turning naive datetimes into aware @@ -517,7 +511,7 @@ Setup one year is 2011-02-28 or 2011-03-01, which depends on your business requirements.) -3. **How do I interact with a database that stores datetimes in local time?** +#. **How do I interact with a database that stores datetimes in local time?** Set the :setting:`TIME_ZONE ` option to the appropriate time zone for this database in the :setting:`DATABASES` setting. @@ -528,7 +522,7 @@ Setup Troubleshooting --------------- -1. **My application crashes with** ``TypeError: can't compare offset-naive`` +#. **My application crashes with** ``TypeError: can't compare offset-naive`` ``and offset-aware datetimes`` **-- what's wrong?** Let's reproduce this error by comparing a naive and an aware datetime:: @@ -560,7 +554,7 @@ Troubleshooting datetime when ``USE_TZ = True``. You can add or subtract :class:`datetime.timedelta` as needed. -2. **I see lots of** ``RuntimeWarning: DateTimeField received a naive +#. **I see lots of** ``RuntimeWarning: DateTimeField received a naive datetime`` ``(YYYY-MM-DD HH:MM:SS)`` ``while time zone support is active`` **-- is that bad?** @@ -573,7 +567,7 @@ Troubleshooting In the meantime, for backwards compatibility, the datetime is considered to be in the default time zone, which is generally what you expect. -3. ``now.date()`` **is yesterday! (or tomorrow)** +#. ``now.date()`` **is yesterday! (or tomorrow)** If you've always used naive datetimes, you probably believe that you can convert a datetime to a date by calling its :meth:`~datetime.datetime.date` @@ -632,7 +626,7 @@ Troubleshooting >>> local.date() datetime.date(2012, 3, 3) -4. **I get an error** "``Are time zone definitions for your database +#. **I get an error** "``Are time zone definitions for your database installed?``" If you are using MySQL, see the :ref:`mysql-time-zone-definitions` section @@ -641,7 +635,7 @@ Troubleshooting Usage ----- -1. **I have a string** ``"2012-02-21 10:28:45"`` **and I know it's in the** +#. **I have a string** ``"2012-02-21 10:28:45"`` **and I know it's in the** ``"Europe/Helsinki"`` **time zone. How do I turn that into an aware datetime?** @@ -658,7 +652,7 @@ Usage documentation of pytz contains `more examples`_. You should review it before attempting to manipulate aware datetimes. -2. **How can I obtain the local time in the current time zone?** +#. **How can I obtain the local time in the current time zone?** Well, the first question is, do you really need to? @@ -681,7 +675,7 @@ Usage In this example, the current time zone is ``"Europe/Paris"``. -3. **How can I see all available time zones?** +#. **How can I see all available time zones?** pytz_ provides helpers_, including a list of current time zones and a list of all available time zones -- some of which are only of historical diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index aa85aff90711..24f42a1a3cfc 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -2,7 +2,7 @@ Translation =========== -.. module:: django.utils.translation +.. currentmodule:: django.utils.translation Overview ======== @@ -49,26 +49,29 @@ Standard translation -------------------- Specify a translation string by using the function -:func:`~django.utils.translation.ugettext`. It's convention to import this +:func:`~django.utils.translation.gettext`. It's convention to import this as a shorter alias, ``_``, to save typing. +.. note:: + The ``u`` prefixing of ``gettext`` functions was originally to distinguish + usage between unicode strings and bytestrings on Python 2. For code that + supports only Python 3, they can be used interchangeably. A deprecation for + the prefixed functions may happen in a future Django release. + .. note:: Python's standard library ``gettext`` module installs ``_()`` into the global namespace, as an alias for ``gettext()``. In Django, we have chosen not to follow this practice, for a couple of reasons: - 1. For international character set (Unicode) support, - :func:`~django.utils.translation.ugettext` is more useful than - ``gettext()``. Sometimes, you should be using - :func:`~django.utils.translation.ugettext_lazy` as the default - translation method for a particular file. Without ``_()`` in the - global namespace, the developer has to think about which is the + #. Sometimes, you should use :func:`~django.utils.translation.gettext_lazy` + as the default translation method for a particular file. Without ``_()`` + in the global namespace, the developer has to think about which is the most appropriate translation function. - 2. The underscore character (``_``) is used to represent "the previous + #. The underscore character (``_``) is used to represent "the previous result" in Python's interactive shell and doctest tests. Installing a global ``_()`` function causes interference. Explicitly importing - ``ugettext()`` as ``_()`` avoids this problem. + ``gettext()`` as ``_()`` avoids this problem. .. admonition:: What functions may be aliased as ``_``? @@ -77,14 +80,12 @@ as a shorter alias, ``_``, to save typing. * :func:`~django.utils.translation.gettext` * :func:`~django.utils.translation.gettext_lazy` - * :func:`~django.utils.translation.ugettext` - * :func:`~django.utils.translation.ugettext_lazy` In this example, the text ``"Welcome to my site."`` is marked as a translation string:: - from django.utils.translation import ugettext as _ from django.http import HttpResponse + from django.utils.translation import gettext as _ def my_view(request): output = _("Welcome to my site.") @@ -93,11 +94,11 @@ string:: Obviously, you could code this without using the alias. This example is identical to the previous one:: - from django.utils.translation import ugettext from django.http import HttpResponse + from django.utils.translation import gettext def my_view(request): - output = ugettext("Welcome to my site.") + output = gettext("Welcome to my site.") return HttpResponse(output) Translation works on computed values. This example is identical to the previous @@ -120,7 +121,7 @@ examples, is that Django's translation-string-detecting utility, :djadmin:`django-admin makemessages `, won't be able to find these strings. More on :djadmin:`makemessages` later.) -The strings you pass to ``_()`` or ``ugettext()`` can take placeholders, +The strings you pass to ``_()`` or ``gettext()`` can take placeholders, specified with Python's standard named-string interpolation syntax. Example:: def my_view(request, m, d): @@ -137,6 +138,13 @@ instead of positional interpolation (e.g., ``%s`` or ``%d``) whenever you have more than a single parameter. If you used positional interpolation, translations wouldn't be able to reorder placeholder text. +Since string extraction is done by the ``xgettext`` command, only syntaxes +supported by ``gettext`` are supported by Django. Python :py:ref:`f-strings +` and `JavaScript template strings`_ are not yet supported by +``xgettext``. + +.. _JavaScript template strings: https://savannah.gnu.org/bugs/?50920 + .. _translator-comments: Comments for translators @@ -148,7 +156,7 @@ preceding the string, e.g.:: def my_view(request): # Translators: This message appears on the home page only - output = ugettext("Welcome to my site.") + output = gettext("Welcome to my site.") The comment will then appear in the resulting ``.po`` file associated with the translatable construct located below it and should also be displayed by most @@ -170,7 +178,7 @@ more details. Marking strings as no-op ------------------------ -Use the function :func:`django.utils.translation.ugettext_noop()` to mark a +Use the function :func:`django.utils.translation.gettext_noop()` to mark a string as a translation string without translating it. The string is later translated from a variable. @@ -182,11 +190,11 @@ such as when the string is presented to the user. Pluralization ------------- -Use the function :func:`django.utils.translation.ungettext()` to specify +Use the function :func:`django.utils.translation.ngettext()` to specify pluralized messages. -``ungettext`` takes three arguments: the singular translation string, the plural -translation string and the number of objects. +``ngettext()`` takes three arguments: the singular translation string, the +plural translation string and the number of objects. This function is useful when you need your Django application to be localizable to languages where the number and complexity of `plural forms @@ -197,11 +205,11 @@ of its value.) For example:: - from django.utils.translation import ungettext from django.http import HttpResponse + from django.utils.translation import ngettext def hello_world(request, count): - page = ungettext( + page = ngettext( 'there is %(count)d object', 'there are %(count)d objects', count) % { @@ -216,7 +224,7 @@ Note that pluralization is complicated and works differently in each language. Comparing ``count`` to 1 isn't always the correct rule. This code looks sophisticated, but will produce incorrect results for some languages:: - from django.utils.translation import ungettext + from django.utils.translation import ngettext from myapp.models import Report count = Report.objects.count() @@ -225,7 +233,7 @@ sophisticated, but will produce incorrect results for some languages:: else: name = Report._meta.verbose_name_plural - text = ungettext( + text = ngettext( 'There is %(count)d %(name)s available.', 'There are %(count)d %(name)s available.', count @@ -234,10 +242,10 @@ sophisticated, but will produce incorrect results for some languages:: 'name': name } -Don't try to implement your own singular-or-plural logic, it won't be correct. +Don't try to implement your own singular-or-plural logic; it won't be correct. In a case like this, consider something like the following:: - text = ungettext( + text = ngettext( 'There is %(count)d %(name)s object available.', 'There are %(count)d %(name)s objects available.', count @@ -250,13 +258,13 @@ In a case like this, consider something like the following:: .. note:: - When using ``ungettext()``, make sure you use a single name for every + When using ``ngettext()``, make sure you use a single name for every extrapolated variable included in the literal. In the examples above, note how we used the ``name`` Python variable in both translation strings. This example, besides being incorrect in some languages as noted above, would fail:: - text = ungettext( + text = ngettext( 'There is %(count)d %(name)s available.', 'There are %(count)d %(plural_name)s available.', count @@ -271,14 +279,9 @@ In a case like this, consider something like the following:: a format specification for argument 'name', as in 'msgstr[0]', doesn't exist in 'msgid' -.. note:: Plural form and po files +.. versionchanged: 2.2.12 - Django does not support custom plural equations in po files. As all - translation catalogs are merged, only the plural form for the main Django po - file (in ``django/conf/locale//LC_MESSAGES/django.po``) is - considered. Plural forms in all other po files are ignored. Therefore, you - should not use different plural equations in your project or application po - files. + Added support for different plural equations in ``.po`` files. .. _contextual-markers: @@ -352,7 +355,7 @@ For example, to translate the help text of the *name* field in the following model, do the following:: from django.db import models - from django.utils.translation import ugettext_lazy as _ + from django.utils.translation import gettext_lazy as _ class MyThing(models.Model): name = models.CharField(help_text=_('This is the help text')) @@ -384,7 +387,7 @@ relying on the fallback English-centric and somewhat naïve determination of verbose names Django performs by looking at the model's class name:: from django.db import models - from django.utils.translation import ugettext_lazy as _ + from django.utils.translation import gettext_lazy as _ class MyThing(models.Model): name = models.CharField(_('name'), help_text=_('This is the help text')) @@ -400,7 +403,7 @@ For model methods, you can provide translations to Django and the admin site with the ``short_description`` attribute:: from django.db import models - from django.utils.translation import ugettext_lazy as _ + from django.utils.translation import gettext_lazy as _ class MyThing(models.Model): kind = models.ForeignKey( @@ -417,51 +420,30 @@ with the ``short_description`` attribute:: Working with lazy translation objects ------------------------------------- -The result of a ``ugettext_lazy()`` call can be used wherever you would use a -unicode string (a :class:`str` object) in other Django code, but it may not -work with arbitrary Python code. For example, the following won't work because -the `requests `_ library doesn't handle -``ugettext_lazy`` objects:: +The result of a ``gettext_lazy()`` call can be used wherever you would use a +string (a :class:`str` object) in other Django code, but it may not work with +arbitrary Python code. For example, the following won't work because the +`requests `_ library doesn't handle +``gettext_lazy`` objects:: - body = ugettext_lazy("I \u2764 Django") # (unicode :heart:) + body = gettext_lazy("I \u2764 Django") # (unicode :heart:) requests.post('https://example.com/send', data={'body': body}) -You can avoid such problems by casting ``ugettext_lazy()`` objects to text +You can avoid such problems by casting ``gettext_lazy()`` objects to text strings before passing them to non-Django code:: requests.post('https://example.com/send', data={'body': str(body)}) -Use ``unicode`` in place of ``str`` on Python 2, or :data:`six.text_type` to -support Python 2 and 3. - -If you try to use a ``ugettext_lazy()`` result where a bytestring (a -:class:`bytes` object) is expected, things won't work as expected since a -``ugettext_lazy()`` object doesn't know how to convert itself to a bytestring. -You can't use a unicode string inside a bytestring, either, so this is -consistent with normal Python behavior. For example, putting a unicode proxy -into a unicode string is fine:: - - "Hello %s" % ugettext_lazy("people") - -But you can't insert a unicode object into a bytestring and nor can you insert -a unicode proxy there:: - - b"Hello %s" % ugettext_lazy("people") - -If you ever see output that looks like ``"hello -"``, you have tried to insert the result of -``ugettext_lazy()`` into a bytestring. That's a bug in your code. - -If you don't like the long ``ugettext_lazy`` name, you can just alias it as +If you don't like the long ``gettext_lazy`` name, you can just alias it as ``_`` (underscore), like so:: from django.db import models - from django.utils.translation import ugettext_lazy as _ + from django.utils.translation import gettext_lazy as _ class MyThing(models.Model): name = models.CharField(help_text=_('This is the help text')) -Using ``ugettext_lazy()`` and ``ungettext_lazy()`` to mark strings in models +Using ``gettext_lazy()`` and ``ngettext_lazy()`` to mark strings in models and utility functions is a common operation. When you're working with these objects elsewhere in your code, you should ensure that you don't accidentally convert them to strings, because they should be converted as late as possible @@ -480,10 +462,10 @@ integer as the ``number`` argument. Then ``number`` will be looked up in the dictionary under that key during string interpolation. Here's example:: from django import forms - from django.utils.translation import ungettext_lazy + from django.utils.translation import ngettext_lazy class MyForm(forms.Form): - error_message = ungettext_lazy("You only provided %(num)d argument", + error_message = ngettext_lazy("You only provided %(num)d argument", "You only provided %(num)d arguments", 'num') def clean(self): @@ -495,7 +477,7 @@ If the string contains exactly one unnamed placeholder, you can interpolate directly with the ``number`` argument:: class MyForm(forms.Form): - error_message = ungettext_lazy( + error_message = ngettext_lazy( "You provided %d argument", "You provided %d arguments", ) @@ -517,10 +499,10 @@ that runs the ``str.format()`` method only when the result is included in a string. For example:: from django.utils.text import format_lazy - from django.utils.translation import ugettext_lazy + from django.utils.translation import gettext_lazy ... - name = ugettext_lazy('John Lennon') - instrument = ugettext_lazy('guitar') + name = gettext_lazy('John Lennon') + instrument = gettext_lazy('guitar') result = format_lazy('{name}: {instrument}', name=name, instrument=instrument) In this case, the lazy translations in ``result`` will only be converted to @@ -534,12 +516,11 @@ For any other case where you would like to delay the translation, but have to pass the translatable string as argument to another function, you can wrap this function inside a lazy call yourself. For example:: - from django.utils import six # Python 3 compatibility from django.utils.functional import lazy from django.utils.safestring import mark_safe - from django.utils.translation import ugettext_lazy as _ + from django.utils.translation import gettext_lazy as _ - mark_safe_lazy = lazy(mark_safe, six.text_type) + mark_safe_lazy = lazy(mark_safe, str) And then later:: @@ -572,7 +553,7 @@ Similar access to this information is available for template code. See below. Internationalization: in template code ====================================== -.. highlightlang:: html+django +.. highlight:: html+django Translations in :doc:`Django templates ` uses two template tags and a slightly different syntax than in Python code. To give your template @@ -581,6 +562,13 @@ As with all template tags, this tag needs to be loaded in all templates which use translations, even those templates that extend from other templates which have already loaded the ``i18n`` tag. +.. warning:: + + Translated strings will not be escaped when rendered in a template. + This allows you to include HTML in translations, for example for emphasis, + but potentially dangerous characters (e.g. ``"``) will also be rendered + unchanged. + .. templatetag:: trans ``trans`` template tag @@ -599,7 +587,7 @@ require translation in the future:: {% trans "myvar" noop %} Internally, inline translations use an -:func:`~django.utils.translation.ugettext` call. +:func:`~django.utils.translation.gettext` call. In case a template var (``myvar`` above) is passed to the tag, the tag will first resolve such variable to a string at run-time and then look up that @@ -631,7 +619,7 @@ filters::

      {% for stage in tour_stages %} - {% cycle start end %}: {{ stage }}{% if forloop.counter|divisibleby:2 %}
      {% else %}, {% endif %} + {% cycle start end %}: {{ stage }}{% if forloop.counter|divisibleby:2 %}
      {% else %}, {% endif %} {% endfor %}

      @@ -677,7 +665,7 @@ You can use multiple expressions inside a single ``blocktrans`` tag:: Other block tags (for example ``{% for %}`` or ``{% if %}``) are not allowed inside a ``blocktrans`` tag. -If resolving one of the block arguments fails, blocktrans will fall back to +If resolving one of the block arguments fails, ``blocktrans`` will fall back to the default language by deactivating the currently active language temporarily with the :func:`~django.utils.translation.deactivate_all` function. @@ -709,8 +697,8 @@ A more complex example:: When you use both the pluralization feature and bind values to local variables in addition to the counter value, keep in mind that the ``blocktrans`` -construct is internally converted to an ``ungettext`` call. This means the -same :ref:`notes regarding ungettext variables ` +construct is internally converted to an ``ngettext`` call. This means the +same :ref:`notes regarding ngettext variables ` apply. Reverse URL lookups cannot be carried out within the ``blocktrans`` and should @@ -859,7 +847,7 @@ If you want to select a language within a template, you can use the While the first occurrence of "Welcome to our page" uses the current language, the second will always be in English. -.. _template-translation-vars: +.. _i18n-template-tags: Other tags ---------- @@ -894,8 +882,13 @@ locale's direction. If ``True``, it's a right-to-left language, e.g. Hebrew, Arabic. If ``False`` it's a left-to-right language, e.g. English, French, German, etc. -If you enable the ``django.template.context_processors.i18n`` context processor -then each ``RequestContext`` will have access to ``LANGUAGES``, +.. _template-translation-vars: + +``i18n`` context processor +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you enable the :class:`django.template.context_processors.i18n` context +processor, then each ``RequestContext`` will have access to ``LANGUAGES``, ``LANGUAGE_CODE``, and ``LANGUAGE_BIDI`` as defined above. .. templatetag:: get_language_info @@ -912,9 +905,9 @@ use the ``{% get_language_info %}`` tag:: You can then access the information:: - Language code: {{ lang.code }}
      - Name of language: {{ lang.name_local }}
      - Name in English: {{ lang.name }}
      + Language code: {{ lang.code }}
      + Name of language: {{ lang.name_local }}
      + Name in English: {{ lang.name }}
      Bi-directional: {{ lang.bidi }} Name in the active language: {{ lang.name_translated }} @@ -961,7 +954,7 @@ There are also simple filters available for convenience: Internationalization: in JavaScript code ======================================== -.. highlightlang:: python +.. highlight:: python Adding translations to JavaScript poses some problems: @@ -981,15 +974,11 @@ The main solution to these problems is the following ``JavaScriptCatalog`` view, which generates a JavaScript code library with functions that mimic the ``gettext`` interface, plus an array of translation strings. -.. _javascript_catalog-view: - The ``JavaScriptCatalog`` view ------------------------------ .. module:: django.views.i18n -.. versionadded:: 1.10 - .. class:: JavaScriptCatalog A view that produces a JavaScript code library with functions that mimic @@ -1017,15 +1006,15 @@ The ``JavaScriptCatalog`` view from django.views.i18n import JavaScriptCatalog urlpatterns = [ - url(r'^jsi18n/$', JavaScriptCatalog.as_view(), name='javascript-catalog'), + path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'), ] **Example with custom packages**:: urlpatterns = [ - url(r'^jsi18n/myapp/$', - JavaScriptCatalog.as_view(packages=['your.app.label']), - name='javascript-catalog'), + path('jsi18n/myapp/', + JavaScriptCatalog.as_view(packages=['your.app.label']), + name='javascript-catalog'), ] If your root URLconf uses :func:`~django.conf.urls.i18n.i18n_patterns`, @@ -1037,7 +1026,7 @@ The ``JavaScriptCatalog`` view from django.conf.urls.i18n import i18n_patterns urlpatterns = i18n_patterns( - url(r'^jsi18n/$', JavaScriptCatalog.as_view(), name='javascript-catalog'), + path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'), ) The precedence of translations is such that the packages appearing later in the @@ -1049,92 +1038,10 @@ If you use more than one ``JavaScriptCatalog`` view on a site and some of them define the same strings, the strings in the catalog that was loaded last take precedence. -The ``javascript_catalog`` view -------------------------------- - -.. function:: javascript_catalog(request, domain='djangojs', packages=None) - -.. deprecated:: 1.10 - - ``javascript_catalog()`` is deprecated in favor of - :class:`JavaScriptCatalog` and will be removed in Django 2.0. - -The main solution to these problems is the -:meth:`django.views.i18n.javascript_catalog` view, which sends out a JavaScript -code library with functions that mimic the ``gettext`` interface, plus an array -of translation strings. Those translation strings are taken from applications or -Django core, according to what you specify in either the ``info_dict`` or the -URL. Paths listed in :setting:`LOCALE_PATHS` are also included. - -You hook it up like this:: - - from django.views.i18n import javascript_catalog - - js_info_dict = { - 'packages': ('your.app.package',), - } - - urlpatterns = [ - url(r'^jsi18n/$', javascript_catalog, js_info_dict, name='javascript-catalog'), - ] - -Each string in ``packages`` should be in Python dotted-package syntax (the -same format as the strings in :setting:`INSTALLED_APPS`) and should refer to a -package that contains a ``locale`` directory. If you specify multiple packages, -all those catalogs are merged into one catalog. This is useful if you have -JavaScript that uses strings from different applications. - -The precedence of translations is such that the packages appearing later in the -``packages`` argument have higher precedence than the ones appearing at the -beginning, this is important in the case of clashing translations for the same -literal. - -By default, the view uses the ``djangojs`` gettext domain. This can be -changed by altering the ``domain`` argument. - -You can make the view dynamic by putting the packages into the URL pattern:: - - urlpatterns = [ - url(r'^jsi18n/(?P\S+?)/$', javascript_catalog, name='javascript-catalog'), - ] - -With this, you specify the packages as a list of package names delimited by '+' -signs in the URL. This is especially useful if your pages use code from -different apps and this changes often and you don't want to pull in one big -catalog file. As a security measure, these values can only be either -``django.conf`` or any package from the :setting:`INSTALLED_APPS` setting. - -You can also split the catalogs in multiple URLs and load them as you need in -your sites:: - - js_info_dict_app = { - 'packages': ('your.app.package',), - } - - js_info_dict_other_app = { - 'packages': ('your.other.app.package',), - } - - urlpatterns = [ - url(r'^jsi18n/app/$', javascript_catalog, js_info_dict_app), - url(r'^jsi18n/other_app/$', javascript_catalog, js_info_dict_other_app), - ] - -If you use more than one ``javascript_catalog`` on a site and some of them -define the same strings, the strings in the catalog that was loaded last take -precedence. - -The JavaScript translations found in the paths listed in the -:setting:`LOCALE_PATHS` setting are also always included. To keep consistency -with the translations lookup order algorithm used for Python and templates, the -directories listed in :setting:`LOCALE_PATHS` have the highest precedence with -the ones appearing first having higher precedence than the ones appearing -later. - Using the JavaScript translation catalog ---------------------------------------- -.. highlightlang:: javascript +.. highlight:: javascript To use the catalog, just pull in the dynamically generated script like this: @@ -1298,8 +1205,6 @@ will render a conditional expression. This will evaluate to either a ``true`` The ``JSONCatalog`` view ------------------------ -.. versionadded:: 1.10 - .. class:: JSONCatalog In order to use another client-side library to handle translations, you may @@ -1326,57 +1231,6 @@ The ``JSONCatalog`` view .. JSON doesn't allow comments so highlighting as JSON won't work here. -The ``json_catalog`` view -------------------------- - -.. function:: json_catalog(request, domain='djangojs', packages=None) - -.. deprecated:: 1.10 - - ``json_catalog()`` is deprecated in favor of :class:`JSONCatalog` and will - be removed in Django 2.0. - -In order to use another client-side library to handle translations, you may -want to take advantage of the ``json_catalog()`` view. It's similar to -:meth:`~django.views.i18n.javascript_catalog` but returns a JSON response. - -The JSON object contains i18n formatting settings (those available for -`get_format`_), a plural rule (as a ``plural`` part of a GNU gettext -``Plural-Forms`` expression), and translation strings. The translation strings -are taken from applications or Django's own translations, according to what is -specified either via ``urlpatterns`` arguments or as request parameters. Paths -listed in :setting:`LOCALE_PATHS` are also included. - -The view is hooked up to your application and configured in the same fashion as -:meth:`~django.views.i18n.javascript_catalog` (namely, the ``domain`` and -``packages`` arguments behave identically):: - - from django.views.i18n import json_catalog - - js_info_dict = { - 'packages': ('your.app.package',), - } - - urlpatterns = [ - url(r'^jsoni18n/$', json_catalog, js_info_dict), - ] - -The response format is as follows: - -.. code-block:: text - - { - "catalog": { - # Translations catalog - }, - "formats": { - # Language formats for date, time, etc. - }, - "plural": "..." # Expression for plural forms, or null. - } - -.. JSON doesn't allow comments so highlighting as JSON won't work here. - Note on performance ------------------- @@ -1395,9 +1249,9 @@ URL:: # The value returned by get_version() must change when translations change. urlpatterns = [ - url(r'^jsi18n/$', - cache_page(86400, key_prefix='js18n-%s' % get_version())(JavaScriptCatalog.as_view()), - name='javascript-catalog'), + path('jsi18n/', + cache_page(86400, key_prefix='js18n-%s' % get_version())(JavaScriptCatalog.as_view()), + name='javascript-catalog'), ] Client-side caching will save bandwidth and make your site load faster. If @@ -1413,9 +1267,9 @@ whenever you restart your application server:: last_modified_date = timezone.now() urlpatterns = [ - url(r'^jsi18n/$', - last_modified(lambda req, **kw: last_modified_date)(JavaScriptCatalog.as_view()), - name='javascript-catalog'), + path('jsi18n/', + last_modified(lambda req, **kw: last_modified_date)(JavaScriptCatalog.as_view()), + name='javascript-catalog'), ] You can even pre-generate the JavaScript catalog as part of your deployment @@ -1438,7 +1292,7 @@ Django provides two mechanisms to internationalize URL patterns: the language to activate from the requested URL. * Making URL patterns themselves translatable via the - :func:`django.utils.translation.ugettext_lazy()` function. + :func:`django.utils.translation.gettext_lazy()` function. .. warning:: @@ -1462,26 +1316,26 @@ translations to existing site so that the current URLs won't change. Example URL patterns:: - from django.conf.urls import include, url from django.conf.urls.i18n import i18n_patterns + from django.urls import include, path from about import views as about_views from news import views as news_views from sitemap.views import sitemap urlpatterns = [ - url(r'^sitemap\.xml$', sitemap, name='sitemap-xml'), + path('sitemap.xml', sitemap, name='sitemap-xml'), ] news_patterns = ([ - url(r'^$', news_views.index, name='index'), - url(r'^category/(?P[\w-]+)/$', news_views.category, name='category'), - url(r'^(?P[\w-]+)/$', news_views.details, name='detail'), + path('', news_views.index, name='index'), + path('category//', news_views.category, name='category'), + path('/', news_views.details, name='detail'), ], 'news') urlpatterns += i18n_patterns( - url(r'^about/$', about_views.main, name='about'), - url(r'^news/', include(news_patterns, namespace='news')), + path('about/', about_views.main, name='about'), + path('news/', include(news_patterns, namespace='news')), ) After defining these URL patterns, Django will automatically add the @@ -1512,22 +1366,12 @@ will be:: >>> reverse('news:index') '/nl/news/' -.. versionadded:: 1.10 - - The ``prefix_default_language`` parameter was added. - .. warning:: :func:`~django.conf.urls.i18n.i18n_patterns` is only allowed in a root URLconf. Using it within an included URLconf will throw an :exc:`~django.core.exceptions.ImproperlyConfigured` exception. -.. versionchanged:: 1.10 - - In older version, using ``i18n_patterns`` in a root URLconf different from - :setting:`ROOT_URLCONF` by setting :attr:`request.urlconf - ` wasn't supported. - .. warning:: Ensure that you don't have non-prefixed URL patterns that might collide @@ -1539,29 +1383,29 @@ Translating URL patterns ------------------------ URL patterns can also be marked translatable using the -:func:`~django.utils.translation.ugettext_lazy` function. Example:: +:func:`~django.utils.translation.gettext_lazy` function. Example:: - from django.conf.urls import include, url from django.conf.urls.i18n import i18n_patterns - from django.utils.translation import ugettext_lazy as _ + from django.urls import include, path + from django.utils.translation import gettext_lazy as _ from about import views as about_views from news import views as news_views from sitemaps.views import sitemap urlpatterns = [ - url(r'^sitemap\.xml$', sitemap, name='sitemap-xml'), + path('sitemap.xml', sitemap, name='sitemap-xml'), ] news_patterns = ([ - url(r'^$', news_views.index, name='index'), - url(_(r'^category/(?P[\w-]+)/$'), news_views.category, name='category'), - url(r'^(?P[\w-]+)/$', news_views.details, name='detail'), + path('', news_views.index, name='index'), + path(_('category//'), news_views.category, name='category'), + path('/', news_views.details, name='detail'), ], 'news') urlpatterns += i18n_patterns( - url(_(r'^about/$'), about_views.main, name='about'), - url(_(r'^news/'), include(news_patterns, namespace='news')), + path(_('about/'), about_views.main, name='about'), + path(_('news/'), include(news_patterns, namespace='news')), ) After you've created the translations, the :func:`~django.urls.reverse` @@ -1580,8 +1424,8 @@ function will return the URL in the active language. Example:: .. warning:: - In most cases, it's best to use translated URLs only within a - language-code-prefixed block of patterns (using + In most cases, it's best to use translated URLs only within a language code + prefixed block of patterns (using :func:`~django.conf.urls.i18n.i18n_patterns`), to avoid the possibility that a carelessly translated URL causes a collision with a non-translated URL pattern. @@ -1668,9 +1512,9 @@ message file under the directory listed first in :setting:`LOCALE_PATHS` or will generate an error if :setting:`LOCALE_PATHS` is empty. By default :djadmin:`django-admin makemessages ` examines every -file that has the ``.html`` or ``.txt`` file extension. In case you want to -override that default, use the ``--extension`` or ``-e`` option to specify the -file extensions to examine:: +file that has the ``.html``, ``.txt`` or ``.py`` file extension. If you want to +override that default, use the :option:`--extension ` +or ``-e`` option to specify the file extensions to examine:: django-admin makemessages -l de -e txt @@ -1683,7 +1527,7 @@ multiple times:: When :ref:`creating message files from JavaScript source code ` you need to use the special - 'djangojs' domain, **not** ``-e js``. + ``djangojs`` domain, **not** ``-e js``. .. admonition:: Using Jinja2 templates? @@ -1805,12 +1649,12 @@ That's it. Your translations are ready for use. (Byte Order Mark) so if your text editor adds such marks to the beginning of files by default then you will need to reconfigure it. -Troubleshooting: ``ugettext()`` incorrectly detects ``python-format`` in strings with percent signs ---------------------------------------------------------------------------------------------------- +Troubleshooting: ``gettext()`` incorrectly detects ``python-format`` in strings with percent signs +-------------------------------------------------------------------------------------------------- In some cases, such as strings with a percent sign followed by a space and a :ref:`string conversion type ` (e.g. -``_("10% interest")``), :func:`~django.utils.translation.ugettext` incorrectly +``_("10% interest")``), :func:`~django.utils.translation.gettext` incorrectly flags strings with ``python-format``. If you try to compile message files with incorrectly flagged strings, you'll @@ -1821,14 +1665,14 @@ unlike 'msgid'``. To workaround this, you can escape percent signs by adding a second percent sign:: - from django.utils.translation import ugettext as _ - output = _("10%% interest) + from django.utils.translation import gettext as _ + output = _("10%% interest") Or you can use ``no-python-format`` so that all percent signs are treated as literals:: # xgettext:no-python-format - output = _("10% interest) + output = _("10% interest") .. _creating-message-files-from-js-code: @@ -1856,7 +1700,7 @@ This is only needed for people who either want to extract message IDs or compile message files (``.po``). Translation work itself just involves editing existing files of this type, but if you want to create your own message files, or want to test or compile a changed message file, download `a precompiled binary -installer `_. +installer `_. You may also use ``gettext`` binaries you have obtained elsewhere, so long as the ``xgettext --version`` command works properly. Do not attempt to use Django @@ -1886,7 +1730,7 @@ If you need more flexibility, you could also add a new argument to your custom class Command(makemessages.Command): def add_arguments(self, parser): - super(Command, self).add_arguments(parser) + super().add_arguments(parser) parser.add_argument( '--extra-keyword', dest='xgettext_keywords', @@ -1900,7 +1744,7 @@ If you need more flexibility, you could also add a new argument to your custom makemessages.Command.xgettext_options[:] + ['--keyword=%s' % kwd for kwd in xgettext_keywords] ) - super(Command, self).handle(*args, **options) + super().handle(*args, **options) Miscellaneous ============= @@ -1920,7 +1764,7 @@ back to the previous page. Activate this view by adding the following line to your URLconf:: - url(r'^i18n/', include('django.conf.urls.i18n')), + path('i18n/', include('django.conf.urls.i18n')), (Note that this example makes the view available at ``/i18n/setlang/``.) @@ -1931,10 +1775,14 @@ Activate this view by adding the following line to your URLconf:: language-independent itself to work correctly. The view expects to be called via the ``POST`` method, with a ``language`` -parameter set in request. If session support is enabled, the view -saves the language choice in the user's session. Otherwise, it saves the -language choice in a cookie that is by default named ``django_language``. -(The name can be changed through the :setting:`LANGUAGE_COOKIE_NAME` setting.) +parameter set in request. If session support is enabled, the view saves the +language choice in the user's session. It also saves the language choice in a +cookie that is named ``django_language`` by default. (The name can be changed +through the :setting:`LANGUAGE_COOKIE_NAME` setting.) + +.. versionchanged:: 2.1 + + In older versions, the cookie is only set if session support isn't enabled. After setting the language choice, Django looks for a ``next`` parameter in the ``POST`` or ``GET`` data. If that is found and Django considers it to be a safe @@ -1947,11 +1795,6 @@ set, to ``/``, depending on the nature of the request: parameter was set. Otherwise a 204 status code (No Content) will be returned. * For non-AJAX requests, the fallback will always be performed. -.. versionchanged:: 1.10 - - Returning a 204 status code for AJAX requests when no redirect is specified - is new. - Here's example HTML template code: .. code-block:: html+django @@ -1959,7 +1802,7 @@ Here's example HTML template code: {% load i18n %}
      {% csrf_token %} - + - +
      In this example, Django looks up the URL of the page to which the user will be @@ -1981,7 +1824,7 @@ redirected in the ``redirect_to`` context variable. Explicitly setting the active language -------------------------------------- -.. highlightlang:: python +.. highlight:: python You may want to set the active language for the current session explicitly. Perhaps a user's language preference is retrieved from another system, for example. @@ -2002,12 +1845,12 @@ preference persist in future requests. If you are not using sessions, the language will persist in a cookie, whose name is configured in :setting:`LANGUAGE_COOKIE_NAME`. For example:: - from django.utils import translation - from django import http from django.conf import settings + from django.http import HttpResponse + from django.utils import translation user_language = 'fr' translation.activate(user_language) - response = http.HttpResponse(...) + response = HttpResponse(...) response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user_language) Using translations outside views and templates @@ -2030,12 +1873,12 @@ For example:: cur_language = translation.get_language() try: translation.activate(language) - text = translation.ugettext('welcome') + text = translation.gettext('welcome') finally: translation.activate(cur_language) return text -Calling this function with the value 'de' will give you ``"Willkommen"``, +Calling this function with the value ``'de'`` will give you ``"Willkommen"``, regardless of :setting:`LANGUAGE_CODE` and language set by middleware. Functions of particular interest are @@ -2053,7 +1896,7 @@ enter and restores it on exit. With it, the above example becomes:: def welcome_translated(language): with translation.override(language): - return translation.ugettext('welcome') + return translation.gettext('welcome') Language cookie --------------- @@ -2117,7 +1960,7 @@ To use ``LocaleMiddleware``, add ``'django.middleware.locale.LocaleMiddleware'`` to your :setting:`MIDDLEWARE` setting. Because middleware order matters, follow these guidelines: -* Make sure it's one of the first middlewares installed. +* Make sure it's one of the first middleware installed. * It should come after ``SessionMiddleware``, because ``LocaleMiddleware`` makes use of session data. And it should come before ``CommonMiddleware`` because ``CommonMiddleware`` needs an activated language in order @@ -2182,17 +2025,17 @@ Notes: ] This example restricts languages that are available for automatic - selection to German and English (and any sublanguage, like de-ch or - en-us). + selection to German and English (and any sublanguage, like ``de-ch`` or + ``en-us``). * If you define a custom :setting:`LANGUAGES` setting, as explained in the previous bullet, you can mark the language names as translation strings - -- but use :func:`~django.utils.translation.ugettext_lazy` instead of - :func:`~django.utils.translation.ugettext` to avoid a circular import. + -- but use :func:`~django.utils.translation.gettext_lazy` instead of + :func:`~django.utils.translation.gettext` to avoid a circular import. Here's a sample settings file:: - from django.utils.translation import ugettext_lazy as _ + from django.utils.translation import gettext_lazy as _ LANGUAGES = [ ('de', _('German')), @@ -2232,25 +2075,34 @@ the order in which it examines the different file paths to load the compiled :term:`message files ` (``.mo``) and the precedence of multiple translations for the same literal: -1. The directories listed in :setting:`LOCALE_PATHS` have the highest +#. The directories listed in :setting:`LOCALE_PATHS` have the highest precedence, with the ones appearing first having higher precedence than the ones appearing later. -2. Then, it looks for and uses if it exists a ``locale`` directory in each +#. Then, it looks for and uses if it exists a ``locale`` directory in each of the installed apps listed in :setting:`INSTALLED_APPS`. The ones appearing first have higher precedence than the ones appearing later. -3. Finally, the Django-provided base translation in ``django/conf/locale`` +#. Finally, the Django-provided base translation in ``django/conf/locale`` is used as a fallback. .. seealso:: The translations for literals included in JavaScript assets are looked up - following a similar but not identical algorithm. See the - :ref:`javascript_catalog view documentation ` for - more details. + following a similar but not identical algorithm. See + :class:`.JavaScriptCatalog` for more details. + + You can also put :ref:`custom format files ` in the + :setting:`LOCALE_PATHS` directories if you also set + :setting:`FORMAT_MODULE_PATH`. In all cases the name of the directory containing the translation is expected to be named using :term:`locale name` notation. E.g. ``de``, ``pt_BR``, ``es_AR``, -etc. +etc. Untranslated strings for territorial language variants use the translations +of the generic language. For example, untranslated ``pt_BR`` strings use ``pt`` +translations. + +.. versionchanged:: 2.1 + + Fallback to the generic language as described above was added. This way, you can write applications that include their own translations, and you can override base translations in your project. Or, you can just build @@ -2287,5 +2139,5 @@ aware of certain limitations: * When an English variant is activated and English strings are missing, the fallback language will not be the :setting:`LANGUAGE_CODE` of the project, but the original strings. For example, an English user visiting a site with - Spanish as the default language and original strings written in Russian will - fallback to Russian, not to Spanish. + :setting:`LANGUAGE_CODE` set to Spanish and original strings written in + Russian will see Russian text rather than Spanish. diff --git a/docs/topics/index.txt b/docs/topics/index.txt index 6f85baddb6e2..55a5fc6af31f 100644 --- a/docs/topics/index.txt +++ b/docs/topics/index.txt @@ -5,7 +5,7 @@ Using Django Introductions to all the key parts of Django you'll need to know: .. toctree:: - :maxdepth: 1 + :maxdepth: 2 install db/index @@ -24,7 +24,6 @@ Introductions to all the key parts of Django you'll need to know: i18n/index logging pagination - python3 security performance serialization diff --git a/docs/topics/install.txt b/docs/topics/install.txt index 5bdd98596d4b..4d762892f0cb 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -7,18 +7,18 @@ This document will get you up and running with Django. Install Python ============== -Being a Python Web framework, Django requires Python. See -:ref:`faq-python-version-support` for details. +Django is a Python Web framework. See :ref:`faq-python-version-support` for +details. -Get the latest version of Python at https://www.python.org/download/ or with +Get the latest version of Python at https://www.python.org/downloads/ or with your operating system's package manager. .. admonition:: Django on Jython - If you use Jython_ (a Python implementation for the Java platform), you'll - need to follow a few additional steps. See :doc:`/howto/jython` for details. + Jython_ (a Python implementation for the Java platform) is not compatible + with Python 3, so Django ≥ 2.0 cannot run on Jython. -.. _jython: http://jython.org/ +.. _jython: http://www.jython.org/ .. admonition:: Python on Windows @@ -34,19 +34,19 @@ testing, so you won't need to set up Apache until you're ready to deploy Django in production. If you want to use Django on a production site, use `Apache`_ with -`mod_wsgi`_. mod_wsgi can operate in one of two modes: an embedded -mode and a daemon mode. In embedded mode, mod_wsgi is similar to +`mod_wsgi`_. mod_wsgi operates in one of two modes: embedded +mode or daemon mode. In embedded mode, mod_wsgi is similar to mod_perl -- it embeds Python within Apache and loads Python code into memory when the server starts. Code stays in memory throughout the life of an Apache process, which leads to significant performance gains over other server arrangements. In daemon mode, mod_wsgi spawns an independent daemon process that handles requests. The daemon process can run as a different user than the Web server, possibly -leading to improved security, and the daemon process can be restarted +leading to improved security. The daemon process can be restarted without restarting the entire Apache Web server, possibly making refreshing your codebase more seamless. Consult the mod_wsgi documentation to determine which mode is right for your setup. Make -sure you have Apache installed, with the mod_wsgi module activated. +sure you have Apache installed with the mod_wsgi module activated. Django will work with any version of Apache that supports mod_wsgi. See :doc:`How to use Django with mod_wsgi ` @@ -59,8 +59,8 @@ very well with `nginx`_. Additionally, Django follows the WSGI spec (:pep:`3333`), which allows it to run on a variety of server platforms. .. _Apache: https://httpd.apache.org/ -.. _nginx: http://nginx.org/ -.. _mod_wsgi: http://www.modwsgi.org/ +.. _nginx: https://nginx.org/ +.. _mod_wsgi: https://modwsgi.readthedocs.io/en/develop/ .. _database-installation: @@ -76,7 +76,7 @@ If you are developing a simple project or something you don't plan to deploy in a production environment, SQLite is generally the simplest option as it doesn't require running a separate server. However, SQLite has many differences from other databases, so if you are working on something substantial, it's -recommended to develop with the same database as you plan on using in +recommended to develop with the same database that you plan on using in production. In addition to the officially supported databases, there are :ref:`backends @@ -119,30 +119,8 @@ database queries, Django will need permission to create a test database. .. _MySQL: https://www.mysql.com/ .. _psycopg2: http://initd.org/psycopg/ .. _SQLite: https://www.sqlite.org/ -.. _cx_Oracle: http://cx-oracle.sourceforge.net/ -.. _Oracle: http://www.oracle.com/ - -.. _removing-old-versions-of-django: - -Remove any old versions of Django -================================= - -If you are upgrading your installation of Django from a previous version, -you will need to uninstall the old Django version before installing the -new version. - -If you installed Django using pip_ or ``easy_install`` previously, installing -with pip_ or ``easy_install`` again will automatically take care of the old -version, so you don't need to do it yourself. - -If you previously installed Django using ``python setup.py install``, -uninstalling is as simple as deleting the ``django`` directory from your Python -``site-packages``. To find the directory you need to remove, you can run the -following at your shell prompt (not the interactive Python prompt): - -.. code-block:: console - - $ python -c "import django; print(django.__path__)" +.. _cx_Oracle: https://oracle.github.io/python-cx_Oracle/ +.. _Oracle: https://www.oracle.com/ .. _install-django-code: @@ -162,19 +140,22 @@ Installing an official release with ``pip`` This is the recommended way to install Django. -1. Install pip_. The easiest is to use the `standalone pip installer`_. If your +#. Install pip_. The easiest is to use the `standalone pip installer`_. If your distribution already has ``pip`` installed, you might need to update it if it's outdated. If it's outdated, you'll know because installation won't work. -2. Take a look at virtualenv_ and virtualenvwrapper_. These tools provide +#. Take a look at virtualenv_ and virtualenvwrapper_. These tools provide isolated Python environments, which are more practical than installing packages systemwide. They also allow installing packages without administrator privileges. The :doc:`contributing tutorial - ` walks through how to create a virtualenv on Python 3. + ` walks through how to create a virtualenv. + +#. After you've created and activated a virtual environment, enter the command: + + .. console:: -3. After you've created and activated a virtual environment, enter the command - ``pip install Django`` at the shell prompt. + $ pip install Django .. _pip: https://pip.pypa.io/ .. _virtualenv: https://virtualenv.pypa.io/ @@ -213,25 +194,25 @@ Installing the development version If you'd like to be able to update your Django code occasionally with the latest bug fixes and improvements, follow these instructions: -1. Make sure that you have Git_ installed and that you can run its commands +#. Make sure that you have Git_ installed and that you can run its commands from a shell. (Enter ``git help`` at a shell prompt to test this.) -2. Check out Django's main development branch like so: +#. Check out Django's main development branch like so: - .. code-block:: console + .. console:: - $ git clone git://github.com/django/django.git + $ git clone https://github.com/django/django.git This will create a directory ``django`` in your current directory. -3. Make sure that the Python interpreter can load Django's code. The most +#. Make sure that the Python interpreter can load Django's code. The most convenient way to do this is to use virtualenv_, virtualenvwrapper_, and pip_. The :doc:`contributing tutorial ` walks through - how to create a virtualenv on Python 3. + how to create a virtualenv. -4. After setting up and activating the virtualenv, run the following command: +#. After setting up and activating the virtualenv, run the following command: - .. code-block:: console + .. console:: $ pip install -e django/ @@ -243,4 +224,4 @@ When you want to update your copy of the Django source code, just run the command ``git pull`` from within the ``django`` directory. When you do this, Git will automatically download any changes. -.. _Git: http://git-scm.com/ +.. _Git: https://git-scm.com/ diff --git a/docs/topics/logging.txt b/docs/topics/logging.txt index 15a49f0d4d92..a2461fc7c656 100644 --- a/docs/topics/logging.txt +++ b/docs/topics/logging.txt @@ -148,7 +148,7 @@ by a name. This name is used to identify the logger for configuration purposes. By convention, the logger name is usually ``__name__``, the name of -the python module that contains the logger. This allows you to filter +the Python module that contains the logger. This allows you to filter and handle logging calls on a per-module basis. However, if you have some other way of organizing your logging messages, you can provide any dot-separated name to identify your logger:: @@ -218,15 +218,15 @@ default logging configuration ` using the following scheme. If the ``disable_existing_loggers`` key in the :setting:`LOGGING` dictConfig is -set to ``True`` (which is the default) then all loggers from the default -configuration will be disabled. Disabled loggers are not the same as removed; -the logger will still exist, but will silently discard anything logged to it, -not even propagating entries to a parent logger. Thus you should be very -careful using ``'disable_existing_loggers': True``; it's probably not what you -want. Instead, you can set ``disable_existing_loggers`` to ``False`` and -redefine some or all of the default loggers; or you can set -:setting:`LOGGING_CONFIG` to ``None`` and :ref:`handle logging config yourself -`. +set to ``True`` (which is the ``dictConfig`` default if the key is missing) +then all loggers from the default configuration will be disabled. Disabled +loggers are not the same as removed; the logger will still exist, but will +silently discard anything logged to it, not even propagating entries to a +parent logger. Thus you should be very careful using +``'disable_existing_loggers': True``; it's probably not what you want. Instead, +you can set ``disable_existing_loggers`` to ``False`` and redefine some or all +of the default loggers; or you can set :setting:`LOGGING_CONFIG` to ``None`` +and :ref:`handle logging config yourself `. Logging is configured as part of the general Django ``setup()`` function. Therefore, you can be certain that loggers are always ready for use in your @@ -299,10 +299,12 @@ Finally, here's an example of a fairly complex logging setup:: 'disable_existing_loggers': False, 'formatters': { 'verbose': { - 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' + 'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}', + 'style': '{', }, 'simple': { - 'format': '%(levelname)s %(message)s' + 'format': '{levelname} {message}', + 'style': '{', }, }, 'filters': { @@ -377,13 +379,13 @@ This logging configuration does the following things: * Defines two handlers: - * ``console``, a StreamHandler, which will print any ``INFO`` - (or higher) message to stderr. This handler uses the ``simple`` output - format. + * ``console``, a :class:`~logging.StreamHandler`, which prints any ``INFO`` + (or higher) message to ``sys.stderr``. This handler uses the ``simple`` + output format. - * ``mail_admins``, an AdminEmailHandler, which will email any - ``ERROR`` (or higher) message to the site admins. This handler uses - the ``special`` filter. + * ``mail_admins``, an :class:`AdminEmailHandler`, which emails any ``ERROR`` + (or higher) message to the site :setting:`ADMINS`. This handler uses the + ``special`` filter. * Configures three loggers: @@ -427,8 +429,8 @@ configuration process for :ref:`Django's default logging `. Here's an example that disables Django's logging configuration and then manually configures logging: -.. snippet:: - :filename: settings.py +.. code-block:: python + :caption: settings.py LOGGING_CONFIG = None @@ -456,8 +458,8 @@ Django provides several built-in loggers. ``django`` ~~~~~~~~~~ -``django`` is the catch-all logger. No messages are posted directly to -this logger. +The catch-all logger for messages in the ``django`` hierarchy. No messages are +posted using this name but instead using one of the loggers below. .. _django-request-logger: @@ -466,7 +468,8 @@ this logger. Log messages related to the handling of requests. 5XX responses are raised as ``ERROR`` messages; 4XX responses are raised as ``WARNING`` -messages. +messages. Requests that are logged to the ``django.security`` logger aren't +logged to ``django.request``. Messages to this logger have the following extra context: @@ -481,8 +484,6 @@ Messages to this logger have the following extra context: ``django.server`` ~~~~~~~~~~~~~~~~~ -.. versionadded:: 1.10 - Log messages related to the handling of requests received by the server invoked by the :djadmin:`runserver` command. HTTP 5XX responses are logged as ``ERROR`` messages, 4XX responses are logged as ``WARNING`` messages, and everything else @@ -503,11 +504,6 @@ Log messages related to the rendering of templates. * Missing context variables are logged as ``DEBUG`` messages. -* Uncaught exceptions raised during the rendering of an - :ttag:`{% include %} ` are logged as ``WARNING`` messages when - debug mode is off (helpful since ``{% include %}`` silences the exception and - returns an empty string in that case). - .. _django-db-logger: ``django.db.backends`` @@ -584,10 +580,6 @@ Messages to this logger have ``params`` and ``sql`` in their extra context (but unlike ``django.db.backends``, not duration). The values have the same meaning as explained in :ref:`django-db-logger`. -.. versionadded:: 1.10 - - The ``extra`` context was added. - Handlers -------- @@ -596,7 +588,7 @@ Python logging module. .. class:: AdminEmailHandler(include_html=False, email_backend=None) - This handler sends an email to the site admins for each log + This handler sends an email to the site :setting:`ADMINS` for each log message it receives. If the log record contains a ``request`` attribute, the full details @@ -657,12 +649,12 @@ Python logging module. subclass the :class:`~django.utils.log.AdminEmailHandler` class and override this method. -.. _Sentry: https://pypi.python.org/pypi/sentry +.. _Sentry: https://pypi.org/project/sentry/ Filters ------- -Django provides two log filters in addition to those provided by the Python +Django provides some log filters in addition to those provided by the Python logging module. .. class:: CallbackFilter(callback) @@ -740,18 +732,25 @@ By default, Django configures the following logging: When :setting:`DEBUG` is ``True``: -* The ``django`` catch-all logger sends all messages at the ``INFO`` level or - higher to the console. +* The ``django`` logger sends messages in the ``django`` hierarchy (except + ``django.server``) at the ``INFO`` level or higher to the console. When :setting:`DEBUG` is ``False``: -* The ``django`` logger send messages with ``ERROR`` or ``CRITICAL`` level to +* The ``django`` logger sends messages in the ``django`` hierarchy (except + ``django.server``) with ``ERROR`` or ``CRITICAL`` level to :class:`AdminEmailHandler`. Independent of the value of :setting:`DEBUG`: -* The :ref:`django-server-logger` logger sends all messages at the ``INFO`` - level or higher to the console. +* The :ref:`django-server-logger` logger sends messages at the ``INFO`` level + or higher to the console. + +All loggers except :ref:`django-server-logger` propagate logging to their +parents, up to the root ``django`` logger. The ``console`` and ``mail_admins`` +handlers are attached to the root logger to provide the behavior described +above. See also :ref:`Configuring logging ` to learn how you can -complement or replace this default logging configuration. +complement or replace this default logging configuration defined in +:source:`django/utils/log.py`. diff --git a/docs/topics/migrations.txt b/docs/topics/migrations.txt index 2be05903aebe..04f2fe5695d6 100644 --- a/docs/topics/migrations.txt +++ b/docs/topics/migrations.txt @@ -67,9 +67,10 @@ PostgreSQL ---------- PostgreSQL is the most capable of all the databases here in terms of schema -support; the only caveat is that adding columns with default values will -cause a full rewrite of the table, for a time proportional to its size. +support. +The only caveat is that prior to PostgreSQL 11, adding columns with default +values causes a full rewrite of the table, for a time proportional to its size. For this reason, it's recommended you always create new columns with ``null=True``, as this way they will be added immediately. @@ -190,6 +191,10 @@ restrict to a single app. Restricting to a single app (either in a guarantee; any other apps that need to be used to get dependencies correct will be. +Apps without migrations must not have relations (``ForeignKey``, +``ManyToManyField``, etc.) to apps with migrations. Sometimes it may work, but +it's not supported. + .. _migration-files: Migration files @@ -205,11 +210,11 @@ A basic migration file looks like this:: class Migration(migrations.Migration): - dependencies = [("migrations", "0001_initial")] + dependencies = [('migrations', '0001_initial')] operations = [ - migrations.DeleteModel("Tribble"), - migrations.AddField("Author", "rating", models.IntegerField(default=0)), + migrations.DeleteModel('Tribble'), + migrations.AddField('Author', 'rating', models.IntegerField(default=0)), ] What Django looks for when it loads a migration file (as a Python module) is @@ -314,11 +319,6 @@ new migrations until it's fixed. When using multiple databases, you can use the ` to control which databases :djadmin:`makemigrations` checks for consistent history. -.. versionchanged:: 1.10 - - Migration consistency checks were added. Checks based on database routers - were added in 1.10.1. - Adding migrations to apps ========================= @@ -349,6 +349,30 @@ Note that this only works given two things: that your database doesn't match your models, you'll just get errors when migrations try to modify those tables. +Reverting migrations +==================== + +Any migration can be reverted with :djadmin:`migrate` by using the number of +previous migrations:: + + $ python manage.py migrate books 0002 + Operations to perform: + Target specific migration: 0002_auto, from books + Running migrations: + Rendering model states... DONE + Unapplying books.0003_auto... OK + +If you want to revert all migrations applied for an app, use the name +``zero``:: + + $ python manage.py migrate books zero + Operations to perform: + Unapply all migrations: books + Running migrations: + Rendering model states... DONE + Unapplying books.0002_auto... OK + Unapplying books.0001_initial... OK + .. _historical-models: Historical models @@ -357,8 +381,19 @@ Historical models When you run migrations, Django is working from historical versions of your models stored in the migration files. If you write Python code using the :class:`~django.db.migrations.operations.RunPython` operation, or if you have -``allow_migrate`` methods on your database routers, you will be exposed to -these versions of your models. +``allow_migrate`` methods on your database routers, you **need to use** these +historical model versions rather than importing them directly. + +.. warning:: + + If you import models directly rather than using the historical models, + your migrations *may work initially* but will fail in the future when you + try to re-run old migrations (commonly, when you set up a new installation + and run through all the migrations to set up the database). + + This means that historical model problems may not be immediately obvious. + If you run into this kind of failure, it's OK to edit the migration to use + the historical models rather than direct imports and commit those changes. Because it's impossible to serialize arbitrary Python code, these historical models will not have any custom methods that you have defined. They will, @@ -379,11 +414,11 @@ classes will need to be kept around for as long as there is a migration referencing them. Any :doc:`custom model fields ` will also need to be kept, since these are imported directly by migrations. -In addition, the base classes of the model are just stored as pointers, so you -must always keep base classes around for as long as there is a migration that -contains a reference to them. On the plus side, methods and managers from these -base classes inherit normally, so if you absolutely need access to these you -can opt to move them into a superclass. +In addition, the concrete base classes of the model are stored as pointers, so +you must always keep base classes around for as long as there is a migration +that contains a reference to them. On the plus side, methods and managers from +these base classes inherit normally, so if you absolutely need access to these +you can opt to move them into a superclass. To remove old references, you can :ref:`squash migrations ` or, if there aren't many references, copy them into the migration files. @@ -459,14 +494,10 @@ the file in the right place, suggest a name, and add dependencies for you):: Then, open up the file; it should look something like this:: - # -*- coding: utf-8 -*- # Generated by Django A.B on YYYY-MM-DD HH:MM - from __future__ import unicode_literals - - from django.db import migrations, models + from django.db import migrations class Migration(migrations.Migration): - initial = True dependencies = [ ('yourappname', '0001_initial'), @@ -490,21 +521,17 @@ combined values of ``first_name`` and ``last_name`` (we've come to our senses and realized that not everyone has first and last names). All we need to do is use the historical model and iterate over the rows:: - # -*- coding: utf-8 -*- - from __future__ import unicode_literals - - from django.db import migrations, models + from django.db import migrations def combine_names(apps, schema_editor): # We can't import the Person model directly as it may be a newer # version than this migration expects. We use the historical version. - Person = apps.get_model("yourappname", "Person") + Person = apps.get_model('yourappname', 'Person') for person in Person.objects.all(): - person.name = "%s %s" % (person.first_name, person.last_name) + person.name = '%s %s' % (person.first_name, person.last_name) person.save() class Migration(migrations.Migration): - initial = True dependencies = [ ('yourappname', '0001_initial'), @@ -618,6 +645,9 @@ work:: all instances of the codebase have applied the migrations you squashed, you can delete them. +Use the :option:`squashmigrations --squashed-name` option if you want to set +the name of the squashed migration rather than use an autogenerated one. + Note that model interdependencies in Django can get very complex, and squashing may result in migrations that do not run; either mis-optimized (in which case you can try again with ``--no-optimize``, though you should also report an issue), @@ -626,9 +656,10 @@ or with a ``CircularDependencyError``, in which case you can manually resolve it To manually resolve a ``CircularDependencyError``, break out one of the ForeignKeys in the circular dependency loop into a separate migration, and move the dependency on the other app with it. If you're unsure, -see how makemigrations deals with the problem when asked to create brand -new migrations from your models. In a future release of Django, squashmigrations -will be updated to attempt to resolve these errors itself. +see how :djadmin:`makemigrations` deals with the problem when asked to create +brand new migrations from your models. In a future release of Django, +:djadmin:`squashmigrations` will be updated to attempt to resolve these errors +itself. Once you've squashed your migration, you should then commit it alongside the migrations it replaces and distribute this change to all running instances @@ -664,32 +695,29 @@ for basic values, and doesn't specify import paths). Django can serialize the following: -- ``int``, ``long``, ``float``, ``bool``, ``str``, ``unicode``, ``bytes``, ``None`` -- ``list``, ``set``, ``tuple``, ``dict`` +- ``int``, ``float``, ``bool``, ``str``, ``bytes``, ``None``, ``NoneType`` +- ``list``, ``set``, ``tuple``, ``dict``, ``range``. - ``datetime.date``, ``datetime.time``, and ``datetime.datetime`` instances (include those that are timezone-aware) - ``decimal.Decimal`` instances - ``enum.Enum`` instances - ``uuid.UUID`` instances -- ``functools.partial`` instances which have serializable ``func``, ``args``, - and ``keywords`` values. +- :func:`functools.partial` and :class:`functools.partialmethod` instances + which have serializable ``func``, ``args``, and ``keywords`` values. - ``LazyObject`` instances which wrap a serializable value. - Any Django field - Any function or method reference (e.g. ``datetime.datetime.today``) (must be in module's top-level scope) +- Unbound methods used from within the class body - Any class reference (must be in module's top-level scope) - Anything with a custom ``deconstruct()`` method (:ref:`see below `) -.. versionchanged:: 1.10 - - Serialization support for ``enum.Enum`` was added. +.. versionchanged:: 2.1 -.. versionchanged:: 1.11 + Serialization support for :class:`functools.partialmethod` was added. - Serialization support for ``uuid.UUID`` was added. +.. versionchanged:: 2.2 -Django can serialize the following on Python 3 only: - -- Unbound methods used from within the class body (see below) + Serialization support for ``NoneType`` was added. Django cannot serialize: @@ -697,20 +725,34 @@ Django cannot serialize: - Arbitrary class instances (e.g. ``MyClass(4.3, 5.7)``) - Lambdas -Due to the fact ``__qualname__`` was only introduced in Python 3, Django can only -serialize the following pattern (an unbound method used within the class body) -on Python 3, and will fail to serialize a reference to it on Python 2:: +.. _custom-migration-serializers: - class MyModel(models.Model): +Custom serializers +------------------ + +.. versionadded:: 2.2 + +You can serialize other types by writing a custom serializer. For example, if +Django didn't serialize :class:`~decimal.Decimal` by default, you could do +this:: + + from decimal import Decimal + + from django.db.migrations.serializer import BaseSerializer + from django.db.migrations.writer import MigrationWriter - def upload_to(self): - return "something dynamic" + class DecimalSerializer(BaseSerializer): + def serialize(self): + return repr(self.value), {'from decimal import Decimal'} - my_file = models.FileField(upload_to=upload_to) + MigrationWriter.register_serializer(Decimal, DecimalSerializer) -If you are using Python 2, we recommend you move your methods for upload_to -and similar arguments that accept callables (e.g. ``default``) to live in -the main module body, rather than the class body. +The first argument of ``MigrationWriter.register_serializer()`` is a type or +iterable of types that should use the serializer. + +The ``serialize()`` method of your serializer must return a string of how the +value should appear in migrations and a set of any imports that are needed in +the migration. .. _custom-deconstruct-method: @@ -752,7 +794,7 @@ serializable, you can use the ``@deconstructible`` class decorator from from django.utils.deconstruct import deconstructible @deconstructible - class MyCustomClass(object): + class MyCustomClass: def __init__(self, foo=1): self.foo = foo @@ -766,26 +808,6 @@ The decorator adds logic to capture and preserve the arguments on their way into your constructor, and then returns those arguments exactly when deconstruct() is called. -Supporting Python 2 and 3 -========================= - -In order to generate migrations that support both Python 2 and 3, all string -literals used in your models and fields (e.g. ``verbose_name``, -``related_name``, etc.), must be consistently either bytestrings or text -(unicode) strings in both Python 2 and 3 (rather than bytes in Python 2 and -text in Python 3, the default situation for unmarked string literals.) -Otherwise running :djadmin:`makemigrations` under Python 3 will generate -spurious new migrations to convert all these string attributes to text. - -The easiest way to achieve this is to follow the advice in Django's -:doc:`Python 3 porting guide ` and make sure that all your -modules begin with ``from __future__ import unicode_literals``, so that all -unmarked string literals are always unicode, regardless of Python version. When -you add this to an app with existing migrations generated on Python 2, your -next run of :djadmin:`makemigrations` on Python 3 will likely generate many -changes as it converts all the bytestring attributes to text strings; this is -normal and should only happen once. - Supporting multiple Django versions =================================== diff --git a/docs/topics/pagination.txt b/docs/topics/pagination.txt index d2737fd5229e..39b9828d99f7 100644 --- a/docs/topics/pagination.txt +++ b/docs/topics/pagination.txt @@ -24,7 +24,7 @@ page:: 4 >>> p.num_pages 2 - >>> type(p.page_range) # `` in Python 2. + >>> type(p.page_range) >>> p.page_range range(1, 3) @@ -74,9 +74,10 @@ page:: objects such as Django's ``QuerySet`` to use a more efficient ``count()`` method when available. +.. _using-paginator-in-view: Using ``Paginator`` in a view -============================== +============================= Here's a slightly more complex example using :class:`Paginator` in a view to paginate a queryset. We give both the view and the accompanying template to @@ -85,7 +86,7 @@ show how you can display the results. This example assumes you have a The view function looks like this:: - from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger + from django.core.paginator import Paginator from django.shortcuts import render def listing(request): @@ -93,29 +94,24 @@ The view function looks like this:: paginator = Paginator(contact_list, 25) # Show 25 contacts per page page = request.GET.get('page') - try: - contacts = paginator.page(page) - except PageNotAnInteger: - # If page is not an integer, deliver first page. - contacts = paginator.page(1) - except EmptyPage: - # If page is out of range (e.g. 9999), deliver last page of results. - contacts = paginator.page(paginator.num_pages) - + contacts = paginator.get_page(page) return render(request, 'list.html', {'contacts': contacts}) In the template :file:`list.html`, you'll want to include navigation between -pages along with any interesting information from the objects themselves:: +pages along with any interesting information from the objects themselves: + +.. code-block:: html+django {% for contact in contacts %} {# Each "contact" is a Contact model object. #} - {{ contact.full_name|upper }}
      + {{ contact.full_name|upper }}
      ... {% endfor %} @@ -161,14 +158,14 @@ Optional arguments ------------------ ``orphans`` - The minimum number of items allowed on the last page, defaults to zero. Use this when you don't want to have a last page with very few items. If the last page would normally have a number of items less than or equal to ``orphans``, then those items will be added to the previous page (which becomes the last page) instead of leaving the items on a page by themselves. For example, with 23 items, ``per_page=10``, and ``orphans=3``, there will be two pages; the first page with 10 items and - the second (and last) page with 13 items. + the second (and last) page with 13 items. ``orphans`` defaults to zero, + which means pages are never combined and the last page may have one item. ``allow_empty_first_page`` Whether or not the first page is allowed to be empty. If ``False`` and @@ -177,6 +174,18 @@ Optional arguments Methods ------- +.. method:: Paginator.get_page(number) + + Returns a :class:`Page` object with the given 1-based index, while also + handling out of range and invalid page numbers. + + If the page isn't a number, it returns the first page. If the page number + is negative or greater than the number of pages, it returns the last page. + + It raises an exception (:exc:`EmptyPage`) only if you specify + ``Paginator(..., allow_empty_first_page=False)`` and the ``object_list`` is + empty. + .. method:: Paginator.page(number) Returns a :class:`Page` object with the given 1-based index. Raises diff --git a/docs/topics/performance.txt b/docs/topics/performance.txt index 8f2516a4eb3d..4ccf158241ee 100644 --- a/docs/topics/performance.txt +++ b/docs/topics/performance.txt @@ -56,10 +56,10 @@ Django tools ~~~~~~~~~~~~ `django-debug-toolbar -`_ is a very -handy tool that provides insights into what your code is doing and how much -time it spends doing it. In particular it can show you all the SQL queries your -page is generating, and how long each one has taken. +`_ is a very handy tool that +provides insights into what your code is doing and how much time it spends +doing it. In particular it can show you all the SQL queries your page is +generating, and how long each one has taken. Third-party panels are also available for the toolbar, that can (for example) report on cache performance and template rendering times. @@ -235,7 +235,7 @@ Databases Database optimization --------------------- -Django’s database layer provides various ways to help developers get the best +Django's database layer provides various ways to help developers get the best performance from their databases. The :doc:`database optimization documentation ` gathers together links to the relevant documentation and adds various tips that outline the steps to take when @@ -290,13 +290,13 @@ Static files Static files, which by definition are not dynamic, make an excellent target for optimization gains. -:class:`~django.contrib.staticfiles.storage.CachedStaticFilesStorage` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +:class:`~django.contrib.staticfiles.storage.ManifestStaticFilesStorage` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ By taking advantage of web browsers' caching abilities, you can eliminate network hits entirely for a given file after the initial download. -:class:`~django.contrib.staticfiles.storage.CachedStaticFilesStorage` appends a +:class:`~django.contrib.staticfiles.storage.ManifestStaticFilesStorage` appends a content-dependent tag to the filenames of :doc:`static files ` to make it safe for browsers to cache them long-term without missing future changes - when a file changes, so will the @@ -410,15 +410,15 @@ performance gains for your application to outweigh the potential risks. With these caveats in mind, you should be aware of: -`PyPy `_ -~~~~~~~~~~~~~~~~~~~~~~~~~~ +`PyPy `_ +~~~~~~~~~~~~~~~~~~~~~~~~~~~ -`PyPy `_ is an implementation of Python in Python itself (the -'standard' Python implementation is in C). PyPy can offer substantial +`PyPy `_ is an implementation of Python in Python itself +(the 'standard' Python implementation is in C). PyPy can offer substantial performance gains, typically for heavyweight applications. A key aim of the PyPy project is `compatibility -`_ with existing Python APIs and libraries. +`_ with existing Python APIs and libraries. Django is compatible, but you will need to check the compatibility of other libraries you rely on. diff --git a/docs/topics/python3.txt b/docs/topics/python3.txt deleted file mode 100644 index 04a975d8f292..000000000000 --- a/docs/topics/python3.txt +++ /dev/null @@ -1,394 +0,0 @@ -=================== -Porting to Python 3 -=================== - -Django 1.5 is the first version of Django to support Python 3. The same code -runs both on Python 2 (≥ 2.6.5) and Python 3 (≥ 3.2), thanks to the six_ -compatibility layer. - -.. _six: https://pythonhosted.org/six/ - -This document is primarily targeted at authors of pluggable applications -who want to support both Python 2 and 3. It also describes guidelines that -apply to Django's code. - -Philosophy -========== - -This document assumes that you are familiar with the changes between Python 2 -and Python 3. If you aren't, read :ref:`Python's official porting guide -` first. Refreshing your knowledge of unicode handling on -Python 2 and 3 will help; the `Pragmatic Unicode`_ presentation is a good -resource. - -Django uses the *Python 2/3 Compatible Source* strategy. Of course, you're -free to chose another strategy for your own code, especially if you don't need -to stay compatible with Python 2. But authors of pluggable applications are -encouraged to use the same porting strategy as Django itself. - -Writing compatible code is much easier if you target Python ≥ 2.6. Django 1.5 -introduces compatibility tools such as :mod:`django.utils.six`, which is a -customized version of the :mod:`six module `. For convenience, -forwards-compatible aliases were introduced in Django 1.4.2. If your -application takes advantage of these tools, it will require Django ≥ 1.4.2. - -Obviously, writing compatible source code adds some overhead, and that can -cause frustration. Django's developers have found that attempting to write -Python 3 code that's compatible with Python 2 is much more rewarding than the -opposite. Not only does that make your code more future-proof, but Python 3's -advantages (like the saner string handling) start shining quickly. Dealing -with Python 2 becomes a backwards compatibility requirement, and we as -developers are used to dealing with such constraints. - -Porting tools provided by Django are inspired by this philosophy, and it's -reflected throughout this guide. - -.. _Pragmatic Unicode: http://nedbatchelder.com/text/unipain.html - -Porting tips -============ - -Unicode literals ----------------- - -This step consists in: - -- Adding ``from __future__ import unicode_literals`` at the top of your Python - modules -- it's best to put it in each and every module, otherwise you'll - keep checking the top of your files to see which mode is in effect; -- Removing the ``u`` prefix before unicode strings; -- Adding a ``b`` prefix before bytestrings. - -Performing these changes systematically guarantees backwards compatibility. - -However, Django applications generally don't need bytestrings, since Django -only exposes unicode interfaces to the programmer. Python 3 discourages using -bytestrings, except for binary data or byte-oriented interfaces. Python 2 -makes bytestrings and unicode strings effectively interchangeable, as long as -they only contain ASCII data. Take advantage of this to use unicode strings -wherever possible and avoid the ``b`` prefixes. - -.. note:: - - Python 2's ``u`` prefix is a syntax error in Python 3.2 but it will be - allowed again in Python 3.3 thanks to :pep:`414`. Thus, this - transformation is optional if you target Python ≥ 3.3. It's still - recommended, per the "write Python 3 code" philosophy. - -String handling ---------------- - -Python 2's `unicode`_ type was renamed :class:`str` in Python 3, -``str()`` was renamed :class:`bytes`, and `basestring`_ disappeared. -six_ provides :ref:`tools ` to deal with these -changes. - -Django also contains several string related classes and functions in the -:mod:`django.utils.encoding` and :mod:`django.utils.safestring` modules. Their -names used the words ``str``, which doesn't mean the same thing in Python 2 -and Python 3, and ``unicode``, which doesn't exist in Python 3. In order to -avoid ambiguity and confusion these concepts were renamed ``bytes`` and -``text``. - -Here are the name changes in :mod:`django.utils.encoding`: - -================== ================== -Old name New name -================== ================== -``smart_str`` ``smart_bytes`` -``smart_unicode`` ``smart_text`` -``force_unicode`` ``force_text`` -================== ================== - -For backwards compatibility, the old names still work on Python 2. Under -Python 3, ``smart_str`` is an alias for ``smart_text``. - -For forwards compatibility, the new names work as of Django 1.4.2. - -.. note:: - - :mod:`django.utils.encoding` was deeply refactored in Django 1.5 to - provide a more consistent API. Check its documentation for more - information. - -:mod:`django.utils.safestring` is mostly used via the -:func:`~django.utils.safestring.mark_safe` and -:func:`~django.utils.safestring.mark_for_escaping` functions, which didn't -change. In case you're using the internals, here are the name changes: - -================== ================== -Old name New name -================== ================== -``EscapeString`` ``EscapeBytes`` -``EscapeUnicode`` ``EscapeText`` -``SafeString`` ``SafeBytes`` -``SafeUnicode`` ``SafeText`` -================== ================== - -For backwards compatibility, the old names still work on Python 2. Under -Python 3, ``EscapeString`` and ``SafeString`` are aliases for ``EscapeText`` -and ``SafeText`` respectively. - -For forwards compatibility, the new names work as of Django 1.4.2. - -``__str__()`` and ``__unicode__()`` methods -------------------------------------------- - -In Python 2, the object model specifies :meth:`~object.__str__` and -` __unicode__()`_ methods. If these methods exist, they must return -``str`` (bytes) and ``unicode`` (text) respectively. - -The ``print`` statement and the :class:`str` built-in call -:meth:`~object.__str__` to determine the human-readable representation of an -object. The ``unicode`` built-in calls ` __unicode__()`_ if it -exists, and otherwise falls back to :meth:`~object.__str__` and decodes the -result with the system encoding. Conversely, the -:class:`~django.db.models.Model` base class automatically derives -:meth:`~object.__str__` from ` __unicode__()`_ by encoding to UTF-8. - -In Python 3, there's simply :meth:`~object.__str__`, which must return ``str`` -(text). - -(It is also possible to define :meth:`~object.__bytes__`, but Django applications -have little use for that method, because they hardly ever deal with ``bytes``.) - -Django provides a simple way to define :meth:`~object.__str__` and -` __unicode__()`_ methods that work on Python 2 and 3: you must -define a :meth:`~object.__str__` method returning text and to apply the -:func:`~django.utils.encoding.python_2_unicode_compatible` decorator. - -On Python 3, the decorator is a no-op. On Python 2, it defines appropriate -` __unicode__()`_ and :meth:`~object.__str__` methods (replacing the -original :meth:`~object.__str__` method in the process). Here's an example:: - - from __future__ import unicode_literals - from django.utils.encoding import python_2_unicode_compatible - - @python_2_unicode_compatible - class MyClass(object): - def __str__(self): - return "Instance of my class" - -This technique is the best match for Django's porting philosophy. - -For forwards compatibility, this decorator is available as of Django 1.4.2. - -Finally, note that :meth:`~object.__repr__` must return a ``str`` on all -versions of Python. - -:class:`dict` and :class:`dict`-like classes --------------------------------------------- - -:meth:`dict.keys`, :meth:`dict.items` and :meth:`dict.values` return lists in -Python 2 and iterators in Python 3. :class:`~django.http.QueryDict` and the -:class:`dict`-like classes defined in ``django.utils.datastructures`` -behave likewise in Python 3. - -six_ provides compatibility functions to work around this change: -:func:`~six.iterkeys`, :func:`~six.iteritems`, and :func:`~six.itervalues`. -It also contains an undocumented ``iterlists`` function that works well for -``django.utils.datastructures.MultiValueDict`` and its subclasses. - -:class:`~django.http.HttpRequest` and :class:`~django.http.HttpResponse` objects --------------------------------------------------------------------------------- - -According to :pep:`3333`: - -- headers are always ``str`` objects, -- input and output streams are always ``bytes`` objects. - -Specifically, :attr:`HttpResponse.content ` -contains ``bytes``, which may become an issue if you compare it with a -``str`` in your tests. The preferred solution is to rely on -:meth:`~django.test.SimpleTestCase.assertContains` and -:meth:`~django.test.SimpleTestCase.assertNotContains`. These methods accept a -response and a unicode string as arguments. - -Coding guidelines -================= - -The following guidelines are enforced in Django's source code. They're also -recommended for third-party applications that follow the same porting strategy. - -Syntax requirements -------------------- - -Unicode -~~~~~~~ - -In Python 3, all strings are considered Unicode by default. The ``unicode`` -type from Python 2 is called ``str`` in Python 3, and ``str`` becomes -``bytes``. - -You mustn't use the ``u`` prefix before a unicode string literal because it's -a syntax error in Python 3.2. You must prefix byte strings with ``b``. - -In order to enable the same behavior in Python 2, every module must import -``unicode_literals`` from ``__future__``:: - - from __future__ import unicode_literals - - my_string = "This is an unicode literal" - my_bytestring = b"This is a bytestring" - -If you need a byte string literal under Python 2 and a unicode string literal -under Python 3, use the :class:`str` builtin:: - - str('my string') - -In Python 3, there aren't any automatic conversions between ``str`` and -``bytes``, and the :mod:`codecs` module became more strict. :meth:`str.encode` -always returns ``bytes``, and ``bytes.decode`` always returns ``str``. As a -consequence, the following pattern is sometimes necessary:: - - value = value.encode('ascii', 'ignore').decode('ascii') - -Be cautious if you have to `index bytestrings`_. - -.. _index bytestrings: https://docs.python.org/3/howto/pyporting.html#text-versus-binary-data - -Exceptions -~~~~~~~~~~ - -When you capture exceptions, use the ``as`` keyword:: - - try: - ... - except MyException as exc: - ... - -This older syntax was removed in Python 3:: - - try: - ... - except MyException, exc: # Don't do that! - ... - -The syntax to reraise an exception with a different traceback also changed. -Use :func:`six.reraise`. - -Magic methods -------------- - -Use the patterns below to handle magic methods renamed in Python 3. - -Iterators -~~~~~~~~~ - -:: - - class MyIterator(six.Iterator): - def __iter__(self): - return self # implement some logic here - - def __next__(self): - raise StopIteration # implement some logic here - -Boolean evaluation -~~~~~~~~~~~~~~~~~~ - -:: - - class MyBoolean(object): - - def __bool__(self): - return True # implement some logic here - - def __nonzero__(self): # Python 2 compatibility - return type(self).__bool__(self) - -Division -~~~~~~~~ - -:: - - class MyDivisible(object): - - def __truediv__(self, other): - return self / other # implement some logic here - - def __div__(self, other): # Python 2 compatibility - return type(self).__truediv__(self, other) - - def __itruediv__(self, other): - return self // other # implement some logic here - - def __idiv__(self, other): # Python 2 compatibility - return type(self).__itruediv__(self, other) - -Special methods are looked up on the class and not on the instance to reflect -the behavior of the Python interpreter. - -.. module: django.utils.six - -Writing compatible code with six --------------------------------- - -six_ is the canonical compatibility library for supporting Python 2 and 3 in -a single codebase. Read its documentation! - -A :mod:`customized version of six ` is bundled with Django -as of version 1.4.2. You can import it as ``django.utils.six``. - -Here are the most common changes required to write compatible code. - -.. _string-handling-with-six: - -String handling -~~~~~~~~~~~~~~~ - -The ``basestring`` and ``unicode`` types were removed in Python 3, and the -meaning of ``str`` changed. To test these types, use the following idioms:: - - isinstance(myvalue, six.string_types) # replacement for basestring - isinstance(myvalue, six.text_type) # replacement for unicode - isinstance(myvalue, bytes) # replacement for str - -Python ≥ 2.6 provides ``bytes`` as an alias for ``str``, so you don't need -:data:`six.binary_type`. - -``long`` -~~~~~~~~ - -The ``long`` type no longer exists in Python 3. ``1L`` is a syntax error. Use -:data:`six.integer_types` check if a value is an integer or a long:: - - isinstance(myvalue, six.integer_types) # replacement for (int, long) - -``xrange`` -~~~~~~~~~~ - -If you use ``xrange`` on Python 2, import ``six.moves.range`` and use that -instead. You can also import ``six.moves.xrange`` (it's equivalent to -``six.moves.range``) but the first technique allows you to simply drop the -import when dropping support for Python 2. - -Moved modules -~~~~~~~~~~~~~ - -Some modules were renamed in Python 3. The ``django.utils.six.moves`` -module (based on the :mod:`six.moves module `) provides a -compatible location to import them. - -``PY2`` -~~~~~~~ - -If you need different code in Python 2 and Python 3, check :data:`six.PY2`:: - - if six.PY2: - # compatibility code for Python 2 - -This is a last resort solution when :mod:`six` doesn't provide an appropriate -function. - -.. module:: django.utils.six - -Django customized version of ``six`` ------------------------------------- - -The version of six bundled with Django (``django.utils.six``) includes a few -customizations for internal use only. - -.. _unicode: https://docs.python.org/2/library/functions.html#unicode -.. _ __unicode__(): https://docs.python.org/2/reference/datamodel.html#object.__unicode__ -.. _basestring: https://docs.python.org/2/library/functions.html#basestring diff --git a/docs/topics/security.txt b/docs/topics/security.txt index 8d7b9c91f1d3..1a155455128a 100644 --- a/docs/topics/security.txt +++ b/docs/topics/security.txt @@ -10,7 +10,7 @@ on securing a Django-powered site. Cross site scripting (XSS) protection ===================================== -.. highlightlang:: html+django +.. highlight:: html+django XSS attacks allow a user to inject client side scripts into the browsers of other users. This is usually achieved by storing the malicious scripts in the @@ -90,14 +90,17 @@ SQL injection is a type of attack where a malicious user is able to execute arbitrary SQL code on a database. This can result in records being deleted or data leakage. -By using Django's querysets, the resulting SQL will be properly escaped by -the underlying database driver. However, Django also gives developers power to -write :ref:`raw queries ` or execute -:ref:`custom sql `. These capabilities should be used -sparingly and you should always be careful to properly escape any parameters -that the user can control. In addition, you should exercise caution when using -:meth:`~django.db.models.query.QuerySet.extra` and -:class:`~django.db.models.expressions.RawSQL`. +Django's querysets are protected from SQL injection since their queries are +constructed using query parameterization. A query's SQL code is defined +separately from the query's parameters. Since parameters may be user-provided +and therefore unsafe, they are escaped by the underlying database driver. + +Django also gives developers power to write :ref:`raw queries +` or execute :ref:`custom sql `. +These capabilities should be used sparingly and you should always be careful to +properly escape any parameters that the user can control. In addition, you +should exercise caution when using :meth:`~django.db.models.query.QuerySet.extra` +and :class:`~django.db.models.expressions.RawSQL`. Clickjacking protection ======================= @@ -241,7 +244,7 @@ User-uploaded content validate all user uploaded file content, however, there are some other steps you can take to mitigate these attacks: - 1. One class of attacks can be prevented by always serving user uploaded + #. One class of attacks can be prevented by always serving user uploaded content from a distinct top-level or second-level domain. This prevents any exploit blocked by `same-origin policy`_ protections such as cross site scripting. For example, if your site runs on ``example.com``, you @@ -249,7 +252,7 @@ User-uploaded content from something like ``usercontent-example.com``. It's *not* sufficient to serve content from a subdomain like ``usercontent.example.com``. - 2. Beyond this, applications may choose to define a whitelist of allowable + #. Beyond this, applications may choose to define a whitelist of allowable file extensions for user uploaded files and configure the web server to only serve such files. @@ -280,4 +283,4 @@ security protection of the Web server, operating system and other components. accounted for in the design of your project. .. _LimitRequestBody: https://httpd.apache.org/docs/2.4/mod/core.html#limitrequestbody -.. _Top 10 list: https://www.owasp.org/index.php/Top_10_2013-Top_10 +.. _Top 10 list: https://www.owasp.org/index.php/Top_10-2017_Top_10 diff --git a/docs/topics/serialization.txt b/docs/topics/serialization.txt index 5db5e656ecb0..11d07bf301ab 100644 --- a/docs/topics/serialization.txt +++ b/docs/topics/serialization.txt @@ -99,7 +99,7 @@ attribute. The ``name`` attribute of the base class will be ignored. In order to fully serialize your ``Restaurant`` instances, you will need to serialize the ``Place`` models as well:: - all_objects = list(Restaurant.objects.all()) + list(Place.objects.all()) + all_objects = [*Restaurant.objects.all(), *Place.objects.all()] data = serializers.serialize('xml', all_objects) Deserializing data @@ -164,8 +164,8 @@ Identifier Information serializer is only available if PyYAML_ is installed. ========== ============================================================== -.. _json: http://json.org/ -.. _PyYAML: http://www.pyyaml.org/ +.. _json: https://json.org/ +.. _PyYAML: https://pyyaml.org/ XML --- @@ -180,7 +180,7 @@ The basic XML serialization format is quite simple:: -The whole collection of objects that is either serialized or de-serialized is +The whole collection of objects that is either serialized or deserialized is represented by a ````-tag which contains multiple ````-elements. Each such object has two attributes: "pk" and "model", the latter being represented by the name of the app ("sessions") and the @@ -198,11 +198,11 @@ Foreign keys and other relational fields are treated a little bit differently:: -In this example we specify that the auth.Permission object with the PK 27 has -a foreign key to the contenttypes.ContentType instance with the PK 9. +In this example we specify that the ``auth.Permission`` object with the PK 27 +has a foreign key to the ``contenttypes.ContentType`` instance with the PK 9. ManyToMany-relations are exported for the model that binds them. For instance, -the auth.User model has such a relation to the auth.Permission model:: +the ``auth.User`` model has such a relation to the ``auth.Permission`` model:: @@ -220,7 +220,7 @@ This example links the given user with the permission models with PKs 46 and 47. accepted in the XML 1.0 standard, the serialization will fail with a :exc:`ValueError` exception. Read also the W3C's explanation of `HTML, XHTML, XML and Control Codes - `_. + `_. .. _serialization-formats-json: @@ -256,14 +256,13 @@ For example, if you have some custom type in an object to be serialized, you'll have to write a custom :mod:`json` encoder for it. Something like this will work:: - from django.utils.encoding import force_text from django.core.serializers.json import DjangoJSONEncoder class LazyEncoder(DjangoJSONEncoder): def default(self, obj): if isinstance(obj, YourCustomType): - return force_text(obj) - return super(LazyEncoder, self).default(obj) + return str(obj) + return super().default(obj) You can then pass ``cls=LazyEncoder`` to the ``serializers.serialize()`` function:: @@ -272,10 +271,6 @@ function:: serialize('json', SomeModel.objects.all(), cls=LazyEncoder) -.. versionchanged:: 1.11 - - The ability to use a custom encoder using ``cls=...`` was added. - Also note that GeoDjango provides a :doc:`customized GeoJSON serializer `. @@ -305,15 +300,7 @@ The JSON serializer uses ``DjangoJSONEncoder`` for encoding. A subclass of :class:`~decimal.Decimal`, ``Promise`` (``django.utils.functional.lazy()`` objects), :class:`~uuid.UUID` A string representation of the object. -.. versionchanged:: 1.10 - - Support for ``Promise`` was added. - -.. versionchanged:: 1.11 - - Support for :class:`~datetime.timedelta` was added. - -.. _ecma-262: http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15 +.. _ecma-262: https://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15 YAML ---- @@ -380,7 +367,7 @@ Consider the following two models:: birthdate = models.DateField() class Meta: - unique_together = (('first_name', 'last_name'),) + unique_together = [['first_name', 'last_name']] class Book(models.Model): name = models.CharField(max_length=100) @@ -417,15 +404,14 @@ name:: return self.get(first_name=first_name, last_name=last_name) class Person(models.Model): - objects = PersonManager() - first_name = models.CharField(max_length=100) last_name = models.CharField(max_length=100) - birthdate = models.DateField() + objects = PersonManager() + class Meta: - unique_together = (('first_name', 'last_name'),) + unique_together = [['first_name', 'last_name']] Now books can use that natural key to refer to ``Person`` objects:: @@ -466,18 +452,17 @@ So how do you get Django to emit a natural key when serializing an object? Firstly, you need to add another method -- this time to the model itself:: class Person(models.Model): - objects = PersonManager() - first_name = models.CharField(max_length=100) last_name = models.CharField(max_length=100) - birthdate = models.DateField() - def natural_key(self): - return (self.first_name, self.last_name) + objects = PersonManager() class Meta: - unique_together = (('first_name', 'last_name'),) + unique_together = [['first_name', 'last_name']] + + def natural_key(self): + return (self.first_name, self.last_name) That method should always return a natural key tuple -- in this example, ``(first name, last name)``. Then, when you call @@ -527,17 +512,68 @@ command line flags to generate natural keys. natural keys during serialization, but *not* be able to load those key values, just don't define the ``get_by_natural_key()`` method. +.. _natural-keys-and-forward-references: + +Natural keys and forward references +----------------------------------- + +.. versionadded:: 2.2 + +Sometimes when you use :ref:`natural foreign keys +` you'll need to deserialize data where +an object has a foreign key referencing another object that hasn't yet been +deserialized. This is called a "forward reference". + +For instance, suppose you have the following objects in your fixture:: + + ... + { + "model": "store.book", + "fields": { + "name": "Mostly Harmless", + "author": ["Douglas", "Adams"] + } + }, + ... + { + "model": "store.person", + "fields": { + "first_name": "Douglas", + "last_name": "Adams" + } + }, + ... + +In order to handle this situation, you need to pass +``handle_forward_references=True`` to ``serializers.deserialize()``. This will +set the ``deferred_fields`` attribute on the ``DeserializedObject`` instances. +You'll need to keep track of ``DeserializedObject`` instances where this +attribute isn't ``None`` and later call ``save_deferred_fields()`` on them. + +Typical usage looks like this:: + + objs_with_deferred_fields = [] + + for obj in serializers.deserialize('xml', data, handle_forward_references=True): + obj.save() + if obj.deferred_fields is not None: + objs_with_deferred_fields.append(obj) + + for obj in objs_with_deferred_fields: + obj.save_deferred_fields() + +For this to work, the ``ForeignKey`` on the referencing model must have +``null=True``. + Dependencies during serialization --------------------------------- -Since natural keys rely on database lookups to resolve references, it -is important that the data exists before it is referenced. You can't make -a "forward reference" with natural keys -- the data you're referencing -must exist before you include a natural key reference to that data. +It's often possible to avoid explicitly having to handle forward references by +taking care with the ordering of objects within a fixture. -To accommodate this limitation, calls to :djadmin:`dumpdata` that use -the :option:`dumpdata --natural-foreign` option will serialize any model with a -``natural_key()`` method before serializing standard primary key objects. +To help with this, calls to :djadmin:`dumpdata` that use the :option:`dumpdata +--natural-foreign` option will serialize any model with a ``natural_key()`` +method before serializing standard primary key objects. However, this may not always be enough. If your natural key refers to another object (by using a foreign key or natural key to another object diff --git a/docs/topics/settings.txt b/docs/topics/settings.txt index 25b3f94fe387..def574546fd9 100644 --- a/docs/topics/settings.txt +++ b/docs/topics/settings.txt @@ -46,7 +46,7 @@ The value of ``DJANGO_SETTINGS_MODULE`` should be in Python path syntax, e.g. ``mysite.settings``. Note that the settings module should be on the Python `import search path`_. -.. _import search path: http://www.diveintopython.net/getting_to_know_python/everything_is_an_object.html +.. _import search path: https://www.diveinto.org/python3/your-first-python-program.html#importsearchpath The ``django-admin`` utility ---------------------------- diff --git a/docs/topics/signals.txt b/docs/topics/signals.txt index 19c0cd3d33e2..df23875d3f85 100644 --- a/docs/topics/signals.txt +++ b/docs/topics/signals.txt @@ -49,7 +49,8 @@ Listening to signals To receive a signal, register a *receiver* function using the :meth:`Signal.connect` method. The receiver function is called when the signal -is sent. +is sent. All of the signal's receiver functions are called one at a time, in +the order they were registered. .. method:: Signal.connect(receiver, sender=None, weak=True, dispatch_uid=None) @@ -206,6 +207,12 @@ Defining and sending signals Your applications can take advantage of the signal infrastructure and provide its own signals. +.. admonition:: When to use custom signals + + Signals are implicit function calls which make debugging harder. If the + sender and receiver of your custom signal are both within your project, + you're better off using an explicit function call. + Defining signals ---------------- @@ -243,7 +250,7 @@ arguments as you like. For example, here's how sending our ``pizza_done`` signal might look:: - class PizzaStore(object): + class PizzaStore: ... def send_pizza(self, toppings, size): @@ -277,8 +284,3 @@ arguments are as described in :meth:`.Signal.connect`. The method returns The ``receiver`` argument indicates the registered receiver to disconnect. It may be ``None`` if ``dispatch_uid`` is used to identify the receiver. - -.. deprecated:: 1.9 - - The ``weak`` argument is deprecated as it has no effect. It will be removed - in Django 2.0. diff --git a/docs/topics/templates.txt b/docs/topics/templates.txt index 3ee9a01efdd1..a2579b41b499 100644 --- a/docs/topics/templates.txt +++ b/docs/topics/templates.txt @@ -36,6 +36,13 @@ For historical reasons, both the generic support for template engines and the implementation of the Django template language live in the ``django.template`` namespace. +.. warning:: + + The template system isn't safe against untrusted template authors. For + example, a site shouldn't allow its users to provide their own templates, + since template authors can do things like perform XSS attacks and access + properties of template variables that may contain sensitive information. + .. _template-engines: Support for template engines @@ -308,10 +315,6 @@ applications. This generic name was kept for backwards-compatibility. Only set it to ``False`` if you're rendering non-HTML templates! - .. versionadded:: 1.10 - - The ``autoescape`` option was added. - * ``'context_processors'``: a list of dotted Python paths to callables that are used to populate the context when a template is rendered with a request. These callables take a request object as their argument and return a @@ -348,7 +351,7 @@ applications. This generic name was kept for backwards-compatibility. * ``'file_charset'``: the charset used to read template files on disk. - It defaults to the value of :setting:`FILE_CHARSET`. + It defaults to ``'utf-8'``. * ``'libraries'``: A dictionary of labels and dotted Python paths of template tag modules to register with the template engine. This can be used to add @@ -380,7 +383,7 @@ applications. This generic name was kept for backwards-compatibility. Requires Jinja2_ to be installed: -.. code-block:: console +.. console:: $ pip install Jinja2 @@ -412,9 +415,31 @@ adds defaults that differ from Jinja2's for a few options: It defaults to an empty list. -.. versionadded:: 1.11 + .. admonition:: Using context processors with Jinja2 templates is discouraged. + + Context processors are useful with Django templates because Django templates + don't support calling functions with arguments. Since Jinja2 doesn't have + that limitation, it's recommended to put the function that you would use as a + context processor in the global variables available to the template using + ``jinja2.Environment`` as described below. You can then call that function in + the template: + + .. code-block:: jinja - The ``'context_processors'`` option was added. + {{ function(request) }} + + Some Django templates context processors return a fixed value. For Jinja2 + templates, this layer of indirection isn't necessary since you can add + constants directly in ``jinja2.Environment``. + + The original use case for adding context processors for Jinja2 involved: + + * Making an expensive computation that depends on the request. + * Needing the result in every template. + * Using the result multiple times in each template. + + Unless all of these conditions are met, passing a function to the template is + simpler and more in line with the design of Jinja2. The default configuration is purposefully kept to a minimum. If a template is rendered with a request (e.g. when using :py:func:`~django.shortcuts.render`), @@ -426,9 +451,7 @@ environment. For example, you can create ``myproject/jinja2.py`` with this content:: - from __future__ import absolute_import # Python 2 only - - from django.contrib.staticfiles.storage import staticfiles_storage + from django.templatetags.static import static from django.urls import reverse from jinja2 import Environment @@ -437,7 +460,7 @@ For example, you can create ``myproject/jinja2.py`` with this content:: def environment(**options): env = Environment(**options) env.globals.update({ - 'static': staticfiles_storage.url, + 'static': static, 'url': reverse, }) return env @@ -485,13 +508,13 @@ fictional ``foobar`` template library:: def __init__(self, params): params = params.copy() options = params.pop('OPTIONS').copy() - super(FooBar, self).__init__(params) + super().__init__(params) self.engine = foobar.Engine(**options) def from_string(self, template_code): try: - return Template(self.engine.from_string(template_code)) + return Template(self.engine.from_string(template_code)) except foobar.TemplateCompilationFailed as exc: raise TemplateSyntaxError(exc.args) @@ -504,7 +527,7 @@ fictional ``foobar`` template library:: raise TemplateSyntaxError(exc.args) - class Template(object): + class Template: def __init__(self, template): self.template = template @@ -634,7 +657,7 @@ creating an object that specifies the following attributes: The Django template language ============================ -.. highlightlang:: html+django +.. highlight:: html+django Syntax ------ @@ -804,4 +827,4 @@ Implementing a custom context processor is as simple as defining a function. .. _Jinja2: http://jinja.pocoo.org/ .. _DEP 182: https://github.com/django/deps/blob/master/final/0182-multiple-template-engines.rst -.. _Django Debug Toolbar: https://github.com/django-debug-toolbar/django-debug-toolbar +.. _Django Debug Toolbar: https://github.com/jazzband/django-debug-toolbar diff --git a/docs/topics/testing/advanced.txt b/docs/topics/testing/advanced.txt index 192a62c516ba..e9f64f1941c2 100644 --- a/docs/topics/testing/advanced.txt +++ b/docs/topics/testing/advanced.txt @@ -25,7 +25,7 @@ restricted subset of the test client API: :meth:`~Client.options()`, and :meth:`~Client.trace()`. * These methods accept all the same arguments *except* for - ``follows``. Since this is just a factory for producing + ``follow``. Since this is just a factory for producing requests, it's up to you to handle the response. * It does not support middleware. Session and authentication @@ -38,7 +38,7 @@ Example The following is a simple unit test using the request factory:: from django.contrib.auth.models import AnonymousUser, User - from django.test import TestCase, RequestFactory + from django.test import RequestFactory, TestCase from .views import MyView, my_view @@ -117,11 +117,6 @@ Disabling :setting:`ALLOWED_HOSTS` checking (``ALLOWED_HOSTS = ['*']``) when running tests prevents the test client from raising a helpful error message if you follow a redirect to an external URL. -.. versionchanged:: 1.11 - - Older versions didn't validate ``ALLOWED_HOSTS`` while testing so these - techniques weren't necessary. - .. _topics-testing-advanced-multidb: Tests and multiple databases @@ -203,7 +198,7 @@ example database configuration:: }, }, 'diamonds': { - ... db settings + # ... db settings 'TEST': { 'DEPENDENCIES': [], }, @@ -254,7 +249,7 @@ Advanced features of ``TransactionTestCase`` By default, ``available_apps`` is set to ``None``. After each test, Django calls :djadmin:`flush` to reset the database state. This empties all tables and emits the :data:`~django.db.models.signals.post_migrate` signal, which - re-creates one content type and three permissions for each model. This + recreates one content type and four permissions for each model. This operation gets expensive proportionally to the number of models. Setting ``available_apps`` to a list of applications instructs Django to @@ -303,7 +298,41 @@ Advanced features of ``TransactionTestCase`` recommended that you do not hard code primary key values in tests. Using ``reset_sequences = True`` will slow down the test, since the primary - key reset is an relatively expensive database operation. + key reset is a relatively expensive database operation. + +.. _topics-testing-enforce-run-sequentially: + +Enforce running test classes sequentially +========================================= + +If you have test classes that cannot be run in parallel (e.g. because they +share a common resource), you can use ``django.test.testcases.SerializeMixin`` +to run them sequentially. This mixin uses a filesystem ``lockfile``. + +For example, you can use ``__file__`` to determine that all test classes in the +same file that inherit from ``SerializeMixin`` will run sequentially:: + + import os + + from django.test import TestCase + from django.test.testcases import SerializeMixin + + class ImageTestCaseMixin(SerializeMixin): + lockfile = __file__ + + def setUp(self): + self.filename = os.path.join(temp_storage_dir, 'my_file.png') + self.file = create_file(self.filename) + + class RemoveImageTests(ImageTestCaseMixin, TestCase): + def test_remove_image(self): + os.remove(self.filename) + self.assertFalse(os.path.exists(self.filename)) + + class ResizeImageTests(ImageTestCaseMixin, TestCase): + def test_resize_image(self): + resize_image(self.file, (48, 48)) + self.assertEqual(get_image_size(self.file), (48, 48)) .. _testing-reusable-applications: @@ -330,8 +359,8 @@ following structure:: Let's take a look inside a couple of those files: -.. snippet:: - :filename: runtests.py +.. code-block:: python + :caption: runtests.py #!/usr/bin/env python import os @@ -358,8 +387,8 @@ necessary to use the Django test runner. You may want to add command-line options for controlling verbosity, passing in specific test labels to run, etc. -.. snippet:: - :filename: tests/test_settings.py +.. code-block:: python + :caption: tests/test_settings.py SECRET_KEY = 'fake-key' INSTALLED_APPS = [ @@ -410,10 +439,6 @@ testing behavior. This behavior involves: #. Performing global post-test teardown. -.. versionchanged:: 1.11 - - Running the system checks was added. - If you define your own test runner class and point :setting:`TEST_RUNNER` at that class, Django will execute your test runner whenever you run ``./manage.py test``. In this way, it is possible to use any test framework @@ -482,10 +507,6 @@ execute and tear down the test suite. custom arguments by calling ``parser.add_argument()`` inside the method, so that the :djadmin:`test` command will be able to use those arguments. - .. versionadded:: 1.11 - - The ``debug_mode`` keyword argument was added. - Attributes ~~~~~~~~~~ @@ -574,8 +595,6 @@ Methods .. method:: DiscoverRunner.run_checks() - .. versionadded:: 1.11 - Runs the :doc:`system checks `. .. method:: DiscoverRunner.run_suite(suite, **kwargs) @@ -586,8 +605,6 @@ Methods .. method:: DiscoverRunner.get_test_runner_kwargs() - .. versionadded:: 1.11 - Returns the keyword arguments to instantiate the ``DiscoverRunner.test_runner`` with. @@ -626,18 +643,12 @@ utility methods in the ``django.test.utils`` module. If ``debug`` isn't ``None``, the :setting:`DEBUG` setting is updated to its value. - .. versionchanged:: 1.11 - - The ``debug`` argument was added. - .. function:: teardown_test_environment() Performs global post-test teardown, such as removing instrumentation from the template system and restoring normal email services. -.. function:: setup_databases(verbosity, interactive, keepdb=False, debug_sql=False, parallel=0, **kwargs) - - .. versionadded:: 1.11 +.. function:: setup_databases(verbosity, interactive, keepdb=False, debug_sql=False, parallel=0, aliases=None, **kwargs) Creates the test databases. @@ -645,9 +656,15 @@ utility methods in the ``django.test.utils`` module. that have been made. This data will be provided to the :func:`teardown_databases` function at the conclusion of testing. -.. function:: teardown_databases(old_config, parallel=0, keepdb=False) + The ``aliases`` argument determines which :setting:`DATABASES` aliases test + databases should be setup for. If it's not provided, it defaults to all of + :setting:`DATABASES` aliases. - .. versionadded:: 1.11 + .. versionadded:: 2.2 + + The ``aliases`` argument was added. + +.. function:: teardown_databases(old_config, parallel=0, keepdb=False) Destroys the test databases, restoring pre-test conditions. @@ -740,5 +757,5 @@ listed here because of the ``source`` flag passed to the previous command. For more options like annotated HTML listings detailing missed lines, see the `coverage.py`_ docs. -.. _coverage.py: http://nedbatchelder.com/code/coverage/ -.. _install coverage.py: https://pypi.python.org/pypi/coverage +.. _coverage.py: https://coverage.readthedocs.io/ +.. _install coverage.py: https://pypi.org/project/coverage/ diff --git a/docs/topics/testing/overview.txt b/docs/topics/testing/overview.txt index 245ee0707902..3b07ed42c69c 100644 --- a/docs/topics/testing/overview.txt +++ b/docs/topics/testing/overview.txt @@ -128,7 +128,7 @@ be reported, and any test databases created by the run will not be destroyed. .. admonition:: Test with warnings enabled It's a good idea to run your tests with Python warnings enabled: - ``python -Wall manage.py test``. The ``-Wall`` flag tells Python to + ``python -Wa manage.py test``. The ``-Wa`` flag tells Python to display deprecation warnings. Django, like many other Python libraries, uses these warnings to flag when features are going away. It also might flag areas in your code that aren't strictly wrong but could benefit @@ -151,6 +151,13 @@ You can prevent the test databases from being destroyed by using the runs. If the database does not exist, it will first be created. Any migrations will also be applied in order to keep it up to date. +As described in the previous section, if a test run is forcefully interrupted, +the test database may not be destroyed. On the next run, you'll be asked +whether you want to reuse or destroy the database. Use the :option:`test +--noinput` option to suppress that prompt and automatically destroy the +database. This can be useful when running tests on a continuous integration +server where tests may be interrupted by a timeout, for example. + The default test database names are created by prepending ``test_`` to the value of each :setting:`NAME` in :setting:`DATABASES`. When using SQLite, the tests will use an in-memory database by default (i.e., the database will be @@ -177,9 +184,9 @@ control the particular collation used by the test database. See the :doc:`settings documentation ` for details of these and other advanced settings. -If using an SQLite in-memory database with Python 3.4+ and SQLite 3.7.13+, -`shared cache `_ will be enabled, so -you can write tests with ability to share the database between threads. +If using an SQLite in-memory database with SQLite, `shared cache +`_ is enabled, so you can write tests +with ability to share the database between threads. .. admonition:: Finding data from your production database when running tests? @@ -268,9 +275,7 @@ setting. Caches are not cleared after each test, and running "manage.py test fooapp" can insert data from the tests into the cache of a live system if you run your tests in production because, unlike databases, a separate "test cache" is not -used. This behavior `may change`_ in the future. - -.. _may change: https://code.djangoproject.com/ticket/11505 +used. This behavior :ticket:`may change <11505>` in the future. Understanding the test output ----------------------------- @@ -343,3 +348,10 @@ the :setting:`PASSWORD_HASHERS` setting to a faster hashing algorithm:: Don't forget to also include in :setting:`PASSWORD_HASHERS` any hashing algorithm used in fixtures, if any. + +Preserving the test database +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :option:`test --keepdb` option preserves the test database between test +runs. It skips the create and destroy actions which can greatly decrease the +time to run tests. diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt index 5ff8563dd191..2e615cd4fda9 100644 --- a/docs/topics/testing/tools.txt +++ b/docs/topics/testing/tools.txt @@ -109,7 +109,7 @@ Making requests Use the ``django.test.Client`` class to make requests. -.. class:: Client(enforce_csrf_checks=False, **defaults) +.. class:: Client(enforce_csrf_checks=False, json_encoder=DjangoJSONEncoder, **defaults) It requires no arguments at time of construction. However, you can use keywords arguments to specify some default headers. For example, this will @@ -125,6 +125,13 @@ Use the ``django.test.Client`` class to make requests. The ``enforce_csrf_checks`` argument can be used to test CSRF protection (see above). + The ``json_encoder`` argument allows setting a custom JSON encoder for + the JSON serialization that's described in :meth:`post`. + + .. versionchanged:: 2.1 + + The ``json_encoder`` argument was added. + Once you have a ``Client`` instance, you can call any of the following methods: @@ -161,7 +168,7 @@ Use the ``django.test.Client`` class to make requests. HTTP request from the browser to the server should be passed as ``HTTP_HOST``. - .. _CGI: http://www.w3.org/CGI/ + .. _CGI: https://www.w3.org/CGI/ If you already have the GET arguments in URL-encoded form, you can use that encoding instead of using the data argument. For example, @@ -206,9 +213,29 @@ Use the ``django.test.Client`` class to make requests. name=fred&passwd=secret - If you provide ``content_type`` (e.g. :mimetype:`text/xml` for an XML - payload), the contents of ``data`` will be sent as-is in the POST - request, using ``content_type`` in the HTTP ``Content-Type`` header. + If you provide ``content_type`` as :mimetype:`application/json`, the + ``data`` is serialized using :func:`json.dumps` if it's a dict, list, + or tuple. Serialization is performed with + :class:`~django.core.serializers.json.DjangoJSONEncoder` by default, + and can be overridden by providing a ``json_encoder`` argument to + :class:`Client`. This serialization also happens for :meth:`put`, + :meth:`patch`, and :meth:`delete` requests. + + .. versionchanged:: 2.1 + + The JSON serialization described above was added. In older versions, + you can call :func:`json.dumps` on ``data`` before passing it to + ``post()`` to achieve the same thing. + + .. versionchanged:: 2.2 + + The JSON serialization was extended to support lists and tuples. In + older versions, only dicts are serialized. + + If you provide any other ``content_type`` (e.g. :mimetype:`text/xml` + for an XML payload), the contents of ``data`` are sent as-is in the + POST request, using ``content_type`` in the HTTP ``Content-Type`` + header. If you don't provide a value for ``content_type``, the values in ``data`` will be transmitted with a content type of @@ -235,7 +262,15 @@ Use the ``django.test.Client`` class to make requests. file-processing code expects.) You may also provide any file-like object (e.g., :class:`~io.StringIO` or - :class:`~io.BytesIO`) as a file handle. + :class:`~io.BytesIO`) as a file handle. If you're uploading to an + :class:`~django.db.models.ImageField`, the object needs a ``name`` + attribute that passes the + :data:`~django.core.validators.validate_image_file_extension` validator. + For example:: + + >>> from io import BytesIO + >>> img = BytesIO(b'mybinarydata') + >>> img.name = 'myimage.jpg' Note that if you wish to use the same file handle for multiple ``post()`` calls then you will need to manually reset the file @@ -371,12 +406,6 @@ Use the ``django.test.Client`` class to make requests. :meth:`~django.contrib.auth.models.UserManager.create_user` helper method to create a new user with a correctly hashed password. - .. versionchanged:: 1.10 - - In previous versions, inactive users (:attr:`is_active=False - `) were not permitted - to login. - .. method:: Client.force_login(user, backend=None) If your site uses Django's :doc:`authentication @@ -669,12 +698,15 @@ A subclass of :class:`unittest.TestCase` that adds this functionality: * Checking that a callable :meth:`raises a certain exception `. + * Checking that a callable :meth:`triggers a certain warning + `. * Testing form field :meth:`rendering and error treatment `. * Testing :meth:`HTML responses for the presence/lack of a given fragment `. * Verifying that a template :meth:`has/hasn't been used to generate a given response content `. + * Verifying that two :meth:`URLs ` are equal. * Verifying a HTTP :meth:`redirect ` is performed by the app. * Robustly testing two :meth:`HTML fragments ` @@ -690,14 +722,24 @@ A subclass of :class:`unittest.TestCase` that adds this functionality: If your tests make any database queries, use subclasses :class:`~django.test.TransactionTestCase` or :class:`~django.test.TestCase`. -.. attribute:: SimpleTestCase.allow_database_queries +.. attribute:: SimpleTestCase.databases + + .. versionadded:: 2.2 :class:`~SimpleTestCase` disallows database queries by default. This helps to avoid executing write queries which will affect other tests since each ``SimpleTestCase`` test isn't run in a transaction. If you aren't concerned about this problem, you can disable this behavior by - setting the ``allow_database_queries`` class attribute to ``True`` on - your test class. + setting the ``databases`` class attribute to ``'__all__'`` on your test + class. + +.. attribute:: SimpleTestCase.allow_database_queries + + .. deprecated:: 2.2 + + This attribute is deprecated in favor of :attr:`databases`. The previous + behavior of ``allow_database_queries = True`` can be achieved by setting + ``databases = '__all__'``. .. warning:: @@ -710,20 +752,20 @@ If your tests make any database queries, use subclasses @classmethod def setUpClass(cls): - super(MyTestCase, cls).setUpClass() + super().setUpClass() ... @classmethod def tearDownClass(cls): ... - super(MyTestCase, cls).tearDownClass() + super().tearDownClass() Be sure to account for Python's behavior if an exception is raised during ``setUpClass()``. If that happens, neither the tests in the class nor ``tearDownClass()`` are run. In the case of :class:`django.test.TestCase`, this will leak the transaction created in ``super()`` which results in various symptoms including a segmentation fault on some platforms (reported - on OS X). If you want to intentionally raise an exception such as + on macOS). If you want to intentionally raise an exception such as :exc:`unittest.SkipTest` in ``setUpClass()``, be sure to do it before calling ``super()`` to avoid this. @@ -795,11 +837,6 @@ The class: * Checks deferrable database constraints at the end of each test. -.. versionchanged:: 1.10 - - The check for deferrable database constraints at the end of each test was - added. - It also provides an additional method: .. classmethod:: TestCase.setUpTestData() @@ -856,18 +893,11 @@ The live server listens on ``localhost`` and binds to port 0 which uses a free port assigned by the operating system. The server's URL can be accessed with ``self.live_server_url`` during the tests. -.. versionchanged:: 1.11 - - In older versions, Django tried a predefined port range which could be - customized in various ways including the ``DJANGO_LIVE_TEST_SERVER_ADDRESS`` - environment variable. This is removed in favor of the simpler "bind to port - 0" technique. - To demonstrate how to use ``LiveServerTestCase``, let's write a simple Selenium test. First of all, you need to install the `selenium package`_ into your Python path: -.. code-block:: console +.. console:: $ pip install selenium @@ -891,14 +921,14 @@ The code for this test may look as follows:: @classmethod def setUpClass(cls): - super(MySeleniumTests, cls).setUpClass() + super().setUpClass() cls.selenium = WebDriver() cls.selenium.implicitly_wait(10) @classmethod def tearDownClass(cls): cls.selenium.quit() - super(MySeleniumTests, cls).tearDownClass() + super().tearDownClass() def test_login(self): self.selenium.get('%s%s' % (self.live_server_url, '/login/')) @@ -910,7 +940,7 @@ The code for this test may look as follows:: Finally, you may run the test as follows: -.. code-block:: console +.. console:: $ ./manage.py test myapp.tests.MySeleniumTests.test_login @@ -921,7 +951,7 @@ example above is just a tiny fraction of what the Selenium client can do; check out the `full reference`_ for more details. .. _Selenium: http://seleniumhq.org/ -.. _selenium package: https://pypi.python.org/pypi/selenium +.. _selenium package: https://pypi.org/project/selenium/ .. _full reference: https://selenium-python.readthedocs.io/api.html .. _Firefox: https://www.mozilla.com/firefox/ @@ -957,7 +987,7 @@ out the `full reference`_ for more details. `Selenium documentation`_ for more information. .. _Selenium FAQ: https://web.archive.org/web/20160129132110/http://code.google.com/p/selenium/wiki/FrequentlyAskedQuestions#Q:_WebDriver_fails_to_find_elements_/_Does_not_block_on_page_loa - .. _Selenium documentation: http://seleniumhq.org/docs/04_webdriver_advanced.html#explicit-waits + .. _Selenium documentation: https://www.seleniumhq.org/docs/04_webdriver_advanced.html#explicit-waits Test cases features =================== @@ -1010,7 +1040,7 @@ If you want to use a different ``Client`` class (for example, a subclass with customized behavior), use the :attr:`~SimpleTestCase.client_class` class attribute:: - from django.test import TestCase, Client + from django.test import Client, TestCase class MyTestClient(Client): # Specialized methods for your environment @@ -1059,7 +1089,7 @@ subclass:: # Test definitions as before. call_setup_methods() - def testFluffyAnimals(self): + def test_fluffy_animals(self): # A test that uses the fixtures. call_some_test_code() @@ -1081,8 +1111,8 @@ you can be certain that the outcome of a test will not be affected by another test or by the order of test execution. By default, fixtures are only loaded into the ``default`` database. If you are -using multiple databases and set :attr:`multi_db=True -`, fixtures will be loaded into all databases. +using multiple databases and set :attr:`TransactionTestCase.databases`, +fixtures will be loaded into all specified databases. URLconf configuration --------------------- @@ -1094,43 +1124,89 @@ tests can't rely upon the fact that your views will be available at a particular URL. Decorate your test class or test method with ``@override_settings(ROOT_URLCONF=...)`` for URLconf configuration. -.. _emptying-test-outbox: +.. _testing-multi-db: Multi-database support ---------------------- -.. attribute:: TransactionTestCase.multi_db +.. attribute:: TransactionTestCase.databases + +.. versionadded:: 2.2 Django sets up a test database corresponding to every database that is -defined in the :setting:`DATABASES` definition in your settings -file. However, a big part of the time taken to run a Django TestCase -is consumed by the call to ``flush`` that ensures that you have a -clean database at the start of each test run. If you have multiple -databases, multiple flushes are required (one for each database), -which can be a time consuming activity -- especially if your tests -don't need to test multi-database activity. +defined in the :setting:`DATABASES` definition in your settings and referred to +by at least one test through ``databases``. + +However, a big part of the time taken to run a Django ``TestCase`` is consumed +by the call to ``flush`` that ensures that you have a clean database at the +start of each test run. If you have multiple databases, multiple flushes are +required (one for each database), which can be a time consuming activity -- +especially if your tests don't need to test multi-database activity. As an optimization, Django only flushes the ``default`` database at the start of each test run. If your setup contains multiple databases, and you have a test that requires every database to be clean, you can -use the ``multi_db`` attribute on the test suite to request a full -flush. +use the ``databases`` attribute on the test suite to request extra databases +to be flushed. For example:: - class TestMyViews(TestCase): - multi_db = True + class TestMyViews(TransactionTestCase): + databases = {'default', 'other'} def test_index_page_view(self): call_some_test_code() -This test case will flush *all* the test databases before running -``test_index_page_view``. +This test case will flush the ``default`` and ``other`` test databases before +running ``test_index_page_view``. You can also use ``'__all__'`` to specify +that all of the test databases must be flushed. + +The ``databases`` flag also controls which databases the +:attr:`TransactionTestCase.fixtures` are loaded into. By default, fixtures are +only loaded into the ``default`` database. + +Queries against databases not in ``databases`` will give assertion errors to +prevent state leaking between tests. + +.. attribute:: TransactionTestCase.multi_db + +.. deprecated:: 2.2 + +This attribute is deprecated in favor of :attr:`~TransactionTestCase.databases`. +The previous behavior of ``multi_db = True`` can be achieved by setting +``databases = '__all__'``. + +.. attribute:: TestCase.databases + +.. versionadded:: 2.2 + +By default, only the ``default`` database will be wrapped in a transaction +during a ``TestCase``'s execution and attempts to query other databases will +result in assertion errors to prevent state leaking between tests. -The ``multi_db`` flag also affects into which databases the -attr:`TransactionTestCase.fixtures` are loaded. By default (when -``multi_db=False``), fixtures are only loaded into the ``default`` database. -If ``multi_db=True``, fixtures are loaded into all databases. +Use the ``databases`` class attribute on the test class to request transaction +wrapping against non-``default`` databases. + +For example:: + + class OtherDBTests(TestCase): + databases = {'other'} + + def test_other_db_query(self): + ... + +This test will only allow queries against the ``other`` database. Just like for +:attr:`SimpleTestCase.databases` and :attr:`TransactionTestCase.databases`, the +``'__all__'`` constant can be used to specify that the test should allow +queries to all databases. + +.. attribute:: TestCase.multi_db + +.. deprecated:: 2.2 + +This attribute is deprecated in favor of :attr:`~TestCase.databases`. The +previous behavior of ``multi_db = True`` can be achieved by setting +``databases = '__all__'``. .. _overriding-settings: @@ -1263,6 +1339,23 @@ The decorator can also be applied to test case classes:: decorator. For a given class, :func:`~django.test.modify_settings` is always applied after :func:`~django.test.override_settings`. +.. admonition:: Considerations with Python 3.5 + + If using Python 3.5 (or older, if using an older version of Django), avoid + mixing ``remove`` with ``append`` and ``prepend`` in + :func:`~django.test.modify_settings`. In some cases it matters whether a + value is first added and then removed or vice versa, and dictionary key + order isn't preserved until Python 3.6. Instead, apply the decorator twice + to guarantee the order of operations. For example, to ensure that + ``SessionMiddleware`` appears first in ``MIDDLEWARE``:: + + @modify_settings(MIDDLEWARE={ + 'remove': ['django.contrib.sessions.middleware.SessionMiddleware'], + ) + @modify_settings(MIDDLEWARE={ + 'prepend': ['django.contrib.sessions.middleware.SessionMiddleware'], + }) + .. warning:: The settings file contains some settings that are only consulted during @@ -1310,6 +1403,8 @@ LOCALE_PATHS, LANGUAGE_CODE Default translation and loaded translations MEDIA_ROOT, DEFAULT_FILE_STORAGE Default file storage ================================ ======================== +.. _emptying-test-outbox: + Emptying the test outbox ------------------------ @@ -1351,10 +1446,14 @@ your test suite. with self.assertRaisesMessage(ValueError, 'invalid literal for int()'): int('a') - .. deprecated:: 1.9 +.. method:: SimpleTestCase.assertWarnsMessage(expected_warning, expected_message, callable, *args, **kwargs) + SimpleTestCase.assertWarnsMessage(expected_warning, expected_message) + + .. versionadded:: 2.1 - Passing ``callable`` as a keyword argument called ``callable_obj`` is - deprecated. Pass the callable as a positional argument instead. + Analogous to :meth:`SimpleTestCase.assertRaisesMessage` but for + :meth:`~unittest.TestCase.assertWarnsRegex` instead of + :meth:`~unittest.TestCase.assertRaisesRegex`. .. method:: SimpleTestCase.assertFieldOutput(fieldclass, valid, invalid, field_args=None, field_kwargs=None, empty_value='') @@ -1460,6 +1559,15 @@ your test suite. You can use this as a context manager in the same way as :meth:`~SimpleTestCase.assertTemplateUsed`. +.. method:: SimpleTestCase.assertURLEqual(url1, url2, msg_prefix='') + + .. versionadded:: 2.2 + + Asserts that two URLs are the same, ignoring the order of query string + parameters except for parameters with the same name. For example, + ``/path/?x=1&y=2`` is equal to ``/path/?y=2&x=1``, but + ``/path/?a=1&a=2`` isn't equal to ``/path/?a=2&a=1``. + .. method:: SimpleTestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='', fetch_redirect_response=True) Asserts that the response returned a ``status_code`` redirect status, @@ -1479,11 +1587,6 @@ your test suite. the original request's scheme is used. If present, the scheme in ``expected_url`` is the one used to make the comparisons to. - .. deprecated:: 1.9 - - The ``host`` argument is deprecated, as redirections are no longer - forced to be absolute URLs. - .. method:: SimpleTestCase.assertHTMLEqual(html1, html2, msg=None) Asserts that the strings ``html1`` and ``html2`` are equal. The comparison @@ -1505,7 +1608,7 @@ your test suite. self.assertHTMLEqual( '

      Hello world!

      ', '''

      - Hello world! + Hello world!

      ''' ) self.assertHTMLEqual( @@ -1538,6 +1641,9 @@ your test suite. syntax differences. When invalid XML is passed in any parameter, an ``AssertionError`` is always raised, even if both string are identical. + XML declaration and comments are ignored. Only the root element and its + children are compared. + Output in case of error can be customized with the ``msg`` argument. .. method:: SimpleTestCase.assertXMLNotEqual(xml1, xml2, msg=None) @@ -1614,8 +1720,6 @@ your test suite. Tagging tests ------------- -.. versionadded:: 1.10 - You can tag your tests so you can easily run a particular subset. For example, you might label fast or slow tests:: @@ -1641,21 +1745,40 @@ You can also tag a test case:: class SampleTestCase(TestCase): ... +Subclasses inherit tags from superclasses, and methods inherit tags from their +class. Given:: + + @tag('foo') + class SampleTestCaseChild(SampleTestCase): + + @tag('bar') + def test(self): + ... + +``SampleTestCaseChild.test`` will be labeled with ``'slow'``, ``'core'``, +``'bar'``, and ``'foo'``. + +.. versionchanged:: 2.1 + + In older versions, tagged tests don't inherit tags from classes, and + tagged subclasses don't inherit tags from superclasses. For example, + ``SampleTestCaseChild.test`` is labeled only with ``'bar'``. + Then you can choose which tests to run. For example, to run only fast tests: -.. code-block:: console +.. console:: $ ./manage.py test --tag=fast Or to run fast tests and the core one (even though it's slow): -.. code-block:: console +.. console:: $ ./manage.py test --tag=fast --tag=core You can also exclude tests by tag. To run core tests if they are not slow: -.. code-block:: console +.. console:: $ ./manage.py test --tag=core --exclude-tag=slow @@ -1731,9 +1854,9 @@ Management commands can be tested with the :func:`~django.core.management.call_command` function. The output can be redirected into a ``StringIO`` instance:: + from io import StringIO from django.core.management import call_command from django.test import TestCase - from django.utils.six import StringIO class ClosepollTest(TestCase): def test_command_output(self): diff --git a/extras/django_bash_completion b/extras/django_bash_completion index 06a2321ff3c4..dfeefe4939a0 100755 --- a/extras/django_bash_completion +++ b/extras/django_bash_completion @@ -43,7 +43,7 @@ _python_django_completion() { if [[ ${COMP_CWORD} -ge 2 ]]; then local PYTHON_EXE=${COMP_WORDS[0]##*/} - echo $PYTHON_EXE | egrep "python([2-9]\.[0-9])?" >/dev/null 2>&1 + echo $PYTHON_EXE | egrep "python([3-9]\.[0-9])?" >/dev/null 2>&1 if [[ $? == 0 ]]; then local PYTHON_SCRIPT=${COMP_WORDS[1]##*/} echo $PYTHON_SCRIPT | egrep "manage\.py|django-admin(\.py)?" >/dev/null 2>&1 diff --git a/js_tests/admin/DateTimeShortcuts.test.js b/js_tests/admin/DateTimeShortcuts.test.js index 5d5b12ba60c8..7cc0bda60c7a 100644 --- a/js_tests/admin/DateTimeShortcuts.test.js +++ b/js_tests/admin/DateTimeShortcuts.test.js @@ -21,3 +21,23 @@ QUnit.test('init', function(assert) { // should be 0 when a timezone offset isn't set in the HTML body attribute. assert.equal(DateTimeShortcuts.timezoneOffset, 0); }); + +QUnit.test('custom time shortcuts', function(assert) { + var $ = django.jQuery; + var timeField = $(''); + $('#qunit-fixture').append(timeField); + DateTimeShortcuts.clockHours.time_test = [['3 a.m.', 3]]; + DateTimeShortcuts.init(); + assert.equal($('.clockbox').find('a').first().text(), '3 a.m.'); +}); + +QUnit.test('time zone offset warning', function(assert) { + var $ = django.jQuery; + var savedOffset = $('body').attr('data-admin-utc-offset'); + var timeField = $(''); + $('#qunit-fixture').append(timeField); + $('body').attr('data-admin-utc-offset', new Date().getTimezoneOffset() * -60 + 3600); + DateTimeShortcuts.init(); + $('body').attr('data-admin-utc-offset', savedOffset); + assert.equal($('.timezonewarning').text(), 'Note: You are 1 hour behind server time.'); +}); diff --git a/js_tests/admin/SelectFilter2.test.js b/js_tests/admin/SelectFilter2.test.js index c000584f4890..c6b300046503 100644 --- a/js_tests/admin/SelectFilter2.test.js +++ b/js_tests/admin/SelectFilter2.test.js @@ -11,6 +11,10 @@ QUnit.test('init', function(assert) { SelectFilter.init('id', 'things', 0); assert.equal($('.selector-available h2').text().trim(), "Available things"); assert.equal($('.selector-chosen h2').text().trim(), "Chosen things"); + assert.equal( + $('.selector-available select').outerHeight() + $('.selector-filter').outerHeight(), + $('.selector-chosen select').height() + ); assert.equal($('.selector-chooseall').text(), "Choose all"); assert.equal($('.selector-add').text(), "Choose"); assert.equal($('.selector-remove').text(), "Remove"); diff --git a/js_tests/admin/URLify.test.js b/js_tests/admin/URLify.test.js new file mode 100644 index 000000000000..cc738bc4adbe --- /dev/null +++ b/js_tests/admin/URLify.test.js @@ -0,0 +1,30 @@ +/* global QUnit, URLify */ +/* eslint global-strict: 0, strict: 0 */ +'use strict'; + +QUnit.module('admin.URLify'); + +QUnit.test('empty string', function(assert) { + assert.strictEqual(URLify('', 8, true), ''); +}); + +QUnit.test('strip nonessential words', function(assert) { + assert.strictEqual(URLify('the D is silent', 8, true), 'd-silent'); +}); + +QUnit.test('strip non-URL characters', function(assert) { + assert.strictEqual(URLify('D#silent@', 7, true), 'dsilent'); +}); + +QUnit.test('merge adjacent whitespace', function(assert) { + assert.strictEqual(URLify('D silent', 8, true), 'd-silent'); +}); + +QUnit.test('trim trailing hyphens', function(assert) { + assert.strictEqual(URLify('D silent always', 9, true), 'd-silent'); +}); + +QUnit.test('do not remove English words if the string contains non-ASCII', function(assert) { + // If removing English words wasn't skipped, the last 'a' would be removed. + assert.strictEqual(URLify('Kaupa-miða', 255, true), 'kaupa-miða'); +}); diff --git a/js_tests/admin/inlines.test.js b/js_tests/admin/inlines.test.js index 2ff6a1457793..433ea0672a7d 100644 --- a/js_tests/admin/inlines.test.js +++ b/js_tests/admin/inlines.test.js @@ -11,7 +11,7 @@ QUnit.module('admin.inlines: tabular formsets', { $('#qunit-fixture').append($('#tabular-formset').text()); this.table = $('table.inline'); this.inlineRow = this.table.find('tr'); - that.inlineRow.tabularFormset({ + that.inlineRow.tabularFormset('table.inline tr', { prefix: 'first', addText: that.addText, deleteText: 'Remove' @@ -54,13 +54,13 @@ QUnit.test('add/remove form events', function(assert) { QUnit.test('existing add button', function(assert) { var $ = django.jQuery; - $('#qunit-fixture').empty(); // Clear the table added in beforeEach + $('#qunit-fixture').empty(); // Clear the table added in beforeEach $('#qunit-fixture').append($('#tabular-formset').text()); this.table = $('table.inline'); this.inlineRow = this.table.find('tr'); this.table.append(''); var addButton = this.table.find('.add-button'); - this.inlineRow.tabularFormset({ + this.inlineRow.tabularFormset('table.inline tr', { prefix: 'first', deleteText: 'Remove', addButton: addButton diff --git a/js_tests/admin/jsi18n-mocks.test.js b/js_tests/admin/jsi18n-mocks.test.js index 4858de6eb369..f04b0576f651 100644 --- a/js_tests/admin/jsi18n-mocks.test.js +++ b/js_tests/admin/jsi18n-mocks.test.js @@ -53,9 +53,9 @@ "%m/%d/%y" ], "DECIMAL_SEPARATOR": ".", - "FIRST_DAY_OF_WEEK": "0", + "FIRST_DAY_OF_WEEK": 0, "MONTH_DAY_FORMAT": "F j", - "NUMBER_GROUPING": "3", + "NUMBER_GROUPING": 3, "SHORT_DATETIME_FORMAT": "m/d/Y P", "SHORT_DATE_FORMAT": "m/d/Y", "THOUSAND_SEPARATOR": ",", diff --git a/js_tests/tests.html b/js_tests/tests.html index 63e56bb52ab6..7c6ba3497715 100644 --- a/js_tests/tests.html +++ b/js_tests/tests.html @@ -43,6 +43,7 @@ + @@ -76,6 +77,7 @@ + - + diff --git a/package.json b/package.json index 77a47a874721..8a3853ff9fab 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "npm": ">=1.3.0 <3.0.0" }, "devDependencies": { - "eslint": "^0.22.1", + "eslint": "^4.18.2", "grunt": "^1.0.1", "grunt-cli": "^1.2.0", "grunt-contrib-qunit": "^1.2.0" diff --git a/scripts/manage_translations.py b/scripts/manage_translations.py index 74534d47dab6..49f9f903db88 100644 --- a/scripts/manage_translations.py +++ b/scripts/manage_translations.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# This python file contains utility scripts to manage Django translations. +# This Python file contains utility scripts to manage Django translations. # It has to be run inside the django git root directory. # # The following commands are available: @@ -114,21 +114,26 @@ def lang_stats(resources=None, languages=None): for name, dir_ in locale_dirs: print("\nShowing translations stats for '%s':" % name) - langs = sorted([d for d in os.listdir(dir_) if not d.startswith('_')]) + langs = sorted(d for d in os.listdir(dir_) if not d.startswith('_')) for lang in langs: if languages and lang not in languages: continue # TODO: merge first with the latest en catalog - p = Popen("msgfmt -vc -o /dev/null %(path)s/%(lang)s/LC_MESSAGES/django%(ext)s.po" % { - 'path': dir_, 'lang': lang, 'ext': 'js' if name.endswith('-js') else ''}, - stdout=PIPE, stderr=PIPE, shell=True) + po_path = '{path}/{lang}/LC_MESSAGES/django{ext}.po'.format( + path=dir_, lang=lang, ext='js' if name.endswith('-js') else '' + ) + p = Popen( + ['msgfmt', '-vc', '-o', '/dev/null', po_path], + stdout=PIPE, stderr=PIPE, + env={'LANG': 'C'} + ) output, errors = p.communicate() if p.returncode == 0: # msgfmt output stats on stderr - print("%s: %s" % (lang, errors.strip())) + print("%s: %s" % (lang, errors.decode().strip())) else: print("Errors happened when checking %s translation for %s:\n%s" % ( - lang, name, errors)) + lang, name, errors.decode())) def fetch(resources=None, languages=None): @@ -142,7 +147,7 @@ def fetch(resources=None, languages=None): # Transifex pull if languages is None: call('tx pull -r %(res)s -a -f --minimum-perc=5' % {'res': _tx_resource_for_name(name)}, shell=True) - target_langs = sorted([d for d in os.listdir(dir_) if not d.startswith('_') and d != 'en']) + target_langs = sorted(d for d in os.listdir(dir_) if not d.startswith('_') and d != 'en') else: for lang in languages: call('tx pull -r %(res)s -f -l %(lang)s' % { diff --git a/setup.cfg b/setup.cfg index 9d336981dda5..ce026e6f49c3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,8 +3,8 @@ doc_files = docs extras AUTHORS INSTALL LICENSE README.rst install-script = scripts/rpm-install.sh [flake8] -exclude = build,.git,.tox,./django/utils/lru_cache.py,./django/utils/six.py,./django/conf/app_template/*,./django/dispatch/weakref_backports.py,./tests/.env,./xmlrunner,tests/view_tests/tests/py3_test_debug.py,tests/template_tests/annotated_tag_function.py -ignore = W601 +exclude = build,.git,.tox,./django/utils/six.py,./django/conf/app_template/*,./tests/.env +ignore = W504,W601 max-line-length = 119 [isort] @@ -18,6 +18,3 @@ not_skip = __init__.py [metadata] license-file = LICENSE - -[wheel] -universal = 1 diff --git a/setup.py b/setup.py index d8dfa58a17db..f4780e2b9cd4 100644 --- a/setup.py +++ b/setup.py @@ -4,6 +4,35 @@ from setuptools import find_packages, setup +CURRENT_PYTHON = sys.version_info[:2] +REQUIRED_PYTHON = (3, 5) + +# This check and everything above must remain compatible with Python 2.7. +if CURRENT_PYTHON < REQUIRED_PYTHON: + sys.stderr.write(""" +========================== +Unsupported Python version +========================== + +This version of Django requires Python {}.{}, but you're trying to +install it on Python {}.{}. + +This may be because you are using a version of pip that doesn't +understand the python_requires classifier. Make sure you +have pip >= 9.0 and setuptools >= 24.2, then try again: + + $ python -m pip install --upgrade pip setuptools + $ python -m pip install django + +This will install the latest version of Django which works on your +version of Python. If you can't upgrade your pip (or Python), request +an older version of Django: + + $ python -m pip install "django<2" +""".format(*(REQUIRED_PYTHON + CURRENT_PYTHON))) + sys.exit(1) + + # Warn if we are installing over top of an existing installation. This can # cause issues where files that were deleted from a more recent Django are # still present in site-packages. See #18115. @@ -30,16 +59,24 @@ # Dynamically calculate the version based on django.VERSION. version = __import__('django').get_version() +nimbis_version = '1' + + +def read(fname): + with open(os.path.join(os.path.dirname(__file__), fname)) as f: + return f.read() setup( name='Django', - version=version, + version='{}+nimbis.{}'.format(version, nimbis_version), + python_requires='>={}.{}'.format(*REQUIRED_PYTHON), url='https://www.djangoproject.com/', author='Django Software Foundation', author_email='foundation@djangoproject.com', description=('A high-level Python Web framework that encourages ' 'rapid development and clean, pragmatic design.'), + long_description=read('README.rst'), license='BSD', packages=find_packages(exclude=EXCLUDE_FROM_PACKAGES), include_package_data=True, @@ -47,32 +84,38 @@ entry_points={'console_scripts': [ 'django-admin = django.core.management:execute_from_command_line', ]}, - install_requires=['pytz'], + install_requires=['pytz', 'sqlparse >= 0.2.2'], extras_require={ "bcrypt": ["bcrypt"], "argon2": ["argon2-cffi >= 16.1.0"], }, zip_safe=False, classifiers=[ - 'Development Status :: 2 - Pre-Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Framework :: Django', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3 :: Only', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Internet :: WWW/HTTP :: WSGI', 'Topic :: Software Development :: Libraries :: Application Frameworks', 'Topic :: Software Development :: Libraries :: Python Modules', ], + project_urls={ + 'Documentation': 'https://docs.djangoproject.com/', + 'Funding': 'https://www.djangoproject.com/fundraising/', + 'Source': 'https://github.com/django/django', + 'Tracker': 'https://code.djangoproject.com/', + }, ) diff --git a/tests/README.rst b/tests/README.rst index 7d4ddb513a02..7f64afe6fcb6 100644 --- a/tests/README.rst +++ b/tests/README.rst @@ -3,7 +3,7 @@ install some requirements and run the tests:: $ cd tests $ pip install -e .. - $ pip install -r requirements/py3.txt # or py2.txt + $ pip install -r requirements/py3.txt $ ./runtests.py For more information about the test suite, see diff --git a/tests/admin_changelist/admin.py b/tests/admin_changelist/admin.py index 39494e99d3b3..1e686bd6fb15 100644 --- a/tests/admin_changelist/admin.py +++ b/tests/admin_changelist/admin.py @@ -12,10 +12,11 @@ class CustomPaginator(Paginator): def __init__(self, queryset, page_size, orphans=0, allow_empty_first_page=True): - super(CustomPaginator, self).__init__(queryset, 5, orphans=2, allow_empty_first_page=allow_empty_first_page) + super().__init__(queryset, 5, orphans=2, allow_empty_first_page=allow_empty_first_page) class EventAdmin(admin.ModelAdmin): + date_hierarchy = 'date' list_display = ['event_date_func'] def event_date_func(self, event): @@ -39,7 +40,7 @@ class ChildAdmin(admin.ModelAdmin): list_filter = ['parent', 'age'] def get_queryset(self, request): - return super(ChildAdmin, self).get_queryset(request).select_related("parent") + return super().get_queryset(request).select_related("parent") class CustomPaginationAdmin(ChildAdmin): @@ -51,8 +52,7 @@ class FilteredChildAdmin(admin.ModelAdmin): list_per_page = 10 def get_queryset(self, request): - return super(FilteredChildAdmin, self).get_queryset(request).filter( - name__contains='filtered') + return super().get_queryset(request).filter(name__contains='filtered') class BandAdmin(admin.ModelAdmin): @@ -85,7 +85,7 @@ class DynamicListDisplayChildAdmin(admin.ModelAdmin): list_display = ('parent', 'name', 'age') def get_list_display(self, request): - my_list_display = super(DynamicListDisplayChildAdmin, self).get_list_display(request) + my_list_display = super().get_list_display(request) if request.user.username == 'noparents': my_list_display = list(my_list_display) my_list_display.remove('parent') @@ -124,7 +124,7 @@ class DynamicListFilterChildAdmin(admin.ModelAdmin): list_filter = ('parent', 'name', 'age') def get_list_filter(self, request): - my_list_filter = super(DynamicListFilterChildAdmin, self).get_list_filter(request) + my_list_filter = super().get_list_filter(request) if request.user.username == 'noparents': my_list_filter = list(my_list_filter) my_list_filter.remove('parent') @@ -135,7 +135,7 @@ class DynamicSearchFieldsChildAdmin(admin.ModelAdmin): search_fields = ('name',) def get_search_fields(self, request): - search_fields = super(DynamicSearchFieldsChildAdmin, self).get_search_fields(request) + search_fields = super().get_search_fields(request) search_fields += ('age',) return search_fields diff --git a/tests/admin_changelist/models.py b/tests/admin_changelist/models.py index fb5f03cbd611..81d7fdfb3abf 100644 --- a/tests/admin_changelist/models.py +++ b/tests/admin_changelist/models.py @@ -1,5 +1,6 @@ +import uuid + from django.db import models -from django.utils.encoding import python_2_unicode_compatible class Event(models.Model): @@ -27,15 +28,14 @@ class Band(models.Model): genres = models.ManyToManyField(Genre) -@python_2_unicode_compatible class Musician(models.Model): name = models.CharField(max_length=30) + age = models.IntegerField(null=True, blank=True) def __str__(self): return self.name -@python_2_unicode_compatible class Group(models.Model): name = models.CharField(max_length=30) members = models.ManyToManyField(Musician, through='Membership') @@ -75,6 +75,7 @@ class Invitation(models.Model): class Swallow(models.Model): + uuid = models.UUIDField(primary_key=True, default=uuid.uuid4) origin = models.CharField(max_length=255) load = models.FloatField() speed = models.FloatField() @@ -97,7 +98,7 @@ class UnorderedObject(models.Model): class OrderedObjectManager(models.Manager): def get_queryset(self): - return super(OrderedObjectManager, self).get_queryset().order_by('number') + return super().get_queryset().order_by('number') class OrderedObject(models.Model): @@ -114,3 +115,7 @@ class OrderedObject(models.Model): class CustomIdUser(models.Model): uuid = models.AutoField(primary_key=True) + + +class CharPK(models.Model): + char_pk = models.CharField(max_length=100, primary_key=True) diff --git a/tests/admin_changelist/test_date_hierarchy.py b/tests/admin_changelist/test_date_hierarchy.py new file mode 100644 index 000000000000..f19e38f9bf22 --- /dev/null +++ b/tests/admin_changelist/test_date_hierarchy.py @@ -0,0 +1,61 @@ +from datetime import datetime + +from django.contrib.admin.options import IncorrectLookupParameters +from django.contrib.auth.models import User +from django.test import RequestFactory, TestCase +from django.utils.timezone import make_aware + +from .admin import EventAdmin, site as custom_site +from .models import Event + + +class DateHierarchyTests(TestCase): + factory = RequestFactory() + + @classmethod + def setUpTestData(cls): + cls.superuser = User.objects.create_superuser(username='super', email='a@b.com', password='xxx') + + def assertDateParams(self, query, expected_from_date, expected_to_date): + query = {'date__%s' % field: val for field, val in query.items()} + request = self.factory.get('/', query) + request.user = self.superuser + changelist = EventAdmin(Event, custom_site).get_changelist_instance(request) + _, _, lookup_params, _ = changelist.get_filters(request) + self.assertEqual(lookup_params['date__gte'], expected_from_date) + self.assertEqual(lookup_params['date__lt'], expected_to_date) + + def test_bounded_params(self): + tests = ( + ({'year': 2017}, datetime(2017, 1, 1), datetime(2018, 1, 1)), + ({'year': 2017, 'month': 2}, datetime(2017, 2, 1), datetime(2017, 3, 1)), + ({'year': 2017, 'month': 12}, datetime(2017, 12, 1), datetime(2018, 1, 1)), + ({'year': 2017, 'month': 12, 'day': 15}, datetime(2017, 12, 15), datetime(2017, 12, 16)), + ({'year': 2017, 'month': 12, 'day': 31}, datetime(2017, 12, 31), datetime(2018, 1, 1)), + ({'year': 2017, 'month': 2, 'day': 28}, datetime(2017, 2, 28), datetime(2017, 3, 1)), + ) + for query, expected_from_date, expected_to_date in tests: + with self.subTest(query=query): + self.assertDateParams(query, expected_from_date, expected_to_date) + + def test_bounded_params_with_time_zone(self): + with self.settings(USE_TZ=True, TIME_ZONE='Asia/Jerusalem'): + self.assertDateParams( + {'year': 2017, 'month': 2, 'day': 28}, + make_aware(datetime(2017, 2, 28)), + make_aware(datetime(2017, 3, 1)), + ) + + def test_invalid_params(self): + tests = ( + {'year': 'x'}, + {'year': 2017, 'month': 'x'}, + {'year': 2017, 'month': 12, 'day': 'x'}, + {'year': 2017, 'month': 13}, + {'year': 2017, 'month': 12, 'day': 32}, + {'year': 2017, 'month': 0}, + {'year': 2017, 'month': 12, 'day': 0}, + ) + for invalid_query in tests: + with self.subTest(query=invalid_query), self.assertRaises(IncorrectLookupParameters): + self.assertDateParams(invalid_query, None, None) diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py index b2292a77a073..464e6904d82b 100644 --- a/tests/admin_changelist/tests.py +++ b/tests/admin_changelist/tests.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import datetime from django.contrib import admin @@ -7,15 +5,22 @@ from django.contrib.admin.options import IncorrectLookupParameters from django.contrib.admin.templatetags.admin_list import pagination from django.contrib.admin.tests import AdminSeleniumTestCase -from django.contrib.admin.views.main import ALL_VAR, SEARCH_VAR, ChangeList +from django.contrib.admin.views.main import ALL_VAR, SEARCH_VAR from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType -from django.template import Context, Template -from django.test import TestCase, ignore_warnings, override_settings +from django.db import connection, models +from django.db.models import F +from django.db.models.fields import Field, IntegerField +from django.db.models.functions import Upper +from django.db.models.lookups import Contains, Exact +from django.template import Context, Template, TemplateSyntaxError +from django.test import TestCase, override_settings from django.test.client import RequestFactory +from django.test.utils import ( + CaptureQueriesContext, isolate_apps, register_lookup, +) from django.urls import reverse -from django.utils import formats, six -from django.utils.deprecation import RemovedInDjango20Warning +from django.utils import formats from .admin import ( BandAdmin, ChildAdmin, ChordsBandAdmin, ConcertAdmin, @@ -27,35 +32,30 @@ site as custom_site, ) from .models import ( - Band, Child, ChordsBand, ChordsMusician, Concert, CustomIdUser, Event, - Genre, Group, Invitation, Membership, Musician, OrderedObject, Parent, - Quartet, Swallow, SwallowOneToOne, UnorderedObject, + Band, CharPK, Child, ChordsBand, ChordsMusician, Concert, CustomIdUser, + Event, Genre, Group, Invitation, Membership, Musician, OrderedObject, + Parent, Quartet, Swallow, SwallowOneToOne, UnorderedObject, ) -def get_changelist_args(modeladmin, **kwargs): - m = modeladmin - args = ( - kwargs.pop('list_display', m.list_display), - kwargs.pop('list_display_links', m.list_display_links), - kwargs.pop('list_filter', m.list_filter), - kwargs.pop('date_hierarchy', m.date_hierarchy), - kwargs.pop('search_fields', m.search_fields), - kwargs.pop('list_select_related', m.list_select_related), - kwargs.pop('list_per_page', m.list_per_page), - kwargs.pop('list_max_show_all', m.list_max_show_all), - kwargs.pop('list_editable', m.list_editable), - m, - ) - assert not kwargs, "Unexpected kwarg %s" % kwargs - return args +def build_tbody_html(pk, href, extra_fields): + return ( + '' + '' + '' + 'name' + '{}' + ).format(pk, href, extra_fields) @override_settings(ROOT_URLCONF="admin_changelist.urls") class ChangeListTests(TestCase): + factory = RequestFactory() - def setUp(self): - self.factory = RequestFactory() + @classmethod + def setUpTestData(cls): + cls.superuser = User.objects.create_superuser(username='super', email='a@b.com', password='xxx') def _create_superuser(self, username): return User.objects.create_superuser(username=username, email='a@b.com', password='xxx') @@ -65,6 +65,32 @@ def _mocked_authenticated_request(self, url, user): request.user = user return request + def test_specified_ordering_by_f_expression(self): + class OrderedByFBandAdmin(admin.ModelAdmin): + list_display = ['name', 'genres', 'nr_of_members'] + ordering = ( + F('nr_of_members').desc(nulls_last=True), + Upper(F('name')).asc(), + F('genres').asc(), + ) + + m = OrderedByFBandAdmin(Band, custom_site) + request = self.factory.get('/band/') + request.user = self.superuser + cl = m.get_changelist_instance(request) + self.assertEqual(cl.get_ordering_field_columns(), {3: 'desc', 2: 'asc'}) + + def test_specified_ordering_by_f_expression_without_asc_desc(self): + class OrderedByFBandAdmin(admin.ModelAdmin): + list_display = ['name', 'genres', 'nr_of_members'] + ordering = (F('nr_of_members'), Upper('name'), F('genres')) + + m = OrderedByFBandAdmin(Band, custom_site) + request = self.factory.get('/band/') + request.user = self.superuser + cl = m.get_changelist_instance(request) + self.assertEqual(cl.get_ordering_field_columns(), {3: 'asc', 2: 'asc'}) + def test_select_related_preserved(self): """ Regression test for #10348: ChangeList.get_queryset() shouldn't @@ -72,29 +98,23 @@ def test_select_related_preserved(self): """ m = ChildAdmin(Child, custom_site) request = self.factory.get('/child/') - cl = ChangeList( - request, Child, - *get_changelist_args(m, list_select_related=m.get_list_select_related(request)) - ) + request.user = self.superuser + cl = m.get_changelist_instance(request) self.assertEqual(cl.queryset.query.select_related, {'parent': {}}) def test_select_related_as_tuple(self): ia = InvitationAdmin(Invitation, custom_site) request = self.factory.get('/invitation/') - cl = ChangeList( - request, Child, - *get_changelist_args(ia, list_select_related=ia.get_list_select_related(request)) - ) + request.user = self.superuser + cl = ia.get_changelist_instance(request) self.assertEqual(cl.queryset.query.select_related, {'player': {}}) def test_select_related_as_empty_tuple(self): ia = InvitationAdmin(Invitation, custom_site) ia.list_select_related = () request = self.factory.get('/invitation/') - cl = ChangeList( - request, Child, - *get_changelist_args(ia, list_select_related=ia.get_list_select_related(request)) - ) + request.user = self.superuser + cl = ia.get_changelist_instance(request) self.assertIs(cl.queryset.query.select_related, False) def test_get_select_related_custom_method(self): @@ -106,10 +126,8 @@ def get_list_select_related(self, request): ia = GetListSelectRelatedAdmin(Invitation, custom_site) request = self.factory.get('/invitation/') - cl = ChangeList( - request, Child, - *get_changelist_args(ia, list_select_related=ia.get_list_select_related(request)) - ) + request.user = self.superuser + cl = ia.get_changelist_instance(request) self.assertEqual(cl.queryset.query.select_related, {'player': {}, 'band': {}}) def test_result_list_empty_changelist_value(self): @@ -119,17 +137,15 @@ def test_result_list_empty_changelist_value(self): """ new_child = Child.objects.create(name='name', parent=None) request = self.factory.get('/child/') + request.user = self.superuser m = ChildAdmin(Child, custom_site) - cl = ChangeList(request, Child, *get_changelist_args(m)) + cl = m.get_changelist_instance(request) cl.formset = None template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}') - context = Context({'cl': cl}) + context = Context({'cl': cl, 'opts': Child._meta}) table_output = template.render(context) link = reverse('admin:admin_changelist_child_change', args=(new_child.id,)) - row_html = ( - 'name' - '-' % link - ) + row_html = build_tbody_html(new_child.id, link, '-') self.assertNotEqual(table_output.find(row_html), -1, 'Failed to find expected row element: %s' % table_output) def test_result_list_set_empty_value_display_on_admin_site(self): @@ -138,19 +154,17 @@ def test_result_list_set_empty_value_display_on_admin_site(self): """ new_child = Child.objects.create(name='name', parent=None) request = self.factory.get('/child/') + request.user = self.superuser # Set a new empty display value on AdminSite. admin.site.empty_value_display = '???' m = ChildAdmin(Child, admin.site) - cl = ChangeList(request, Child, *get_changelist_args(m)) + cl = m.get_changelist_instance(request) cl.formset = None template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}') - context = Context({'cl': cl}) + context = Context({'cl': cl, 'opts': Child._meta}) table_output = template.render(context) link = reverse('admin:admin_changelist_child_change', args=(new_child.id,)) - row_html = ( - 'name' - '???' % link - ) + row_html = build_tbody_html(new_child.id, link, '???') self.assertNotEqual(table_output.find(row_html), -1, 'Failed to find expected row element: %s' % table_output) def test_result_list_set_empty_value_display_in_model_admin(self): @@ -159,16 +173,19 @@ def test_result_list_set_empty_value_display_in_model_admin(self): """ new_child = Child.objects.create(name='name', parent=None) request = self.factory.get('/child/') + request.user = self.superuser m = EmptyValueChildAdmin(Child, admin.site) - cl = ChangeList(request, Child, *get_changelist_args(m)) + cl = m.get_changelist_instance(request) cl.formset = None template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}') - context = Context({'cl': cl}) + context = Context({'cl': cl, 'opts': Child._meta}) table_output = template.render(context) link = reverse('admin:admin_changelist_child_change', args=(new_child.id,)) - row_html = ( - 'name' - '&dagger;-empty-' % link + row_html = build_tbody_html( + new_child.id, + link, + '&dagger;' + '-empty-' ) self.assertNotEqual(table_output.find(row_html), -1, 'Failed to find expected row element: %s' % table_output) @@ -180,17 +197,15 @@ def test_result_list_html(self): new_parent = Parent.objects.create(name='parent') new_child = Child.objects.create(name='name', parent=new_parent) request = self.factory.get('/child/') + request.user = self.superuser m = ChildAdmin(Child, custom_site) - cl = ChangeList(request, Child, *get_changelist_args(m)) + cl = m.get_changelist_instance(request) cl.formset = None template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}') - context = Context({'cl': cl}) + context = Context({'cl': cl, 'opts': Child._meta}) table_output = template.render(context) link = reverse('admin:admin_changelist_child_change', args=(new_child.id,)) - row_html = ( - 'name' - 'Parent object' % link - ) + row_html = build_tbody_html(new_child.id, link, '%s' % new_parent) self.assertNotEqual(table_output.find(row_html), -1, 'Failed to find expected row element: %s' % table_output) def test_result_list_editable_html(self): @@ -205,22 +220,23 @@ def test_result_list_editable_html(self): new_parent = Parent.objects.create(name='parent') new_child = Child.objects.create(name='name', parent=new_parent) request = self.factory.get('/child/') + request.user = self.superuser m = ChildAdmin(Child, custom_site) # Test with list_editable fields m.list_display = ['id', 'name', 'parent'] m.list_display_links = ['id'] m.list_editable = ['name'] - cl = ChangeList(request, Child, *get_changelist_args(m)) + cl = m.get_changelist_instance(request) FormSet = m.get_changelist_formset(request) cl.formset = FormSet(queryset=cl.result_list) template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}') - context = Context({'cl': cl}) + context = Context({'cl': cl, 'opts': Child._meta}) table_output = template.render(context) # make sure that hidden fields are in the correct place hiddenfields_div = ( '
      ' - '' + '' '
      ' ) % new_child.id self.assertInHTML(hiddenfields_div, table_output, msg_prefix='Failed to find hidden fields') @@ -228,7 +244,7 @@ def test_result_list_editable_html(self): # make sure that list editable fields are rendered in divs correctly editable_name_field = ( '' + 'maxlength="30" type="text" id="id_form-0-name">' ) self.assertInHTML( '%s' % editable_name_field, @@ -244,6 +260,7 @@ def test_result_list_editable(self): for i in range(200): Child.objects.create(name='name %s' % i, parent=new_parent) request = self.factory.get('/child/', data={'p': -1}) # Anything outside range + request.user = self.superuser m = ChildAdmin(Child, custom_site) # Test with list_editable fields @@ -251,35 +268,7 @@ def test_result_list_editable(self): m.list_display_links = ['id'] m.list_editable = ['name'] with self.assertRaises(IncorrectLookupParameters): - ChangeList(request, Child, *get_changelist_args(m)) - - @ignore_warnings(category=RemovedInDjango20Warning) - def test_result_list_with_allow_tags(self): - """ - Test for deprecation of allow_tags attribute - """ - new_parent = Parent.objects.create(name='parent') - for i in range(2): - Child.objects.create(name='name %s' % i, parent=new_parent) - request = self.factory.get('/child/') - m = ChildAdmin(Child, custom_site) - - def custom_method(self, obj=None): - return 'Unsafe html
      ' - custom_method.allow_tags = True - - # Add custom method with allow_tags attribute - m.custom_method = custom_method - m.list_display = ['id', 'name', 'parent', 'custom_method'] - - cl = ChangeList(request, Child, *get_changelist_args(m)) - FormSet = m.get_changelist_formset(request) - cl.formset = FormSet(queryset=cl.result_list) - template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}') - context = Context({'cl': cl}) - table_output = template.render(context) - custom_field_html = 'Unsafe html
      ' - self.assertInHTML(custom_field_html, table_output) + m.get_changelist_instance(request) def test_custom_paginator(self): new_parent = Parent.objects.create(name='parent') @@ -287,9 +276,10 @@ def test_custom_paginator(self): Child.objects.create(name='name %s' % i, parent=new_parent) request = self.factory.get('/child/') + request.user = self.superuser m = CustomPaginationAdmin(Child, custom_site) - cl = ChangeList(request, Child, *get_changelist_args(m)) + cl = m.get_changelist_instance(request) cl.get_results(request) self.assertIsInstance(cl.paginator, CustomPaginator) @@ -306,8 +296,9 @@ def test_distinct_for_m2m_in_list_filter(self): m = BandAdmin(Band, custom_site) request = self.factory.get('/band/', data={'genres': blues.pk}) + request.user = self.superuser - cl = ChangeList(request, Band, *get_changelist_args(m)) + cl = m.get_changelist_instance(request) cl.get_results(request) # There's only one Group instance @@ -325,8 +316,9 @@ def test_distinct_for_through_m2m_in_list_filter(self): m = GroupAdmin(Group, custom_site) request = self.factory.get('/group/', data={'members': lead.pk}) + request.user = self.superuser - cl = ChangeList(request, Group, *get_changelist_args(m)) + cl = m.get_changelist_instance(request) cl.get_results(request) # There's only one Group instance @@ -346,8 +338,9 @@ def test_distinct_for_through_m2m_at_second_level_in_list_filter(self): m = ConcertAdmin(Concert, custom_site) request = self.factory.get('/concert/', data={'group__members': lead.pk}) + request.user = self.superuser - cl = ChangeList(request, Concert, *get_changelist_args(m)) + cl = m.get_changelist_instance(request) cl.get_results(request) # There's only one Concert instance @@ -357,7 +350,7 @@ def test_distinct_for_inherited_m2m_in_list_filter(self): """ Regression test for #13902: When using a ManyToMany in list_filter, results shouldn't appear more than once. Model managed in the - admin inherits from the one that defins the relationship. + admin inherits from the one that defines the relationship. """ lead = Musician.objects.create(name='John') four = Quartet.objects.create(name='The Beatles') @@ -366,8 +359,9 @@ def test_distinct_for_inherited_m2m_in_list_filter(self): m = QuartetAdmin(Quartet, custom_site) request = self.factory.get('/quartet/', data={'members': lead.pk}) + request.user = self.superuser - cl = ChangeList(request, Quartet, *get_changelist_args(m)) + cl = m.get_changelist_instance(request) cl.get_results(request) # There's only one Quartet instance @@ -386,8 +380,9 @@ def test_distinct_for_m2m_to_inherited_in_list_filter(self): m = ChordsBandAdmin(ChordsBand, custom_site) request = self.factory.get('/chordsband/', data={'members': lead.pk}) + request.user = self.superuser - cl = ChangeList(request, ChordsBand, *get_changelist_args(m)) + cl = m.get_changelist_instance(request) cl.get_results(request) # There's only one ChordsBand instance @@ -405,8 +400,9 @@ def test_distinct_for_non_unique_related_object_in_list_filter(self): m = ParentAdmin(Parent, custom_site) request = self.factory.get('/parent/', data={'child__name': 'Daniel'}) + request.user = self.superuser - cl = ChangeList(request, Parent, *get_changelist_args(m)) + cl = m.get_changelist_instance(request) # Make sure distinct() was called self.assertEqual(cl.queryset.count(), 1) @@ -421,8 +417,9 @@ def test_distinct_for_non_unique_related_object_in_search_fields(self): m = ParentAdmin(Parent, custom_site) request = self.factory.get('/parent/', data={SEARCH_VAR: 'daniel'}) + request.user = self.superuser - cl = ChangeList(request, Parent, *get_changelist_args(m)) + cl = m.get_changelist_instance(request) # Make sure distinct() was called self.assertEqual(cl.queryset.count(), 1) @@ -440,11 +437,103 @@ def test_distinct_for_many_to_many_at_second_level_in_search_fields(self): m = ConcertAdmin(Concert, custom_site) request = self.factory.get('/concert/', data={SEARCH_VAR: 'vox'}) + request.user = self.superuser - cl = ChangeList(request, Concert, *get_changelist_args(m)) + cl = m.get_changelist_instance(request) # There's only one Concert instance self.assertEqual(cl.queryset.count(), 1) + def test_pk_in_search_fields(self): + band = Group.objects.create(name='The Hype') + Concert.objects.create(name='Woodstock', group=band) + + m = ConcertAdmin(Concert, custom_site) + m.search_fields = ['group__pk'] + + request = self.factory.get('/concert/', data={SEARCH_VAR: band.pk}) + request.user = self.superuser + cl = m.get_changelist_instance(request) + self.assertEqual(cl.queryset.count(), 1) + + request = self.factory.get('/concert/', data={SEARCH_VAR: band.pk + 5}) + request.user = self.superuser + cl = m.get_changelist_instance(request) + self.assertEqual(cl.queryset.count(), 0) + + def test_builtin_lookup_in_search_fields(self): + band = Group.objects.create(name='The Hype') + concert = Concert.objects.create(name='Woodstock', group=band) + + m = ConcertAdmin(Concert, custom_site) + m.search_fields = ['name__iexact'] + + request = self.factory.get('/', data={SEARCH_VAR: 'woodstock'}) + request.user = self.superuser + cl = m.get_changelist_instance(request) + self.assertCountEqual(cl.queryset, [concert]) + + request = self.factory.get('/', data={SEARCH_VAR: 'wood'}) + request.user = self.superuser + cl = m.get_changelist_instance(request) + self.assertCountEqual(cl.queryset, []) + + def test_custom_lookup_in_search_fields(self): + band = Group.objects.create(name='The Hype') + concert = Concert.objects.create(name='Woodstock', group=band) + + m = ConcertAdmin(Concert, custom_site) + m.search_fields = ['group__name__cc'] + with register_lookup(Field, Contains, lookup_name='cc'): + request = self.factory.get('/', data={SEARCH_VAR: 'Hype'}) + request.user = self.superuser + cl = m.get_changelist_instance(request) + self.assertCountEqual(cl.queryset, [concert]) + + request = self.factory.get('/', data={SEARCH_VAR: 'Woodstock'}) + request.user = self.superuser + cl = m.get_changelist_instance(request) + self.assertCountEqual(cl.queryset, []) + + def test_spanning_relations_with_custom_lookup_in_search_fields(self): + hype = Group.objects.create(name='The Hype') + concert = Concert.objects.create(name='Woodstock', group=hype) + vox = Musician.objects.create(name='Vox', age=20) + Membership.objects.create(music=vox, group=hype) + # Register a custom lookup on IntegerField to ensure that field + # traversing logic in ModelAdmin.get_search_results() works. + with register_lookup(IntegerField, Exact, lookup_name='exactly'): + m = ConcertAdmin(Concert, custom_site) + m.search_fields = ['group__members__age__exactly'] + + request = self.factory.get('/', data={SEARCH_VAR: '20'}) + request.user = self.superuser + cl = m.get_changelist_instance(request) + self.assertCountEqual(cl.queryset, [concert]) + + request = self.factory.get('/', data={SEARCH_VAR: '21'}) + request.user = self.superuser + cl = m.get_changelist_instance(request) + self.assertCountEqual(cl.queryset, []) + + def test_custom_lookup_with_pk_shortcut(self): + self.assertEqual(CharPK._meta.pk.name, 'char_pk') # Not equal to 'pk'. + m = admin.ModelAdmin(CustomIdUser, custom_site) + + abc = CharPK.objects.create(char_pk='abc') + abcd = CharPK.objects.create(char_pk='abcd') + m = admin.ModelAdmin(CharPK, custom_site) + m.search_fields = ['pk__exact'] + + request = self.factory.get('/', data={SEARCH_VAR: 'abc'}) + request.user = self.superuser + cl = m.get_changelist_instance(request) + self.assertCountEqual(cl.queryset, [abc]) + + request = self.factory.get('/', data={SEARCH_VAR: 'abcd'}) + request.user = self.superuser + cl = m.get_changelist_instance(request) + self.assertCountEqual(cl.queryset, [abcd]) + def test_no_distinct_for_m2m_in_list_filter_without_params(self): """ If a ManyToManyField is in list_filter but isn't in any lookup params, @@ -453,12 +542,14 @@ def test_no_distinct_for_m2m_in_list_filter_without_params(self): m = BandAdmin(Band, custom_site) for lookup_params in ({}, {'name': 'test'}): request = self.factory.get('/band/', lookup_params) - cl = ChangeList(request, Band, *get_changelist_args(m)) + request.user = self.superuser + cl = m.get_changelist_instance(request) self.assertFalse(cl.queryset.query.distinct) # A ManyToManyField in params does have distinct applied. request = self.factory.get('/band/', {'genres': '0'}) - cl = ChangeList(request, Band, *get_changelist_args(m)) + request.user = self.superuser + cl = m.get_changelist_instance(request) self.assertTrue(cl.queryset.query.distinct) def test_pagination(self): @@ -472,17 +563,18 @@ def test_pagination(self): Child.objects.create(name='filtered %s' % i, parent=parent) request = self.factory.get('/child/') + request.user = self.superuser # Test default queryset m = ChildAdmin(Child, custom_site) - cl = ChangeList(request, Child, *get_changelist_args(m)) + cl = m.get_changelist_instance(request) self.assertEqual(cl.queryset.count(), 60) self.assertEqual(cl.paginator.count, 60) self.assertEqual(list(cl.paginator.page_range), [1, 2, 3, 4, 5, 6]) # Test custom queryset m = FilteredChildAdmin(Child, custom_site) - cl = ChangeList(request, Child, *get_changelist_args(m)) + cl = m.get_changelist_instance(request) self.assertEqual(cl.queryset.count(), 30) self.assertEqual(cl.paginator.count, 30) self.assertEqual(list(cl.paginator.page_range), [1, 2, 3]) @@ -492,12 +584,11 @@ def test_computed_list_display_localization(self): Regression test for #13196: output of functions should be localized in the changelist. """ - superuser = User.objects.create_superuser(username='super', email='super@localhost', password='secret') - self.client.force_login(superuser) + self.client.force_login(self.superuser) event = Event.objects.create(date=datetime.date.today()) response = self.client.get(reverse('admin:admin_changelist_event_changelist')) self.assertContains(response, formats.localize(event.date)) - self.assertNotContains(response, six.text_type(event.date)) + self.assertNotContains(response, str(event.date)) def test_dynamic_list_display(self): """ @@ -549,12 +640,13 @@ def test_show_all(self): # Add "show all" parameter to request request = self.factory.get('/child/', data={ALL_VAR: ''}) + request.user = self.superuser # Test valid "show all" request (number of total objects is under max) m = ChildAdmin(Child, custom_site) m.list_max_show_all = 200 # 200 is the max we'll pass to ChangeList - cl = ChangeList(request, Child, *get_changelist_args(m)) + cl = m.get_changelist_instance(request) cl.get_results(request) self.assertEqual(len(cl.result_list), 60) @@ -563,7 +655,7 @@ def test_show_all(self): m = ChildAdmin(Child, custom_site) m.list_max_show_all = 30 # 30 is the max we'll pass to ChangeList for this test - cl = ChangeList(request, Child, *get_changelist_args(m)) + cl = m.get_changelist_instance(request) cl.get_results(request) self.assertEqual(len(cl.result_list), 10) @@ -599,10 +691,6 @@ def test_no_list_display_links(self): self.assertNotContains(response, '' % link) def test_tuple_list_display(self): - """ - Regression test for #17128 - (ChangeList failing under Python 2.5 after r16319) - """ swallow = Swallow.objects.create(origin='Africa', load='12.34', speed='22.2') swallow2 = Swallow.objects.create(origin='Africa', load='12.34', speed='22.2') swallow_o2o = SwallowOneToOne.objects.create(swallow=swallow2) @@ -612,9 +700,9 @@ def test_tuple_list_display(self): request = self._mocked_authenticated_request('/swallow/', superuser) response = model_admin.changelist_view(request) # just want to ensure it doesn't blow up during rendering - self.assertContains(response, six.text_type(swallow.origin)) - self.assertContains(response, six.text_type(swallow.load)) - self.assertContains(response, six.text_type(swallow.speed)) + self.assertContains(response, str(swallow.origin)) + self.assertContains(response, str(swallow.load)) + self.assertContains(response, str(swallow.speed)) # Reverse one-to-one relations should work. self.assertContains(response, '-') self.assertContains(response, '%s' % swallow_o2o) @@ -654,9 +742,9 @@ def test_multiuser_edit(self): 'form-INITIAL_FORMS': '3', 'form-MIN_NUM_FORMS': '0', 'form-MAX_NUM_FORMS': '1000', - 'form-0-id': str(d.pk), - 'form-1-id': str(c.pk), - 'form-2-id': str(a.pk), + 'form-0-uuid': str(d.pk), + 'form-1-uuid': str(c.pk), + 'form-2-uuid': str(a.pk), 'form-0-load': '9.0', 'form-0-speed': '9.0', 'form-1-load': '5.0', @@ -686,6 +774,103 @@ def test_multiuser_edit(self): # No new swallows were created. self.assertEqual(len(Swallow.objects.all()), 4) + def test_get_edited_object_ids(self): + a = Swallow.objects.create(origin='Swallow A', load=4, speed=1) + b = Swallow.objects.create(origin='Swallow B', load=2, speed=2) + c = Swallow.objects.create(origin='Swallow C', load=5, speed=5) + superuser = self._create_superuser('superuser') + self.client.force_login(superuser) + changelist_url = reverse('admin:admin_changelist_swallow_changelist') + m = SwallowAdmin(Swallow, custom_site) + data = { + 'form-TOTAL_FORMS': '3', + 'form-INITIAL_FORMS': '3', + 'form-MIN_NUM_FORMS': '0', + 'form-MAX_NUM_FORMS': '1000', + 'form-0-uuid': str(a.pk), + 'form-1-uuid': str(b.pk), + 'form-2-uuid': str(c.pk), + 'form-0-load': '9.0', + 'form-0-speed': '9.0', + 'form-1-load': '5.0', + 'form-1-speed': '5.0', + 'form-2-load': '5.0', + 'form-2-speed': '4.0', + '_save': 'Save', + } + request = self.factory.post(changelist_url, data=data) + pks = m._get_edited_object_pks(request, prefix='form') + self.assertEqual(sorted(pks), sorted([str(a.pk), str(b.pk), str(c.pk)])) + + def test_get_list_editable_queryset(self): + a = Swallow.objects.create(origin='Swallow A', load=4, speed=1) + Swallow.objects.create(origin='Swallow B', load=2, speed=2) + data = { + 'form-TOTAL_FORMS': '2', + 'form-INITIAL_FORMS': '2', + 'form-MIN_NUM_FORMS': '0', + 'form-MAX_NUM_FORMS': '1000', + 'form-0-uuid': str(a.pk), + 'form-0-load': '10', + '_save': 'Save', + } + superuser = self._create_superuser('superuser') + self.client.force_login(superuser) + changelist_url = reverse('admin:admin_changelist_swallow_changelist') + m = SwallowAdmin(Swallow, custom_site) + request = self.factory.post(changelist_url, data=data) + queryset = m._get_list_editable_queryset(request, prefix='form') + self.assertEqual(queryset.count(), 1) + data['form-0-uuid'] = 'INVALD_PRIMARY_KEY' + # The unfiltered queryset is returned if there's invalid data. + request = self.factory.post(changelist_url, data=data) + queryset = m._get_list_editable_queryset(request, prefix='form') + self.assertEqual(queryset.count(), 2) + + def test_get_list_editable_queryset_with_regex_chars_in_prefix(self): + a = Swallow.objects.create(origin='Swallow A', load=4, speed=1) + Swallow.objects.create(origin='Swallow B', load=2, speed=2) + data = { + 'form$-TOTAL_FORMS': '2', + 'form$-INITIAL_FORMS': '2', + 'form$-MIN_NUM_FORMS': '0', + 'form$-MAX_NUM_FORMS': '1000', + 'form$-0-uuid': str(a.pk), + 'form$-0-load': '10', + '_save': 'Save', + } + superuser = self._create_superuser('superuser') + self.client.force_login(superuser) + changelist_url = reverse('admin:admin_changelist_swallow_changelist') + m = SwallowAdmin(Swallow, custom_site) + request = self.factory.post(changelist_url, data=data) + queryset = m._get_list_editable_queryset(request, prefix='form$') + self.assertEqual(queryset.count(), 1) + + def test_changelist_view_list_editable_changed_objects_uses_filter(self): + """list_editable edits use a filtered queryset to limit memory usage.""" + a = Swallow.objects.create(origin='Swallow A', load=4, speed=1) + Swallow.objects.create(origin='Swallow B', load=2, speed=2) + data = { + 'form-TOTAL_FORMS': '2', + 'form-INITIAL_FORMS': '2', + 'form-MIN_NUM_FORMS': '0', + 'form-MAX_NUM_FORMS': '1000', + 'form-0-uuid': str(a.pk), + 'form-0-load': '10', + '_save': 'Save', + } + superuser = self._create_superuser('superuser') + self.client.force_login(superuser) + changelist_url = reverse('admin:admin_changelist_swallow_changelist') + with CaptureQueriesContext(connection) as context: + response = self.client.post(changelist_url, data=data) + self.assertEqual(response.status_code, 200) + self.assertIn('WHERE', context.captured_queries[4]['sql']) + self.assertIn('IN', context.captured_queries[4]['sql']) + # Check only the first few characters since the UUID may have dashes. + self.assertIn(str(a.pk)[:8], context.captured_queries[4]['sql']) + def test_deterministic_order_for_unordered_model(self): """ The primary key is used in the ordering of the changelist's results to @@ -774,6 +959,81 @@ def check_results_order(ascending=False): OrderedObjectAdmin.ordering = ['id', 'bool'] check_results_order(ascending=True) + @isolate_apps('admin_changelist') + def test_total_ordering_optimization(self): + class Related(models.Model): + unique_field = models.BooleanField(unique=True) + + class Meta: + ordering = ('unique_field',) + + class Model(models.Model): + unique_field = models.BooleanField(unique=True) + unique_nullable_field = models.BooleanField(unique=True, null=True) + related = models.ForeignKey(Related, models.CASCADE) + other_related = models.ForeignKey(Related, models.CASCADE) + related_unique = models.OneToOneField(Related, models.CASCADE) + field = models.BooleanField() + other_field = models.BooleanField() + null_field = models.BooleanField(null=True) + + class Meta: + unique_together = { + ('field', 'other_field'), + ('field', 'null_field'), + ('related', 'other_related_id'), + } + + class ModelAdmin(admin.ModelAdmin): + def get_queryset(self, request): + return Model.objects.none() + + request = self._mocked_authenticated_request('/', self.superuser) + site = admin.AdminSite(name='admin') + model_admin = ModelAdmin(Model, site) + change_list = model_admin.get_changelist_instance(request) + tests = ( + ([], ['-pk']), + # Unique non-nullable field. + (['unique_field'], ['unique_field']), + (['-unique_field'], ['-unique_field']), + # Unique nullable field. + (['unique_nullable_field'], ['unique_nullable_field', '-pk']), + # Field. + (['field'], ['field', '-pk']), + # Related field introspection is not implemented. + (['related__unique_field'], ['related__unique_field', '-pk']), + # Related attname unique. + (['related_unique_id'], ['related_unique_id']), + # Related ordering introspection is not implemented. + (['related_unique'], ['related_unique', '-pk']), + # Composite unique. + (['field', '-other_field'], ['field', '-other_field']), + # Composite unique nullable. + (['-field', 'null_field'], ['-field', 'null_field', '-pk']), + # Composite unique nullable. + (['-field', 'null_field'], ['-field', 'null_field', '-pk']), + # Composite unique nullable. + (['-field', 'null_field'], ['-field', 'null_field', '-pk']), + # Composite unique and nullable. + (['-field', 'null_field', 'other_field'], ['-field', 'null_field', 'other_field']), + # Composite unique attnames. + (['related_id', '-other_related_id'], ['related_id', '-other_related_id']), + # Composite unique names. + (['related', '-other_related_id'], ['related', '-other_related_id', '-pk']), + ) + # F() objects composite unique. + total_ordering = [F('field'), F('other_field').desc(nulls_last=True)] + # F() objects composite unique nullable. + non_total_ordering = [F('field'), F('null_field').desc(nulls_last=True)] + tests += ( + (total_ordering, total_ordering), + (non_total_ordering, non_total_ordering + ['-pk']), + ) + for ordering, expected in tests: + with self.subTest(ordering=ordering): + self.assertEqual(change_list._get_deterministic_ordering(ordering), expected) + def test_dynamic_list_filter(self): """ Regression tests for ticket #17646: dynamic list_filter support. @@ -812,7 +1072,8 @@ def test_pagination_page_range(self): # instantiating and setting up ChangeList object m = GroupAdmin(Group, custom_site) request = self.factory.get('/group/') - cl = ChangeList(request, Group, *get_changelist_args(m)) + request.user = self.superuser + cl = m.get_changelist_instance(request) per_page = cl.list_per_page = 10 for page_num, objects_count, expected_page_range in [ @@ -832,11 +1093,7 @@ def test_pagination_page_range(self): cl.page_num = page_num cl.get_results(request) real_page_range = pagination(cl)['page_range'] - - self.assertListEqual( - expected_page_range, - list(real_page_range), - ) + self.assertEqual(expected_page_range, list(real_page_range)) def test_object_tools_displayed_no_add_permission(self): """ @@ -853,34 +1110,24 @@ def test_object_tools_displayed_no_add_permission(self): self.assertNotIn('Add ', response.rendered_content) -class AdminLogNodeTestCase(TestCase): +class GetAdminLogTests(TestCase): - def test_get_admin_log_templatetag_custom_user(self): + def test_custom_user_pk_not_named_id(self): """ - Regression test for ticket #20088: admin log depends on User model - having id field as primary key. - - The old implementation raised an AttributeError when trying to use - the id field. + {% get_admin_log %} works if the user model's primary key isn't named + 'id'. """ context = Context({'user': CustomIdUser()}) - template_string = '{% load log %}{% get_admin_log 10 as admin_log for_user user %}' - - template = Template(template_string) - - # Rendering should be u'' since this templatetag just logs, - # it doesn't render any string. + template = Template('{% load log %}{% get_admin_log 10 as admin_log for_user user %}') + # This template tag just logs. self.assertEqual(template.render(context), '') - def test_get_admin_log_templatetag_no_user(self): - """ - The {% get_admin_log %} tag should work without specifying a user. - """ + def test_no_user(self): + """{% get_admin_log %} works without specifying a user.""" user = User(username='jondoe', password='secret', email='super@example.com') user.save() ct = ContentType.objects.get_for_model(User) LogEntry.objects.log_action(user.pk, ct.pk, user.pk, repr(user), 1) - t = Template( '{% load log %}' '{% get_admin_log 100 as admin_log %}' @@ -890,6 +1137,26 @@ def test_get_admin_log_templatetag_no_user(self): ) self.assertEqual(t.render(Context({})), 'Added "".') + def test_missing_args(self): + msg = "'get_admin_log' statements require two arguments" + with self.assertRaisesMessage(TemplateSyntaxError, msg): + Template('{% load log %}{% get_admin_log 10 as %}') + + def test_non_integer_limit(self): + msg = "First argument to 'get_admin_log' must be an integer" + with self.assertRaisesMessage(TemplateSyntaxError, msg): + Template('{% load log %}{% get_admin_log "10" as admin_log for_user user %}') + + def test_without_as(self): + msg = "Second argument to 'get_admin_log' must be 'as'" + with self.assertRaisesMessage(TemplateSyntaxError, msg): + Template('{% load log %}{% get_admin_log 10 ad admin_log for_user user %}') + + def test_without_for_user(self): + msg = "Fourth argument to 'get_admin_log' must be 'for_user'" + with self.assertRaisesMessage(TemplateSyntaxError, msg): + Template('{% load log %}{% get_admin_log 10 as admin_log foruser user %}') + @override_settings(ROOT_URLCONF='admin_changelist.urls') class SeleniumTests(AdminSeleniumTestCase): diff --git a/tests/admin_changelist/urls.py b/tests/admin_changelist/urls.py index 1f553a85a999..be569cdca51c 100644 --- a/tests/admin_changelist/urls.py +++ b/tests/admin_changelist/urls.py @@ -1,7 +1,7 @@ -from django.conf.urls import url +from django.urls import path from . import admin urlpatterns = [ - url(r'^admin/', admin.site.urls), + path('admin/', admin.site.urls), ] diff --git a/tests/admin_checks/models.py b/tests/admin_checks/models.py index 822d695e4d97..3336ce878e6a 100644 --- a/tests/admin_checks/models.py +++ b/tests/admin_checks/models.py @@ -5,14 +5,12 @@ from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.db import models -from django.utils.encoding import python_2_unicode_compatible class Album(models.Model): title = models.CharField(max_length=150) -@python_2_unicode_compatible class Song(models.Model): title = models.CharField(max_length=150) album = models.ForeignKey(Album, models.CASCADE) diff --git a/tests/admin_checks/tests.py b/tests/admin_checks/tests.py index 3d629077c5cb..1e267e03a3b0 100644 --- a/tests/admin_checks/tests.py +++ b/tests/admin_checks/tests.py @@ -1,9 +1,11 @@ -from __future__ import unicode_literals - from django import forms from django.contrib import admin from django.contrib.admin import AdminSite +from django.contrib.auth.backends import ModelBackend +from django.contrib.auth.middleware import AuthenticationMiddleware from django.contrib.contenttypes.admin import GenericStackedInline +from django.contrib.messages.middleware import MessageMiddleware +from django.contrib.sessions.middleware import SessionMiddleware from django.core import checks from django.test import SimpleTestCase, override_settings @@ -39,9 +41,31 @@ def check(self, **kwargs): return ['error!'] +class AuthenticationMiddlewareSubclass(AuthenticationMiddleware): + pass + + +class MessageMiddlewareSubclass(MessageMiddleware): + pass + + +class ModelBackendSubclass(ModelBackend): + pass + + +class SessionMiddlewareSubclass(SessionMiddleware): + pass + + @override_settings( SILENCED_SYSTEM_CHECKS=['fields.W342'], # ForeignKey(unique=True) - INSTALLED_APPS=['django.contrib.auth', 'django.contrib.contenttypes', 'admin_checks'] + INSTALLED_APPS=[ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.messages', + 'admin_checks', + ], ) class SystemChecksTestCase(SimpleTestCase): @@ -55,23 +79,39 @@ def test_checks_are_performed(self): admin.site.unregister(Song) @override_settings(INSTALLED_APPS=['django.contrib.admin']) - def test_contenttypes_dependency(self): + def test_apps_dependencies(self): errors = admin.checks.check_dependencies() expected = [ checks.Error( "'django.contrib.contenttypes' must be in " "INSTALLED_APPS in order to use the admin application.", id="admin.E401", - ) + ), + checks.Error( + "'django.contrib.auth' must be in INSTALLED_APPS in order " + "to use the admin application.", + id='admin.E405', + ), + checks.Error( + "'django.contrib.messages' must be in INSTALLED_APPS in order " + "to use the admin application.", + id='admin.E406', + ), ] self.assertEqual(errors, expected) + @override_settings(TEMPLATES=[]) + def test_no_template_engines(self): + self.assertEqual(admin.checks.check_dependencies(), [ + checks.Error( + "A 'django.template.backends.django.DjangoTemplates' " + "instance must be configured in TEMPLATES in order to use " + "the admin application.", + id='admin.E403', + ) + ]) + @override_settings( - INSTALLED_APPS=[ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - ], TEMPLATES=[{ 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], @@ -81,17 +121,111 @@ def test_contenttypes_dependency(self): }, }], ) - def test_auth_contextprocessor_dependency(self): - errors = admin.checks.check_dependencies() + def test_context_processor_dependencies(self): expected = [ checks.Error( - "'django.contrib.auth.context_processors.auth' must be in " - "TEMPLATES in order to use the admin application.", - id="admin.E402", + "'django.contrib.auth.context_processors.auth' must be " + "enabled in DjangoTemplates (TEMPLATES) if using the default " + "auth backend in order to use the admin application.", + id='admin.E402', + ), + checks.Error( + "'django.contrib.messages.context_processors.messages' must " + "be enabled in DjangoTemplates (TEMPLATES) in order to use " + "the admin application.", + id='admin.E404', ) ] + self.assertEqual(admin.checks.check_dependencies(), expected) + # The first error doesn't happen if + # 'django.contrib.auth.backends.ModelBackend' isn't in + # AUTHENTICATION_BACKENDS. + with self.settings(AUTHENTICATION_BACKENDS=[]): + self.assertEqual(admin.checks.check_dependencies(), expected[1:]) + + @override_settings( + AUTHENTICATION_BACKENDS=['admin_checks.tests.ModelBackendSubclass'], + TEMPLATES=[{ + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': ['django.contrib.messages.context_processors.messages'], + }, + }], + ) + def test_context_processor_dependencies_model_backend_subclass(self): + self.assertEqual(admin.checks.check_dependencies(), [ + checks.Error( + "'django.contrib.auth.context_processors.auth' must be " + "enabled in DjangoTemplates (TEMPLATES) if using the default " + "auth backend in order to use the admin application.", + id='admin.E402', + ), + ]) + + @override_settings( + TEMPLATES=[ + { + 'BACKEND': 'django.template.backends.dummy.TemplateStrings', + 'DIRS': [], + 'APP_DIRS': True, + }, + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, + ], + ) + def test_several_templates_backends(self): + self.assertEqual(admin.checks.check_dependencies(), []) + + @override_settings(MIDDLEWARE=[]) + def test_middleware_dependencies(self): + errors = admin.checks.check_dependencies() + expected = [ + checks.Error( + "'django.contrib.auth.middleware.AuthenticationMiddleware' " + "must be in MIDDLEWARE in order to use the admin application.", + id='admin.E408', + ), + checks.Error( + "'django.contrib.messages.middleware.MessageMiddleware' " + "must be in MIDDLEWARE in order to use the admin application.", + id='admin.E409', + ), + checks.Error( + "'django.contrib.sessions.middleware.SessionMiddleware' " + "must be in MIDDLEWARE in order to use the admin application.", + id='admin.E410', + ), + ] self.assertEqual(errors, expected) + @override_settings(MIDDLEWARE=[ + 'admin_checks.tests.AuthenticationMiddlewareSubclass', + 'admin_checks.tests.MessageMiddlewareSubclass', + 'admin_checks.tests.SessionMiddlewareSubclass', + ]) + def test_middleware_subclasses(self): + self.assertEqual(admin.checks.check_dependencies(), []) + + @override_settings(MIDDLEWARE=[ + 'django.contrib.does.not.Exist', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + ]) + def test_admin_check_ignores_import_error_in_middleware(self): + self.assertEqual(admin.checks.check_dependencies(), []) + def test_custom_adminsite(self): class CustomAdminSite(admin.AdminSite): pass @@ -108,7 +242,7 @@ class CustomAdminSite(admin.AdminSite): def test_allows_checks_relying_on_other_modeladmins(self): class MyBookAdmin(admin.ModelAdmin): def check(self, **kwargs): - errors = super(MyBookAdmin, self).check(**kwargs) + errors = super().check(**kwargs) author_admin = self.admin_site._registry.get(Author) if author_admin is None: errors.append('AuthorAdmin missing!') @@ -140,6 +274,31 @@ class SongAdmin(admin.ModelAdmin): ] self.assertEqual(errors, expected) + def test_list_editable_not_a_list_or_tuple(self): + class SongAdmin(admin.ModelAdmin): + list_editable = 'test' + + self.assertEqual(SongAdmin(Song, AdminSite()).check(), [ + checks.Error( + "The value of 'list_editable' must be a list or tuple.", + obj=SongAdmin, + id='admin.E120', + ) + ]) + + def test_list_editable_missing_field(self): + class SongAdmin(admin.ModelAdmin): + list_editable = ('test',) + + self.assertEqual(SongAdmin(Song, AdminSite()).check(), [ + checks.Error( + "The value of 'list_editable[0]' refers to 'test', which is " + "not an attribute of 'admin_checks.Song'.", + obj=SongAdmin, + id='admin.E121', + ) + ]) + def test_readonly_and_editable(self): class SongAdmin(admin.ModelAdmin): readonly_fields = ["original_release"] @@ -327,7 +486,7 @@ class SongAdmin(admin.ModelAdmin): def test_generic_inline_model_admin_non_generic_model(self): """ A model without a GenericForeignKey raises problems if it's included - in an GenericInlineModelAdmin definition. + in a GenericInlineModelAdmin definition. """ class BookInline(GenericStackedInline): model = Book @@ -346,7 +505,10 @@ class SongAdmin(admin.ModelAdmin): self.assertEqual(errors, expected) def test_generic_inline_model_admin_bad_ct_field(self): - "A GenericInlineModelAdmin raises problems if the ct_field points to a non-existent field." + """ + A GenericInlineModelAdmin errors if the ct_field points to a + nonexistent field. + """ class InfluenceInline(GenericStackedInline): model = Influence ct_field = 'nonexistent' @@ -365,7 +527,10 @@ class SongAdmin(admin.ModelAdmin): self.assertEqual(errors, expected) def test_generic_inline_model_admin_bad_fk_field(self): - "A GenericInlineModelAdmin raises problems if the ct_fk_field points to a non-existent field." + """ + A GenericInlineModelAdmin errors if the ct_fk_field points to a + nonexistent field. + """ class InfluenceInline(GenericStackedInline): model = Influence ct_fk_field = 'nonexistent' @@ -430,18 +595,15 @@ class SongAdmin(admin.ModelAdmin): self.assertEqual(errors, expected) def test_app_label_in_admin_checks(self): - """ - Regression test for #15669 - Include app label in admin system check messages - """ - class RawIdNonexistingAdmin(admin.ModelAdmin): - raw_id_fields = ('nonexisting',) + class RawIdNonexistentAdmin(admin.ModelAdmin): + raw_id_fields = ('nonexistent',) - errors = RawIdNonexistingAdmin(Album, AdminSite()).check() + errors = RawIdNonexistentAdmin(Album, AdminSite()).check() expected = [ checks.Error( - "The value of 'raw_id_fields[0]' refers to 'nonexisting', " + "The value of 'raw_id_fields[0]' refers to 'nonexistent', " "which is not an attribute of 'admin_checks.Album'.", - obj=RawIdNonexistingAdmin, + obj=RawIdNonexistentAdmin, id='admin.E002', ) ] @@ -571,6 +733,18 @@ class CityInline(admin.TabularInline): ] self.assertEqual(errors, expected) + def test_readonly_fields_not_list_or_tuple(self): + class SongAdmin(admin.ModelAdmin): + readonly_fields = 'test' + + self.assertEqual(SongAdmin(Song, AdminSite()).check(), [ + checks.Error( + "The value of 'readonly_fields' must be a list or tuple.", + obj=SongAdmin, + id='admin.E034', + ) + ]) + def test_extra(self): class SongAdmin(admin.ModelAdmin): def awesome_song(self, instance): diff --git a/tests/admin_custom_urls/models.py b/tests/admin_custom_urls/models.py index 5365ee34bdad..8b91383b0f39 100644 --- a/tests/admin_custom_urls/models.py +++ b/tests/admin_custom_urls/models.py @@ -4,10 +4,8 @@ from django.db import models from django.http import HttpResponseRedirect from django.urls import reverse -from django.utils.encoding import python_2_unicode_compatible -@python_2_unicode_compatible class Action(models.Model): name = models.CharField(max_length=50, primary_key=True) description = models.CharField(max_length=70) @@ -30,12 +28,12 @@ def remove_url(self, name): Remove all entries named 'name' from the ModelAdmin instance URL patterns list """ - return [url for url in super(ActionAdmin, self).get_urls() if url.name != name] + return [url for url in super().get_urls() if url.name != name] def get_urls(self): # Add the URL of our custom 'add_view' view to the front of the URLs # list. Remove the existing one(s) first - from django.conf.urls import url + from django.urls import re_path def wrap(view): def wrapper(*args, **kwargs): @@ -47,7 +45,7 @@ def wrapper(*args, **kwargs): view_name = '%s_%s_add' % info return [ - url(r'^!add/$', wrap(self.add_view), name=view_name), + re_path('^!add/$', wrap(self.add_view), name=view_name), ] + self.remove_url(view_name) @@ -73,8 +71,10 @@ class Car(models.Model): class CarAdmin(admin.ModelAdmin): def response_add(self, request, obj, post_url_continue=None): - return super(CarAdmin, self).response_add( - request, obj, post_url_continue=reverse('admin:admin_custom_urls_car_history', args=[obj.pk])) + return super().response_add( + request, obj, + post_url_continue=reverse('admin:admin_custom_urls_car_history', args=[obj.pk]), + ) site = admin.AdminSite(name='admin_custom_urls') diff --git a/tests/admin_custom_urls/tests.py b/tests/admin_custom_urls/tests.py index 319e6f66534c..e0c2d4f74662 100644 --- a/tests/admin_custom_urls/tests.py +++ b/tests/admin_custom_urls/tests.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.contrib.admin.utils import quote from django.contrib.auth.models import User from django.template.response import TemplateResponse diff --git a/tests/admin_custom_urls/urls.py b/tests/admin_custom_urls/urls.py index b07e1395b992..ade49b3957e0 100644 --- a/tests/admin_custom_urls/urls.py +++ b/tests/admin_custom_urls/urls.py @@ -1,7 +1,7 @@ -from django.conf.urls import url +from django.urls import path from .models import site urlpatterns = [ - url(r'^admin/', site.urls), + path('admin/', site.urls), ] diff --git a/django/contrib/auth/management/commands/__init__.py b/tests/admin_default_site/__init__.py similarity index 100% rename from django/contrib/auth/management/commands/__init__.py rename to tests/admin_default_site/__init__.py diff --git a/tests/admin_default_site/apps.py b/tests/admin_default_site/apps.py new file mode 100644 index 000000000000..92743c18d4fe --- /dev/null +++ b/tests/admin_default_site/apps.py @@ -0,0 +1,6 @@ +from django.contrib.admin.apps import SimpleAdminConfig + + +class MyCustomAdminConfig(SimpleAdminConfig): + verbose_name = 'My custom default admin site.' + default_site = 'admin_default_site.sites.CustomAdminSite' diff --git a/tests/admin_default_site/sites.py b/tests/admin_default_site/sites.py new file mode 100644 index 000000000000..f2c33bd790eb --- /dev/null +++ b/tests/admin_default_site/sites.py @@ -0,0 +1,5 @@ +from django.contrib import admin + + +class CustomAdminSite(admin.AdminSite): + pass diff --git a/tests/admin_default_site/tests.py b/tests/admin_default_site/tests.py new file mode 100644 index 000000000000..5d05ec9c4515 --- /dev/null +++ b/tests/admin_default_site/tests.py @@ -0,0 +1,31 @@ +from django.contrib import admin +from django.contrib.admin import sites +from django.test import SimpleTestCase, override_settings + + +@override_settings(INSTALLED_APPS=[ + 'admin_default_site.apps.MyCustomAdminConfig', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +]) +class CustomAdminSiteTests(SimpleTestCase): + + def setUp(self): + # Reset admin.site since it may have already been instantiated by + # another test app. + self._old_site = admin.site + admin.site = sites.site = sites.DefaultAdminSite() + + def tearDown(self): + admin.site = sites.site = self._old_site + + def test_use_custom_admin_site(self): + self.assertEqual(admin.site.__class__.__name__, 'CustomAdminSite') + + +class DefaultAdminSiteTests(SimpleTestCase): + def test_use_default_admin_site(self): + self.assertEqual(admin.site.__class__.__name__, 'AdminSite') diff --git a/tests/admin_docs/models.py b/tests/admin_docs/models.py index a425ae0fcd24..02bf1efa9fc2 100644 --- a/tests/admin_docs/models.py +++ b/tests/admin_docs/models.py @@ -52,6 +52,10 @@ def rename_company(self, new_name): def dummy_function(self, baz, rox, *some_args, **some_kwargs): return some_kwargs + @property + def a_property(self): + return 'a_property' + def suffix_company_name(self, suffix='ltd'): return self.company.name + suffix diff --git a/tests/admin_docs/namespace_urls.py b/tests/admin_docs/namespace_urls.py index d05922c33e0f..719bf0ddf523 100644 --- a/tests/admin_docs/namespace_urls.py +++ b/tests/admin_docs/namespace_urls.py @@ -1,14 +1,14 @@ -from django.conf.urls import include, url from django.contrib import admin +from django.urls import include, path from . import views backend_urls = ([ - url(r'^something/$', views.XViewClass.as_view(), name='something'), + path('something/', views.XViewClass.as_view(), name='something'), ], 'backend') urlpatterns = [ - url(r'^admin/doc/', include('django.contrib.admindocs.urls')), - url(r'^admin/', admin.site.urls), - url(r'^api/backend/', include(backend_urls, namespace='backend')), + path('admin/doc/', include('django.contrib.admindocs.urls')), + path('admin/', admin.site.urls), + path('api/backend/', include(backend_urls, namespace='backend')), ] diff --git a/tests/admin_docs/test_middleware.py b/tests/admin_docs/test_middleware.py index 426c78d58fb0..ab53716481f8 100644 --- a/tests/admin_docs/test_middleware.py +++ b/tests/admin_docs/test_middleware.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.contrib.auth.models import User from .tests import AdminDocsTestCase, TestDataMixin @@ -42,3 +40,8 @@ def test_xview_class(self): user.save() response = self.client.head('/xview/class/') self.assertNotIn('X-View', response) + + def test_callable_object_view(self): + self.client.force_login(self.superuser) + response = self.client.head('/xview/callable_object/') + self.assertEqual(response['X-View'], 'admin_docs.views.XViewCallableObject') diff --git a/tests/admin_docs/test_utils.py b/tests/admin_docs/test_utils.py index 013d00391453..17ea91201514 100644 --- a/tests/admin_docs/test_utils.py +++ b/tests/admin_docs/test_utils.py @@ -1,16 +1,14 @@ -from __future__ import unicode_literals - import unittest from django.contrib.admindocs.utils import ( docutils_is_available, parse_docstring, parse_rst, trim_docstring, ) -from .tests import AdminDocsTestCase +from .tests import AdminDocsSimpleTestCase @unittest.skipUnless(docutils_is_available, "no docutils installed.") -class TestUtils(AdminDocsTestCase): +class TestUtils(AdminDocsSimpleTestCase): """ This __doc__ output is required for testing. I copied this example from `admindocs` documentation. (TITLE) diff --git a/tests/admin_docs/test_views.py b/tests/admin_docs/test_views.py index b48147fc852e..bcadff7d8a62 100644 --- a/tests/admin_docs/test_views.py +++ b/tests/admin_docs/test_views.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import sys import unittest @@ -12,7 +10,6 @@ from django.test import SimpleTestCase, modify_settings, override_settings from django.test.utils import captured_stderr from django.urls import reverse -from django.utils import six from .models import Company, Person from .tests import AdminDocsTestCase, TestDataMixin @@ -31,7 +28,7 @@ def test_index(self): self.client.logout() response = self.client.get(reverse('django-admindocs-docroot'), follow=True) # Should display the login screen - self.assertContains(response, '', html=True) + self.assertContains(response, '', html=True) def test_bookmarklets(self): response = self.client.get(reverse('django-admindocs-bookmarklets')) @@ -54,8 +51,13 @@ def test_view_index(self): ) self.assertContains(response, 'Views by namespace test') self.assertContains(response, 'Name: test:func.') + self.assertContains( + response, + '

      ' + '/xview/callable_object_without_xview/

      ', + html=True, + ) - @unittest.skipIf(six.PY2, "Python 2 doesn't support __qualname__.") def test_view_index_with_method(self): """ Views that are methods are listed correctly. @@ -91,7 +93,7 @@ def test_view_detail_as_method(self): """ url = reverse('django-admindocs-views-detail', args=['django.contrib.admin.sites.AdminSite.index']) response = self.client.get(url) - self.assertEqual(response.status_code, 200 if six.PY3 else 404) + self.assertEqual(response.status_code, 200) def test_model_index(self): response = self.client.get(reverse('django-admindocs-models-index')) @@ -206,6 +208,10 @@ def test_methods_with_multiple_arguments_display_arguments(self): """ self.assertContains(self.response, "baz, rox, *some_args, **some_kwargs") + def test_instance_of_property_methods_are_displayed(self): + """Model properties are displayed as fields.""" + self.assertContains(self.response, 'a_property') + def test_method_data_types(self): company = Company.objects.create(name="Django") person = Person.objects.create(first_name="Human", last_name="User", company=company) @@ -297,6 +303,16 @@ def test_model_docstring_renders_correctly(self): def test_model_detail_title(self): self.assertContains(self.response, '

      admin_docs.Person

      ', html=True) + def test_app_not_found(self): + response = self.client.get(reverse('django-admindocs-models-detail', args=['doesnotexist', 'Person'])) + self.assertEqual(response.context['exception'], "App 'doesnotexist' not found") + self.assertEqual(response.status_code, 404) + + def test_model_not_found(self): + response = self.client.get(reverse('django-admindocs-models-detail', args=['admin_docs', 'doesnotexist'])) + self.assertEqual(response.context['exception'], "Model 'doesnotexist' not found in app 'admin_docs'") + self.assertEqual(response.status_code, 404) + class CustomField(models.Field): description = "A custom field type" @@ -307,9 +323,6 @@ class DescriptionLackingField(models.Field): class TestFieldType(unittest.TestCase): - def setUp(self): - pass - def test_field_name(self): with self.assertRaises(AttributeError): views.get_readable_field_data_type("NotAField") @@ -340,6 +353,8 @@ def test_simplify_regex(self): (r'^(?P(x|y))/b/(?P\w+)$', '//b/'), (r'^(?P(x|y))/b/(?P\w+)ab', '//b/ab'), (r'^(?P(x|y)(\(|\)))/b/(?P\w+)ab', '//b/ab'), + (r'^a/?$', '/a/'), ) for pattern, output in tests: - self.assertEqual(simplify_regex(pattern), output) + with self.subTest(pattern=pattern): + self.assertEqual(simplify_regex(pattern), output) diff --git a/tests/admin_docs/tests.py b/tests/admin_docs/tests.py index c376a4cf5ec3..d53cb80c949f 100644 --- a/tests/admin_docs/tests.py +++ b/tests/admin_docs/tests.py @@ -1,16 +1,22 @@ -from __future__ import unicode_literals - from django.contrib.auth.models import User -from django.test import TestCase, modify_settings, override_settings +from django.test import ( + SimpleTestCase, TestCase, modify_settings, override_settings, +) -class TestDataMixin(object): +class TestDataMixin: @classmethod def setUpTestData(cls): cls.superuser = User.objects.create_superuser(username='super', password='secret', email='super@example.com') +@override_settings(ROOT_URLCONF='admin_docs.urls') +@modify_settings(INSTALLED_APPS={'append': 'django.contrib.admindocs'}) +class AdminDocsSimpleTestCase(SimpleTestCase): + pass + + @override_settings(ROOT_URLCONF='admin_docs.urls') @modify_settings(INSTALLED_APPS={'append': 'django.contrib.admindocs'}) class AdminDocsTestCase(TestCase): diff --git a/tests/admin_docs/urls.py b/tests/admin_docs/urls.py index 0bcdee4b4bd7..f535afc9f2b4 100644 --- a/tests/admin_docs/urls.py +++ b/tests/admin_docs/urls.py @@ -1,16 +1,18 @@ -from django.conf.urls import include, url from django.contrib import admin +from django.urls import include, path from . import views ns_patterns = ([ - url(r'^xview/func/$', views.xview_dec(views.xview), name='func'), + path('xview/func/', views.xview_dec(views.xview), name='func'), ], 'test') urlpatterns = [ - url(r'^admin/', admin.site.urls), - url(r'^admindocs/', include('django.contrib.admindocs.urls')), - url(r'^', include(ns_patterns, namespace='test')), - url(r'^xview/func/$', views.xview_dec(views.xview)), - url(r'^xview/class/$', views.xview_dec(views.XViewClass.as_view())), + path('admin/', admin.site.urls), + path('admindocs/', include('django.contrib.admindocs.urls')), + path('', include(ns_patterns, namespace='test')), + path('xview/func/', views.xview_dec(views.xview)), + path('xview/class/', views.xview_dec(views.XViewClass.as_view())), + path('xview/callable_object/', views.xview_dec(views.XViewCallableObject())), + path('xview/callable_object_without_xview/', views.XViewCallableObject()), ] diff --git a/tests/admin_docs/views.py b/tests/admin_docs/views.py index 31d253f7e285..21fe382bba7b 100644 --- a/tests/admin_docs/views.py +++ b/tests/admin_docs/views.py @@ -13,3 +13,8 @@ def xview(request): class XViewClass(View): def get(self, request): return HttpResponse() + + +class XViewCallableObject(View): + def __call__(self, request): + return HttpResponse() diff --git a/tests/admin_filters/models.py b/tests/admin_filters/models.py index 8860201d35a5..ae78282d3457 100644 --- a/tests/admin_filters/models.py +++ b/tests/admin_filters/models.py @@ -1,15 +1,11 @@ -from __future__ import unicode_literals - from django.contrib.auth.models import User from django.contrib.contenttypes.fields import ( GenericForeignKey, GenericRelation, ) from django.contrib.contenttypes.models import ContentType from django.db import models -from django.utils.encoding import python_2_unicode_compatible -@python_2_unicode_compatible class Book(models.Model): title = models.CharField(max_length=50) year = models.PositiveIntegerField(null=True, blank=True) @@ -32,7 +28,8 @@ class Book(models.Model): verbose_name='Employee', blank=True, null=True, ) - is_best_seller = models.NullBooleanField(default=0) + is_best_seller = models.BooleanField(default=0, null=True) + is_best_seller2 = models.NullBooleanField(default=0) date_registered = models.DateField(null=True) # This field name is intentionally 2 characters long (#16080). no = models.IntegerField(verbose_name='number', blank=True, null=True) @@ -41,7 +38,6 @@ def __str__(self): return self.title -@python_2_unicode_compatible class Department(models.Model): code = models.CharField(max_length=4, unique=True) description = models.CharField(max_length=50, blank=True, null=True) @@ -50,7 +46,6 @@ def __str__(self): return self.description -@python_2_unicode_compatible class Employee(models.Model): department = models.ForeignKey(Department, models.CASCADE, to_field="code") name = models.CharField(max_length=100) @@ -59,7 +54,6 @@ def __str__(self): return self.name -@python_2_unicode_compatible class TaggedItem(models.Model): tag = models.SlugField() content_type = models.ForeignKey(ContentType, models.CASCADE, related_name='tagged_items') @@ -70,7 +64,6 @@ def __str__(self): return self.tag -@python_2_unicode_compatible class Bookmark(models.Model): url = models.URLField() tags = GenericRelation(TaggedItem) diff --git a/tests/admin_filters/tests.py b/tests/admin_filters/tests.py index e2da7ec59e59..75563bbaaf79 100644 --- a/tests/admin_filters/tests.py +++ b/tests/admin_filters/tests.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import datetime import sys import unittest @@ -8,12 +6,11 @@ AllValuesFieldListFilter, BooleanFieldListFilter, ModelAdmin, RelatedOnlyFieldListFilter, SimpleListFilter, site, ) -from django.contrib.admin.views.main import ChangeList +from django.contrib.admin.options import IncorrectLookupParameters from django.contrib.auth.admin import UserAdmin from django.contrib.auth.models import User from django.core.exceptions import ImproperlyConfigured from django.test import RequestFactory, TestCase, override_settings -from django.utils.encoding import force_text from .models import Book, Bookmark, Department, Employee, TaggedItem @@ -145,6 +142,10 @@ class BookAdmin(ModelAdmin): ordering = ('-id',) +class BookAdmin2(ModelAdmin): + list_filter = ('year', 'author', 'contributors', 'is_best_seller2', 'date_registered', 'no') + + class BookAdminWithTupleBooleanFilter(BookAdmin): list_filter = ( 'year', @@ -171,12 +172,12 @@ class BookAdminWithCustomQueryset(ModelAdmin): def __init__(self, user, *args, **kwargs): self.user = user - super(BookAdminWithCustomQueryset, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) list_filter = ('year',) def get_queryset(self, request): - return super(BookAdminWithCustomQueryset, self).get_queryset(request).filter(author=self.user) + return super().get_queryset(request).filter(author=self.user) class BookAdminRelatedOnlyFilter(ModelAdmin): @@ -232,15 +233,15 @@ class EmployeeAdmin(ModelAdmin): class DepartmentFilterEmployeeAdmin(EmployeeAdmin): - list_filter = [DepartmentListFilterLookupWithNonStringValue, ] + list_filter = [DepartmentListFilterLookupWithNonStringValue] class DepartmentFilterUnderscoredEmployeeAdmin(EmployeeAdmin): - list_filter = [DepartmentListFilterLookupWithUnderscoredParameter, ] + list_filter = [DepartmentListFilterLookupWithUnderscoredParameter] class DepartmentFilterDynamicValueBookAdmin(EmployeeAdmin): - list_filter = [DepartmentListFilterLookupWithDynamicValue, ] + list_filter = [DepartmentListFilterLookupWithDynamicValue] class BookmarkAdminGenericRelation(ModelAdmin): @@ -248,59 +249,54 @@ class BookmarkAdminGenericRelation(ModelAdmin): class ListFiltersTests(TestCase): - - def setUp(self): - self.today = datetime.date.today() - self.tomorrow = self.today + datetime.timedelta(days=1) - self.one_week_ago = self.today - datetime.timedelta(days=7) - if self.today.month == 12: - self.next_month = self.today.replace(year=self.today.year + 1, month=1, day=1) + request_factory = RequestFactory() + + @classmethod + def setUpTestData(cls): + cls.today = datetime.date.today() + cls.tomorrow = cls.today + datetime.timedelta(days=1) + cls.one_week_ago = cls.today - datetime.timedelta(days=7) + if cls.today.month == 12: + cls.next_month = cls.today.replace(year=cls.today.year + 1, month=1, day=1) else: - self.next_month = self.today.replace(month=self.today.month + 1, day=1) - self.next_year = self.today.replace(year=self.today.year + 1, month=1, day=1) - - self.request_factory = RequestFactory() + cls.next_month = cls.today.replace(month=cls.today.month + 1, day=1) + cls.next_year = cls.today.replace(year=cls.today.year + 1, month=1, day=1) # Users - self.alfred = User.objects.create_user('alfred', 'alfred@example.com') - self.bob = User.objects.create_user('bob', 'bob@example.com') - self.lisa = User.objects.create_user('lisa', 'lisa@example.com') + cls.alfred = User.objects.create_superuser('alfred', 'alfred@example.com', 'password') + cls.bob = User.objects.create_user('bob', 'bob@example.com') + cls.lisa = User.objects.create_user('lisa', 'lisa@example.com') # Books - self.djangonaut_book = Book.objects.create( + cls.djangonaut_book = Book.objects.create( title='Djangonaut: an art of living', year=2009, - author=self.alfred, is_best_seller=True, date_registered=self.today, + author=cls.alfred, is_best_seller=True, date_registered=cls.today, + is_best_seller2=True, ) - self.bio_book = Book.objects.create( - title='Django: a biography', year=1999, author=self.alfred, + cls.bio_book = Book.objects.create( + title='Django: a biography', year=1999, author=cls.alfred, is_best_seller=False, no=207, + is_best_seller2=False, ) - self.django_book = Book.objects.create( - title='The Django Book', year=None, author=self.bob, - is_best_seller=None, date_registered=self.today, no=103, + cls.django_book = Book.objects.create( + title='The Django Book', year=None, author=cls.bob, + is_best_seller=None, date_registered=cls.today, no=103, + is_best_seller2=None, ) - self.guitar_book = Book.objects.create( + cls.guitar_book = Book.objects.create( title='Guitar for dummies', year=2002, is_best_seller=True, - date_registered=self.one_week_ago, + date_registered=cls.one_week_ago, + is_best_seller2=True, ) - self.guitar_book.contributors.set([self.bob, self.lisa]) + cls.guitar_book.contributors.set([cls.bob, cls.lisa]) # Departments - self.dev = Department.objects.create(code='DEV', description='Development') - self.design = Department.objects.create(code='DSN', description='Design') + cls.dev = Department.objects.create(code='DEV', description='Development') + cls.design = Department.objects.create(code='DSN', description='Design') # Employees - self.john = Employee.objects.create(name='John Blue', department=self.dev) - self.jack = Employee.objects.create(name='Jack Red', department=self.design) - - def get_changelist(self, request, model, modeladmin): - return ChangeList( - request, model, modeladmin.list_display, - modeladmin.list_display_links, modeladmin.list_filter, - modeladmin.date_hierarchy, modeladmin.search_fields, - modeladmin.list_select_related, modeladmin.list_per_page, - modeladmin.list_max_show_all, modeladmin.list_editable, modeladmin, - ) + cls.john = Employee.objects.create(name='John Blue', department=cls.dev) + cls.jack = Employee.objects.create(name='Jack Red', department=cls.design) def test_choicesfieldlistfilter_has_none_choice(self): """ @@ -312,7 +308,8 @@ class BookmarkChoicesAdmin(ModelAdmin): modeladmin = BookmarkChoicesAdmin(Bookmark, site) request = self.request_factory.get('/', {}) - changelist = self.get_changelist(request, Bookmark, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) filterspec = changelist.get_filters(request)[0][0] choices = list(filterspec.choices(changelist)) self.assertEqual(choices[-1]['display'], 'None') @@ -322,11 +319,15 @@ def test_datefieldlistfilter(self): modeladmin = BookAdmin(Book, site) request = self.request_factory.get('/') - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist(request) - request = self.request_factory.get('/', {'date_registered__gte': self.today, - 'date_registered__lt': self.tomorrow}) - changelist = self.get_changelist(request, Book, modeladmin) + request = self.request_factory.get('/', { + 'date_registered__gte': self.today, + 'date_registered__lt': self.tomorrow}, + ) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) @@ -334,7 +335,7 @@ def test_datefieldlistfilter(self): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][4] - self.assertEqual(force_text(filterspec.title), 'date registered') + self.assertEqual(filterspec.title, 'date registered') choice = select_by(filterspec.choices(changelist), "display", "Today") self.assertIs(choice['selected'], True) self.assertEqual( @@ -345,9 +346,12 @@ def test_datefieldlistfilter(self): ) ) - request = self.request_factory.get('/', {'date_registered__gte': self.today.replace(day=1), - 'date_registered__lt': self.next_month}) - changelist = self.get_changelist(request, Book, modeladmin) + request = self.request_factory.get('/', { + 'date_registered__gte': self.today.replace(day=1), + 'date_registered__lt': self.next_month}, + ) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) @@ -359,7 +363,7 @@ def test_datefieldlistfilter(self): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][4] - self.assertEqual(force_text(filterspec.title), 'date registered') + self.assertEqual(filterspec.title, 'date registered') choice = select_by(filterspec.choices(changelist), "display", "This month") self.assertIs(choice['selected'], True) self.assertEqual( @@ -370,9 +374,12 @@ def test_datefieldlistfilter(self): ) ) - request = self.request_factory.get('/', {'date_registered__gte': self.today.replace(month=1, day=1), - 'date_registered__lt': self.next_year}) - changelist = self.get_changelist(request, Book, modeladmin) + request = self.request_factory.get('/', { + 'date_registered__gte': self.today.replace(month=1, day=1), + 'date_registered__lt': self.next_year}, + ) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) @@ -384,7 +391,7 @@ def test_datefieldlistfilter(self): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][4] - self.assertEqual(force_text(filterspec.title), 'date registered') + self.assertEqual(filterspec.title, 'date registered') choice = select_by(filterspec.choices(changelist), "display", "This year") self.assertIs(choice['selected'], True) self.assertEqual( @@ -399,7 +406,8 @@ def test_datefieldlistfilter(self): 'date_registered__gte': str(self.one_week_ago), 'date_registered__lt': str(self.tomorrow), }) - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) @@ -407,7 +415,7 @@ def test_datefieldlistfilter(self): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][4] - self.assertEqual(force_text(filterspec.title), 'date registered') + self.assertEqual(filterspec.title, 'date registered') choice = select_by(filterspec.choices(changelist), "display", "Past 7 days") self.assertIs(choice['selected'], True) self.assertEqual( @@ -420,7 +428,8 @@ def test_datefieldlistfilter(self): # Null/not null queries request = self.request_factory.get('/', {'date_registered__isnull': 'True'}) - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) @@ -429,13 +438,14 @@ def test_datefieldlistfilter(self): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][4] - self.assertEqual(force_text(filterspec.title), 'date registered') + self.assertEqual(filterspec.title, 'date registered') choice = select_by(filterspec.choices(changelist), 'display', 'No date') self.assertIs(choice['selected'], True) self.assertEqual(choice['query_string'], '?date_registered__isnull=True') request = self.request_factory.get('/', {'date_registered__isnull': 'False'}) - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) @@ -444,7 +454,7 @@ def test_datefieldlistfilter(self): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][4] - self.assertEqual(force_text(filterspec.title), 'date registered') + self.assertEqual(filterspec.title, 'date registered') choice = select_by(filterspec.choices(changelist), 'display', 'Has date') self.assertIs(choice['selected'], True) self.assertEqual(choice['query_string'], '?date_registered__isnull=False') @@ -463,7 +473,8 @@ def test_allvaluesfieldlistfilter(self): modeladmin = BookAdmin(Book, site) request = self.request_factory.get('/', {'year__isnull': 'True'}) - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) @@ -471,17 +482,18 @@ def test_allvaluesfieldlistfilter(self): # Make sure the last choice is None and is selected filterspec = changelist.get_filters(request)[0][0] - self.assertEqual(force_text(filterspec.title), 'year') + self.assertEqual(filterspec.title, 'year') choices = list(filterspec.choices(changelist)) self.assertIs(choices[-1]['selected'], True) self.assertEqual(choices[-1]['query_string'], '?year__isnull=True') request = self.request_factory.get('/', {'year': '2002'}) - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][0] - self.assertEqual(force_text(filterspec.title), 'year') + self.assertEqual(filterspec.title, 'year') choices = list(filterspec.choices(changelist)) self.assertIs(choices[2]['selected'], True) self.assertEqual(choices[2]['query_string'], '?year=2002') @@ -490,7 +502,8 @@ def test_allvaluesfieldlistfilter_custom_qs(self): # Make sure that correct filters are returned with custom querysets modeladmin = BookAdminWithCustomQueryset(self.alfred, Book, site) request = self.request_factory.get('/') - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) filterspec = changelist.get_filters(request)[0][0] choices = list(filterspec.choices(changelist)) @@ -506,7 +519,8 @@ def test_relatedfieldlistfilter_foreignkey(self): modeladmin = BookAdmin(Book, site) request = self.request_factory.get('/') - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure that all users are present in the author's list filter filterspec = changelist.get_filters(request)[0][1] @@ -514,7 +528,8 @@ def test_relatedfieldlistfilter_foreignkey(self): self.assertEqual(sorted(filterspec.lookup_choices), sorted(expected)) request = self.request_factory.get('/', {'author__isnull': 'True'}) - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) @@ -522,27 +537,82 @@ def test_relatedfieldlistfilter_foreignkey(self): # Make sure the last choice is None and is selected filterspec = changelist.get_filters(request)[0][1] - self.assertEqual(force_text(filterspec.title), 'Verbose Author') + self.assertEqual(filterspec.title, 'Verbose Author') choices = list(filterspec.choices(changelist)) self.assertIs(choices[-1]['selected'], True) self.assertEqual(choices[-1]['query_string'], '?author__isnull=True') request = self.request_factory.get('/', {'author__id__exact': self.alfred.pk}) - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][1] - self.assertEqual(force_text(filterspec.title), 'Verbose Author') + self.assertEqual(filterspec.title, 'Verbose Author') # order of choices depends on User model, which has no order choice = select_by(filterspec.choices(changelist), "display", "alfred") self.assertIs(choice['selected'], True) self.assertEqual(choice['query_string'], '?author__id__exact=%d' % self.alfred.pk) + def test_relatedfieldlistfilter_foreignkey_ordering(self): + """RelatedFieldListFilter ordering respects ModelAdmin.ordering.""" + class EmployeeAdminWithOrdering(ModelAdmin): + ordering = ('name',) + + class BookAdmin(ModelAdmin): + list_filter = ('employee',) + + site.register(Employee, EmployeeAdminWithOrdering) + self.addCleanup(lambda: site.unregister(Employee)) + modeladmin = BookAdmin(Book, site) + + request = self.request_factory.get('/') + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) + filterspec = changelist.get_filters(request)[0][0] + expected = [(self.jack.pk, 'Jack Red'), (self.john.pk, 'John Blue')] + self.assertEqual(filterspec.lookup_choices, expected) + + def test_relatedfieldlistfilter_foreignkey_ordering_reverse(self): + class EmployeeAdminWithOrdering(ModelAdmin): + ordering = ('-name',) + + class BookAdmin(ModelAdmin): + list_filter = ('employee',) + + site.register(Employee, EmployeeAdminWithOrdering) + self.addCleanup(lambda: site.unregister(Employee)) + modeladmin = BookAdmin(Book, site) + + request = self.request_factory.get('/') + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) + filterspec = changelist.get_filters(request)[0][0] + expected = [(self.john.pk, 'John Blue'), (self.jack.pk, 'Jack Red')] + self.assertEqual(filterspec.lookup_choices, expected) + + def test_relatedfieldlistfilter_foreignkey_default_ordering(self): + """RelatedFieldListFilter ordering respects Model.ordering.""" + class BookAdmin(ModelAdmin): + list_filter = ('employee',) + + self.addCleanup(setattr, Employee._meta, 'ordering', Employee._meta.ordering) + Employee._meta.ordering = ('name',) + modeladmin = BookAdmin(Book, site) + + request = self.request_factory.get('/') + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) + filterspec = changelist.get_filters(request)[0][0] + expected = [(self.jack.pk, 'Jack Red'), (self.john.pk, 'John Blue')] + self.assertEqual(filterspec.lookup_choices, expected) + def test_relatedfieldlistfilter_manytomany(self): modeladmin = BookAdmin(Book, site) request = self.request_factory.get('/') - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure that all users are present in the contrib's list filter filterspec = changelist.get_filters(request)[0][2] @@ -550,7 +620,8 @@ def test_relatedfieldlistfilter_manytomany(self): self.assertEqual(sorted(filterspec.lookup_choices), sorted(expected)) request = self.request_factory.get('/', {'contributors__isnull': 'True'}) - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) @@ -558,17 +629,18 @@ def test_relatedfieldlistfilter_manytomany(self): # Make sure the last choice is None and is selected filterspec = changelist.get_filters(request)[0][2] - self.assertEqual(force_text(filterspec.title), 'Verbose Contributors') + self.assertEqual(filterspec.title, 'Verbose Contributors') choices = list(filterspec.choices(changelist)) self.assertIs(choices[-1]['selected'], True) self.assertEqual(choices[-1]['query_string'], '?contributors__isnull=True') request = self.request_factory.get('/', {'contributors__id__exact': self.bob.pk}) - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][2] - self.assertEqual(force_text(filterspec.title), 'Verbose Contributors') + self.assertEqual(filterspec.title, 'Verbose Contributors') choice = select_by(filterspec.choices(changelist), "display", "bob") self.assertIs(choice['selected'], True) self.assertEqual(choice['query_string'], '?contributors__id__exact=%d' % self.bob.pk) @@ -578,7 +650,8 @@ def test_relatedfieldlistfilter_reverse_relationships(self): # FK relationship ----- request = self.request_factory.get('/', {'books_authored__isnull': 'True'}) - changelist = self.get_changelist(request, User, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) @@ -586,24 +659,26 @@ def test_relatedfieldlistfilter_reverse_relationships(self): # Make sure the last choice is None and is selected filterspec = changelist.get_filters(request)[0][0] - self.assertEqual(force_text(filterspec.title), 'book') + self.assertEqual(filterspec.title, 'book') choices = list(filterspec.choices(changelist)) self.assertIs(choices[-1]['selected'], True) self.assertEqual(choices[-1]['query_string'], '?books_authored__isnull=True') request = self.request_factory.get('/', {'books_authored__id__exact': self.bio_book.pk}) - changelist = self.get_changelist(request, User, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][0] - self.assertEqual(force_text(filterspec.title), 'book') + self.assertEqual(filterspec.title, 'book') choice = select_by(filterspec.choices(changelist), "display", self.bio_book.title) self.assertIs(choice['selected'], True) self.assertEqual(choice['query_string'], '?books_authored__id__exact=%d' % self.bio_book.pk) # M2M relationship ----- request = self.request_factory.get('/', {'books_contributed__isnull': 'True'}) - changelist = self.get_changelist(request, User, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) @@ -611,17 +686,18 @@ def test_relatedfieldlistfilter_reverse_relationships(self): # Make sure the last choice is None and is selected filterspec = changelist.get_filters(request)[0][1] - self.assertEqual(force_text(filterspec.title), 'book') + self.assertEqual(filterspec.title, 'book') choices = list(filterspec.choices(changelist)) self.assertIs(choices[-1]['selected'], True) self.assertEqual(choices[-1]['query_string'], '?books_contributed__isnull=True') request = self.request_factory.get('/', {'books_contributed__id__exact': self.django_book.pk}) - changelist = self.get_changelist(request, User, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][1] - self.assertEqual(force_text(filterspec.title), 'book') + self.assertEqual(filterspec.title, 'book') choice = select_by(filterspec.choices(changelist), "display", self.django_book.title) self.assertIs(choice['selected'], True) self.assertEqual(choice['query_string'], '?books_contributed__id__exact=%d' % self.django_book.pk) @@ -636,17 +712,59 @@ def test_relatedfieldlistfilter_reverse_relationships(self): filterspec = changelist.get_filters(request)[0] self.assertEqual(len(filterspec), 0) + def test_relatedfieldlistfilter_reverse_relationships_default_ordering(self): + self.addCleanup(setattr, Book._meta, 'ordering', Book._meta.ordering) + Book._meta.ordering = ('title',) + modeladmin = CustomUserAdmin(User, site) + + request = self.request_factory.get('/') + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) + filterspec = changelist.get_filters(request)[0][0] + expected = [ + (self.bio_book.pk, 'Django: a biography'), + (self.djangonaut_book.pk, 'Djangonaut: an art of living'), + (self.guitar_book.pk, 'Guitar for dummies'), + (self.django_book.pk, 'The Django Book') + ] + self.assertEqual(filterspec.lookup_choices, expected) + def test_relatedonlyfieldlistfilter_foreignkey(self): modeladmin = BookAdminRelatedOnlyFilter(Book, site) request = self.request_factory.get('/') - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure that only actual authors are present in author's list filter filterspec = changelist.get_filters(request)[0][4] expected = [(self.alfred.pk, 'alfred'), (self.bob.pk, 'bob')] self.assertEqual(sorted(filterspec.lookup_choices), sorted(expected)) + def test_relatedonlyfieldlistfilter_foreignkey_default_ordering(self): + """RelatedOnlyFieldListFilter ordering respects Meta.ordering.""" + class BookAdmin(ModelAdmin): + list_filter = ( + ('employee', RelatedOnlyFieldListFilter), + ) + + albert = Employee.objects.create(name='Albert Green', department=self.dev) + self.djangonaut_book.employee = albert + self.djangonaut_book.save() + self.bio_book.employee = self.jack + self.bio_book.save() + + self.addCleanup(setattr, Employee._meta, 'ordering', Employee._meta.ordering) + Employee._meta.ordering = ('name',) + modeladmin = BookAdmin(Book, site) + + request = self.request_factory.get('/') + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) + filterspec = changelist.get_filters(request)[0][0] + expected = [(albert.pk, 'Albert Green'), (self.jack.pk, 'Jack Red')] + self.assertEqual(filterspec.lookup_choices, expected) + def test_relatedonlyfieldlistfilter_underscorelookup_foreignkey(self): Department.objects.create(code='TEST', description='Testing') self.djangonaut_book.employee = self.john @@ -656,7 +774,8 @@ def test_relatedonlyfieldlistfilter_underscorelookup_foreignkey(self): modeladmin = BookAdminRelatedOnlyFilter(Book, site) request = self.request_factory.get('/') - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Only actual departments should be present in employee__department's # list filter. @@ -671,7 +790,8 @@ def test_relatedonlyfieldlistfilter_manytomany(self): modeladmin = BookAdminRelatedOnlyFilter(Book, site) request = self.request_factory.get('/') - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure that only actual contributors are present in contrib's list filter filterspec = changelist.get_filters(request)[0][5] @@ -690,7 +810,8 @@ def test_listfilter_genericrelation(self): modeladmin = BookmarkAdminGenericRelation(Bookmark, site) request = self.request_factory.get('/', {'tags__tag': 'python'}) - changelist = self.get_changelist(request, Bookmark, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) queryset = changelist.get_queryset(request) expected = [python_bookmark, django_bookmark] @@ -706,10 +827,12 @@ def test_booleanfieldlistfilter_tuple(self): def verify_booleanfieldlistfilter(self, modeladmin): request = self.request_factory.get('/') - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) request = self.request_factory.get('/', {'is_best_seller__exact': 0}) - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) @@ -717,13 +840,14 @@ def verify_booleanfieldlistfilter(self, modeladmin): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][3] - self.assertEqual(force_text(filterspec.title), 'is best seller') + self.assertEqual(filterspec.title, 'is best seller') choice = select_by(filterspec.choices(changelist), "display", "No") self.assertIs(choice['selected'], True) self.assertEqual(choice['query_string'], '?is_best_seller__exact=0') request = self.request_factory.get('/', {'is_best_seller__exact': 1}) - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) @@ -731,13 +855,14 @@ def verify_booleanfieldlistfilter(self, modeladmin): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][3] - self.assertEqual(force_text(filterspec.title), 'is best seller') + self.assertEqual(filterspec.title, 'is best seller') choice = select_by(filterspec.choices(changelist), "display", "Yes") self.assertIs(choice['selected'], True) self.assertEqual(choice['query_string'], '?is_best_seller__exact=1') request = self.request_factory.get('/', {'is_best_seller__isnull': 'True'}) - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) @@ -745,11 +870,63 @@ def verify_booleanfieldlistfilter(self, modeladmin): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][3] - self.assertEqual(force_text(filterspec.title), 'is best seller') + self.assertEqual(filterspec.title, 'is best seller') choice = select_by(filterspec.choices(changelist), "display", "Unknown") self.assertIs(choice['selected'], True) self.assertEqual(choice['query_string'], '?is_best_seller__isnull=True') + def test_booleanfieldlistfilter_nullbooleanfield(self): + modeladmin = BookAdmin2(Book, site) + + request = self.request_factory.get('/') + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) + + request = self.request_factory.get('/', {'is_best_seller2__exact': 0}) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) + + # Make sure the correct queryset is returned + queryset = changelist.get_queryset(request) + self.assertEqual(list(queryset), [self.bio_book]) + + # Make sure the correct choice is selected + filterspec = changelist.get_filters(request)[0][3] + self.assertEqual(filterspec.title, 'is best seller2') + choice = select_by(filterspec.choices(changelist), "display", "No") + self.assertIs(choice['selected'], True) + self.assertEqual(choice['query_string'], '?is_best_seller2__exact=0') + + request = self.request_factory.get('/', {'is_best_seller2__exact': 1}) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) + + # Make sure the correct queryset is returned + queryset = changelist.get_queryset(request) + self.assertEqual(list(queryset), [self.guitar_book, self.djangonaut_book]) + + # Make sure the correct choice is selected + filterspec = changelist.get_filters(request)[0][3] + self.assertEqual(filterspec.title, 'is best seller2') + choice = select_by(filterspec.choices(changelist), "display", "Yes") + self.assertIs(choice['selected'], True) + self.assertEqual(choice['query_string'], '?is_best_seller2__exact=1') + + request = self.request_factory.get('/', {'is_best_seller2__isnull': 'True'}) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) + + # Make sure the correct queryset is returned + queryset = changelist.get_queryset(request) + self.assertEqual(list(queryset), [self.django_book]) + + # Make sure the correct choice is selected + filterspec = changelist.get_filters(request)[0][3] + self.assertEqual(filterspec.title, 'is best seller2') + choice = select_by(filterspec.choices(changelist), "display", "Unknown") + self.assertIs(choice['selected'], True) + self.assertEqual(choice['query_string'], '?is_best_seller2__isnull=True') + def test_fieldlistfilter_underscorelookup_tuple(self): """ Ensure ('fieldpath', ClassName ) lookups pass lookup_allowed checks @@ -757,21 +934,32 @@ def test_fieldlistfilter_underscorelookup_tuple(self): """ modeladmin = BookAdminWithUnderscoreLookupAndTuple(Book, site) request = self.request_factory.get('/') - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) request = self.request_factory.get('/', {'author__email': 'alfred@example.com'}) - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.bio_book, self.djangonaut_book]) + def test_fieldlistfilter_invalid_lookup_parameters(self): + """Filtering by an invalid value.""" + modeladmin = BookAdmin(Book, site) + request = self.request_factory.get('/', {'author__id__exact': 'StringNotInteger!'}) + request.user = self.alfred + with self.assertRaises(IncorrectLookupParameters): + modeladmin.get_changelist_instance(request) + def test_simplelistfilter(self): modeladmin = DecadeFilterBookAdmin(Book, site) # Make sure that the first option is 'All' --------------------------- request = self.request_factory.get('/', {}) - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) @@ -779,7 +967,7 @@ def test_simplelistfilter(self): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][1] - self.assertEqual(force_text(filterspec.title), 'publication decade') + self.assertEqual(filterspec.title, 'publication decade') choices = list(filterspec.choices(changelist)) self.assertEqual(choices[0]['display'], 'All') self.assertIs(choices[0]['selected'], True) @@ -787,7 +975,8 @@ def test_simplelistfilter(self): # Look for books in the 1980s ---------------------------------------- request = self.request_factory.get('/', {'publication-decade': 'the 80s'}) - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) @@ -795,7 +984,7 @@ def test_simplelistfilter(self): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][1] - self.assertEqual(force_text(filterspec.title), 'publication decade') + self.assertEqual(filterspec.title, 'publication decade') choices = list(filterspec.choices(changelist)) self.assertEqual(choices[1]['display'], 'the 1980\'s') self.assertIs(choices[1]['selected'], True) @@ -803,7 +992,8 @@ def test_simplelistfilter(self): # Look for books in the 1990s ---------------------------------------- request = self.request_factory.get('/', {'publication-decade': 'the 90s'}) - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) @@ -811,7 +1001,7 @@ def test_simplelistfilter(self): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][1] - self.assertEqual(force_text(filterspec.title), 'publication decade') + self.assertEqual(filterspec.title, 'publication decade') choices = list(filterspec.choices(changelist)) self.assertEqual(choices[2]['display'], 'the 1990\'s') self.assertIs(choices[2]['selected'], True) @@ -819,7 +1009,8 @@ def test_simplelistfilter(self): # Look for books in the 2000s ---------------------------------------- request = self.request_factory.get('/', {'publication-decade': 'the 00s'}) - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) @@ -827,7 +1018,7 @@ def test_simplelistfilter(self): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][1] - self.assertEqual(force_text(filterspec.title), 'publication decade') + self.assertEqual(filterspec.title, 'publication decade') choices = list(filterspec.choices(changelist)) self.assertEqual(choices[3]['display'], 'the 2000\'s') self.assertIs(choices[3]['selected'], True) @@ -835,7 +1026,8 @@ def test_simplelistfilter(self): # Combine multiple filters ------------------------------------------- request = self.request_factory.get('/', {'publication-decade': 'the 00s', 'author__id__exact': self.alfred.pk}) - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) @@ -843,7 +1035,7 @@ def test_simplelistfilter(self): # Make sure the correct choices are selected filterspec = changelist.get_filters(request)[0][1] - self.assertEqual(force_text(filterspec.title), 'publication decade') + self.assertEqual(filterspec.title, 'publication decade') choices = list(filterspec.choices(changelist)) self.assertEqual(choices[3]['display'], 'the 2000\'s') self.assertIs(choices[3]['selected'], True) @@ -853,7 +1045,7 @@ def test_simplelistfilter(self): ) filterspec = changelist.get_filters(request)[0][0] - self.assertEqual(force_text(filterspec.title), 'Verbose Author') + self.assertEqual(filterspec.title, 'Verbose Author') choice = select_by(filterspec.choices(changelist), "display", "alfred") self.assertIs(choice['selected'], True) self.assertEqual(choice['query_string'], '?author__id__exact=%s&publication-decade=the+00s' % self.alfred.pk) @@ -864,9 +1056,10 @@ def test_listfilter_without_title(self): """ modeladmin = DecadeFilterBookAdminWithoutTitle(Book, site) request = self.request_factory.get('/', {}) + request.user = self.alfred msg = "The list filter 'DecadeListFilterWithoutTitle' does not specify a 'title'." with self.assertRaisesMessage(ImproperlyConfigured, msg): - self.get_changelist(request, Book, modeladmin) + modeladmin.get_changelist_instance(request) def test_simplelistfilter_without_parameter(self): """ @@ -874,9 +1067,10 @@ def test_simplelistfilter_without_parameter(self): """ modeladmin = DecadeFilterBookAdminWithoutParameter(Book, site) request = self.request_factory.get('/', {}) + request.user = self.alfred msg = "The list filter 'DecadeListFilterWithoutParameter' does not specify a 'parameter_name'." with self.assertRaisesMessage(ImproperlyConfigured, msg): - self.get_changelist(request, Book, modeladmin) + modeladmin.get_changelist_instance(request) def test_simplelistfilter_with_none_returning_lookups(self): """ @@ -885,7 +1079,8 @@ def test_simplelistfilter_with_none_returning_lookups(self): """ modeladmin = DecadeFilterBookAdminWithNoneReturningLookups(Book, site) request = self.request_factory.get('/', {}) - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) filterspec = changelist.get_filters(request)[0] self.assertEqual(len(filterspec), 0) @@ -896,16 +1091,18 @@ def test_filter_with_failing_queryset(self): """ modeladmin = DecadeFilterBookAdminWithFailingQueryset(Book, site) request = self.request_factory.get('/', {}) + request.user = self.alfred with self.assertRaises(ZeroDivisionError): - self.get_changelist(request, Book, modeladmin) + modeladmin.get_changelist_instance(request) def test_simplelistfilter_with_queryset_based_lookups(self): modeladmin = DecadeFilterBookAdminWithQuerysetBasedLookups(Book, site) request = self.request_factory.get('/', {}) - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) filterspec = changelist.get_filters(request)[0][0] - self.assertEqual(force_text(filterspec.title), 'publication decade') + self.assertEqual(filterspec.title, 'publication decade') choices = list(filterspec.choices(changelist)) self.assertEqual(len(choices), 3) @@ -927,14 +1124,15 @@ def test_two_characters_long_field(self): """ modeladmin = BookAdmin(Book, site) request = self.request_factory.get('/', {'no': '207'}) - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.bio_book]) filterspec = changelist.get_filters(request)[0][-1] - self.assertEqual(force_text(filterspec.title), 'number') + self.assertEqual(filterspec.title, 'number') choices = list(filterspec.choices(changelist)) self.assertIs(choices[2]['selected'], True) self.assertEqual(choices[2]['query_string'], '?no=207') @@ -947,7 +1145,8 @@ def test_parameter_ends_with__in__or__isnull(self): # When it ends with '__in' ----------------------------------------- modeladmin = DecadeFilterBookAdminParameterEndsWith__In(Book, site) request = self.request_factory.get('/', {'decade__in': 'the 90s'}) - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) @@ -955,7 +1154,7 @@ def test_parameter_ends_with__in__or__isnull(self): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][0] - self.assertEqual(force_text(filterspec.title), 'publication decade') + self.assertEqual(filterspec.title, 'publication decade') choices = list(filterspec.choices(changelist)) self.assertEqual(choices[2]['display'], 'the 1990\'s') self.assertIs(choices[2]['selected'], True) @@ -964,7 +1163,8 @@ def test_parameter_ends_with__in__or__isnull(self): # When it ends with '__isnull' --------------------------------------- modeladmin = DecadeFilterBookAdminParameterEndsWith__Isnull(Book, site) request = self.request_factory.get('/', {'decade__isnull': 'the 90s'}) - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) @@ -972,7 +1172,7 @@ def test_parameter_ends_with__in__or__isnull(self): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][0] - self.assertEqual(force_text(filterspec.title), 'publication decade') + self.assertEqual(filterspec.title, 'publication decade') choices = list(filterspec.choices(changelist)) self.assertEqual(choices[2]['display'], 'the 1990\'s') self.assertIs(choices[2]['selected'], True) @@ -985,14 +1185,15 @@ def test_lookup_with_non_string_value(self): """ modeladmin = DepartmentFilterEmployeeAdmin(Employee, site) request = self.request_factory.get('/', {'department': self.john.department.pk}) - changelist = self.get_changelist(request, Employee, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.john]) filterspec = changelist.get_filters(request)[0][-1] - self.assertEqual(force_text(filterspec.title), 'department') + self.assertEqual(filterspec.title, 'department') choices = list(filterspec.choices(changelist)) self.assertEqual(choices[1]['display'], 'DEV') self.assertIs(choices[1]['selected'], True) @@ -1005,14 +1206,15 @@ def test_lookup_with_non_string_value_underscored(self): """ modeladmin = DepartmentFilterUnderscoredEmployeeAdmin(Employee, site) request = self.request_factory.get('/', {'department__whatever': self.john.department.pk}) - changelist = self.get_changelist(request, Employee, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.john]) filterspec = changelist.get_filters(request)[0][-1] - self.assertEqual(force_text(filterspec.title), 'department') + self.assertEqual(filterspec.title, 'department') choices = list(filterspec.choices(changelist)) self.assertEqual(choices[1]['display'], 'DEV') self.assertIs(choices[1]['selected'], True) @@ -1025,14 +1227,15 @@ def test_fk_with_to_field(self): modeladmin = EmployeeAdmin(Employee, site) request = self.request_factory.get('/', {}) - changelist = self.get_changelist(request, Employee, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.jack, self.john]) filterspec = changelist.get_filters(request)[0][-1] - self.assertEqual(force_text(filterspec.title), 'department') + self.assertEqual(filterspec.title, 'department') choices = list(filterspec.choices(changelist)) self.assertEqual(choices[0]['display'], 'All') @@ -1050,14 +1253,15 @@ def test_fk_with_to_field(self): # Filter by Department=='Development' -------------------------------- request = self.request_factory.get('/', {'department__code__exact': 'DEV'}) - changelist = self.get_changelist(request, Employee, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.john]) filterspec = changelist.get_filters(request)[0][-1] - self.assertEqual(force_text(filterspec.title), 'department') + self.assertEqual(filterspec.title, 'department') choices = list(filterspec.choices(changelist)) self.assertEqual(choices[0]['display'], 'All') @@ -1079,9 +1283,10 @@ def test_lookup_with_dynamic_value(self): modeladmin = DepartmentFilterDynamicValueBookAdmin(Book, site) def _test_choices(request, expected_displays): - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) filterspec = changelist.get_filters(request)[0][0] - self.assertEqual(force_text(filterspec.title), 'publication decade') + self.assertEqual(filterspec.title, 'publication decade') choices = tuple(c['display'] for c in filterspec.choices(changelist)) self.assertEqual(choices, expected_displays) @@ -1101,6 +1306,7 @@ def test_list_filter_queryset_filtered_by_default(self): """ modeladmin = NotNinetiesListFilterAdmin(Book, site) request = self.request_factory.get('/', {}) - changelist = self.get_changelist(request, Book, modeladmin) + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) changelist.get_results(request) self.assertEqual(changelist.full_result_count, 4) diff --git a/tests/admin_inlines/admin.py b/tests/admin_inlines/admin.py index c3bc8d769840..dd2624943ee6 100644 --- a/tests/admin_inlines/admin.py +++ b/tests/admin_inlines/admin.py @@ -1,13 +1,15 @@ from django import forms from django.contrib import admin +from django.db import models from .models import ( - Author, BinaryTree, CapoFamiglia, Chapter, ChildModel1, ChildModel2, + Author, BinaryTree, CapoFamiglia, Chapter, Child, ChildModel1, ChildModel2, Consigliere, EditablePKBook, ExtraTerrestrial, Fashionista, Holder, Holder2, Holder3, Holder4, Inner, Inner2, Inner3, Inner4Stacked, - Inner4Tabular, NonAutoPKBook, Novel, ParentModelWithCustomPk, Poll, - Profile, ProfileCollection, Question, ReadOnlyInline, ShoppingWeakness, - Sighting, SomeChildModel, SomeParentModel, SottoCapo, Title, + Inner4Tabular, NonAutoPKBook, NonAutoPKBookChild, Novel, + NovelReadonlyChapter, ParentModelWithCustomPk, Poll, Profile, + ProfileCollection, Question, ReadOnlyInline, ShoppingWeakness, Sighting, + SomeChildModel, SomeParentModel, SottoCapo, Teacher, Title, TitleCollection, ) @@ -23,6 +25,11 @@ class NonAutoPKBookTabularInline(admin.TabularInline): classes = ('collapse',) +class NonAutoPKBookChildTabularInline(admin.TabularInline): + model = NonAutoPKBookChild + classes = ('collapse',) + + class NonAutoPKBookStackedInline(admin.StackedInline): model = NonAutoPKBook classes = ('collapse',) @@ -40,6 +47,7 @@ class AuthorAdmin(admin.ModelAdmin): inlines = [ BookInline, NonAutoPKBookTabularInline, NonAutoPKBookStackedInline, EditablePKBookTabularInline, EditablePKBookStackedInline, + NonAutoPKBookChildTabularInline, ] @@ -67,8 +75,20 @@ class Media: js = ('my_awesome_inline_scripts.js',) +class InnerInline2Tabular(admin.TabularInline): + model = Inner2 + + +class CustomNumberWidget(forms.NumberInput): + class Media: + js = ('custom_number.js',) + + class InnerInline3(admin.StackedInline): model = Inner3 + formfield_overrides = { + models.IntegerField: {'widget': CustomNumberWidget}, + } class Media: js = ('my_awesome_inline_scripts.js',) @@ -138,6 +158,17 @@ class NovelAdmin(admin.ModelAdmin): inlines = [ChapterInline] +class ReadOnlyChapterInline(admin.TabularInline): + model = Chapter + + def has_change_permission(self, request, obj=None): + return False + + +class NovelReadonlyChapterAdmin(admin.ModelAdmin): + inlines = [ReadOnlyChapterInline] + + class ConsigliereInline(admin.TabularInline): model = Consigliere @@ -191,15 +222,30 @@ class Meta: widgets = { 'position': forms.HiddenInput, } + labels = {'readonly_field': 'Label from ModelForm.Meta'} + help_texts = {'readonly_field': 'Help text from ModelForm.Meta'} def __init__(self, *args, **kwargs): - super(SomeChildModelForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields['name'].label = 'new label' class SomeChildModelInline(admin.TabularInline): model = SomeChildModel form = SomeChildModelForm + readonly_fields = ('readonly_field',) + + +class StudentInline(admin.StackedInline): + model = Child + extra = 1 + fieldsets = [ + ('Name', {'fields': ('name',), 'classes': ('collapse',)}), + ] + + +class TeacherAdmin(admin.ModelAdmin): + inlines = [StudentInline] site.register(TitleCollection, inlines=[TitleInline]) @@ -207,12 +253,13 @@ class SomeChildModelInline(admin.TabularInline): # only ModelAdmin media site.register(Holder, HolderAdmin, inlines=[InnerInline]) # ModelAdmin and Inline media -site.register(Holder2, HolderAdmin, inlines=[InnerInline2]) +site.register(Holder2, HolderAdmin, inlines=[InnerInline2, InnerInline2Tabular]) # only Inline media site.register(Holder3, inlines=[InnerInline3]) site.register(Poll, PollAdmin) site.register(Novel, NovelAdmin) +site.register(NovelReadonlyChapter, NovelReadonlyChapterAdmin) site.register(Fashionista, inlines=[InlineWeakness]) site.register(Holder4, Holder4Admin) site.register(Author, AuthorAdmin) @@ -223,3 +270,4 @@ class SomeChildModelInline(admin.TabularInline): site.register(ExtraTerrestrial, inlines=[SightingInline]) site.register(SomeParentModel, inlines=[SomeChildModelInline]) site.register([Question, Inner4Stacked, Inner4Tabular]) +site.register(Teacher, TeacherAdmin) diff --git a/tests/admin_inlines/models.py b/tests/admin_inlines/models.py index 15297c521ff3..a42e2588e9e9 100644 --- a/tests/admin_inlines/models.py +++ b/tests/admin_inlines/models.py @@ -1,18 +1,13 @@ """ Testing of admin inline formsets. - """ -from __future__ import unicode_literals - import random from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.db import models -from django.utils.encoding import python_2_unicode_compatible -@python_2_unicode_compatible class Parent(models.Model): name = models.CharField(max_length=50) @@ -20,7 +15,6 @@ def __str__(self): return self.name -@python_2_unicode_compatible class Teacher(models.Model): name = models.CharField(max_length=50) @@ -28,7 +22,6 @@ def __str__(self): return self.name -@python_2_unicode_compatible class Child(models.Model): name = models.CharField(max_length=50) teacher = models.ForeignKey(Teacher, models.CASCADE) @@ -44,6 +37,9 @@ def __str__(self): class Book(models.Model): name = models.CharField(max_length=50) + def __str__(self): + return self.name + class Author(models.Model): name = models.CharField(max_length=50) @@ -60,7 +56,11 @@ def save(self, *args, **kwargs): test_pk = random.randint(1, 99999) if not NonAutoPKBook.objects.filter(rand_pk=test_pk).exists(): self.rand_pk = test_pk - super(NonAutoPKBook, self).save(*args, **kwargs) + super().save(*args, **kwargs) + + +class NonAutoPKBookChild(NonAutoPKBook): + pass class EditablePKBook(models.Model): @@ -155,6 +155,7 @@ class Poll(models.Model): class Question(models.Model): + text = models.CharField(max_length=40) poll = models.ForeignKey(Poll, models.CASCADE) @@ -162,6 +163,12 @@ class Novel(models.Model): name = models.CharField(max_length=40) +class NovelReadonlyChapter(Novel): + + class Meta: + proxy = True + + class Chapter(models.Model): name = models.CharField(max_length=40) novel = models.ForeignKey(Novel, models.CASCADE) @@ -251,6 +258,7 @@ class SomeChildModel(models.Model): name = models.CharField(max_length=1) position = models.PositiveIntegerField() parent = models.ForeignKey(SomeParentModel, models.CASCADE) + readonly_field = models.CharField(max_length=1) # Other models diff --git a/tests/admin_inlines/test_templates.py b/tests/admin_inlines/test_templates.py index 236ec5df7b0a..5c8d7ce0c105 100644 --- a/tests/admin_inlines/test_templates.py +++ b/tests/admin_inlines/test_templates.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import json from django.template.loader import render_to_string diff --git a/tests/admin_inlines/tests.py b/tests/admin_inlines/tests.py index 6922a9f82fd5..dd40ff675cfd 100644 --- a/tests/admin_inlines/tests.py +++ b/tests/admin_inlines/tests.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals +from selenium.common.exceptions import NoSuchElementException from django.contrib.admin import ModelAdmin, TabularInline from django.contrib.admin.helpers import InlineAdminForm @@ -20,7 +20,7 @@ INLINE_CHANGELINK_HTML = 'class="inlinechangelink">Change' -class TestDataMixin(object): +class TestDataMixin: @classmethod def setUpTestData(cls): @@ -29,22 +29,23 @@ def setUpTestData(cls): @override_settings(ROOT_URLCONF='admin_inlines.urls') class TestInline(TestDataMixin, TestCase): + factory = RequestFactory() - def setUp(self): - holder = Holder(dummy=13) - holder.save() - Inner(dummy=42, holder=holder).save() + @classmethod + def setUpTestData(cls): + super().setUpTestData() + cls.holder = Holder.objects.create(dummy=13) + Inner.objects.create(dummy=42, holder=cls.holder) + def setUp(self): self.client.force_login(self.superuser) - self.factory = RequestFactory() def test_can_delete(self): """ can_delete should be passed to inlineformset factory. """ - holder = Holder.objects.get(dummy=13) response = self.client.get( - reverse('admin:admin_inlines_holder_change', args=(holder.id,)) + reverse('admin:admin_inlines_holder_change', args=(self.holder.id,)) ) inner_formset = response.context['inline_admin_formsets'][0].formset expected = InnerInline.can_delete @@ -87,13 +88,27 @@ def test_inline_primary(self): self.assertEqual(response.status_code, 302) self.assertEqual(len(Fashionista.objects.filter(person__firstname='Imelda')), 1) + def test_tabular_inline_column_css_class(self): + """ + Field names are included in the context to output a field-specific + CSS class name in the column headers. + """ + response = self.client.get(reverse('admin:admin_inlines_poll_add')) + text_field, call_me_field = list(response.context['inline_admin_formset'].fields()) + # Editable field. + self.assertEqual(text_field['name'], 'text') + self.assertContains(response, '') + # Read-only field. + self.assertEqual(call_me_field['name'], 'call_me') + self.assertContains(response, '') + def test_custom_form_tabular_inline_label(self): """ A model form with a form field specified (TitleForm.title1) should have its label rendered in the tabular inline. """ response = self.client.get(reverse('admin:admin_inlines_titlecollection_add')) - self.assertContains(response, 'Title1', html=True) + self.assertContains(response, 'Title1', html=True) def test_custom_form_tabular_inline_overridden_label(self): """ @@ -103,7 +118,7 @@ def test_custom_form_tabular_inline_overridden_label(self): response = self.client.get(reverse('admin:admin_inlines_someparentmodel_add')) field = list(response.context['inline_admin_formset'].fields())[0] self.assertEqual(field['label'], 'new label') - self.assertContains(response, 'New label', html=True) + self.assertContains(response, 'New label', html=True) def test_tabular_non_field_errors(self): """ @@ -163,7 +178,7 @@ def test_help_text(self): '', + 'title="Awesome tabular help text is awesome.">', 1 ) # ReadOnly fields @@ -173,10 +188,25 @@ def test_help_text(self): '', + 'title="Help text for ReadOnlyInline">', 1 ) + def test_tabular_model_form_meta_readonly_field(self): + """ + Tabular inlines use ModelForm.Meta.help_texts and labels for read-only + fields. + """ + response = self.client.get(reverse('admin:admin_inlines_someparentmodel_add')) + self.assertContains( + response, + '' + ) + self.assertContains(response, 'Label from ModelForm.Meta') + def test_inline_hidden_field_no_column(self): """#18263 -- Make sure hidden fields don't get a column in tabular inlines""" parent = SomeParentModel.objects.create(name='a') @@ -186,7 +216,7 @@ def test_inline_hidden_field_no_column(self): self.assertNotContains(response, '') self.assertInHTML( '', + 'name="somechildmodel_set-1-position" type="hidden" value="1">', response.rendered_content, ) @@ -195,26 +225,26 @@ def test_non_related_name_inline(self): Multiple inlines with related_name='+' have correct form prefixes. """ response = self.client.get(reverse('admin:admin_inlines_capofamiglia_add')) - self.assertContains(response, '', html=True) + self.assertContains(response, '', html=True) self.assertContains( response, - '', + '', html=True ) self.assertContains( response, - '', + '', html=True ) - self.assertContains(response, '', html=True) + self.assertContains(response, '', html=True) self.assertContains( response, - '', + '', html=True ) self.assertContains( response, - '', + '', html=True ) @@ -267,12 +297,12 @@ def test_custom_get_extra_form(self): # ModelAdmin max_forms_input = ( '' + 'name="binarytree_set-MAX_NUM_FORMS" type="hidden" value="%d">' ) # The total number of forms will remain the same in either case total_forms_hidden = ( '' + 'name="binarytree_set-TOTAL_FORMS" type="hidden" value="2">' ) response = self.client.get(reverse('admin:admin_inlines_binarytree_add')) self.assertInHTML(max_forms_input % 3, response.rendered_content) @@ -295,11 +325,11 @@ class MinNumInline(TabularInline): modeladmin.inlines = [MinNumInline] min_forms = ( '' + 'name="binarytree_set-MIN_NUM_FORMS" type="hidden" value="2">' ) total_forms = ( '' + 'name="binarytree_set-TOTAL_FORMS" type="hidden" value="5">' ) request = self.factory.get(reverse('admin:admin_inlines_binarytree_add')) request.user = User(username='super', is_superuser=True) @@ -324,11 +354,11 @@ def get_min_num(self, request, obj=None, **kwargs): modeladmin.inlines = [MinNumInline] min_forms = ( '' + 'name="binarytree_set-MIN_NUM_FORMS" type="hidden" value="%d">' ) total_forms = ( '' + 'name="binarytree_set-TOTAL_FORMS" type="hidden" value="%d">' ) request = self.factory.get(reverse('admin:admin_inlines_binarytree_add')) request.user = User(username='super', is_superuser=True) @@ -347,13 +377,28 @@ def test_inline_nonauto_noneditable_pk(self): self.assertContains( response, '', + 'name="nonautopkbook_set-0-rand_pk" type="hidden">', html=True ) self.assertContains( response, '', + 'name="nonautopkbook_set-2-0-rand_pk" type="hidden">', + html=True + ) + + def test_inline_nonauto_noneditable_inherited_pk(self): + response = self.client.get(reverse('admin:admin_inlines_author_add')) + self.assertContains( + response, + '', + html=True + ) + self.assertContains( + response, + '', html=True ) @@ -362,13 +407,13 @@ def test_inline_editable_pk(self): self.assertContains( response, '', + 'name="editablepkbook_set-0-manual_pk" type="number">', html=True, count=1 ) self.assertContains( response, '', + 'name="editablepkbook_set-2-0-manual_pk" type="number">', html=True, count=1 ) @@ -419,6 +464,16 @@ def test_tabular_inline_show_change_link_false_registered(self): self.assertTrue(response.context['inline_admin_formset'].opts.has_registered_model) self.assertNotContains(response, INLINE_CHANGELINK_HTML) + def test_noneditable_inline_has_field_inputs(self): + """Inlines without change permission shows field inputs on add form.""" + response = self.client.get(reverse('admin:admin_inlines_novelreadonlychapter_add')) + self.assertContains( + response, + '', + html=True + ) + @override_settings(ROOT_URLCONF='admin_inlines.urls') class TestInlineMedia(TestDataMixin, TestCase): @@ -440,6 +495,16 @@ def test_inline_media_only_inline(self): Inner3(dummy=42, holder=holder).save() change_url = reverse('admin:admin_inlines_holder3_change', args=(holder.id,)) response = self.client.get(change_url) + self.assertEqual( + response.context['inline_admin_formsets'][0].media._js, + [ + 'admin/js/vendor/jquery/jquery.min.js', + 'my_awesome_inline_scripts.js', + 'custom_number.js', + 'admin/js/jquery.init.js', + 'admin/js/inlines.min.js', + ] + ) self.assertContains(response, 'my_awesome_inline_scripts.js') def test_all_inline_media(self): @@ -508,42 +573,41 @@ class TestInlinePermissions(TestCase): inline. Refs #8060. """ - def setUp(self): - self.user = User(username='admin') - self.user.is_staff = True - self.user.is_active = True - self.user.set_password('secret') - self.user.save() + @classmethod + def setUpTestData(cls): + cls.user = User(username='admin', is_staff=True, is_active=True) + cls.user.set_password('secret') + cls.user.save() - self.author_ct = ContentType.objects.get_for_model(Author) - self.holder_ct = ContentType.objects.get_for_model(Holder2) - self.book_ct = ContentType.objects.get_for_model(Book) - self.inner_ct = ContentType.objects.get_for_model(Inner2) + cls.author_ct = ContentType.objects.get_for_model(Author) + cls.holder_ct = ContentType.objects.get_for_model(Holder2) + cls.book_ct = ContentType.objects.get_for_model(Book) + cls.inner_ct = ContentType.objects.get_for_model(Inner2) # User always has permissions to add and change Authors, and Holders, # the main (parent) models of the inlines. Permissions on the inlines # vary per test. - permission = Permission.objects.get(codename='add_author', content_type=self.author_ct) - self.user.user_permissions.add(permission) - permission = Permission.objects.get(codename='change_author', content_type=self.author_ct) - self.user.user_permissions.add(permission) - permission = Permission.objects.get(codename='add_holder2', content_type=self.holder_ct) - self.user.user_permissions.add(permission) - permission = Permission.objects.get(codename='change_holder2', content_type=self.holder_ct) - self.user.user_permissions.add(permission) + permission = Permission.objects.get(codename='add_author', content_type=cls.author_ct) + cls.user.user_permissions.add(permission) + permission = Permission.objects.get(codename='change_author', content_type=cls.author_ct) + cls.user.user_permissions.add(permission) + permission = Permission.objects.get(codename='add_holder2', content_type=cls.holder_ct) + cls.user.user_permissions.add(permission) + permission = Permission.objects.get(codename='change_holder2', content_type=cls.holder_ct) + cls.user.user_permissions.add(permission) author = Author.objects.create(pk=1, name='The Author') - book = author.books.create(name='The inline Book') - self.author_change_url = reverse('admin:admin_inlines_author_change', args=(author.id,)) + cls.book = author.books.create(name='The inline Book') + cls.author_change_url = reverse('admin:admin_inlines_author_change', args=(author.id,)) # Get the ID of the automatically created intermediate model for the Author-Book m2m - author_book_auto_m2m_intermediate = Author.books.through.objects.get(author=author, book=book) - self.author_book_auto_m2m_intermediate_id = author_book_auto_m2m_intermediate.pk + author_book_auto_m2m_intermediate = Author.books.through.objects.get(author=author, book=cls.book) + cls.author_book_auto_m2m_intermediate_id = author_book_auto_m2m_intermediate.pk - holder = Holder2.objects.create(dummy=13) - inner2 = Inner2.objects.create(dummy=42, holder=holder) - self.holder_change_url = reverse('admin:admin_inlines_holder2_change', args=(holder.id,)) - self.inner2_id = inner2.id + cls.holder = Holder2.objects.create(dummy=13) + cls.inner2 = Inner2.objects.create(dummy=42, holder=cls.holder) + def setUp(self): + self.holder_change_url = reverse('admin:admin_inlines_holder2_change', args=(self.holder.id,)) self.client.force_login(self.user) def test_inline_add_m2m_noperm(self): @@ -574,6 +638,25 @@ def test_inline_change_fk_noperm(self): self.assertNotContains(response, 'Add another Inner2') self.assertNotContains(response, 'id="id_inner2_set-TOTAL_FORMS"') + def test_inline_add_m2m_view_only_perm(self): + permission = Permission.objects.get(codename='view_book', content_type=self.book_ct) + self.user.user_permissions.add(permission) + response = self.client.get(reverse('admin:admin_inlines_author_add')) + # View-only inlines. (It could be nicer to hide the empty, non-editable + # inlines on the add page.) + self.assertIs(response.context['inline_admin_formset'].has_view_permission, True) + self.assertIs(response.context['inline_admin_formset'].has_add_permission, False) + self.assertIs(response.context['inline_admin_formset'].has_change_permission, False) + self.assertIs(response.context['inline_admin_formset'].has_delete_permission, False) + self.assertContains(response, '

      Author-book relationships

      ') + self.assertContains( + response, + '', + html=True, + ) + self.assertNotContains(response, 'Add another Author-Book Relationship') + def test_inline_add_m2m_add_perm(self): permission = Permission.objects.get(codename='add_book', content_type=self.book_ct) self.user.user_permissions.add(permission) @@ -591,7 +674,7 @@ def test_inline_add_fk_add_perm(self): self.assertContains(response, '

      Inner2s

      ') self.assertContains(response, 'Add another Inner2') self.assertContains(response, '', html=True) + 'value="3" name="inner2_set-TOTAL_FORMS">', html=True) def test_inline_change_m2m_add_perm(self): permission = Permission.objects.get(codename='add_book', content_type=self.book_ct) @@ -603,19 +686,47 @@ def test_inline_change_m2m_add_perm(self): self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"') self.assertNotContains(response, 'id="id_Author_books-0-DELETE"') + def test_inline_change_m2m_view_only_perm(self): + permission = Permission.objects.get(codename='view_book', content_type=self.book_ct) + self.user.user_permissions.add(permission) + response = self.client.get(self.author_change_url) + # View-only inlines. + self.assertIs(response.context['inline_admin_formset'].has_view_permission, True) + self.assertIs(response.context['inline_admin_formset'].has_add_permission, False) + self.assertIs(response.context['inline_admin_formset'].has_change_permission, False) + self.assertIs(response.context['inline_admin_formset'].has_delete_permission, False) + self.assertContains(response, '

      Author-book relationships

      ') + self.assertContains( + response, + '', + html=True, + ) + # The field in the inline is read-only. + self.assertContains(response, '

      %s

      ' % self.book) + self.assertNotContains( + response, + '', + html=True, + ) + def test_inline_change_m2m_change_perm(self): permission = Permission.objects.get(codename='change_book', content_type=self.book_ct) self.user.user_permissions.add(permission) response = self.client.get(self.author_change_url) # We have change perm on books, so we can add/change/delete inlines + self.assertIs(response.context['inline_admin_formset'].has_view_permission, True) + self.assertIs(response.context['inline_admin_formset'].has_add_permission, True) + self.assertIs(response.context['inline_admin_formset'].has_change_permission, True) + self.assertIs(response.context['inline_admin_formset'].has_delete_permission, True) self.assertContains(response, '

      Author-book relationships

      ') self.assertContains(response, 'Add another Author-book relationship') self.assertContains(response, '', html=True) + 'value="4" name="Author_books-TOTAL_FORMS">', html=True) self.assertContains( response, '' % self.author_book_auto_m2m_intermediate_id, + 'name="Author_books-0-id">' % self.author_book_auto_m2m_intermediate_id, html=True ) self.assertContains(response, 'id="id_Author_books-0-DELETE"') @@ -631,12 +742,12 @@ def test_inline_change_fk_add_perm(self): self.assertContains( response, '', + 'name="inner2_set-TOTAL_FORMS">', html=True ) self.assertNotContains( response, - '' % self.inner2_id, + '' % self.inner2.id, html=True ) @@ -645,23 +756,31 @@ def test_inline_change_fk_change_perm(self): self.user.user_permissions.add(permission) response = self.client.get(self.holder_change_url) # Change permission on inner2s, so we can change existing but not add new - self.assertContains(response, '

      Inner2s

      ') + self.assertContains(response, '

      Inner2s

      ', count=2) # Just the one form for existing instances self.assertContains( - response, '', + response, '', html=True ) self.assertContains( response, - '' % self.inner2_id, + '' % self.inner2.id, html=True ) # max-num 0 means we can't add new ones self.assertContains( response, - '', + '', html=True ) + # TabularInline + self.assertContains(response, 'Dummy', html=True) + self.assertContains( + response, + '' % self.inner2.dummy, + html=True, + ) def test_inline_change_fk_add_change_perm(self): permission = Permission.objects.get(codename='add_inner2', content_type=self.inner_ct) @@ -673,12 +792,12 @@ def test_inline_change_fk_add_change_perm(self): self.assertContains(response, '

      Inner2s

      ') # One form for existing instance and three extra for new self.assertContains( - response, '', + response, '', html=True ) self.assertContains( response, - '' % self.inner2_id, + '' % self.inner2.id, html=True ) @@ -693,12 +812,12 @@ def test_inline_change_fk_change_del_perm(self): # One form for existing instance only, no new self.assertContains( response, - '', + '', html=True ) self.assertContains( response, - '' % self.inner2_id, + '' % self.inner2.id, html=True ) self.assertContains(response, 'id="id_inner2_set-0-DELETE"') @@ -712,19 +831,119 @@ def test_inline_change_fk_all_perms(self): self.user.user_permissions.add(permission) response = self.client.get(self.holder_change_url) # All perms on inner2s, so we can add/change/delete - self.assertContains(response, '

      Inner2s

      ') + self.assertContains(response, '

      Inner2s

      ', count=2) # One form for existing instance only, three for new self.assertContains( response, - '', + '', html=True ) self.assertContains( response, - '' % self.inner2_id, + '' % self.inner2.id, html=True ) self.assertContains(response, 'id="id_inner2_set-0-DELETE"') + # TabularInline + self.assertContains(response, 'Dummy', html=True) + self.assertContains( + response, + '' % self.inner2.dummy, + html=True, + ) + + +@override_settings(ROOT_URLCONF='admin_inlines.urls') +class TestReadOnlyChangeViewInlinePermissions(TestCase): + + @classmethod + def setUpTestData(cls): + cls.user = User.objects.create_user('testing', password='password', is_staff=True) + cls.user.user_permissions.add( + Permission.objects.get(codename='view_poll', content_type=ContentType.objects.get_for_model(Poll)) + ) + cls.user.user_permissions.add( + *Permission.objects.filter( + codename__endswith="question", content_type=ContentType.objects.get_for_model(Question) + ).values_list('pk', flat=True) + ) + + cls.poll = Poll.objects.create(name="Survey") + cls.add_url = reverse('admin:admin_inlines_poll_add') + cls.change_url = reverse('admin:admin_inlines_poll_change', args=(cls.poll.id,)) + + def setUp(self): + self.client.force_login(self.user) + + def test_add_url_not_allowed(self): + response = self.client.get(self.add_url) + self.assertEqual(response.status_code, 403) + + response = self.client.post(self.add_url, {}) + self.assertEqual(response.status_code, 403) + + def test_post_to_change_url_not_allowed(self): + response = self.client.post(self.change_url, {}) + self.assertEqual(response.status_code, 403) + + def test_get_to_change_url_is_allowed(self): + response = self.client.get(self.change_url) + self.assertEqual(response.status_code, 200) + + def test_main_model_is_rendered_as_read_only(self): + response = self.client.get(self.change_url) + self.assertContains( + response, + '
      %s
      ' % self.poll.name, + html=True + ) + input = '' + self.assertNotContains( + response, + input % self.poll.name, + html=True + ) + + def test_inlines_are_rendered_as_read_only(self): + question = Question.objects.create(text="How will this be rendered?", poll=self.poll) + response = self.client.get(self.change_url) + self.assertContains( + response, + '

      %s

      ' % question.text, + html=True + ) + self.assertNotContains(response, 'id="id_question_set-0-text"') + self.assertNotContains(response, 'id="id_related_objs-0-DELETE"') + + def test_submit_line_shows_only_close_button(self): + response = self.client.get(self.change_url) + self.assertContains( + response, + 'Close', + html=True + ) + delete_link = '' # noqa + self.assertNotContains( + response, + delete_link % self.poll.id, + html=True + ) + self.assertNotContains(response, '') + self.assertNotContains(response, '') + + def test_inline_delete_buttons_are_not_shown(self): + Question.objects.create(text="How will this be rendered?", poll=self.poll) + response = self.client.get(self.change_url) + self.assertNotContains( + response, + '', + html=True + ) + + def test_extra_inlines_are_not_shown(self): + response = self.client.get(self.change_url) + self.assertNotContains(response, 'id="id_question_set-0-text"') @override_settings(ROOT_URLCONF='admin_inlines.urls') @@ -830,6 +1049,24 @@ def test_add_inlines(self): self.assertEqual(ProfileCollection.objects.all().count(), 1) self.assertEqual(Profile.objects.all().count(), 3) + def test_add_inline_link_absent_for_view_only_parent_model(self): + user = User.objects.create_user('testing', password='password', is_staff=True) + user.user_permissions.add( + Permission.objects.get(codename='view_poll', content_type=ContentType.objects.get_for_model(Poll)) + ) + user.user_permissions.add( + *Permission.objects.filter( + codename__endswith="question", content_type=ContentType.objects.get_for_model(Question) + ).values_list('pk', flat=True) + ) + self.admin_login(username='testing', password='password') + poll = Poll.objects.create(name="Survey") + change_url = reverse('admin:admin_inlines_poll_change', args=(poll.id,)) + self.selenium.get(self.live_server_url + change_url) + with self.disable_implicit_wait(): + with self.assertRaises(NoSuchElementException): + self.selenium.find_element_by_link_text('Add another Question') + def test_delete_inlines(self): self.admin_login(username='super', password='secret') self.selenium.get(self.live_server_url + reverse('admin:admin_inlines_profilecollection_add')) @@ -888,6 +1125,24 @@ def test_collapsed_inlines(self): # One field is in a stacked inline, other in a tabular one. test_fields = ['#id_nonautopkbook_set-0-title', '#id_nonautopkbook_set-2-0-title'] show_links = self.selenium.find_elements_by_link_text('SHOW') + self.assertEqual(len(show_links), 3) + for show_index, field_name in enumerate(test_fields, 0): + self.wait_until_invisible(field_name) + show_links[show_index].click() + self.wait_until_visible(field_name) + hide_links = self.selenium.find_elements_by_link_text('HIDE') + self.assertEqual(len(hide_links), 2) + for hide_index, field_name in enumerate(test_fields, 0): + self.wait_until_visible(field_name) + hide_links[hide_index].click() + self.wait_until_invisible(field_name) + + def test_added_stacked_inline_with_collapsed_fields(self): + self.admin_login(username='super', password='secret') + self.selenium.get(self.live_server_url + reverse('admin:admin_inlines_teacher_add')) + self.selenium.find_element_by_link_text('Add another Child').click() + test_fields = ['#id_child_set-0-name', '#id_child_set-1-name'] + show_links = self.selenium.find_elements_by_link_text('SHOW') self.assertEqual(len(show_links), 2) for show_index, field_name in enumerate(test_fields, 0): self.wait_until_invisible(field_name) diff --git a/tests/admin_inlines/urls.py b/tests/admin_inlines/urls.py index 1f553a85a999..be569cdca51c 100644 --- a/tests/admin_inlines/urls.py +++ b/tests/admin_inlines/urls.py @@ -1,7 +1,7 @@ -from django.conf.urls import url +from django.urls import path from . import admin urlpatterns = [ - url(r'^admin/', admin.site.urls), + path('admin/', admin.site.urls), ] diff --git a/tests/admin_ordering/models.py b/tests/admin_ordering/models.py index cd26f2a710d0..fbddeaa28334 100644 --- a/tests/admin_ordering/models.py +++ b/tests/admin_ordering/models.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from django.contrib import admin from django.db import models @@ -28,7 +27,7 @@ class SongInlineDefaultOrdering(admin.StackedInline): class SongInlineNewOrdering(admin.StackedInline): model = Song - ordering = ('duration', ) + ordering = ('duration',) class DynOrderingBandAdmin(admin.ModelAdmin): diff --git a/tests/admin_ordering/tests.py b/tests/admin_ordering/tests.py index def9b8b7c7b4..f68b64ae70c0 100644 --- a/tests/admin_ordering/tests.py +++ b/tests/admin_ordering/tests.py @@ -1,8 +1,7 @@ -from __future__ import unicode_literals - from django.contrib import admin from django.contrib.admin.options import ModelAdmin from django.contrib.auth.models import User +from django.db.models import F from django.test import RequestFactory, TestCase from .models import ( @@ -11,11 +10,11 @@ ) -class MockRequest(object): +class MockRequest: pass -class MockSuperUser(object): +class MockSuperUser: def has_perm(self, perm): return True @@ -35,9 +34,10 @@ class TestAdminOrdering(TestCase): in ModelAdmin rather that ordering defined in the model's inner Meta class. """ + request_factory = RequestFactory() - def setUp(self): - self.request_factory = RequestFactory() + @classmethod + def setUpTestData(cls): Band.objects.bulk_create([ Band(name='Aerosmith', bio='', rank=3), Band(name='Radiohead', bio='', rank=1), @@ -51,7 +51,7 @@ def test_default_ordering(self): """ ma = ModelAdmin(Band, site) names = [b.name for b in ma.get_queryset(request)] - self.assertListEqual(['Aerosmith', 'Radiohead', 'Van Halen'], names) + self.assertEqual(['Aerosmith', 'Radiohead', 'Van Halen'], names) def test_specified_ordering(self): """ @@ -62,7 +62,14 @@ class BandAdmin(ModelAdmin): ordering = ('rank',) # default ordering is ('name',) ma = BandAdmin(Band, site) names = [b.name for b in ma.get_queryset(request)] - self.assertListEqual(['Radiohead', 'Van Halen', 'Aerosmith'], names) + self.assertEqual(['Radiohead', 'Van Halen', 'Aerosmith'], names) + + def test_specified_ordering_by_f_expression(self): + class BandAdmin(ModelAdmin): + ordering = (F('rank').desc(nulls_last=True),) + band_admin = BandAdmin(Band, site) + names = [b.name for b in band_admin.get_queryset(request)] + self.assertEqual(['Aerosmith', 'Van Halen', 'Radiohead'], names) def test_dynamic_ordering(self): """ @@ -74,10 +81,10 @@ def test_dynamic_ordering(self): request.user = super_user ma = DynOrderingBandAdmin(Band, site) names = [b.name for b in ma.get_queryset(request)] - self.assertListEqual(['Radiohead', 'Van Halen', 'Aerosmith'], names) + self.assertEqual(['Radiohead', 'Van Halen', 'Aerosmith'], names) request.user = other_user names = [b.name for b in ma.get_queryset(request)] - self.assertListEqual(['Aerosmith', 'Radiohead', 'Van Halen'], names) + self.assertEqual(['Aerosmith', 'Radiohead', 'Van Halen'], names) class TestInlineModelAdminOrdering(TestCase): @@ -86,12 +93,13 @@ class TestInlineModelAdminOrdering(TestCase): define in InlineModelAdmin. """ - def setUp(self): - self.band = Band.objects.create(name='Aerosmith', bio='', rank=3) + @classmethod + def setUpTestData(cls): + cls.band = Band.objects.create(name='Aerosmith', bio='', rank=3) Song.objects.bulk_create([ - Song(band=self.band, name='Pink', duration=235), - Song(band=self.band, name='Dude (Looks Like a Lady)', duration=264), - Song(band=self.band, name='Jaded', duration=214), + Song(band=cls.band, name='Pink', duration=235), + Song(band=cls.band, name='Dude (Looks Like a Lady)', duration=264), + Song(band=cls.band, name='Jaded', duration=214), ]) def test_default_ordering(self): @@ -101,7 +109,7 @@ def test_default_ordering(self): """ inline = SongInlineDefaultOrdering(self.band, site) names = [s.name for s in inline.get_queryset(request)] - self.assertListEqual(['Dude (Looks Like a Lady)', 'Jaded', 'Pink'], names) + self.assertEqual(['Dude (Looks Like a Lady)', 'Jaded', 'Pink'], names) def test_specified_ordering(self): """ @@ -109,14 +117,16 @@ def test_specified_ordering(self): """ inline = SongInlineNewOrdering(self.band, site) names = [s.name for s in inline.get_queryset(request)] - self.assertListEqual(['Jaded', 'Pink', 'Dude (Looks Like a Lady)'], names) + self.assertEqual(['Jaded', 'Pink', 'Dude (Looks Like a Lady)'], names) class TestRelatedFieldsAdminOrdering(TestCase): - def setUp(self): - self.b1 = Band.objects.create(name='Pink Floyd', bio='', rank=1) - self.b2 = Band.objects.create(name='Foo Fighters', bio='', rank=5) + @classmethod + def setUpTestData(cls): + cls.b1 = Band.objects.create(name='Pink Floyd', bio='', rank=1) + cls.b2 = Band.objects.create(name='Foo Fighters', bio='', rank=5) + def setUp(self): # we need to register a custom ModelAdmin (instead of just using # ModelAdmin) because the field creator tries to find the ModelAdmin # for the related model @@ -132,9 +142,8 @@ def tearDown(self): def check_ordering_of_field_choices(self, correct_ordering): fk_field = site._registry[Song].formfield_for_foreignkey(Song.band.field, request=None) m2m_field = site._registry[Song].formfield_for_manytomany(Song.other_interpreters.field, request=None) - - self.assertListEqual(list(fk_field.queryset), correct_ordering) - self.assertListEqual(list(m2m_field.queryset), correct_ordering) + self.assertEqual(list(fk_field.queryset), correct_ordering) + self.assertEqual(list(m2m_field.queryset), correct_ordering) def test_no_admin_fallback_to_model_ordering(self): # should be ordered by name (as defined by the model) @@ -163,12 +172,12 @@ class SongAdmin(admin.ModelAdmin): def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == 'band': kwargs["queryset"] = Band.objects.filter(rank__gt=2) - return super(SongAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs) + return super().formfield_for_foreignkey(db_field, request, **kwargs) def formfield_for_manytomany(self, db_field, request, **kwargs): if db_field.name == 'other_interpreters': kwargs["queryset"] = Band.objects.filter(rank__gt=2) - return super(SongAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs) + return super().formfield_for_foreignkey(db_field, request, **kwargs) class StaticOrderingBandAdmin(admin.ModelAdmin): ordering = ('rank',) diff --git a/tests/admin_registration/tests.py b/tests/admin_registration/tests.py index db38265182b1..8601328647f2 100644 --- a/tests/admin_registration/tests.py +++ b/tests/admin_registration/tests.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.contrib import admin from django.contrib.admin.decorators import register from django.contrib.admin.sites import site @@ -32,7 +30,8 @@ def test_registration_with_model_admin(self): def test_prevent_double_registration(self): self.site.register(Person) - with self.assertRaises(admin.sites.AlreadyRegistered): + msg = 'The model Person is already registered' + with self.assertRaisesMessage(admin.sites.AlreadyRegistered, msg): self.site.register(Person) def test_registration_with_star_star_options(self): @@ -57,7 +56,8 @@ def test_abstract_model(self): Exception is raised when trying to register an abstract model. Refs #12004. """ - with self.assertRaises(ImproperlyConfigured): + msg = 'The model Location is abstract, so it cannot be registered with admin.' + with self.assertRaisesMessage(ImproperlyConfigured, msg): self.site.register(Location) def test_is_registered_model(self): diff --git a/tests/admin_scripts/another_app_waiting_migration/migrations/0001_initial.py b/tests/admin_scripts/another_app_waiting_migration/migrations/0001_initial.py index 1486231d6b0b..d5fce1af362b 100644 --- a/tests/admin_scripts/another_app_waiting_migration/migrations/0001_initial.py +++ b/tests/admin_scripts/another_app_waiting_migration/migrations/0001_initial.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/tests/admin_scripts/another_app_waiting_migration/models.py b/tests/admin_scripts/another_app_waiting_migration/models.py index 6c12c6ab5d5c..c089cb64e6e6 100644 --- a/tests/admin_scripts/another_app_waiting_migration/models.py +++ b/tests/admin_scripts/another_app_waiting_migration/models.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.db import models diff --git a/tests/admin_scripts/app_raising_messages/models.py b/tests/admin_scripts/app_raising_messages/models.py index bd37ba458a37..400fc8f9079a 100644 --- a/tests/admin_scripts/app_raising_messages/models.py +++ b/tests/admin_scripts/app_raising_messages/models.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.core import checks from django.db import models diff --git a/tests/admin_scripts/app_raising_warning/models.py b/tests/admin_scripts/app_raising_warning/models.py index 0ae304263460..bc9b3b380f47 100644 --- a/tests/admin_scripts/app_raising_warning/models.py +++ b/tests/admin_scripts/app_raising_warning/models.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.core import checks from django.db import models diff --git a/tests/admin_scripts/app_waiting_migration/migrations/0001_initial.py b/tests/admin_scripts/app_waiting_migration/migrations/0001_initial.py index 52d594a94a69..aa47c4d47408 100644 --- a/tests/admin_scripts/app_waiting_migration/migrations/0001_initial.py +++ b/tests/admin_scripts/app_waiting_migration/migrations/0001_initial.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/tests/admin_scripts/app_waiting_migration/models.py b/tests/admin_scripts/app_waiting_migration/models.py index 5e9f0e33148a..46fee9c8571b 100644 --- a/tests/admin_scripts/app_waiting_migration/models.py +++ b/tests/admin_scripts/app_waiting_migration/models.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.db import models diff --git a/tests/admin_scripts/configured_settings_manage.py b/tests/admin_scripts/configured_settings_manage.py new file mode 100644 index 000000000000..e057e70810d2 --- /dev/null +++ b/tests/admin_scripts/configured_settings_manage.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python +import sys + +from django.conf import settings +from django.core.management import execute_from_command_line + +if __name__ == '__main__': + settings.configure(DEBUG=True, CUSTOM=1) + execute_from_command_line(sys.argv) diff --git a/tests/admin_scripts/management/commands/custom_startproject.py b/tests/admin_scripts/management/commands/custom_startproject.py index 3fa19d98ccc5..b258e4b80df4 100644 --- a/tests/admin_scripts/management/commands/custom_startproject.py +++ b/tests/admin_scripts/management/commands/custom_startproject.py @@ -3,5 +3,5 @@ class Command(BaseCommand): def add_arguments(self, parser): - super(Command, self).add_arguments(parser) + super().add_arguments(parser) parser.add_argument('--extra', help='An arbitrary extra value passed to the context') diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py index af3ada5bcc4c..9dc48c677dec 100644 --- a/tests/admin_scripts/tests.py +++ b/tests/admin_scripts/tests.py @@ -1,11 +1,8 @@ -# -*- coding: utf-8 -*- """ A series of tests to establish that the command-line management tools work as advertised - especially with regards to the handling of the DJANGO_SETTINGS_MODULE and default settings.py files. """ -from __future__ import unicode_literals - import codecs import os import re @@ -15,6 +12,8 @@ import sys import tempfile import unittest +from io import StringIO +from unittest import mock import django from django import conf, get_version @@ -22,27 +21,30 @@ from django.core.management import ( BaseCommand, CommandError, call_command, color, ) -from django.db import ConnectionHandler -from django.db.migrations.exceptions import MigrationSchemaMissing +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, +) +from django.db import ConnectionHandler, connection from django.db.migrations.recorder import MigrationRecorder from django.test import ( - LiveServerTestCase, SimpleTestCase, TestCase, mock, override_settings, + LiveServerTestCase, SimpleTestCase, TestCase, override_settings, ) -from django.utils._os import npath, upath -from django.utils.encoding import force_text -from django.utils.six import PY2, PY3, StringIO +from django.utils.version import PY36 -custom_templates_dir = os.path.join(os.path.dirname(upath(__file__)), 'custom_templates') +custom_templates_dir = os.path.join(os.path.dirname(__file__), 'custom_templates') -PY36 = sys.version_info >= (3, 6) SYSTEM_CHECK_MSG = 'System check identified no issues' -class AdminScriptTestCase(unittest.TestCase): +class AdminScriptTestCase(SimpleTestCase): @classmethod def setUpClass(cls): - super(AdminScriptTestCase, cls).setUpClass() + super().setUpClass() cls.test_dir = os.path.realpath(os.path.join( tempfile.gettempdir(), cls.__name__, @@ -56,7 +58,7 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): shutil.rmtree(cls.test_dir) - super(AdminScriptTestCase, cls).tearDownClass() + super().tearDownClass() def write_settings(self, filename, apps=None, is_dir=False, sdict=None, extra=None): if is_dir: @@ -67,7 +69,6 @@ def write_settings(self, filename, apps=None, is_dir=False, sdict=None, extra=No settings_file_path = os.path.join(self.test_dir, filename) with open(settings_file_path, 'w') as settings_file: - settings_file.write('# -*- coding: utf-8 -*\n') settings_file.write('# Settings file automatically generated by admin_scripts test case\n') if extra: settings_file.write("%s\n" % extra) @@ -99,18 +100,8 @@ def remove_settings(self, filename, is_dir=False): else: os.remove(full_name) - # Also try to remove the compiled file; if it exists, it could + # Also remove a __pycache__ directory, if it exists; it could # mess up later tests that depend upon the .py file not existing - try: - if sys.platform.startswith('java'): - # Jython produces module$py.class files - os.remove(re.sub(r'\.py$', '$py.class', full_name)) - else: - # CPython produces module.pyc files - os.remove(full_name + 'c') - except OSError: - pass - # Also remove a __pycache__ directory, if it exists cache_name = os.path.join(self.test_dir, '__pycache__') if os.path.isdir(cache_name): shutil.rmtree(cache_name) @@ -120,11 +111,10 @@ def _ext_backend_paths(self): Returns the paths for any external backend packages. """ paths = [] - first_package_re = re.compile(r'(^[^\.]+)\.') for backend in settings.DATABASES.values(): - result = first_package_re.findall(backend['ENGINE']) - if result and result != ['django']: - backend_pkg = __import__(result[0]) + package = backend['ENGINE'].split('.')[0] + if package != 'django': + backend_pkg = __import__(package) backend_dir = os.path.dirname(backend_pkg.__file__) paths.append(os.path.dirname(backend_dir)) return paths @@ -132,7 +122,7 @@ def _ext_backend_paths(self): def run_test(self, script, args, settings_file=None, apps=None): base_dir = os.path.dirname(self.test_dir) # The base dir for Django's tests is one level up. - tests_dir = os.path.dirname(os.path.dirname(upath(__file__))) + tests_dir = os.path.dirname(os.path.dirname(__file__)) # The base dir for Django is one level above the test dir. We don't use # `import django` to figure that out, so we don't pick up a Django # from site-packages or similar. @@ -141,23 +131,17 @@ def run_test(self, script, args, settings_file=None, apps=None): # Define a temporary environment for the subprocess test_environ = os.environ.copy() - if sys.platform.startswith('java'): - python_path_var_name = 'JYTHONPATH' - else: - python_path_var_name = 'PYTHONPATH' - old_cwd = os.getcwd() # Set the test environment if settings_file: - test_environ['DJANGO_SETTINGS_MODULE'] = str(settings_file) + test_environ['DJANGO_SETTINGS_MODULE'] = settings_file elif 'DJANGO_SETTINGS_MODULE' in test_environ: del test_environ['DJANGO_SETTINGS_MODULE'] python_path = [base_dir, django_dir, tests_dir] python_path.extend(ext_backend_base_dirs) - # Use native strings for better compatibility - test_environ[str(python_path_var_name)] = npath(os.pathsep.join(python_path)) - test_environ[str('PYTHONWARNINGS')] = str('') + test_environ['PYTHONPATH'] = os.pathsep.join(python_path) + test_environ['PYTHONWARNINGS'] = '' # Move to the test directory and run os.chdir(self.test_dir) @@ -172,19 +156,21 @@ def run_test(self, script, args, settings_file=None, apps=None): return out, err def run_django_admin(self, args, settings_file=None): - script_dir = os.path.abspath(os.path.join(os.path.dirname(upath(django.__file__)), 'bin')) + script_dir = os.path.abspath(os.path.join(os.path.dirname(django.__file__), 'bin')) return self.run_test(os.path.join(script_dir, 'django-admin.py'), args, settings_file) - def run_manage(self, args, settings_file=None): + def run_manage(self, args, settings_file=None, configured_settings=False): def safe_remove(path): try: os.remove(path) except OSError: pass - conf_dir = os.path.dirname(upath(conf.__file__)) - template_manage_py = os.path.join(conf_dir, 'project_template', 'manage.py-tpl') - + template_manage_py = ( + os.path.join(os.path.dirname(__file__), 'configured_settings_manage.py') + if configured_settings else + os.path.join(os.path.dirname(conf.__file__), 'project_template', 'manage.py-tpl') + ) test_manage_py = os.path.join(self.test_dir, 'manage.py') shutil.copyfile(template_manage_py, test_manage_py) @@ -204,7 +190,6 @@ def assertNoOutput(self, stream): def assertOutput(self, stream, msg, regex=False): "Utility assertion: assert that the given message exists in the output" - stream = force_text(stream) if regex: self.assertIsNotNone( re.search(msg, stream), @@ -215,7 +200,6 @@ def assertOutput(self, stream, msg, regex=False): def assertNotInOutput(self, stream, msg): "Utility assertion: assert that the given message doesn't exist in the output" - stream = force_text(stream) self.assertNotIn(msg, stream, "'%s' matches actual output text '%s'" % (msg, stream)) ########################################################################## @@ -249,6 +233,16 @@ def test_builtin_with_bad_environment(self): self.assertNoOutput(out) self.assertOutput(err, "No module named '?bad_settings'?", regex=True) + def test_commands_with_invalid_settings(self): + """" + Commands that don't require settings succeed if the settings file + doesn't exist. + """ + args = ['startproject'] + out, err = self.run_django_admin(args, settings_file='bad_settings') + self.assertNoOutput(out) + self.assertOutput(err, "You must provide a project name", regex=True) + class DjangoAdminDefaultSettings(AdminScriptTestCase): """A series of tests for django-admin.py when using a settings.py file that @@ -614,26 +608,10 @@ def test_setup_environ(self): self.addCleanup(shutil.rmtree, app_path) self.assertNoOutput(err) self.assertTrue(os.path.exists(app_path)) - unicode_literals_import = "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\n" with open(os.path.join(app_path, 'apps.py'), 'r') as f: content = f.read() self.assertIn("class SettingsTestConfig(AppConfig)", content) self.assertIn("name = 'settings_test'", content) - if not PY3: - self.assertIn(unicode_literals_import, content) - if not PY3: - with open(os.path.join(app_path, 'models.py'), 'r') as fp: - content = fp.read() - self.assertIn(unicode_literals_import, content) - with open(os.path.join(app_path, 'views.py'), 'r') as fp: - content = fp.read() - self.assertIn(unicode_literals_import, content) - with open(os.path.join(app_path, 'admin.py'), 'r') as fp: - content = fp.read() - self.assertIn(unicode_literals_import, content) - with open(os.path.join(app_path, 'tests.py'), 'r') as fp: - content = fp.read() - self.assertIn(unicode_literals_import, content) def test_setup_environ_custom_template(self): "directory: startapp creates the correct directory with a custom template" @@ -646,7 +624,6 @@ def test_setup_environ_custom_template(self): self.assertTrue(os.path.exists(app_path)) self.assertTrue(os.path.exists(os.path.join(app_path, 'api.py'))) - @unittest.skipIf(PY2, "Python 2 doesn't support Unicode package names.") def test_startapp_unicode_name(self): "directory: startapp creates the correct directory with unicode characters" args = ['startapp', 'こんにちは'] @@ -993,9 +970,9 @@ def test_custom_command_with_settings(self): out, err = self.run_manage(args) self.assertOutput( out, - "EXECUTE: noargs_command options=[('no_color', False), " - "('pythonpath', None), ('settings', 'alternate_settings'), " - "('traceback', False), ('verbosity', 1)]" + "EXECUTE: noargs_command options=[('force_color', False), " + "('no_color', False), ('pythonpath', None), ('settings', " + "'alternate_settings'), ('traceback', False), ('verbosity', 1)]" ) self.assertNoOutput(err) @@ -1005,9 +982,9 @@ def test_custom_command_with_environment(self): out, err = self.run_manage(args, 'alternate_settings') self.assertOutput( out, - "EXECUTE: noargs_command options=[('no_color', False), " - "('pythonpath', None), ('settings', None), ('traceback', False), " - "('verbosity', 1)]" + "EXECUTE: noargs_command options=[('force_color', False), " + "('no_color', False), ('pythonpath', None), ('settings', None), " + "('traceback', False), ('verbosity', 1)]" ) self.assertNoOutput(err) @@ -1017,9 +994,9 @@ def test_custom_command_output_color(self): out, err = self.run_manage(args) self.assertOutput( out, - "EXECUTE: noargs_command options=[('no_color', True), " - "('pythonpath', None), ('settings', 'alternate_settings'), " - "('traceback', False), ('verbosity', 1)]" + "EXECUTE: noargs_command options=[('force_color', False), " + "('no_color', True), ('pythonpath', None), ('settings', " + "'alternate_settings'), ('traceback', False), ('verbosity', 1)]" ) self.assertNoOutput(err) @@ -1160,9 +1137,7 @@ def tearDown(self): self.remove_settings('settings.py') def test_nonexistent_app(self): - """ manage.py check reports an error on a non-existent app in - INSTALLED_APPS """ - + """check reports an error on a nonexistent app in INSTALLED_APPS.""" self.write_settings( 'settings.py', apps=['admin_scriptz.broken_app'], @@ -1197,9 +1172,28 @@ def test_complex_app(self): 'django.contrib.admin.apps.SimpleAdminConfig', 'django.contrib.auth', 'django.contrib.contenttypes', + 'django.contrib.messages', ], sdict={ - 'DEBUG': True + 'DEBUG': True, + 'MIDDLEWARE': [ + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + ], + 'TEMPLATES': [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, + ], } ) args = ['check'] @@ -1291,13 +1285,11 @@ def test_warning_does_not_halt(self): class ManageRunserver(AdminScriptTestCase): def setUp(self): - from django.core.management.commands.runserver import Command - def monkey_run(*args, **options): return self.output = StringIO() - self.cmd = Command(stdout=self.output) + self.cmd = RunserverCommand(stdout=self.output) self.cmd.run = monkey_run def assertServerSettings(self, addr, port, ipv6=False, raw_ipv6=False): @@ -1339,6 +1331,18 @@ def test_runner_hostname_ipv6(self): call_command(self.cmd, addrport="test.domain.local:7000", use_ipv6=True) self.assertServerSettings('test.domain.local', '7000', ipv6=True) + def test_runner_custom_defaults(self): + self.cmd.default_addr = '0.0.0.0' + self.cmd.default_port = '5000' + call_command(self.cmd) + self.assertServerSettings('0.0.0.0', '5000') + + @unittest.skipUnless(socket.has_ipv6, "platform doesn't support IPv6") + def test_runner_custom_defaults_ipv6(self): + self.cmd.default_addr_ipv6 = '::' + call_command(self.cmd, use_ipv6=True) + self.assertServerSettings('::', '8000', ipv6=True, raw_ipv6=True) + def test_runner_ambiguous(self): # Only 4 characters, all of which could be in an ipv6 address call_command(self.cmd, addrport="beef:7654") @@ -1358,23 +1362,19 @@ def test_no_database(self): def test_readonly_database(self): """ - Ensure runserver.check_migrations doesn't choke when a database is read-only - (with possibly no django_migrations table). + runserver.check_migrations() doesn't choke when a database is read-only. """ - with mock.patch.object( - MigrationRecorder, 'ensure_schema', - side_effect=MigrationSchemaMissing()): + with mock.patch.object(MigrationRecorder, 'has_table', return_value=False): self.cmd.check_migrations() - # Check a warning is emitted - self.assertIn("Not checking migrations", self.output.getvalue()) + # You have # ... + self.assertIn('unapplied migration(s)', self.output.getvalue()) class ManageRunserverMigrationWarning(TestCase): def setUp(self): - from django.core.management.commands.runserver import Command self.stdout = StringIO() - self.runserver_command = Command(stdout=self.stdout) + self.runserver_command = RunserverCommand(stdout=self.stdout) @override_settings(INSTALLED_APPS=["admin_scripts.app_waiting_migration"]) def test_migration_warning_one_app(self): @@ -1416,9 +1416,8 @@ def test_empty_allowed_hosts_error(self): class ManageTestserver(AdminScriptTestCase): - from django.core.management.commands.testserver import Command as TestserverCommand - @mock.patch.object(TestserverCommand, 'handle') + @mock.patch.object(TestserverCommand, 'handle', return_value='') def test_testserver_handle_params(self, mock_handle): out = StringIO() call_command('testserver', 'blah.json', stdout=out) @@ -1426,7 +1425,33 @@ def test_testserver_handle_params(self, mock_handle): 'blah.json', stdout=out, settings=None, pythonpath=None, verbosity=1, traceback=False, addrport='', no_color=False, use_ipv6=False, - skip_checks=True, interactive=True, + skip_checks=True, interactive=True, force_color=False, + ) + + @mock.patch('django.db.connection.creation.create_test_db', return_value='test_db') + @mock.patch.object(LoaddataCommand, 'handle', return_value='') + @mock.patch.object(RunserverCommand, 'handle', return_value='') + def test_params_to_runserver(self, mock_runserver_handle, mock_loaddata_handle, mock_create_test_db): + out = StringIO() + call_command('testserver', 'blah.json', stdout=out) + mock_runserver_handle.assert_called_with( + addrport='', + force_color=False, + insecure_serving=False, + no_color=False, + pythonpath=None, + settings=None, + shutdown_message=( + "\nServer stopped.\nNote that the test database, 'test_db', " + "has not been deleted. You can explore it on your own." + ), + skip_checks=True, + traceback=False, + use_ipv6=False, + use_reloader=False, + use_static_handler=True, + use_threading=connection.features.test_db_allows_multiple_connections, + verbosity=1, ) @@ -1435,6 +1460,13 @@ def test_testserver_handle_params(self, mock_handle): # user-space commands are correctly handled - in particular, arguments to # the commands are correctly parsed and processed. ########################################################################## +class ColorCommand(BaseCommand): + requires_system_checks = False + + def handle(self, *args, **options): + self.stdout.write('Hello, world!', self.style.ERROR) + self.stderr.write('Hello, world!', self.style.ERROR) + class CommandTypes(AdminScriptTestCase): "Tests for the various types of base command types that can be defined." @@ -1492,6 +1524,13 @@ def test_specific_help(self): args = ['check', '--help'] out, err = self.run_manage(args) self.assertNoOutput(err) + # Command-specific options like --tag appear before options common to + # all commands like --version. + tag_location = out.find('--tag') + version_location = out.find('--version') + self.assertNotEqual(tag_location, -1) + self.assertNotEqual(version_location, -1) + self.assertLess(tag_location, version_location) self.assertOutput(out, "Checks the entire Django project for potential problems.") def test_color_style(self): @@ -1511,16 +1550,9 @@ def test_color_style(self): self.assertNotEqual(style.ERROR('Hello, world!'), 'Hello, world!') def test_command_color(self): - class Command(BaseCommand): - requires_system_checks = False - - def handle(self, *args, **options): - self.stdout.write('Hello, world!', self.style.ERROR) - self.stderr.write('Hello, world!', self.style.ERROR) - out = StringIO() err = StringIO() - command = Command(stdout=out, stderr=err) + command = ColorCommand(stdout=out, stderr=err) call_command(command) if color.supports_color(): self.assertIn('Hello, world!\n', out.getvalue()) @@ -1533,27 +1565,48 @@ def handle(self, *args, **options): def test_command_no_color(self): "--no-color prevent colorization of the output" - class Command(BaseCommand): - requires_system_checks = False - - def handle(self, *args, **options): - self.stdout.write('Hello, world!', self.style.ERROR) - self.stderr.write('Hello, world!', self.style.ERROR) - out = StringIO() err = StringIO() - command = Command(stdout=out, stderr=err, no_color=True) + command = ColorCommand(stdout=out, stderr=err, no_color=True) call_command(command) self.assertEqual(out.getvalue(), 'Hello, world!\n') self.assertEqual(err.getvalue(), 'Hello, world!\n') out = StringIO() err = StringIO() - command = Command(stdout=out, stderr=err) + command = ColorCommand(stdout=out, stderr=err) call_command(command, no_color=True) self.assertEqual(out.getvalue(), 'Hello, world!\n') self.assertEqual(err.getvalue(), 'Hello, world!\n') + def test_force_color_execute(self): + out = StringIO() + err = StringIO() + with mock.patch.object(sys.stdout, 'isatty', lambda: False): + command = ColorCommand(stdout=out, stderr=err) + call_command(command, force_color=True) + self.assertEqual(out.getvalue(), '\x1b[31;1mHello, world!\n\x1b[0m') + self.assertEqual(err.getvalue(), '\x1b[31;1mHello, world!\n\x1b[0m') + + def test_force_color_command_init(self): + out = StringIO() + err = StringIO() + with mock.patch.object(sys.stdout, 'isatty', lambda: False): + command = ColorCommand(stdout=out, stderr=err, force_color=True) + call_command(command) + self.assertEqual(out.getvalue(), '\x1b[31;1mHello, world!\n\x1b[0m') + self.assertEqual(err.getvalue(), '\x1b[31;1mHello, world!\n\x1b[0m') + + def test_no_color_force_color_mutually_exclusive_execute(self): + msg = "The --no-color and --force-color options can't be used together." + with self.assertRaisesMessage(CommandError, msg): + call_command(BaseCommand(), no_color=True, force_color=True) + + def test_no_color_force_color_mutually_exclusive_command_init(self): + msg = "'no_color' and 'force_color' can't be used together." + with self.assertRaisesMessage(CommandError, msg): + call_command(BaseCommand(no_color=True, force_color=True)) + def test_custom_stdout(self): class Command(BaseCommand): requires_system_checks = False @@ -1631,9 +1684,10 @@ def _test_base_command(self, args, labels, option_a="'1'", option_b="'2'"): expected_out = ( "EXECUTE:BaseCommand labels=%s, " - "options=[('no_color', False), ('option_a', %s), ('option_b', %s), " - "('option_c', '3'), ('pythonpath', None), ('settings', None), " - "('traceback', False), ('verbosity', 1)]") % (labels, option_a, option_b) + "options=[('force_color', False), ('no_color', False), " + "('option_a', %s), ('option_b', %s), ('option_c', '3'), " + "('pythonpath', None), ('settings', None), ('traceback', False), " + "('verbosity', 1)]") % (labels, option_a, option_b) self.assertNoOutput(err) self.assertOutput(out, expected_out) @@ -1707,9 +1761,9 @@ def test_noargs(self): self.assertNoOutput(err) self.assertOutput( out, - "EXECUTE: noargs_command options=[('no_color', False), " - "('pythonpath', None), ('settings', None), ('traceback', False), " - "('verbosity', 1)]" + "EXECUTE: noargs_command options=[('force_color', False), " + "('no_color', False), ('pythonpath', None), ('settings', None), " + "('traceback', False), ('verbosity', 1)]" ) def test_noargs_with_args(self): @@ -1726,8 +1780,9 @@ def test_app_command(self): self.assertOutput(out, "EXECUTE:AppCommand name=django.contrib.auth, options=") self.assertOutput( out, - ", options=[('no_color', False), ('pythonpath', None), " - "('settings', None), ('traceback', False), ('verbosity', 1)]" + ", options=[('force_color', False), ('no_color', False), " + "('pythonpath', None), ('settings', None), ('traceback', False), " + "('verbosity', 1)]" ) def test_app_command_no_apps(self): @@ -1744,14 +1799,16 @@ def test_app_command_multiple_apps(self): self.assertOutput(out, "EXECUTE:AppCommand name=django.contrib.auth, options=") self.assertOutput( out, - ", options=[('no_color', False), ('pythonpath', None), " - "('settings', None), ('traceback', False), ('verbosity', 1)]" + ", options=[('force_color', False), ('no_color', False), " + "('pythonpath', None), ('settings', None), ('traceback', False), " + "('verbosity', 1)]" ) self.assertOutput(out, "EXECUTE:AppCommand name=django.contrib.contenttypes, options=") self.assertOutput( out, - ", options=[('no_color', False), ('pythonpath', None), " - "('settings', None), ('traceback', False), ('verbosity', 1)]" + ", options=[('force_color', False), ('no_color', False), " + "('pythonpath', None), ('settings', None), ('traceback', False), " + "('verbosity', 1)]" ) def test_app_command_invalid_app_label(self): @@ -1773,8 +1830,9 @@ def test_label_command(self): self.assertNoOutput(err) self.assertOutput( out, - "EXECUTE:LabelCommand label=testlabel, options=[('no_color', False), " - "('pythonpath', None), ('settings', None), ('traceback', False), ('verbosity', 1)]" + "EXECUTE:LabelCommand label=testlabel, options=[('force_color', " + "False), ('no_color', False), ('pythonpath', None), ('settings', " + "None), ('traceback', False), ('verbosity', 1)]" ) def test_label_command_no_label(self): @@ -1790,13 +1848,15 @@ def test_label_command_multiple_label(self): self.assertNoOutput(err) self.assertOutput( out, - "EXECUTE:LabelCommand label=testlabel, options=[('no_color', False), " - "('pythonpath', None), ('settings', None), ('traceback', False), ('verbosity', 1)]" + "EXECUTE:LabelCommand label=testlabel, options=[('force_color', " + "False), ('no_color', False), ('pythonpath', None), " + "('settings', None), ('traceback', False), ('verbosity', 1)]" ) self.assertOutput( out, - "EXECUTE:LabelCommand label=anotherlabel, options=[('no_color', False), " - "('pythonpath', None), ('settings', None), ('traceback', False), ('verbosity', 1)]" + "EXECUTE:LabelCommand label=anotherlabel, options=[('force_color', " + "False), ('no_color', False), ('pythonpath', None), " + "('settings', None), ('traceback', False), ('verbosity', 1)]" ) @@ -1870,10 +1930,11 @@ def _test(self, args, option_b="'2'"): self.assertNoOutput(err) self.assertOutput( out, - "EXECUTE:BaseCommand labels=('testlabel',), options=[('no_color', False), " - "('option_a', 'x'), ('option_b', %s), ('option_c', '3'), " - "('pythonpath', None), ('settings', 'alternate_settings'), " - "('traceback', False), ('verbosity', 1)]" % option_b + "EXECUTE:BaseCommand labels=('testlabel',), options=[" + "('force_color', False), ('no_color', False), ('option_a', 'x'), " + "('option_b', %s), ('option_c', '3'), ('pythonpath', None), " + "('settings', 'alternate_settings'), ('traceback', False), " + "('verbosity', 1)]" % option_b ) @@ -1917,20 +1978,32 @@ def test_invalid_project_name(self): self.addCleanup(shutil.rmtree, testproject_dir, True) out, err = self.run_django_admin(args) - if PY2: - self.assertOutput( - err, - "Error: '%s' is not a valid project name. Please make " - "sure the name begins with a letter or underscore." % bad_name - ) - else: - self.assertOutput( - err, - "Error: '%s' is not a valid project name. Please make " - "sure the name is a valid identifier." % bad_name - ) + self.assertOutput( + err, + "Error: '%s' is not a valid project name. Please make " + "sure the name is a valid identifier." % bad_name + ) self.assertFalse(os.path.exists(testproject_dir)) + def test_importable_project_name(self): + """ + startproject validates that project name doesn't clash with existing + Python modules. + """ + bad_name = 'os' + args = ['startproject', bad_name] + testproject_dir = os.path.join(self.test_dir, bad_name) + self.addCleanup(shutil.rmtree, testproject_dir, True) + + out, err = self.run_django_admin(args) + self.assertOutput( + err, + "CommandError: 'os' conflicts with the name of an existing " + "Python module and cannot be used as a project name. Please try " + "another name." + ) + self.assertFalse(os.path.exists(testproject_dir)) + def test_simple_project_different_directory(self): "Make sure the startproject management command creates a project in a specific directory" args = ['startproject', 'testproject', 'othertestproject'] @@ -2053,7 +2126,7 @@ def test_custom_project_template_context_variables(self): self.assertNoOutput(err) test_manage_py = os.path.join(testproject_dir, 'manage.py') with open(test_manage_py, 'r') as fp: - content = force_text(fp.read()) + content = fp.read() self.assertIn("project_name = 'another_project'", content) self.assertIn("project_directory = '%s'" % testproject_dir, content) @@ -2111,6 +2184,43 @@ def test_custom_project_template_with_non_ascii_templates(self): 'üäö €']) +class StartApp(AdminScriptTestCase): + + def test_invalid_name(self): + """startapp validates that app name is a valid Python identifier.""" + for bad_name in ('7testproject', '../testproject'): + args = ['startapp', bad_name] + testproject_dir = os.path.join(self.test_dir, bad_name) + self.addCleanup(shutil.rmtree, testproject_dir, True) + + out, err = self.run_django_admin(args) + self.assertOutput( + err, + "CommandError: '{}' is not a valid app name. Please make " + "sure the name is a valid identifier.".format(bad_name) + ) + self.assertFalse(os.path.exists(testproject_dir)) + + def test_importable_name(self): + """ + startapp validates that app name doesn't clash with existing Python + modules. + """ + bad_name = 'os' + args = ['startapp', bad_name] + testproject_dir = os.path.join(self.test_dir, bad_name) + self.addCleanup(shutil.rmtree, testproject_dir, True) + + out, err = self.run_django_admin(args) + self.assertOutput( + err, + "CommandError: 'os' conflicts with the name of an existing " + "Python module and cannot be used as an app name. Please try " + "another name." + ) + self.assertFalse(os.path.exists(testproject_dir)) + + class DiffSettings(AdminScriptTestCase): """Tests for diffsettings management command.""" @@ -2123,6 +2233,11 @@ def test_basic(self): self.assertNoOutput(err) self.assertOutput(out, "FOO = 'bar' ###") + def test_settings_configured(self): + out, err = self.run_manage(['diffsettings'], configured_settings=True) + self.assertNoOutput(err) + self.assertOutput(out, 'CUSTOM = 1 ###\nDEBUG = True') + def test_all(self): """The all option also shows settings with the default value.""" self.write_settings('settings_to_diff.py', sdict={'STATIC_URL': 'None'}) @@ -2146,6 +2261,32 @@ def test_custom_default(self): self.assertNotInOutput(out, "FOO") self.assertOutput(out, "BAR = 'bar2'") + def test_unified(self): + """--output=unified emits settings diff in unified mode.""" + self.write_settings('settings_to_diff.py', sdict={'FOO': '"bar"'}) + self.addCleanup(self.remove_settings, 'settings_to_diff.py') + args = ['diffsettings', '--settings=settings_to_diff', '--output=unified'] + out, err = self.run_manage(args) + self.assertNoOutput(err) + self.assertOutput(out, "+ FOO = 'bar'") + self.assertOutput(out, "- SECRET_KEY = ''") + self.assertOutput(out, "+ SECRET_KEY = 'django_tests_secret_key'") + self.assertNotInOutput(out, " APPEND_SLASH = True") + + def test_unified_all(self): + """ + --output=unified --all emits settings diff in unified mode and includes + settings with the default value. + """ + self.write_settings('settings_to_diff.py', sdict={'FOO': '"bar"'}) + self.addCleanup(self.remove_settings, 'settings_to_diff.py') + args = ['diffsettings', '--settings=settings_to_diff', '--output=unified', '--all'] + out, err = self.run_manage(args) + self.assertNoOutput(err) + self.assertOutput(out, " APPEND_SLASH = True") + self.assertOutput(out, "+ FOO = 'bar'") + self.assertOutput(out, "- SECRET_KEY = ''") + class Dumpdata(AdminScriptTestCase): """Tests for dumpdata management command.""" @@ -2174,3 +2315,27 @@ def test_runs_django_admin(self): cmd_out, _ = self.run_django_admin(['--version']) mod_out, _ = self.run_test('-m', ['django', '--version']) self.assertEqual(mod_out, cmd_out) + + def test_program_name_in_help(self): + out, err = self.run_test('-m', ['django', 'help']) + self.assertOutput(out, "Type 'python -m django help ' for help on a specific subcommand.") + + +class DjangoAdminSuggestions(AdminScriptTestCase): + def setUp(self): + self.write_settings('settings.py') + + def tearDown(self): + self.remove_settings('settings.py') + + def test_suggestions(self): + args = ['rnserver', '--settings=test_project.settings'] + out, err = self.run_django_admin(args) + self.assertNoOutput(out) + self.assertOutput(err, "Unknown command: 'rnserver'. Did you mean runserver?") + + def test_no_suggestions(self): + args = ['abcdef', '--settings=test_project.settings'] + out, err = self.run_django_admin(args) + self.assertNoOutput(out) + self.assertNotInOutput(err, 'Did you mean') diff --git a/tests/admin_scripts/urls.py b/tests/admin_scripts/urls.py index d258641fbe31..b5bb44392636 100644 --- a/tests/admin_scripts/urls.py +++ b/tests/admin_scripts/urls.py @@ -1,12 +1,11 @@ import os -from django.conf.urls import url -from django.utils._os import upath +from django.urls import path from django.views.static import serve -here = os.path.dirname(upath(__file__)) +here = os.path.dirname(__file__) urlpatterns = [ - url(r'^custom_templates/(?P.*)$', serve, { - 'document_root': os.path.join(here, 'custom_templates')}), + path('custom_templates/', serve, { + 'document_root': os.path.join(here, 'custom_templates')}), ] diff --git a/tests/admin_utils/models.py b/tests/admin_utils/models.py index 87060cbff8de..7b9c08a2f732 100644 --- a/tests/admin_utils/models.py +++ b/tests/admin_utils/models.py @@ -1,10 +1,7 @@ from django.db import models -from django.utils import six -from django.utils.encoding import python_2_unicode_compatible -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ -@python_2_unicode_compatible class Site(models.Model): domain = models.CharField(max_length=100) @@ -21,6 +18,9 @@ class Article(models.Model): hist = models.CharField(max_length=100, verbose_name=_("History")) created = models.DateTimeField(null=True) + def __str__(self): + return self.title + def test_from_model(self): return "nothing" @@ -34,13 +34,12 @@ class Meta: proxy = True -@python_2_unicode_compatible class Count(models.Model): num = models.PositiveSmallIntegerField() parent = models.ForeignKey('self', models.CASCADE, null=True) def __str__(self): - return six.text_type(self.num) + return str(self.num) class Event(models.Model): diff --git a/tests/admin_utils/test_logentry.py b/tests/admin_utils/test_logentry.py index ba0e3c1bfd99..b56b209433d1 100644 --- a/tests/admin_utils/test_logentry.py +++ b/tests/admin_utils/test_logentry.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import json from datetime import datetime @@ -10,8 +7,7 @@ from django.contrib.contenttypes.models import ContentType from django.test import TestCase, override_settings from django.urls import reverse -from django.utils import six, translation -from django.utils.encoding import force_bytes +from django.utils import translation from django.utils.html import escape from .models import Article, ArticleProxy, Site @@ -59,7 +55,7 @@ def test_logentry_change_message(self): logentry = LogEntry.objects.filter(content_type__model__iexact='article').latest('id') self.assertEqual(logentry.get_change_message(), 'Changed title and hist.') with translation.override('fr'): - self.assertEqual(logentry.get_change_message(), 'Title et hist modifié(s).') + self.assertEqual(logentry.get_change_message(), 'Modification de title et hist.') add_url = reverse('admin:admin_utils_article_add') post_data['title'] = 'New' @@ -70,6 +66,11 @@ def test_logentry_change_message(self): with translation.override('fr'): self.assertEqual(logentry.get_change_message(), 'Ajout.') + def test_logentry_change_message_not_json(self): + """LogEntry.change_message was a string before Django 1.10.""" + logentry = LogEntry(change_message='non-JSON string') + self.assertEqual(logentry.get_change_message(), logentry.change_message) + @override_settings(USE_L10N=True) def test_logentry_change_message_localized_datetime_input(self): """ @@ -123,22 +124,23 @@ def test_logentry_change_message_formsets(self): json.loads(logentry.change_message), [ {"changed": {"fields": ["domain"]}}, - {"added": {"object": "Article object", "name": "article"}}, - {"changed": {"fields": ["title"], "object": "Article object", "name": "article"}}, - {"deleted": {"object": "Article object", "name": "article"}}, + {"added": {"object": "Added article", "name": "article"}}, + {"changed": {"fields": ["title"], "object": "Changed Title", "name": "article"}}, + {"deleted": {"object": "Title second article", "name": "article"}}, ] ) self.assertEqual( logentry.get_change_message(), - 'Changed domain. Added article "Article object". ' - 'Changed title for article "Article object". Deleted article "Article object".' + 'Changed domain. Added article "Added article". ' + 'Changed title for article "Changed Title". Deleted article "Title second article".' ) with translation.override('fr'): self.assertEqual( logentry.get_change_message(), - "Domain modifié(s). Article « Article object » ajouté. " - "Title modifié(s) pour l'objet article « Article object ». Article « Article object » supprimé." + "Modification de domain. Ajout de article « Added article ». " + "Modification de title pour l'objet article « Changed Title ». " + "Suppression de article « Title second article »." ) def test_logentry_get_edited_object(self): @@ -153,31 +155,35 @@ def test_logentry_get_edited_object(self): def test_logentry_get_admin_url(self): """ LogEntry.get_admin_url returns a URL to edit the entry's object or - None for non-existent (possibly deleted) models. + None for nonexistent (possibly deleted) models. """ logentry = LogEntry.objects.get(content_type__model__iexact='article') expected_url = reverse('admin:admin_utils_article_change', args=(quote(self.a1.pk),)) self.assertEqual(logentry.get_admin_url(), expected_url) self.assertIn('article/%d/change/' % self.a1.pk, logentry.get_admin_url()) - logentry.content_type.model = "non-existent" + logentry.content_type.model = "nonexistent" self.assertIsNone(logentry.get_admin_url()) def test_logentry_unicode(self): log_entry = LogEntry() log_entry.action_flag = ADDITION - self.assertTrue(six.text_type(log_entry).startswith('Added ')) + self.assertTrue(str(log_entry).startswith('Added ')) log_entry.action_flag = CHANGE - self.assertTrue(six.text_type(log_entry).startswith('Changed ')) + self.assertTrue(str(log_entry).startswith('Changed ')) log_entry.action_flag = DELETION - self.assertTrue(six.text_type(log_entry).startswith('Deleted ')) + self.assertTrue(str(log_entry).startswith('Deleted ')) # Make sure custom action_flags works log_entry.action_flag = 4 - self.assertEqual(six.text_type(log_entry), 'LogEntry Object') + self.assertEqual(str(log_entry), 'LogEntry Object') + + def test_logentry_repr(self): + logentry = LogEntry.objects.first() + self.assertEqual(repr(logentry), str(logentry.action_time)) def test_log_action(self): content_type_pk = ContentType.objects.get_for_model(Article).pk @@ -204,9 +210,10 @@ def test_recentactions_without_content_type(self): logentry.content_type = None logentry.save() - counted_presence_before = response.content.count(force_bytes(should_contain)) + should_contain = should_contain.encode() + counted_presence_before = response.content.count(should_contain) response = self.client.get(reverse('admin:index')) - counted_presence_after = response.content.count(force_bytes(should_contain)) + counted_presence_after = response.content.count(should_contain) self.assertEqual(counted_presence_before - 1, counted_presence_after) def test_proxy_model_content_type_is_used_for_log_entries(self): @@ -246,3 +253,10 @@ def test_proxy_model_content_type_is_used_for_log_entries(self): proxy_delete_log = LogEntry.objects.latest('id') self.assertEqual(proxy_delete_log.action_flag, DELETION) self.assertEqual(proxy_delete_log.content_type, proxy_content_type) + + def test_action_flag_choices(self): + tests = ((1, 'Addition'), (2, 'Change'), (3, 'Deletion')) + for action_flag, display_name in tests: + with self.subTest(action_flag=action_flag): + log = LogEntry(action_flag=action_flag) + self.assertEqual(log.get_action_flag_display(), display_name) diff --git a/tests/admin_utils/tests.py b/tests/admin_utils/tests.py index 966a1f11f2d8..463ba9556d49 100644 --- a/tests/admin_utils/tests.py +++ b/tests/admin_utils/tests.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from datetime import datetime from decimal import Decimal @@ -107,7 +105,7 @@ def test_values_from_lookup_field(self): SIMPLE_FUNCTION = 'function' INSTANCE_ATTRIBUTE = 'attr' - class MockModelAdmin(object): + class MockModelAdmin: def get_admin_value(self, obj): return ADMIN_METHOD @@ -165,6 +163,10 @@ def test_null_display_for_field(self): # Regression test for #13071: NullBooleanField has special # handling. display_value = display_for_field(None, models.NullBooleanField(), self.empty_value) + expected = 'None' % settings.STATIC_URL + self.assertHTMLEqual(display_value, expected) + + display_value = display_for_field(None, models.BooleanField(null=True), self.empty_value) expected = 'None' % settings.STATIC_URL self.assertHTMLEqual(display_value, expected) @@ -202,6 +204,19 @@ def test_list_display_for_value(self): display_value = display_for_value([1, 2, 'buckle', 'my', 'shoe'], self.empty_value) self.assertEqual(display_value, '1, 2, buckle, my, shoe') + @override_settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True) + def test_list_display_for_value_boolean(self): + self.assertEqual( + display_for_value(True, '', boolean=True), + 'True' + ) + self.assertEqual( + display_for_value(False, '', boolean=True), + 'False' + ) + self.assertEqual(display_for_value(True, ''), 'True') + self.assertEqual(display_for_value(False, ''), 'False') + def test_label_for_field(self): """ Tests for label_for_field @@ -219,16 +234,12 @@ def test_label_for_field(self): ("History", None) ) - self.assertEqual( - label_for_field("__unicode__", Article), - "article" - ) self.assertEqual( label_for_field("__str__", Article), - str("article") + "article" ) - with self.assertRaises(AttributeError): + with self.assertRaisesMessage(AttributeError, "Unable to lookup 'unknown' on Article"): label_for_field("unknown", Article) def test_callable(obj): @@ -261,7 +272,7 @@ def test_callable(obj): ) self.assertEqual(label_for_field('site_id', Article), 'Site id') - class MockModelAdmin(object): + class MockModelAdmin: def test_from_model(self, obj): return "nothing" test_from_model.short_description = "not Really the Model" @@ -275,10 +286,26 @@ def test_from_model(self, obj): ("not Really the Model", MockModelAdmin.test_from_model) ) + def test_label_for_field_form_argument(self): + class ArticleForm(forms.ModelForm): + extra_form_field = forms.BooleanField() + + class Meta: + fields = '__all__' + model = Article + + self.assertEqual( + label_for_field('extra_form_field', Article, form=ArticleForm()), + 'Extra form field' + ) + msg = "Unable to lookup 'nonexistent' on Article or ArticleForm" + with self.assertRaisesMessage(AttributeError, msg): + label_for_field('nonexistent', Article, form=ArticleForm()), + def test_label_for_property(self): # NOTE: cannot use @property decorator, because of # AttributeError: 'property' object has no attribute 'short_description' - class MockModelAdmin(object): + class MockModelAdmin: def my_property(self): return "this if from property" my_property.short_description = 'property short description' diff --git a/tests/admin_utils/urls.py b/tests/admin_utils/urls.py index b3b865f8bc04..2e472fc57568 100644 --- a/tests/admin_utils/urls.py +++ b/tests/admin_utils/urls.py @@ -1,7 +1,7 @@ -from django.conf.urls import url +from django.urls import path from .admin import site urlpatterns = [ - url(r'^test_admin/admin/', site.urls), + path('test_admin/admin/', site.urls), ] diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py index e8a1cf3bff0e..a18fb363aacb 100644 --- a/tests/admin_views/admin.py +++ b/tests/admin_views/admin.py @@ -1,12 +1,10 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - +import datetime import os import tempfile +from io import StringIO from wsgiref.util import FileWrapper from django import forms -from django.conf.urls import url from django.contrib import admin from django.contrib.admin import BooleanFieldListFilter from django.contrib.admin.views.main import ChangeList @@ -18,14 +16,14 @@ from django.db import models from django.forms.models import BaseModelFormSet from django.http import HttpResponse, StreamingHttpResponse +from django.urls import path from django.utils.html import format_html from django.utils.safestring import mark_safe -from django.utils.six import StringIO from .forms import MediaActionForm from .models import ( Actor, AdminOrderedAdminMethod, AdminOrderedCallable, AdminOrderedField, - AdminOrderedModelMethod, Album, Answer, Article, BarAccount, Book, + AdminOrderedModelMethod, Album, Answer, Answer2, Article, BarAccount, Book, Bookmark, Category, Chapter, ChapterXtra1, Child, ChildOfReferer, Choice, City, Collector, Color, Color2, ComplexSortedPerson, CoverLetter, CustomArticle, CyclicOne, CyclicTwo, DependentChild, DooHickey, EmptyModel, @@ -38,14 +36,15 @@ OtherStory, Paper, Parent, ParentWithDependentChildren, ParentWithUUIDPK, Person, Persona, Picture, Pizza, Plot, PlotDetails, PlotProxy, PluggableSearchPerson, Podcast, Post, PrePopulatedPost, - PrePopulatedPostLargeSlug, PrePopulatedSubPost, Promo, Question, Recipe, - Recommendation, Recommender, ReferencedByGenRel, ReferencedByInline, - ReferencedByParent, RelatedPrepopulated, RelatedWithUUIDPKModel, Report, - Reservation, Restaurant, RowLevelChangePermissionModel, Section, - ShortMessage, Simple, Sketch, State, Story, StumpJoke, Subscriber, - SuperVillain, Telegram, Thing, Topping, UnchangeableObject, - UndeletableObject, UnorderedObject, UserMessenger, Villain, Vodcast, - Whatsit, Widget, Worker, WorkHour, + PrePopulatedPostLargeSlug, PrePopulatedSubPost, Promo, Question, + ReadablePizza, ReadOnlyPizza, Recipe, Recommendation, Recommender, + ReferencedByGenRel, ReferencedByInline, ReferencedByParent, + RelatedPrepopulated, RelatedWithUUIDPKModel, Report, Reservation, + Restaurant, RowLevelChangePermissionModel, Section, ShortMessage, Simple, + Sketch, State, Story, StumpJoke, Subscriber, SuperVillain, Telegram, Thing, + Topping, UnchangeableObject, UndeletableObject, UnorderedObject, + UserMessenger, UserProxy, Villain, Vodcast, Whatsit, Widget, Worker, + WorkHour, ) @@ -82,26 +81,40 @@ class ChapterInline(admin.TabularInline): class ChapterXtra1Admin(admin.ModelAdmin): - list_filter = ('chap', - 'chap__title', - 'chap__book', - 'chap__book__name', - 'chap__book__promo', - 'chap__book__promo__name',) + list_filter = ( + 'chap', + 'chap__title', + 'chap__book', + 'chap__book__name', + 'chap__book__promo', + 'chap__book__promo__name', + 'guest_author__promo__book', + ) + + +class ArticleForm(forms.ModelForm): + extra_form_field = forms.BooleanField(required=False) + + class Meta: + fields = '__all__' + model = Article class ArticleAdmin(admin.ModelAdmin): list_display = ( 'content', 'date', callable_year, 'model_year', 'modeladmin_year', 'model_year_reversed', 'section', lambda obj: obj.title, + 'order_by_expression', ) list_editable = ('section',) list_filter = ('date', 'section') + autocomplete_fields = ('section',) view_on_site = False + form = ArticleForm fieldsets = ( ('Some fields', { 'classes': ('collapse',), - 'fields': ('title', 'content') + 'fields': ('title', 'content', 'extra_form_field'), }), ('Some other fields', { 'classes': ('wide',), @@ -109,12 +122,14 @@ class ArticleAdmin(admin.ModelAdmin): }) ) + def order_by_expression(self, obj): + return obj.model_year + # This ordering isn't particularly useful but shows that expressions can + # be used for admin_order_field. + order_by_expression.admin_order_field = models.F('date') + datetime.timedelta(days=3) + def changelist_view(self, request): - return super(ArticleAdmin, self).changelist_view( - request, extra_context={ - 'extra_var': 'Hello!' - } - ) + return super().changelist_view(request, extra_context={'extra_var': 'Hello!'}) def modeladmin_year(self, obj): return obj.date.year @@ -128,7 +143,7 @@ def delete_model(self, request, obj): 'from@example.com', ['to@example.com'] ).send() - return super(ArticleAdmin, self).delete_model(request, obj) + return super().delete_model(request, obj) def save_model(self, request, obj, form, change=True): EmailMessage( @@ -137,7 +152,7 @@ def save_model(self, request, obj, form, change=True): 'from@example.com', ['to@example.com'] ).send() - return super(ArticleAdmin, self).save_model(request, obj, form, change) + return super().save_model(request, obj, form, change) class ArticleAdmin2(admin.ModelAdmin): @@ -151,6 +166,10 @@ def has_change_permission(self, request, obj=None): """ Only allow changing objects with even id number """ return request.user.is_staff and (obj is not None) and (obj.id % 2 == 0) + def has_view_permission(self, request, obj=None): + """Only allow viewing objects if id is a multiple of 3.""" + return request.user.is_staff and obj is not None and obj.id % 3 == 0 + class CustomArticleAdmin(admin.ModelAdmin): """ @@ -165,15 +184,11 @@ class CustomArticleAdmin(admin.ModelAdmin): popup_response_template = 'custom_admin/popup_response.html' def changelist_view(self, request): - return super(CustomArticleAdmin, self).changelist_view( - request, extra_context={ - 'extra_var': 'Hello!' - } - ) + return super().changelist_view(request, extra_context={'extra_var': 'Hello!'}) class ThingAdmin(admin.ModelAdmin): - list_filter = ('color__warm', 'color__value', 'pub_date',) + list_filter = ('color', 'color__warm', 'color__value', 'pub_date') class InquisitionAdmin(admin.ModelAdmin): @@ -210,12 +225,12 @@ class PersonAdmin(admin.ModelAdmin): save_as = True def get_changelist_formset(self, request, **kwargs): - return super(PersonAdmin, self).get_changelist_formset(request, formset=BasePersonModelFormSet, **kwargs) + return super().get_changelist_formset(request, formset=BasePersonModelFormSet, **kwargs) def get_queryset(self, request): # Order by a field that isn't in list display, to be able to test # whether ordering is preserved. - return super(PersonAdmin, self).get_queryset(request).order_by('age') + return super().get_queryset(request).order_by('age') class FooAccountAdmin(admin.StackedInline): @@ -239,6 +254,10 @@ class SubscriberAdmin(admin.ModelAdmin): actions = ['mail_admin'] action_form = MediaActionForm + def delete_queryset(self, request, queryset): + SubscriberAdmin.overridden = True + super().delete_queryset(request, queryset) + def mail_admin(self, request, selected): EmailMessage( 'Greetings from a ModelAdmin action', @@ -277,8 +296,7 @@ def download(modeladmin, request, selected): def no_perm(modeladmin, request, selected): - return HttpResponse(content='No permission to perform this action', - status=403) + return HttpResponse(content='No permission to perform this action', status=403) no_perm.short_description = 'No permission to run' @@ -315,7 +333,7 @@ class ParentAdmin(admin.ModelAdmin): list_editable = ('name',) def save_related(self, request, form, formsets, change): - super(ParentAdmin, self).save_related(request, form, formsets, change) + super().save_related(request, form, formsets, change) first_name, last_name = form.instance.name.split() for child in form.instance.child_set.all(): if len(child.name.split()) < 2: @@ -325,7 +343,7 @@ def save_related(self, request, form, formsets, change): class EmptyModelAdmin(admin.ModelAdmin): def get_queryset(self, request): - return super(EmptyModelAdmin, self).get_queryset(request).filter(pk__gt=1) + return super().get_queryset(request).filter(pk__gt=1) class OldSubscriberAdmin(admin.ModelAdmin): @@ -442,12 +460,19 @@ def get_prepopulated_fields(self, request, obj=None): return self.prepopulated_fields +class PrePopulatedPostReadOnlyAdmin(admin.ModelAdmin): + prepopulated_fields = {'slug': ('title',)} + + def has_change_permission(self, *args, **kwargs): + return False + + class PostAdmin(admin.ModelAdmin): list_display = ['title', 'public'] readonly_fields = ( 'posted', 'awesomeness_level', 'coolness', 'value', 'multiline', 'multiline_html', lambda obj: "foo", - 'multiline_html_allow_tags', 'readonly_content', + 'readonly_content', ) inlines = [ @@ -470,10 +495,6 @@ def multiline(self, instance): def multiline_html(self, instance): return mark_safe("Multiline
      \nhtml
      \ncontent") - def multiline_html_allow_tags(self, instance): - return "Multiline
      html
      content
      with allow tags" - multiline_html_allow_tags.allow_tags = True - class FieldOverridePostForm(forms.ModelForm): model = FieldOverridePost @@ -509,6 +530,23 @@ class PizzaAdmin(admin.ModelAdmin): readonly_fields = ('toppings',) +class StudentAdmin(admin.ModelAdmin): + search_fields = ('name',) + + +class ReadOnlyPizzaAdmin(admin.ModelAdmin): + readonly_fields = ('name', 'toppings') + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return True + + def has_delete_permission(self, request, obj=None): + return True + + class WorkHourAdmin(admin.ModelAdmin): list_display = ('datum', 'employee') list_filter = ('employee',) @@ -528,7 +566,7 @@ class CoverLetterAdmin(admin.ModelAdmin): """ def get_queryset(self, request): - return super(CoverLetterAdmin, self).get_queryset(request).defer('date_written') + return super().get_queryset(request).defer('date_written') class PaperAdmin(admin.ModelAdmin): @@ -540,7 +578,7 @@ class PaperAdmin(admin.ModelAdmin): """ def get_queryset(self, request): - return super(PaperAdmin, self).get_queryset(request).only('title') + return super().get_queryset(request).only('title') class ShortMessageAdmin(admin.ModelAdmin): @@ -552,7 +590,7 @@ class ShortMessageAdmin(admin.ModelAdmin): """ def get_queryset(self, request): - return super(ShortMessageAdmin, self).get_queryset(request).defer('timestamp') + return super().get_queryset(request).defer('timestamp') class TelegramAdmin(admin.ModelAdmin): @@ -564,7 +602,7 @@ class TelegramAdmin(admin.ModelAdmin): """ def get_queryset(self, request): - return super(TelegramAdmin, self).get_queryset(request).only('title') + return super().get_queryset(request).only('title') class StoryForm(forms.ModelForm): @@ -575,7 +613,7 @@ class Meta: class StoryAdmin(admin.ModelAdmin): list_display = ('id', 'title', 'content') list_display_links = ('title',) # 'id' not in list_display_links - list_editable = ('content', ) + list_editable = ('content',) form = StoryForm ordering = ['-id'] @@ -583,7 +621,7 @@ class StoryAdmin(admin.ModelAdmin): class OtherStoryAdmin(admin.ModelAdmin): list_display = ('id', 'title', 'content') list_display_links = ('title', 'id') # 'id' in list_display_links - list_editable = ('content', ) + list_editable = ('content',) ordering = ['-id'] @@ -601,9 +639,7 @@ class PluggableSearchPersonAdmin(admin.ModelAdmin): search_fields = ('name',) def get_search_results(self, request, queryset, search_term): - queryset, use_distinct = super(PluggableSearchPersonAdmin, self).get_search_results( - request, queryset, search_term - ) + queryset, use_distinct = super().get_search_results(request, queryset, search_term) try: search_term_as_int = int(search_term) except ValueError: @@ -617,6 +653,16 @@ class AlbumAdmin(admin.ModelAdmin): list_filter = ['title'] +class QuestionAdmin(admin.ModelAdmin): + ordering = ['-posted'] + search_fields = ['question'] + autocomplete_fields = ['related_questions'] + + +class AnswerAdmin(admin.ModelAdmin): + autocomplete_fields = ['question'] + + class PrePopulatedPostLargeSlugAdmin(admin.ModelAdmin): prepopulated_fields = { 'slug': ('title',) @@ -659,11 +705,7 @@ def extra(self, request): def get_urls(self): # Corner case: Don't call parent implementation - return [ - url(r'^extra/$', - self.extra, - name='cable_extra'), - ] + return [path('extra/', self.extra, name='cable_extra')] class CustomTemplateBooleanFieldListFilter(BooleanFieldListFilter): @@ -678,25 +720,41 @@ class CustomTemplateFilterColorAdmin(admin.ModelAdmin): class RelatedPrepopulatedInline1(admin.StackedInline): fieldsets = ( (None, { - 'fields': (('pubdate', 'status'), ('name', 'slug1', 'slug2',),) + 'fields': ( + ('fk', 'm2m'), + ('pubdate', 'status'), + ('name', 'slug1', 'slug2',), + ), }), ) formfield_overrides = {models.CharField: {'strip': False}} model = RelatedPrepopulated extra = 1 - prepopulated_fields = {'slug1': ['name', 'pubdate'], - 'slug2': ['status', 'name']} + autocomplete_fields = ['fk', 'm2m'] + prepopulated_fields = { + 'slug1': ['name', 'pubdate'], + 'slug2': ['status', 'name'], + } class RelatedPrepopulatedInline2(admin.TabularInline): model = RelatedPrepopulated extra = 1 - prepopulated_fields = {'slug1': ['name', 'pubdate'], - 'slug2': ['status', 'name']} + autocomplete_fields = ['fk', 'm2m'] + prepopulated_fields = { + 'slug1': ['name', 'pubdate'], + 'slug2': ['status', 'name'], + } + + +class RelatedPrepopulatedInline3(admin.TabularInline): + model = RelatedPrepopulated + extra = 0 + autocomplete_fields = ['fk', 'm2m'] class MainPrepopulatedAdmin(admin.ModelAdmin): - inlines = [RelatedPrepopulatedInline1, RelatedPrepopulatedInline2] + inlines = [RelatedPrepopulatedInline1, RelatedPrepopulatedInline2, RelatedPrepopulatedInline3] fieldsets = ( (None, { 'fields': (('pubdate', 'status'), ('name', 'slug1', 'slug2', 'slug3')) @@ -720,13 +778,13 @@ class UnorderedObjectAdmin(admin.ModelAdmin): class UndeletableObjectAdmin(admin.ModelAdmin): def change_view(self, *args, **kwargs): kwargs['extra_context'] = {'show_delete': False} - return super(UndeletableObjectAdmin, self).change_view(*args, **kwargs) + return super().change_view(*args, **kwargs) class UnchangeableObjectAdmin(admin.ModelAdmin): def get_urls(self): # Disable change_view, but leave other urls untouched - urlpatterns = super(UnchangeableObjectAdmin, self).get_urls() + urlpatterns = super().get_urls() return [p for p in urlpatterns if p.name and not p.name.endswith("_change")] @@ -735,7 +793,7 @@ def callable_on_unknown(obj): class AttributeErrorRaisingAdmin(admin.ModelAdmin): - list_display = [callable_on_unknown, ] + list_display = [callable_on_unknown] class CustomManagerAdmin(admin.ModelAdmin): @@ -782,7 +840,7 @@ def clean(self): if parent.family_name and parent.family_name != self.cleaned_data.get('family_name'): raise ValidationError("Children must share a family name with their parents " + "in this contrived test case") - return super(DependentChildAdminForm, self).clean() + return super().clean() class DependentChildInline(admin.TabularInline): @@ -890,25 +948,28 @@ class GetFormsetsArgumentCheckingAdmin(admin.ModelAdmin): def add_view(self, request, *args, **kwargs): request.is_add_view = True - return super(GetFormsetsArgumentCheckingAdmin, self).add_view(request, *args, **kwargs) + return super().add_view(request, *args, **kwargs) def change_view(self, request, *args, **kwargs): request.is_add_view = False - return super(GetFormsetsArgumentCheckingAdmin, self).change_view(request, *args, **kwargs) + return super().change_view(request, *args, **kwargs) def get_formsets_with_inlines(self, request, obj=None): if request.is_add_view and obj is not None: raise Exception("'obj' passed to get_formsets_with_inlines wasn't None during add_view") if not request.is_add_view and obj is None: raise Exception("'obj' passed to get_formsets_with_inlines was None during change_view") - return super(GetFormsetsArgumentCheckingAdmin, self).get_formsets_with_inlines(request, obj) + return super().get_formsets_with_inlines(request, obj) site = admin.AdminSite(name="admin") site.site_url = '/my-site-url/' site.register(Article, ArticleAdmin) site.register(CustomArticle, CustomArticleAdmin) -site.register(Section, save_as=True, inlines=[ArticleInline], readonly_fields=['name_property']) +site.register( + Section, save_as=True, inlines=[ArticleInline], + readonly_fields=['name_property'], search_fields=['name'], +) site.register(ModelWithStringPrimaryKey) site.register(Color) site.register(Thing, ThingAdmin) @@ -970,6 +1031,7 @@ def get_formsets_with_inlines(self, request, obj=None): site.register(ReferencedByGenRel) site.register(GenRelReference) site.register(ParentWithUUIDPK) +site.register(RelatedPrepopulated, search_fields=['name']) site.register(RelatedWithUUIDPKModel) # We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2. @@ -978,17 +1040,19 @@ def get_formsets_with_inlines(self, request, obj=None): # related ForeignKey object not registered in admin # related OneToOne object registered in admin # related OneToOne object not registered in admin -# when deleting Book so as exercise all four troublesome (w.r.t escaping -# and calling force_text to avoid problems on Python 2.3) paths through +# when deleting Book so as exercise all four paths through # contrib.admin.utils's get_deleted_objects function. site.register(Book, inlines=[ChapterInline]) site.register(Promo) site.register(ChapterXtra1, ChapterXtra1Admin) site.register(Pizza, PizzaAdmin) +site.register(ReadOnlyPizza, ReadOnlyPizzaAdmin) +site.register(ReadablePizza) site.register(Topping, ToppingAdmin) site.register(Album, AlbumAdmin) -site.register(Question) -site.register(Answer, date_hierarchy='question__posted') +site.register(Question, QuestionAdmin) +site.register(Answer, AnswerAdmin, date_hierarchy='question__posted') +site.register(Answer2, date_hierarchy='question__expires') site.register(PrePopulatedPost, PrePopulatedPostAdmin) site.register(ComplexSortedPerson, ComplexSortedPersonAdmin) site.register(FilteredManager, CustomManagerAdmin) @@ -1012,6 +1076,7 @@ def get_formsets_with_inlines(self, request, obj=None): site.register(NotReferenced) site.register(ExplicitlyProvidedPK, GetFormsetsArgumentCheckingAdmin) site.register(ImplicitlyGeneratedPK, GetFormsetsArgumentCheckingAdmin) +site.register(UserProxy) # Register core models we need in our tests site.register(User, UserAdmin) @@ -1033,3 +1098,54 @@ def get_formsets_with_inlines(self, request, obj=None): site7 = admin.AdminSite(name="admin7") site7.register(Article, ArticleAdmin2) site7.register(Section) +site7.register(PrePopulatedPost, PrePopulatedPostReadOnlyAdmin) + + +# Used to test ModelAdmin.sortable_by and get_sortable_by(). +class ArticleAdmin6(admin.ModelAdmin): + list_display = ( + 'content', 'date', callable_year, 'model_year', 'modeladmin_year', + 'model_year_reversed', 'section', + ) + sortable_by = ('date', callable_year) + + def modeladmin_year(self, obj): + return obj.date.year + modeladmin_year.admin_order_field = 'date' + + +class ActorAdmin6(admin.ModelAdmin): + list_display = ('name', 'age') + sortable_by = ('name',) + + def get_sortable_by(self, request): + return ('age',) + + +class ChapterAdmin6(admin.ModelAdmin): + list_display = ('title', 'book') + sortable_by = () + + +class ColorAdmin6(admin.ModelAdmin): + list_display = ('value',) + + def get_sortable_by(self, request): + return () + + +site6 = admin.AdminSite(name='admin6') +site6.register(Article, ArticleAdmin6) +site6.register(Actor, ActorAdmin6) +site6.register(Chapter, ChapterAdmin6) +site6.register(Color, ColorAdmin6) + + +class ArticleAdmin9(admin.ModelAdmin): + def has_change_permission(self, request, obj=None): + # Simulate that the user can't change a specific object. + return obj is None + + +site9 = admin.AdminSite(name='admin9') +site9.register(Article, ArticleAdmin9) diff --git a/tests/admin_views/custom_has_permission_admin.py b/tests/admin_views/custom_has_permission_admin.py index a578895b5a75..0c774ef57372 100644 --- a/tests/admin_views/custom_has_permission_admin.py +++ b/tests/admin_views/custom_has_permission_admin.py @@ -1,8 +1,6 @@ """ A custom AdminSite for AdminViewPermissionsTest.test_login_has_permission(). """ -from __future__ import unicode_literals - from django.contrib import admin from django.contrib.auth import get_permission_codename from django.contrib.auth.forms import AuthenticationForm diff --git a/tests/admin_views/customadmin.py b/tests/admin_views/customadmin.py index 644bbedae77e..a9d8a060b9c7 100644 --- a/tests/admin_views/customadmin.py +++ b/tests/admin_views/customadmin.py @@ -1,13 +1,11 @@ """ A second, custom AdminSite -- see tests.CustomAdminSiteTests. """ -from __future__ import unicode_literals - -from django.conf.urls import url from django.contrib import admin from django.contrib.auth.admin import UserAdmin from django.contrib.auth.models import User from django.http import HttpResponse +from django.urls import path from . import admin as base_admin, forms, models @@ -23,24 +21,24 @@ class Admin2(admin.AdminSite): # A custom index view. def index(self, request, extra_context=None): - return super(Admin2, self).index(request, {'foo': '*bar*'}) + return super().index(request, {'foo': '*bar*'}) def get_urls(self): return [ - url(r'^my_view/$', self.admin_view(self.my_view), name='my_view'), - ] + super(Admin2, self).get_urls() + path('my_view/', self.admin_view(self.my_view), name='my_view'), + ] + super().get_urls() def my_view(self, request): return HttpResponse("Django is a magical pony!") def password_change(self, request, extra_context=None): - return super(Admin2, self).password_change(request, {'spam': 'eggs'}) + return super().password_change(request, {'spam': 'eggs'}) class UserLimitedAdmin(UserAdmin): # used for testing password change on a user not in queryset def get_queryset(self, request): - qs = super(UserLimitedAdmin, self).get_queryset(request) + qs = super().get_queryset(request) return qs.filter(is_superuser=False) @@ -48,10 +46,16 @@ class CustomPwdTemplateUserAdmin(UserAdmin): change_user_password_template = ['admin/auth/user/change_password.html'] # a list, to test fix for #18697 +class BookAdmin(admin.ModelAdmin): + def get_deleted_objects(self, objs, request): + return ['a deletable object'], {'books': 1}, set(), [] + + site = Admin2(name="admin2") site.register(models.Article, base_admin.ArticleAdmin) -site.register(models.Section, inlines=[base_admin.ArticleInline]) +site.register(models.Book, BookAdmin) +site.register(models.Section, inlines=[base_admin.ArticleInline], search_fields=['name']) site.register(models.Thing, base_admin.ThingAdmin) site.register(models.Fabric, base_admin.FabricAdmin) site.register(models.ChapterXtra1, base_admin.ChapterXtra1Admin) diff --git a/tests/admin_views/models.py b/tests/admin_views/models.py index 86ab055f3027..d134b3492395 100644 --- a/tests/admin_views/models.py +++ b/tests/admin_views/models.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import datetime import os import tempfile @@ -14,10 +11,8 @@ from django.core.exceptions import ValidationError from django.core.files.storage import FileSystemStorage from django.db import models -from django.utils.encoding import python_2_unicode_compatible -@python_2_unicode_compatible class Section(models.Model): """ A simple section that links to articles, to test linking to related items @@ -36,7 +31,6 @@ def name_property(self): return self.name -@python_2_unicode_compatible class Article(models.Model): """ A simple article to test admin views. Test backwards compatibility. @@ -62,7 +56,6 @@ def model_year_reversed(self): model_year_reversed.short_description = '' -@python_2_unicode_compatible class Book(models.Model): """ A simple book that has chapters. @@ -73,39 +66,37 @@ def __str__(self): return self.name -@python_2_unicode_compatible class Promo(models.Model): name = models.CharField(max_length=100, verbose_name='¿Name?') book = models.ForeignKey(Book, models.CASCADE) + author = models.ForeignKey(User, models.SET_NULL, blank=True, null=True) def __str__(self): return self.name -@python_2_unicode_compatible class Chapter(models.Model): title = models.CharField(max_length=100, verbose_name='¿Title?') content = models.TextField() book = models.ForeignKey(Book, models.CASCADE) - def __str__(self): - return self.title - class Meta: # Use a utf-8 bytestring to ensure it works (see #11710) verbose_name = '¿Chapter?' + def __str__(self): + return self.title + -@python_2_unicode_compatible class ChapterXtra1(models.Model): chap = models.OneToOneField(Chapter, models.CASCADE, verbose_name='¿Chap?') xtra = models.CharField(max_length=100, verbose_name='¿Xtra?') + guest_author = models.ForeignKey(User, models.SET_NULL, blank=True, null=True) def __str__(self): return '¿Xtra1: %s' % self.xtra -@python_2_unicode_compatible class ChapterXtra2(models.Model): chap = models.OneToOneField(Chapter, models.CASCADE, verbose_name='¿Chap?') xtra = models.CharField(max_length=100, verbose_name='¿Xtra?') @@ -123,7 +114,6 @@ class CustomArticle(models.Model): date = models.DateTimeField() -@python_2_unicode_compatible class ModelWithStringPrimaryKey(models.Model): string_pk = models.CharField(max_length=255, primary_key=True) @@ -134,7 +124,6 @@ def get_absolute_url(self): return '/dummy/%s/' % self.string_pk -@python_2_unicode_compatible class Color(models.Model): value = models.CharField(max_length=10) warm = models.BooleanField(default=False) @@ -149,7 +138,6 @@ class Meta: proxy = True -@python_2_unicode_compatible class Thing(models.Model): title = models.CharField(max_length=20) color = models.ForeignKey(Color, models.CASCADE, limit_choices_to={'warm': True}) @@ -159,7 +147,6 @@ def __str__(self): return self.title -@python_2_unicode_compatible class Actor(models.Model): name = models.CharField(max_length=50) age = models.IntegerField() @@ -169,7 +156,6 @@ def __str__(self): return self.name -@python_2_unicode_compatible class Inquisition(models.Model): expected = models.BooleanField(default=False) leader = models.ForeignKey(Actor, models.CASCADE) @@ -179,7 +165,6 @@ def __str__(self): return "by %s from %s" % (self.leader, self.country) -@python_2_unicode_compatible class Sketch(models.Model): title = models.CharField(max_length=100) inquisition = models.ForeignKey( @@ -216,7 +201,6 @@ def today_callable_q(): return models.Q(last_action__gte=datetime.datetime.today()) -@python_2_unicode_compatible class Character(models.Model): username = models.CharField(max_length=100) last_action = models.DateTimeField() @@ -225,7 +209,6 @@ def __str__(self): return self.username -@python_2_unicode_compatible class StumpJoke(models.Model): variation = models.CharField(max_length=100) most_recently_fooled = models.ForeignKey( @@ -251,7 +234,6 @@ class Fabric(models.Model): surface = models.CharField(max_length=20, choices=NG_CHOICES) -@python_2_unicode_compatible class Person(models.Model): GENDER_CHOICES = ( (1, "Male"), @@ -266,7 +248,6 @@ def __str__(self): return self.name -@python_2_unicode_compatible class Persona(models.Model): """ A simple persona associated with accounts, to test inlining of related @@ -278,7 +259,6 @@ def __str__(self): return self.name -@python_2_unicode_compatible class Account(models.Model): """ A simple, generic account encapsulating the information shared by all @@ -302,7 +282,6 @@ class BarAccount(Account): servicename = 'bar' -@python_2_unicode_compatible class Subscriber(models.Model): name = models.CharField(blank=False, max_length=80) email = models.EmailField(blank=False, max_length=175) @@ -352,7 +331,6 @@ def clean(self): raise ValidationError('invalid') -@python_2_unicode_compatible class EmptyModel(models.Model): def __str__(self): return "Primary key = %s" % self.id @@ -436,7 +414,6 @@ class FancyDoodad(Doodad): expensive = models.BooleanField(default=True) -@python_2_unicode_compatible class Category(models.Model): collector = models.ForeignKey(Collector, models.CASCADE) order = models.PositiveIntegerField() @@ -479,7 +456,7 @@ class Post(models.Model): default=datetime.date.today, help_text="Some help text for the date (with unicode ŠĐĆŽćžšđ)" ) - public = models.NullBooleanField() + public = models.BooleanField(null=True, blank=True) def awesomeness_level(self): return "Very awesome." @@ -492,7 +469,6 @@ class Meta: proxy = True -@python_2_unicode_compatible class Gadget(models.Model): name = models.CharField(max_length=100) @@ -500,7 +476,6 @@ def __str__(self): return self.name -@python_2_unicode_compatible class Villain(models.Model): name = models.CharField(max_length=100) @@ -512,7 +487,6 @@ class SuperVillain(Villain): pass -@python_2_unicode_compatible class FunkyTag(models.Model): "Because we all know there's only one real use case for GFKs." name = models.CharField(max_length=25) @@ -524,7 +498,6 @@ def __str__(self): return self.name -@python_2_unicode_compatible class Plot(models.Model): name = models.CharField(max_length=100) team_leader = models.ForeignKey(Villain, models.CASCADE, related_name='lead_plots') @@ -535,7 +508,6 @@ def __str__(self): return self.name -@python_2_unicode_compatible class PlotDetails(models.Model): details = models.CharField(max_length=100) plot = models.OneToOneField(Plot, models.CASCADE, null=True, blank=True) @@ -549,7 +521,6 @@ class Meta: proxy = True -@python_2_unicode_compatible class SecretHideout(models.Model): """ Secret! Not registered with the admin! """ location = models.CharField(max_length=100) @@ -559,7 +530,6 @@ def __str__(self): return self.location -@python_2_unicode_compatible class SuperSecretHideout(models.Model): """ Secret! Not registered with the admin! """ location = models.CharField(max_length=100) @@ -569,7 +539,6 @@ def __str__(self): return self.location -@python_2_unicode_compatible class Bookmark(models.Model): name = models.CharField(max_length=60) tag = GenericRelation(FunkyTag, related_query_name='bookmark') @@ -578,7 +547,6 @@ def __str__(self): return self.name -@python_2_unicode_compatible class CyclicOne(models.Model): name = models.CharField(max_length=25) two = models.ForeignKey('CyclicTwo', models.CASCADE) @@ -587,7 +555,6 @@ def __str__(self): return self.name -@python_2_unicode_compatible class CyclicTwo(models.Model): name = models.CharField(max_length=25) one = models.ForeignKey(CyclicOne, models.CASCADE) @@ -596,7 +563,6 @@ def __str__(self): return self.name -@python_2_unicode_compatible class Topping(models.Model): name = models.CharField(max_length=20) @@ -609,6 +575,21 @@ class Pizza(models.Model): toppings = models.ManyToManyField('Topping', related_name='pizzas') +# Pizza's ModelAdmin has readonly_fields = ['toppings']. +# toppings is editable for this model's admin. +class ReadablePizza(Pizza): + class Meta: + proxy = True + + +# No default permissions are created for this model and both name and toppings +# are readonly for this model's admin. +class ReadOnlyPizza(Pizza): + class Meta: + proxy = True + default_permissions = () + + class Album(models.Model): owner = models.ForeignKey(User, models.SET_NULL, null=True, blank=True) title = models.CharField(max_length=30) @@ -626,9 +607,13 @@ class WorkHour(models.Model): class Question(models.Model): question = models.CharField(max_length=20) posted = models.DateField(default=datetime.date.today) + expires = models.DateTimeField(null=True, blank=True) + related_questions = models.ManyToManyField('self') + + def __str__(self): + return self.question -@python_2_unicode_compatible class Answer(models.Model): question = models.ForeignKey(Question, models.PROTECT) answer = models.CharField(max_length=20) @@ -637,24 +622,26 @@ def __str__(self): return self.answer +class Answer2(Answer): + class Meta: + proxy = True + + class Reservation(models.Model): start_date = models.DateTimeField() price = models.IntegerField() -DRIVER_CHOICES = ( - ('bill', 'Bill G'), - ('steve', 'Steve J'), -) - -RESTAURANT_CHOICES = ( - ('indian', 'A Taste of India'), - ('thai', 'Thai Pography'), - ('pizza', 'Pizza Mama'), -) - - class FoodDelivery(models.Model): + DRIVER_CHOICES = ( + ('bill', 'Bill G'), + ('steve', 'Steve J'), + ) + RESTAURANT_CHOICES = ( + ('indian', 'A Taste of India'), + ('thai', 'Thai Pography'), + ('pizza', 'Pizza Mama'), + ) reference = models.CharField(max_length=100) driver = models.CharField(max_length=100, choices=DRIVER_CHOICES, blank=True) restaurant = models.CharField(max_length=100, choices=RESTAURANT_CHOICES, blank=True) @@ -663,7 +650,6 @@ class Meta: unique_together = (("driver", "restaurant"),) -@python_2_unicode_compatible class CoverLetter(models.Model): author = models.CharField(max_length=30) date_written = models.DateField(null=True, blank=True) @@ -682,7 +668,6 @@ class ShortMessage(models.Model): timestamp = models.DateTimeField(null=True, blank=True) -@python_2_unicode_compatible class Telegram(models.Model): title = models.CharField(max_length=30) date_sent = models.DateField(null=True, blank=True) @@ -704,7 +689,7 @@ class OtherStory(models.Model): class ComplexSortedPerson(models.Model): name = models.CharField(max_length=100) age = models.PositiveIntegerField() - is_employee = models.NullBooleanField() + is_employee = models.BooleanField(null=True) class PluggableSearchPerson(models.Model): @@ -748,7 +733,6 @@ class AdminOrderedCallable(models.Model): stuff = models.CharField(max_length=200) -@python_2_unicode_compatible class Report(models.Model): title = models.CharField(max_length=100) @@ -771,6 +755,8 @@ class MainPrepopulated(models.Model): class RelatedPrepopulated(models.Model): parent = models.ForeignKey(MainPrepopulated, models.CASCADE) name = models.CharField(max_length=75) + fk = models.ForeignKey('self', models.CASCADE, blank=True, null=True) + m2m = models.ManyToManyField('self', blank=True) pubdate = models.DateField() status = models.CharField( max_length=20, @@ -845,7 +831,7 @@ class DependentChild(models.Model): class _Manager(models.Manager): def get_queryset(self): - return super(_Manager, self).get_queryset().filter(pk__gt=1) + return super().get_queryset().filter(pk__gt=1) class FilteredManager(models.Model): @@ -931,7 +917,6 @@ class InlineReference(models.Model): ) -# Models for #23604 and #23915 class Recipe(models.Model): rname = models.CharField(max_length=20, unique=True) @@ -982,3 +967,18 @@ def __str__(self): class RelatedWithUUIDPKModel(models.Model): parent = models.ForeignKey(ParentWithUUIDPK, on_delete=models.SET_NULL, null=True, blank=True) + + +class Author(models.Model): + pass + + +class Authorship(models.Model): + book = models.ForeignKey(Book, models.CASCADE) + author = models.ForeignKey(Author, models.CASCADE) + + +class UserProxy(User): + """Proxy a model with a different app_label.""" + class Meta: + proxy = True diff --git a/tests/admin_views/templates/admin/admin_views/article/actions.html b/tests/admin_views/templates/admin/admin_views/article/actions.html new file mode 100644 index 000000000000..9aa238fd2a5e --- /dev/null +++ b/tests/admin_views/templates/admin/admin_views/article/actions.html @@ -0,0 +1,6 @@ +{% extends "admin/actions.html" %} +{% load i18n %} + +{% block actions-submit %} + +{% endblock %} diff --git a/tests/admin_views/templates/admin/admin_views/article/change_form_object_tools.html b/tests/admin_views/templates/admin/admin_views/article/change_form_object_tools.html new file mode 100644 index 000000000000..609974bb95b3 --- /dev/null +++ b/tests/admin_views/templates/admin/admin_views/article/change_form_object_tools.html @@ -0,0 +1,7 @@ +{% extends "admin/change_form_object_tools.html" %} +{% load i18n admin_urls %} + +{% block object-tools-items %} +
    • {% trans "Export" %}
    • +{{ block.super }} +{% endblock %} diff --git a/tests/admin_views/templates/admin/admin_views/article/change_list_object_tools.html b/tests/admin_views/templates/admin/admin_views/article/change_list_object_tools.html new file mode 100644 index 000000000000..1a9be91952eb --- /dev/null +++ b/tests/admin_views/templates/admin/admin_views/article/change_list_object_tools.html @@ -0,0 +1,7 @@ +{% extends "admin/change_list_object_tools.html" %} +{% load i18n admin_urls %} + +{% block object-tools-items %} +
    • {% trans "Export" %}
    • +{{ block.super }} +{% endblock %} diff --git a/tests/admin_views/templates/admin/admin_views/article/change_list_results.html b/tests/admin_views/templates/admin/admin_views/article/change_list_results.html new file mode 100644 index 000000000000..ceb581f8ef53 --- /dev/null +++ b/tests/admin_views/templates/admin/admin_views/article/change_list_results.html @@ -0,0 +1,38 @@ +{% load i18n static %} +{% if result_hidden_fields %} +
      {# DIV for HTML validation #} +{% for item in result_hidden_fields %}{{ item }}{% endfor %} +
      +{% endif %} +{% if results %} +
      + + + +{% for header in result_headers %} +{% endfor %} + + + +{% for result in results %} +{% if result.form.non_field_errors %} + +{% endif %} +{% for item in result %}{{ item }}{% endfor %} +{% endfor %} + +
      + {% if header.sortable %} + {% if header.sort_priority > 0 %} +
      + + {% if num_sorted_fields > 1 %}{{ header.sort_priority }}{% endif %} + +
      + {% endif %} + {% endif %} +
      {% if header.sortable %}{{ header.text|capfirst }}{% else %}{{ header.text|capfirst }}{% endif %}
      +
      +
      {{ result.form.non_field_errors }}
      +
      +{% endif %} diff --git a/tests/admin_views/templates/admin/admin_views/article/date_hierarchy.html b/tests/admin_views/templates/admin/admin_views/article/date_hierarchy.html new file mode 100644 index 000000000000..de1cb747b5be --- /dev/null +++ b/tests/admin_views/templates/admin/admin_views/article/date_hierarchy.html @@ -0,0 +1,9 @@ +{% extends "admin/date_hierarchy.html" %} +{% load i18n %} + +{% block date-hierarchy-choices %} + + +{% endblock %} diff --git a/tests/admin_views/templates/admin/admin_views/article/pagination.html b/tests/admin_views/templates/admin/admin_views/article/pagination.html new file mode 100644 index 000000000000..e072cacd3c10 --- /dev/null +++ b/tests/admin_views/templates/admin/admin_views/article/pagination.html @@ -0,0 +1,12 @@ +{% load admin_list %} +{% load i18n %} +

      +{% if pagination_required %} +{% for i in page_range %} + {% paginator_number cl i %} +{% endfor %} +{% endif %} +{{ cl.result_count }} {% if cl.result_count == 1 %}{{ cl.opts.verbose_name }}{% else %}{{ cl.opts.verbose_name_plural }}{% endif %} +{% if show_all_url %}  {% trans 'Show all' %}{% endif %} +{% if cl.formset and cl.result_count %}{% endif %} +

      diff --git a/tests/admin_views/templates/admin/admin_views/article/prepopulated_fields_js.html b/tests/admin_views/templates/admin/admin_views/article/prepopulated_fields_js.html new file mode 100644 index 000000000000..0ee8c7a06ca4 --- /dev/null +++ b/tests/admin_views/templates/admin/admin_views/article/prepopulated_fields_js.html @@ -0,0 +1,7 @@ +{% load l10n static %} + diff --git a/tests/admin_views/templates/admin/admin_views/article/search_form.html b/tests/admin_views/templates/admin/admin_views/article/search_form.html new file mode 100644 index 000000000000..5b5e6a58f645 --- /dev/null +++ b/tests/admin_views/templates/admin/admin_views/article/search_form.html @@ -0,0 +1,16 @@ +{% load i18n static %} +{% if cl.search_fields %} +
      +{% endif %} diff --git a/tests/admin_views/templates/admin/admin_views/article/submit_line.html b/tests/admin_views/templates/admin/admin_views/article/submit_line.html new file mode 100644 index 000000000000..4a2ca08890e4 --- /dev/null +++ b/tests/admin_views/templates/admin/admin_views/article/submit_line.html @@ -0,0 +1,7 @@ +{% extends "admin/submit_line.html" %} +{% load i18n admin_urls %} + +{% block submit-row %} +{% if show_publish %}{% endif %} +{{ block.super }} +{% endblock %} diff --git a/tests/admin_views/test_actions.py b/tests/admin_views/test_actions.py new file mode 100644 index 000000000000..a98b80a1cbee --- /dev/null +++ b/tests/admin_views/test_actions.py @@ -0,0 +1,451 @@ +import json + +from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME +from django.contrib.admin.views.main import IS_POPUP_VAR +from django.contrib.auth.models import Permission, User +from django.core import mail +from django.template.loader import render_to_string +from django.template.response import TemplateResponse +from django.test import TestCase, override_settings +from django.urls import reverse + +from .admin import SubscriberAdmin +from .forms import MediaActionForm +from .models import ( + Actor, Answer, Book, ExternalSubscriber, Question, Subscriber, + UnchangeableObject, +) + + +@override_settings(ROOT_URLCONF='admin_views.urls') +class AdminActionsTest(TestCase): + + @classmethod + def setUpTestData(cls): + cls.superuser = User.objects.create_superuser(username='super', password='secret', email='super@example.com') + cls.s1 = ExternalSubscriber.objects.create(name='John Doe', email='john@example.org') + cls.s2 = Subscriber.objects.create(name='Max Mustermann', email='max@example.org') + + def setUp(self): + self.client.force_login(self.superuser) + + def test_model_admin_custom_action(self): + """A custom action defined in a ModelAdmin method.""" + action_data = { + ACTION_CHECKBOX_NAME: [self.s1.pk], + 'action': 'mail_admin', + 'index': 0, + } + self.client.post(reverse('admin:admin_views_subscriber_changelist'), action_data) + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].subject, 'Greetings from a ModelAdmin action') + + def test_model_admin_default_delete_action(self): + action_data = { + ACTION_CHECKBOX_NAME: [self.s1.pk, self.s2.pk], + 'action': 'delete_selected', + 'index': 0, + } + delete_confirmation_data = { + ACTION_CHECKBOX_NAME: [self.s1.pk, self.s2.pk], + 'action': 'delete_selected', + 'post': 'yes', + } + confirmation = self.client.post(reverse('admin:admin_views_subscriber_changelist'), action_data) + self.assertIsInstance(confirmation, TemplateResponse) + self.assertContains(confirmation, 'Are you sure you want to delete the selected subscribers?') + self.assertContains(confirmation, '

      Summary

      ') + self.assertContains(confirmation, '
    • Subscribers: 2
    • ') + self.assertContains(confirmation, '
    • External subscribers: 1
    • ') + self.assertContains(confirmation, ACTION_CHECKBOX_NAME, count=2) + self.client.post(reverse('admin:admin_views_subscriber_changelist'), delete_confirmation_data) + self.assertEqual(Subscriber.objects.count(), 0) + + def test_default_delete_action_nonexistent_pk(self): + self.assertFalse(Subscriber.objects.filter(id=9998).exists()) + action_data = { + ACTION_CHECKBOX_NAME: ['9998'], + 'action': 'delete_selected', + 'index': 0, + } + response = self.client.post(reverse('admin:admin_views_subscriber_changelist'), action_data) + self.assertContains(response, 'Are you sure you want to delete the selected subscribers?') + self.assertContains(response, '
        ', html=True) + + @override_settings(USE_THOUSAND_SEPARATOR=True, USE_L10N=True, NUMBER_GROUPING=3) + def test_non_localized_pk(self): + """ + If USE_THOUSAND_SEPARATOR is set, the ids for the objects selected for + deletion are rendered without separators. + """ + s = ExternalSubscriber.objects.create(id=9999) + action_data = { + ACTION_CHECKBOX_NAME: [s.pk, self.s2.pk], + 'action': 'delete_selected', + 'index': 0, + } + response = self.client.post(reverse('admin:admin_views_subscriber_changelist'), action_data) + self.assertTemplateUsed(response, 'admin/delete_selected_confirmation.html') + self.assertContains(response, 'value="9999"') # Instead of 9,999 + self.assertContains(response, 'value="%s"' % self.s2.pk) + + def test_model_admin_default_delete_action_protected(self): + """ + The default delete action where some related objects are protected + from deletion. + """ + q1 = Question.objects.create(question='Why?') + a1 = Answer.objects.create(question=q1, answer='Because.') + a2 = Answer.objects.create(question=q1, answer='Yes.') + q2 = Question.objects.create(question='Wherefore?') + action_data = { + ACTION_CHECKBOX_NAME: [q1.pk, q2.pk], + 'action': 'delete_selected', + 'index': 0, + } + delete_confirmation_data = action_data.copy() + delete_confirmation_data['post'] = 'yes' + response = self.client.post(reverse('admin:admin_views_question_changelist'), action_data) + self.assertContains(response, 'would require deleting the following protected related objects') + self.assertContains( + response, + '
      • Answer: Because.
      • ' % reverse('admin:admin_views_answer_change', args=(a1.pk,)), + html=True + ) + self.assertContains( + response, + '
      • Answer: Yes.
      • ' % reverse('admin:admin_views_answer_change', args=(a2.pk,)), + html=True + ) + # A POST request to delete protected objects displays the page which + # says the deletion is prohibited. + response = self.client.post(reverse('admin:admin_views_question_changelist'), delete_confirmation_data) + self.assertContains(response, 'would require deleting the following protected related objects') + self.assertEqual(Question.objects.count(), 2) + + def test_model_admin_default_delete_action_no_change_url(self): + """ + The default delete action doesn't break if a ModelAdmin removes the + change_view URL (#20640). + """ + obj = UnchangeableObject.objects.create() + action_data = { + ACTION_CHECKBOX_NAME: obj.pk, + 'action': 'delete_selected', + 'index': '0', + } + response = self.client.post(reverse('admin:admin_views_unchangeableobject_changelist'), action_data) + # No 500 caused by NoReverseMatch + self.assertEqual(response.status_code, 200) + # The page doesn't display a link to the nonexistent change page. + self.assertContains(response, '
      • Unchangeable object: %s
      • ' % obj, 1, html=True) + + def test_delete_queryset_hook(self): + delete_confirmation_data = { + ACTION_CHECKBOX_NAME: [self.s1.pk, self.s2.pk], + 'action': 'delete_selected', + 'post': 'yes', + 'index': 0, + } + SubscriberAdmin.overridden = False + self.client.post(reverse('admin:admin_views_subscriber_changelist'), delete_confirmation_data) + # SubscriberAdmin.delete_queryset() sets overridden to True. + self.assertIs(SubscriberAdmin.overridden, True) + self.assertEqual(Subscriber.objects.all().count(), 0) + + def test_delete_selected_uses_get_deleted_objects(self): + """The delete_selected action uses ModelAdmin.get_deleted_objects().""" + book = Book.objects.create(name='Test Book') + data = { + ACTION_CHECKBOX_NAME: [book.pk], + 'action': 'delete_selected', + 'index': 0, + } + response = self.client.post(reverse('admin2:admin_views_book_changelist'), data) + # BookAdmin.get_deleted_objects() returns custom text. + self.assertContains(response, 'a deletable object') + + def test_custom_function_mail_action(self): + """A custom action may be defined in a function.""" + action_data = { + ACTION_CHECKBOX_NAME: [self.s1.pk], + 'action': 'external_mail', + 'index': 0, + } + self.client.post(reverse('admin:admin_views_externalsubscriber_changelist'), action_data) + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].subject, 'Greetings from a function action') + + def test_custom_function_action_with_redirect(self): + """Another custom action defined in a function.""" + action_data = { + ACTION_CHECKBOX_NAME: [self.s1.pk], + 'action': 'redirect_to', + 'index': 0, + } + response = self.client.post(reverse('admin:admin_views_externalsubscriber_changelist'), action_data) + self.assertEqual(response.status_code, 302) + + def test_default_redirect(self): + """ + Actions which don't return an HttpResponse are redirected to the same + page, retaining the querystring (which may contain changelist info). + """ + action_data = { + ACTION_CHECKBOX_NAME: [self.s1.pk], + 'action': 'external_mail', + 'index': 0, + } + url = reverse('admin:admin_views_externalsubscriber_changelist') + '?o=1' + response = self.client.post(url, action_data) + self.assertRedirects(response, url) + + def test_custom_function_action_streaming_response(self): + """A custom action may return a StreamingHttpResponse.""" + action_data = { + ACTION_CHECKBOX_NAME: [self.s1.pk], + 'action': 'download', + 'index': 0, + } + response = self.client.post(reverse('admin:admin_views_externalsubscriber_changelist'), action_data) + content = b''.join(response.streaming_content) + self.assertEqual(content, b'This is the content of the file') + self.assertEqual(response.status_code, 200) + + def test_custom_function_action_no_perm_response(self): + """A custom action may returns an HttpResponse with a 403 code.""" + action_data = { + ACTION_CHECKBOX_NAME: [self.s1.pk], + 'action': 'no_perm', + 'index': 0, + } + response = self.client.post(reverse('admin:admin_views_externalsubscriber_changelist'), action_data) + self.assertEqual(response.status_code, 403) + self.assertEqual(response.content, b'No permission to perform this action') + + def test_actions_ordering(self): + """Actions are ordered as expected.""" + response = self.client.get(reverse('admin:admin_views_externalsubscriber_changelist')) + self.assertContains(response, '''
        """ # NOQA - @staticmethod - def _comparison_value(value): - # The XML serializer handles everything as strings, so comparisons - # need to be performed on the stringified value - return six.text_type(value) - @staticmethod def _validate_output(serial_str): try: diff --git a/tests/serializers/test_yaml.py b/tests/serializers/test_yaml.py index 0b4b9b00d1b3..10f73901cb8a 100644 --- a/tests/serializers/test_yaml.py +++ b/tests/serializers/test_yaml.py @@ -1,14 +1,10 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import importlib import unittest +from io import StringIO from django.core import management, serializers from django.core.serializers.base import DeserializationError from django.test import SimpleTestCase, TestCase, TransactionTestCase -from django.utils import six -from django.utils.six import StringIO from .models import Author from .tests import SerializersTestBase, SerializersTransactionTestBase @@ -22,7 +18,7 @@ YAML_IMPORT_ERROR_MESSAGE = r'No module named yaml' -class YamlImportModuleMock(object): +class YamlImportModuleMock: """Provides a wrapped import_module function to simulate yaml ImportError In order to run tests that verify the behavior of the YAML serializer @@ -51,7 +47,7 @@ class NoYamlSerializerTestCase(SimpleTestCase): @classmethod def setUpClass(cls): """Removes imported yaml and stubs importlib.import_module""" - super(NoYamlSerializerTestCase, cls).setUpClass() + super().setUpClass() cls._import_module_mock = YamlImportModuleMock() importlib.import_module = cls._import_module_mock.import_module @@ -62,7 +58,7 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): """Puts yaml back if necessary""" - super(NoYamlSerializerTestCase, cls).tearDownClass() + super().tearDownClass() importlib.import_module = cls._import_module_mock._import_module @@ -119,7 +115,9 @@ class YamlSerializerTestCase(SerializersTestBase, TestCase): author: %(author_pk)s headline: Poker has no place on ESPN pub_date: 2006-06-16 11:00:00 - categories: [%(first_category_pk)s, %(second_category_pk)s] + categories:""" + ( + ' [%(first_category_pk)s, %(second_category_pk)s]' if HAS_YAML and yaml.__version__ < '5.1' + else '\n - %(first_category_pk)s\n - %(second_category_pk)s') + """ meta_data: [] """ @@ -150,7 +148,7 @@ def _get_field_values(serial_str, field_name): # yaml.safe_load will return non-string objects for some # of the fields we are interested in, this ensures that # everything comes back as a string - if isinstance(field_value, six.string_types): + if isinstance(field_value, str): ret_list.append(field_value) else: ret_list.append(str(field_value)) diff --git a/tests/serializers/tests.py b/tests/serializers/tests.py index f765aea81568..66fc2ef40cd7 100644 --- a/tests/serializers/tests.py +++ b/tests/serializers/tests.py @@ -1,19 +1,15 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from datetime import datetime +from functools import partialmethod +from io import StringIO +from unittest import mock, skipIf from django.core import serializers from django.core.serializers import SerializerDoesNotExist from django.core.serializers.base import ProgressBar from django.db import connection, transaction from django.http import HttpResponse -from django.test import ( - SimpleTestCase, mock, override_settings, skipUnlessDBFeature, -) +from django.test import SimpleTestCase, override_settings, skipUnlessDBFeature from django.test.utils import Approximate -from django.utils.functional import curry -from django.utils.six import StringIO from .models import ( Actor, Article, Author, AuthorProfile, BaseModel, Category, ComplexModel, @@ -91,13 +87,9 @@ def test_get_unknown_deserializer(self): serializers.get_deserializer("nonsense") -class SerializersTestBase(object): +class SerializersTestBase: serializer_name = None # Set by subclasses to the serialization format name - @staticmethod - def _comparison_value(value): - return value - def setUp(self): sports = Category.objects.create(name="Sports") music = Category.objects.create(name="Music") @@ -148,7 +140,7 @@ def test_serialize_to_stream(self): if isinstance(stream, StringIO): self.assertEqual(string_data, stream.getvalue()) else: - self.assertEqual(string_data, stream.content.decode('utf-8')) + self.assertEqual(string_data, stream.content.decode()) def test_serialize_specific_fields(self): obj = ComplexModel(field1='first', field2='second', field3='third') @@ -197,7 +189,7 @@ def test_one_to_one_as_pk(self): self.assertFalse(self._get_field_values(serial_str, 'author')) for obj in serializers.deserialize(self.serializer_name, serial_str): - self.assertEqual(obj.object.pk, self._comparison_value(self.joe.pk)) + self.assertEqual(obj.object.pk, self.joe.pk) def test_serialize_field_subset(self): """Output can be restricted to a subset of fields""" @@ -352,7 +344,7 @@ def test_serialize_proxy_model(self): class SerializerAPITests(SimpleTestCase): def test_stream_class(self): - class File(object): + class File: def __init__(self): self.lines = [] @@ -372,7 +364,7 @@ class Serializer(serializers.json.Serializer): self.assertEqual(data, '[{"model": "serializers.score", "pk": 1, "fields": {"score": 3.4}}]') -class SerializersTransactionTestBase(object): +class SerializersTransactionTestBase: available_apps = ['serializers'] @@ -397,16 +389,16 @@ def test_forward_refs(self): self.assertEqual(art_obj.author.name, "Agnes") -def register_tests(test_class, method_name, test_func, exclude=None): +def register_tests(test_class, method_name, test_func, exclude=()): """ Dynamically create serializer tests to ensure that all registered serializers are automatically tested. """ - formats = [ - f for f in serializers.get_serializer_formats() - if (not isinstance(serializers.get_serializer(f), serializers.BadSerializer) and - f != 'geojson' and - (exclude is None or f not in exclude)) - ] - for format_ in formats: - setattr(test_class, method_name % format_, curry(test_func, format_)) + for format_ in serializers.get_serializer_formats(): + if format_ == 'geojson' or format_ in exclude: + continue + decorated_func = skipIf( + isinstance(serializers.get_serializer(format_), serializers.BadSerializer), + 'The Python library for the %s serializer is not installed.' % format_, + )(test_func) + setattr(test_class, method_name % format_, partialmethod(decorated_func, format_)) diff --git a/tests/servers/models.py b/tests/servers/models.py index 6e1414ae1945..b523bae6f2df 100644 --- a/tests/servers/models.py +++ b/tests/servers/models.py @@ -2,4 +2,4 @@ class Person(models.Model): - name = models.CharField(max_length=256) + name = models.CharField(max_length=255) diff --git a/tests/servers/test_basehttp.py b/tests/servers/test_basehttp.py index cd9bcd2e86e5..32fdbf3c0e5c 100644 --- a/tests/servers/test_basehttp.py +++ b/tests/servers/test_basehttp.py @@ -1,14 +1,12 @@ -import logging from io import BytesIO from django.core.handlers.wsgi import WSGIRequest from django.core.servers.basehttp import WSGIRequestHandler from django.test import SimpleTestCase from django.test.client import RequestFactory -from django.test.utils import patch_logger -class Stub(object): +class Stub: def __init__(self, **kwargs): self.__dict__.update(kwargs) @@ -17,54 +15,42 @@ def sendall(self, data): class WSGIRequestHandlerTestCase(SimpleTestCase): + request_factory = RequestFactory() def test_log_message(self): - # Silence the django.server logger by replacing its StreamHandler with - # NullHandler. - logger = logging.getLogger('django.server') - original_handlers = logger.handlers - logger.handlers = [logging.NullHandler()] - try: - request = WSGIRequest(RequestFactory().get('/').environ) - request.makefile = lambda *args, **kwargs: BytesIO() - handler = WSGIRequestHandler(request, '192.168.0.2', None) - level_status_codes = { - 'info': [200, 301, 304], - 'warning': [400, 403, 404], - 'error': [500, 503], - } - - def _log_level_code(level, status_code): - with patch_logger('django.server', level) as messages: + request = WSGIRequest(self.request_factory.get('/').environ) + request.makefile = lambda *args, **kwargs: BytesIO() + handler = WSGIRequestHandler(request, '192.168.0.2', None) + level_status_codes = { + 'info': [200, 301, 304], + 'warning': [400, 403, 404], + 'error': [500, 503], + } + for level, status_codes in level_status_codes.items(): + for status_code in status_codes: + # The correct level gets the message. + with self.assertLogs('django.server', level.upper()) as cm: handler.log_message('GET %s %s', 'A', str(status_code)) - return messages - - for level, status_codes in level_status_codes.items(): - for status_code in status_codes: - # The correct level gets the message. - messages = _log_level_code(level, status_code) - self.assertIn('GET A %d' % status_code, messages[0]) - - # Incorrect levels shouldn't have any messages. - for wrong_level in level_status_codes.keys(): - if wrong_level != level: - messages = _log_level_code(wrong_level, status_code) - self.assertEqual(len(messages), 0) - finally: - logger.handlers = original_handlers + self.assertIn('GET A %d' % status_code, cm.output[0]) + # Incorrect levels don't have any messages. + for wrong_level in level_status_codes: + if wrong_level != level: + with self.assertLogs('django.server', 'INFO') as cm: + handler.log_message('GET %s %s', 'A', str(status_code)) + self.assertNotEqual(cm.records[0].levelname, wrong_level.upper()) def test_https(self): - request = WSGIRequest(RequestFactory().get('/').environ) + request = WSGIRequest(self.request_factory.get('/').environ) request.makefile = lambda *args, **kwargs: BytesIO() handler = WSGIRequestHandler(request, '192.168.0.2', None) - with patch_logger('django.server', 'error') as messages: - handler.log_message("GET %s %s", str('\x16\x03'), "4") + with self.assertLogs('django.server', 'ERROR') as cm: + handler.log_message("GET %s %s", '\x16\x03', "4") self.assertIn( "You're accessing the development server over HTTPS, " "but it only supports HTTP.", - messages[0] + cm.records[0].getMessage() ) def test_strips_underscore_headers(self): @@ -81,7 +67,7 @@ def test_app(environ, start_response): '%s:%s' % (k, v) for k, v in environ.items() if k.startswith('HTTP_') ) - yield (','.join(http_environ_items)).encode('utf-8') + yield (','.join(http_environ_items)).encode() rfile = BytesIO() rfile.write(b"GET / HTTP/1.0\r\n") @@ -107,8 +93,8 @@ def makefile(mode, *a, **kw): request = Stub(makefile=makefile) server = Stub(base_environ={}, get_app=lambda: test_app) - # We don't need to check stderr, but we don't want it in test output - with patch_logger('django.server', 'info'): + # Prevent logging from appearing in test output. + with self.assertLogs('django.server', 'INFO'): # instantiating a handler runs the request as side effect WSGIRequestHandler(request, '192.168.0.2', server) diff --git a/tests/servers/test_liveserverthread.py b/tests/servers/test_liveserverthread.py index d39aac8183ad..9762b53791ff 100644 --- a/tests/servers/test_liveserverthread.py +++ b/tests/servers/test_liveserverthread.py @@ -18,11 +18,10 @@ def test_closes_connections(self): # Pass a connection to the thread to check they are being closed. connections_override = {DEFAULT_DB_ALIAS: conn} - saved_sharing = conn.allow_thread_sharing + conn.inc_thread_sharing() try: - conn.allow_thread_sharing = True self.assertTrue(conn.is_usable()) self.run_live_server_thread(connections_override) self.assertFalse(conn.is_usable()) finally: - conn.allow_thread_sharing = saved_sharing + conn.dec_thread_sharing() diff --git a/tests/servers/tests.py b/tests/servers/tests.py index 03e05d0957e8..7f75b85d6cbf 100644 --- a/tests/servers/tests.py +++ b/tests/servers/tests.py @@ -1,24 +1,19 @@ -# -*- encoding: utf-8 -*- """ Tests for django.core.servers. """ -from __future__ import unicode_literals - -import contextlib import errno import os import socket +from http.client import HTTPConnection +from urllib.error import HTTPError +from urllib.parse import urlencode +from urllib.request import urlopen from django.test import LiveServerTestCase, override_settings -from django.utils._os import upath -from django.utils.http import urlencode -from django.utils.six import text_type -from django.utils.six.moves.urllib.error import HTTPError -from django.utils.six.moves.urllib.request import urlopen from .models import Person -TEST_ROOT = os.path.dirname(upath(__file__)) +TEST_ROOT = os.path.dirname(__file__) TEST_SETTINGS = { 'MEDIA_URL': '/media/', 'MEDIA_ROOT': os.path.join(TEST_ROOT, 'media'), @@ -46,27 +41,105 @@ class LiveServerAddress(LiveServerBase): @classmethod def setUpClass(cls): - super(LiveServerAddress, cls).setUpClass() + super().setUpClass() # put it in a list to prevent descriptor lookups in test cls.live_server_url_test = [cls.live_server_url] def test_live_server_url_is_class_property(self): - self.assertIsInstance(self.live_server_url_test[0], text_type) + self.assertIsInstance(self.live_server_url_test[0], str) self.assertEqual(self.live_server_url_test[0], self.live_server_url) class LiveServerViews(LiveServerBase): + def test_protocol(self): + """Launched server serves with HTTP 1.1.""" + with self.urlopen('/example_view/') as f: + self.assertEqual(f.version, 11) + + def test_closes_connection_without_content_length(self): + """ + A HTTP 1.1 server is supposed to support keep-alive. Since our + development server is rather simple we support it only in cases where + we can detect a content length from the response. This should be doable + for all simple views and streaming responses where an iterable with + length of one is passed. The latter follows as result of `set_content_length` + from https://github.com/python/cpython/blob/master/Lib/wsgiref/handlers.py. + + If we cannot detect a content length we explicitly set the `Connection` + header to `close` to notify the client that we do not actually support + it. + """ + conn = HTTPConnection(LiveServerViews.server_thread.host, LiveServerViews.server_thread.port, timeout=1) + try: + conn.request('GET', '/streaming_example_view/', headers={'Connection': 'keep-alive'}) + response = conn.getresponse() + self.assertTrue(response.will_close) + self.assertEqual(response.read(), b'Iamastream') + self.assertEqual(response.status, 200) + self.assertEqual(response.getheader('Connection'), 'close') + + conn.request('GET', '/streaming_example_view/', headers={'Connection': 'close'}) + response = conn.getresponse() + self.assertTrue(response.will_close) + self.assertEqual(response.read(), b'Iamastream') + self.assertEqual(response.status, 200) + self.assertEqual(response.getheader('Connection'), 'close') + finally: + conn.close() + + def test_keep_alive_on_connection_with_content_length(self): + """ + See `test_closes_connection_without_content_length` for details. This + is a follow up test, which ensure that we do not close the connection + if not needed, hence allowing us to take advantage of keep-alive. + """ + conn = HTTPConnection(LiveServerViews.server_thread.host, LiveServerViews.server_thread.port) + try: + conn.request('GET', '/example_view/', headers={"Connection": "keep-alive"}) + response = conn.getresponse() + self.assertFalse(response.will_close) + self.assertEqual(response.read(), b'example view') + self.assertEqual(response.status, 200) + self.assertIsNone(response.getheader('Connection')) + + conn.request('GET', '/example_view/', headers={"Connection": "close"}) + response = conn.getresponse() + self.assertFalse(response.will_close) + self.assertEqual(response.read(), b'example view') + self.assertEqual(response.status, 200) + self.assertIsNone(response.getheader('Connection')) + finally: + conn.close() + + def test_keep_alive_connection_clears_previous_request_data(self): + conn = HTTPConnection(LiveServerViews.server_thread.host, LiveServerViews.server_thread.port) + try: + conn.request('POST', '/method_view/', b'{}', headers={"Connection": "keep-alive"}) + response = conn.getresponse() + self.assertFalse(response.will_close) + self.assertEqual(response.status, 200) + self.assertEqual(response.read(), b'POST') + + conn.request('POST', '/method_view/', b'{}', headers={"Connection": "close"}) + response = conn.getresponse() + self.assertFalse(response.will_close) + self.assertEqual(response.status, 200) + self.assertEqual(response.read(), b'POST') + finally: + conn.close() + def test_404(self): with self.assertRaises(HTTPError) as err: self.urlopen('/') + err.exception.close() self.assertEqual(err.exception.code, 404, 'Expected 404 response') def test_view(self): - with contextlib.closing(self.urlopen('/example_view/')) as f: + with self.urlopen('/example_view/') as f: self.assertEqual(f.read(), b'example view') def test_static_files(self): - with contextlib.closing(self.urlopen('/static/example_static_file.txt')) as f: + with self.urlopen('/static/example_static_file.txt') as f: self.assertEqual(f.read().rstrip(b'\r\n'), b'example static file') def test_no_collectstatic_emulation(self): @@ -77,14 +150,15 @@ def test_no_collectstatic_emulation(self): """ with self.assertRaises(HTTPError) as err: self.urlopen('/static/another_app/another_app_static_file.txt') + err.exception.close() self.assertEqual(err.exception.code, 404, 'Expected 404 response') def test_media_files(self): - with contextlib.closing(self.urlopen('/media/example_media_file.txt')) as f: + with self.urlopen('/media/example_media_file.txt') as f: self.assertEqual(f.read().rstrip(b'\r\n'), b'example media file') def test_environ(self): - with contextlib.closing(self.urlopen('/environ_view/?%s' % urlencode({'q': 'тест'}))) as f: + with self.urlopen('/environ_view/?%s' % urlencode({'q': 'тест'})) as f: self.assertIn(b"QUERY_STRING: 'q=%D1%82%D0%B5%D1%81%D1%82'", f.read()) @@ -94,14 +168,15 @@ def test_fixtures_loaded(self): """ Fixtures are properly loaded and visible to the live server thread. """ - with contextlib.closing(self.urlopen('/model_view/')) as f: + with self.urlopen('/model_view/') as f: self.assertEqual(f.read().splitlines(), [b'jane', b'robert']) def test_database_writes(self): """ Data written to the database by a view can be read. """ - self.urlopen('/create_model_instance/') + with self.urlopen('/create_model_instance/'): + pass self.assertQuerysetEqual( Person.objects.all().order_by('pk'), ['jane', 'robert', 'emily'], @@ -116,7 +191,7 @@ def test_port_bind(self): Each LiveServerTestCase binds to a unique port or fails to start a server thread when run concurrently (#26011). """ - TestCase = type(str("TestCase"), (LiveServerBase,), {}) + TestCase = type("TestCase", (LiveServerBase,), {}) try: TestCase.setUpClass() except socket.error as e: @@ -134,5 +209,37 @@ def test_port_bind(self): "Acquired duplicate server addresses for server threads: %s" % self.live_server_url ) finally: - if hasattr(TestCase, 'server_thread'): - TestCase.server_thread.terminate() + TestCase.tearDownClass() + + def test_specified_port_bind(self): + """LiveServerTestCase.port customizes the server's port.""" + TestCase = type(str('TestCase'), (LiveServerBase,), {}) + # Find an open port and tell TestCase to use it. + s = socket.socket() + s.bind(('', 0)) + TestCase.port = s.getsockname()[1] + s.close() + TestCase.setUpClass() + try: + self.assertEqual( + TestCase.port, TestCase.server_thread.port, + 'Did not use specified port for LiveServerTestCase thread: %s' % TestCase.port + ) + finally: + TestCase.tearDownClass() + + +class LiverServerThreadedTests(LiveServerBase): + """If LiverServerTestCase isn't threaded, these tests will hang.""" + + def test_view_calls_subview(self): + url = '/subview_calling_view/?%s' % urlencode({'url': self.live_server_url}) + with self.urlopen(url) as f: + self.assertEqual(f.read(), b'subview calling view: subview') + + def test_check_model_instance_from_subview(self): + url = '/check_model_instance_from_subview/?%s' % urlencode({ + 'url': self.live_server_url, + }) + with self.urlopen(url) as f: + self.assertIn(b'emily', f.read()) diff --git a/tests/servers/urls.py b/tests/servers/urls.py index 868ecc34f904..d07712776ae1 100644 --- a/tests/servers/urls.py +++ b/tests/servers/urls.py @@ -1,10 +1,15 @@ -from django.conf.urls import url +from django.urls import path from . import views urlpatterns = [ - url(r'^example_view/$', views.example_view), - url(r'^model_view/$', views.model_view), - url(r'^create_model_instance/$', views.create_model_instance), - url(r'^environ_view/$', views.environ_view), + path('example_view/', views.example_view), + path('streaming_example_view/', views.streaming_example_view), + path('model_view/', views.model_view), + path('create_model_instance/', views.create_model_instance), + path('environ_view/', views.environ_view), + path('subview_calling_view/', views.subview_calling_view), + path('subview/', views.subview), + path('check_model_instance_from_subview/', views.check_model_instance_from_subview), + path('method_view/', views.method_view), ] diff --git a/tests/servers/views.py b/tests/servers/views.py index f1fb8714c58a..1db56f44a385 100644 --- a/tests/servers/views.py +++ b/tests/servers/views.py @@ -1,4 +1,7 @@ -from django.http import HttpResponse +from urllib.request import urlopen + +from django.http import HttpResponse, StreamingHttpResponse +from django.views.decorators.csrf import csrf_exempt from .models import Person @@ -7,6 +10,10 @@ def example_view(request): return HttpResponse('example view') +def streaming_example_view(request): + return StreamingHttpResponse((b'I', b'am', b'a', b'stream')) + + def model_view(request): people = Person.objects.all() return HttpResponse('\n'.join(person.name for person in people)) @@ -20,3 +27,24 @@ def create_model_instance(request): def environ_view(request): return HttpResponse("\n".join("%s: %r" % (k, v) for k, v in request.environ.items())) + + +def subview(request): + return HttpResponse('subview') + + +def subview_calling_view(request): + with urlopen(request.GET['url'] + '/subview/') as response: + return HttpResponse('subview calling view: {}'.format(response.read().decode())) + + +def check_model_instance_from_subview(request): + with urlopen(request.GET['url'] + '/create_model_instance/'): + pass + with urlopen(request.GET['url'] + '/model_view/') as response: + return HttpResponse('subview calling view: {}'.format(response.read().decode())) + + +@csrf_exempt +def method_view(request): + return HttpResponse(request.method) diff --git a/tests/sessions_tests/models.py b/tests/sessions_tests/models.py index 7a358595e9b4..4fa216d9e65d 100644 --- a/tests/sessions_tests/models.py +++ b/tests/sessions_tests/models.py @@ -29,7 +29,7 @@ def get_model_class(cls): return CustomSession def create_model_instance(self, data): - obj = super(SessionStore, self).create_model_instance(data) + obj = super().create_model_instance(data) try: account_id = int(data.get('_auth_user_id')) diff --git a/tests/sessions_tests/tests.py b/tests/sessions_tests/tests.py index ae57f1d91973..733f5adb1dc5 100644 --- a/tests/sessions_tests/tests.py +++ b/tests/sessions_tests/tests.py @@ -2,20 +2,22 @@ import os import shutil import string -import sys import tempfile import unittest from datetime import timedelta +from http import cookies from django.conf import settings from django.contrib.sessions.backends.base import UpdateError from django.contrib.sessions.backends.cache import SessionStore as CacheSession -from django.contrib.sessions.backends.cached_db import \ - SessionStore as CacheDBSession +from django.contrib.sessions.backends.cached_db import ( + SessionStore as CacheDBSession, +) from django.contrib.sessions.backends.db import SessionStore as DatabaseSession from django.contrib.sessions.backends.file import SessionStore as FileSession -from django.contrib.sessions.backends.signed_cookies import \ - SessionStore as CookieSession +from django.contrib.sessions.backends.signed_cookies import ( + SessionStore as CookieSession, +) from django.contrib.sessions.exceptions import InvalidSessionKey from django.contrib.sessions.middleware import SessionMiddleware from django.contrib.sessions.models import Session @@ -30,15 +32,12 @@ from django.test import ( RequestFactory, TestCase, ignore_warnings, override_settings, ) -from django.test.utils import patch_logger -from django.utils import six, timezone -from django.utils.encoding import force_text -from django.utils.six.moves import http_cookies +from django.utils import timezone from .models import SessionStore as CustomDatabaseSession -class SessionTestsMixin(object): +class SessionTestsMixin: # This does not inherit from TestCase to avoid any tests being run with this # class, which wouldn't work, and to allow different TestCase subclasses to # be used. @@ -55,15 +54,15 @@ def tearDown(self): self.session.delete() def test_new_session(self): - self.assertFalse(self.session.modified) - self.assertFalse(self.session.accessed) + self.assertIs(self.session.modified, False) + self.assertIs(self.session.accessed, False) def test_get_empty(self): self.assertIsNone(self.session.get('cat')) def test_store(self): self.session['cat'] = "dog" - self.assertTrue(self.session.modified) + self.assertIs(self.session.modified, True) self.assertEqual(self.session.pop('cat'), 'dog') def test_pop(self): @@ -73,20 +72,20 @@ def test_pop(self): self.modified = False self.assertEqual(self.session.pop('some key'), 'exists') - self.assertTrue(self.session.accessed) - self.assertTrue(self.session.modified) + self.assertIs(self.session.accessed, True) + self.assertIs(self.session.modified, True) self.assertIsNone(self.session.get('some key')) def test_pop_default(self): self.assertEqual(self.session.pop('some key', 'does not exist'), 'does not exist') - self.assertTrue(self.session.accessed) - self.assertFalse(self.session.modified) + self.assertIs(self.session.accessed, True) + self.assertIs(self.session.modified, False) def test_pop_default_named_argument(self): self.assertEqual(self.session.pop('some key', default='does not exist'), 'does not exist') - self.assertTrue(self.session.accessed) - self.assertFalse(self.session.modified) + self.assertIs(self.session.accessed, True) + self.assertIs(self.session.modified, False) def test_pop_no_default_keyerror_raised(self): with self.assertRaises(KeyError): @@ -95,13 +94,13 @@ def test_pop_no_default_keyerror_raised(self): def test_setdefault(self): self.assertEqual(self.session.setdefault('foo', 'bar'), 'bar') self.assertEqual(self.session.setdefault('foo', 'baz'), 'bar') - self.assertTrue(self.session.accessed) - self.assertTrue(self.session.modified) + self.assertIs(self.session.accessed, True) + self.assertIs(self.session.modified, True) def test_update(self): self.session.update({'update key': 1}) - self.assertTrue(self.session.accessed) - self.assertTrue(self.session.modified) + self.assertIs(self.session.accessed, True) + self.assertIs(self.session.modified, True) self.assertEqual(self.session.get('update key', None), 1) def test_has_key(self): @@ -109,44 +108,34 @@ def test_has_key(self): self.session.modified = False self.session.accessed = False self.assertIn('some key', self.session) - self.assertTrue(self.session.accessed) - self.assertFalse(self.session.modified) + self.assertIs(self.session.accessed, True) + self.assertIs(self.session.modified, False) def test_values(self): self.assertEqual(list(self.session.values()), []) - self.assertTrue(self.session.accessed) + self.assertIs(self.session.accessed, True) self.session['some key'] = 1 - self.assertEqual(list(self.session.values()), [1]) - - def test_iterkeys(self): - self.session['x'] = 1 self.session.modified = False self.session.accessed = False - i = six.iterkeys(self.session) - self.assertTrue(hasattr(i, '__iter__')) - self.assertTrue(self.session.accessed) - self.assertFalse(self.session.modified) - self.assertEqual(list(i), ['x']) + self.assertEqual(list(self.session.values()), [1]) + self.assertIs(self.session.accessed, True) + self.assertIs(self.session.modified, False) - def test_itervalues(self): + def test_keys(self): self.session['x'] = 1 self.session.modified = False self.session.accessed = False - i = six.itervalues(self.session) - self.assertTrue(hasattr(i, '__iter__')) - self.assertTrue(self.session.accessed) - self.assertFalse(self.session.modified) - self.assertEqual(list(i), [1]) + self.assertEqual(list(self.session.keys()), ['x']) + self.assertIs(self.session.accessed, True) + self.assertIs(self.session.modified, False) - def test_iteritems(self): + def test_items(self): self.session['x'] = 1 self.session.modified = False self.session.accessed = False - i = six.iteritems(self.session) - self.assertTrue(hasattr(i, '__iter__')) - self.assertTrue(self.session.accessed) - self.assertFalse(self.session.modified) - self.assertEqual(list(i), [('x', 1)]) + self.assertEqual(list(self.session.items()), [('x', 1)]) + self.assertIs(self.session.accessed, True) + self.assertIs(self.session.modified, False) def test_clear(self): self.session['x'] = 1 @@ -155,28 +144,28 @@ def test_clear(self): self.assertEqual(list(self.session.items()), [('x', 1)]) self.session.clear() self.assertEqual(list(self.session.items()), []) - self.assertTrue(self.session.accessed) - self.assertTrue(self.session.modified) + self.assertIs(self.session.accessed, True) + self.assertIs(self.session.modified, True) def test_save(self): self.session.save() - self.assertTrue(self.session.exists(self.session.session_key)) + self.assertIs(self.session.exists(self.session.session_key), True) def test_delete(self): self.session.save() self.session.delete(self.session.session_key) - self.assertFalse(self.session.exists(self.session.session_key)) + self.assertIs(self.session.exists(self.session.session_key), False) def test_flush(self): self.session['foo'] = 'bar' self.session.save() prev_key = self.session.session_key self.session.flush() - self.assertFalse(self.session.exists(prev_key)) + self.assertIs(self.session.exists(prev_key), False) self.assertNotEqual(self.session.session_key, prev_key) self.assertIsNone(self.session.session_key) - self.assertTrue(self.session.modified) - self.assertTrue(self.session.accessed) + self.assertIs(self.session.modified, True) + self.assertIs(self.session.accessed, True) def test_cycle(self): self.session['a'], self.session['b'] = 'c', 'd' @@ -184,13 +173,18 @@ def test_cycle(self): prev_key = self.session.session_key prev_data = list(self.session.items()) self.session.cycle_key() - self.assertFalse(self.session.exists(prev_key)) + self.assertIs(self.session.exists(prev_key), False) self.assertNotEqual(self.session.session_key, prev_key) self.assertEqual(list(self.session.items()), prev_data) def test_cycle_with_no_session_cache(self): - self.assertFalse(hasattr(self.session, '_session_cache')) + self.session['a'], self.session['b'] = 'c', 'd' + self.session.save() + prev_data = self.session.items() + self.session = self.backend(self.session.session_key) + self.assertIs(hasattr(self.session, '_session_cache'), False) self.session.cycle_key() + self.assertCountEqual(self.session.items(), prev_data) def test_save_doesnt_clear_data(self): self.session['a'] = 'b' @@ -292,23 +286,23 @@ def test_get_expire_at_browser_close(self): # set_expiry calls with override_settings(SESSION_EXPIRE_AT_BROWSER_CLOSE=False): self.session.set_expiry(10) - self.assertFalse(self.session.get_expire_at_browser_close()) + self.assertIs(self.session.get_expire_at_browser_close(), False) self.session.set_expiry(0) - self.assertTrue(self.session.get_expire_at_browser_close()) + self.assertIs(self.session.get_expire_at_browser_close(), True) self.session.set_expiry(None) - self.assertFalse(self.session.get_expire_at_browser_close()) + self.assertIs(self.session.get_expire_at_browser_close(), False) with override_settings(SESSION_EXPIRE_AT_BROWSER_CLOSE=True): self.session.set_expiry(10) - self.assertFalse(self.session.get_expire_at_browser_close()) + self.assertIs(self.session.get_expire_at_browser_close(), False) self.session.set_expiry(0) - self.assertTrue(self.session.get_expire_at_browser_close()) + self.assertIs(self.session.get_expire_at_browser_close(), True) self.session.set_expiry(None) - self.assertTrue(self.session.get_expire_at_browser_close()) + self.assertIs(self.session.get_expire_at_browser_close(), True) def test_decode(self): # Ensure we can decode what we encode @@ -317,12 +311,11 @@ def test_decode(self): self.assertEqual(self.session.decode(encoded), data) def test_decode_failure_logged_to_security(self): - bad_encode = base64.b64encode(b'flaskdj:alkdjf') - with patch_logger('django.security.SuspiciousSession', 'warning') as calls: + bad_encode = base64.b64encode(b'flaskdj:alkdjf').decode('ascii') + with self.assertLogs('django.security.SuspiciousSession', 'WARNING') as cm: self.assertEqual({}, self.session.decode(bad_encode)) - # check that the failed decode is logged - self.assertEqual(len(calls), 1) - self.assertIn('corrupted', calls[0]) + # The failed decode is logged. + self.assertIn('corrupted', cm.output[0]) def test_actual_expiry(self): # this doesn't work with JSONSerializer (serializing timedelta) @@ -354,7 +347,8 @@ def test_session_load_does_not_create_record(self): session = self.backend('someunknownkey') session.load() - self.assertFalse(session.exists(session.session_key)) + self.assertIsNone(session.session_key) + self.assertIs(session.exists(session.session_key), False) # provided unknown key was cycled, not reused self.assertNotEqual(session.session_key, 'someunknownkey') @@ -398,7 +392,7 @@ def test_session_str(self): session_key = self.session.session_key s = self.model.objects.get(session_key=session_key) - self.assertEqual(force_text(s), session_key) + self.assertEqual(str(s), session_key) def test_session_get_decoded(self): """ @@ -487,7 +481,7 @@ class CacheDBSessionTests(SessionTestsMixin, TestCase): def test_exists_searches_cache_first(self): self.session.save() with self.assertNumQueries(0): - self.assertTrue(self.session.exists(self.session.session_key)) + self.assertIs(self.session.exists(self.session.session_key), True) # Some backends might issue a warning @ignore_warnings(module="django.core.cache.backends.base") @@ -519,15 +513,16 @@ def setUp(self): # Reset the file session backend's internal caches if hasattr(self.backend, '_storage_path'): del self.backend._storage_path - super(FileSessionTests, self).setUp() + super().setUp() def tearDown(self): - super(FileSessionTests, self).tearDown() + super().tearDown() settings.SESSION_FILE_PATH = self.original_session_file_path shutil.rmtree(self.temp_session_store) @override_settings( - SESSION_FILE_PATH="/if/this/directory/exists/you/have/a/weird/computer") + SESSION_FILE_PATH='/if/this/directory/exists/you/have/a/weird/computer', + ) def test_configuration_check(self): del self.backend._storage_path # Make sure the file backend checks for a good storage dir @@ -630,10 +625,11 @@ def test_create_and_save(self): class SessionMiddlewareTests(TestCase): + request_factory = RequestFactory() @override_settings(SESSION_COOKIE_SECURE=True) def test_secure_session_cookie(self): - request = RequestFactory().get('/') + request = self.request_factory.get('/') response = HttpResponse('Session test') middleware = SessionMiddleware() @@ -643,12 +639,11 @@ def test_secure_session_cookie(self): # Handle the response through the middleware response = middleware.process_response(request, response) - self.assertTrue( - response.cookies[settings.SESSION_COOKIE_NAME]['secure']) + self.assertIs(response.cookies[settings.SESSION_COOKIE_NAME]['secure'], True) @override_settings(SESSION_COOKIE_HTTPONLY=True) def test_httponly_session_cookie(self): - request = RequestFactory().get('/') + request = self.request_factory.get('/') response = HttpResponse('Session test') middleware = SessionMiddleware() @@ -658,16 +653,25 @@ def test_httponly_session_cookie(self): # Handle the response through the middleware response = middleware.process_response(request, response) - self.assertTrue( - response.cookies[settings.SESSION_COOKIE_NAME]['httponly']) + self.assertIs(response.cookies[settings.SESSION_COOKIE_NAME]['httponly'], True) self.assertIn( - http_cookies.Morsel._reserved['httponly'], + cookies.Morsel._reserved['httponly'], str(response.cookies[settings.SESSION_COOKIE_NAME]) ) + @override_settings(SESSION_COOKIE_SAMESITE='Strict') + def test_samesite_session_cookie(self): + request = self.request_factory.get('/') + response = HttpResponse() + middleware = SessionMiddleware() + middleware.process_request(request) + request.session['hello'] = 'world' + response = middleware.process_response(request, response) + self.assertEqual(response.cookies[settings.SESSION_COOKIE_NAME]['samesite'], 'Strict') + @override_settings(SESSION_COOKIE_HTTPONLY=False) def test_no_httponly_session_cookie(self): - request = RequestFactory().get('/') + request = self.request_factory.get('/') response = HttpResponse('Session test') middleware = SessionMiddleware() @@ -677,13 +681,14 @@ def test_no_httponly_session_cookie(self): # Handle the response through the middleware response = middleware.process_response(request, response) - self.assertFalse(response.cookies[settings.SESSION_COOKIE_NAME]['httponly']) - - self.assertNotIn(http_cookies.Morsel._reserved['httponly'], - str(response.cookies[settings.SESSION_COOKIE_NAME])) + self.assertEqual(response.cookies[settings.SESSION_COOKIE_NAME]['httponly'], '') + self.assertNotIn( + cookies.Morsel._reserved['httponly'], + str(response.cookies[settings.SESSION_COOKIE_NAME]) + ) def test_session_save_on_500(self): - request = RequestFactory().get('/') + request = self.request_factory.get('/') response = HttpResponse('Horrible error') response.status_code = 500 middleware = SessionMiddleware() @@ -700,7 +705,7 @@ def test_session_save_on_500(self): def test_session_update_error_redirect(self): path = '/foo/' - request = RequestFactory().get(path) + request = self.request_factory.get(path) response = HttpResponse() middleware = SessionMiddleware() @@ -719,7 +724,7 @@ def test_session_update_error_redirect(self): middleware.process_response(request, response) def test_session_delete_on_end(self): - request = RequestFactory().get('/') + request = self.request_factory.get('/') response = HttpResponse('Session test') middleware = SessionMiddleware() @@ -735,19 +740,18 @@ def test_session_delete_on_end(self): # The cookie was deleted, not recreated. # A deleted cookie header looks like: - # Set-Cookie: sessionid=; expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Path=/ + # Set-Cookie: sessionid=; expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/ self.assertEqual( - 'Set-Cookie: {}={}; expires=Thu, 01-Jan-1970 00:00:00 GMT; ' + 'Set-Cookie: {}=""; expires=Thu, 01 Jan 1970 00:00:00 GMT; ' 'Max-Age=0; Path=/'.format( settings.SESSION_COOKIE_NAME, - '""' if sys.version_info >= (3, 5) else '', ), str(response.cookies[settings.SESSION_COOKIE_NAME]) ) @override_settings(SESSION_COOKIE_DOMAIN='.example.local', SESSION_COOKIE_PATH='/example/') def test_session_delete_on_end_with_custom_domain_and_path(self): - request = RequestFactory().get('/') + request = self.request_factory.get('/') response = HttpResponse('Session test') middleware = SessionMiddleware() @@ -764,19 +768,18 @@ def test_session_delete_on_end_with_custom_domain_and_path(self): # The cookie was deleted, not recreated. # A deleted cookie header with a custom domain and path looks like: # Set-Cookie: sessionid=; Domain=.example.local; - # expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; + # expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; # Path=/example/ self.assertEqual( - 'Set-Cookie: {}={}; Domain=.example.local; expires=Thu, ' - '01-Jan-1970 00:00:00 GMT; Max-Age=0; Path=/example/'.format( + 'Set-Cookie: {}=""; Domain=.example.local; expires=Thu, ' + '01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/example/'.format( settings.SESSION_COOKIE_NAME, - '""' if sys.version_info >= (3, 5) else '', ), str(response.cookies[settings.SESSION_COOKIE_NAME]) ) def test_flush_empty_without_session_cookie_doesnt_set_cookie(self): - request = RequestFactory().get('/') + request = self.request_factory.get('/') response = HttpResponse('Session test') middleware = SessionMiddleware() @@ -797,7 +800,7 @@ def test_empty_session_saved(self): If a session is emptied of data but still has a key, it should still be updated. """ - request = RequestFactory().get('/') + request = self.request_factory.get('/') response = HttpResponse('Session test') middleware = SessionMiddleware() @@ -856,7 +859,7 @@ def test_cycle(self): @unittest.expectedFailure def test_actual_expiry(self): # The cookie backend doesn't handle non-default expiry dates, see #19201 - super(CookieSessionTests, self).test_actual_expiry() + super().test_actual_expiry() def test_unpickling_exception(self): # signed_cookies backend should handle unpickle exceptions gracefully diff --git a/tests/settings_tests/test_file_charset.py b/tests/settings_tests/test_file_charset.py new file mode 100644 index 000000000000..1be96a26d2a5 --- /dev/null +++ b/tests/settings_tests/test_file_charset.py @@ -0,0 +1,40 @@ +import sys +from types import ModuleType + +from django.conf import FILE_CHARSET_DEPRECATED_MSG, Settings, settings +from django.test import SimpleTestCase, ignore_warnings +from django.utils.deprecation import RemovedInDjango31Warning + + +class DeprecationTests(SimpleTestCase): + msg = FILE_CHARSET_DEPRECATED_MSG + + def test_override_settings_warning(self): + with self.assertRaisesMessage(RemovedInDjango31Warning, self.msg): + with self.settings(FILE_CHARSET='latin1'): + pass + + def test_settings_init_warning(self): + settings_module = ModuleType('fake_settings_module') + settings_module.FILE_CHARSET = 'latin1' + settings_module.SECRET_KEY = 'ABC' + sys.modules['fake_settings_module'] = settings_module + try: + with self.assertRaisesMessage(RemovedInDjango31Warning, self.msg): + Settings('fake_settings_module') + finally: + del sys.modules['fake_settings_module'] + + def test_access_warning(self): + with self.assertRaisesMessage(RemovedInDjango31Warning, self.msg): + settings.FILE_CHARSET + # Works a second time. + with self.assertRaisesMessage(RemovedInDjango31Warning, self.msg): + settings.FILE_CHARSET + + @ignore_warnings(category=RemovedInDjango31Warning) + def test_access(self): + with self.settings(FILE_CHARSET='latin1'): + self.assertEqual(settings.FILE_CHARSET, 'latin1') + # Works a second time. + self.assertEqual(settings.FILE_CHARSET, 'latin1') diff --git a/tests/settings_tests/tests.py b/tests/settings_tests/tests.py index bf015affc2d3..d0127db42703 100644 --- a/tests/settings_tests/tests.py +++ b/tests/settings_tests/tests.py @@ -1,8 +1,8 @@ import os import sys import unittest -import warnings from types import ModuleType +from unittest import mock from django.conf import ENVIRONMENT_VARIABLE, LazySettings, Settings, settings from django.core.exceptions import ImproperlyConfigured @@ -11,6 +11,7 @@ SimpleTestCase, TestCase, TransactionTestCase, modify_settings, override_settings, signals, ) +from django.test.utils import requires_tz_support @modify_settings(ITEMS={ @@ -24,8 +25,8 @@ class FullyDecoratedTranTestCase(TransactionTestCase): available_apps = [] def test_override(self): - self.assertListEqual(settings.ITEMS, ['b', 'c', 'd']) - self.assertListEqual(settings.ITEMS_OUTER, [1, 2, 3]) + self.assertEqual(settings.ITEMS, ['b', 'c', 'd']) + self.assertEqual(settings.ITEMS_OUTER, [1, 2, 3]) self.assertEqual(settings.TEST, 'override') self.assertEqual(settings.TEST_OUTER, 'outer') @@ -35,8 +36,8 @@ def test_override(self): 'remove': ['d', 'c'], }) def test_method_list_override(self): - self.assertListEqual(settings.ITEMS, ['a', 'b', 'e', 'f']) - self.assertListEqual(settings.ITEMS_OUTER, [1, 2, 3]) + self.assertEqual(settings.ITEMS, ['a', 'b', 'e', 'f']) + self.assertEqual(settings.ITEMS_OUTER, [1, 2, 3]) @modify_settings(ITEMS={ 'append': ['b'], @@ -44,7 +45,7 @@ def test_method_list_override(self): 'remove': ['a', 'c', 'e'], }) def test_method_list_override_no_ops(self): - self.assertListEqual(settings.ITEMS, ['b', 'd']) + self.assertEqual(settings.ITEMS, ['b', 'd']) @modify_settings(ITEMS={ 'append': 'e', @@ -52,12 +53,12 @@ def test_method_list_override_no_ops(self): 'remove': 'c', }) def test_method_list_override_strings(self): - self.assertListEqual(settings.ITEMS, ['a', 'b', 'd', 'e']) + self.assertEqual(settings.ITEMS, ['a', 'b', 'd', 'e']) @modify_settings(ITEMS={'remove': ['b', 'd']}) @modify_settings(ITEMS={'append': ['b'], 'prepend': ['d']}) def test_method_list_override_nested_order(self): - self.assertListEqual(settings.ITEMS, ['d', 'c', 'b']) + self.assertEqual(settings.ITEMS, ['d', 'c', 'b']) @override_settings(TEST='override2') def test_method_override(self): @@ -80,7 +81,7 @@ def test_decorated_testcase_module(self): class FullyDecoratedTestCase(TestCase): def test_override(self): - self.assertListEqual(settings.ITEMS, ['b', 'c', 'd']) + self.assertEqual(settings.ITEMS, ['b', 'c', 'd']) self.assertEqual(settings.TEST, 'override') @modify_settings(ITEMS={ @@ -90,7 +91,7 @@ def test_override(self): }) @override_settings(TEST='override2') def test_method_override(self): - self.assertListEqual(settings.ITEMS, ['a', 'b', 'd', 'e']) + self.assertEqual(settings.ITEMS, ['a', 'b', 'd', 'e']) self.assertEqual(settings.TEST, 'override2') @@ -108,7 +109,7 @@ class ClassDecoratedTestCase(ClassDecoratedTestCaseSuper): @classmethod def setUpClass(cls): - super(ClassDecoratedTestCase, cls).setUpClass() + super().setUpClass() cls.foo = getattr(settings, 'TEST', 'BUG') def test_override(self): @@ -127,7 +128,7 @@ def test_max_recursion_error(self): Overriding a method on a super class and then calling that method on the super class should not trigger infinite recursion. See #17011. """ - super(ClassDecoratedTestCase, self).test_max_recursion_error() + super().test_max_recursion_error() @modify_settings(ITEMS={'append': 'mother'}) @@ -232,11 +233,12 @@ def test_settings_delete(self): settings.TEST = 'test' self.assertEqual('test', settings.TEST) del settings.TEST - with self.assertRaises(AttributeError): + msg = "'Settings' object has no attribute 'TEST'" + with self.assertRaisesMessage(AttributeError, msg): getattr(settings, 'TEST') def test_settings_delete_wrapped(self): - with self.assertRaises(TypeError): + with self.assertRaisesMessage(TypeError, "can't delete _wrapped."): delattr(settings, '_wrapped') def test_override_settings_delete(self): @@ -286,6 +288,42 @@ def test_override_settings_nested(self): with self.assertRaises(AttributeError): getattr(settings, 'TEST2') + def test_no_secret_key(self): + settings_module = ModuleType('fake_settings_module') + sys.modules['fake_settings_module'] = settings_module + msg = 'The SECRET_KEY setting must not be empty.' + try: + with self.assertRaisesMessage(ImproperlyConfigured, msg): + Settings('fake_settings_module') + finally: + del sys.modules['fake_settings_module'] + + def test_no_settings_module(self): + msg = ( + 'Requested setting%s, but settings are not configured. You ' + 'must either define the environment variable DJANGO_SETTINGS_MODULE ' + 'or call settings.configure() before accessing settings.' + ) + orig_settings = os.environ[ENVIRONMENT_VARIABLE] + os.environ[ENVIRONMENT_VARIABLE] = '' + try: + with self.assertRaisesMessage(ImproperlyConfigured, msg % 's'): + settings._setup() + with self.assertRaisesMessage(ImproperlyConfigured, msg % ' TEST'): + settings._setup('TEST') + finally: + os.environ[ENVIRONMENT_VARIABLE] = orig_settings + + def test_already_configured(self): + with self.assertRaisesMessage(RuntimeError, 'Settings already configured.'): + settings.configure() + + @requires_tz_support + @mock.patch('django.conf.global_settings.TIME_ZONE', 'test') + def test_incorrect_timezone(self): + with self.assertRaisesMessage(ValueError, 'Incorrect timezone setting: test'): + settings._setup() + class TestComplexSettingOverride(SimpleTestCase): def setUp(self): @@ -298,16 +336,11 @@ def tearDown(self): def test_complex_override_warning(self): """Regression test for #19031""" - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - + msg = 'Overriding setting TEST_WARN can lead to unexpected behavior.' + with self.assertWarnsMessage(UserWarning, msg) as cm: with override_settings(TEST_WARN='override'): self.assertEqual(settings.TEST_WARN, 'override') - - self.assertEqual(len(w), 1) - # File extension may by .py, .pyc, etc. Compare only basename. - self.assertEqual(os.path.splitext(w[0].filename)[0], os.path.splitext(__file__)[0]) - self.assertEqual(str(w[0].message), 'Overriding setting TEST_WARN can lead to unexpected behavior.') + self.assertEqual(cm.filename, __file__) class SecureProxySslHeaderTest(SimpleTestCase): @@ -334,6 +367,18 @@ def test_set_with_xheader_right(self): req.META['HTTP_X_FORWARDED_PROTOCOL'] = 'https' self.assertIs(req.is_secure(), True) + @override_settings(SECURE_PROXY_SSL_HEADER=('HTTP_X_FORWARDED_PROTOCOL', 'https')) + def test_xheader_preferred_to_underlying_request(self): + class ProxyRequest(HttpRequest): + def _get_scheme(self): + """Proxy always connecting via HTTPS""" + return 'https' + + # Client connects via HTTP. + req = ProxyRequest() + req.META['HTTP_X_FORWARDED_PROTOCOL'] = 'http' + self.assertIs(req.is_secure(), False) + class IsOverriddenTest(SimpleTestCase): def test_configure(self): @@ -408,3 +453,106 @@ def test_tuple_settings(self): finally: del sys.modules['fake_settings_module'] delattr(settings_module, setting) + + +class SettingChangeEnterException(Exception): + pass + + +class SettingChangeExitException(Exception): + pass + + +class OverrideSettingsIsolationOnExceptionTests(SimpleTestCase): + """ + The override_settings context manager restore settings if one of the + receivers of "setting_changed" signal fails. Check the three cases of + receiver failure detailed in receiver(). In each case, ALL receivers are + called when exiting the context manager. + """ + def setUp(self): + signals.setting_changed.connect(self.receiver) + self.addCleanup(signals.setting_changed.disconnect, self.receiver) + # Create a spy that's connected to the `setting_changed` signal and + # executed AFTER `self.receiver`. + self.spy_receiver = mock.Mock() + signals.setting_changed.connect(self.spy_receiver) + self.addCleanup(signals.setting_changed.disconnect, self.spy_receiver) + + def receiver(self, **kwargs): + """ + A receiver that fails while certain settings are being changed. + - SETTING_BOTH raises an error while receiving the signal + on both entering and exiting the context manager. + - SETTING_ENTER raises an error only on enter. + - SETTING_EXIT raises an error only on exit. + """ + setting = kwargs['setting'] + enter = kwargs['enter'] + if setting in ('SETTING_BOTH', 'SETTING_ENTER') and enter: + raise SettingChangeEnterException + if setting in ('SETTING_BOTH', 'SETTING_EXIT') and not enter: + raise SettingChangeExitException + + def check_settings(self): + """Assert that settings for these tests aren't present.""" + self.assertFalse(hasattr(settings, 'SETTING_BOTH')) + self.assertFalse(hasattr(settings, 'SETTING_ENTER')) + self.assertFalse(hasattr(settings, 'SETTING_EXIT')) + self.assertFalse(hasattr(settings, 'SETTING_PASS')) + + def check_spy_receiver_exit_calls(self, call_count): + """ + Assert that `self.spy_receiver` was called exactly `call_count` times + with the ``enter=False`` keyword argument. + """ + kwargs_with_exit = [ + kwargs for args, kwargs in self.spy_receiver.call_args_list + if ('enter', False) in kwargs.items() + ] + self.assertEqual(len(kwargs_with_exit), call_count) + + def test_override_settings_both(self): + """Receiver fails on both enter and exit.""" + with self.assertRaises(SettingChangeEnterException): + with override_settings(SETTING_PASS='BOTH', SETTING_BOTH='BOTH'): + pass + + self.check_settings() + # Two settings were touched, so expect two calls of `spy_receiver`. + self.check_spy_receiver_exit_calls(call_count=2) + + def test_override_settings_enter(self): + """Receiver fails on enter only.""" + with self.assertRaises(SettingChangeEnterException): + with override_settings(SETTING_PASS='ENTER', SETTING_ENTER='ENTER'): + pass + + self.check_settings() + # Two settings were touched, so expect two calls of `spy_receiver`. + self.check_spy_receiver_exit_calls(call_count=2) + + def test_override_settings_exit(self): + """Receiver fails on exit only.""" + with self.assertRaises(SettingChangeExitException): + with override_settings(SETTING_PASS='EXIT', SETTING_EXIT='EXIT'): + pass + + self.check_settings() + # Two settings were touched, so expect two calls of `spy_receiver`. + self.check_spy_receiver_exit_calls(call_count=2) + + def test_override_settings_reusable_on_enter(self): + """ + Error is raised correctly when reusing the same override_settings + instance. + """ + @override_settings(SETTING_ENTER='ENTER') + def decorated_function(): + pass + + with self.assertRaises(SettingChangeEnterException): + decorated_function() + signals.setting_changed.disconnect(self.receiver) + # This call shouldn't raise any errors. + decorated_function() diff --git a/tests/shell/tests.py b/tests/shell/tests.py index f2d1d0e392c5..f33a9ae701de 100644 --- a/tests/shell/tests.py +++ b/tests/shell/tests.py @@ -1,16 +1,17 @@ import sys import unittest +from unittest import mock from django import __version__ from django.core.management import CommandError, call_command -from django.test import SimpleTestCase, mock -from django.test.utils import captured_stdin, captured_stdout, patch_logger +from django.test import SimpleTestCase +from django.test.utils import captured_stdin, captured_stdout class ShellCommandTestCase(SimpleTestCase): def test_command_option(self): - with patch_logger('test', 'info') as logger: + with self.assertLogs('test', 'INFO') as cm: call_command( 'shell', command=( @@ -18,8 +19,7 @@ def test_command_option(self): 'getLogger("test").info(django.__version__)' ), ) - self.assertEqual(len(logger), 1) - self.assertEqual(logger[0], __version__) + self.assertEqual(cm.records[0].getMessage(), __version__) @unittest.skipIf(sys.platform == 'win32', "Windows select() doesn't support file descriptors.") @mock.patch('django.core.management.commands.shell.select') diff --git a/tests/shortcuts/test_render_to_response.py b/tests/shortcuts/test_render_to_response.py new file mode 100644 index 000000000000..7bf7ac9ad6b2 --- /dev/null +++ b/tests/shortcuts/test_render_to_response.py @@ -0,0 +1,39 @@ +from django.test import SimpleTestCase, ignore_warnings, override_settings +from django.test.utils import require_jinja2 +from django.utils.deprecation import RemovedInDjango30Warning + + +@ignore_warnings(category=RemovedInDjango30Warning) +@override_settings(ROOT_URLCONF='shortcuts.urls') +class RenderToResponseTests(SimpleTestCase): + + def test_render_to_response(self): + response = self.client.get('/render_to_response/') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b'FOO.BAR..\n') + self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8') + + def test_render_to_response_with_multiple_templates(self): + response = self.client.get('/render_to_response/multiple_templates/') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b'FOO.BAR..\n') + + def test_render_to_response_with_content_type(self): + response = self.client.get('/render_to_response/content_type/') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b'FOO.BAR..\n') + self.assertEqual(response['Content-Type'], 'application/x-rendertest') + + def test_render_to_response_with_status(self): + response = self.client.get('/render_to_response/status/') + self.assertEqual(response.status_code, 403) + self.assertEqual(response.content, b'FOO.BAR..\n') + + @require_jinja2 + def test_render_to_response_with_using(self): + response = self.client.get('/render_to_response/using/') + self.assertEqual(response.content, b'DTL\n') + response = self.client.get('/render_to_response/using/?using=django') + self.assertEqual(response.content, b'DTL\n') + response = self.client.get('/render_to_response/using/?using=jinja2') + self.assertEqual(response.content, b'Jinja2\n') diff --git a/tests/shortcuts/tests.py b/tests/shortcuts/tests.py index ec792264467f..fe68d6776754 100644 --- a/tests/shortcuts/tests.py +++ b/tests/shortcuts/tests.py @@ -2,41 +2,8 @@ from django.test.utils import require_jinja2 -@override_settings( - ROOT_URLCONF='shortcuts.urls', -) -class ShortcutTests(SimpleTestCase): - - def test_render_to_response(self): - response = self.client.get('/render_to_response/') - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b'FOO.BAR..\n') - self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8') - - def test_render_to_response_with_multiple_templates(self): - response = self.client.get('/render_to_response/multiple_templates/') - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b'FOO.BAR..\n') - - def test_render_to_response_with_content_type(self): - response = self.client.get('/render_to_response/content_type/') - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b'FOO.BAR..\n') - self.assertEqual(response['Content-Type'], 'application/x-rendertest') - - def test_render_to_response_with_status(self): - response = self.client.get('/render_to_response/status/') - self.assertEqual(response.status_code, 403) - self.assertEqual(response.content, b'FOO.BAR..\n') - - @require_jinja2 - def test_render_to_response_with_using(self): - response = self.client.get('/render_to_response/using/') - self.assertEqual(response.content, b'DTL\n') - response = self.client.get('/render_to_response/using/?using=django') - self.assertEqual(response.content, b'DTL\n') - response = self.client.get('/render_to_response/using/?using=jinja2') - self.assertEqual(response.content, b'Jinja2\n') +@override_settings(ROOT_URLCONF='shortcuts.urls') +class RenderTests(SimpleTestCase): def test_render(self): response = self.client.get('/render/') diff --git a/tests/shortcuts/urls.py b/tests/shortcuts/urls.py index e24ee9314c88..0ac994b7d314 100644 --- a/tests/shortcuts/urls.py +++ b/tests/shortcuts/urls.py @@ -1,16 +1,16 @@ -from django.conf.urls import url +from django.urls import path from . import views urlpatterns = [ - url(r'^render_to_response/$', views.render_to_response_view), - url(r'^render_to_response/multiple_templates/$', views.render_to_response_view_with_multiple_templates), - url(r'^render_to_response/content_type/$', views.render_to_response_view_with_content_type), - url(r'^render_to_response/status/$', views.render_to_response_view_with_status), - url(r'^render_to_response/using/$', views.render_to_response_view_with_using), - url(r'^render/$', views.render_view), - url(r'^render/multiple_templates/$', views.render_view_with_multiple_templates), - url(r'^render/content_type/$', views.render_view_with_content_type), - url(r'^render/status/$', views.render_view_with_status), - url(r'^render/using/$', views.render_view_with_using), + path('render_to_response/', views.render_to_response_view), + path('render_to_response/multiple_templates/', views.render_to_response_view_with_multiple_templates), + path('render_to_response/content_type/', views.render_to_response_view_with_content_type), + path('render_to_response/status/', views.render_to_response_view_with_status), + path('render_to_response/using/', views.render_to_response_view_with_using), + path('render/', views.render_view), + path('render/multiple_templates/', views.render_view_with_multiple_templates), + path('render/content_type/', views.render_view_with_content_type), + path('render/status/', views.render_view_with_status), + path('render/using/', views.render_view_with_using), ] diff --git a/tests/signals/models.py b/tests/signals/models.py index 2f76343ecf13..960df390de04 100644 --- a/tests/signals/models.py +++ b/tests/signals/models.py @@ -1,13 +1,9 @@ """ Testing signals before/after saving and deleting. """ -from __future__ import unicode_literals - from django.db import models -from django.utils.encoding import python_2_unicode_compatible -@python_2_unicode_compatible class Person(models.Model): first_name = models.CharField(max_length=20) last_name = models.CharField(max_length=20) @@ -16,7 +12,6 @@ def __str__(self): return "%s %s" % (self.first_name, self.last_name) -@python_2_unicode_compatible class Car(models.Model): make = models.CharField(max_length=20) model = models.CharField(max_length=20) @@ -25,7 +20,6 @@ def __str__(self): return "%s %s" % (self.make, self.model) -@python_2_unicode_compatible class Author(models.Model): name = models.CharField(max_length=20) @@ -33,7 +27,6 @@ def __str__(self): return self.name -@python_2_unicode_compatible class Book(models.Model): name = models.CharField(max_length=20) authors = models.ManyToManyField(Author) diff --git a/tests/signals/tests.py b/tests/signals/tests.py index 7fd04df36a88..e0a24ccc6ea1 100644 --- a/tests/signals/tests.py +++ b/tests/signals/tests.py @@ -1,17 +1,16 @@ -from __future__ import unicode_literals +from unittest import mock from django.apps.registry import Apps from django.db import models from django.db.models import signals from django.dispatch import receiver -from django.test import TestCase, mock +from django.test import SimpleTestCase, TestCase from django.test.utils import isolate_apps -from django.utils import six from .models import Author, Book, Car, Person -class BaseSignalTest(TestCase): +class BaseSignalSetup: def setUp(self): # Save up the number of connected signals so that we can check at the # end that all the signals we register get properly unregistered (#9989) @@ -33,7 +32,7 @@ def tearDown(self): self.assertEqual(self.pre_signals, post_signals) -class SignalTests(BaseSignalTest): +class SignalTests(BaseSignalSetup, TestCase): def test_model_pre_init_and_post_init(self): data = [] @@ -125,7 +124,7 @@ def pre_delete_handler(signal, sender, instance, **kwargs): ) # #8285: signals can be any callable - class PostDeleteHandler(object): + class PostDeleteHandler: def __init__(self, data): self.data = data @@ -162,7 +161,7 @@ def __call__(self, signal, sender, instance, **kwargs): Person.objects.all(), [ "James Jones", ], - six.text_type + str ) finally: signals.pre_delete.disconnect(pre_delete_handler) @@ -250,7 +249,7 @@ def test_disconnect_in_dispatch(self): dispatching. """ - class Handler(object): + class Handler: def __init__(self, param): self.param = param self._run = False @@ -282,9 +281,9 @@ def callback(sender, args, **kwargs): ref.assert_not_called() -class LazyModelRefTest(BaseSignalTest): +class LazyModelRefTests(BaseSignalSetup, SimpleTestCase): def setUp(self): - super(LazyModelRefTest, self).setUp() + super().setUp() self.received = [] def receiver(self, **kwargs): diff --git a/tests/signed_cookies_tests/tests.py b/tests/signed_cookies_tests/tests.py index 15b0a7c033c0..b7ea902bf49e 100644 --- a/tests/signed_cookies_tests/tests.py +++ b/tests/signed_cookies_tests/tests.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.core import signing from django.http import HttpRequest, HttpResponse from django.test import SimpleTestCase, override_settings diff --git a/tests/signing/tests.py b/tests/signing/tests.py index e2677dfc1e54..8e0cb0dc3b79 100644 --- a/tests/signing/tests.py +++ b/tests/signing/tests.py @@ -1,12 +1,8 @@ -from __future__ import unicode_literals - import datetime from django.core import signing from django.test import SimpleTestCase from django.test.utils import freeze_time -from django.utils import six -from django.utils.encoding import force_str class TestSigner(SimpleTestCase): @@ -18,11 +14,11 @@ def test_signature(self): for s in ( b'hello', b'3098247:529:087:', - '\u2019'.encode('utf-8'), + '\u2019'.encode(), ): self.assertEqual( signer.signature(s), - signing.base64_hmac(signer.salt + 'signer', s, 'predictable-secret').decode() + signing.base64_hmac(signer.salt + 'signer', s, 'predictable-secret') ) self.assertNotEqual(signer.signature(s), signer2.signature(s)) @@ -31,7 +27,7 @@ def test_signature_with_salt(self): signer = signing.Signer('predictable-secret', salt='extra-salt') self.assertEqual( signer.signature('hello'), - signing.base64_hmac('extra-salt' + 'signer', 'hello', 'predictable-secret').decode() + signing.base64_hmac('extra-salt' + 'signer', 'hello', 'predictable-secret') ) self.assertNotEqual( signing.Signer('predictable-secret', salt='one').signature('hello'), @@ -47,12 +43,10 @@ def test_sign_unsign(self): 'jkw osanteuh ,rcuh nthu aou oauh ,ud du', '\u2019', ] - if six.PY2: - examples.append(b'a byte string') for example in examples: signed = signer.sign(example) self.assertIsInstance(signed, str) - self.assertNotEqual(force_str(example), signed) + self.assertNotEqual(example, signed) self.assertEqual(example, signer.unsign(signed)) def test_unsign_detects_tampering(self): @@ -75,11 +69,9 @@ def test_dumps_loads(self): "dumps and loads be reversible for any JSON serializable object" objects = [ ['a', 'list'], - 'a unicode string \u2019', + 'a string \u2019', {'a': 'dictionary'}, ] - if six.PY2: - objects.append(b'a byte string') for o in objects: self.assertNotEqual(o, signing.dumps(o)) self.assertEqual(o, signing.loads(signing.dumps(o))) diff --git a/tests/sitemaps_tests/base.py b/tests/sitemaps_tests/base.py index 473ced7d3af9..3373af98e73d 100644 --- a/tests/sitemaps_tests/base.py +++ b/tests/sitemaps_tests/base.py @@ -13,16 +13,19 @@ class SitemapTestsBase(TestCase): sites_installed = apps.is_installed('django.contrib.sites') domain = 'example.com' if sites_installed else 'testserver' + @classmethod + def setUpTestData(cls): + # Create an object for sitemap content. + TestModel.objects.create(name='Test Object') + cls.i18n_model = I18nTestModel.objects.create(name='Test Object') + def setUp(self): self.base_url = '%s://%s' % (self.protocol, self.domain) cache.clear() - # Create an object for sitemap content. - TestModel.objects.create(name='Test Object') - self.i18n_model = I18nTestModel.objects.create(name='Test Object') @classmethod def setUpClass(cls): - super(SitemapTestsBase, cls).setUpClass() + super().setUpClass() # This cleanup is necessary because contrib.sites cache # makes tests interfere with each other, see #11505 Site.objects.clear_cache() diff --git a/tests/sitemaps_tests/models.py b/tests/sitemaps_tests/models.py index 29b3e8cde715..77ac60184128 100644 --- a/tests/sitemaps_tests/models.py +++ b/tests/sitemaps_tests/models.py @@ -4,6 +4,7 @@ class TestModel(models.Model): name = models.CharField(max_length=100) + lastmod = models.DateTimeField(null=True) def get_absolute_url(self): return '/testmodel/%s/' % self.id diff --git a/tests/sitemaps_tests/test_generic.py b/tests/sitemaps_tests/test_generic.py index 96736c261ac6..141e2e2a39a1 100644 --- a/tests/sitemaps_tests/test_generic.py +++ b/tests/sitemaps_tests/test_generic.py @@ -1,5 +1,6 @@ -from __future__ import unicode_literals +from datetime import datetime +from django.contrib.sitemaps import GenericSitemap from django.test import override_settings from .base import SitemapTestsBase @@ -9,6 +10,29 @@ @override_settings(ABSOLUTE_URL_OVERRIDES={}) class GenericViewsSitemapTests(SitemapTestsBase): + def test_generic_sitemap_attributes(self): + datetime_value = datetime.now() + queryset = TestModel.objects.all() + generic_sitemap = GenericSitemap( + info_dict={ + 'queryset': queryset, + 'date_field': datetime_value, + }, + priority=0.6, + changefreq='monthly', + protocol='https', + ) + attr_values = ( + ('date_field', datetime_value), + ('priority', 0.6), + ('changefreq', 'monthly'), + ('protocol', 'https'), + ) + for attr_name, expected_value in attr_values: + with self.subTest(attr_name=attr_name): + self.assertEqual(getattr(generic_sitemap, attr_name), expected_value) + self.assertCountEqual(generic_sitemap.queryset, queryset) + def test_generic_sitemap(self): "A minimal generic sitemap can be rendered" response = self.client.get('/generic/sitemap.xml') @@ -20,4 +44,16 @@ def test_generic_sitemap(self): %s """ % expected - self.assertXMLEqual(response.content.decode('utf-8'), expected_content) + self.assertXMLEqual(response.content.decode(), expected_content) + + def test_generic_sitemap_lastmod(self): + test_model = TestModel.objects.first() + TestModel.objects.update(lastmod=datetime(2013, 3, 13, 10, 0, 0)) + response = self.client.get('/generic-lastmod/sitemap.xml') + expected_content = """ + +%s/testmodel/%s/2013-03-13 + +""" % (self.base_url, test_model.pk) + self.assertXMLEqual(response.content.decode(), expected_content) + self.assertEqual(response['Last-Modified'], 'Wed, 13 Mar 2013 10:00:00 GMT') diff --git a/tests/sitemaps_tests/test_http.py b/tests/sitemaps_tests/test_http.py index 8367b495d87c..e757170241d7 100644 --- a/tests/sitemaps_tests/test_http.py +++ b/tests/sitemaps_tests/test_http.py @@ -1,16 +1,12 @@ -from __future__ import unicode_literals - import os from datetime import date from unittest import skipUnless -from django.apps import apps from django.conf import settings -from django.contrib.sitemaps import GenericSitemap, Sitemap +from django.contrib.sitemaps import Sitemap from django.contrib.sites.models import Site from django.core.exceptions import ImproperlyConfigured from django.test import modify_settings, override_settings -from django.utils._os import upath from django.utils.formats import localize from django.utils.translation import activate, deactivate @@ -19,6 +15,10 @@ class HTTPSitemapTests(SitemapTestsBase): + use_sitemap_err_msg = ( + 'To use sitemaps, either enable the sites framework or pass a ' + 'Site/RequestSite object in your view.' + ) def test_simple_sitemap_index(self): "A simple sitemap index can be rendered" @@ -28,11 +28,31 @@ def test_simple_sitemap_index(self): %s/simple/sitemap-simple.xml """ % self.base_url - self.assertXMLEqual(response.content.decode('utf-8'), expected_content) + self.assertXMLEqual(response.content.decode(), expected_content) + + def test_sitemap_not_callable(self): + """A sitemap may not be callable.""" + response = self.client.get('/simple-not-callable/index.xml') + expected_content = """ + +%s/simple/sitemap-simple.xml + +""" % self.base_url + self.assertXMLEqual(response.content.decode(), expected_content) + + def test_paged_sitemap(self): + """A sitemap may have multiple pages.""" + response = self.client.get('/simple-paged/index.xml') + expected_content = """ + +{0}/simple/sitemap-simple.xml{0}/simple/sitemap-simple.xml?p=2 + +""".format(self.base_url) + self.assertXMLEqual(response.content.decode(), expected_content) @override_settings(TEMPLATES=[{ 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [os.path.join(os.path.dirname(upath(__file__)), 'templates')], + 'DIRS': [os.path.join(os.path.dirname(__file__), 'templates')], }]) def test_simple_sitemap_custom_index(self): "A simple sitemap index can be rendered with a custom template" @@ -43,7 +63,7 @@ def test_simple_sitemap_custom_index(self): %s/simple/sitemap-simple.xml """ % self.base_url - self.assertXMLEqual(response.content.decode('utf-8'), expected_content) + self.assertXMLEqual(response.content.decode(), expected_content) def test_simple_sitemap_section(self): "A simple sitemap section can be rendered" @@ -53,7 +73,22 @@ def test_simple_sitemap_section(self): %s/location/%snever0.5 """ % (self.base_url, date.today()) - self.assertXMLEqual(response.content.decode('utf-8'), expected_content) + self.assertXMLEqual(response.content.decode(), expected_content) + + def test_no_section(self): + response = self.client.get('/simple/sitemap-simple2.xml') + self.assertEqual(str(response.context['exception']), "No sitemap available for section: 'simple2'") + self.assertEqual(response.status_code, 404) + + def test_empty_page(self): + response = self.client.get('/simple/sitemap-simple.xml?p=0') + self.assertEqual(str(response.context['exception']), 'Page 0 empty') + self.assertEqual(response.status_code, 404) + + def test_page_not_int(self): + response = self.client.get('/simple/sitemap-simple.xml?p=test') + self.assertEqual(str(response.context['exception']), "No page 'test'") + self.assertEqual(response.status_code, 404) def test_simple_sitemap(self): "A simple sitemap can be rendered" @@ -63,11 +98,11 @@ def test_simple_sitemap(self): %s/location/%snever0.5 """ % (self.base_url, date.today()) - self.assertXMLEqual(response.content.decode('utf-8'), expected_content) + self.assertXMLEqual(response.content.decode(), expected_content) @override_settings(TEMPLATES=[{ 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [os.path.join(os.path.dirname(upath(__file__)), 'templates')], + 'DIRS': [os.path.join(os.path.dirname(__file__), 'templates')], }]) def test_simple_custom_sitemap(self): "A simple sitemap can be rendered with a custom template" @@ -78,7 +113,7 @@ def test_simple_custom_sitemap(self): %s/location/%snever0.5 """ % (self.base_url, date.today()) - self.assertXMLEqual(response.content.decode('utf-8'), expected_content) + self.assertXMLEqual(response.content.decode(), expected_content) def test_sitemap_last_modified(self): "Last-Modified header is set correctly" @@ -165,17 +200,15 @@ def test_requestsite_sitemap(self): http://testserver/location/%snever0.5 """ % date.today() - self.assertXMLEqual(response.content.decode('utf-8'), expected_content) + self.assertXMLEqual(response.content.decode(), expected_content) - @skipUnless(apps.is_installed('django.contrib.sites'), - "django.contrib.sites app not installed.") def test_sitemap_get_urls_no_site_1(self): """ Check we get ImproperlyConfigured if we don't pass a site object to Sitemap.get_urls and no Site objects exist """ Site.objects.all().delete() - with self.assertRaises(ImproperlyConfigured): + with self.assertRaisesMessage(ImproperlyConfigured, self.use_sitemap_err_msg): Sitemap().get_urls() @modify_settings(INSTALLED_APPS={'remove': 'django.contrib.sites'}) @@ -185,7 +218,7 @@ def test_sitemap_get_urls_no_site_2(self): Sitemap.get_urls if Site objects exists, but the sites framework is not actually installed. """ - with self.assertRaises(ImproperlyConfigured): + with self.assertRaisesMessage(ImproperlyConfigured, self.use_sitemap_err_msg): Sitemap().get_urls() def test_sitemap_item(self): @@ -193,7 +226,8 @@ def test_sitemap_item(self): Check to make sure that the raw item is included with each Sitemap.get_url() url result. """ - test_sitemap = GenericSitemap({'queryset': TestModel.objects.order_by('pk').all()}) + test_sitemap = Sitemap() + test_sitemap.items = TestModel.objects.order_by('pk').all def is_testmodel(url): return isinstance(url['item'], TestModel) @@ -210,7 +244,7 @@ def test_cached_sitemap_index(self): %s/cached/sitemap-simple.xml """ % self.base_url - self.assertXMLEqual(response.content.decode('utf-8'), expected_content) + self.assertXMLEqual(response.content.decode(), expected_content) def test_x_robots_sitemap(self): response = self.client.get('/simple/index.xml') @@ -232,7 +266,7 @@ def test_simple_i18nsitemap_index(self): {0}/en/i18n/testmodel/{1}/never0.5{0}/pt/i18n/testmodel/{1}/never0.5 """.format(self.base_url, self.i18n_model.pk) - self.assertXMLEqual(response.content.decode('utf-8'), expected_content) + self.assertXMLEqual(response.content.decode(), expected_content) def test_sitemap_without_entries(self): response = self.client.get('/sitemap-without-entries/sitemap.xml') @@ -240,4 +274,4 @@ def test_sitemap_without_entries(self): """ - self.assertXMLEqual(response.content.decode('utf-8'), expected_content) + self.assertXMLEqual(response.content.decode(), expected_content) diff --git a/tests/sitemaps_tests/test_https.py b/tests/sitemaps_tests/test_https.py index 3b68baac43ac..c0e21a6258bc 100644 --- a/tests/sitemaps_tests/test_https.py +++ b/tests/sitemaps_tests/test_https.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from datetime import date from django.test import override_settings @@ -19,7 +17,7 @@ def test_secure_sitemap_index(self): %s/secure/sitemap-simple.xml """ % self.base_url - self.assertXMLEqual(response.content.decode('utf-8'), expected_content) + self.assertXMLEqual(response.content.decode(), expected_content) def test_secure_sitemap_section(self): "A secure sitemap section can be rendered" @@ -29,7 +27,7 @@ def test_secure_sitemap_section(self): %s/location/%snever0.5 """ % (self.base_url, date.today()) - self.assertXMLEqual(response.content.decode('utf-8'), expected_content) + self.assertXMLEqual(response.content.decode(), expected_content) @override_settings(SECURE_PROXY_SSL_HEADER=False) @@ -44,7 +42,7 @@ def test_sitemap_index_with_https_request(self): %s/simple/sitemap-simple.xml """ % self.base_url.replace('http://', 'https://') - self.assertXMLEqual(response.content.decode('utf-8'), expected_content) + self.assertXMLEqual(response.content.decode(), expected_content) def test_sitemap_section_with_https_request(self): "A sitemap section requested in HTTPS is rendered with HTTPS links" @@ -54,4 +52,4 @@ def test_sitemap_section_with_https_request(self): %s/location/%snever0.5 """ % (self.base_url.replace('http://', 'https://'), date.today()) - self.assertXMLEqual(response.content.decode('utf-8'), expected_content) + self.assertXMLEqual(response.content.decode(), expected_content) diff --git a/tests/sitemaps_tests/test_management.py b/tests/sitemaps_tests/test_management.py index 459a1d1b3c52..f91de8ac4b0d 100644 --- a/tests/sitemaps_tests/test_management.py +++ b/tests/sitemaps_tests/test_management.py @@ -1,5 +1,6 @@ +from unittest import mock + from django.core.management import call_command -from django.test import mock from .base import SitemapTestsBase @@ -9,8 +10,8 @@ class PingGoogleTests(SitemapTestsBase): def test_default(self, ping_google_func): call_command('ping_google') - ping_google_func.assert_called_with(sitemap_url=None) + ping_google_func.assert_called_with(sitemap_url=None, sitemap_uses_https=True) - def test_arg(self, ping_google_func): - call_command('ping_google', 'foo.xml') - ping_google_func.assert_called_with(sitemap_url='foo.xml') + def test_args(self, ping_google_func): + call_command('ping_google', 'foo.xml', '--sitemap-uses-http') + ping_google_func.assert_called_with(sitemap_url='foo.xml', sitemap_uses_https=False) diff --git a/tests/sitemaps_tests/test_utils.py b/tests/sitemaps_tests/test_utils.py index ba1eadd5d165..34f46c45b391 100644 --- a/tests/sitemaps_tests/test_utils.py +++ b/tests/sitemaps_tests/test_utils.py @@ -1,9 +1,11 @@ +from unittest import mock +from urllib.parse import urlencode + from django.contrib.sitemaps import ( SitemapNotFound, _get_sitemap_full_url, ping_google, ) from django.core.exceptions import ImproperlyConfigured -from django.test import mock, modify_settings, override_settings -from django.utils.six.moves.urllib.parse import urlencode +from django.test import modify_settings, override_settings from .base import SitemapTestsBase @@ -13,16 +15,16 @@ class PingGoogleTests(SitemapTestsBase): @mock.patch('django.contrib.sitemaps.urlopen') def test_something(self, urlopen): ping_google() - params = urlencode({'sitemap': 'http://example.com/sitemap-without-entries/sitemap.xml'}) + params = urlencode({'sitemap': 'https://example.com/sitemap-without-entries/sitemap.xml'}) full_url = 'https://www.google.com/webmasters/tools/ping?%s' % params urlopen.assert_called_with(full_url) def test_get_sitemap_full_url_global(self): - self.assertEqual(_get_sitemap_full_url(None), 'http://example.com/sitemap-without-entries/sitemap.xml') + self.assertEqual(_get_sitemap_full_url(None), 'https://example.com/sitemap-without-entries/sitemap.xml') @override_settings(ROOT_URLCONF='sitemaps_tests.urls.index_only') def test_get_sitemap_full_url_index(self): - self.assertEqual(_get_sitemap_full_url(None), 'http://example.com/simple/index.xml') + self.assertEqual(_get_sitemap_full_url(None), 'https://example.com/simple/index.xml') @override_settings(ROOT_URLCONF='sitemaps_tests.urls.empty') def test_get_sitemap_full_url_not_detected(self): @@ -31,7 +33,13 @@ def test_get_sitemap_full_url_not_detected(self): _get_sitemap_full_url(None) def test_get_sitemap_full_url_exact_url(self): - self.assertEqual(_get_sitemap_full_url('/foo.xml'), 'http://example.com/foo.xml') + self.assertEqual(_get_sitemap_full_url('/foo.xml'), 'https://example.com/foo.xml') + + def test_get_sitemap_full_url_insecure(self): + self.assertEqual( + _get_sitemap_full_url('/foo.xml', sitemap_uses_https=False), + 'http://example.com/foo.xml' + ) @modify_settings(INSTALLED_APPS={'remove': 'django.contrib.sites'}) def test_get_sitemap_full_url_no_sites(self): diff --git a/tests/sitemaps_tests/urls/http.py b/tests/sitemaps_tests/urls/http.py index e2fc991a29e5..03652902fb60 100644 --- a/tests/sitemaps_tests/urls/http.py +++ b/tests/sitemaps_tests/urls/http.py @@ -1,10 +1,10 @@ from collections import OrderedDict from datetime import date, datetime -from django.conf.urls import url from django.conf.urls.i18n import i18n_patterns from django.contrib.sitemaps import GenericSitemap, Sitemap, views from django.http import HttpResponse +from django.urls import path from django.utils import timezone from django.views.decorators.cache import cache_page @@ -21,6 +21,11 @@ def items(self): return [object()] +class SimplePagedSitemap(Sitemap): + def items(self): + return [object() for x in range(Sitemap.limit + 1)] + + class SimpleI18nSitemap(Sitemap): changefreq = "never" priority = 0.5 @@ -35,9 +40,6 @@ class EmptySitemap(Sitemap): priority = 0.5 location = '/location/' - def items(self): - return [] - class FixedLastmodSitemap(SimpleSitemap): lastmod = datetime(2013, 3, 13, 10, 0, 0) @@ -80,6 +82,14 @@ def testmodelview(request, id): 'simple': SimpleI18nSitemap, } +simple_sitemaps_not_callable = { + 'simple': SimpleSitemap(), +} + +simple_sitemaps_paged = { + 'simple': SimplePagedSitemap, +} + empty_sitemaps = { 'empty': EmptySitemap, } @@ -118,61 +128,91 @@ def testmodelview(request, id): 'generic': GenericSitemap({'queryset': TestModel.objects.order_by('pk').all()}), } +generic_sitemaps_lastmod = { + 'generic': GenericSitemap({ + 'queryset': TestModel.objects.order_by('pk').all(), + 'date_field': 'lastmod', + }), +} urlpatterns = [ - url(r'^simple/index\.xml$', views.index, {'sitemaps': simple_sitemaps}), - url(r'^simple/custom-index\.xml$', views.index, + path('simple/index.xml', views.index, {'sitemaps': simple_sitemaps}), + path('simple-paged/index.xml', views.index, {'sitemaps': simple_sitemaps_paged}), + path('simple-not-callable/index.xml', views.index, {'sitemaps': simple_sitemaps_not_callable}), + path( + 'simple/custom-index.xml', views.index, {'sitemaps': simple_sitemaps, 'template_name': 'custom_sitemap_index.xml'}), - url(r'^simple/sitemap-(?P
        .+)\.xml$', views.sitemap, + path( + 'simple/sitemap-
        .xml', views.sitemap, {'sitemaps': simple_sitemaps}, name='django.contrib.sitemaps.views.sitemap'), - url(r'^simple/sitemap\.xml$', views.sitemap, + path( + 'simple/sitemap.xml', views.sitemap, {'sitemaps': simple_sitemaps}, name='django.contrib.sitemaps.views.sitemap'), - url(r'^simple/i18n\.xml$', views.sitemap, + path( + 'simple/i18n.xml', views.sitemap, {'sitemaps': simple_i18nsitemaps}, name='django.contrib.sitemaps.views.sitemap'), - url(r'^simple/custom-sitemap\.xml$', views.sitemap, + path( + 'simple/custom-sitemap.xml', views.sitemap, {'sitemaps': simple_sitemaps, 'template_name': 'custom_sitemap.xml'}, name='django.contrib.sitemaps.views.sitemap'), - url(r'^empty/sitemap\.xml$', views.sitemap, + path( + 'empty/sitemap.xml', views.sitemap, {'sitemaps': empty_sitemaps}, name='django.contrib.sitemaps.views.sitemap'), - url(r'^lastmod/sitemap\.xml$', views.sitemap, + path( + 'lastmod/sitemap.xml', views.sitemap, {'sitemaps': fixed_lastmod_sitemaps}, name='django.contrib.sitemaps.views.sitemap'), - url(r'^lastmod-mixed/sitemap\.xml$', views.sitemap, + path( + 'lastmod-mixed/sitemap.xml', views.sitemap, {'sitemaps': fixed_lastmod__mixed_sitemaps}, name='django.contrib.sitemaps.views.sitemap'), - url(r'^lastmod/date-sitemap\.xml$', views.sitemap, + path( + 'lastmod/date-sitemap.xml', views.sitemap, {'sitemaps': {'date-sitemap': DateSiteMap}}, name='django.contrib.sitemaps.views.sitemap'), - url(r'^lastmod/tz-sitemap\.xml$', views.sitemap, + path( + 'lastmod/tz-sitemap.xml', views.sitemap, {'sitemaps': {'tz-sitemap': TimezoneSiteMap}}, name='django.contrib.sitemaps.views.sitemap'), - url(r'^lastmod-sitemaps/mixed-ascending.xml$', views.sitemap, + path( + 'lastmod-sitemaps/mixed-ascending.xml', views.sitemap, {'sitemaps': sitemaps_lastmod_mixed_ascending}, name='django.contrib.sitemaps.views.sitemap'), - url(r'^lastmod-sitemaps/mixed-descending.xml$', views.sitemap, + path( + 'lastmod-sitemaps/mixed-descending.xml', views.sitemap, {'sitemaps': sitemaps_lastmod_mixed_descending}, name='django.contrib.sitemaps.views.sitemap'), - url(r'^lastmod-sitemaps/ascending.xml$', views.sitemap, + path( + 'lastmod-sitemaps/ascending.xml', views.sitemap, {'sitemaps': sitemaps_lastmod_ascending}, name='django.contrib.sitemaps.views.sitemap'), - url(r'^lastmod-sitemaps/descending.xml$', views.sitemap, + path( + 'lastmod-sitemaps/descending.xml', views.sitemap, {'sitemaps': sitemaps_lastmod_descending}, name='django.contrib.sitemaps.views.sitemap'), - url(r'^generic/sitemap\.xml$', views.sitemap, + path( + 'generic/sitemap.xml', views.sitemap, {'sitemaps': generic_sitemaps}, name='django.contrib.sitemaps.views.sitemap'), - url(r'^cached/index\.xml$', cache_page(1)(views.index), + path( + 'generic-lastmod/sitemap.xml', views.sitemap, + {'sitemaps': generic_sitemaps_lastmod}, + name='django.contrib.sitemaps.views.sitemap'), + path( + 'cached/index.xml', cache_page(1)(views.index), {'sitemaps': simple_sitemaps, 'sitemap_url_name': 'cached_sitemap'}), - url(r'^cached/sitemap-(?P
        .+)\.xml', cache_page(1)(views.sitemap), + path( + 'cached/sitemap-
        .xml', cache_page(1)(views.sitemap), {'sitemaps': simple_sitemaps}, name='cached_sitemap'), - url(r'^sitemap-without-entries/sitemap\.xml$', views.sitemap, + path( + 'sitemap-without-entries/sitemap.xml', views.sitemap, {'sitemaps': {}}, name='django.contrib.sitemaps.views.sitemap'), ] urlpatterns += i18n_patterns( - url(r'^i18n/testmodel/(?P\d+)/$', testmodelview, name='i18n_testmodel'), + path('i18n/testmodel//', testmodelview, name='i18n_testmodel'), ) diff --git a/tests/sitemaps_tests/urls/https.py b/tests/sitemaps_tests/urls/https.py index 4f07d4759cb4..191fb5163e47 100644 --- a/tests/sitemaps_tests/urls/https.py +++ b/tests/sitemaps_tests/urls/https.py @@ -1,5 +1,5 @@ -from django.conf.urls import url from django.contrib.sitemaps import views +from django.urls import path from .http import SimpleSitemap @@ -13,8 +13,9 @@ class HTTPSSitemap(SimpleSitemap): } urlpatterns = [ - url(r'^secure/index\.xml$', views.index, {'sitemaps': secure_sitemaps}), - url(r'^secure/sitemap-(?P
        .+)\.xml$', views.sitemap, + path('secure/index.xml', views.index, {'sitemaps': secure_sitemaps}), + path( + 'secure/sitemap-
        .xml', views.sitemap, {'sitemaps': secure_sitemaps}, name='django.contrib.sitemaps.views.sitemap'), ] diff --git a/tests/sitemaps_tests/urls/index_only.py b/tests/sitemaps_tests/urls/index_only.py index 7b9a093d871a..6f8f8e162e9b 100644 --- a/tests/sitemaps_tests/urls/index_only.py +++ b/tests/sitemaps_tests/urls/index_only.py @@ -1,9 +1,10 @@ -from django.conf.urls import url from django.contrib.sitemaps import views +from django.urls import path from .http import simple_sitemaps urlpatterns = [ - url(r'^simple/index\.xml$', views.index, {'sitemaps': simple_sitemaps}, + path( + 'simple/index.xml', views.index, {'sitemaps': simple_sitemaps}, name='django.contrib.sitemaps.views.index'), ] diff --git a/tests/sites_framework/migrations/0001_initial.py b/tests/sites_framework/migrations/0001_initial.py index 78d26cab317b..f4a2ba9460ca 100644 --- a/tests/sites_framework/migrations/0001_initial.py +++ b/tests/sites_framework/migrations/0001_initial.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/tests/sites_framework/models.py b/tests/sites_framework/models.py index ed0f4443a15c..fb6a9d4e79c3 100644 --- a/tests/sites_framework/models.py +++ b/tests/sites_framework/models.py @@ -1,10 +1,8 @@ from django.contrib.sites.managers import CurrentSiteManager from django.contrib.sites.models import Site from django.db import models -from django.utils.encoding import python_2_unicode_compatible -@python_2_unicode_compatible class AbstractArticle(models.Model): title = models.CharField(max_length=50) diff --git a/tests/sites_framework/tests.py b/tests/sites_framework/tests.py index de37bb5a008d..e44b3a6d84ab 100644 --- a/tests/sites_framework/tests.py +++ b/tests/sites_framework/tests.py @@ -10,7 +10,8 @@ class SitesFrameworkTestCase(TestCase): - def setUp(self): + @classmethod + def setUpTestData(cls): Site.objects.get_or_create(id=settings.SITE_ID, domain="example.com", name="example.com") Site.objects.create(id=settings.SITE_ID + 1, domain="example2.com", name="example2.com") diff --git a/tests/sites_tests/tests.py b/tests/sites_tests/tests.py index 3ea66be9bb5f..500a422b216c 100644 --- a/tests/sites_tests/tests.py +++ b/tests/sites_tests/tests.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.apps import apps from django.apps.registry import Apps from django.conf import settings @@ -12,21 +10,20 @@ from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.db.models.signals import post_migrate from django.http import HttpRequest, HttpResponse -from django.test import TestCase, modify_settings, override_settings +from django.test import ( + SimpleTestCase, TestCase, modify_settings, override_settings, +) from django.test.utils import captured_stdout @modify_settings(INSTALLED_APPS={'append': 'django.contrib.sites'}) class SitesFrameworkTests(TestCase): - multi_db = True + databases = {'default', 'other'} - def setUp(self): - self.site = Site( - id=settings.SITE_ID, - domain="example.com", - name="example.com", - ) - self.site.save() + @classmethod + def setUpTestData(cls): + cls.site = Site(id=settings.SITE_ID, domain='example.com', name='example.com') + cls.site.save() def tearDown(self): Site.objects.clear_cache() @@ -205,43 +202,50 @@ def test_site_natural_key(self): self.assertEqual(Site.objects.get_by_natural_key(self.site.domain), self.site) self.assertEqual(self.site.natural_key(), (self.site.domain,)) - @override_settings(ALLOWED_HOSTS=['example.com']) - def test_requestsite_save_notimplemented_msg(self): - # Test response msg for RequestSite.save NotImplementedError + +@override_settings(ALLOWED_HOSTS=['example.com']) +class RequestSiteTests(SimpleTestCase): + + def setUp(self): request = HttpRequest() - request.META = { - "HTTP_HOST": "example.com", - } + request.META = {'HTTP_HOST': 'example.com'} + self.site = RequestSite(request) + + def test_init_attributes(self): + self.assertEqual(self.site.domain, 'example.com') + self.assertEqual(self.site.name, 'example.com') + + def test_str(self): + self.assertEqual(str(self.site), 'example.com') + + def test_save(self): msg = 'RequestSite cannot be saved.' with self.assertRaisesMessage(NotImplementedError, msg): - RequestSite(request).save() + self.site.save() - @override_settings(ALLOWED_HOSTS=['example.com']) - def test_requestsite_delete_notimplemented_msg(self): - # Test response msg for RequestSite.delete NotImplementedError - request = HttpRequest() - request.META = { - "HTTP_HOST": "example.com", - } + def test_delete(self): msg = 'RequestSite cannot be deleted.' with self.assertRaisesMessage(NotImplementedError, msg): - RequestSite(request).delete() + self.site.delete() -class JustOtherRouter(object): +class JustOtherRouter: def allow_migrate(self, db, app_label, **hints): return db == 'other' @modify_settings(INSTALLED_APPS={'append': 'django.contrib.sites'}) class CreateDefaultSiteTests(TestCase): - multi_db = True + databases = {'default', 'other'} - def setUp(self): - self.app_config = apps.get_app_config('sites') + @classmethod + def setUpTestData(cls): # Delete the site created as part of the default migration process. Site.objects.all().delete() + def setUp(self): + self.app_config = apps.get_app_config('sites') + def test_basic(self): """ #15346, #15573 - create_default_site() creates an example site only if diff --git a/tests/staticfiles_tests/apps/staticfiles_config.py b/tests/staticfiles_tests/apps/staticfiles_config.py index e48a0c8d99d9..b8b3960c9dcd 100644 --- a/tests/staticfiles_tests/apps/staticfiles_config.py +++ b/tests/staticfiles_tests/apps/staticfiles_config.py @@ -2,4 +2,4 @@ class IgnorePatternsAppConfig(StaticFilesConfig): - ignore_patterns = ['*.css'] + ignore_patterns = ['*.css', '*/vendor/*.js'] diff --git a/tests/staticfiles_tests/apps/test/static/test/%2F.txt b/tests/staticfiles_tests/apps/test/static/test/%2F.txt new file mode 100644 index 000000000000..d98b646c7c66 --- /dev/null +++ b/tests/staticfiles_tests/apps/test/static/test/%2F.txt @@ -0,0 +1 @@ +%2F content diff --git a/tests/staticfiles_tests/apps/test/static/test/vendor/module.js b/tests/staticfiles_tests/apps/test/static/test/vendor/module.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/staticfiles_tests/cases.py b/tests/staticfiles_tests/cases.py index 009672ee1233..d4a48ce7142d 100644 --- a/tests/staticfiles_tests/cases.py +++ b/tests/staticfiles_tests/cases.py @@ -1,6 +1,3 @@ -# -*- encoding: utf-8 -*- -from __future__ import unicode_literals - import codecs import os import shutil @@ -10,13 +7,11 @@ from django.core.management import call_command from django.template import Context, Template from django.test import SimpleTestCase, override_settings -from django.utils import six -from django.utils.encoding import force_text from .settings import TEST_SETTINGS -class BaseStaticFilesMixin(object): +class BaseStaticFilesMixin: """ Test case with a couple utility assertions. """ @@ -24,7 +19,7 @@ class BaseStaticFilesMixin(object): def assertFileContains(self, filepath, text): self.assertIn( text, - self._get_file(force_text(filepath)), + self._get_file(filepath), "'%s' not in '%s'" % (text, filepath), ) @@ -33,14 +28,14 @@ def assertFileNotFound(self, filepath): self._get_file(filepath) def render_template(self, template, **kwargs): - if isinstance(template, six.string_types): + if isinstance(template, str): template = Template(template) return template.render(Context(**kwargs)).strip() def static_template_snippet(self, path, asvar=False): if asvar: - return "{%% load static from staticfiles %%}{%% static '%s' as var %%}{{ var }}" % path - return "{%% load static from staticfiles %%}{%% static '%s' %%}" % path + return "{%% load static from static %%}{%% static '%s' as var %%}{{ var }}" % path + return "{%% load static from static %%}{%% static '%s' %%}" % path def assertStaticRenders(self, path, result, asvar=False, **kwargs): template = self.static_template_snippet(path, asvar) @@ -66,23 +61,25 @@ class CollectionTestCase(BaseStaticFilesMixin, SimpleTestCase): is separated because some test cases need those asserts without all these tests. """ + run_collectstatic_in_setUp = True + def setUp(self): - super(CollectionTestCase, self).setUp() + super().setUp() temp_dir = tempfile.mkdtemp() # Override the STATIC_ROOT for all tests from setUp to tearDown # rather than as a context manager self.patched_settings = self.settings(STATIC_ROOT=temp_dir) self.patched_settings.enable() - self.run_collectstatic() + if self.run_collectstatic_in_setUp: + self.run_collectstatic() # Same comment as in runtests.teardown. - self.addCleanup(shutil.rmtree, six.text_type(temp_dir)) + self.addCleanup(shutil.rmtree, temp_dir) def tearDown(self): self.patched_settings.disable() - super(CollectionTestCase, self).tearDown() + super().tearDown() - def run_collectstatic(self, **kwargs): - verbosity = kwargs.pop('verbosity', 0) + def run_collectstatic(self, *, verbosity=0, **kwargs): call_command('collectstatic', interactive=False, verbosity=verbosity, ignore_patterns=['*.ignoreme'], **kwargs) @@ -93,7 +90,7 @@ def _get_file(self, filepath): return f.read() -class TestDefaults(object): +class TestDefaults: """ A few standard test cases. """ @@ -134,3 +131,6 @@ def test_camelcase_filenames(self): Can find a file with capital letters. """ self.assertFileContains('test/camelCase.txt', 'camelCase') + + def test_filename_with_percent_sign(self): + self.assertFileContains('test/%2F.txt', '%2F content') diff --git a/tests/staticfiles_tests/project/documents/cached/css/fonts/font.eot b/tests/staticfiles_tests/project/documents/cached/css/fonts/font.eot index 7c58b2e62269..fdd7138c52ba 100644 --- a/tests/staticfiles_tests/project/documents/cached/css/fonts/font.eot +++ b/tests/staticfiles_tests/project/documents/cached/css/fonts/font.eot @@ -1 +1 @@ -not really a EOT ;) \ No newline at end of file +not really an EOT ;) \ No newline at end of file diff --git a/tests/staticfiles_tests/settings.py b/tests/staticfiles_tests/settings.py index caf42b2505e5..1320da7a0dc0 100644 --- a/tests/staticfiles_tests/settings.py +++ b/tests/staticfiles_tests/settings.py @@ -1,10 +1,6 @@ -from __future__ import unicode_literals - import os.path -from django.utils._os import upath - -TEST_ROOT = os.path.dirname(upath(__file__)) +TEST_ROOT = os.path.dirname(__file__) TEST_SETTINGS = { 'MEDIA_URL': '/media/', @@ -26,4 +22,7 @@ 'staticfiles_tests.apps.test', 'staticfiles_tests.apps.no_label', ], + # In particular, AuthenticationMiddleware can't be used because + # contrib.auth isn't in INSTALLED_APPS. + 'MIDDLEWARE': [], } diff --git a/tests/staticfiles_tests/storage.py b/tests/staticfiles_tests/storage.py index 79e6245f5967..b9cac3cd057e 100644 --- a/tests/staticfiles_tests/storage.py +++ b/tests/staticfiles_tests/storage.py @@ -1,9 +1,8 @@ -import errno import os from datetime import datetime, timedelta from django.conf import settings -from django.contrib.staticfiles.storage import CachedStaticFilesStorage +from django.contrib.staticfiles.storage import ManifestStaticFilesStorage from django.core.files import storage from django.utils import timezone @@ -40,20 +39,19 @@ def exists(self, name): def listdir(self, path): path = self._path(path) directories, files = [], [] - for entry in os.listdir(path): - if os.path.isdir(os.path.join(path, entry)): - directories.append(entry) + for entry in os.scandir(path): + if entry.is_dir(): + directories.append(entry.name) else: - files.append(entry) + files.append(entry.name) return directories, files def delete(self, name): name = self._path(name) try: os.remove(name) - except OSError as e: - if e.errno != errno.ENOENT: - raise + except FileNotFoundError: + pass def path(self, name): raise NotImplementedError @@ -72,18 +70,18 @@ def url(self, path): return path + '?a=b&c=d' -class SimpleCachedStaticFilesStorage(CachedStaticFilesStorage): +class SimpleStorage(ManifestStaticFilesStorage): def file_hash(self, name, content=None): return 'deploy12345' -class ExtraPatternsCachedStaticFilesStorage(CachedStaticFilesStorage): +class ExtraPatternsStorage(ManifestStaticFilesStorage): """ A storage class to test pattern substitutions with more than one pattern entry. The added pattern rewrites strings like "url(...)" to JS_URL("..."). """ - patterns = tuple(CachedStaticFilesStorage.patterns) + ( + patterns = tuple(ManifestStaticFilesStorage.patterns) + ( ( "*.js", ( (r"""(url\(['"]{0,1}\s*(.*?)["']{0,1}\))""", 'JS_URL("%s")'), diff --git a/tests/staticfiles_tests/test_checks.py b/tests/staticfiles_tests/test_checks.py new file mode 100644 index 000000000000..d5dc90b78168 --- /dev/null +++ b/tests/staticfiles_tests/test_checks.py @@ -0,0 +1,87 @@ +from unittest import mock + +from django.conf import settings +from django.contrib.staticfiles.checks import check_finders +from django.contrib.staticfiles.finders import BaseFinder +from django.core.checks import Error +from django.test import SimpleTestCase, override_settings + + +class FindersCheckTests(SimpleTestCase): + + def test_base_finder_check_not_implemented(self): + finder = BaseFinder() + msg = 'subclasses may provide a check() method to verify the finder is configured correctly.' + with self.assertRaisesMessage(NotImplementedError, msg): + finder.check() + + def test_check_finders(self): + """check_finders() concatenates all errors.""" + error1 = Error('1') + error2 = Error('2') + error3 = Error('3') + + def get_finders(): + class Finder1(BaseFinder): + def check(self, **kwargs): + return [error1] + + class Finder2(BaseFinder): + def check(self, **kwargs): + return [] + + class Finder3(BaseFinder): + def check(self, **kwargs): + return [error2, error3] + + class Finder4(BaseFinder): + pass + + return [Finder1(), Finder2(), Finder3(), Finder4()] + + with mock.patch('django.contrib.staticfiles.checks.get_finders', get_finders): + errors = check_finders(None) + self.assertEqual(errors, [error1, error2, error3]) + + def test_no_errors_with_test_settings(self): + self.assertEqual(check_finders(None), []) + + @override_settings(STATICFILES_DIRS='a string') + def test_dirs_not_tuple_or_list(self): + self.assertEqual(check_finders(None), [ + Error( + 'The STATICFILES_DIRS setting is not a tuple or list.', + hint='Perhaps you forgot a trailing comma?', + id='staticfiles.E001', + ) + ]) + + @override_settings(STATICFILES_DIRS=['/fake/path', settings.STATIC_ROOT]) + def test_dirs_contains_static_root(self): + self.assertEqual(check_finders(None), [ + Error( + 'The STATICFILES_DIRS setting should not contain the ' + 'STATIC_ROOT setting.', + id='staticfiles.E002', + ) + ]) + + @override_settings(STATICFILES_DIRS=[('prefix', settings.STATIC_ROOT)]) + def test_dirs_contains_static_root_in_tuple(self): + self.assertEqual(check_finders(None), [ + Error( + 'The STATICFILES_DIRS setting should not contain the ' + 'STATIC_ROOT setting.', + id='staticfiles.E002', + ) + ]) + + @override_settings(STATICFILES_DIRS=[('prefix/', '/fake/path')]) + def test_prefix_contains_trailing_slash(self): + self.assertEqual(check_finders(None), [ + Error( + "The prefix 'prefix/' in the STATICFILES_DIRS setting must " + "not end with a slash.", + id='staticfiles.E003', + ) + ]) diff --git a/tests/staticfiles_tests/test_finders.py b/tests/staticfiles_tests/test_finders.py index 7355113ad617..9d5707cc2db9 100644 --- a/tests/staticfiles_tests/test_finders.py +++ b/tests/staticfiles_tests/test_finders.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import os from django.conf import settings @@ -11,7 +9,7 @@ from .settings import TEST_ROOT -class TestFinders(object): +class TestFinders: """ Base finder test mixin. @@ -37,7 +35,7 @@ class TestFileSystemFinder(TestFinders, StaticFilesTestCase): Test FileSystemFinder. """ def setUp(self): - super(TestFileSystemFinder, self).setUp() + super().setUp() self.finder = finders.FileSystemFinder() test_file_path = os.path.join(TEST_ROOT, 'project', 'documents', 'test', 'file.txt') self.find_first = (os.path.join('test', 'file.txt'), test_file_path) @@ -49,7 +47,7 @@ class TestAppDirectoriesFinder(TestFinders, StaticFilesTestCase): Test AppDirectoriesFinder. """ def setUp(self): - super(TestAppDirectoriesFinder, self).setUp() + super().setUp() self.finder = finders.AppDirectoriesFinder() test_file_path = os.path.join(TEST_ROOT, 'apps', 'test', 'static', 'test', 'file1.txt') self.find_first = (os.path.join('test', 'file1.txt'), test_file_path) @@ -61,7 +59,7 @@ class TestDefaultStorageFinder(TestFinders, StaticFilesTestCase): Test DefaultStorageFinder. """ def setUp(self): - super(TestDefaultStorageFinder, self).setUp() + super().setUp() self.finder = finders.DefaultStorageFinder( storage=storage.StaticFilesStorage(location=settings.MEDIA_ROOT)) test_file_path = os.path.join(settings.MEDIA_ROOT, 'media-file.txt') @@ -105,16 +103,12 @@ def test_searched_locations(self): [os.path.join(TEST_ROOT, 'project', 'documents')] ) - @override_settings(STATICFILES_DIRS='a string') - def test_non_tuple_raises_exception(self): - """ - We can't determine if STATICFILES_DIRS is set correctly just by - looking at the type, but we can determine if it's definitely wrong. - """ - with self.assertRaises(ImproperlyConfigured): - finders.FileSystemFinder() - @override_settings(MEDIA_ROOT='') def test_location_empty(self): - with self.assertRaises(ImproperlyConfigured): + msg = ( + "The storage backend of the staticfiles finder " + " " + "doesn't have a valid location." + ) + with self.assertRaisesMessage(ImproperlyConfigured, msg): finders.DefaultStorageFinder() diff --git a/tests/staticfiles_tests/test_forms.py b/tests/staticfiles_tests/test_forms.py index e3d4772662d6..0178bab931e1 100644 --- a/tests/staticfiles_tests/test_forms.py +++ b/tests/staticfiles_tests/test_forms.py @@ -1,8 +1,9 @@ +from urllib.parse import urljoin + from django.contrib.staticfiles import storage -from django.contrib.staticfiles.templatetags.staticfiles import static from django.forms import Media +from django.templatetags.static import static from django.test import SimpleTestCase, override_settings -from django.utils.six.moves.urllib.parse import urljoin class StaticTestStorage(storage.StaticFilesStorage): @@ -12,7 +13,7 @@ def url(self, name): @override_settings( STATIC_URL='http://media.example.com/static/', - INSTALLED_APPS=('django.contrib.staticfiles', ), + INSTALLED_APPS=('django.contrib.staticfiles',), STATICFILES_STORAGE='staticfiles_tests.test_forms.StaticTestStorage', ) class StaticFilesFormsMediaTestCase(SimpleTestCase): @@ -28,8 +29,8 @@ def test_absolute_url(self): ) self.assertEqual( str(m), - """ - + """ + diff --git a/tests/staticfiles_tests/test_liveserver.py b/tests/staticfiles_tests/test_liveserver.py index 1714ae8b1bfa..820fa5bc8951 100644 --- a/tests/staticfiles_tests/test_liveserver.py +++ b/tests/staticfiles_tests/test_liveserver.py @@ -4,16 +4,14 @@ django.test.LiveServerTestCase. """ -import contextlib import os +from urllib.request import urlopen from django.contrib.staticfiles.testing import StaticLiveServerTestCase from django.core.exceptions import ImproperlyConfigured from django.test import modify_settings, override_settings -from django.utils._os import upath -from django.utils.six.moves.urllib.request import urlopen -TEST_ROOT = os.path.dirname(upath(__file__)) +TEST_ROOT = os.path.dirname(__file__) TEST_SETTINGS = { 'MEDIA_URL': '/media/', 'STATIC_URL': '/static/', @@ -31,11 +29,11 @@ def setUpClass(cls): # Override settings cls.settings_override = override_settings(**TEST_SETTINGS) cls.settings_override.enable() - super(LiveServerBase, cls).setUpClass() + super().setUpClass() @classmethod def tearDownClass(cls): - super(LiveServerBase, cls).tearDownClass() + super().tearDownClass() # Restore original settings cls.settings_override.disable() @@ -59,14 +57,17 @@ def tearDownClass(cls): @classmethod def raises_exception(cls): try: - super(StaticLiveServerChecks, cls).setUpClass() + super().setUpClass() raise Exception("The line above should have raised an exception") except ImproperlyConfigured: # This raises ImproperlyConfigured("You're using the staticfiles # app without having set the required STATIC_URL setting.") pass finally: - super(StaticLiveServerChecks, cls).tearDownClass() + # Use del to avoid decrementing the database thread sharing count a + # second time. + del cls.server_thread + super().tearDownClass() def test_test_test(self): # Intentionally empty method so that the test is picked up by the @@ -86,5 +87,5 @@ def test_collectstatic_emulation(self): StaticLiveServerTestCase use of staticfiles' serve() allows it to discover app's static assets without having to collectstatic first. """ - with contextlib.closing(self.urlopen('/static/test/file.txt')) as f: + with self.urlopen('/static/test/file.txt') as f: self.assertEqual(f.read().rstrip(b'\r\n'), b'In static directory.') diff --git a/tests/staticfiles_tests/test_management.py b/tests/staticfiles_tests/test_management.py index bbb8567c2a1e..9006f2a23d61 100644 --- a/tests/staticfiles_tests/test_management.py +++ b/tests/staticfiles_tests/test_management.py @@ -1,24 +1,25 @@ -from __future__ import unicode_literals - import codecs import datetime import os import shutil import tempfile import unittest +from io import StringIO +from unittest import mock from admin_scripts.tests import AdminScriptTestCase from django.conf import settings from django.contrib.staticfiles import storage -from django.contrib.staticfiles.management.commands import collectstatic +from django.contrib.staticfiles.management.commands import ( + collectstatic, runserver, +) from django.core.exceptions import ImproperlyConfigured -from django.core.management import call_command -from django.test import mock, override_settings +from django.core.management import CommandError, call_command +from django.test import RequestFactory, override_settings from django.test.utils import extend_sys_path -from django.utils import six, timezone +from django.utils import timezone from django.utils._os import symlinks_supported -from django.utils.encoding import force_text from django.utils.functional import empty from .cases import CollectionTestCase, StaticFilesTestCase, TestDefaults @@ -26,7 +27,7 @@ from .storage import DummyStorage -class TestNoFilesCreated(object): +class TestNoFilesCreated: def test_no_files_created(self): """ @@ -35,46 +36,67 @@ def test_no_files_created(self): self.assertEqual(os.listdir(settings.STATIC_ROOT), []) +class TestRunserver(StaticFilesTestCase): + @override_settings(MIDDLEWARE=['django.middleware.common.CommonMiddleware']) + def test_middleware_loaded_only_once(self): + command = runserver.Command() + with mock.patch('django.middleware.common.CommonMiddleware') as mocked: + command.get_handler(use_static_handler=True, insecure_serving=True) + self.assertEqual(mocked.call_count, 1) + + def test_404_response(self): + command = runserver.Command() + handler = command.get_handler(use_static_handler=True, insecure_serving=True) + missing_static_file = os.path.join(settings.STATIC_URL, 'unknown.css') + req = RequestFactory().get(missing_static_file) + with override_settings(DEBUG=False): + response = handler.get_response(req) + self.assertEqual(response.status_code, 404) + with override_settings(DEBUG=True): + response = handler.get_response(req) + self.assertEqual(response.status_code, 404) + + class TestFindStatic(TestDefaults, CollectionTestCase): """ Test ``findstatic`` management command. """ def _get_file(self, filepath): - path = call_command('findstatic', filepath, all=False, verbosity=0, stdout=six.StringIO()) - with codecs.open(force_text(path), "r", "utf-8") as f: + path = call_command('findstatic', filepath, all=False, verbosity=0, stdout=StringIO()) + with codecs.open(path, "r", "utf-8") as f: return f.read() def test_all_files(self): """ findstatic returns all candidate files if run without --first and -v1. """ - result = call_command('findstatic', 'test/file.txt', verbosity=1, stdout=six.StringIO()) - lines = [l.strip() for l in result.split('\n')] + result = call_command('findstatic', 'test/file.txt', verbosity=1, stdout=StringIO()) + lines = [line.strip() for line in result.split('\n')] self.assertEqual(len(lines), 3) # three because there is also the "Found here" line - self.assertIn('project', force_text(lines[1])) - self.assertIn('apps', force_text(lines[2])) + self.assertIn('project', lines[1]) + self.assertIn('apps', lines[2]) def test_all_files_less_verbose(self): """ findstatic returns all candidate files if run without --first and -v0. """ - result = call_command('findstatic', 'test/file.txt', verbosity=0, stdout=six.StringIO()) - lines = [l.strip() for l in result.split('\n')] + result = call_command('findstatic', 'test/file.txt', verbosity=0, stdout=StringIO()) + lines = [line.strip() for line in result.split('\n')] self.assertEqual(len(lines), 2) - self.assertIn('project', force_text(lines[0])) - self.assertIn('apps', force_text(lines[1])) + self.assertIn('project', lines[0]) + self.assertIn('apps', lines[1]) def test_all_files_more_verbose(self): """ findstatic returns all candidate files if run without --first and -v2. Also, test that findstatic returns the searched locations with -v2. """ - result = call_command('findstatic', 'test/file.txt', verbosity=2, stdout=six.StringIO()) - lines = [l.strip() for l in result.split('\n')] - self.assertIn('project', force_text(lines[1])) - self.assertIn('apps', force_text(lines[2])) - self.assertIn("Looking in the following locations:", force_text(lines[3])) - searched_locations = ', '.join(force_text(x) for x in lines[4:]) + result = call_command('findstatic', 'test/file.txt', verbosity=2, stdout=StringIO()) + lines = [line.strip() for line in result.split('\n')] + self.assertIn('project', lines[1]) + self.assertIn('apps', lines[2]) + self.assertIn("Looking in the following locations:", lines[3]) + searched_locations = ', '.join(lines[4:]) # AppDirectoriesFinder searched locations self.assertIn(os.path.join('staticfiles_tests', 'apps', 'test', 'static'), searched_locations) self.assertIn(os.path.join('staticfiles_tests', 'apps', 'no_label', 'static'), searched_locations) @@ -91,7 +113,7 @@ def test_all_files_more_verbose(self): class TestConfiguration(StaticFilesTestCase): def test_location_empty(self): msg = 'without having set the STATIC_ROOT setting to a filesystem path' - err = six.StringIO() + err = StringIO() for root in ['', None]: with override_settings(STATIC_ROOT=root): with self.assertRaisesMessage(ImproperlyConfigured, msg): @@ -154,6 +176,44 @@ def test_common_ignore_patterns(self): self.assertFileNotFound('test/CVS') +class TestCollectionVerbosity(CollectionTestCase): + copying_msg = 'Copying ' + run_collectstatic_in_setUp = False + post_process_msg = 'Post-processed' + staticfiles_copied_msg = 'static files copied to' + + def test_verbosity_0(self): + stdout = StringIO() + self.run_collectstatic(verbosity=0, stdout=stdout) + self.assertEqual(stdout.getvalue(), '') + + def test_verbosity_1(self): + stdout = StringIO() + self.run_collectstatic(verbosity=1, stdout=stdout) + output = stdout.getvalue() + self.assertIn(self.staticfiles_copied_msg, output) + self.assertNotIn(self.copying_msg, output) + + def test_verbosity_2(self): + stdout = StringIO() + self.run_collectstatic(verbosity=2, stdout=stdout) + output = stdout.getvalue() + self.assertIn(self.staticfiles_copied_msg, output) + self.assertIn(self.copying_msg, output) + + @override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.ManifestStaticFilesStorage') + def test_verbosity_1_with_post_process(self): + stdout = StringIO() + self.run_collectstatic(verbosity=1, stdout=stdout, post_process=True) + self.assertNotIn(self.post_process_msg, stdout.getvalue()) + + @override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.ManifestStaticFilesStorage') + def test_verbosity_2_with_post_process(self): + stdout = StringIO() + self.run_collectstatic(verbosity=2, stdout=stdout, post_process=True) + self.assertIn(self.post_process_msg, stdout.getvalue()) + + class TestCollectionClear(CollectionTestCase): """ Test the ``--clear`` option of the ``collectstatic`` management command. @@ -162,14 +222,14 @@ def run_collectstatic(self, **kwargs): clear_filepath = os.path.join(settings.STATIC_ROOT, 'cleared.txt') with open(clear_filepath, 'w') as f: f.write('should be cleared') - super(TestCollectionClear, self).run_collectstatic(clear=True) + super().run_collectstatic(clear=True) def test_cleared_not_found(self): self.assertFileNotFound('cleared.txt') def test_dir_not_exists(self, **kwargs): - shutil.rmtree(six.text_type(settings.STATIC_ROOT)) - super(TestCollectionClear, self).run_collectstatic(clear=True) + shutil.rmtree(settings.STATIC_ROOT) + super().run_collectstatic(clear=True) @override_settings(STATICFILES_STORAGE='staticfiles_tests.storage.PathNotImplementedStorage') def test_handle_path_notimplemented(self): @@ -185,52 +245,54 @@ class TestInteractiveMessages(CollectionTestCase): @staticmethod def mock_input(stdout): def _input(msg): - # Python 2 reads bytes from the console output, use bytes for the StringIO - stdout.write(msg.encode('utf-8') if six.PY2 else msg) + stdout.write(msg) return 'yes' return _input def test_warning_when_clearing_staticdir(self): - stdout = six.StringIO() + stdout = StringIO() self.run_collectstatic() - with mock.patch('django.contrib.staticfiles.management.commands.collectstatic.input', - side_effect=self.mock_input(stdout)): + with mock.patch('builtins.input', side_effect=self.mock_input(stdout)): call_command('collectstatic', interactive=True, clear=True, stdout=stdout) - output = force_text(stdout.getvalue()) + output = stdout.getvalue() self.assertNotIn(self.overwrite_warning_msg, output) self.assertIn(self.delete_warning_msg, output) def test_warning_when_overwriting_files_in_staticdir(self): - stdout = six.StringIO() + stdout = StringIO() self.run_collectstatic() - with mock.patch('django.contrib.staticfiles.management.commands.collectstatic.input', - side_effect=self.mock_input(stdout)): + with mock.patch('builtins.input', side_effect=self.mock_input(stdout)): call_command('collectstatic', interactive=True, stdout=stdout) - output = force_text(stdout.getvalue()) + output = stdout.getvalue() self.assertIn(self.overwrite_warning_msg, output) self.assertNotIn(self.delete_warning_msg, output) def test_no_warning_when_staticdir_does_not_exist(self): - stdout = six.StringIO() - shutil.rmtree(six.text_type(settings.STATIC_ROOT)) + stdout = StringIO() + shutil.rmtree(settings.STATIC_ROOT) call_command('collectstatic', interactive=True, stdout=stdout) - output = force_text(stdout.getvalue()) + output = stdout.getvalue() self.assertNotIn(self.overwrite_warning_msg, output) self.assertNotIn(self.delete_warning_msg, output) self.assertIn(self.files_copied_msg, output) def test_no_warning_for_empty_staticdir(self): - stdout = six.StringIO() - static_dir = tempfile.mkdtemp(prefix='collectstatic_empty_staticdir_test') - with override_settings(STATIC_ROOT=static_dir): - call_command('collectstatic', interactive=True, stdout=stdout) - shutil.rmtree(six.text_type(static_dir)) - output = force_text(stdout.getvalue()) + stdout = StringIO() + with tempfile.TemporaryDirectory(prefix='collectstatic_empty_staticdir_test') as static_dir: + with override_settings(STATIC_ROOT=static_dir): + call_command('collectstatic', interactive=True, stdout=stdout) + output = stdout.getvalue() self.assertNotIn(self.overwrite_warning_msg, output) self.assertNotIn(self.delete_warning_msg, output) self.assertIn(self.files_copied_msg, output) + def test_cancelled(self): + self.run_collectstatic() + with mock.patch('builtins.input', side_effect=lambda _: 'no'): + with self.assertRaisesMessage(CommandError, 'Collecting static files cancelled'): + call_command('collectstatic', interactive=True) + class TestCollectionExcludeNoDefaultIgnore(TestDefaults, CollectionTestCase): """ @@ -238,8 +300,7 @@ class TestCollectionExcludeNoDefaultIgnore(TestDefaults, CollectionTestCase): ``collectstatic`` management command. """ def run_collectstatic(self): - super(TestCollectionExcludeNoDefaultIgnore, self).run_collectstatic( - use_default_ignore_patterns=False) + super().run_collectstatic(use_default_ignore_patterns=False) def test_no_common_ignore_patterns(self): """ @@ -258,11 +319,12 @@ def test_no_common_ignore_patterns(self): class TestCollectionCustomIgnorePatterns(CollectionTestCase): def test_custom_ignore_patterns(self): """ - A custom ignore_patterns list, ['*.css'] in this case, can be specified - in an AppConfig definition. + A custom ignore_patterns list, ['*.css', '*/vendor/*.js'] in this case, + can be specified in an AppConfig definition. """ self.assertFileNotFound('test/nonascii.css') self.assertFileContains('test/.hidden', 'should be ignored') + self.assertFileNotFound(os.path.join('test', 'vendor', 'module.js')) class TestCollectionDryRun(TestNoFilesCreated, CollectionTestCase): @@ -270,7 +332,7 @@ class TestCollectionDryRun(TestNoFilesCreated, CollectionTestCase): Test ``--dry-run`` option for ``collectstatic`` management command. """ def run_collectstatic(self): - super(TestCollectionDryRun, self).run_collectstatic(dry_run=True) + super().run_collectstatic(dry_run=True) class TestCollectionFilesOverride(CollectionTestCase): @@ -308,14 +370,15 @@ def setUp(self): os.utime(self.testfile_path, (self.orig_atime - 1, self.orig_mtime - 1)) self.settings_with_test_app = self.modify_settings( - INSTALLED_APPS={'prepend': 'staticfiles_test_app'}) + INSTALLED_APPS={'prepend': 'staticfiles_test_app'}, + ) with extend_sys_path(self.temp_dir): self.settings_with_test_app.enable() - super(TestCollectionFilesOverride, self).setUp() + super().setUp() def tearDown(self): - super(TestCollectionFilesOverride, self).tearDown() + super().tearDown() self.settings_with_test_app.disable() def test_ordering_override(self): @@ -350,9 +413,9 @@ def _collectstatic_output(self, **kwargs): the command at highest verbosity, which is why we can't just call e.g. BaseCollectionTestCase.run_collectstatic() """ - out = six.StringIO() + out = StringIO() call_command('collectstatic', interactive=False, verbosity=3, stdout=out, **kwargs) - return force_text(out.getvalue()) + return out.getvalue() def test_no_warning(self): """ @@ -365,24 +428,22 @@ def test_warning(self): """ There is a warning when there are duplicate destinations. """ - static_dir = tempfile.mkdtemp() - self.addCleanup(shutil.rmtree, static_dir) + with tempfile.TemporaryDirectory() as static_dir: + duplicate = os.path.join(static_dir, 'test', 'file.txt') + os.mkdir(os.path.dirname(duplicate)) + with open(duplicate, 'w+') as f: + f.write('duplicate of file.txt') - duplicate = os.path.join(static_dir, 'test', 'file.txt') - os.mkdir(os.path.dirname(duplicate)) - with open(duplicate, 'w+') as f: - f.write('duplicate of file.txt') + with self.settings(STATICFILES_DIRS=[static_dir]): + output = self._collectstatic_output(clear=True) + self.assertIn(self.warning_string, output) - with self.settings(STATICFILES_DIRS=[static_dir]): - output = self._collectstatic_output(clear=True) - self.assertIn(self.warning_string, output) + os.remove(duplicate) - os.remove(duplicate) - - # Make sure the warning went away again. - with self.settings(STATICFILES_DIRS=[static_dir]): - output = self._collectstatic_output(clear=True) - self.assertNotIn(self.warning_string, output) + # Make sure the warning went away again. + with self.settings(STATICFILES_DIRS=[static_dir]): + output = self._collectstatic_output(clear=True) + self.assertNotIn(self.warning_string, output) @override_settings(STATICFILES_STORAGE='staticfiles_tests.storage.DummyStorage') @@ -410,9 +471,9 @@ def test_skips_newer_files_in_remote_storage(self): NeverCopyRemoteStorage.get_modified_time() returns a datetime in the future to simulate an unmodified file. """ - stdout = six.StringIO() + stdout = StringIO() self.run_collectstatic(stdout=stdout, verbosity=2) - output = force_text(stdout.getvalue()) + output = stdout.getvalue() self.assertIn("Skipping 'test.txt' (not modified)", output) @@ -426,7 +487,7 @@ class TestCollectionLinks(TestDefaults, CollectionTestCase): ``--link`` does not change the file-selection semantics. """ def run_collectstatic(self, clear=False, link=True, **kwargs): - super(TestCollectionLinks, self).run_collectstatic(link=link, clear=clear, **kwargs) + super().run_collectstatic(link=link, clear=clear, **kwargs) def test_links_created(self): """ @@ -464,3 +525,8 @@ def test_clear_broken_symlink(self): os.symlink(nonexistent_file_path, broken_symlink_path) self.run_collectstatic(clear=True) self.assertFalse(os.path.lexists(broken_symlink_path)) + + @override_settings(STATICFILES_STORAGE='staticfiles_tests.storage.PathNotImplementedStorage') + def test_no_remote_link(self): + with self.assertRaisesMessage(CommandError, "Can't symlink to a remote destination."): + self.run_collectstatic() diff --git a/tests/staticfiles_tests/test_storage.py b/tests/staticfiles_tests/test_storage.py index 4e1dec84f950..97e3b9113d64 100644 --- a/tests/staticfiles_tests/test_storage.py +++ b/tests/staticfiles_tests/test_storage.py @@ -1,20 +1,19 @@ -from __future__ import unicode_literals - import os import shutil import sys import tempfile import unittest +from io import StringIO from django.conf import settings from django.contrib.staticfiles import finders, storage -from django.contrib.staticfiles.management.commands.collectstatic import \ - Command as CollectstaticCommand +from django.contrib.staticfiles.management.commands.collectstatic import ( + Command as CollectstaticCommand, +) from django.core.cache.backends.base import BaseCache from django.core.management import call_command -from django.test import override_settings -from django.utils import six -from django.utils.encoding import force_text +from django.test import SimpleTestCase, ignore_warnings, override_settings +from django.utils.deprecation import RemovedInDjango31Warning from .cases import CollectionTestCase from .settings import TEST_ROOT @@ -25,12 +24,12 @@ def hashed_file_path(test, path): return fullpath.replace(settings.STATIC_URL, '') -class TestHashedFiles(object): +class TestHashedFiles: hashed_file_path = hashed_file_path def setUp(self): self._max_post_process_passes = storage.staticfiles_storage.max_post_process_passes - super(TestHashedFiles, self).setUp() + super().setUp() def tearDown(self): # Clear hashed files to avoid side effects among tests. @@ -45,9 +44,6 @@ def assertPostCondition(self): pass def test_template_tag_return(self): - """ - Test the CachedStaticFilesStorage backend. - """ self.assertStaticRaises(ValueError, "does/not/exist.png", "/static/does/not/exist.png") self.assertStaticRenders("test/file.txt", "/static/test/file.dad0999e4f8f.txt") self.assertStaticRenders("test/file.txt", "/static/test/file.dad0999e4f8f.txt", asvar=True) @@ -98,10 +94,10 @@ def test_path_with_fragment(self): def test_path_with_querystring_and_fragment(self): relpath = self.hashed_file_path("cached/css/fragments.css") - self.assertEqual(relpath, "cached/css/fragments.c4e6753b52d3.css") + self.assertEqual(relpath, "cached/css/fragments.a60c0e74834f.css") with storage.staticfiles_storage.open(relpath) as relfile: content = relfile.read() - self.assertIn(b'fonts/font.a4b0478549d0.eot?#iefix', content) + self.assertIn(b'fonts/font.b9b105392eb8.eot?#iefix', content) self.assertIn(b'fonts/font.b8d603e42714.svg#webfontIyfZbseF', content) self.assertIn(b'fonts/font.b8d603e42714.svg#path/to/../../fonts/font.svg', content) self.assertIn(b'data:font/woff;charset=utf-8;base64,d09GRgABAAAAADJoAA0AAAAAR2QAAQAAAAAAAAAAAAA', content) @@ -174,7 +170,7 @@ def test_template_tag_url(self): ) def test_import_loop(self): finders.get_finder.cache_clear() - err = six.StringIO() + 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()) @@ -227,13 +223,14 @@ def test_post_processing_failure(self): post_processing indicates the origin of the error when it fails. """ finders.get_finder.cache_clear() - err = six.StringIO() + err = StringIO() with self.assertRaises(Exception): call_command('collectstatic', interactive=False, verbosity=0, stderr=err) self.assertEqual("Post-processing 'faulty.css' failed!\n\n", err.getvalue()) self.assertPostCondition() +@ignore_warnings(category=RemovedInDjango31Warning) @override_settings( STATICFILES_STORAGE='django.contrib.staticfiles.storage.CachedStaticFilesStorage', ) @@ -301,14 +298,24 @@ def test_corrupt_intermediate_files(self): self.hashed_file_path('cached/styles.css') -@override_settings( - STATICFILES_STORAGE='staticfiles_tests.storage.ExtraPatternsCachedStaticFilesStorage', -) -class TestExtraPatternsCachedStorage(CollectionTestCase): +class TestCachedStaticFilesStorageDeprecation(SimpleTestCase): + def test_warning(self): + from django.contrib.staticfiles.storage import CachedStaticFilesStorage + from django.utils.deprecation import RemovedInDjango31Warning + msg = ( + 'CachedStaticFilesStorage is deprecated in favor of ' + 'ManifestStaticFilesStorage.' + ) + with self.assertRaisesMessage(RemovedInDjango31Warning, msg): + CachedStaticFilesStorage() + + +@override_settings(STATICFILES_STORAGE='staticfiles_tests.storage.ExtraPatternsStorage') +class TestExtraPatternsStorage(CollectionTestCase): def setUp(self): storage.staticfiles_storage.hashed_files.clear() # avoid cache interference - super(TestExtraPatternsCachedStorage, self).setUp() + super().setUp() def cached_file_path(self, path): fullpath = self.render_template(self.static_template_snippet(path)) @@ -341,7 +348,7 @@ class TestCollectionManifestStorage(TestHashedFiles, CollectionTestCase): Tests for the Cache busting storage """ def setUp(self): - super(TestCollectionManifestStorage, self).setUp() + super().setUp() temp_dir = tempfile.mkdtemp() os.makedirs(os.path.join(temp_dir, 'test')) @@ -350,9 +357,10 @@ def setUp(self): f.write('to be deleted in one test') self.patched_settings = self.settings( - STATICFILES_DIRS=settings.STATICFILES_DIRS + [temp_dir]) + STATICFILES_DIRS=settings.STATICFILES_DIRS + [temp_dir], + ) self.patched_settings.enable() - self.addCleanup(shutil.rmtree, six.text_type(temp_dir)) + self.addCleanup(shutil.rmtree, temp_dir) self._manifest_strict = storage.staticfiles_storage.manifest_strict def tearDown(self): @@ -362,7 +370,7 @@ def tearDown(self): os.unlink(self._clear_filename) storage.staticfiles_storage.manifest_strict = self._manifest_strict - super(TestCollectionManifestStorage, self).tearDown() + super().tearDown() def assertPostCondition(self): hashed_files = storage.staticfiles_storage.hashed_files @@ -382,7 +390,7 @@ def test_loaded_cache(self): manifest_content = storage.staticfiles_storage.read_manifest() self.assertIn( '"version": "%s"' % storage.staticfiles_storage.manifest_version, - force_text(manifest_content) + manifest_content ) def test_parse_cache(self): @@ -431,30 +439,22 @@ def test_missing_entry(self): with self.assertRaisesMessage(ValueError, err_msg): self.hashed_file_path(missing_file_name) - content = six.StringIO() + content = StringIO() content.write('Found') configured_storage.save(missing_file_name, content) # File exists on disk self.hashed_file_path(missing_file_name) -@override_settings( - STATICFILES_STORAGE='staticfiles_tests.storage.SimpleCachedStaticFilesStorage', -) -class TestCollectionSimpleCachedStorage(CollectionTestCase): - """ - Tests for the Cache busting storage - """ +@override_settings(STATICFILES_STORAGE='staticfiles_tests.storage.SimpleStorage') +class TestCollectionSimpleStorage(CollectionTestCase): hashed_file_path = hashed_file_path def setUp(self): storage.staticfiles_storage.hashed_files.clear() # avoid cache interference - super(TestCollectionSimpleCachedStorage, self).setUp() + super().setUp() def test_template_tag_return(self): - """ - Test the CachedStaticFilesStorage backend. - """ self.assertStaticRaises(ValueError, "does/not/exist.png", "/static/does/not/exist.png") self.assertStaticRenders("test/file.txt", "/static/test/file.deploy12345.txt") self.assertStaticRenders("cached/styles.css", "/static/cached/styles.deploy12345.css") @@ -477,7 +477,7 @@ class CustomStaticFilesStorage(storage.StaticFilesStorage): def __init__(self, *args, **kwargs): kwargs['file_permissions_mode'] = 0o640 kwargs['directory_permissions_mode'] = 0o740 - super(CustomStaticFilesStorage, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) @unittest.skipIf(sys.platform.startswith('win'), "Windows only partially supports chmod.") @@ -492,11 +492,11 @@ class TestStaticFilePermissions(CollectionTestCase): def setUp(self): self.umask = 0o027 self.old_umask = os.umask(self.umask) - super(TestStaticFilePermissions, self).setUp() + super().setUp() def tearDown(self): os.umask(self.old_umask) - super(TestStaticFilePermissions, self).tearDown() + super().tearDown() # Don't run collectstatic command in this test class. def run_collectstatic(self, **kwargs): @@ -544,7 +544,7 @@ def test_collect_static_files_subclass_of_static_storage(self): @override_settings( - STATICFILES_STORAGE='django.contrib.staticfiles.storage.CachedStaticFilesStorage', + STATICFILES_STORAGE='django.contrib.staticfiles.storage.ManifestStaticFilesStorage', ) class TestCollectionHashedFilesCache(CollectionTestCase): """ @@ -554,32 +554,46 @@ class TestCollectionHashedFilesCache(CollectionTestCase): hashed_file_path = hashed_file_path def setUp(self): - self.testimage_path = os.path.join( - TEST_ROOT, 'project', 'documents', 'cached', 'css', 'img', 'window.png' - ) - with open(self.testimage_path, 'r+b') as f: - self._orig_image_content = f.read() - super(TestCollectionHashedFilesCache, self).setUp() + super().setUp() + self._temp_dir = temp_dir = tempfile.mkdtemp() + os.makedirs(os.path.join(temp_dir, 'test')) + self.addCleanup(shutil.rmtree, temp_dir) - def tearDown(self): - with open(self.testimage_path, 'w+b') as f: - f.write(self._orig_image_content) - super(TestCollectionHashedFilesCache, self).tearDown() + def _get_filename_path(self, filename): + return os.path.join(self._temp_dir, 'test', filename) def test_file_change_after_collectstatic(self): - finders.get_finder.cache_clear() - err = six.StringIO() - call_command('collectstatic', interactive=False, verbosity=0, stderr=err) - with open(self.testimage_path, 'w+b') as f: - f.write(b"new content of png file to change it's hash") - - # Change modification time of self.testimage_path to make sure it gets - # collected again. - mtime = os.path.getmtime(self.testimage_path) - atime = os.path.getatime(self.testimage_path) - os.utime(self.testimage_path, (mtime + 1, atime + 1)) - - call_command('collectstatic', interactive=False, verbosity=0, stderr=err) - relpath = self.hashed_file_path('cached/css/window.css') - with storage.staticfiles_storage.open(relpath) as relfile: - self.assertIn(b'window.a836fe39729e.png', relfile.read()) + # Create initial static files. + file_contents = ( + ('foo.png', 'foo'), + ('bar.css', 'url("foo.png")\nurl("xyz.png")'), + ('xyz.png', 'xyz'), + ) + for filename, content in file_contents: + with open(self._get_filename_path(filename), 'w') as f: + f.write(content) + + with self.modify_settings(STATICFILES_DIRS={'append': self._temp_dir}): + finders.get_finder.cache_clear() + err = StringIO() + # First collectstatic run. + call_command('collectstatic', interactive=False, verbosity=0, stderr=err) + relpath = self.hashed_file_path('test/bar.css') + with storage.staticfiles_storage.open(relpath) as relfile: + content = relfile.read() + self.assertIn(b'foo.acbd18db4cc2.png', content) + self.assertIn(b'xyz.d16fb36f0911.png', content) + + # Change the contents of the png files. + for filename in ('foo.png', 'xyz.png'): + with open(self._get_filename_path(filename), 'w+b') as f: + f.write(b"new content of file to change its hash") + + # The hashes of the png files in the CSS file are updated after + # a second collectstatic. + call_command('collectstatic', interactive=False, verbosity=0, stderr=err) + relpath = self.hashed_file_path('test/bar.css') + with storage.staticfiles_storage.open(relpath) as relfile: + content = relfile.read() + self.assertIn(b'foo.57a5cb9ba68d.png', content) + self.assertIn(b'xyz.57a5cb9ba68d.png', content) diff --git a/tests/staticfiles_tests/test_templatetag_deprecation.py b/tests/staticfiles_tests/test_templatetag_deprecation.py new file mode 100644 index 000000000000..898ee68093ea --- /dev/null +++ b/tests/staticfiles_tests/test_templatetag_deprecation.py @@ -0,0 +1,36 @@ +from urllib.parse import urljoin + +from django.contrib.staticfiles import storage +from django.contrib.staticfiles.templatetags.staticfiles import static +from django.template import Context, Template +from django.test import SimpleTestCase, override_settings +from django.utils.deprecation import RemovedInDjango30Warning + + +class StaticTestStorage(storage.StaticFilesStorage): + def url(self, name): + return urljoin('https://example.com/assets/', name) + + +@override_settings( + STATIC_URL='http://media.example.com/static/', + INSTALLED_APPS=('django.contrib.staticfiles',), + STATICFILES_STORAGE='staticfiles_tests.test_forms.StaticTestStorage', +) +class StaticDeprecationTests(SimpleTestCase): + def test_templatetag_deprecated(self): + msg = '{% load staticfiles %} is deprecated in favor of {% load static %}.' + template = "{% load staticfiles %}{% static 'main.js' %}" + with self.assertWarnsMessage(RemovedInDjango30Warning, msg): + template = Template(template) + rendered = template.render(Context()) + self.assertEqual(rendered, 'https://example.com/assets/main.js') + + def test_static_deprecated(self): + msg = ( + 'django.contrib.staticfiles.templatetags.static() is deprecated in ' + 'favor of django.templatetags.static.static().' + ) + with self.assertWarnsMessage(RemovedInDjango30Warning, msg): + url = static('main.js') + self.assertEqual(url, 'https://example.com/assets/main.js') diff --git a/tests/staticfiles_tests/test_templatetags.py b/tests/staticfiles_tests/test_templatetags.py index 541b233855ad..cd3f7d4bfa5e 100644 --- a/tests/staticfiles_tests/test_templatetags.py +++ b/tests/staticfiles_tests/test_templatetags.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.test import override_settings from .cases import StaticFilesTestCase diff --git a/tests/staticfiles_tests/test_utils.py b/tests/staticfiles_tests/test_utils.py new file mode 100644 index 000000000000..4610b7f00ffd --- /dev/null +++ b/tests/staticfiles_tests/test_utils.py @@ -0,0 +1,14 @@ +from django.contrib.staticfiles.utils import check_settings +from django.core.exceptions import ImproperlyConfigured +from django.test import SimpleTestCase, override_settings + + +class CheckSettingsTests(SimpleTestCase): + + @override_settings(DEBUG=True, MEDIA_URL='/static/media/', STATIC_URL='/static/',) + def test_media_url_in_static_url(self): + msg = "runserver can't serve media if MEDIA_URL is within STATIC_URL." + with self.assertRaisesMessage(ImproperlyConfigured, msg): + check_settings() + with self.settings(DEBUG=False): # Check disabled if DEBUG=False. + check_settings() diff --git a/tests/staticfiles_tests/test_views.py b/tests/staticfiles_tests/test_views.py index 6d9096784f4c..d16f4a2cec1b 100644 --- a/tests/staticfiles_tests/test_views.py +++ b/tests/staticfiles_tests/test_views.py @@ -1,6 +1,5 @@ -from __future__ import unicode_literals - import posixpath +from urllib.parse import quote from django.conf import settings from django.test import override_settings @@ -14,8 +13,7 @@ class TestServeStatic(StaticFilesTestCase): Test static asset serving view. """ def _response(self, filepath): - return self.client.get( - posixpath.join(settings.STATIC_URL, filepath)) + return self.client.get(quote(posixpath.join(settings.STATIC_URL, filepath))) def assertFileContains(self, filepath, text): self.assertContains(self._response(filepath), text) diff --git a/tests/staticfiles_tests/urls/default.py b/tests/staticfiles_tests/urls/default.py index 5931ebc3be89..7d45483131d8 100644 --- a/tests/staticfiles_tests/urls/default.py +++ b/tests/staticfiles_tests/urls/default.py @@ -1,6 +1,6 @@ -from django.conf.urls import url from django.contrib.staticfiles import views +from django.urls import re_path urlpatterns = [ - url(r'^static/(?P.*)$', views.serve), + re_path('^static/(?P.*)$', views.serve), ] diff --git a/tests/str/models.py b/tests/str/models.py index e606e912aab6..d2f7a45c2045 100644 --- a/tests/str/models.py +++ b/tests/str/models.py @@ -1,34 +1,16 @@ -# -*- coding: utf-8 -*- """ -Adding __str__() or __unicode__() to models +Adding __str__() to models -Although it's not a strict requirement, each model should have a -``_str__()`` or ``__unicode__()`` method to return a "human-readable" -representation of the object. Do this not only for your own sanity when dealing -with the interactive prompt, but also because objects' representations are used -throughout Django's automatically-generated admin. - -Normally, you should write ``__unicode__()`` method, since this will work for -all field types (and Django will automatically provide an appropriate -``__str__()`` method). However, you can write a ``__str__()`` method directly, -if you prefer. You must be careful to encode the results correctly, though. +Although it's not a strict requirement, each model should have a ``_str__()`` +method to return a "human-readable" representation of the object. Do this not +only for your own sanity when dealing with the interactive prompt, but also +because objects' representations are used throughout Django's +automatically-generated admin. """ from django.db import models -from django.utils.encoding import python_2_unicode_compatible - - -class Article(models.Model): - headline = models.CharField(max_length=100) - pub_date = models.DateTimeField() - - def __str__(self): - # Caution: this is only safe if you are certain that headline will be - # in ASCII. - return self.headline -@python_2_unicode_compatible class InternationalArticle(models.Model): headline = models.CharField(max_length=100) pub_date = models.DateTimeField() diff --git a/tests/str/tests.py b/tests/str/tests.py index 08fd554e5ecb..181906fa0dc9 100644 --- a/tests/str/tests.py +++ b/tests/str/tests.py @@ -1,40 +1,20 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import datetime -from unittest import skipIf from django.db import models from django.test import TestCase from django.test.utils import isolate_apps -from django.utils import six -from .models import Article, InternationalArticle +from .models import InternationalArticle class SimpleTests(TestCase): - @skipIf(six.PY3, "tests a __str__ method returning unicode under Python 2") - def test_basic(self): - a = Article.objects.create( - headline=b'Parrot programs in Python', - pub_date=datetime.datetime(2005, 7, 28) - ) - self.assertEqual(str(a), str('Parrot programs in Python')) - self.assertEqual(repr(a), str('')) - def test_international(self): a = InternationalArticle.objects.create( headline='Girl wins €12.500 in lottery', pub_date=datetime.datetime(2005, 7, 28) ) - if six.PY3: - self.assertEqual(str(a), 'Girl wins €12.500 in lottery') - else: - # On Python 2, the default str() output will be the UTF-8 encoded - # output of __unicode__() -- or __str__() when the - # python_2_unicode_compatible decorator is used. - self.assertEqual(str(a), b'Girl wins \xe2\x82\xac12.500 in lottery') + self.assertEqual(str(a), 'Girl wins €12.500 in lottery') @isolate_apps('str') def test_defaults(self): @@ -50,5 +30,8 @@ class Default(models.Model): # coerce the returned value. self.assertIsInstance(obj.__str__(), str) self.assertIsInstance(obj.__repr__(), str) - self.assertEqual(str(obj), str('Default object')) - self.assertEqual(repr(obj), str('')) + self.assertEqual(str(obj), 'Default object (None)') + self.assertEqual(repr(obj), '') + obj2 = Default(pk=100) + self.assertEqual(str(obj2), 'Default object (100)') + self.assertEqual(repr(obj2), '') diff --git a/tests/string_lookup/models.py b/tests/string_lookup/models.py index 775ec2faf001..5351ee5fe931 100644 --- a/tests/string_lookup/models.py +++ b/tests/string_lookup/models.py @@ -1,11 +1,6 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models -from django.utils.encoding import python_2_unicode_compatible -@python_2_unicode_compatible class Foo(models.Model): name = models.CharField(max_length=50) friend = models.CharField(max_length=50, blank=True) @@ -14,7 +9,6 @@ def __str__(self): return "Foo %s" % self.name -@python_2_unicode_compatible class Bar(models.Model): name = models.CharField(max_length=50) normal = models.ForeignKey(Foo, models.CASCADE, related_name='normal_foo') @@ -25,7 +19,6 @@ def __str__(self): return "Bar %s" % self.place.name -@python_2_unicode_compatible class Whiz(models.Model): name = models.CharField(max_length=50) @@ -33,7 +26,6 @@ def __str__(self): return "Whiz %s" % self.name -@python_2_unicode_compatible class Child(models.Model): parent = models.OneToOneField('Base', models.CASCADE) name = models.CharField(max_length=50) @@ -42,7 +34,6 @@ def __str__(self): return "Child %s" % self.name -@python_2_unicode_compatible class Base(models.Model): name = models.CharField(max_length=50) @@ -50,7 +41,6 @@ def __str__(self): return "Base %s" % self.name -@python_2_unicode_compatible class Article(models.Model): name = models.CharField(max_length=50) text = models.TextField() diff --git a/tests/string_lookup/tests.py b/tests/string_lookup/tests.py index 805833a8e483..a8e39dbb6c5e 100644 --- a/tests/string_lookup/tests.py +++ b/tests/string_lookup/tests.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.test import TestCase from .models import Article, Bar, Base, Child, Foo, Whiz @@ -54,9 +51,6 @@ def test_unicode_chars_in_queries(self): fx.save() self.assertEqual(Foo.objects.get(friend__contains='\xe7'), fx) - # We can also do the above query using UTF-8 strings. - self.assertEqual(Foo.objects.get(friend__contains=b'\xc3\xa7'), fx) - def test_queries_on_textfields(self): """ Regression tests for #5087 diff --git a/tests/swappable_models/tests.py b/tests/swappable_models/tests.py index 548c7b328610..bdf681dd87de 100644 --- a/tests/swappable_models/tests.py +++ b/tests/swappable_models/tests.py @@ -1,16 +1,22 @@ -from __future__ import unicode_literals - -from swappable_models.models import Article +from io import StringIO from django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType from django.core import management from django.test import TestCase, override_settings -from django.utils.six import StringIO + +from .models import Article class SwappableModelTests(TestCase): + # Limit memory usage when calling 'migrate'. + available_apps = [ + 'swappable_models', + 'django.contrib.auth', + 'django.contrib.contenttypes', + ] + @override_settings(TEST_ARTICLE_MODEL='swappable_models.AlternateArticle') def test_generated_data(self): "Permissions and content types are not created for a swapped model" diff --git a/tests/syndication_tests/feeds.py b/tests/syndication_tests/feeds.py index f4162b345e63..080525813864 100644 --- a/tests/syndication_tests/feeds.py +++ b/tests/syndication_tests/feeds.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.contrib.syndication import views from django.utils import feedgenerator from django.utils.timezone import get_fixed_timezone @@ -133,7 +131,7 @@ class TemplateContextFeed(TestRss2Feed): description_template = 'syndication/description_context.html' def get_context_data(self, **kwargs): - context = super(TemplateContextFeed, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context['foo'] = 'bar' return context @@ -166,21 +164,21 @@ class MyCustomAtom1Feed(feedgenerator.Atom1Feed): Test of a custom feed generator class. """ def root_attributes(self): - attrs = super(MyCustomAtom1Feed, self).root_attributes() + attrs = super().root_attributes() attrs['django'] = 'rocks' return attrs def add_root_elements(self, handler): - super(MyCustomAtom1Feed, self).add_root_elements(handler) + super().add_root_elements(handler) handler.addQuickElement('spam', 'eggs') def item_attributes(self, item): - attrs = super(MyCustomAtom1Feed, self).item_attributes(item) + attrs = super().item_attributes(item) attrs['bacon'] = 'yum' return attrs def add_item_elements(self, handler, item): - super(MyCustomAtom1Feed, self).add_item_elements(handler, item) + super().add_item_elements(handler, item) handler.addQuickElement('ministry', 'silly walks') diff --git a/tests/syndication_tests/models.py b/tests/syndication_tests/models.py index 22b0f81fbb0d..f6c499ccca29 100644 --- a/tests/syndication_tests/models.py +++ b/tests/syndication_tests/models.py @@ -1,8 +1,6 @@ from django.db import models -from django.utils.encoding import python_2_unicode_compatible -@python_2_unicode_compatible class Entry(models.Model): title = models.CharField(max_length=200) updated = models.DateTimeField() @@ -18,7 +16,6 @@ def get_absolute_url(self): return "/blog/%s/" % self.pk -@python_2_unicode_compatible class Article(models.Model): title = models.CharField(max_length=200) entry = models.ForeignKey(Entry, models.CASCADE) diff --git a/tests/syndication_tests/tests.py b/tests/syndication_tests/tests.py index f8948ac21d78..043533d94339 100644 --- a/tests/syndication_tests/tests.py +++ b/tests/syndication_tests/tests.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import datetime from xml.dom import minidom @@ -9,10 +7,7 @@ from django.test import TestCase, override_settings from django.test.utils import requires_tz_support from django.utils import timezone -from django.utils.deprecation import RemovedInDjango20Warning -from django.utils.feedgenerator import ( - Enclosure, SyndicationFeed, rfc2822_date, rfc3339_date, -) +from django.utils.feedgenerator import rfc2822_date, rfc3339_date from .models import Article, Entry @@ -46,7 +41,7 @@ def setUpTestData(cls): cls.a1 = Article.objects.create(title='My first article', entry=cls.e1) def assertChildNodes(self, elem, expected): - actual = set(n.nodeName for n in elem.childNodes) + actual = {n.nodeName for n in elem.childNodes} expected = set(expected) self.assertEqual(actual, expected) @@ -57,7 +52,7 @@ def assertChildNodeContent(self, elem, expected): def assertCategories(self, elem, expected): self.assertEqual( - set(i.firstChild.wholeText for i in elem.childNodes if i.nodeName == 'category'), + {i.firstChild.wholeText for i in elem.childNodes if i.nodeName == 'category'}, set(expected) ) @@ -69,7 +64,7 @@ class SyndicationFeedTest(FeedTestCase): """ @classmethod def setUpClass(cls): - super(SyndicationFeedTest, cls).setUpClass() + super().setUpClass() # This cleanup is necessary because contrib.sites cache # makes tests interfere with each other, see #11505 Site.objects.clear_cache() @@ -457,7 +452,11 @@ def test_item_link_error(self): An ImproperlyConfigured is raised if no link could be found for the item(s). """ - with self.assertRaises(ImproperlyConfigured): + msg = ( + 'Give your Article class a get_absolute_url() method, or define ' + 'an item_link() method in your Feed class.' + ) + with self.assertRaisesMessage(ImproperlyConfigured, msg): self.client.get('/syndication/articles/') def test_template_feed(self): @@ -496,40 +495,14 @@ def test_add_domain(self): """ add_domain() prefixes domains onto the correct URLs. """ - self.assertEqual( - views.add_domain('example.com', '/foo/?arg=value'), - 'http://example.com/foo/?arg=value' - ) - self.assertEqual( - views.add_domain('example.com', '/foo/?arg=value', True), - 'https://example.com/foo/?arg=value' - ) - self.assertEqual( - views.add_domain('example.com', 'http://djangoproject.com/doc/'), - 'http://djangoproject.com/doc/' + prefix_domain_mapping = ( + (('example.com', '/foo/?arg=value'), 'http://example.com/foo/?arg=value'), + (('example.com', '/foo/?arg=value', True), 'https://example.com/foo/?arg=value'), + (('example.com', 'http://djangoproject.com/doc/'), 'http://djangoproject.com/doc/'), + (('example.com', 'https://djangoproject.com/doc/'), 'https://djangoproject.com/doc/'), + (('example.com', 'mailto:uhoh@djangoproject.com'), 'mailto:uhoh@djangoproject.com'), + (('example.com', '//example.com/foo/?arg=value'), 'http://example.com/foo/?arg=value'), ) - self.assertEqual( - views.add_domain('example.com', 'https://djangoproject.com/doc/'), - 'https://djangoproject.com/doc/' - ) - self.assertEqual( - views.add_domain('example.com', 'mailto:uhoh@djangoproject.com'), - 'mailto:uhoh@djangoproject.com' - ) - self.assertEqual( - views.add_domain('example.com', '//example.com/foo/?arg=value'), - 'http://example.com/foo/?arg=value' - ) - - -class FeedgeneratorTestCase(TestCase): - def test_add_item_warns_when_enclosure_kwarg_is_used(self): - feed = SyndicationFeed(title='Example', link='http://example.com', description='Foo') - msg = 'The enclosure keyword argument is deprecated, use enclosures instead.' - with self.assertRaisesMessage(RemovedInDjango20Warning, msg): - feed.add_item( - title='Example Item', - link='https://example.com/item', - description='bar', - enclosure=Enclosure('http://example.com/favicon.ico', 0, 'image/png'), - ) + for prefix in prefix_domain_mapping: + with self.subTest(prefix=prefix): + self.assertEqual(views.add_domain(*prefix[0]), prefix[1]) diff --git a/tests/syndication_tests/urls.py b/tests/syndication_tests/urls.py index 09f7e789cdd8..d23c33e21b52 100644 --- a/tests/syndication_tests/urls.py +++ b/tests/syndication_tests/urls.py @@ -1,26 +1,28 @@ -from django.conf.urls import url +from django.urls import path from . import feeds urlpatterns = [ - url(r'^syndication/rss2/$', feeds.TestRss2Feed()), - url(r'^syndication/rss2/guid_ispermalink_true/$', + path('syndication/rss2/', feeds.TestRss2Feed()), + path( + 'syndication/rss2/guid_ispermalink_true/', feeds.TestRss2FeedWithGuidIsPermaLinkTrue()), - url(r'^syndication/rss2/guid_ispermalink_false/$', + path( + 'syndication/rss2/guid_ispermalink_false/', feeds.TestRss2FeedWithGuidIsPermaLinkFalse()), - url(r'^syndication/rss091/$', feeds.TestRss091Feed()), - url(r'^syndication/no_pubdate/$', feeds.TestNoPubdateFeed()), - url(r'^syndication/atom/$', feeds.TestAtomFeed()), - url(r'^syndication/latest/$', feeds.TestLatestFeed()), - url(r'^syndication/custom/$', feeds.TestCustomFeed()), - url(r'^syndication/naive-dates/$', feeds.NaiveDatesFeed()), - url(r'^syndication/aware-dates/$', feeds.TZAwareDatesFeed()), - url(r'^syndication/feedurl/$', feeds.TestFeedUrlFeed()), - url(r'^syndication/articles/$', feeds.ArticlesFeed()), - url(r'^syndication/template/$', feeds.TemplateFeed()), - url(r'^syndication/template_context/$', feeds.TemplateContextFeed()), - url(r'^syndication/rss2/single-enclosure/$', feeds.TestSingleEnclosureRSSFeed()), - url(r'^syndication/rss2/multiple-enclosure/$', feeds.TestMultipleEnclosureRSSFeed()), - url(r'^syndication/atom/single-enclosure/$', feeds.TestSingleEnclosureAtomFeed()), - url(r'^syndication/atom/multiple-enclosure/$', feeds.TestMultipleEnclosureAtomFeed()), + path('syndication/rss091/', feeds.TestRss091Feed()), + path('syndication/no_pubdate/', feeds.TestNoPubdateFeed()), + path('syndication/atom/', feeds.TestAtomFeed()), + path('syndication/latest/', feeds.TestLatestFeed()), + path('syndication/custom/', feeds.TestCustomFeed()), + path('syndication/naive-dates/', feeds.NaiveDatesFeed()), + path('syndication/aware-dates/', feeds.TZAwareDatesFeed()), + path('syndication/feedurl/', feeds.TestFeedUrlFeed()), + path('syndication/articles/', feeds.ArticlesFeed()), + path('syndication/template/', feeds.TemplateFeed()), + path('syndication/template_context/', feeds.TemplateContextFeed()), + path('syndication/rss2/single-enclosure/', feeds.TestSingleEnclosureRSSFeed()), + path('syndication/rss2/multiple-enclosure/', feeds.TestMultipleEnclosureRSSFeed()), + path('syndication/atom/single-enclosure/', feeds.TestSingleEnclosureAtomFeed()), + path('syndication/atom/multiple-enclosure/', feeds.TestMultipleEnclosureAtomFeed()), ] diff --git a/tests/template_backends/test_django.py b/tests/template_backends/test_django.py index 567537f7a23f..e7a4a035468a 100644 --- a/tests/template_backends/test_django.py +++ b/tests/template_backends/test_django.py @@ -12,6 +12,7 @@ class DjangoTemplatesTests(TemplateStringsTests): engine_class = DjangoTemplates backend_name = 'django' + request_factory = RequestFactory() def test_context_has_priority_over_template_context_processors(self): # See ticket #23789. @@ -25,7 +26,7 @@ def test_context_has_priority_over_template_context_processors(self): }) template = engine.from_string('{{ processors }}') - request = RequestFactory().get('/') + request = self.request_factory.get('/') # Context processors run content = template.render({}, request) @@ -45,7 +46,7 @@ def test_render_requires_dict(self): }) template = engine.from_string('') context = Context() - request_context = RequestContext(RequestFactory().get('/'), {}) + request_context = RequestContext(self.request_factory.get('/'), {}) msg = 'context must be a dict rather than Context.' with self.assertRaisesMessage(TypeError, msg): template.render(context) diff --git a/tests/template_backends/test_dummy.py b/tests/template_backends/test_dummy.py index 83b42c7eb4bf..24c30c97e3b0 100644 --- a/tests/template_backends/test_dummy.py +++ b/tests/template_backends/test_dummy.py @@ -1,7 +1,3 @@ -# coding: utf-8 - -from __future__ import unicode_literals - import re from django.forms import CharField, Form, Media @@ -22,7 +18,7 @@ class TemplateStringsTests(SimpleTestCase): @classmethod def setUpClass(cls): - super(TemplateStringsTests, cls).setUpClass() + super().setUpClass() params = { 'DIRS': [], 'APP_DIRS': True, @@ -41,9 +37,9 @@ def test_get_template(self): content = template.render({'name': 'world'}) self.assertEqual(content, "Hello world!\n") - def test_get_template_non_existing(self): + def test_get_template_nonexistent(self): with self.assertRaises(TemplateDoesNotExist) as e: - self.engine.get_template('template_backends/non_existing.html') + self.engine.get_template('template_backends/nonexistent.html') self.assertEqual(e.exception.backend, self.engine) def test_get_template_syntax_error(self): @@ -85,7 +81,7 @@ def test_csrf_token(self): template = self.engine.get_template('template_backends/csrf.html') content = template.render(request=request) - expected = '' + expected = '' match = re.match(expected, content) or re.match(expected.replace('"', "'"), content) self.assertTrue(match, "hidden csrftoken field not found in output") self.assertTrue(equivalent_tokens(match.group(1), get_token(request))) diff --git a/tests/template_backends/test_jinja2.py b/tests/template_backends/test_jinja2.py index a79c6bc4603f..117719fa0d20 100644 --- a/tests/template_backends/test_jinja2.py +++ b/tests/template_backends/test_jinja2.py @@ -1,7 +1,3 @@ -# Since this package contains a "jinja2" directory, this is required to -# silence an ImportWarning warning on Python 2. -from __future__ import absolute_import - from unittest import skipIf from django.template import TemplateSyntaxError diff --git a/tests/template_backends/test_utils.py b/tests/template_backends/test_utils.py index d1f3aaa7935a..d0bd01cdc26b 100644 --- a/tests/template_backends/test_utils.py +++ b/tests/template_backends/test_utils.py @@ -11,9 +11,9 @@ def test_backend_import_error(self): Failing to import a backend keeps raising the original import error (#24265). """ - with self.assertRaisesRegex(ImportError, "No module named '?raise"): + with self.assertRaisesMessage(ImportError, "No module named 'raise"): engines.all() - with self.assertRaisesRegex(ImportError, "No module named '?raise"): + with self.assertRaisesMessage(ImportError, "No module named 'raise"): engines.all() @override_settings(TEMPLATES=[{ diff --git a/tests/template_tests/alternate_urls.py b/tests/template_tests/alternate_urls.py index cb73513879cf..181e5c50a575 100644 --- a/tests/template_tests/alternate_urls.py +++ b/tests/template_tests/alternate_urls.py @@ -1,11 +1,11 @@ -from django.conf.urls import url +from django.urls import path from . import views urlpatterns = [ # View returning a template response - url(r'^template_response_view/$', views.template_response_view), + path('template_response_view/', views.template_response_view), # A view that can be hard to find... - url(r'^snark/', views.snark, name='snark'), + path('snark/', views.snark, name='snark'), ] diff --git a/tests/template_tests/filter_tests/test_chaining.py b/tests/template_tests/filter_tests/test_chaining.py index 1c12e252a031..9bc3976f375a 100644 --- a/tests/template_tests/filter_tests/test_chaining.py +++ b/tests/template_tests/filter_tests/test_chaining.py @@ -1,8 +1,4 @@ -import warnings - -from django.test import SimpleTestCase, ignore_warnings -from django.test.utils import reset_warning_registry -from django.utils.deprecation import RemovedInDjango20Warning +from django.test import SimpleTestCase from django.utils.safestring import mark_safe from ..utils import setup @@ -42,20 +38,9 @@ def test_chaining04(self): # Using a filter that forces safeness does not lead to double-escaping @setup({'chaining05': '{{ a|escape|capfirst }}'}) def test_chaining05(self): - reset_warning_registry() - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter('always') - output = self.engine.render_to_string('chaining05', {'a': 'a < b'}) - self.assertEqual(output, 'A < b') - - self.assertEqual(len(warns), 1) - self.assertEqual( - str(warns[0].message), - "escape isn't the last filter in ['escape_filter', 'capfirst'] and " - "will be applied immediately in Django 2.0 so the output may change." - ) - - @ignore_warnings(category=RemovedInDjango20Warning) + output = self.engine.render_to_string('chaining05', {'a': 'a < b'}) + self.assertEqual(output, 'A < b') + @setup({'chaining06': '{% autoescape off %}{{ a|escape|capfirst }}{% endautoescape %}'}) def test_chaining06(self): output = self.engine.render_to_string('chaining06', {'a': 'a < b'}) diff --git a/tests/template_tests/filter_tests/test_date.py b/tests/template_tests/filter_tests/test_date.py index 4c83068eb8bf..f973c229b9b5 100644 --- a/tests/template_tests/filter_tests/test_date.py +++ b/tests/template_tests/filter_tests/test_date.py @@ -80,5 +80,9 @@ class FunctionTests(SimpleTestCase): def test_date(self): self.assertEqual(date(datetime(2005, 12, 29), "d F Y"), '29 December 2005') + def test_no_args(self): + self.assertEqual(date(''), '') + self.assertEqual(date(None), '') + def test_escape_characters(self): self.assertEqual(date(datetime(2005, 12, 29), r'jS \o\f F'), '29th of December') diff --git a/tests/template_tests/filter_tests/test_escape.py b/tests/template_tests/filter_tests/test_escape.py index 644ed7ac9e7e..22d5ca7d5594 100644 --- a/tests/template_tests/filter_tests/test_escape.py +++ b/tests/template_tests/filter_tests/test_escape.py @@ -1,7 +1,5 @@ from django.template.defaultfilters import escape -from django.test import SimpleTestCase, ignore_warnings -from django.utils import six -from django.utils.deprecation import RemovedInDjango20Warning +from django.test import SimpleTestCase from django.utils.functional import Promise, lazy from django.utils.safestring import mark_safe @@ -24,22 +22,18 @@ def test_escape02(self): output = self.engine.render_to_string('escape02', {"a": "x&y", "b": mark_safe("x&y")}) self.assertEqual(output, "x&y x&y") - # It is only applied once, regardless of the number of times it - # appears in a chain (to be changed in Django 2.0). - @ignore_warnings(category=RemovedInDjango20Warning) @setup({'escape03': '{% autoescape off %}{{ a|escape|escape }}{% endautoescape %}'}) def test_escape03(self): output = self.engine.render_to_string('escape03', {"a": "x&y"}) self.assertEqual(output, "x&y") - @ignore_warnings(category=RemovedInDjango20Warning) @setup({'escape04': '{{ a|escape|escape }}'}) def test_escape04(self): output = self.engine.render_to_string('escape04', {"a": "x&y"}) self.assertEqual(output, "x&y") def test_escape_lazy_string(self): - add_html = lazy(lambda string: string + 'special characters > here', six.text_type) + add_html = lazy(lambda string: string + 'special characters > here', str) escaped = escape(add_html('this' + string, six.text_type) + append_script = lazy(lambda string: r'' + string, str) self.assertEqual( escapejs_filter(append_script('whitespace: \r\n\t\v\f\b')), '\\u003Cscript\\u003Ethis\\u003C/script\\u003E' diff --git a/tests/template_tests/filter_tests/test_filesizeformat.py b/tests/template_tests/filter_tests/test_filesizeformat.py index 9d143ca10015..2e425af8ac69 100644 --- a/tests/template_tests/filter_tests/test_filesizeformat.py +++ b/tests/template_tests/filter_tests/test_filesizeformat.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.template.defaultfilters import filesizeformat from django.test import SimpleTestCase from django.utils import translation diff --git a/tests/template_tests/filter_tests/test_floatformat.py b/tests/template_tests/filter_tests/test_floatformat.py index b1ffbe56c1b5..cfc3eaf73b24 100644 --- a/tests/template_tests/filter_tests/test_floatformat.py +++ b/tests/template_tests/filter_tests/test_floatformat.py @@ -1,11 +1,7 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from decimal import Decimal, localcontext from django.template.defaultfilters import floatformat from django.test import SimpleTestCase -from django.utils import six from django.utils.safestring import mark_safe from ..utils import setup @@ -33,6 +29,7 @@ def test_inputs(self): self.assertEqual(floatformat(0.07), '0.1') self.assertEqual(floatformat(0.007), '0.0') self.assertEqual(floatformat(0.0), '0') + self.assertEqual(floatformat(7.7, 0), '8') self.assertEqual(floatformat(7.7, 3), '7.700') self.assertEqual(floatformat(6.000000, 3), '6.000') self.assertEqual(floatformat(6.200000, 3), '6.200') @@ -67,16 +64,13 @@ def test_zero_values(self): def test_infinity(self): pos_inf = float(1e30000) - self.assertEqual(floatformat(pos_inf), six.text_type(pos_inf)) - neg_inf = float(-1e30000) - self.assertEqual(floatformat(neg_inf), six.text_type(neg_inf)) - - nan = pos_inf / pos_inf - self.assertEqual(floatformat(nan), six.text_type(nan)) + self.assertEqual(floatformat(pos_inf), 'inf') + self.assertEqual(floatformat(neg_inf), '-inf') + self.assertEqual(floatformat(pos_inf / pos_inf), 'nan') def test_float_dunder_method(self): - class FloatWrapper(object): + class FloatWrapper: def __init__(self, value): self.value = value diff --git a/tests/template_tests/filter_tests/test_force_escape.py b/tests/template_tests/filter_tests/test_force_escape.py index f163f2cd75c1..776dc8042d74 100644 --- a/tests/template_tests/filter_tests/test_force_escape.py +++ b/tests/template_tests/filter_tests/test_force_escape.py @@ -1,9 +1,5 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.template.defaultfilters import force_escape -from django.test import SimpleTestCase, ignore_warnings -from django.utils.deprecation import RemovedInDjango20Warning +from django.test import SimpleTestCase from django.utils.safestring import SafeData from ..utils import setup @@ -36,8 +32,7 @@ def test_force_escape04(self): self.assertEqual(output, "x&amp;y") # Because the result of force_escape is "safe", an additional - # escape filter has no effect (to be changed in Django 2.0). - @ignore_warnings(category=RemovedInDjango20Warning) + # escape filter has no effect. @setup({'force-escape05': '{% autoescape off %}{{ a|force_escape|escape }}{% endautoescape %}'}) def test_force_escape05(self): output = self.engine.render_to_string('force-escape05', {"a": "x&y"}) @@ -48,17 +43,15 @@ def test_force_escape06(self): output = self.engine.render_to_string('force-escape06', {"a": "x&y"}) self.assertEqual(output, "x&y") - @ignore_warnings(category=RemovedInDjango20Warning) @setup({'force-escape07': '{% autoescape off %}{{ a|escape|force_escape }}{% endautoescape %}'}) def test_force_escape07(self): output = self.engine.render_to_string('force-escape07', {"a": "x&y"}) - self.assertEqual(output, "x&y") + self.assertEqual(output, "x&amp;y") - @ignore_warnings(category=RemovedInDjango20Warning) @setup({'force-escape08': '{{ a|escape|force_escape }}'}) def test_force_escape08(self): output = self.engine.render_to_string('force-escape08', {"a": "x&y"}) - self.assertEqual(output, "x&y") + self.assertEqual(output, "x&amp;y") class FunctionTests(SimpleTestCase): diff --git a/tests/template_tests/filter_tests/test_iriencode.py b/tests/template_tests/filter_tests/test_iriencode.py index 837d6aa0f722..7d83b67f9df0 100644 --- a/tests/template_tests/filter_tests/test_iriencode.py +++ b/tests/template_tests/filter_tests/test_iriencode.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.template.defaultfilters import iriencode, urlencode from django.test import SimpleTestCase from django.utils.safestring import mark_safe diff --git a/tests/template_tests/filter_tests/test_join.py b/tests/template_tests/filter_tests/test_join.py index 2d2a9cf78eab..43ac0da7c2c6 100644 --- a/tests/template_tests/filter_tests/test_join.py +++ b/tests/template_tests/filter_tests/test_join.py @@ -65,3 +65,11 @@ def test_autoescape_off(self): join(['', '', ''], '
        ', autoescape=False), '<br><br>', ) + + def test_noniterable_arg(self): + obj = object() + self.assertEqual(join(obj, '
        '), obj) + + def test_noniterable_arg_autoescape_off(self): + obj = object() + self.assertEqual(join(obj, '
        ', autoescape=False), obj) diff --git a/tests/template_tests/filter_tests/test_json_script.py b/tests/template_tests/filter_tests/test_json_script.py new file mode 100644 index 000000000000..061fe32c12fc --- /dev/null +++ b/tests/template_tests/filter_tests/test_json_script.py @@ -0,0 +1,19 @@ +from django.test import SimpleTestCase + +from ..utils import setup + + +class JsonScriptTests(SimpleTestCase): + + @setup({'json-tag01': '{{ value|json_script:"test_id" }}'}) + def test_basic(self): + output = self.engine.render_to_string( + 'json-tag01', + {'value': {'a': 'testing\r\njson \'string" escaping'}} + ) + self.assertEqual( + output, + '' + ) diff --git a/tests/template_tests/filter_tests/test_last.py b/tests/template_tests/filter_tests/test_last.py index 29c558fa26c0..e03caa238216 100644 --- a/tests/template_tests/filter_tests/test_last.py +++ b/tests/template_tests/filter_tests/test_last.py @@ -15,3 +15,8 @@ def test_last01(self): def test_last02(self): output = self.engine.render_to_string('last02', {"a": ["x", "a&b"], "b": ["x", mark_safe("a&b")]}) self.assertEqual(output, "a&b a&b") + + @setup({'empty_list': '{% autoescape off %}{{ a|last }}{% endautoescape %}'}) + def test_empty_list(self): + output = self.engine.render_to_string('empty_list', {"a": []}) + self.assertEqual(output, '') diff --git a/tests/template_tests/filter_tests/test_linebreaks.py b/tests/template_tests/filter_tests/test_linebreaks.py index 50a66f3c47e2..8fdb91f37735 100644 --- a/tests/template_tests/filter_tests/test_linebreaks.py +++ b/tests/template_tests/filter_tests/test_linebreaks.py @@ -1,6 +1,5 @@ from django.template.defaultfilters import linebreaks_filter from django.test import SimpleTestCase -from django.utils import six from django.utils.functional import lazy from django.utils.safestring import mark_safe @@ -16,12 +15,12 @@ class LinebreaksTests(SimpleTestCase): @setup({'linebreaks01': '{{ a|linebreaks }} {{ b|linebreaks }}'}) def test_linebreaks01(self): output = self.engine.render_to_string('linebreaks01', {"a": "x&\ny", "b": mark_safe("x&\ny")}) - self.assertEqual(output, "

        x&
        y

        x&
        y

        ") + self.assertEqual(output, "

        x&
        y

        x&
        y

        ") @setup({'linebreaks02': '{% autoescape off %}{{ a|linebreaks }} {{ b|linebreaks }}{% endautoescape %}'}) def test_linebreaks02(self): output = self.engine.render_to_string('linebreaks02', {"a": "x&\ny", "b": mark_safe("x&\ny")}) - self.assertEqual(output, "

        x&
        y

        x&
        y

        ") + self.assertEqual(output, "

        x&
        y

        x&
        y

        ") class FunctionTests(SimpleTestCase): @@ -30,13 +29,13 @@ def test_line(self): self.assertEqual(linebreaks_filter('line 1'), '

        line 1

        ') def test_newline(self): - self.assertEqual(linebreaks_filter('line 1\nline 2'), '

        line 1
        line 2

        ') + self.assertEqual(linebreaks_filter('line 1\nline 2'), '

        line 1
        line 2

        ') def test_carriage(self): - self.assertEqual(linebreaks_filter('line 1\rline 2'), '

        line 1
        line 2

        ') + self.assertEqual(linebreaks_filter('line 1\rline 2'), '

        line 1
        line 2

        ') def test_carriage_newline(self): - self.assertEqual(linebreaks_filter('line 1\r\nline 2'), '

        line 1
        line 2

        ') + self.assertEqual(linebreaks_filter('line 1\r\nline 2'), '

        line 1
        line 2

        ') def test_non_string_input(self): self.assertEqual(linebreaks_filter(123), '

        123

        ') @@ -44,18 +43,18 @@ def test_non_string_input(self): def test_autoescape(self): self.assertEqual( linebreaks_filter('foo\nbar\nbuz'), - '

        foo
        <a>bar</a>
        buz

        ', + '

        foo
        <a>bar</a>
        buz

        ', ) def test_autoescape_off(self): self.assertEqual( linebreaks_filter('foo\nbar\nbuz', autoescape=False), - '

        foo
        bar
        buz

        ', + '

        foo
        bar
        buz

        ', ) def test_lazy_string_input(self): - add_header = lazy(lambda string: 'Header\n\n' + string, six.text_type) + add_header = lazy(lambda string: 'Header\n\n' + string, str) self.assertEqual( linebreaks_filter(add_header('line 1\r\nline2')), - '

        Header

        \n\n

        line 1
        line2

        ' + '

        Header

        \n\n

        line 1
        line2

        ' ) diff --git a/tests/template_tests/filter_tests/test_linebreaksbr.py b/tests/template_tests/filter_tests/test_linebreaksbr.py index 83a15f97db7b..f2583f0aafe6 100644 --- a/tests/template_tests/filter_tests/test_linebreaksbr.py +++ b/tests/template_tests/filter_tests/test_linebreaksbr.py @@ -14,24 +14,24 @@ class LinebreaksbrTests(SimpleTestCase): @setup({'linebreaksbr01': '{{ a|linebreaksbr }} {{ b|linebreaksbr }}'}) def test_linebreaksbr01(self): output = self.engine.render_to_string('linebreaksbr01', {"a": "x&\ny", "b": mark_safe("x&\ny")}) - self.assertEqual(output, "x&
        y x&
        y") + self.assertEqual(output, "x&
        y x&
        y") @setup({'linebreaksbr02': '{% autoescape off %}{{ a|linebreaksbr }} {{ b|linebreaksbr }}{% endautoescape %}'}) def test_linebreaksbr02(self): output = self.engine.render_to_string('linebreaksbr02', {"a": "x&\ny", "b": mark_safe("x&\ny")}) - self.assertEqual(output, "x&
        y x&
        y") + self.assertEqual(output, "x&
        y x&
        y") class FunctionTests(SimpleTestCase): def test_newline(self): - self.assertEqual(linebreaksbr('line 1\nline 2'), 'line 1
        line 2') + self.assertEqual(linebreaksbr('line 1\nline 2'), 'line 1
        line 2') def test_carriage(self): - self.assertEqual(linebreaksbr('line 1\rline 2'), 'line 1
        line 2') + self.assertEqual(linebreaksbr('line 1\rline 2'), 'line 1
        line 2') def test_carriage_newline(self): - self.assertEqual(linebreaksbr('line 1\r\nline 2'), 'line 1
        line 2') + self.assertEqual(linebreaksbr('line 1\r\nline 2'), 'line 1
        line 2') def test_non_string_input(self): self.assertEqual(linebreaksbr(123), '123') @@ -39,11 +39,11 @@ def test_non_string_input(self): def test_autoescape(self): self.assertEqual( linebreaksbr('foo\nbar\nbuz'), - 'foo
        <a>bar</a>
        buz', + 'foo
        <a>bar</a>
        buz', ) def test_autoescape_off(self): self.assertEqual( linebreaksbr('foo\nbar\nbuz', autoescape=False), - 'foo
        bar
        buz', + 'foo
        bar
        buz', ) diff --git a/tests/template_tests/filter_tests/test_lower.py b/tests/template_tests/filter_tests/test_lower.py index 974664387513..1db934c5fac0 100644 --- a/tests/template_tests/filter_tests/test_lower.py +++ b/tests/template_tests/filter_tests/test_lower.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.template.defaultfilters import lower from django.test import SimpleTestCase from django.utils.safestring import mark_safe diff --git a/tests/template_tests/filter_tests/test_make_list.py b/tests/template_tests/filter_tests/test_make_list.py index 454b394d3b42..17c4cac48036 100644 --- a/tests/template_tests/filter_tests/test_make_list.py +++ b/tests/template_tests/filter_tests/test_make_list.py @@ -1,6 +1,5 @@ from django.template.defaultfilters import make_list from django.test import SimpleTestCase -from django.test.utils import str_prefix from django.utils.safestring import mark_safe from ..utils import setup @@ -15,22 +14,22 @@ class MakeListTests(SimpleTestCase): @setup({'make_list01': '{% autoescape off %}{{ a|make_list }}{% endautoescape %}'}) def test_make_list01(self): output = self.engine.render_to_string('make_list01', {"a": mark_safe("&")}) - self.assertEqual(output, str_prefix("[%(_)s'&']")) + self.assertEqual(output, "['&']") @setup({'make_list02': '{{ a|make_list }}'}) def test_make_list02(self): output = self.engine.render_to_string('make_list02', {"a": mark_safe("&")}) - self.assertEqual(output, str_prefix("[%(_)s'&']")) + self.assertEqual(output, "['&']") @setup({'make_list03': '{% autoescape off %}{{ a|make_list|stringformat:"s"|safe }}{% endautoescape %}'}) def test_make_list03(self): output = self.engine.render_to_string('make_list03', {"a": mark_safe("&")}) - self.assertEqual(output, str_prefix("[%(_)s'&']")) + self.assertEqual(output, "['&']") @setup({'make_list04': '{{ a|make_list|stringformat:"s"|safe }}'}) def test_make_list04(self): output = self.engine.render_to_string('make_list04', {"a": mark_safe("&")}) - self.assertEqual(output, str_prefix("[%(_)s'&']")) + self.assertEqual(output, "['&']") class FunctionTests(SimpleTestCase): diff --git a/tests/template_tests/filter_tests/test_pluralize.py b/tests/template_tests/filter_tests/test_pluralize.py index 16371da8bece..96c580229633 100644 --- a/tests/template_tests/filter_tests/test_pluralize.py +++ b/tests/template_tests/filter_tests/test_pluralize.py @@ -3,6 +3,29 @@ from django.template.defaultfilters import pluralize from django.test import SimpleTestCase +from ..utils import setup + + +class PluralizeTests(SimpleTestCase): + + def check_values(self, *tests): + for value, expected in tests: + with self.subTest(value=value): + output = self.engine.render_to_string('t', {'value': value}) + self.assertEqual(output, expected) + + @setup({'t': 'vote{{ value|pluralize }}'}) + def test_no_arguments(self): + self.check_values(('0', 'votes'), ('1', 'vote'), ('2', 'votes')) + + @setup({'t': 'class{{ value|pluralize:"es" }}'}) + def test_suffix(self): + self.check_values(('0', 'classes'), ('1', 'class'), ('2', 'classes')) + + @setup({'t': 'cand{{ value|pluralize:"y,ies" }}'}) + def test_singular_and_plural_suffix(self): + self.check_values(('0', 'candies'), ('1', 'candy'), ('2', 'candies')) + class FunctionTests(SimpleTestCase): @@ -33,3 +56,10 @@ def test_suffixes(self): self.assertEqual(pluralize(0, 'y,ies'), 'ies') self.assertEqual(pluralize(2, 'y,ies'), 'ies') self.assertEqual(pluralize(0, 'y,ies,error'), '') + + def test_no_len_type(self): + self.assertEqual(pluralize(object(), 'y,es'), 'y') + self.assertEqual(pluralize(object(), 'es'), '') + + def test_value_error(self): + self.assertEqual(pluralize('', 'y,es'), 'y') diff --git a/tests/template_tests/filter_tests/test_slice.py b/tests/template_tests/filter_tests/test_slice.py index 6d67de86618d..1b92776707b7 100644 --- a/tests/template_tests/filter_tests/test_slice.py +++ b/tests/template_tests/filter_tests/test_slice.py @@ -26,6 +26,9 @@ def test_zero_length(self): def test_index(self): self.assertEqual(slice_filter('abcdefg', '1'), 'a') + def test_index_integer(self): + self.assertEqual(slice_filter('abcdefg', 1), 'a') + def test_negative_index(self): self.assertEqual(slice_filter('abcdefg', '-1'), 'abcdef') @@ -37,3 +40,7 @@ def test_range_multiple(self): def test_range_step(self): self.assertEqual(slice_filter('abcdefg', '0::2'), 'aceg') + + def test_fail_silently(self): + obj = object() + self.assertEqual(slice_filter(obj, '0::2'), obj) diff --git a/tests/template_tests/filter_tests/test_slugify.py b/tests/template_tests/filter_tests/test_slugify.py index 5852417b2ec4..b1d617f5bc63 100644 --- a/tests/template_tests/filter_tests/test_slugify.py +++ b/tests/template_tests/filter_tests/test_slugify.py @@ -1,10 +1,5 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.template.defaultfilters import slugify from django.test import SimpleTestCase -from django.utils import six -from django.utils.encoding import force_text from django.utils.functional import lazy from django.utils.safestring import mark_safe @@ -46,7 +41,7 @@ def test_non_string_input(self): self.assertEqual(slugify(123), '123') def test_slugify_lazy_string(self): - lazy_str = lazy(lambda string: force_text(string), six.text_type) + lazy_str = lazy(lambda string: string, str) self.assertEqual( slugify(lazy_str(' Jack & Jill like numbers 1,2,3 and 4 and silly characters ?%.$!/')), 'jack-jill-like-numbers-123-and-4-and-silly-characters', diff --git a/tests/template_tests/filter_tests/test_stringformat.py b/tests/template_tests/filter_tests/test_stringformat.py index 9501878ebd6e..62ee19aef8a3 100644 --- a/tests/template_tests/filter_tests/test_stringformat.py +++ b/tests/template_tests/filter_tests/test_stringformat.py @@ -29,6 +29,14 @@ class FunctionTests(SimpleTestCase): def test_format(self): self.assertEqual(stringformat(1, '03d'), '001') + self.assertEqual(stringformat([1, None], 's'), '[1, None]') + self.assertEqual(stringformat((1, 2, 3), 's'), '(1, 2, 3)') + self.assertEqual(stringformat((1,), 's'), '(1,)') + self.assertEqual(stringformat({1, 2}, 's'), '{1, 2}') + self.assertEqual(stringformat({1: 2, 2: 3}, 's'), '{1: 2, 2: 3}') def test_invalid(self): self.assertEqual(stringformat(1, 'z'), '') + self.assertEqual(stringformat(object(), 'd'), '') + self.assertEqual(stringformat(None, 'd'), '') + self.assertEqual(stringformat((1, 2, 3), 'd'), '') diff --git a/tests/template_tests/filter_tests/test_time.py b/tests/template_tests/filter_tests/test_time.py index a05624a0e1f9..919417dc126b 100644 --- a/tests/template_tests/filter_tests/test_time.py +++ b/tests/template_tests/filter_tests/test_time.py @@ -58,6 +58,10 @@ def test_time06(self): class FunctionTests(SimpleTestCase): + def test_no_args(self): + self.assertEqual(time_filter(''), '') + self.assertEqual(time_filter(None), '') + def test_inputs(self): self.assertEqual(time_filter(time(13), 'h'), '01') self.assertEqual(time_filter(time(0), 'h'), '12') diff --git a/tests/template_tests/filter_tests/test_timesince.py b/tests/template_tests/filter_tests/test_timesince.py index 0cb975a90ed0..7fe4831d6173 100644 --- a/tests/template_tests/filter_tests/test_timesince.py +++ b/tests/template_tests/filter_tests/test_timesince.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from datetime import datetime, timedelta from django.template.defaultfilters import timesince_filter @@ -133,5 +131,8 @@ class FunctionTests(SimpleTestCase): def test_since_now(self): self.assertEqual(timesince_filter(datetime.now() - timedelta(1)), '1\xa0day') + def test_no_args(self): + self.assertEqual(timesince_filter(None), '') + def test_explicit_date(self): self.assertEqual(timesince_filter(datetime(2005, 12, 29), datetime(2005, 12, 30)), '1\xa0day') diff --git a/tests/template_tests/filter_tests/test_timeuntil.py b/tests/template_tests/filter_tests/test_timeuntil.py index 1b06a21c937a..f3a211c6261b 100644 --- a/tests/template_tests/filter_tests/test_timeuntil.py +++ b/tests/template_tests/filter_tests/test_timeuntil.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from datetime import datetime, timedelta from django.template.defaultfilters import timeuntil_filter @@ -99,11 +97,24 @@ def test_timeuntil14(self): output = self.engine.render_to_string('timeuntil14', {'a': self.today, 'b': self.today - timedelta(hours=24)}) self.assertEqual(output, '1\xa0day') + @setup({'timeuntil15': '{{ a|timeuntil:b }}'}) + def test_naive_aware_type_error(self): + output = self.engine.render_to_string('timeuntil15', {'a': self.now, 'b': self.now_tz_i}) + self.assertEqual(output, '') + + @setup({'timeuntil16': '{{ a|timeuntil:b }}'}) + def test_aware_naive_type_error(self): + output = self.engine.render_to_string('timeuntil16', {'a': self.now_tz_i, 'b': self.now}) + self.assertEqual(output, '') + class FunctionTests(SimpleTestCase): def test_until_now(self): self.assertEqual(timeuntil_filter(datetime.now() + timedelta(1, 1)), '1\xa0day') + def test_no_args(self): + self.assertEqual(timeuntil_filter(None), '') + def test_explicit_date(self): self.assertEqual(timeuntil_filter(datetime(2005, 12, 30), datetime(2005, 12, 29)), '1\xa0day') diff --git a/tests/template_tests/filter_tests/test_title.py b/tests/template_tests/filter_tests/test_title.py index cf4d1033c636..08a5fb0de405 100644 --- a/tests/template_tests/filter_tests/test_title.py +++ b/tests/template_tests/filter_tests/test_title.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.template.defaultfilters import title from django.test import SimpleTestCase diff --git a/tests/template_tests/filter_tests/test_truncatechars.py b/tests/template_tests/filter_tests/test_truncatechars.py index eea6f64a6edc..89d48fd1cffc 100644 --- a/tests/template_tests/filter_tests/test_truncatechars.py +++ b/tests/template_tests/filter_tests/test_truncatechars.py @@ -5,12 +5,17 @@ class TruncatecharsTests(SimpleTestCase): - @setup({'truncatechars01': '{{ a|truncatechars:5 }}'}) + @setup({'truncatechars01': '{{ a|truncatechars:3 }}'}) def test_truncatechars01(self): output = self.engine.render_to_string('truncatechars01', {'a': 'Testing, testing'}) - self.assertEqual(output, 'Te...') + self.assertEqual(output, 'Te…') @setup({'truncatechars02': '{{ a|truncatechars:7 }}'}) def test_truncatechars02(self): output = self.engine.render_to_string('truncatechars02', {'a': 'Testing'}) self.assertEqual(output, 'Testing') + + @setup({'truncatechars03': "{{ a|truncatechars:'e' }}"}) + def test_fail_silently_incorrect_arg(self): + output = self.engine.render_to_string('truncatechars03', {'a': 'Testing, testing'}) + self.assertEqual(output, 'Testing, testing') diff --git a/tests/template_tests/filter_tests/test_truncatechars_html.py b/tests/template_tests/filter_tests/test_truncatechars_html.py index cf2623d74050..4948e6534e3f 100644 --- a/tests/template_tests/filter_tests/test_truncatechars_html.py +++ b/tests/template_tests/filter_tests/test_truncatechars_html.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.template.defaultfilters import truncatechars_html from django.test import SimpleTestCase @@ -8,18 +5,18 @@ class FunctionTests(SimpleTestCase): def test_truncate_zero(self): - self.assertEqual(truncatechars_html('

        one two - three
        four
        five

        ', 0), '...') + self.assertEqual(truncatechars_html('

        one two - three
        four
        five

        ', 0), '…') def test_truncate(self): self.assertEqual( - truncatechars_html('

        one two - three
        four
        five

        ', 6), - '

        one...

        ', + truncatechars_html('

        one two - three
        four
        five

        ', 4), + '

        one…

        ', ) def test_truncate2(self): self.assertEqual( - truncatechars_html('

        one two - three
        four
        five

        ', 11), - '

        one two ...

        ', + truncatechars_html('

        one two - three
        four
        five

        ', 9), + '

        one two …

        ', ) def test_truncate3(self): @@ -29,7 +26,11 @@ def test_truncate3(self): ) def test_truncate_unicode(self): - self.assertEqual(truncatechars_html('\xc5ngstr\xf6m was here', 5), '\xc5n...') + self.assertEqual(truncatechars_html('\xc5ngstr\xf6m was here', 3), '\xc5n…') def test_truncate_something(self): self.assertEqual(truncatechars_html('abc', 3), 'abc') + + def test_invalid_arg(self): + html = '

        one two - three
        four
        five

        ' + self.assertEqual(truncatechars_html(html, 'a'), html) diff --git a/tests/template_tests/filter_tests/test_truncatewords.py b/tests/template_tests/filter_tests/test_truncatewords.py index 4941e736fdc9..636cd55fd534 100644 --- a/tests/template_tests/filter_tests/test_truncatewords.py +++ b/tests/template_tests/filter_tests/test_truncatewords.py @@ -14,25 +14,25 @@ def test_truncatewords01(self): output = self.engine.render_to_string( 'truncatewords01', {'a': 'alpha & bravo', 'b': mark_safe('alpha & bravo')} ) - self.assertEqual(output, 'alpha & ... alpha & ...') + self.assertEqual(output, 'alpha & … alpha & …') @setup({'truncatewords02': '{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}'}) def test_truncatewords02(self): output = self.engine.render_to_string( 'truncatewords02', {'a': 'alpha & bravo', 'b': mark_safe('alpha & bravo')} ) - self.assertEqual(output, 'alpha & ... alpha & ...') + self.assertEqual(output, 'alpha & … alpha & …') class FunctionTests(SimpleTestCase): def test_truncate(self): - self.assertEqual(truncatewords('A sentence with a few words in it', 1), 'A ...') + self.assertEqual(truncatewords('A sentence with a few words in it', 1), 'A …') def test_truncate2(self): self.assertEqual( truncatewords('A sentence with a few words in it', 5), - 'A sentence with a few ...', + 'A sentence with a few …', ) def test_overtruncate(self): diff --git a/tests/template_tests/filter_tests/test_truncatewords_html.py b/tests/template_tests/filter_tests/test_truncatewords_html.py index aec2abf2d4d0..6177fc875daa 100644 --- a/tests/template_tests/filter_tests/test_truncatewords_html.py +++ b/tests/template_tests/filter_tests/test_truncatewords_html.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.template.defaultfilters import truncatewords_html from django.test import SimpleTestCase @@ -13,19 +10,19 @@ def test_truncate_zero(self): def test_truncate(self): self.assertEqual( truncatewords_html('

        one two - three
        four
        five

        ', 2), - '

        one two ...

        ', + '

        one two …

        ', ) def test_truncate2(self): self.assertEqual( truncatewords_html('

        one two - three
        four
        five

        ', 4), - '

        one two - three
        four ...

        ', + '

        one two - three …

        ', ) def test_truncate3(self): self.assertEqual( truncatewords_html('

        one two - three
        four
        five

        ', 5), - '

        one two - three
        four
        five

        ', + '

        one two - three
        four …

        ', ) def test_truncate4(self): @@ -35,10 +32,13 @@ def test_truncate4(self): ) def test_truncate_unicode(self): - self.assertEqual(truncatewords_html('\xc5ngstr\xf6m was here', 1), '\xc5ngstr\xf6m ...') + self.assertEqual(truncatewords_html('\xc5ngstr\xf6m was here', 1), '\xc5ngstr\xf6m …') def test_truncate_complex(self): self.assertEqual( truncatewords_html('Buenos días! ¿Cómo está?', 3), - 'Buenos días! ¿Cómo ...', + 'Buenos días! ¿Cómo …', ) + + def test_invalid_arg(self): + self.assertEqual(truncatewords_html('

        string

        ', 'a'), '

        string

        ') diff --git a/tests/template_tests/filter_tests/test_unordered_list.py b/tests/template_tests/filter_tests/test_unordered_list.py index eab94427805d..6a04664d992f 100644 --- a/tests/template_tests/filter_tests/test_unordered_list.py +++ b/tests/template_tests/filter_tests/test_unordered_list.py @@ -1,7 +1,7 @@ from django.template.defaultfilters import unordered_list from django.test import SimpleTestCase -from django.utils.encoding import python_2_unicode_compatible from django.utils.safestring import mark_safe +from django.utils.translation import gettext_lazy from ..utils import setup @@ -39,6 +39,12 @@ class FunctionTests(SimpleTestCase): def test_list(self): self.assertEqual(unordered_list(['item 1', 'item 2']), '\t
      • item 1
      • \n\t
      • item 2
      • ') + def test_list_gettext(self): + self.assertEqual( + unordered_list(['item 1', gettext_lazy('item 2')]), + '\t
      • item 1
      • \n\t
      • item 2
      • ' + ) + def test_nested(self): self.assertEqual( unordered_list(['item 1', ['item 1.1']]), @@ -88,8 +94,7 @@ def test_autoescape_off(self): ) def test_ulitem(self): - @python_2_unicode_compatible - class ULItem(object): + class ULItem: def __init__(self, title): self.title = title @@ -105,18 +110,29 @@ def __str__(self): ) def item_generator(): - yield a - yield b - yield c + yield from (a, b, c) self.assertEqual( unordered_list(item_generator()), '\t
      • ulitem-a
      • \n\t
      • ulitem-b
      • \n\t
      • ulitem-<a>c</a>
      • ', ) + def test_nested_generators(self): + def inner_generator(): + yield from ('B', 'C') + + def item_generator(): + yield 'A' + yield inner_generator() + yield 'D' + + self.assertEqual( + unordered_list(item_generator()), + '\t
      • A\n\t
          \n\t\t
        • B
        • \n\t\t
        • C
        • \n\t
        \n\t
      • \n\t
      • D
      • ', + ) + def test_ulitem_autoescape_off(self): - @python_2_unicode_compatible - class ULItem(object): + class ULItem: def __init__(self, title): self.title = title @@ -132,9 +148,7 @@ def __str__(self): ) def item_generator(): - yield a - yield b - yield c + yield from (a, b, c) self.assertEqual( unordered_list(item_generator(), autoescape=False), diff --git a/tests/template_tests/filter_tests/test_upper.py b/tests/template_tests/filter_tests/test_upper.py index f9c52bc0f8d1..a9efaf5322e1 100644 --- a/tests/template_tests/filter_tests/test_upper.py +++ b/tests/template_tests/filter_tests/test_upper.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.template.defaultfilters import upper from django.test import SimpleTestCase from django.utils.safestring import mark_safe diff --git a/tests/template_tests/filter_tests/test_urlencode.py b/tests/template_tests/filter_tests/test_urlencode.py index 82199f8f1cdf..d095bb8fae67 100644 --- a/tests/template_tests/filter_tests/test_urlencode.py +++ b/tests/template_tests/filter_tests/test_urlencode.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.template.defaultfilters import urlencode from django.test import SimpleTestCase diff --git a/tests/template_tests/filter_tests/test_urlize.py b/tests/template_tests/filter_tests/test_urlize.py index 6822092943c7..649a9652033c 100644 --- a/tests/template_tests/filter_tests/test_urlize.py +++ b/tests/template_tests/filter_tests/test_urlize.py @@ -1,9 +1,5 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.template.defaultfilters import urlize from django.test import SimpleTestCase -from django.utils import six from django.utils.functional import lazy from django.utils.safestring import mark_safe @@ -282,6 +278,24 @@ def test_brackets(self): 'http://168.192.0.1](http://168.192.0.1)', ) + def test_wrapping_characters(self): + wrapping_chars = ( + ('()', ('(', ')')), + ('<>', ('<', '>')), + ('[]', ('[', ']')), + ('""', ('"', '"')), + ("''", (''', ''')), + ) + for wrapping_in, (start_out, end_out) in wrapping_chars: + with self.subTest(wrapping_in=wrapping_in): + start_in, end_in = wrapping_in + self.assertEqual( + urlize(start_in + 'https://www.example.org/' + end_in), + start_out + + 'https://www.example.org/' + + end_out, + ) + def test_ipv4(self): self.assertEqual( urlize('http://192.168.0.15/api/9'), @@ -370,7 +384,7 @@ def test_autoescape_off(self): ) def test_lazystring(self): - prepend_www = lazy(lambda url: 'www.' + url, six.text_type) + prepend_www = lazy(lambda url: 'www.' + url, str) self.assertEqual( urlize(prepend_www('google.com')), 'www.google.com', diff --git a/tests/template_tests/filter_tests/test_urlizetrunc.py b/tests/template_tests/filter_tests/test_urlizetrunc.py index 18a5336c86c8..e37e27721242 100644 --- a/tests/template_tests/filter_tests/test_urlizetrunc.py +++ b/tests/template_tests/filter_tests/test_urlizetrunc.py @@ -20,8 +20,8 @@ def test_urlizetrunc01(self): ) self.assertEqual( output, - '"Unsafe" http:... ' - '"Safe" http:...' + '"Unsafe" http://… ' + '"Safe" http://…' ) @setup({'urlizetrunc02': '{{ a|urlizetrunc:"8" }} {{ b|urlizetrunc:"8" }}'}) @@ -35,8 +35,8 @@ def test_urlizetrunc02(self): ) self.assertEqual( output, - '"Unsafe" http:... ' - '"Safe" http:...' + '"Unsafe" http://… ' + '"Safe" http://…' ) @@ -55,13 +55,13 @@ def test_truncate(self): self.assertEqual( urlizetrunc(uri, 30), '' - 'http://31characteruri.com/t...', + 'http://31characteruri.com/tes…', ) self.assertEqual( - urlizetrunc(uri, 2), + urlizetrunc(uri, 1), '...', + ' rel="nofollow">…', ) def test_overtruncate(self): @@ -74,7 +74,7 @@ def test_query_string(self): self.assertEqual( urlizetrunc('http://www.google.co.uk/search?hl=en&q=some+long+url&btnG=Search&meta=', 20), 'http://www.google...', + 'meta=" rel="nofollow">http://www.google.c…', ) def test_non_string_input(self): @@ -89,5 +89,5 @@ def test_autoescape(self): def test_autoescape_off(self): self.assertEqual( urlizetrunc('foobarbuz', 9, autoescape=False), - 'foogoogle... ">barbuz', + 'foogoogle.c… ">barbuz', ) diff --git a/tests/template_tests/filter_tests/test_yesno.py b/tests/template_tests/filter_tests/test_yesno.py index 43ea447caa42..70b383a2b615 100644 --- a/tests/template_tests/filter_tests/test_yesno.py +++ b/tests/template_tests/filter_tests/test_yesno.py @@ -1,6 +1,15 @@ from django.template.defaultfilters import yesno from django.test import SimpleTestCase +from ..utils import setup + + +class YesNoTests(SimpleTestCase): + @setup({'t': '{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}'}) + def test_true(self): + output = self.engine.render_to_string('t', {'var': True}) + self.assertEqual(output, 'yup yes') + class FunctionTests(SimpleTestCase): @@ -24,3 +33,8 @@ def test_none_two_arguments(self): def test_none_three_arguments(self): self.assertEqual(yesno(None, 'certainly,get out of town,perhaps'), 'perhaps') + + def test_invalid_value(self): + self.assertIs(yesno(True, 'yes'), True) + self.assertIs(yesno(False, 'yes'), False) + self.assertIsNone(yesno(None, 'yes')) diff --git a/tests/template_tests/syntax_tests/i18n/base.py b/tests/template_tests/syntax_tests/i18n/base.py index b8c083d53684..61d2bbdb1c97 100644 --- a/tests/template_tests/syntax_tests/i18n/base.py +++ b/tests/template_tests/syntax_tests/i18n/base.py @@ -1,13 +1,10 @@ -from __future__ import unicode_literals - import os from django.conf import settings from django.test import SimpleTestCase -from django.utils._os import upath from django.utils.translation import activate, get_language -here = os.path.dirname(os.path.dirname(os.path.abspath(upath(__file__)))) +here = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) pdir = os.path.split(os.path.split(os.path.abspath(here))[0])[0] extended_locale_paths = settings.LOCALE_PATHS + [ os.path.join(pdir, 'i18n', 'other', 'locale'), diff --git a/tests/template_tests/syntax_tests/i18n/test_blocktrans.py b/tests/template_tests/syntax_tests/i18n/test_blocktrans.py index 552c08b0381c..ac8fc16da8e4 100644 --- a/tests/template_tests/syntax_tests/i18n/test_blocktrans.py +++ b/tests/template_tests/syntax_tests/i18n/test_blocktrans.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import os from threading import local @@ -20,20 +17,20 @@ class I18nBlockTransTagTests(SimpleTestCase): @setup({'i18n03': '{% load i18n %}{% blocktrans %}{{ anton }}{% endblocktrans %}'}) def test_i18n03(self): """simple translation of a variable""" - output = self.engine.render_to_string('i18n03', {'anton': b'\xc3\x85'}) + output = self.engine.render_to_string('i18n03', {'anton': 'Å'}) self.assertEqual(output, 'Å') @setup({'i18n04': '{% load i18n %}{% blocktrans with berta=anton|lower %}{{ berta }}{% endblocktrans %}'}) def test_i18n04(self): """simple translation of a variable and filter""" - output = self.engine.render_to_string('i18n04', {'anton': b'\xc3\x85'}) + output = self.engine.render_to_string('i18n04', {'anton': 'Å'}) self.assertEqual(output, 'å') @setup({'legacyi18n04': '{% load i18n %}' '{% blocktrans with anton|lower as berta %}{{ berta }}{% endblocktrans %}'}) def test_legacyi18n04(self): """simple translation of a variable and filter""" - output = self.engine.render_to_string('legacyi18n04', {'anton': b'\xc3\x85'}) + output = self.engine.render_to_string('legacyi18n04', {'anton': 'Å'}) self.assertEqual(output, 'å') @setup({'i18n05': '{% load i18n %}{% blocktrans %}xxx{{ anton }}xxx{% endblocktrans %}'}) @@ -236,6 +233,45 @@ def test_blocktrans_tag_using_a_string_that_looks_like_str_fmt(self): output = self.engine.render_to_string('template') self.assertEqual(output, '%s') + @setup({'template': '{% load i18n %}{% blocktrans %}{% block b %} {% endblock %}{% endblocktrans %}'}) + def test_with_block(self): + msg = "'blocktrans' doesn't allow other block tags (seen 'block b') inside it" + with self.assertRaisesMessage(TemplateSyntaxError, msg): + self.engine.render_to_string('template') + + @setup({'template': '{% load i18n %}{% blocktrans %}{% for b in [1, 2, 3] %} {% endfor %}{% endblocktrans %}'}) + def test_with_for(self): + msg = "'blocktrans' doesn't allow other block tags (seen 'for b in [1, 2, 3]') inside it" + with self.assertRaisesMessage(TemplateSyntaxError, msg): + self.engine.render_to_string('template') + + @setup({'template': '{% load i18n %}{% blocktrans with foo=bar with %}{{ foo }}{% endblocktrans %}'}) + def test_variable_twice(self): + with self.assertRaisesMessage(TemplateSyntaxError, "The 'with' option was specified more than once"): + self.engine.render_to_string('template', {'foo': 'bar'}) + + @setup({'template': '{% load i18n %}{% blocktrans with %}{% endblocktrans %}'}) + def test_no_args_with(self): + msg = '"with" in \'blocktrans\' tag needs at least one keyword argument.' + with self.assertRaisesMessage(TemplateSyntaxError, msg): + self.engine.render_to_string('template') + + @setup({'template': '{% load i18n %}{% blocktrans count a %}{% endblocktrans %}'}) + def test_count(self): + msg = '"count" in \'blocktrans\' tag expected exactly one keyword argument.' + with self.assertRaisesMessage(TemplateSyntaxError, msg): + self.engine.render_to_string('template', {'a': [1, 2, 3]}) + + @setup({'template': ( + '{% load i18n %}{% blocktrans count count=var|length %}' + 'There is {{ count }} object. {% block a %} {% endblock %}' + '{% endblocktrans %}' + )}) + def test_plural_bad_syntax(self): + msg = "'blocktrans' doesn't allow other block tags inside it" + with self.assertRaisesMessage(TemplateSyntaxError, msg): + self.engine.render_to_string('template', {'var': [1, 2, 3]}) + class TranslationBlockTransTagTests(SimpleTestCase): @@ -335,11 +371,13 @@ def test_template_tags_pgettext(self): self.assertEqual(rendered, '2 andere Super-Ergebnisse') # Misuses - with self.assertRaises(TemplateSyntaxError): + msg = "Unknown argument for 'blocktrans' tag: %r." + with self.assertRaisesMessage(TemplateSyntaxError, msg % 'month="May"'): Template('{% load i18n %}{% blocktrans context with month="May" %}{{ month }}{% endblocktrans %}') - with self.assertRaises(TemplateSyntaxError): + msg = '"context" in %r tag expected exactly one argument.' % 'blocktrans' + with self.assertRaisesMessage(TemplateSyntaxError, msg): Template('{% load i18n %}{% blocktrans context %}{% endblocktrans %}') - with self.assertRaises(TemplateSyntaxError): + with self.assertRaisesMessage(TemplateSyntaxError, msg): Template( '{% load i18n %}{% blocktrans count number=2 context %}' '{{ number }} super result{% plural %}{{ number }}' diff --git a/tests/template_tests/syntax_tests/i18n/test_filters.py b/tests/template_tests/syntax_tests/i18n/test_filters.py index 6a5a3fd1d529..cf8e4573c302 100644 --- a/tests/template_tests/syntax_tests/i18n/test_filters.py +++ b/tests/template_tests/syntax_tests/i18n/test_filters.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.test import SimpleTestCase from django.utils import translation diff --git a/tests/template_tests/syntax_tests/i18n/test_get_available_languages.py b/tests/template_tests/syntax_tests/i18n/test_get_available_languages.py index 187aa4e7abaa..5ac97a4f7952 100644 --- a/tests/template_tests/syntax_tests/i18n/test_get_available_languages.py +++ b/tests/template_tests/syntax_tests/i18n/test_get_available_languages.py @@ -1,5 +1,4 @@ -from __future__ import unicode_literals - +from django.template import TemplateSyntaxError from django.test import SimpleTestCase from ...utils import setup @@ -14,3 +13,9 @@ class GetAvailableLanguagesTagTests(SimpleTestCase): def test_i18n12(self): output = self.engine.render_to_string('i18n12') self.assertEqual(output, 'de') + + @setup({'syntax_i18n': '{% load i18n %}{% get_available_languages a langs %}'}) + def test_no_as_var(self): + msg = "'get_available_languages' requires 'as variable' (got ['get_available_languages', 'a', 'langs'])" + with self.assertRaisesMessage(TemplateSyntaxError, msg): + self.engine.render_to_string('syntax_i18n') diff --git a/tests/template_tests/syntax_tests/i18n/test_get_current_language.py b/tests/template_tests/syntax_tests/i18n/test_get_current_language.py new file mode 100644 index 000000000000..f168b74f8ed6 --- /dev/null +++ b/tests/template_tests/syntax_tests/i18n/test_get_current_language.py @@ -0,0 +1,14 @@ +from template_tests.utils import setup + +from django.template import TemplateSyntaxError +from django.test import SimpleTestCase + + +class I18nGetCurrentLanguageTagTests(SimpleTestCase): + libraries = {'i18n': 'django.templatetags.i18n'} + + @setup({'template': '{% load i18n %} {% get_current_language %}'}) + def test_no_as_var(self): + msg = "'get_current_language' requires 'as variable' (got ['get_current_language'])" + with self.assertRaisesMessage(TemplateSyntaxError, msg): + self.engine.render_to_string('template') diff --git a/tests/template_tests/syntax_tests/i18n/test_get_current_language_bidi.py b/tests/template_tests/syntax_tests/i18n/test_get_current_language_bidi.py new file mode 100644 index 000000000000..0ac408f78f2e --- /dev/null +++ b/tests/template_tests/syntax_tests/i18n/test_get_current_language_bidi.py @@ -0,0 +1,14 @@ +from template_tests.utils import setup + +from django.template import TemplateSyntaxError +from django.test import SimpleTestCase + + +class I18nGetCurrentLanguageBidiTagTests(SimpleTestCase): + libraries = {'i18n': 'django.templatetags.i18n'} + + @setup({'template': '{% load i18n %} {% get_current_language_bidi %}'}) + def test_no_as_var(self): + msg = "'get_current_language_bidi' requires 'as variable' (got ['get_current_language_bidi'])" + with self.assertRaisesMessage(TemplateSyntaxError, msg): + self.engine.render_to_string('template') diff --git a/tests/template_tests/syntax_tests/i18n/test_get_language_info.py b/tests/template_tests/syntax_tests/i18n/test_get_language_info.py index f555f67b7a86..51e8d2bc796c 100644 --- a/tests/template_tests/syntax_tests/i18n/test_get_language_info.py +++ b/tests/template_tests/syntax_tests/i18n/test_get_language_info.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - +from django.template import TemplateSyntaxError from django.test import SimpleTestCase from django.utils import translation @@ -37,3 +35,9 @@ def test_i18n38(self): with translation.override('cs'): output = self.engine.render_to_string('i18n38') self.assertEqual(output, 'de: German/Deutsch/německy bidi=False') + + @setup({'template': '{% load i18n %}''{% get_language_info %}'}) + def test_no_for_as(self): + msg = "'get_language_info' requires 'for string as variable' (got [])" + with self.assertRaisesMessage(TemplateSyntaxError, msg): + self.engine.render_to_string('template') diff --git a/tests/template_tests/syntax_tests/i18n/test_get_language_info_list.py b/tests/template_tests/syntax_tests/i18n/test_get_language_info_list.py index 4b782c94d691..c2c7f8b9cbd5 100644 --- a/tests/template_tests/syntax_tests/i18n/test_get_language_info_list.py +++ b/tests/template_tests/syntax_tests/i18n/test_get_language_info_list.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - +from django.template import TemplateSyntaxError from django.test import SimpleTestCase from django.utils import translation @@ -46,3 +44,9 @@ def test_i18n38_2(self): 'it: Italian/italiano/italsky bidi=False; ' 'fr: French/français/francouzsky bidi=False; ' ) + + @setup({'i18n_syntax': '{% load i18n %} {% get_language_info_list error %}'}) + def test_no_for_as(self): + msg = "'get_language_info_list' requires 'for sequence as variable' (got ['error'])" + with self.assertRaisesMessage(TemplateSyntaxError, msg): + self.engine.render_to_string('i18n_syntax') diff --git a/tests/template_tests/syntax_tests/i18n/test_language.py b/tests/template_tests/syntax_tests/i18n/test_language.py new file mode 100644 index 000000000000..47797befae8e --- /dev/null +++ b/tests/template_tests/syntax_tests/i18n/test_language.py @@ -0,0 +1,13 @@ +from template_tests.utils import setup + +from django.template import TemplateSyntaxError +from django.test import SimpleTestCase + + +class I18nLanguageTagTests(SimpleTestCase): + libraries = {'i18n': 'django.templatetags.i18n'} + + @setup({'i18n_language': '{% load i18n %} {% language %} {% endlanguage %}'}) + def test_no_arg(self): + with self.assertRaisesMessage(TemplateSyntaxError, "'language' takes one argument (language)"): + self.engine.render_to_string('i18n_language') diff --git a/tests/template_tests/syntax_tests/i18n/test_trans.py b/tests/template_tests/syntax_tests/i18n/test_trans.py index dd3860817556..ba5021a5d5be 100644 --- a/tests/template_tests/syntax_tests/i18n/test_trans.py +++ b/tests/template_tests/syntax_tests/i18n/test_trans.py @@ -1,8 +1,7 @@ -from __future__ import unicode_literals - from threading import local from django.template import Context, Template, TemplateSyntaxError +from django.templatetags.l10n import LocalizeNode from django.test import SimpleTestCase, override_settings from django.utils import translation from django.utils.safestring import mark_safe @@ -205,3 +204,9 @@ def test_multiple_locale_direct_switch_trans(self): t = Template("{% load i18n %}{% trans 'No' %}") with translation.override('nl'): self.assertEqual(t.render(Context({})), 'Nee') + + +class LocalizeNodeTests(SimpleTestCase): + def test_repr(self): + node = LocalizeNode(nodelist=[], use_l10n=True) + self.assertEqual(repr(node), '') diff --git a/tests/template_tests/syntax_tests/i18n/test_underscore_syntax.py b/tests/template_tests/syntax_tests/i18n/test_underscore_syntax.py index aed204d63b17..38d929e21421 100644 --- a/tests/template_tests/syntax_tests/i18n/test_underscore_syntax.py +++ b/tests/template_tests/syntax_tests/i18n/test_underscore_syntax.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.template import Context, Template from django.test import SimpleTestCase from django.utils import translation diff --git a/tests/template_tests/syntax_tests/test_autoescape.py b/tests/template_tests/syntax_tests/test_autoescape.py index 35debd526937..810731978b07 100644 --- a/tests/template_tests/syntax_tests/test_autoescape.py +++ b/tests/template_tests/syntax_tests/test_autoescape.py @@ -123,3 +123,15 @@ def test_autoescape_lookup01(self): """ output = self.engine.render_to_string('autoescape-lookup01', {'var': {'key': 'this & that'}}) self.assertEqual(output, 'this & that') + + @setup({'autoescape-incorrect-arg': '{% autoescape true %}{{ var.key }}{% endautoescape %}'}) + def test_invalid_arg(self): + msg = "'autoescape' argument should be 'on' or 'off'" + with self.assertRaisesMessage(TemplateSyntaxError, msg): + self.engine.render_to_string('autoescape-incorrect-arg', {'var': {'key': 'this & that'}}) + + @setup({'autoescape-incorrect-arg': '{% autoescape %}{{ var.key }}{% endautoescape %}'}) + def test_no_arg(self): + msg = "'autoescape' tag requires exactly one argument." + with self.assertRaisesMessage(TemplateSyntaxError, msg): + self.engine.render_to_string('autoescape-incorrect-arg', {'var': {'key': 'this & that'}}) diff --git a/tests/template_tests/syntax_tests/test_cache.py b/tests/template_tests/syntax_tests/test_cache.py index 7f7a6cd2cbe2..80af1c2bd6f5 100644 --- a/tests/template_tests/syntax_tests/test_cache.py +++ b/tests/template_tests/syntax_tests/test_cache.py @@ -108,7 +108,7 @@ def test_cache17(self): 'As plurdled gabbleblotchits/On a lurgid bee/' 'That mordiously hath bitled out/Its earted jurtles/' 'Into a rancid festering/Or else I shall rend thee in the gobberwarts' - 'with my blurglecruncheon/See if I dont.' + 'with my blurglecruncheon/See if I don\'t.' ), } ) @@ -122,13 +122,24 @@ def test_cache18(self): output = self.engine.render_to_string('cache18') self.assertEqual(output, 'cache18') + @setup({ + 'first': '{% load cache %}{% cache None fragment19 %}content{% endcache %}', + 'second': '{% load cache %}{% cache None fragment19 %}not rendered{% endcache %}' + }) + def test_none_timeout(self): + """A timeout of None means "cache forever".""" + output = self.engine.render_to_string('first') + self.assertEqual(output, 'content') + output = self.engine.render_to_string('second') + self.assertEqual(output, 'content') + class CacheTests(SimpleTestCase): @classmethod def setUpClass(cls): cls.engine = Engine(libraries={'cache': 'django.templatetags.cache'}) - super(CacheTests, cls).setUpClass() + super().setUpClass() def test_cache_regression_20130(self): t = self.engine.from_string('{% load cache %}{% cache 1 regression_20130 %}foo{% endcache %}') diff --git a/tests/template_tests/syntax_tests/test_cycle.py b/tests/template_tests/syntax_tests/test_cycle.py index b5712b54bb09..12bf66e11728 100644 --- a/tests/template_tests/syntax_tests/test_cycle.py +++ b/tests/template_tests/syntax_tests/test_cycle.py @@ -8,22 +8,20 @@ class CycleTagTests(SimpleTestCase): @setup({'cycle01': '{% cycle a %}'}) def test_cycle01(self): - with self.assertRaises(TemplateSyntaxError): + msg = "No named cycles in template. 'a' is not defined" + with self.assertRaisesMessage(TemplateSyntaxError, msg): self.engine.get_template('cycle01') @setup({'cycle05': '{% cycle %}'}) def test_cycle05(self): - with self.assertRaises(TemplateSyntaxError): + msg = "'cycle' tag requires at least two arguments" + with self.assertRaisesMessage(TemplateSyntaxError, msg): self.engine.get_template('cycle05') - @setup({'cycle06': '{% cycle a %}'}) - def test_cycle06(self): - with self.assertRaises(TemplateSyntaxError): - self.engine.get_template('cycle06') - @setup({'cycle07': '{% cycle a,b,c as foo %}{% cycle bar %}'}) def test_cycle07(self): - with self.assertRaises(TemplateSyntaxError): + msg = "Could not parse the remainder: ',b,c' from 'a,b,c'" + with self.assertRaisesMessage(TemplateSyntaxError, msg): self.engine.get_template('cycle07') @setup({'cycle10': "{% cycle 'a' 'b' 'c' as abc %}{% cycle abc %}"}) @@ -69,7 +67,8 @@ def test_cycle17(self): @setup({'cycle18': "{% cycle 'a' 'b' 'c' as foo invalid_flag %}"}) def test_cycle18(self): - with self.assertRaises(TemplateSyntaxError): + msg = "Only 'silent' flag is allowed after cycle's name, not 'invalid_flag'." + with self.assertRaisesMessage(TemplateSyntaxError, msg): self.engine.get_template('cycle18') @setup({'cycle19': "{% cycle 'a' 'b' as silent %}{% cycle silent %}"}) @@ -167,3 +166,14 @@ def test_cycle30(self): 'values': [1, 2, 3, 4, 5, 6, 7, 8, 8, 8, 9, 9] }) self.assertEqual(output, 'bcabcabcccaa') + + @setup({ + 'undefined_cycle': + "{% cycle 'a' 'b' 'c' as cycler silent %}" + "{% for x in values %}" + "{% cycle undefined %}{{ cycler }}" + "{% endfor %}" + }) + def test_cycle_undefined(self): + with self.assertRaisesMessage(TemplateSyntaxError, "Named cycle 'undefined' does not exist"): + self.engine.render_to_string('undefined_cycle') diff --git a/tests/template_tests/syntax_tests/test_extends.py b/tests/template_tests/syntax_tests/test_extends.py index bc320a2fc2e4..0689838ae8c4 100644 --- a/tests/template_tests/syntax_tests/test_extends.py +++ b/tests/template_tests/syntax_tests/test_extends.py @@ -1,3 +1,6 @@ +from django.template import NodeList, TemplateSyntaxError +from django.template.base import Node +from django.template.loader_tags import ExtendsNode from django.test import SimpleTestCase from ..utils import setup @@ -52,6 +55,9 @@ 'inheritance40': "{% extends 'inheritance33' %}{% block opt %}new{{ block.super }}{% endblock %}", 'inheritance41': "{% extends 'inheritance36' %}{% block opt %}new{{ block.super }}{% endblock %}", 'inheritance42': "{% extends 'inheritance02'|cut:' ' %}", + 'inheritance_empty': "{% extends %}", + 'extends_duplicate': "{% extends 'base.html' %}{% extends 'base.html' %}", + 'duplicate_block': "{% extends 'base.html' %}{% block content %}2{% endblock %}{% block content %}4{% endblock %}", } @@ -396,3 +402,30 @@ def test_inheritance42(self): """ output = self.engine.render_to_string('inheritance42') self.assertEqual(output, '1234') + + @setup(inheritance_templates) + def test_inheritance_empty(self): + with self.assertRaisesMessage(TemplateSyntaxError, "'extends' takes one argument"): + self.engine.render_to_string('inheritance_empty') + + @setup(inheritance_templates) + def test_extends_duplicate(self): + msg = "'extends' cannot appear more than once in the same template" + with self.assertRaisesMessage(TemplateSyntaxError, msg): + self.engine.render_to_string('extends_duplicate') + + @setup(inheritance_templates) + def test_duplicate_block(self): + msg = "'block' tag with name 'content' appears more than once" + with self.assertRaisesMessage(TemplateSyntaxError, msg): + self.engine.render_to_string('duplicate_block') + + +class ExtendsNodeTests(SimpleTestCase): + def test_extends_node_repr(self): + extends_node = ExtendsNode( + nodelist=NodeList([]), + parent_name=Node(), + template_dirs=[], + ) + self.assertEqual(repr(extends_node), '') diff --git a/tests/template_tests/syntax_tests/test_filter_syntax.py b/tests/template_tests/syntax_tests/test_filter_syntax.py index 3e524a61eb9b..1d37163d606c 100644 --- a/tests/template_tests/syntax_tests/test_filter_syntax.py +++ b/tests/template_tests/syntax_tests/test_filter_syntax.py @@ -1,6 +1,3 @@ -# coding: utf-8 -from __future__ import unicode_literals - from django.template import TemplateSyntaxError from django.test import SimpleTestCase @@ -46,7 +43,8 @@ def test_filter_syntax05(self): """ Raise TemplateSyntaxError for a nonexistent filter """ - with self.assertRaises(TemplateSyntaxError): + msg = "Invalid filter: 'does_not_exist'" + with self.assertRaisesMessage(TemplateSyntaxError, msg): self.engine.get_template('filter-syntax05') @setup({'filter-syntax06': '{{ var|fil(ter) }}'}) @@ -55,7 +53,7 @@ def test_filter_syntax06(self): Raise TemplateSyntaxError when trying to access a filter containing an illegal character """ - with self.assertRaises(TemplateSyntaxError): + with self.assertRaisesMessage(TemplateSyntaxError, "Invalid filter: 'fil'"): self.engine.get_template('filter-syntax06') @setup({'filter-syntax07': "{% nothing_to_see_here %}"}) @@ -63,7 +61,11 @@ def test_filter_syntax07(self): """ Raise TemplateSyntaxError for invalid block tags """ - with self.assertRaises(TemplateSyntaxError): + msg = ( + "Invalid block tag on line 1: 'nothing_to_see_here'. Did you " + "forget to register or load this tag?" + ) + with self.assertRaisesMessage(TemplateSyntaxError, msg): self.engine.get_template('filter-syntax07') @setup({'filter-syntax08': "{% %}"}) @@ -107,14 +109,6 @@ def test_filter_syntax11(self): output = self.engine.render_to_string('filter-syntax11', {"var": None, "var2": "happy"}) self.assertEqual(output, 'happy') - @setup({'filter-syntax12': r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}'}) - def test_filter_syntax12(self): - """ - Default argument testing - """ - output = self.engine.render_to_string('filter-syntax12', {"var": True}) - self.assertEqual(output, 'yup yes') - @setup({'filter-syntax13': r'1{{ var.method3 }}2'}) def test_filter_syntax13(self): """ @@ -163,8 +157,7 @@ def test_filter_syntax17(self): @setup({'filter-syntax18': r'{{ var }}'}) def test_filter_syntax18(self): """ - Make sure that any unicode strings are converted to bytestrings - in the final output. + Strings are converted to bytestrings in the final output. """ output = self.engine.render_to_string('filter-syntax18', {'var': UTF8Class()}) self.assertEqual(output, '\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111') @@ -175,7 +168,7 @@ def test_filter_syntax19(self): Numbers as filter arguments should work """ output = self.engine.render_to_string('filter-syntax19', {"var": "hello world"}) - self.assertEqual(output, "hello ...") + self.assertEqual(output, "hello …") @setup({'filter-syntax20': '{{ ""|default_if_none:"was none" }}'}) def test_filter_syntax20(self): diff --git a/tests/template_tests/syntax_tests/test_firstof.py b/tests/template_tests/syntax_tests/test_firstof.py index 1f382a6db31b..cc8df211eba4 100644 --- a/tests/template_tests/syntax_tests/test_firstof.py +++ b/tests/template_tests/syntax_tests/test_firstof.py @@ -82,3 +82,10 @@ def test_firstof15(self): output = self.engine.render_to_string('firstof15', ctx) self.assertEqual(ctx['myvar'], '2') self.assertEqual(output, '') + + @setup({'firstof16': '{% firstof a b c as myvar %}'}) + def test_all_false_arguments_asvar(self): + ctx = {'a': 0, 'b': 0, 'c': 0} + output = self.engine.render_to_string('firstof16', ctx) + self.assertEqual(ctx['myvar'], '') + self.assertEqual(output, '') diff --git a/tests/template_tests/syntax_tests/test_for.py b/tests/template_tests/syntax_tests/test_for.py index cf556f1b71d2..d7db6108a204 100644 --- a/tests/template_tests/syntax_tests/test_for.py +++ b/tests/template_tests/syntax_tests/test_for.py @@ -1,4 +1,5 @@ from django.template import TemplateSyntaxError +from django.template.defaulttags import ForNode from django.test import SimpleTestCase from ..utils import setup @@ -180,3 +181,38 @@ def test_for_tag_unpack12(self): def test_for_tag_unpack14(self): with self.assertRaisesMessage(ValueError, 'Need 2 values to unpack in for loop; got 1.'): self.engine.render_to_string('for-tag-unpack14', {'items': (1, 2)}) + + @setup({ + 'main': '{% with alpha=alpha.values %}{% include "base" %}{% endwith %}_' + '{% with alpha=alpha.extra %}{% include "base" %}{% endwith %}', + 'base': '{% for x, y in alpha %}{{ x }}:{{ y }},{% endfor %}' + }) + def test_for_tag_context(self): + """ + ForNode.render() pops the values it pushes to the context (#28001). + """ + output = self.engine.render_to_string('main', { + 'alpha': { + 'values': [('two', 2), ('four', 4)], + 'extra': [('six', 6), ('eight', 8)], + }, + }) + self.assertEqual(output, 'two:2,four:4,_six:6,eight:8,') + + @setup({'invalid_for_loop': '{% for x items %}{{ x }}{% endfor %}'}) + def test_invalid_arg(self): + msg = "'for' statements should have at least four words: for x items" + with self.assertRaisesMessage(TemplateSyntaxError, msg): + self.engine.render_to_string('invalid_for_loop', {'items': (1, 2)}) + + @setup({'invalid_for_loop': '{% for x from items %}{{ x }}{% endfor %}'}) + def test_invalid_in_keyword(self): + msg = "'for' statements should use the format 'for x in y': for x from items" + with self.assertRaisesMessage(TemplateSyntaxError, msg): + self.engine.render_to_string('invalid_for_loop', {'items': (1, 2)}) + + +class ForNodeTests(SimpleTestCase): + def test_repr(self): + node = ForNode('x', 'sequence', is_reversed=True, nodelist_loop=['val'], nodelist_empty=['val2']) + self.assertEqual(repr(node), '') diff --git a/tests/template_tests/syntax_tests/test_if.py b/tests/template_tests/syntax_tests/test_if.py index 40593c7694a2..711f871af93e 100644 --- a/tests/template_tests/syntax_tests/test_if.py +++ b/tests/template_tests/syntax_tests/test_if.py @@ -1,4 +1,5 @@ from django.template import TemplateSyntaxError +from django.template.defaulttags import IfNode from django.test import SimpleTestCase from ..utils import TestObj, setup @@ -533,9 +534,7 @@ def test_if_tag_shortcircuit02(self): @setup({'if-tag-badarg01': '{% if x|default_if_none:y %}yes{% endif %}'}) def test_if_tag_badarg01(self): - """ - Non-existent args - """ + """Nonexistent args""" output = self.engine.render_to_string('if-tag-badarg01') self.assertEqual(output, '') @@ -601,3 +600,9 @@ def test_if_is_not_variable_missing(self): def test_if_is_not_both_variables_missing(self): output = self.engine.render_to_string('template', {}) self.assertEqual(output, 'no') + + +class IfNodeTests(SimpleTestCase): + def test_repr(self): + node = IfNode(conditions_nodelists=[]) + self.assertEqual(repr(node), '') diff --git a/tests/template_tests/syntax_tests/test_if_changed.py b/tests/template_tests/syntax_tests/test_if_changed.py index bf8a95d8a4a4..5ea6b0dcf447 100644 --- a/tests/template_tests/syntax_tests/test_if_changed.py +++ b/tests/template_tests/syntax_tests/test_if_changed.py @@ -161,7 +161,7 @@ class IfChangedTests(SimpleTestCase): @classmethod def setUpClass(cls): cls.engine = Engine() - super(IfChangedTests, cls).setUpClass() + super().setUpClass() def test_ifchanged_concurrency(self): """ @@ -210,5 +210,16 @@ def test_include(self): 'include': '{% ifchanged %}{{ x }}{% endifchanged %}', }), ]) - output = engine.render_to_string('template', dict(vars=[1, 1, 2, 2, 3, 3])) + output = engine.render_to_string('template', {'vars': [1, 1, 2, 2, 3, 3]}) self.assertEqual(output, "123") + + def test_include_state(self): + """Tests the node state for different IncludeNodes (#27974).""" + engine = Engine(loaders=[ + ('django.template.loaders.locmem.Loader', { + 'template': '{% for x in vars %}{% include "include" %}{% include "include" %}{% endfor %}', + 'include': '{% ifchanged %}{{ x }}{% endifchanged %}', + }), + ]) + output = engine.render_to_string('template', {'vars': [1, 1, 2, 2, 3, 3]}) + self.assertEqual(output, '112233') diff --git a/tests/template_tests/syntax_tests/test_if_equal.py b/tests/template_tests/syntax_tests/test_if_equal.py index 6124608b0800..f416b9552306 100644 --- a/tests/template_tests/syntax_tests/test_if_equal.py +++ b/tests/template_tests/syntax_tests/test_if_equal.py @@ -1,3 +1,5 @@ +from django.template import TemplateSyntaxError +from django.template.defaulttags import IfEqualNode from django.test import SimpleTestCase from ..utils import setup @@ -215,3 +217,14 @@ def test_ifnotequal03(self): def test_ifnotequal04(self): output = self.engine.render_to_string('ifnotequal04', {'a': 1, 'b': 1}) self.assertEqual(output, 'no') + + @setup({'one_var': '{% ifnotequal a %}yes{% endifnotequal %}'}) + def test_one_var(self): + with self.assertRaisesMessage(TemplateSyntaxError, "'ifnotequal' takes two arguments"): + self.engine.render_to_string('one_var', {'a': 1}) + + +class IfEqualTests(SimpleTestCase): + def test_repr(self): + node = IfEqualNode(var1='a', var2='b', nodelist_true=[], nodelist_false=[], negate=False) + self.assertEqual(repr(node), '') diff --git a/tests/template_tests/syntax_tests/test_include.py b/tests/template_tests/syntax_tests/test_include.py index ca7ee6f5b0b8..858763967467 100644 --- a/tests/template_tests/syntax_tests/test_include.py +++ b/tests/template_tests/syntax_tests/test_include.py @@ -1,10 +1,7 @@ -import warnings - from django.template import ( - Context, Engine, TemplateDoesNotExist, TemplateSyntaxError, + Context, Engine, TemplateDoesNotExist, TemplateSyntaxError, loader, ) -from django.test import SimpleTestCase, ignore_warnings -from django.utils.deprecation import RemovedInDjango21Warning +from django.test import SimpleTestCase from ..utils import setup from .test_basic import basic_templates @@ -39,24 +36,8 @@ def test_include03(self): @setup({'include04': 'a{% include "nonexistent" %}b'}) def test_include04(self): template = self.engine.get_template('include04') - - if self.engine.debug: - with self.assertRaises(TemplateDoesNotExist): - template.render(Context({})) - else: - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter('always') - output = template.render(Context({})) - - self.assertEqual(output, "ab") - - self.assertEqual(len(warns), 1) - self.assertEqual( - str(warns[0].message), - "Rendering {% include 'include04' %} raised " - "TemplateDoesNotExist. In Django 2.1, this exception will be " - "raised rather than silenced and rendered as an empty string.", - ) + with self.assertRaises(TemplateDoesNotExist): + template.render(Context({})) @setup({ 'include 05': 'template with a space', @@ -178,48 +159,37 @@ def test_include_fail2(self): @setup({'include-error07': '{% include "include-fail1" %}'}, include_fail_templates) def test_include_error07(self): template = self.engine.get_template('include-error07') - - if self.engine.debug: - with self.assertRaises(RuntimeError): - template.render(Context()) - else: - with ignore_warnings(category=RemovedInDjango21Warning): - self.assertEqual(template.render(Context()), '') + with self.assertRaises(RuntimeError): + template.render(Context()) @setup({'include-error08': '{% include "include-fail2" %}'}, include_fail_templates) def test_include_error08(self): template = self.engine.get_template('include-error08') - - if self.engine.debug: - with self.assertRaises(TemplateSyntaxError): - template.render(Context()) - else: - with ignore_warnings(category=RemovedInDjango21Warning): - self.assertEqual(template.render(Context()), '') + with self.assertRaises(TemplateSyntaxError): + template.render(Context()) @setup({'include-error09': '{% include failed_include %}'}, include_fail_templates) def test_include_error09(self): context = Context({'failed_include': 'include-fail1'}) template = self.engine.get_template('include-error09') - - if self.engine.debug: - with self.assertRaises(RuntimeError): - template.render(context) - else: - with ignore_warnings(category=RemovedInDjango21Warning): - self.assertEqual(template.render(context), '') + with self.assertRaises(RuntimeError): + template.render(context) @setup({'include-error10': '{% include failed_include %}'}, include_fail_templates) def test_include_error10(self): context = Context({'failed_include': 'include-fail2'}) template = self.engine.get_template('include-error10') + with self.assertRaises(TemplateSyntaxError): + template.render(context) - if self.engine.debug: - with self.assertRaises(TemplateSyntaxError): - template.render(context) - else: - with ignore_warnings(category=RemovedInDjango21Warning): - self.assertEqual(template.render(context), '') + @setup({'include_empty': '{% include %}'}) + def test_include_empty(self): + msg = ( + "'include' tag takes at least one argument: the name of the " + "template to be included." + ) + with self.assertRaisesMessage(TemplateSyntaxError, msg): + self.engine.get_template('include_empty') class IncludeTests(SimpleTestCase): @@ -248,9 +218,6 @@ def test_extends_include_missing_baseloader(self): self.assertEqual(e.exception.args[0], 'missing.html') def test_extends_include_missing_cachedloader(self): - """ - Test the cache loader separately since it overrides load_template. - """ engine = Engine(debug=True, loaders=[ ('django.template.loaders.cached.Loader', [ 'django.template.loaders.app_directories.Loader', @@ -280,6 +247,11 @@ def test_include_template_argument(self): output = outer_tmpl.render(ctx) self.assertEqual(output, 'This worked!') + def test_include_from_loader_get_template(self): + tmpl = loader.get_template('include_tpl.html') # {% include tmpl %} + output = tmpl.render({'tmpl': loader.get_template('index.html')}) + self.assertEqual(output, 'index\n\n') + def test_include_immediate_missing(self): """ #16417 -- Include tags pointing to missing templates should not raise @@ -306,3 +278,23 @@ def test_include_recursive(self): "Recursion! A1 Recursion! B1 B2 B3 Recursion! C1", t.render(Context({'comments': comments})).replace(' ', '').replace('\n', ' ').strip(), ) + + def test_include_cache(self): + """ + {% include %} keeps resolved templates constant (#27974). The + CounterNode object in the {% counter %} template tag is created once + if caching works properly. Each iteration increases the counter instead + of restarting it. + + This works as a regression test only if the cached loader + isn't used, so the @setup decorator isn't used. + """ + engine = Engine(loaders=[ + ('django.template.loaders.locmem.Loader', { + 'template': '{% for x in vars %}{% include "include" %}{% endfor %}', + 'include': '{% include "next" %}', + 'next': '{% load custom %}{% counter %}' + }), + ], libraries={'custom': 'template_tests.templatetags.custom'}) + output = engine.render_to_string('template', {'vars': range(9)}) + self.assertEqual(output, '012345678') diff --git a/tests/template_tests/syntax_tests/test_lorem.py b/tests/template_tests/syntax_tests/test_lorem.py index 9963bbd026ad..631bc3d0678c 100644 --- a/tests/template_tests/syntax_tests/test_lorem.py +++ b/tests/template_tests/syntax_tests/test_lorem.py @@ -1,4 +1,5 @@ from django.test import SimpleTestCase +from django.utils.lorem_ipsum import WORDS from ..utils import setup @@ -9,3 +10,11 @@ class LoremTagTests(SimpleTestCase): def test_lorem1(self): output = self.engine.render_to_string('lorem1') self.assertEqual(output, 'lorem ipsum dolor') + + @setup({'lorem_random': '{% lorem 3 w random %}'}) + def test_lorem_random(self): + output = self.engine.render_to_string('lorem_random') + words = output.split(' ') + self.assertEqual(len(words), 3) + for word in words: + self.assertIn(word, WORDS) diff --git a/tests/template_tests/syntax_tests/test_multiline.py b/tests/template_tests/syntax_tests/test_multiline.py index b2371f7fd103..a95e12986a98 100644 --- a/tests/template_tests/syntax_tests/test_multiline.py +++ b/tests/template_tests/syntax_tests/test_multiline.py @@ -2,7 +2,6 @@ from ..utils import setup - multiline_string = """ Hello, boys. diff --git a/tests/template_tests/syntax_tests/test_now.py b/tests/template_tests/syntax_tests/test_now.py index 54cd4ccd2ec4..ac2a167b4f72 100644 --- a/tests/template_tests/syntax_tests/test_now.py +++ b/tests/template_tests/syntax_tests/test_now.py @@ -1,5 +1,6 @@ from datetime import datetime +from django.template import TemplateSyntaxError from django.test import SimpleTestCase from django.utils.formats import date_format @@ -59,3 +60,8 @@ def test_now07(self): self.assertEqual(output, '-%d %d %d-' % ( datetime.now().day, datetime.now().month, datetime.now().year, )) + + @setup({'no_args': '{% now %}'}) + def test_now_args(self): + with self.assertRaisesMessage(TemplateSyntaxError, "'now' statement takes one argument"): + self.engine.render_to_string('no_args') diff --git a/tests/template_tests/syntax_tests/test_numpy.py b/tests/template_tests/syntax_tests/test_numpy.py index 7a6caf6528e5..09d57ebcc2b1 100644 --- a/tests/template_tests/syntax_tests/test_numpy.py +++ b/tests/template_tests/syntax_tests/test_numpy.py @@ -1,4 +1,3 @@ -import warnings from unittest import skipIf from django.test import SimpleTestCase @@ -7,23 +6,12 @@ try: import numpy - VisibleDeprecationWarning = numpy.VisibleDeprecationWarning except ImportError: numpy = False -except AttributeError: # numpy < 1.9.0, e.g. 1.8.2 in Debian 8 - VisibleDeprecationWarning = DeprecationWarning @skipIf(numpy is False, "Numpy must be installed to run these tests.") class NumpyTests(SimpleTestCase): - # Ignore numpy deprecation warnings (#23890) - if numpy: - warnings.filterwarnings( - "ignore", - "Using a non-integer number instead of an " - "integer will result in an error in the future", - VisibleDeprecationWarning - ) @setup({'numpy-array-index01': '{{ var.1 }}'}) def test_numpy_array_index01(self): diff --git a/tests/template_tests/syntax_tests/test_static.py b/tests/template_tests/syntax_tests/test_static.py index deb0ce6c7836..00f8cdbc1148 100644 --- a/tests/template_tests/syntax_tests/test_static.py +++ b/tests/template_tests/syntax_tests/test_static.py @@ -1,6 +1,8 @@ +from urllib.parse import urljoin + from django.conf import settings +from django.template import TemplateSyntaxError from django.test import SimpleTestCase, override_settings -from django.utils.six.moves.urllib.parse import urljoin from ..utils import setup @@ -31,6 +33,12 @@ def test_static_prefixtag04(self): output = self.engine.render_to_string('static-prefixtag04') self.assertEqual(output, settings.MEDIA_URL) + @setup({'t': '{% load static %}{% get_media_prefix ad media_prefix %}{{ media_prefix }}'}) + def test_static_prefixtag_without_as(self): + msg = "First argument in 'get_media_prefix' must be 'as'" + with self.assertRaisesMessage(TemplateSyntaxError, msg): + self.engine.render_to_string('t') + @setup({'static-statictag01': '{% load static %}{% static "admin/base.css" %}'}) def test_static_statictag01(self): output = self.engine.render_to_string('static-statictag01') @@ -55,3 +63,9 @@ def test_static_statictag04(self): def test_static_quotes_urls(self): output = self.engine.render_to_string('static-statictag05') self.assertEqual(output, urljoin(settings.STATIC_URL, '/static/special%3Fchars%26quoted.html')) + + @setup({'t': '{% load static %}{% static %}'}) + def test_static_statictag_without_path(self): + msg = "'static' takes at least one argument (path to file)" + with self.assertRaisesMessage(TemplateSyntaxError, msg): + self.engine.render_to_string('t') diff --git a/tests/template_tests/syntax_tests/test_url.py b/tests/template_tests/syntax_tests/test_url.py index e6e20d08f361..a6cc2d50a0f2 100644 --- a/tests/template_tests/syntax_tests/test_url.py +++ b/tests/template_tests/syntax_tests/test_url.py @@ -1,4 +1,3 @@ -# coding: utf-8 from django.template import RequestContext, TemplateSyntaxError from django.test import RequestFactory, SimpleTestCase, override_settings from django.urls import NoReverseMatch, resolve @@ -8,6 +7,7 @@ @override_settings(ROOT_URLCONF='template_tests.urls') class UrlTagTests(SimpleTestCase): + request_factory = RequestFactory() # Successes @setup({'url01': '{% url "client" client.id %}'}) @@ -228,7 +228,7 @@ def test_url_asvar03(self): @setup({'url-namespace01': '{% url "app:named.client" 42 %}'}) def test_url_namespace01(self): - request = RequestFactory().get('/') + request = self.request_factory.get('/') request.resolver_match = resolve('/ns1/') template = self.engine.get_template('url-namespace01') context = RequestContext(request) @@ -237,7 +237,7 @@ def test_url_namespace01(self): @setup({'url-namespace02': '{% url "app:named.client" 42 %}'}) def test_url_namespace02(self): - request = RequestFactory().get('/') + request = self.request_factory.get('/') request.resolver_match = resolve('/ns2/') template = self.engine.get_template('url-namespace02') context = RequestContext(request) @@ -246,7 +246,7 @@ def test_url_namespace02(self): @setup({'url-namespace03': '{% url "app:named.client" 42 %}'}) def test_url_namespace03(self): - request = RequestFactory().get('/') + request = self.request_factory.get('/') template = self.engine.get_template('url-namespace03') context = RequestContext(request) output = template.render(context) @@ -254,7 +254,7 @@ def test_url_namespace03(self): @setup({'url-namespace-no-current-app': '{% url "app:named.client" 42 %}'}) def test_url_namespace_no_current_app(self): - request = RequestFactory().get('/') + request = self.request_factory.get('/') request.resolver_match = resolve('/ns1/') request.current_app = None template = self.engine.get_template('url-namespace-no-current-app') @@ -264,7 +264,7 @@ def test_url_namespace_no_current_app(self): @setup({'url-namespace-explicit-current-app': '{% url "app:named.client" 42 %}'}) def test_url_namespace_explicit_current_app(self): - request = RequestFactory().get('/') + request = self.request_factory.get('/') request.resolver_match = resolve('/ns1/') request.current_app = 'app' template = self.engine.get_template('url-namespace-explicit-current-app') diff --git a/tests/template_tests/syntax_tests/test_width_ratio.py b/tests/template_tests/syntax_tests/test_width_ratio.py index 8206b83c58c7..7db90a44e1e7 100644 --- a/tests/template_tests/syntax_tests/test_width_ratio.py +++ b/tests/template_tests/syntax_tests/test_width_ratio.py @@ -1,6 +1,5 @@ from django.template import TemplateSyntaxError from django.test import SimpleTestCase -from django.utils import six from ..utils import setup @@ -36,11 +35,10 @@ def test_widthratio05(self): @setup({'widthratio06': '{% widthratio a b 100 %}'}) def test_widthratio06(self): """ - 62.5 should round to 63 on Python 2 and 62 on Python 3 - See http://docs.python.org/py3k/whatsnew/3.0.html + 62.5 should round to 62 """ output = self.engine.render_to_string('widthratio06', {'a': 50, 'b': 80}) - self.assertEqual(output, '62' if six.PY3 else '63') + self.assertEqual(output, '62') @setup({'widthratio07': '{% widthratio a b 100 %}'}) def test_widthratio07(self): @@ -144,3 +142,13 @@ def test_widthratio20(self): def test_widthratio21(self): output = self.engine.render_to_string('widthratio21', {'a': float('inf'), 'b': 2}) self.assertEqual(output, '') + + @setup({'t': '{% widthratio a b 100 as variable %}-{{ variable }}-'}) + def test_zerodivisionerror_as_var(self): + output = self.engine.render_to_string('t', {'a': 0, 'b': 0}) + self.assertEqual(output, '-0-') + + @setup({'t': '{% widthratio a b c as variable %}-{{ variable }}-'}) + def test_typeerror_as_var(self): + output = self.engine.render_to_string('t', {'a': 'a', 'c': 100, 'b': 100}) + self.assertEqual(output, '--') diff --git a/tests/template_tests/syntax_tests/test_with.py b/tests/template_tests/syntax_tests/test_with.py index ba1875af2148..d8f24349f529 100644 --- a/tests/template_tests/syntax_tests/test_with.py +++ b/tests/template_tests/syntax_tests/test_with.py @@ -1,10 +1,12 @@ from django.template import TemplateSyntaxError +from django.template.defaulttags import WithNode from django.test import SimpleTestCase from ..utils import setup class WithTagTests(SimpleTestCase): + at_least_with_one_msg = "'with' expected at least one variable assignment" @setup({'with01': '{% with key=dict.key %}{{ key }}{% endwith %}'}) def test_with01(self): @@ -43,10 +45,16 @@ def test_with03(self): @setup({'with-error01': '{% with dict.key xx key %}{{ key }}{% endwith %}'}) def test_with_error01(self): - with self.assertRaises(TemplateSyntaxError): + with self.assertRaisesMessage(TemplateSyntaxError, self.at_least_with_one_msg): self.engine.render_to_string('with-error01', {'dict': {'key': 50}}) @setup({'with-error02': '{% with dict.key as %}{{ key }}{% endwith %}'}) def test_with_error02(self): - with self.assertRaises(TemplateSyntaxError): + with self.assertRaisesMessage(TemplateSyntaxError, self.at_least_with_one_msg): self.engine.render_to_string('with-error02', {'dict': {'key': 50}}) + + +class WithNodeTests(SimpleTestCase): + def test_repr(self): + node = WithNode(nodelist=[], name='a', var='dict.key') + self.assertEqual(repr(node), '') diff --git a/tests/template_tests/templates/27956_child.html b/tests/template_tests/templates/27956_child.html new file mode 100644 index 000000000000..ea9aeb21978b --- /dev/null +++ b/tests/template_tests/templates/27956_child.html @@ -0,0 +1,3 @@ +{% extends "27956_parent.html" %} + +{% block content %}{% endblock %} diff --git a/tests/template_tests/templates/27956_parent.html b/tests/template_tests/templates/27956_parent.html new file mode 100644 index 000000000000..dad4fbbfe5ea --- /dev/null +++ b/tests/template_tests/templates/27956_parent.html @@ -0,0 +1,5 @@ +{% load tag_27584 %} + +{% badtag %}{% endbadtag %} + +{% block content %}{% endblock %} diff --git a/tests/template_tests/templates/include_tpl.html b/tests/template_tests/templates/include_tpl.html new file mode 100644 index 000000000000..7a8374df518d --- /dev/null +++ b/tests/template_tests/templates/include_tpl.html @@ -0,0 +1 @@ +{% include tmpl %} diff --git a/tests/template_tests/templates/included_content.html b/tests/template_tests/templates/included_content.html index bfc87c042527..cc033bd3b91a 100644 --- a/tests/template_tests/templates/included_content.html +++ b/tests/template_tests/templates/included_content.html @@ -7,5 +7,5 @@ {% block error_here %} error here - {% url "non_existing_url" %} + {% url "nonexistent_url" %} {% endblock %} diff --git a/tests/template_tests/templatetags/custom.py b/tests/template_tests/templatetags/custom.py index fffef022ac50..eaaff193eed4 100644 --- a/tests/template_tests/templatetags/custom.py +++ b/tests/template_tests/templatetags/custom.py @@ -1,10 +1,9 @@ import operator -import warnings from django import template from django.template.defaultfilters import stringfilter -from django.utils import six from django.utils.html import escape, format_html +from django.utils.safestring import mark_safe register = template.Library() @@ -15,6 +14,13 @@ def trim(value, num): return value[:num] +@register.filter +@mark_safe +def make_data_div(value): + """A filter that uses a decorator (@mark_safe).""" + return '
        ' % value + + @register.filter def noop(value, param=None): """A noop filter that always return its first argument and does nothing with @@ -82,6 +88,16 @@ def simple_two_params(one, two): simple_two_params.anything = "Expected simple_two_params __dict__" +@register.simple_tag +def simple_keyword_only_param(*, kwarg): + return "simple_keyword_only_param - Expected result: %s" % kwarg + + +@register.simple_tag +def simple_keyword_only_default(*, kwarg=42): + return "simple_keyword_only_default - Expected result: %s" % kwarg + + @register.simple_tag def simple_one_default(one, two='hi'): """Expected simple_one_default __doc__""" @@ -95,7 +111,7 @@ def simple_one_default(one, two='hi'): def simple_unlimited_args(one, two='hi', *args): """Expected simple_unlimited_args __doc__""" return "simple_unlimited_args - Expected result: %s" % ( - ', '.join(six.text_type(arg) for arg in [one, two] + list(args)) + ', '.join(str(arg) for arg in [one, two, *args]) ) @@ -105,7 +121,7 @@ def simple_unlimited_args(one, two='hi', *args): @register.simple_tag def simple_only_unlimited_args(*args): """Expected simple_only_unlimited_args __doc__""" - return "simple_only_unlimited_args - Expected result: %s" % ', '.join(six.text_type(arg) for arg in args) + return "simple_only_unlimited_args - Expected result: %s" % ', '.join(str(arg) for arg in args) simple_only_unlimited_args.anything = "Expected simple_only_unlimited_args __dict__" @@ -115,9 +131,9 @@ def simple_only_unlimited_args(*args): def simple_unlimited_args_kwargs(one, two='hi', *args, **kwargs): """Expected simple_unlimited_args_kwargs __doc__""" # Sort the dictionary by key to guarantee the order for testing. - sorted_kwarg = sorted(six.iteritems(kwargs), key=operator.itemgetter(0)) + sorted_kwarg = sorted(kwargs.items(), key=operator.itemgetter(0)) return "simple_unlimited_args_kwargs - Expected result: %s / %s" % ( - ', '.join(six.text_type(arg) for arg in [one, two] + list(args)), + ', '.join(str(arg) for arg in [one, two, *args]), ', '.join('%s=%s' % (k, v) for (k, v) in sorted_kwarg) ) @@ -170,17 +186,16 @@ def minustwo_overridden_name(value): register.simple_tag(lambda x: x - 1, name='minusone') -with warnings.catch_warnings(): - warnings.simplefilter('ignore') +@register.tag('counter') +def counter(parser, token): + return CounterNode() + - @register.assignment_tag - def assignment_no_params(): - """Expected assignment_no_params __doc__""" - return "assignment_no_params - Expected result" - assignment_no_params.anything = "Expected assignment_no_params __dict__" +class CounterNode(template.Node): + def __init__(self): + self.count = 0 - @register.assignment_tag(takes_context=True) - def assignment_tag_without_context_parameter(arg): - """Expected assignment_tag_without_context_parameter __doc__""" - return "Expected result" - assignment_tag_without_context_parameter.anything = "Expected assignment_tag_without_context_parameter __dict__" + def render(self, context): + count = self.count + self.count = count + 1 + return count diff --git a/tests/template_tests/templatetags/inclusion.py b/tests/template_tests/templatetags/inclusion.py index dbdfa45c9500..242fbe80cbe0 100644 --- a/tests/template_tests/templatetags/inclusion.py +++ b/tests/template_tests/templatetags/inclusion.py @@ -1,7 +1,6 @@ import operator from django.template import Engine, Library -from django.utils import six engine = Engine(app_dirs=True) register = Library() @@ -152,7 +151,7 @@ def inclusion_unlimited_args(one, two='hi', *args): return { "result": ( "inclusion_unlimited_args - Expected result: %s" % ( - ', '.join(six.text_type(arg) for arg in [one, two] + list(args)) + ', '.join(str(arg) for arg in [one, two, *args]) ) ) } @@ -167,7 +166,7 @@ def inclusion_unlimited_args_from_template(one, two='hi', *args): return { "result": ( "inclusion_unlimited_args_from_template - Expected result: %s" % ( - ', '.join(six.text_type(arg) for arg in [one, two] + list(args)) + ', '.join(str(arg) for arg in [one, two, *args]) ) ) } @@ -181,7 +180,7 @@ def inclusion_only_unlimited_args(*args): """Expected inclusion_only_unlimited_args __doc__""" return { "result": "inclusion_only_unlimited_args - Expected result: %s" % ( - ', '.join(six.text_type(arg) for arg in args) + ', '.join(str(arg) for arg in args) ) } @@ -194,7 +193,7 @@ def inclusion_only_unlimited_args_from_template(*args): """Expected inclusion_only_unlimited_args_from_template __doc__""" return { "result": "inclusion_only_unlimited_args_from_template - Expected result: %s" % ( - ', '.join(six.text_type(arg) for arg in args) + ', '.join(str(arg) for arg in args) ) } @@ -215,9 +214,9 @@ def inclusion_tag_use_l10n(context): def inclusion_unlimited_args_kwargs(one, two='hi', *args, **kwargs): """Expected inclusion_unlimited_args_kwargs __doc__""" # Sort the dictionary by key to guarantee the order for testing. - sorted_kwarg = sorted(six.iteritems(kwargs), key=operator.itemgetter(0)) + sorted_kwarg = sorted(kwargs.items(), key=operator.itemgetter(0)) return {"result": "inclusion_unlimited_args_kwargs - Expected result: %s / %s" % ( - ', '.join(six.text_type(arg) for arg in [one, two] + list(args)), + ', '.join(str(arg) for arg in [one, two, *args]), ', '.join('%s=%s' % (k, v) for (k, v) in sorted_kwarg) )} diff --git a/tests/template_tests/test_base.py b/tests/template_tests/test_base.py new file mode 100644 index 000000000000..475a647dd2c0 --- /dev/null +++ b/tests/template_tests/test_base.py @@ -0,0 +1,29 @@ +from django.template import Context, Template, Variable, VariableDoesNotExist +from django.test import SimpleTestCase +from django.utils.translation import gettext_lazy + + +class TemplateTests(SimpleTestCase): + def test_lazy_template_string(self): + template_string = gettext_lazy('lazy string') + self.assertEqual(Template(template_string).render(Context()), template_string) + + +class VariableDoesNotExistTests(SimpleTestCase): + def test_str(self): + exc = VariableDoesNotExist(msg='Failed lookup in %r', params=({'foo': 'bar'},)) + self.assertEqual(str(exc), "Failed lookup in {'foo': 'bar'}") + + +class VariableTests(SimpleTestCase): + def test_integer_literals(self): + self.assertEqual(Variable('999999999999999999999999999').literal, 999999999999999999999999999) + + def test_nonliterals(self): + """Variable names that aren't resolved as literals.""" + var_names = [] + for var in ('inf', 'infinity', 'iNFiniTy', 'nan'): + var_names.extend((var, '-' + var, '+' + var)) + for var in var_names: + with self.subTest(var=var): + self.assertIsNone(Variable(var).literal) diff --git a/tests/template_tests/test_callables.py b/tests/template_tests/test_callables.py index b7ce2944033b..b72b122deb87 100644 --- a/tests/template_tests/test_callables.py +++ b/tests/template_tests/test_callables.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from unittest import TestCase from django.template import Context, Engine @@ -10,11 +8,11 @@ class CallableVariablesTests(TestCase): @classmethod def setUpClass(cls): cls.engine = Engine() - super(CallableVariablesTests, cls).setUpClass() + super().setUpClass() def test_callable(self): - class Doodad(object): + class Doodad: def __init__(self, value): self.num_calls = 0 self.value = value @@ -43,7 +41,7 @@ def __call__(self): def test_alters_data(self): - class Doodad(object): + class Doodad: alters_data = True def __init__(self, value): @@ -70,7 +68,7 @@ def __call__(self): def test_do_not_call(self): - class Doodad(object): + class Doodad: do_not_call_in_templates = True def __init__(self, value): @@ -102,7 +100,7 @@ def test_do_not_call_and_alters_data(self): # ``alters_data`` attribute will not make any difference in the # template system's behavior. - class Doodad(object): + class Doodad: do_not_call_in_templates = True alters_data = True diff --git a/tests/template_tests/test_context.py b/tests/template_tests/test_context.py index 79f7b067941f..bdfd5566909b 100644 --- a/tests/template_tests/test_context.py +++ b/tests/template_tests/test_context.py @@ -1,13 +1,9 @@ -# -*- coding: utf-8 -*- -import warnings - from django.http import HttpRequest from django.template import ( Context, Engine, RequestContext, Template, Variable, VariableDoesNotExist, ) from django.template.context import RenderContext -from django.test import RequestFactory, SimpleTestCase, ignore_warnings -from django.utils.deprecation import RemovedInDjango20Warning +from django.test import RequestFactory, SimpleTestCase class ContextTests(SimpleTestCase): @@ -184,26 +180,6 @@ def test_copy_request_context_twice(self): """ RequestContext(HttpRequest()).new().new() - @ignore_warnings(category=RemovedInDjango20Warning) - def test_has_key(self): - a = Context({'a': 1}) - b = RequestContext(HttpRequest(), {'a': 1}) - msg = "Context.has_key() is deprecated in favor of the 'in' operator." - msg2 = "RequestContext.has_key() is deprecated in favor of the 'in' operator." - - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter('always') - self.assertIs(a.has_key('a'), True) - self.assertIs(a.has_key('b'), False) - self.assertIs(b.has_key('a'), True) - self.assertIs(b.has_key('b'), False) - - self.assertEqual(len(warns), 4) - self.assertEqual(str(warns[0].message), msg) - self.assertEqual(str(warns[1].message), msg) - self.assertEqual(str(warns[2].message), msg2) - self.assertEqual(str(warns[3].message), msg2) - def test_set_upward(self): c = Context({'a': 1}) c.set_upward('a', 2) @@ -237,6 +213,7 @@ def test_set_upward_with_push_no_match(self): class RequestContextTests(SimpleTestCase): + request_factory = RequestFactory() def test_include_only(self): """ @@ -248,7 +225,7 @@ def test_include_only(self): 'child': '{{ var|default:"none" }}', }), ]) - request = RequestFactory().get('/') + request = self.request_factory.get('/') ctx = RequestContext(request, {'var': 'parent'}) self.assertEqual(engine.from_string('{% include "child" %}').render(ctx), 'parent') self.assertEqual(engine.from_string('{% include "child" only %}').render(ctx), 'none') @@ -257,7 +234,7 @@ def test_stack_size(self): """ #7116 -- Optimize RequetsContext construction """ - request = RequestFactory().get('/') + request = self.request_factory.get('/') ctx = RequestContext(request, {}) # The stack should now contain 3 items: # [builtins, supplied context, context processor, empty dict] @@ -269,7 +246,7 @@ def test_context_comparable(self): # test comparing RequestContext to prevent problems if somebody # adds __eq__ in the future - request = RequestFactory().get('/') + request = self.request_factory.get('/') self.assertEqual( RequestContext(request, dict_=test_data), @@ -278,7 +255,7 @@ def test_context_comparable(self): def test_modify_context_and_render(self): template = Template('{{ foo }}') - request = RequestFactory().get('/') + request = self.request_factory.get('/') context = RequestContext(request, {}) context['foo'] = 'foo' self.assertEqual(template.render(context), 'foo') diff --git a/tests/template_tests/test_custom.py b/tests/template_tests/test_custom.py index e6a876086e8c..dbc5bc267d8d 100644 --- a/tests/template_tests/test_custom.py +++ b/tests/template_tests/test_custom.py @@ -1,14 +1,10 @@ -from __future__ import unicode_literals - import os -from unittest import skipUnless from django.template import Context, Engine, TemplateSyntaxError from django.template.base import Node from django.template.library import InvalidTemplateLibrary from django.test import SimpleTestCase from django.test.utils import extend_sys_path -from django.utils import six from .templatetags import custom, inclusion from .utils import ROOT @@ -29,13 +25,18 @@ def test_filter(self): "abcde" ) + def test_decorated_filter(self): + engine = Engine(libraries=LIBRARIES) + t = engine.from_string('{% load custom %}{{ name|make_data_div }}') + self.assertEqual(t.render(Context({'name': 'foo'})), '
        ') + class TagTestCase(SimpleTestCase): @classmethod def setUpClass(cls): cls.engine = Engine(app_dirs=True, libraries=LIBRARIES) - super(TagTestCase, cls).setUpClass() + super().setUpClass() def verify_tag(self, tag, name): self.assertEqual(tag.__name__, name) @@ -57,6 +58,10 @@ def test_simple_tags(self): ('{% load custom %}{% params_and_context 37 %}', 'params_and_context - Expected result (context value: 42): 37'), ('{% load custom %}{% simple_two_params 37 42 %}', 'simple_two_params - Expected result: 37, 42'), + ('{% load custom %}{% simple_keyword_only_param kwarg=37 %}', + 'simple_keyword_only_param - Expected result: 37'), + ('{% load custom %}{% simple_keyword_only_default %}', + 'simple_keyword_only_default - Expected result: 42'), ('{% load custom %}{% simple_one_default 37 %}', 'simple_one_default - Expected result: 37, hi'), ('{% load custom %}{% simple_one_default 37 two="hello" %}', 'simple_one_default - Expected result: 37, hello'), @@ -90,6 +95,8 @@ def test_simple_tag_errors(self): '{% load custom %}{% simple_two_params 37 42 56 %}'), ("'simple_one_default' received too many positional arguments", '{% load custom %}{% simple_one_default 37 42 56 %}'), + ("'simple_keyword_only_param' did not receive value(s) for the argument(s): 'kwarg'", + '{% load custom %}{% simple_keyword_only_param %}'), ("'simple_unlimited_args_kwargs' received some positional argument(s) after some keyword argument(s)", '{% load custom %}{% simple_unlimited_args_kwargs 37 40|add:2 eggs="scrambled" 56 four=1|add:3 %}'), ("'simple_unlimited_args_kwargs' received multiple values for keyword argument 'eggs'", @@ -306,59 +313,32 @@ def test_render_context_is_cleared(self): self.assertEqual(template.render(Context({})).strip(), 'one\ntwo') -class AssignmentTagTests(TagTestCase): - - def test_assignment_tags(self): - c = Context({'value': 42}) - - t = self.engine.from_string('{% load custom %}{% assignment_no_params as var %}The result is: {{ var }}') - self.assertEqual(t.render(c), 'The result is: assignment_no_params - Expected result') - - def test_assignment_tag_registration(self): - # The decorators preserve the decorated function's docstring, name, - # and attributes. - self.verify_tag(custom.assignment_no_params, 'assignment_no_params') - - def test_assignment_tag_missing_context(self): - # The 'context' parameter must be present when takes_context is True - msg = ( - "'assignment_tag_without_context_parameter' is decorated with " - "takes_context=True so it must have a first argument of 'context'" - ) - with self.assertRaisesMessage(TemplateSyntaxError, msg): - self.engine.from_string('{% load custom %}{% assignment_tag_without_context_parameter 123 as var %}') - - class TemplateTagLoadingTests(SimpleTestCase): @classmethod def setUpClass(cls): cls.egg_dir = os.path.join(ROOT, 'eggs') - super(TemplateTagLoadingTests, cls).setUpClass() + super().setUpClass() def test_load_error(self): msg = ( "Invalid template library specified. ImportError raised when " "trying to load 'template_tests.broken_tag': cannot import name " - "'?Xtemplate'?" + "'Xtemplate'" ) - with self.assertRaisesRegex(InvalidTemplateLibrary, msg): - Engine(libraries={ - 'broken_tag': 'template_tests.broken_tag', - }) + with self.assertRaisesMessage(InvalidTemplateLibrary, msg): + Engine(libraries={'broken_tag': 'template_tests.broken_tag'}) def test_load_error_egg(self): egg_name = '%s/tagsegg.egg' % self.egg_dir msg = ( "Invalid template library specified. ImportError raised when " "trying to load 'tagsegg.templatetags.broken_egg': cannot " - "import name '?Xtemplate'?" + "import name 'Xtemplate'" ) with extend_sys_path(egg_name): - with self.assertRaisesRegex(InvalidTemplateLibrary, msg): - Engine(libraries={ - 'broken_egg': 'tagsegg.templatetags.broken_egg', - }) + with self.assertRaisesMessage(InvalidTemplateLibrary, msg): + Engine(libraries={'broken_egg': 'tagsegg.templatetags.broken_egg'}) def test_load_working_egg(self): ttext = "{% load working_egg %}" @@ -369,7 +349,6 @@ def test_load_working_egg(self): }) engine.from_string(ttext) - @skipUnless(six.PY3, "Python 3 only -- Python 2 doesn't have annotations.") def test_load_annotated_function(self): Engine(libraries={ 'annotated_tag_function': 'template_tests.annotated_tag_function', diff --git a/tests/template_tests/test_engine.py b/tests/template_tests/test_engine.py index 3b65dcb4f2dd..2bb8601fbbaf 100644 --- a/tests/template_tests/test_engine.py +++ b/tests/template_tests/test_engine.py @@ -1,8 +1,9 @@ import os +from django.core.exceptions import ImproperlyConfigured from django.template import Context from django.template.engine import Engine -from django.test import SimpleTestCase +from django.test import SimpleTestCase, override_settings from .utils import ROOT, TEMPLATE_DIR @@ -21,6 +22,35 @@ def test_basic_context(self): ) +class GetDefaultTests(SimpleTestCase): + + @override_settings(TEMPLATES=[]) + def test_no_engines_configured(self): + msg = 'No DjangoTemplates backend is configured.' + with self.assertRaisesMessage(ImproperlyConfigured, msg): + Engine.get_default() + + @override_settings(TEMPLATES=[{ + 'NAME': 'default', + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'OPTIONS': {'file_charset': 'abc'}, + }]) + def test_single_engine_configured(self): + self.assertEqual(Engine.get_default().file_charset, 'abc') + + @override_settings(TEMPLATES=[{ + 'NAME': 'default', + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'OPTIONS': {'file_charset': 'abc'}, + }, { + 'NAME': 'other', + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'OPTIONS': {'file_charset': 'def'}, + }]) + def test_multiple_engines_configured(self): + self.assertEqual(Engine.get_default().file_charset, 'abc') + + class LoaderTests(SimpleTestCase): def test_origin(self): diff --git a/tests/template_tests/test_extends.py b/tests/template_tests/test_extends.py index 1d7e00b60d08..0950340f52da 100644 --- a/tests/template_tests/test_extends.py +++ b/tests/template_tests/test_extends.py @@ -1,10 +1,7 @@ import os from django.template import Context, Engine, TemplateDoesNotExist -from django.template.loader_tags import ExtendsError -from django.template.loaders.base import Loader -from django.test import SimpleTestCase, ignore_warnings -from django.utils.deprecation import RemovedInDjango20Warning +from django.test import SimpleTestCase from .utils import ROOT @@ -121,63 +118,23 @@ def test_unique_history_per_loader(self): output = template.render(Context({})) self.assertEqual(output.strip(), 'loader2 loader1') - -class NonRecursiveLoader(Loader): - - def __init__(self, engine, templates_dict): - self.templates_dict = templates_dict - super(NonRecursiveLoader, self).__init__(engine) - - def load_template_source(self, template_name, template_dirs=None): - try: - return self.templates_dict[template_name], template_name - except KeyError: - raise TemplateDoesNotExist(template_name) - - -@ignore_warnings(category=RemovedInDjango20Warning) -class NonRecursiveLoaderExtendsTests(SimpleTestCase): - - loaders = [ - ('template_tests.test_extends.NonRecursiveLoader', { - 'base.html': 'base', - 'index.html': '{% extends "base.html" %}', - 'recursive.html': '{% extends "recursive.html" %}', - 'other-recursive.html': '{% extends "recursive.html" %}', - 'a.html': '{% extends "b.html" %}', - 'b.html': '{% extends "a.html" %}', - }), - ] - - def test_extend(self): - engine = Engine(loaders=self.loaders) - output = engine.render_to_string('index.html') - self.assertEqual(output, 'base') - - def test_extend_cached(self): - engine = Engine(loaders=[ - ('django.template.loaders.cached.Loader', self.loaders), - ]) - output = engine.render_to_string('index.html') - self.assertEqual(output, 'base') - - cache = engine.template_loaders[0].template_cache - self.assertIn('base.html', cache) - self.assertIn('index.html', cache) - - # Render a second time from cache - output = engine.render_to_string('index.html') - self.assertEqual(output, 'base') - - def test_extend_error(self): - engine = Engine(loaders=self.loaders) - msg = 'Cannot extend templates recursively when using non-recursive template loaders' - - with self.assertRaisesMessage(ExtendsError, msg): - engine.render_to_string('recursive.html') - - with self.assertRaisesMessage(ExtendsError, msg): - engine.render_to_string('other-recursive.html') - - with self.assertRaisesMessage(ExtendsError, msg): - engine.render_to_string('a.html') + def test_block_override_in_extended_included_template(self): + """ + ExtendsNode.find_template() initializes history with self.origin + (#28071). + """ + engine = Engine( + loaders=[ + ['django.template.loaders.locmem.Loader', { + 'base.html': "{% extends 'base.html' %}{% block base %}{{ block.super }}2{% endblock %}", + 'included.html': + "{% extends 'included.html' %}{% block included %}{{ block.super }}B{% endblock %}", + }], + ['django.template.loaders.locmem.Loader', { + 'base.html': "{% block base %}1{% endblock %}{% include 'included.html' %}", + 'included.html': "{% block included %}A{% endblock %}", + }], + ], + ) + template = engine.get_template('base.html') + self.assertEqual(template.render(Context({})), '12AB') diff --git a/tests/template_tests/test_library.py b/tests/template_tests/test_library.py index 50a00ca0829b..b7a1f73a2e0d 100644 --- a/tests/template_tests/test_library.py +++ b/tests/template_tests/test_library.py @@ -1,9 +1,9 @@ from django.template import Library from django.template.base import Node -from django.test import TestCase +from django.test import SimpleTestCase -class FilterRegistrationTests(TestCase): +class FilterRegistrationTests(SimpleTestCase): def setUp(self): self.library = Library() @@ -44,7 +44,7 @@ def test_filter_invalid(self): self.library.filter(None, '') -class InclusionTagRegistrationTests(TestCase): +class InclusionTagRegistrationTests(SimpleTestCase): def setUp(self): self.library = Library() @@ -62,7 +62,7 @@ def func(): self.assertIn('name', self.library.tags) -class SimpleTagRegistrationTests(TestCase): +class SimpleTagRegistrationTests(SimpleTestCase): def setUp(self): self.library = Library() @@ -91,7 +91,7 @@ def test_simple_tag_invalid(self): self.library.simple_tag('invalid') -class TagRegistrationTests(TestCase): +class TagRegistrationTests(SimpleTestCase): def setUp(self): self.library = Library() diff --git a/tests/template_tests/test_loaders.py b/tests/template_tests/test_loaders.py index 22f32941feb7..ea694722640c 100644 --- a/tests/template_tests/test_loaders.py +++ b/tests/template_tests/test_loaders.py @@ -1,27 +1,16 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import os.path import sys import tempfile -import types import unittest from contextlib import contextmanager -from django.template import Context, TemplateDoesNotExist +from django.template import TemplateDoesNotExist from django.template.engine import Engine -from django.test import SimpleTestCase, ignore_warnings, override_settings -from django.utils import six -from django.utils.deprecation import RemovedInDjango20Warning +from django.test import SimpleTestCase, override_settings from django.utils.functional import lazystr from .utils import TEMPLATE_DIR -try: - import pkg_resources -except ImportError: - pkg_resources = None - class CachedLoaderTests(SimpleTestCase): @@ -74,7 +63,6 @@ def test_get_template_missing_debug_on(self): self.assertIsInstance(e, TemplateDoesNotExist) self.assertEqual(e.args[0], 'debug-template-missing.html') - @unittest.skipIf(six.PY2, "Python 2 doesn't set extra exception attributes") def test_cached_exception_no_traceback(self): """ When a TemplateDoesNotExist instance is cached, the cached instance @@ -91,62 +79,6 @@ def test_cached_exception_no_traceback(self): self.assertIsNone(e.__context__, error_msg) self.assertIsNone(e.__cause__, error_msg) - @ignore_warnings(category=RemovedInDjango20Warning) - def test_load_template(self): - loader = self.engine.template_loaders[0] - template, origin = loader.load_template('index.html') - self.assertEqual(template.origin.template_name, 'index.html') - - cache = self.engine.template_loaders[0].template_cache - self.assertEqual(cache['index.html'][0], template) - - # Run a second time from cache - loader = self.engine.template_loaders[0] - source, name = loader.load_template('index.html') - self.assertEqual(template.origin.template_name, 'index.html') - - @ignore_warnings(category=RemovedInDjango20Warning) - def test_load_template_missing(self): - """ - #19949 -- TemplateDoesNotExist exceptions should be cached. - """ - loader = self.engine.template_loaders[0] - - self.assertNotIn('missing.html', loader.template_cache) - - with self.assertRaises(TemplateDoesNotExist): - loader.load_template("missing.html") - - self.assertEqual( - loader.template_cache["missing.html"], - TemplateDoesNotExist, - "Cached loader failed to cache the TemplateDoesNotExist exception", - ) - - @ignore_warnings(category=RemovedInDjango20Warning) - def test_load_nonexistent_cached_template(self): - loader = self.engine.template_loaders[0] - template_name = 'nonexistent.html' - - # fill the template cache - with self.assertRaises(TemplateDoesNotExist): - loader.find_template(template_name) - - with self.assertRaisesMessage(TemplateDoesNotExist, template_name): - loader.get_template(template_name) - - def test_templatedir_caching(self): - """ - #13573 -- Template directories should be part of the cache key. - """ - # Retrieve a template specifying a template directory to check - t1, name = self.engine.find_template('test.html', (os.path.join(TEMPLATE_DIR, 'first'),)) - # Now retrieve the same template name, but from a different directory - t2, name = self.engine.find_template('test.html', (os.path.join(TEMPLATE_DIR, 'second'),)) - - # The two templates should not have the same content - self.assertNotEqual(t1.render(Context({})), t2.render(Context({}))) - def test_template_name_leading_dash_caching(self): """ #26536 -- A leading dash in a template name shouldn't be stripped @@ -162,125 +94,12 @@ def test_template_name_lazy_string(self): self.assertEqual(self.engine.template_loaders[0].cache_key(lazystr('template.html'), []), 'template.html') -@unittest.skipUnless(pkg_resources, 'setuptools is not installed') -class EggLoaderTests(SimpleTestCase): - - @contextmanager - def create_egg(self, name, resources): - """ - Creates a mock egg with a list of resources. - - name: The name of the module. - resources: A dictionary of template names mapped to file-like objects. - """ - - if six.PY2: - name = name.encode('utf-8') - - class MockLoader(object): - pass - - class MockProvider(pkg_resources.NullProvider): - def __init__(self, module): - pkg_resources.NullProvider.__init__(self, module) - self.module = module - - def _has(self, path): - return path in self.module._resources - - def _isdir(self, path): - return False - - def get_resource_stream(self, manager, resource_name): - return self.module._resources[resource_name] - - def _get(self, path): - return self.module._resources[path].read() - - def _fn(self, base, resource_name): - return os.path.normcase(resource_name) - - egg = types.ModuleType(name) - egg.__loader__ = MockLoader() - egg.__path__ = ['/some/bogus/path/'] - egg.__file__ = '/some/bogus/path/__init__.pyc' - egg._resources = resources - sys.modules[name] = egg - pkg_resources._provider_factories[MockLoader] = MockProvider - - try: - yield - finally: - del sys.modules[name] - del pkg_resources._provider_factories[MockLoader] - - @classmethod - @ignore_warnings(category=RemovedInDjango20Warning) - def setUpClass(cls): - cls.engine = Engine(loaders=[ - 'django.template.loaders.eggs.Loader', - ]) - cls.loader = cls.engine.template_loaders[0] - super(EggLoaderTests, cls).setUpClass() - - def test_get_template(self): - templates = { - os.path.normcase('templates/y.html'): six.StringIO("y"), - } - - with self.create_egg('egg', templates): - with override_settings(INSTALLED_APPS=['egg']): - template = self.engine.get_template("y.html") - - self.assertEqual(template.origin.name, 'egg:egg:templates/y.html') - self.assertEqual(template.origin.template_name, 'y.html') - self.assertEqual(template.origin.loader, self.engine.template_loaders[0]) - - output = template.render(Context({})) - self.assertEqual(output, "y") - - @ignore_warnings(category=RemovedInDjango20Warning) - def test_load_template_source(self): - loader = self.engine.template_loaders[0] - templates = { - os.path.normcase('templates/y.html'): six.StringIO("y"), - } - - with self.create_egg('egg', templates): - with override_settings(INSTALLED_APPS=['egg']): - source, name = loader.load_template_source('y.html') - - self.assertEqual(source.strip(), 'y') - self.assertEqual(name, 'egg:egg:templates/y.html') - - def test_non_existing(self): - """ - Template loading fails if the template is not in the egg. - """ - with self.create_egg('egg', {}): - with override_settings(INSTALLED_APPS=['egg']): - with self.assertRaises(TemplateDoesNotExist): - self.engine.get_template('not-existing.html') - - def test_not_installed(self): - """ - Template loading fails if the egg is not in INSTALLED_APPS. - """ - templates = { - os.path.normcase('templates/y.html'): six.StringIO("y"), - } - - with self.create_egg('egg', templates): - with self.assertRaises(TemplateDoesNotExist): - self.engine.get_template('y.html') - - class FileSystemLoaderTests(SimpleTestCase): @classmethod def setUpClass(cls): cls.engine = Engine(dirs=[TEMPLATE_DIR], loaders=['django.template.loaders.filesystem.Loader']) - super(FileSystemLoaderTests, cls).setUpClass() + super().setUpClass() @contextmanager def set_dirs(self, dirs): @@ -323,13 +142,6 @@ def test_loaders_dirs_empty(self): with self.assertRaises(TemplateDoesNotExist): engine.get_template('index.html') - @ignore_warnings(category=RemovedInDjango20Warning) - def test_load_template_source(self): - loader = self.engine.template_loaders[0] - source, name = loader.load_template_source('index.html') - self.assertEqual(source.strip(), 'index') - self.assertEqual(name, os.path.join(TEMPLATE_DIR, 'index.html')) - def test_directory_security(self): with self.source_checker(['/dir1', '/dir2']) as check_sources: check_sources('index.html', ['/dir1/index.html', '/dir2/index.html']) @@ -344,24 +156,17 @@ def test_directory_security(self): def test_unicode_template_name(self): with self.source_checker(['/dir1', '/dir2']) as check_sources: - # UTF-8 bytestrings are permitted. - check_sources(b'\xc3\x85ngstr\xc3\xb6m', ['/dir1/Ångström', '/dir2/Ångström']) - # Unicode strings are permitted. check_sources('Ångström', ['/dir1/Ångström', '/dir2/Ångström']) - def test_utf8_bytestring(self): - """ - Invalid UTF-8 encoding in bytestrings should raise a useful error - """ - engine = Engine() - loader = engine.template_loaders[0] - with self.assertRaises(UnicodeDecodeError): - list(loader.get_template_sources(b'\xc3\xc3', ['/dir1'])) + def test_bytestring(self): + loader = self.engine.template_loaders[0] + msg = "Can't mix strings and bytes in path components" + with self.assertRaisesMessage(TypeError, msg): + list(loader.get_template_sources(b'\xc3\x85ngstr\xc3\xb6m')) def test_unicode_dir_name(self): - with self.source_checker([b'/Stra\xc3\x9fe']) as check_sources: + with self.source_checker(['/Straße']) as check_sources: check_sources('Ångström', ['/Straße/Ångström']) - check_sources(b'\xc3\x85ngstr\xc3\xb6m', ['/Straße/Ångström']) @unittest.skipUnless( os.path.normcase('/TEST') == os.path.normpath('/test'), @@ -401,7 +206,7 @@ def setUpClass(cls): cls.engine = Engine( loaders=['django.template.loaders.app_directories.Loader'], ) - super(AppDirectoriesLoaderTests, cls).setUpClass() + super().setUpClass() @override_settings(INSTALLED_APPS=['template_tests']) def test_get_template(self): @@ -410,14 +215,6 @@ def test_get_template(self): self.assertEqual(template.origin.template_name, 'index.html') self.assertEqual(template.origin.loader, self.engine.template_loaders[0]) - @ignore_warnings(category=RemovedInDjango20Warning) - @override_settings(INSTALLED_APPS=['template_tests']) - def test_load_template_source(self): - loader = self.engine.template_loaders[0] - source, name = loader.load_template_source('index.html') - self.assertEqual(source.strip(), 'index') - self.assertEqual(name, os.path.join(TEMPLATE_DIR, 'index.html')) - @override_settings(INSTALLED_APPS=[]) def test_not_installed(self): with self.assertRaises(TemplateDoesNotExist): @@ -433,17 +230,10 @@ def setUpClass(cls): 'index.html': 'index', })], ) - super(LocmemLoaderTests, cls).setUpClass() + super().setUpClass() def test_get_template(self): template = self.engine.get_template('index.html') self.assertEqual(template.origin.name, 'index.html') self.assertEqual(template.origin.template_name, 'index.html') self.assertEqual(template.origin.loader, self.engine.template_loaders[0]) - - @ignore_warnings(category=RemovedInDjango20Warning) - def test_load_template_source(self): - loader = self.engine.template_loaders[0] - source, name = loader.load_template_source('index.html') - self.assertEqual(source.strip(), 'index') - self.assertEqual(name, 'index.html') diff --git a/tests/template_tests/test_logging.py b/tests/template_tests/test_logging.py index a11af3c02d1f..568f5a5f1e6a 100644 --- a/tests/template_tests/test_logging.py +++ b/tests/template_tests/test_logging.py @@ -1,39 +1,14 @@ -from __future__ import unicode_literals - import logging -from django.template import Context, Engine, Variable, VariableDoesNotExist -from django.test import SimpleTestCase, ignore_warnings -from django.utils.deprecation import RemovedInDjango21Warning - - -class TestHandler(logging.Handler): - def __init__(self): - super(TestHandler, self).__init__() - self.log_record = None - - def emit(self, record): - self.log_record = record - +from django.template import Engine, Variable, VariableDoesNotExist +from django.test import SimpleTestCase -class BaseTemplateLoggingTestCase(SimpleTestCase): - def setUp(self): - self.test_handler = TestHandler() - self.logger = logging.getLogger('django.template') - self.original_level = self.logger.level - self.logger.addHandler(self.test_handler) - self.logger.setLevel(self.loglevel) - def tearDown(self): - self.logger.removeHandler(self.test_handler) - self.logger.level = self.original_level - - -class VariableResolveLoggingTests(BaseTemplateLoggingTestCase): +class VariableResolveLoggingTests(SimpleTestCase): loglevel = logging.DEBUG def test_log_on_variable_does_not_exist_silent(self): - class TestObject(object): + class TestObject: class SilentDoesNotExist(Exception): silent_variable_failure = True @@ -55,74 +30,38 @@ def __iter__(self): def __getitem__(self, item): return self.__dict__[item] - Variable('article').resolve(TestObject()) + with self.assertLogs('django.template', self.loglevel) as cm: + Variable('article').resolve(TestObject()) + self.assertEqual(len(cm.records), 1) + log_record = cm.records[0] self.assertEqual( - self.test_handler.log_record.getMessage(), + log_record.getMessage(), "Exception while resolving variable 'article' in template 'template_name'." ) - self.assertIsNotNone(self.test_handler.log_record.exc_info) - raised_exception = self.test_handler.log_record.exc_info[1] + self.assertIsNotNone(log_record.exc_info) + raised_exception = log_record.exc_info[1] self.assertEqual(str(raised_exception), 'Attribute does not exist.') def test_log_on_variable_does_not_exist_not_silent(self): - with self.assertRaises(VariableDoesNotExist): - Variable('article.author').resolve({'article': {'section': 'News'}}) + with self.assertLogs('django.template', self.loglevel) as cm: + with self.assertRaises(VariableDoesNotExist): + Variable('article.author').resolve({'article': {'section': 'News'}}) + self.assertEqual(len(cm.records), 1) + log_record = cm.records[0] self.assertEqual( - self.test_handler.log_record.getMessage(), + log_record.getMessage(), "Exception while resolving variable 'author' in template 'unknown'." ) - self.assertIsNotNone(self.test_handler.log_record.exc_info) - raised_exception = self.test_handler.log_record.exc_info[1] + self.assertIsNotNone(log_record.exc_info) + raised_exception = log_record.exc_info[1] self.assertEqual( str(raised_exception), - 'Failed lookup for key [author] in %r' % ("{%r: %r}" % ('section', 'News')) + "Failed lookup for key [author] in {'section': 'News'}" ) def test_no_log_when_variable_exists(self): - Variable('article.section').resolve({'article': {'section': 'News'}}) - self.assertIsNone(self.test_handler.log_record) - - -class IncludeNodeLoggingTests(BaseTemplateLoggingTestCase): - loglevel = logging.WARN - - @classmethod - def setUpClass(cls): - super(IncludeNodeLoggingTests, cls).setUpClass() - cls.engine = Engine(loaders=[ - ('django.template.loaders.locmem.Loader', { - 'child': '{{ raises_exception }}', - }), - ], debug=False) - - def error_method(): - raise IndexError("some generic exception") - - cls.ctx = Context({'raises_exception': error_method}) - - def test_logs_exceptions_during_rendering_with_debug_disabled(self): - template = self.engine.from_string('{% include "child" %}') - template.name = 'template_name' - with ignore_warnings(category=RemovedInDjango21Warning): - self.assertEqual(template.render(self.ctx), '') - self.assertEqual( - self.test_handler.log_record.getMessage(), - "Exception raised while rendering {% include %} for template " - "'template_name'. Empty string rendered instead." - ) - self.assertIsNotNone(self.test_handler.log_record.exc_info) - self.assertEqual(self.test_handler.log_record.levelno, logging.WARN) - - def test_logs_exceptions_during_rendering_with_no_template_name(self): - template = self.engine.from_string('{% include "child" %}') - with ignore_warnings(category=RemovedInDjango21Warning): - self.assertEqual(template.render(self.ctx), '') - self.assertEqual( - self.test_handler.log_record.getMessage(), - "Exception raised while rendering {% include %} for template " - "'unknown'. Empty string rendered instead." - ) - self.assertIsNotNone(self.test_handler.log_record.exc_info) - self.assertEqual(self.test_handler.log_record.levelno, logging.WARN) + with self.assertRaisesMessage(AssertionError, 'no logs'): + with self.assertLogs('django.template', self.loglevel): + Variable('article.section').resolve({'article': {'section': 'News'}}) diff --git a/tests/template_tests/test_nodelist.py b/tests/template_tests/test_nodelist.py index 042702e3c0d1..35f382a16370 100644 --- a/tests/template_tests/test_nodelist.py +++ b/tests/template_tests/test_nodelist.py @@ -2,7 +2,6 @@ from django.template import Context, Engine from django.template.base import TextNode, VariableNode -from django.utils import six class NodelistTest(TestCase): @@ -10,7 +9,7 @@ class NodelistTest(TestCase): @classmethod def setUpClass(cls): cls.engine = Engine() - super(NodelistTest, cls).setUpClass() + super().setUpClass() def test_for(self): template = self.engine.from_string('{% for i in 1 %}{{ a }}{% endfor %}') @@ -38,13 +37,11 @@ class TextNodeTest(TestCase): def test_textnode_repr(self): engine = Engine() for temptext, reprtext in [ - ("Hello, world!", ""), - ("One\ntwo.", ""), + ("Hello, world!", ""), + ("One\ntwo.", ""), ]: template = engine.from_string(temptext) texts = template.nodelist.get_nodes_by_type(TextNode) - if six.PY3: - reprtext = reprtext.replace("u'", "'") self.assertEqual(repr(texts[0]), reprtext) diff --git a/tests/template_tests/test_parser.py b/tests/template_tests/test_parser.py index c6800f68dc3d..2370e0263e8c 100644 --- a/tests/template_tests/test_parser.py +++ b/tests/template_tests/test_parser.py @@ -1,11 +1,9 @@ """ Testing some internals of the template processing. These are *not* examples to be copied in user code. """ -from __future__ import unicode_literals - from django.template import Library, TemplateSyntaxError from django.template.base import ( - TOKEN_BLOCK, FilterExpression, Parser, Token, Variable, + FilterExpression, Parser, Token, TokenType, Variable, ) from django.template.defaultfilters import register as filter_library from django.test import SimpleTestCase @@ -17,7 +15,7 @@ def test_token_smart_split(self): """ #7027 -- _() syntax should work with spaces """ - token = Token(TOKEN_BLOCK, 'sometag _("Page not found") value|yesno:_("yes,no")') + token = Token(TokenType.BLOCK, 'sometag _("Page not found") value|yesno:_("yes,no")') split = token.split_contents() self.assertEqual(split, ["sometag", '_("Page not found")', 'value|yesno:_("yes,no")']) @@ -42,7 +40,8 @@ def fe_test(s, val): # Filtered variables should reject access of attributes beginning with # underscores. - with self.assertRaises(TemplateSyntaxError): + msg = "Variables and attributes may not begin with underscores: 'article._hidden'" + with self.assertRaisesMessage(TemplateSyntaxError, msg): FilterExpression("article._hidden|upper", p) def test_variable_parsing(self): @@ -66,11 +65,12 @@ def test_variable_parsing(self): # Variables should reject access of attributes beginning with # underscores. - with self.assertRaises(TemplateSyntaxError): + msg = "Variables and attributes may not begin with underscores: 'article._hidden'" + with self.assertRaisesMessage(TemplateSyntaxError, msg): Variable("article._hidden") # Variables should raise on non string type - with self.assertRaisesRegex(TypeError, "Variable must be a string or number, got <(class|type) 'dict'>"): + with self.assertRaisesMessage(TypeError, "Variable must be a string or number, got "): Variable({}) def test_filter_args_count(self): diff --git a/tests/template_tests/test_response.py b/tests/template_tests/test_response.py index 60839f5c6b2e..b0d66dc8097f 100644 --- a/tests/template_tests/test_response.py +++ b/tests/template_tests/test_response.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import pickle import time from datetime import datetime @@ -11,8 +9,8 @@ from django.test import ( RequestFactory, SimpleTestCase, modify_settings, override_settings, ) -from django.test.utils import ignore_warnings, require_jinja2 -from django.utils.deprecation import MiddlewareMixin, RemovedInDjango20Warning +from django.test.utils import require_jinja2 +from django.utils.deprecation import MiddlewareMixin from .utils import TEMPLATE_DIR @@ -226,9 +224,7 @@ def test_pickling_cookie(self): }, }]) class TemplateResponseTest(SimpleTestCase): - - def setUp(self): - self.factory = RequestFactory() + factory = RequestFactory() def _response(self, template='foo', *args, **kwargs): self._request = self.factory.get('/') @@ -251,8 +247,7 @@ def test_context_processor_priority(self): self.assertEqual(response.content, b'no') def test_kwargs(self): - response = self._response(content_type='application/json', - status=504) + response = self._response(content_type='application/json', status=504) self.assertEqual(response['content-type'], 'application/json') self.assertEqual(response.status_code, 504) @@ -362,34 +357,3 @@ def test_middleware_caching(self): self.assertEqual(response2.status_code, 200) self.assertNotEqual(response.content, response2.content) - - -@ignore_warnings(category=RemovedInDjango20Warning) -@override_settings( - MIDDLEWARE=None, - MIDDLEWARE_CLASSES=[ - 'django.middleware.cache.FetchFromCacheMiddleware', - 'django.middleware.cache.UpdateCacheMiddleware', - ], - CACHE_MIDDLEWARE_SECONDS=2.0, - ROOT_URLCONF='template_tests.alternate_urls' -) -class CacheMiddlewareClassesTest(SimpleTestCase): - def test_middleware_caching(self): - response = self.client.get('/template_response_view/') - self.assertEqual(response.status_code, 200) - - time.sleep(1.0) - - response2 = self.client.get('/template_response_view/') - self.assertEqual(response2.status_code, 200) - - self.assertEqual(response.content, response2.content) - - time.sleep(2.0) - - # Let the cache expire and test again - response2 = self.client.get('/template_response_view/') - self.assertEqual(response2.status_code, 200) - - self.assertNotEqual(response.content, response2.content) diff --git a/tests/template_tests/test_unicode.py b/tests/template_tests/test_unicode.py deleted file mode 100644 index 169f15a6ed97..000000000000 --- a/tests/template_tests/test_unicode.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from unittest import TestCase - -from django.template import Context, Engine -from django.template.base import TemplateEncodingError -from django.utils import six -from django.utils.safestring import SafeData - - -class UnicodeTests(TestCase): - def test_template(self): - # Templates can be created from unicode strings. - engine = Engine() - t1 = engine.from_string('ŠĐĆŽćžšđ {{ var }}') - # Templates can also be created from bytestrings. These are assumed to - # be encoded using UTF-8. - s = b'\xc5\xa0\xc4\x90\xc4\x86\xc5\xbd\xc4\x87\xc5\xbe\xc5\xa1\xc4\x91 {{ var }}' - t2 = engine.from_string(s) - with self.assertRaises(TemplateEncodingError): - engine.from_string(b'\x80\xc5\xc0') - - # Contexts can be constructed from unicode or UTF-8 bytestrings. - Context({b"var": b"foo"}) - Context({"var": b"foo"}) - c3 = Context({b"var": "Đđ"}) - Context({"var": b"\xc4\x90\xc4\x91"}) - - # Since both templates and all four contexts represent the same thing, - # they all render the same (and are returned as unicode objects and - # "safe" objects as well, for auto-escaping purposes). - self.assertEqual(t1.render(c3), t2.render(c3)) - self.assertIsInstance(t1.render(c3), six.text_type) - self.assertIsInstance(t1.render(c3), SafeData) diff --git a/tests/template_tests/tests.py b/tests/template_tests/tests.py index 50ddee66743e..a8f089c35111 100644 --- a/tests/template_tests/tests.py +++ b/tests/template_tests/tests.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import sys from django.contrib.auth.models import Group @@ -125,6 +122,18 @@ def test_compile_tag_error_27584(self): t.render(Context()) self.assertEqual(e.exception.template_debug['during'], '{% badtag %}') + def test_compile_tag_error_27956(self): + """Errors in a child of {% extends %} are displayed correctly.""" + engine = Engine( + app_dirs=True, + debug=True, + libraries={'tag_27584': 'template_tests.templatetags.tag_27584'}, + ) + t = engine.get_template('27956_child.html') + with self.assertRaises(TemplateSyntaxError) as e: + t.render(Context()) + self.assertEqual(e.exception.template_debug['during'], '{% badtag %}') + def test_super_errors(self): """ #18169 -- NoReverseMatch should not be silence in block.super. diff --git a/tests/template_tests/urls.py b/tests/template_tests/urls.py index b0304e1c5451..c9d6900baf19 100644 --- a/tests/template_tests/urls.py +++ b/tests/template_tests/urls.py @@ -1,26 +1,23 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.conf.urls import include, url +from django.urls import include, path, re_path from . import views ns_patterns = [ # Test urls for testing reverse lookups - url(r'^$', views.index, name='index'), - url(r'^client/([0-9,]+)/$', views.client, name='client'), - url(r'^client/(?P[0-9]+)/(?P[^/]+)/$', views.client_action, name='client_action'), - url(r'^client/(?P[0-9]+)/(?P[^/]+)/$', views.client_action, name='client_action'), - url(r'^named-client/([0-9]+)/$', views.client2, name="named.client"), + path('', views.index, name='index'), + re_path(r'^client/([0-9,]+)/$', views.client, name='client'), + re_path(r'^client/(?P[0-9]+)/(?P[^/]+)/$', views.client_action, name='client_action'), + re_path(r'^client/(?P[0-9]+)/(?P[^/]+)/$', views.client_action, name='client_action'), + re_path(r'^named-client/([0-9]+)/$', views.client2, name="named.client"), ] urlpatterns = ns_patterns + [ # Unicode strings are permitted everywhere. - url(r'^Юникод/(\w+)/$', views.client2, name="метка_оператора"), - url(r'^Юникод/(?P\S+)/$', views.client2, name="метка_оператора_2"), + re_path(r'^Юникод/(\w+)/$', views.client2, name="метка_оператора"), + re_path(r'^Юникод/(?P\S+)/$', views.client2, name="метка_оператора_2"), # Test urls for namespaces and current_app - url(r'ns1/', include((ns_patterns, 'app'), 'ns1')), - url(r'ns2/', include((ns_patterns, 'app'))), + path('ns1/', include((ns_patterns, 'app'), 'ns1')), + path('ns2/', include((ns_patterns, 'app'))), ] diff --git a/tests/template_tests/utils.py b/tests/template_tests/utils.py index 1747f080bc8f..66a173396c09 100644 --- a/tests/template_tests/utils.py +++ b/tests/template_tests/utils.py @@ -1,17 +1,11 @@ -# coding: utf-8 - -from __future__ import unicode_literals - import functools import os from django.template.engine import Engine from django.test.utils import override_settings -from django.utils._os import upath -from django.utils.encoding import python_2_unicode_compatible from django.utils.safestring import mark_safe -ROOT = os.path.dirname(os.path.abspath(upath(__file__))) +ROOT = os.path.dirname(os.path.abspath(__file__)) TEMPLATE_DIR = os.path.join(ROOT, 'templates') @@ -147,7 +141,7 @@ def method(self): return 'OtherClass.method' -class TestObj(object): +class TestObj: def is_true(self): return True @@ -158,32 +152,29 @@ def is_bad(self): raise ShouldNotExecuteException() -class SilentGetItemClass(object): +class SilentGetItemClass: def __getitem__(self, key): raise SomeException -class SilentAttrClass(object): +class SilentAttrClass: def b(self): raise SomeException b = property(b) -@python_2_unicode_compatible class UTF8Class: - "Class whose __str__ returns non-ASCII data on Python 2" + "Class whose __str__ returns non-ASCII data" def __str__(self): return 'ŠĐĆŽćžšđ' -# These two classes are used to test auto-escaping of unicode output. -@python_2_unicode_compatible +# These two classes are used to test auto-escaping of string output. class UnsafeClass: def __str__(self): return 'you & me' -@python_2_unicode_compatible class SafeClass: def __str__(self): return mark_safe('you > me') diff --git a/tests/templates/form_view.html b/tests/templates/form_view.html index 1ef410fb7138..16945a018cf6 100644 --- a/tests/templates/form_view.html +++ b/tests/templates/form_view.html @@ -2,7 +2,7 @@ {% block title %}Submit data{% endblock %} {% block content %}

        {{ message }}

        -
        + {% if form.errors %}

        Please correct the errors below:

        {% endif %} diff --git a/tests/templates/login.html b/tests/templates/login.html index 0d301600a5fe..ddc3224009e6 100644 --- a/tests/templates/login.html +++ b/tests/templates/login.html @@ -1,17 +1,17 @@ {% extends "base.html" %} {% block title %}Login{% endblock %} {% block content %} -{% if form.has_errors %} +{% if form.errors %}

        Your username and password didn't match. Please try again.

        {% endif %} - +
        {{ form.username }}
        {{ form.password }}
        - - + +
        {% endblock %} diff --git a/tests/test_client/auth_backends.py b/tests/test_client/auth_backends.py index 1bb1d96eeb5b..97a2763aaab3 100644 --- a/tests/test_client/auth_backends.py +++ b/tests/test_client/auth_backends.py @@ -5,5 +5,5 @@ class TestClientBackend(ModelBackend): pass -class BackendWithoutGetUserMethod(object): +class BackendWithoutGetUserMethod: pass diff --git a/tests/test_client/test_conditional_content_removal.py b/tests/test_client/test_conditional_content_removal.py index 644eb23a066e..bd26b8fb5216 100644 --- a/tests/test_client/test_conditional_content_removal.py +++ b/tests/test_client/test_conditional_content_removal.py @@ -1,21 +1,10 @@ -from __future__ import unicode_literals - import gzip -import io from django.http import HttpRequest, HttpResponse, StreamingHttpResponse from django.test import SimpleTestCase from django.test.client import conditional_content_removal -# based on Python 3.3's gzip.compress -def gzip_compress(data): - buf = io.BytesIO() - with gzip.GzipFile(fileobj=buf, mode='wb', compresslevel=0) as f: - f.write(data) - return buf.getvalue() - - class ConditionalContentTests(SimpleTestCase): def test_conditional_content_removal(self): @@ -45,7 +34,7 @@ def test_conditional_content_removal(self): self.assertEqual(b''.join(res), b'') # Issue #20472 - abc = gzip_compress(b'abc') + abc = gzip.compress(b'abc') res = HttpResponse(abc, status=304) res['Content-Encoding'] = 'gzip' conditional_content_removal(req, res) diff --git a/tests/test_client/tests.py b/tests/test_client/tests.py index 8b834e02e057..aa4f0e94b5ef 100644 --- a/tests/test_client/tests.py +++ b/tests/test_client/tests.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Testing using the Test Client @@ -20,9 +19,9 @@ rather than the HTML rendered to the end-user. """ -from __future__ import unicode_literals - +import itertools import tempfile +from unittest import mock from django.contrib.auth.models import User from django.core import mail @@ -32,7 +31,7 @@ ) from django.urls import reverse_lazy -from .views import get_view, post_view, trace_view +from .views import TwoArgException, get_view, post_view, trace_view @override_settings(ROOT_URLCONF='test_client.urls') @@ -55,6 +54,19 @@ def test_get_view(self): self.assertEqual(response.context['var'], '\xf2') self.assertEqual(response.templates[0].name, 'GET Template') + def test_query_string_encoding(self): + # WSGI requires latin-1 encoded strings. + response = self.client.get('/get_view/?var=1\ufffd') + self.assertEqual(response.context['var'], '1\ufffd') + + def test_get_data_none(self): + msg = ( + 'Cannot encode None in a query string. Did you mean to pass an ' + 'empty string or omit the value?' + ) + with self.assertRaisesMessage(TypeError, msg): + self.client.get('/get_view/', {'value': None}) + def test_get_post_view(self): "GET a view that normally expects POSTs" response = self.client.get('/post_view/', {}) @@ -88,6 +100,53 @@ def test_post(self): self.assertEqual(response.templates[0].name, 'POST Template') self.assertContains(response, 'Data received') + def test_post_data_none(self): + msg = ( + 'Cannot encode None as POST data. Did you mean to pass an empty ' + 'string or omit the value?' + ) + with self.assertRaisesMessage(TypeError, msg): + self.client.post('/post_view/', {'value': None}) + + def test_json_serialization(self): + """The test client serializes JSON data.""" + methods = ('post', 'put', 'patch', 'delete') + tests = ( + ({'value': 37}, {'value': 37}), + ([37, True], [37, True]), + ((37, False), [37, False]), + ) + for method in methods: + with self.subTest(method=method): + for data, expected in tests: + with self.subTest(data): + client_method = getattr(self.client, method) + method_name = method.upper() + response = client_method('/json_view/', data, content_type='application/json') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context['data'], expected) + self.assertContains(response, 'Viewing %s page.' % method_name) + + def test_json_encoder_argument(self): + """The test Client accepts a json_encoder.""" + mock_encoder = mock.MagicMock() + mock_encoding = mock.MagicMock() + mock_encoder.return_value = mock_encoding + mock_encoding.encode.return_value = '{"value": 37}' + + client = self.client_class(json_encoder=mock_encoder) + # Vendored tree JSON content types are accepted. + client.post('/json_view/', {'value': 37}, content_type='application/vnd.api+json') + self.assertTrue(mock_encoder.called) + self.assertTrue(mock_encoding.encode.called) + + def test_put(self): + response = self.client.put('/put_view/', {'foo': 'bar'}) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.templates[0].name, 'PUT Template') + self.assertEqual(response.context['data'], "{'foo': 'bar'}") + self.assertEqual(response.context['Content-Length'], '14') + def test_trace(self): """TRACE a view""" response = self.client.trace('/trace_view/') @@ -142,8 +201,7 @@ def test_raw_post(self): test_doc = """ BlinkMalcolm Gladwell """ - response = self.client.post("/raw_post_view/", test_doc, - content_type="text/xml") + response = self.client.post('/raw_post_view/', test_doc, content_type='text/xml') self.assertEqual(response.status_code, 200) self.assertEqual(response.templates[0].name, "Book template") self.assertEqual(response.content, b"Blink - Malcolm Gladwell") @@ -170,6 +228,12 @@ def test_redirect_with_query(self): response = self.client.get('/redirect_view/', {'var': 'value'}) self.assertRedirects(response, '/get_view/?var=value') + def test_redirect_with_query_ordering(self): + """assertRedirects() ignores the order of query string parameters.""" + response = self.client.get('/redirect_view/', {'var': 'value', 'foo': 'bar'}) + self.assertRedirects(response, '/get_view/?var=value&foo=bar') + self.assertRedirects(response, '/get_view/?foo=bar&var=value') + def test_permanent_redirect(self): "GET a URL that redirects permanently elsewhere" response = self.client.get('/permanent_redirect_view/') @@ -205,6 +269,39 @@ def test_follow_relative_redirect_no_trailing_slash(self): self.assertEqual(response.status_code, 200) self.assertEqual(response.request['PATH_INFO'], '/accounts/login/') + def test_follow_307_and_308_redirect(self): + """ + A 307 or 308 redirect preserves the request method after the redirect. + """ + methods = ('get', 'post', 'head', 'options', 'put', 'patch', 'delete', 'trace') + codes = (307, 308) + for method, code in itertools.product(methods, codes): + with self.subTest(method=method, code=code): + req_method = getattr(self.client, method) + response = req_method('/redirect_view_%s/' % code, data={'value': 'test'}, follow=True) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.request['PATH_INFO'], '/post_view/') + self.assertEqual(response.request['REQUEST_METHOD'], method.upper()) + + def test_follow_307_and_308_preserves_post_data(self): + for code in (307, 308): + with self.subTest(code=code): + response = self.client.post('/redirect_view_%s/' % code, data={'value': 'test'}, follow=True) + self.assertContains(response, 'test is the value') + + def test_follow_307_and_308_preserves_put_body(self): + for code in (307, 308): + with self.subTest(code=code): + response = self.client.put('/redirect_view_%s/?to=/put_view/' % code, data='a=b', follow=True) + self.assertContains(response, 'a=b is the body') + + def test_follow_307_and_308_preserves_get_params(self): + data = {'var': 30, 'to': '/get_view/'} + for code in (307, 308): + with self.subTest(code=code): + response = self.client.get('/redirect_view_%s/' % code, data=data, follow=True) + self.assertContains(response, '30 is the value') + def test_redirect_http(self): "GET a URL that redirects to an http URI" response = self.client.get('/http_redirect_view/', follow=True) @@ -716,6 +813,12 @@ def test_exception_following_nested_client_request(self): with self.assertRaisesMessage(Exception, 'exception message'): self.client.get('/nesting_exception_view/') + def test_response_raises_multi_arg_exception(self): + """A request may raise an exception with more than one required arg.""" + with self.assertRaises(TwoArgException) as cm: + self.client.get('/two_arg_exception/') + self.assertEqual(cm.exception.args, ('one', 'two')) + def test_uploading_temp_file(self): with tempfile.TemporaryFile() as test_file: response = self.client.post('/upload_view/', data={'temp_file': test_file}) @@ -775,9 +878,7 @@ class RequestFactoryTest(SimpleTestCase): ('options', _generic_view), ('trace', trace_view), ) - - def setUp(self): - self.request_factory = RequestFactory() + request_factory = RequestFactory() def test_request_factory(self): """The request factory implements all the HTTP/1.1 methods.""" diff --git a/tests/test_client/urls.py b/tests/test_client/urls.py index 9f8d09218e78..61cbe005472c 100644 --- a/tests/test_client/urls.py +++ b/tests/test_client/urls.py @@ -1,42 +1,47 @@ -from django.conf.urls import url from django.contrib.auth import views as auth_views +from django.urls import path from django.views.generic import RedirectView from . import views urlpatterns = [ - url(r'^upload_view/$', views.upload_view, name='upload_view'), - url(r'^get_view/$', views.get_view, name='get_view'), - url(r'^post_view/$', views.post_view), - url(r'^trace_view/$', views.trace_view), - url(r'^header_view/$', views.view_with_header), - url(r'^raw_post_view/$', views.raw_post_view), - url(r'^redirect_view/$', views.redirect_view), - url(r'^secure_view/$', views.view_with_secure), - url(r'^permanent_redirect_view/$', RedirectView.as_view(url='/get_view/', permanent=True)), - url(r'^temporary_redirect_view/$', RedirectView.as_view(url='/get_view/', permanent=False)), - url(r'^http_redirect_view/$', RedirectView.as_view(url='/secure_view/')), - url(r'^https_redirect_view/$', RedirectView.as_view(url='https://testserver/secure_view/')), - url(r'^double_redirect_view/$', views.double_redirect_view), - url(r'^bad_view/$', views.bad_view), - url(r'^form_view/$', views.form_view), - url(r'^form_view_with_template/$', views.form_view_with_template), - url(r'^formset_view/$', views.formset_view), - url(r'^login_protected_view/$', views.login_protected_view), - url(r'^login_protected_method_view/$', views.login_protected_method_view), - url(r'^login_protected_view_custom_redirect/$', views.login_protected_view_changed_redirect), - url(r'^permission_protected_view/$', views.permission_protected_view), - url(r'^permission_protected_view_exception/$', views.permission_protected_view_exception), - url(r'^permission_protected_method_view/$', views.permission_protected_method_view), - url(r'^session_view/$', views.session_view), - url(r'^broken_view/$', views.broken_view), - url(r'^mail_sending_view/$', views.mail_sending_view), - url(r'^mass_mail_sending_view/$', views.mass_mail_sending_view), - url(r'^nesting_exception_view/$', views.nesting_exception_view), - url(r'^django_project_redirect/$', views.django_project_redirect), + path('upload_view/', views.upload_view, name='upload_view'), + path('get_view/', views.get_view, name='get_view'), + path('post_view/', views.post_view), + path('put_view/', views.put_view), + path('trace_view/', views.trace_view), + path('header_view/', views.view_with_header), + path('raw_post_view/', views.raw_post_view), + path('redirect_view/', views.redirect_view), + path('redirect_view_307/', views.method_saving_307_redirect_view), + path('redirect_view_308/', views.method_saving_308_redirect_view), + path('secure_view/', views.view_with_secure), + path('permanent_redirect_view/', RedirectView.as_view(url='/get_view/', permanent=True)), + path('temporary_redirect_view/', RedirectView.as_view(url='/get_view/', permanent=False)), + path('http_redirect_view/', RedirectView.as_view(url='/secure_view/')), + path('https_redirect_view/', RedirectView.as_view(url='https://testserver/secure_view/')), + path('double_redirect_view/', views.double_redirect_view), + path('bad_view/', views.bad_view), + path('form_view/', views.form_view), + path('form_view_with_template/', views.form_view_with_template), + path('formset_view/', views.formset_view), + path('json_view/', views.json_view), + path('login_protected_view/', views.login_protected_view), + path('login_protected_method_view/', views.login_protected_method_view), + path('login_protected_view_custom_redirect/', views.login_protected_view_changed_redirect), + path('permission_protected_view/', views.permission_protected_view), + path('permission_protected_view_exception/', views.permission_protected_view_exception), + path('permission_protected_method_view/', views.permission_protected_method_view), + path('session_view/', views.session_view), + path('broken_view/', views.broken_view), + path('mail_sending_view/', views.mail_sending_view), + path('mass_mail_sending_view/', views.mass_mail_sending_view), + path('nesting_exception_view/', views.nesting_exception_view), + path('django_project_redirect/', views.django_project_redirect), + path('two_arg_exception/', views.two_arg_exception), - url(r'^accounts/$', RedirectView.as_view(url='login/')), - url(r'^accounts/no_trailing_slash$', RedirectView.as_view(url='login/')), - url(r'^accounts/login/$', auth_views.LoginView.as_view(template_name='login.html')), - url(r'^accounts/logout/$', auth_views.LogoutView.as_view()), + path('accounts/', RedirectView.as_view(url='login/')), + path('accounts/no_trailing_slash', RedirectView.as_view(url='login/')), + path('accounts/login/', auth_views.LoginView.as_view(template_name='login.html')), + path('accounts/logout/', auth_views.LogoutView.as_view()), ] diff --git a/tests/test_client/views.py b/tests/test_client/views.py index af30c4283d7e..2d076fafaf2e 100644 --- a/tests/test_client/views.py +++ b/tests/test_client/views.py @@ -1,3 +1,5 @@ +import json +from urllib.parse import urlencode from xml.dom.minidom import parseString from django.contrib.auth.decorators import login_required, permission_required @@ -13,7 +15,6 @@ from django.template import Context, Template from django.test import Client from django.utils.decorators import method_decorator -from django.utils.six.moves.urllib.parse import urlencode def get_view(request): @@ -49,6 +50,19 @@ def trace_view(request): return HttpResponse(t.render(c)) +def put_view(request): + if request.method == 'PUT': + t = Template('Data received: {{ data }} is the body.', name='PUT Template') + c = Context({ + 'Content-Length': request.META['CONTENT_LENGTH'], + 'data': request.body.decode(), + }) + else: + t = Template('Viewing GET page.', name='Empty GET Template') + c = Context() + return HttpResponse(t.render(c)) + + def post_view(request): """A view that expects a POST, and returns a different template depending on whether any POST data is available @@ -63,7 +77,20 @@ def post_view(request): else: t = Template('Viewing GET page.', name='Empty GET Template') c = Context() + return HttpResponse(t.render(c)) + +def json_view(request): + """ + A view that expects a request with the header 'application/json' and JSON + data, which is deserialized and included in the context. + """ + if request.META.get('CONTENT_TYPE') != 'application/json': + return HttpResponse() + + t = Template('Viewing {} page. With data {{ data }}.'.format(request.method)) + data = json.loads(request.body.decode('utf-8')) + c = Context({'data': data}) return HttpResponse(t.render(c)) @@ -99,6 +126,20 @@ def redirect_view(request): return HttpResponseRedirect('/get_view/' + query) +def _post_view_redirect(request, status_code): + """Redirect to /post_view/ using the status code.""" + redirect_to = request.GET.get('to', '/post_view/') + return HttpResponseRedirect(redirect_to, status=status_code) + + +def method_saving_307_redirect_view(request): + return _post_view_redirect(request, 307) + + +def method_saving_308_redirect_view(request): + return _post_view_redirect(request, 308) + + def view_with_secure(request): "A view that indicates if the request was secure" response = HttpResponse() @@ -248,7 +289,7 @@ def _permission_protected_view(request): ) -class _ViewManager(object): +class _ViewManager: @method_decorator(login_required) def login_protected_view(self, request): t = Template('This is a login protected test using a method. ' @@ -330,4 +371,13 @@ def django_project_redirect(request): def upload_view(request): """Prints keys of request.FILES to the response.""" - return HttpResponse(', '.join(request.FILES.keys())) + return HttpResponse(', '.join(request.FILES)) + + +class TwoArgException(Exception): + def __init__(self, one, two): + pass + + +def two_arg_exception(request): + raise TwoArgException('one', 'two') diff --git a/tests/test_client_regress/session.py b/tests/test_client_regress/session.py index 461f66b624e5..ab374d37f043 100644 --- a/tests/test_client_regress/session.py +++ b/tests/test_client_regress/session.py @@ -9,7 +9,7 @@ class SessionStore(SessionBase): This means that saving the session will change the session key. """ def __init__(self, session_key=None): - super(SessionStore, self).__init__(session_key) + super().__init__(session_key) def exists(self, session_key): return False diff --git a/tests/test_client_regress/tests.py b/tests/test_client_regress/tests.py index 1e30017d5ad9..568317ec26a0 100644 --- a/tests/test_client_regress/tests.py +++ b/tests/test_client_regress/tests.py @@ -1,9 +1,6 @@ -# -*- coding: utf-8 -*- """ Regression tests for the Test Client, especially the customized assertions. """ -from __future__ import unicode_literals - import itertools import os @@ -15,21 +12,18 @@ ) from django.template.response import SimpleTemplateResponse from django.test import ( - Client, SimpleTestCase, TestCase, ignore_warnings, modify_settings, - override_settings, + Client, SimpleTestCase, TestCase, modify_settings, override_settings, ) from django.test.client import RedirectCycleError, RequestFactory, encode_file -from django.test.utils import ContextList, str_prefix +from django.test.utils import ContextList from django.urls import NoReverseMatch, reverse -from django.utils._os import upath -from django.utils.deprecation import RemovedInDjango20Warning -from django.utils.translation import ugettext_lazy +from django.utils.translation import gettext_lazy from .models import CustomUser from .views import CustomTestException -class TestDataMixin(object): +class TestDataMixin: @classmethod def setUpTestData(cls): @@ -137,14 +131,14 @@ def test_unicode_contains(self): # Regression test for #10183 r = self.client.get('/check_unicode/') self.assertContains(r, 'さかき') - self.assertContains(r, b'\xe5\xb3\xa0'.decode('utf-8')) + self.assertContains(r, b'\xe5\xb3\xa0'.decode()) def test_unicode_not_contains(self): "Unicode characters can be searched for, and not found in template context" # Regression test for #10183 r = self.client.get('/check_unicode/') self.assertNotContains(r, 'はたけ') - self.assertNotContains(r, b'\xe3\x81\xaf\xe3\x81\x9f\xe3\x81\x91'.decode('utf-8')) + self.assertNotContains(r, b'\xe3\x81\xaf\xe3\x81\x9f\xe3\x81\x91'.decode()) def test_binary_contains(self): r = self.client.get('/check_binary/') @@ -160,11 +154,11 @@ def test_binary_not_contains(self): def test_nontext_contains(self): r = self.client.get('/no_template_view/') - self.assertContains(r, ugettext_lazy('once')) + self.assertContains(r, gettext_lazy('once')) def test_nontext_not_contains(self): r = self.client.get('/no_template_view/') - self.assertNotContains(r, ugettext_lazy('never')) + self.assertNotContains(r, gettext_lazy('never')) def test_assert_contains_renders_template_response(self): """ @@ -397,7 +391,7 @@ def test_multiple_redirect_chain(self): self.assertEqual(response.redirect_chain[2], ('/no_template_view/', 302)) def test_redirect_chain_to_non_existent(self): - "You can follow a chain to a non-existent view" + "You can follow a chain to a nonexistent view." response = self.client.get('/redirect_to_non_existent_view2/', {}, follow=True) self.assertRedirects(response, '/non_existent_view/', status_code=302, target_status_code=404) @@ -514,15 +508,6 @@ def test_redirect_scheme(self): with self.assertRaises(AssertionError): self.assertRedirects(response, 'http://testserver/secure_view/', status_code=302) - @ignore_warnings(category=RemovedInDjango20Warning) - def test_full_path_in_expected_urls(self): - """ - Specifying a full URL as assertRedirects expected_url still - work as backwards compatible behavior until Django 2.0. - """ - response = self.client.get('/redirect_view/') - self.assertRedirects(response, 'http://testserver/get_view/') - @override_settings(ROOT_URLCONF='test_client_regress.urls') class AssertFormErrorTests(SimpleTestCase): @@ -610,21 +595,19 @@ def test_unknown_error(self): self.assertFormError(response, 'form', 'email', 'Some error.') except AssertionError as e: self.assertIn( - str_prefix( - "The field 'email' on form 'form' in context 0 does not " - "contain the error 'Some error.' (actual errors: " - "[%(_)s'Enter a valid email address.'])" - ), str(e) + "The field 'email' on form 'form' in context 0 does not " + "contain the error 'Some error.' (actual errors: " + "['Enter a valid email address.'])", + str(e) ) try: self.assertFormError(response, 'form', 'email', 'Some error.', msg_prefix='abc') except AssertionError as e: self.assertIn( - str_prefix( - "abc: The field 'email' on form 'form' in context 0 does " - "not contain the error 'Some error.' (actual errors: " - "[%(_)s'Enter a valid email address.'])", - ), str(e) + "abc: The field 'email' on form 'form' in context 0 does " + "not contain the error 'Some error.' (actual errors: " + "['Enter a valid email address.'])", + str(e) ) def test_unknown_nonfield_error(self): @@ -648,7 +631,7 @@ def test_unknown_nonfield_error(self): except AssertionError as e: self.assertIn( "The form 'form' in context 0 does not contain the non-field " - "error 'Some error.' (actual errors: )", + "error 'Some error.' (actual errors: none)", str(e) ) try: @@ -656,7 +639,7 @@ def test_unknown_nonfield_error(self): except AssertionError as e: self.assertIn( "abc: The form 'form' in context 0 does not contain the " - "non-field error 'Some error.' (actual errors: )", + "non-field error 'Some error.' (actual errors: none)", str(e) ) @@ -734,10 +717,10 @@ def test_no_error_field(self): def test_unknown_error(self): "An assertion is raised if the field doesn't contain the specified error" for prefix, kwargs in self.msg_prefixes: - msg = str_prefix( - prefix + "The field 'email' on formset 'my_formset', form 0 " + msg = prefix + ( + "The field 'email' on formset 'my_formset', form 0 " "in context 0 does not contain the error 'Some error.' " - "(actual errors: [%(_)s'Enter a valid email address.'])" + "(actual errors: ['Enter a valid email address.'])" ) with self.assertRaisesMessage(AssertionError, msg): self.assertFormsetError(self.response_form_errors, 'my_formset', 0, 'email', 'Some error.', **kwargs) @@ -758,10 +741,10 @@ def test_no_nonfield_error(self): def test_unknown_nonfield_error(self): "An assertion is raised if the formsets non-field errors doesn't contain the provided error." for prefix, kwargs in self.msg_prefixes: - msg = str_prefix( - prefix + "The formset 'my_formset', form 0 in context 0 does not " + msg = prefix + ( + "The formset 'my_formset', form 0 in context 0 does not " "contain the non-field error 'Some error.' (actual errors: " - "[%(_)s'Non-field error.'])" + "['Non-field error.'])" ) with self.assertRaisesMessage(AssertionError, msg): self.assertFormsetError(self.response_form_errors, 'my_formset', 0, None, 'Some error.', **kwargs) @@ -781,11 +764,10 @@ def test_no_nonform_error(self): def test_unknown_nonform_error(self): "An assertion is raised if the formsets non-form errors doesn't contain the provided error." for prefix, kwargs in self.msg_prefixes: - msg = str_prefix( - prefix + + msg = prefix + ( "The formset 'my_formset' in context 0 does not contain the " - "non-form error 'Some error.' (actual errors: [%(_)s'Forms " - "in a set must have distinct email addresses.'])" + "non-form error 'Some error.' (actual errors: ['Forms in a set " + "must have distinct email addresses.'])" ) with self.assertRaisesMessage(AssertionError, msg): self.assertFormsetError( @@ -888,7 +870,7 @@ class TemplateExceptionTests(SimpleTestCase): @override_settings(TEMPLATES=[{ 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [os.path.join(os.path.dirname(upath(__file__)), 'bad_templates')], + 'DIRS': [os.path.join(os.path.dirname(__file__), 'bad_templates')], }]) def test_bad_404_template(self): "Errors found when rendering 404 error templates are re-raised" @@ -1214,12 +1196,23 @@ def test_empty_string_data(self): response = self.client.head('/body/', data='', content_type='application/json') self.assertEqual(response.content, b'') + def test_json_bytes(self): + response = self.client.post('/body/', data=b"{'value': 37}", content_type='application/json') + self.assertEqual(response.content, b"{'value': 37}") + def test_json(self): response = self.client.get('/json_response/') self.assertEqual(response.json(), {'key': 'value'}) - def test_json_vendor(self): - for content_type in ('application/vnd.api+json', 'application/vnd.api.foo+json'): + def test_json_structured_suffixes(self): + valid_types = ( + 'application/vnd.api+json', + 'application/vnd.api.foo+json', + 'application/json; charset=utf-8', + 'application/activity+json', + 'application/activity+json; charset=utf-8', + ) + for content_type in valid_types: response = self.client.get('/json_response/', {'content_type': content_type}) self.assertEqual(response['Content-Type'], content_type) self.assertEqual(response.json(), {'key': 'value'}) @@ -1281,38 +1274,35 @@ def test_post_like_requests(self): @override_settings(ROOT_URLCONF='test_client_regress.urls') -class UnicodePayloadTests(SimpleTestCase): - - def test_simple_unicode_payload(self): - "A simple ASCII-only unicode JSON document can be POSTed" - # Regression test for #10571 - json = '{"english": "mountain pass"}' - response = self.client.post("/parse_unicode_json/", json, content_type="application/json") - self.assertEqual(response.content, json.encode()) - - def test_unicode_payload_utf8(self): - "A non-ASCII unicode data encoded as UTF-8 can be POSTed" - # Regression test for #10571 - json = '{"dog": "собака"}' - response = self.client.post("/parse_unicode_json/", json, content_type="application/json; charset=utf-8") - self.assertEqual(response.content, json.encode('utf-8')) - - def test_unicode_payload_utf16(self): - "A non-ASCII unicode data encoded as UTF-16 can be POSTed" - # Regression test for #10571 - json = '{"dog": "собака"}' - response = self.client.post("/parse_unicode_json/", json, content_type="application/json; charset=utf-16") - self.assertEqual(response.content, json.encode('utf-16')) - - def test_unicode_payload_non_utf(self): - "A non-ASCII unicode data as a non-UTF based encoding can be POSTed" - # Regression test for #10571 - json = '{"dog": "собака"}' - response = self.client.post("/parse_unicode_json/", json, content_type="application/json; charset=koi8-r") - self.assertEqual(response.content, json.encode('koi8-r')) - - -class DummyFile(object): +class PayloadEncodingTests(SimpleTestCase): + """Regression tests for #10571.""" + + def test_simple_payload(self): + """A simple ASCII-only text can be POSTed.""" + text = 'English: mountain pass' + response = self.client.post('/parse_encoded_text/', text, content_type='text/plain') + self.assertEqual(response.content, text.encode()) + + def test_utf8_payload(self): + """Non-ASCII data encoded as UTF-8 can be POSTed.""" + text = 'dog: собака' + response = self.client.post('/parse_encoded_text/', text, content_type='text/plain; charset=utf-8') + self.assertEqual(response.content, text.encode()) + + def test_utf16_payload(self): + """Non-ASCII data encoded as UTF-16 can be POSTed.""" + text = 'dog: собака' + response = self.client.post('/parse_encoded_text/', text, content_type='text/plain; charset=utf-16') + self.assertEqual(response.content, text.encode('utf-16')) + + def test_non_utf_payload(self): + """Non-ASCII data as a non-UTF based encoding can be POSTed.""" + text = 'dog: собака' + response = self.client.post('/parse_encoded_text/', text, content_type='text/plain; charset=koi8-r') + self.assertEqual(response.content, text.encode('koi8-r')) + + +class DummyFile: def __init__(self, filename): self.name = filename @@ -1435,3 +1425,9 @@ def test_should_set_correct_env_variables(self): self.assertEqual(request.META.get('SERVER_PORT'), '80') self.assertEqual(request.META.get('SERVER_PROTOCOL'), 'HTTP/1.1') self.assertEqual(request.META.get('SCRIPT_NAME') + request.META.get('PATH_INFO'), '/path/') + + def test_cookies(self): + factory = RequestFactory() + factory.cookies.load('A="B"; C="D"; Path=/; Version=1') + request = factory.get('/') + self.assertEqual(request.META['HTTP_COOKIE'], 'A="B"; C="D"') diff --git a/tests/test_client_regress/urls.py b/tests/test_client_regress/urls.py index 9821ea910d70..18c037bc2398 100644 --- a/tests/test_client_regress/urls.py +++ b/tests/test_client_regress/urls.py @@ -1,42 +1,42 @@ -from django.conf.urls import include, url +from django.urls import include, path from django.views.generic import RedirectView from . import views urlpatterns = [ - url(r'', include('test_client.urls')), + path('', include('test_client.urls')), - url(r'^no_template_view/$', views.no_template_view), - url(r'^staff_only/$', views.staff_only_view), - url(r'^get_view/$', views.get_view), - url(r'^request_data/$', views.request_data), - url(r'^request_data_extended/$', views.request_data, {'template': 'extended.html', 'data': 'bacon'}), - url(r'^arg_view/(?P.+)/$', views.view_with_argument, name='arg_view'), - url(r'^nested_view/$', views.nested_view, name='nested_view'), - url(r'^login_protected_redirect_view/$', views.login_protected_redirect_view), - url(r'^redirects/$', RedirectView.as_view(url='/redirects/further/')), - url(r'^redirects/further/$', RedirectView.as_view(url='/redirects/further/more/')), - url(r'^redirects/further/more/$', RedirectView.as_view(url='/no_template_view/')), - url(r'^redirect_to_non_existent_view/$', RedirectView.as_view(url='/non_existent_view/')), - url(r'^redirect_to_non_existent_view2/$', RedirectView.as_view(url='/redirect_to_non_existent_view/')), - url(r'^redirect_to_self/$', RedirectView.as_view(url='/redirect_to_self/')), - url(r'^redirect_to_self_with_changing_query_view/$', views.redirect_to_self_with_changing_query_view), - url(r'^circular_redirect_1/$', RedirectView.as_view(url='/circular_redirect_2/')), - url(r'^circular_redirect_2/$', RedirectView.as_view(url='/circular_redirect_3/')), - url(r'^circular_redirect_3/$', RedirectView.as_view(url='/circular_redirect_1/')), - url(r'^redirect_other_host/$', RedirectView.as_view(url='https://otherserver:8443/no_template_view/')), - url(r'^set_session/$', views.set_session_view), - url(r'^check_session/$', views.check_session_view), - url(r'^request_methods/$', views.request_methods_view), - url(r'^check_unicode/$', views.return_unicode), - url(r'^check_binary/$', views.return_undecodable_binary), - url(r'^json_response/$', views.return_json_response), - url(r'^parse_unicode_json/$', views.return_json_file), - url(r'^check_headers/$', views.check_headers), - url(r'^check_headers_redirect/$', RedirectView.as_view(url='/check_headers/')), - url(r'^body/$', views.body), - url(r'^read_all/$', views.read_all), - url(r'^read_buffer/$', views.read_buffer), - url(r'^request_context_view/$', views.request_context_view), - url(r'^render_template_multiple_times/$', views.render_template_multiple_times), + path('no_template_view/', views.no_template_view), + path('staff_only/', views.staff_only_view), + path('get_view/', views.get_view), + path('request_data/', views.request_data), + path('request_data_extended/', views.request_data, {'template': 'extended.html', 'data': 'bacon'}), + path('arg_view//', views.view_with_argument, name='arg_view'), + path('nested_view/', views.nested_view, name='nested_view'), + path('login_protected_redirect_view/', views.login_protected_redirect_view), + path('redirects/', RedirectView.as_view(url='/redirects/further/')), + path('redirects/further/', RedirectView.as_view(url='/redirects/further/more/')), + path('redirects/further/more/', RedirectView.as_view(url='/no_template_view/')), + path('redirect_to_non_existent_view/', RedirectView.as_view(url='/non_existent_view/')), + path('redirect_to_non_existent_view2/', RedirectView.as_view(url='/redirect_to_non_existent_view/')), + path('redirect_to_self/', RedirectView.as_view(url='/redirect_to_self/')), + path('redirect_to_self_with_changing_query_view/', views.redirect_to_self_with_changing_query_view), + path('circular_redirect_1/', RedirectView.as_view(url='/circular_redirect_2/')), + path('circular_redirect_2/', RedirectView.as_view(url='/circular_redirect_3/')), + path('circular_redirect_3/', RedirectView.as_view(url='/circular_redirect_1/')), + path('redirect_other_host/', RedirectView.as_view(url='https://otherserver:8443/no_template_view/')), + path('set_session/', views.set_session_view), + path('check_session/', views.check_session_view), + path('request_methods/', views.request_methods_view), + path('check_unicode/', views.return_unicode), + path('check_binary/', views.return_undecodable_binary), + path('json_response/', views.return_json_response), + path('parse_encoded_text/', views.return_text_file), + path('check_headers/', views.check_headers), + path('check_headers_redirect/', RedirectView.as_view(url='/check_headers/')), + path('body/', views.body), + path('read_all/', views.read_all), + path('read_buffer/', views.read_buffer), + path('request_context_view/', views.request_context_view), + path('render_template_multiple_times/', views.render_template_multiple_times), ] diff --git a/tests/test_client_regress/views.py b/tests/test_client_regress/views.py index ce60c0398c8d..5e4c9968fd7a 100644 --- a/tests/test_client_regress/views.py +++ b/tests/test_client_regress/views.py @@ -1,14 +1,12 @@ -import json +from urllib.parse import urlencode from django.conf import settings from django.contrib.auth.decorators import login_required -from django.core.serializers.json import DjangoJSONEncoder from django.http import HttpResponse, HttpResponseRedirect, JsonResponse from django.shortcuts import render from django.template.loader import render_to_string from django.test import Client from django.test.client import CONTENT_TYPE_RE -from django.utils.six.moves.urllib.parse import urlencode class CustomTestException(Exception): @@ -111,20 +109,15 @@ def return_json_response(request): return JsonResponse({'key': 'value'}, **kwargs) -def return_json_file(request): - "A view that parses and returns a JSON string as a file." +def return_text_file(request): + "A view that parses and returns text as a file." match = CONTENT_TYPE_RE.match(request.META['CONTENT_TYPE']) if match: charset = match.group(1) else: charset = settings.DEFAULT_CHARSET - # This just checks that the uploaded data is JSON - obj_dict = json.loads(request.body.decode(charset)) - obj_json = json.dumps(obj_dict, cls=DjangoJSONEncoder, ensure_ascii=False) - response = HttpResponse(obj_json.encode(charset), status=200, - content_type='application/json; charset=%s' % charset) - response['Content-Disposition'] = 'attachment; filename=testfile.json' + response = HttpResponse(request.body, status=200, content_type='text/plain; charset=%s' % charset) return response diff --git a/tests/test_runner/models.py b/tests/test_runner/models.py index 20cb384b037e..9b26dbfc1e94 100644 --- a/tests/test_runner/models.py +++ b/tests/test_runner/models.py @@ -4,3 +4,18 @@ class Person(models.Model): first_name = models.CharField(max_length=20) last_name = models.CharField(max_length=20) + friends = models.ManyToManyField('self') + + +# A set of models that use a non-abstract inherited 'through' model. +class ThroughBase(models.Model): + person = models.ForeignKey(Person, models.CASCADE) + b = models.ForeignKey('B', models.CASCADE) + + +class Through(ThroughBase): + extra = models.CharField(max_length=20) + + +class B(models.Model): + people = models.ManyToManyField(Person, through=Through) diff --git a/tests/test_runner/runner.py b/tests/test_runner/runner.py index e61de58a93ab..a2c9ede8971f 100644 --- a/tests/test_runner/runner.py +++ b/tests/test_runner/runner.py @@ -5,18 +5,16 @@ class CustomOptionsTestRunner(DiscoverRunner): def __init__(self, verbosity=1, interactive=True, failfast=True, option_a=None, option_b=None, option_c=None, **kwargs): - super(CustomOptionsTestRunner, self).__init__( - verbosity=verbosity, interactive=interactive, failfast=failfast, - ) + super().__init__(verbosity=verbosity, interactive=interactive, failfast=failfast) self.option_a = option_a self.option_b = option_b self.option_c = option_c @classmethod def add_arguments(cls, parser): - parser.add_argument('--option_a', '-a', action='store', dest='option_a', default='1'), - parser.add_argument('--option_b', '-b', action='store', dest='option_b', default='2'), - parser.add_argument('--option_c', '-c', action='store', dest='option_c', default='3'), + parser.add_argument('--option_a', '-a', default='1'), + parser.add_argument('--option_b', '-b', default='2'), + parser.add_argument('--option_c', '-c', default='3'), def run_tests(self, test_labels, extra_tests=None, **kwargs): print("%s:%s:%s" % (self.option_a, self.option_b, self.option_c)) diff --git a/tests/test_runner/test_debug_sql.py b/tests/test_runner/test_debug_sql.py index e23006610093..8c4cb6fef682 100644 --- a/tests/test_runner/test_debug_sql.py +++ b/tests/test_runner/test_debug_sql.py @@ -1,11 +1,9 @@ -import sys import unittest +from io import StringIO from django.db import connection from django.test import TestCase from django.test.runner import DiscoverRunner -from django.utils import six -from django.utils.encoding import force_text from .models import Person @@ -27,14 +25,34 @@ def runTest(self): Person.objects.filter(first_name='error').count() raise Exception + class PassingSubTest(TestCase): + def runTest(self): + with self.subTest(): + Person.objects.filter(first_name='subtest-pass').count() + + class FailingSubTest(TestCase): + def runTest(self): + with self.subTest(): + Person.objects.filter(first_name='subtest-fail').count() + self.fail() + + class ErrorSubTest(TestCase): + def runTest(self): + with self.subTest(): + Person.objects.filter(first_name='subtest-error').count() + raise Exception + def _test_output(self, verbosity): runner = DiscoverRunner(debug_sql=True, verbosity=0) suite = runner.test_suite() suite.addTest(self.FailingTest()) suite.addTest(self.ErrorTest()) suite.addTest(self.PassingTest()) + suite.addTest(self.PassingSubTest()) + suite.addTest(self.FailingSubTest()) + suite.addTest(self.ErrorSubTest()) old_config = runner.setup_databases() - stream = six.StringIO() + stream = StringIO() resultclass = runner.get_resultclass() runner.test_runner( verbosity=verbosity, @@ -43,8 +61,6 @@ def _test_output(self, verbosity): ).run(suite) runner.teardown_databases(old_config) - if six.PY2: - stream.buflist = [force_text(x) for x in stream.buflist] return stream.getvalue() def test_output_normal(self): @@ -68,17 +84,26 @@ def test_output_verbose(self): ('''SELECT COUNT(*) AS "__count" ''' '''FROM "test_runner_person" WHERE ''' '''"test_runner_person"."first_name" = 'fail';'''), + ('''SELECT COUNT(*) AS "__count" ''' + '''FROM "test_runner_person" WHERE ''' + '''"test_runner_person"."first_name" = 'subtest-error';'''), + ('''SELECT COUNT(*) AS "__count" ''' + '''FROM "test_runner_person" WHERE ''' + '''"test_runner_person"."first_name" = 'subtest-fail';'''), ] verbose_expected_outputs = [ - # Output format changed in Python 3.5+ - x.format('' if sys.version_info < (3, 5) else 'TestDebugSQL.') for x in [ - 'runTest (test_runner.test_debug_sql.{}FailingTest) ... FAIL', - 'runTest (test_runner.test_debug_sql.{}ErrorTest) ... ERROR', - 'runTest (test_runner.test_debug_sql.{}PassingTest) ... ok', - ] - ] + [ + 'runTest (test_runner.test_debug_sql.TestDebugSQL.FailingTest) ... FAIL', + 'runTest (test_runner.test_debug_sql.TestDebugSQL.ErrorTest) ... ERROR', + 'runTest (test_runner.test_debug_sql.TestDebugSQL.PassingTest) ... ok', + # If there are errors/failures in subtests but not in test itself, + # the status is not written. That behavior comes from Python. + 'runTest (test_runner.test_debug_sql.TestDebugSQL.FailingSubTest) ...', + 'runTest (test_runner.test_debug_sql.TestDebugSQL.ErrorSubTest) ...', ('''SELECT COUNT(*) AS "__count" ''' '''FROM "test_runner_person" WHERE ''' '''"test_runner_person"."first_name" = 'pass';'''), + ('''SELECT COUNT(*) AS "__count" ''' + '''FROM "test_runner_person" WHERE ''' + '''"test_runner_person"."first_name" = 'subtest-pass';'''), ] diff --git a/tests/test_runner/test_discover_runner.py b/tests/test_runner/test_discover_runner.py index 3ea563d1e680..caa48a852dde 100644 --- a/tests/test_runner/test_discover_runner.py +++ b/tests/test_runner/test_discover_runner.py @@ -3,8 +3,10 @@ from contextlib import contextmanager from unittest import TestSuite, TextTestRunner, defaultTestLoader -from django.test import TestCase +from django.db import connections +from django.test import SimpleTestCase from django.test.runner import DiscoverRunner +from django.test.utils import captured_stdout @contextmanager @@ -19,7 +21,7 @@ def change_cwd(directory): os.chdir(old_cwd) -class DiscoverRunnerTest(TestCase): +class DiscoverRunnerTests(SimpleTestCase): def test_init_debug_mode(self): runner = DiscoverRunner() @@ -36,28 +38,28 @@ def test_add_arguments_debug_mode(self): def test_dotted_test_module(self): count = DiscoverRunner().build_suite( - ["test_discovery_sample.tests_sample"], + ['test_runner_apps.sample.tests_sample'], ).countTestCases() - self.assertEqual(count, 6) + self.assertEqual(count, 4) def test_dotted_test_class_vanilla_unittest(self): count = DiscoverRunner().build_suite( - ["test_discovery_sample.tests_sample.TestVanillaUnittest"], + ['test_runner_apps.sample.tests_sample.TestVanillaUnittest'], ).countTestCases() self.assertEqual(count, 1) def test_dotted_test_class_django_testcase(self): count = DiscoverRunner().build_suite( - ["test_discovery_sample.tests_sample.TestDjangoTestCase"], + ['test_runner_apps.sample.tests_sample.TestDjangoTestCase'], ).countTestCases() self.assertEqual(count, 1) def test_dotted_test_method_django_testcase(self): count = DiscoverRunner().build_suite( - ["test_discovery_sample.tests_sample.TestDjangoTestCase.test_sample"], + ['test_runner_apps.sample.tests_sample.TestDjangoTestCase.test_sample'], ).countTestCases() self.assertEqual(count, 1) @@ -65,17 +67,17 @@ def test_dotted_test_method_django_testcase(self): def test_pattern(self): count = DiscoverRunner( pattern="*_tests.py", - ).build_suite(["test_discovery_sample"]).countTestCases() + ).build_suite(['test_runner_apps.sample']).countTestCases() self.assertEqual(count, 1) def test_file_path(self): with change_cwd(".."): count = DiscoverRunner().build_suite( - ["test_discovery_sample/"], + ['test_runner_apps/sample/'], ).countTestCases() - self.assertEqual(count, 7) + self.assertEqual(count, 5) def test_empty_label(self): """ @@ -91,14 +93,14 @@ def test_empty_label(self): def test_empty_test_case(self): count = DiscoverRunner().build_suite( - ["test_discovery_sample.tests_sample.EmptyTestCase"], + ['test_runner_apps.sample.tests_sample.EmptyTestCase'], ).countTestCases() self.assertEqual(count, 0) def test_discovery_on_package(self): count = DiscoverRunner().build_suite( - ["test_discovery_sample.tests"], + ['test_runner_apps.sample.tests'], ).countTestCases() self.assertEqual(count, 1) @@ -112,14 +114,14 @@ def test_ignore_adjacent(self): should not. The discover runner avoids this behavior. """ count = DiscoverRunner().build_suite( - ["test_discovery_sample.empty"], + ['test_runner_apps.sample.empty'], ).countTestCases() self.assertEqual(count, 0) def test_testcase_ordering(self): with change_cwd(".."): - suite = DiscoverRunner().build_suite(["test_discovery_sample/"]) + suite = DiscoverRunner().build_suite(['test_runner_apps/sample/']) self.assertEqual( suite._tests[0].__class__.__name__, 'TestDjangoTestCase', @@ -135,8 +137,8 @@ def test_duplicates_ignored(self): """ Tests shouldn't be discovered twice when discovering on overlapping paths. """ - base_app = 'gis_tests' - sub_app = 'gis_tests.geo3d' + base_app = 'forms_tests' + sub_app = 'forms_tests.field_tests' with self.modify_settings(INSTALLED_APPS={'append': sub_app}): single = DiscoverRunner().build_suite([base_app]).countTestCases() dups = DiscoverRunner().build_suite([base_app, sub_app]).countTestCases() @@ -149,10 +151,10 @@ def test_reverse(self): """ runner = DiscoverRunner(reverse=True) suite = runner.build_suite( - test_labels=('test_discovery_sample', 'test_discovery_sample2')) - self.assertIn('test_discovery_sample2', next(iter(suite)).id(), + test_labels=('test_runner_apps.sample', 'test_runner_apps.simple')) + self.assertIn('test_runner_apps.simple', next(iter(suite)).id(), msg="Test labels should be reversed.") - suite = runner.build_suite(test_labels=('test_discovery_sample2',)) + suite = runner.build_suite(test_labels=('test_runner_apps.simple',)) suite = tuple(suite) self.assertIn('DjangoCase', suite[0].id(), msg="Test groups should not be reversed.") @@ -185,16 +187,84 @@ def test_overridable_test_loader(self): def test_tags(self): runner = DiscoverRunner(tags=['core']) - self.assertEqual(runner.build_suite(['test_discovery_sample.tests_sample']).countTestCases(), 1) + self.assertEqual(runner.build_suite(['test_runner_apps.tagged.tests']).countTestCases(), 1) runner = DiscoverRunner(tags=['fast']) - self.assertEqual(runner.build_suite(['test_discovery_sample.tests_sample']).countTestCases(), 2) + self.assertEqual(runner.build_suite(['test_runner_apps.tagged.tests']).countTestCases(), 2) runner = DiscoverRunner(tags=['slow']) - self.assertEqual(runner.build_suite(['test_discovery_sample.tests_sample']).countTestCases(), 2) + self.assertEqual(runner.build_suite(['test_runner_apps.tagged.tests']).countTestCases(), 2) def test_exclude_tags(self): runner = DiscoverRunner(tags=['fast'], exclude_tags=['core']) - self.assertEqual(runner.build_suite(['test_discovery_sample.tests_sample']).countTestCases(), 1) + self.assertEqual(runner.build_suite(['test_runner_apps.tagged.tests']).countTestCases(), 1) runner = DiscoverRunner(tags=['fast'], exclude_tags=['slow']) - self.assertEqual(runner.build_suite(['test_discovery_sample.tests_sample']).countTestCases(), 0) + self.assertEqual(runner.build_suite(['test_runner_apps.tagged.tests']).countTestCases(), 0) runner = DiscoverRunner(exclude_tags=['slow']) - self.assertEqual(runner.build_suite(['test_discovery_sample.tests_sample']).countTestCases(), 4) + self.assertEqual(runner.build_suite(['test_runner_apps.tagged.tests']).countTestCases(), 0) + + def test_tag_inheritance(self): + def count_tests(**kwargs): + suite = DiscoverRunner(**kwargs).build_suite(['test_runner_apps.tagged.tests_inheritance']) + return suite.countTestCases() + + self.assertEqual(count_tests(tags=['foo']), 4) + self.assertEqual(count_tests(tags=['bar']), 2) + self.assertEqual(count_tests(tags=['baz']), 2) + self.assertEqual(count_tests(tags=['foo'], exclude_tags=['bar']), 2) + self.assertEqual(count_tests(tags=['foo'], exclude_tags=['bar', 'baz']), 1) + self.assertEqual(count_tests(exclude_tags=['foo']), 0) + + def test_included_tags_displayed(self): + runner = DiscoverRunner(tags=['foo', 'bar'], verbosity=2) + with captured_stdout() as stdout: + runner.build_suite(['test_runner_apps.tagged.tests']) + self.assertIn('Including test tag(s): bar, foo.\n', stdout.getvalue()) + + def test_excluded_tags_displayed(self): + runner = DiscoverRunner(exclude_tags=['foo', 'bar'], verbosity=3) + with captured_stdout() as stdout: + runner.build_suite(['test_runner_apps.tagged.tests']) + self.assertIn('Excluding test tag(s): bar, foo.\n', stdout.getvalue()) + + +class DiscoverRunnerGetDatabasesTests(SimpleTestCase): + runner = DiscoverRunner(verbosity=2) + skip_msg = 'Skipping setup of unused database(s): ' + + def get_databases(self, test_labels): + suite = self.runner.build_suite(test_labels) + with captured_stdout() as stdout: + databases = self.runner.get_databases(suite) + return databases, stdout.getvalue() + + def test_mixed(self): + databases, output = self.get_databases(['test_runner_apps.databases.tests']) + self.assertEqual(databases, set(connections)) + self.assertNotIn(self.skip_msg, output) + + def test_all(self): + databases, output = self.get_databases(['test_runner_apps.databases.tests.AllDatabasesTests']) + self.assertEqual(databases, set(connections)) + self.assertNotIn(self.skip_msg, output) + + def test_default_and_other(self): + databases, output = self.get_databases([ + 'test_runner_apps.databases.tests.DefaultDatabaseTests', + 'test_runner_apps.databases.tests.OtherDatabaseTests', + ]) + self.assertEqual(databases, set(connections)) + self.assertNotIn(self.skip_msg, output) + + def test_default_only(self): + databases, output = self.get_databases(['test_runner_apps.databases.tests.DefaultDatabaseTests']) + self.assertEqual(databases, {'default'}) + self.assertIn(self.skip_msg + 'other', output) + + def test_other_only(self): + databases, output = self.get_databases(['test_runner_apps.databases.tests.OtherDatabaseTests']) + self.assertEqual(databases, {'other'}) + self.assertIn(self.skip_msg + 'default', output) + + def test_no_databases_required(self): + databases, output = self.get_databases(['test_runner_apps.databases.tests.NoDatabaseTests']) + self.assertEqual(databases, set()) + self.assertIn(self.skip_msg + 'default, other', output) diff --git a/tests/test_runner/test_parallel.py b/tests/test_runner/test_parallel.py index b888dc62afb6..c1a89bd0f09d 100644 --- a/tests/test_runner/test_parallel.py +++ b/tests/test_runner/test_parallel.py @@ -2,7 +2,7 @@ from django.test import SimpleTestCase from django.test.runner import RemoteTestResult -from django.utils import six +from django.utils.version import PY37 try: import tblib @@ -16,7 +16,7 @@ class ExceptionThatFailsUnpickling(Exception): arguments passed to __init__(). """ def __init__(self, arg): - super(ExceptionThatFailsUnpickling, self).__init__() + super().__init__() class ParallelTestRunnerTest(SimpleTestCase): @@ -28,7 +28,6 @@ class ParallelTestRunnerTest(SimpleTestCase): parallel. """ - @unittest.skipUnless(six.PY3, 'subtests were added in Python 3.4') def test_subtest(self): """ Passing subtests work. @@ -61,12 +60,10 @@ def test_pickle_errors_detection(self): result._confirm_picklable(picklable_error) msg = '__init__() missing 1 required positional argument' - if six.PY2: - msg = '__init__() takes exactly 2 arguments (1 given)' with self.assertRaisesMessage(TypeError, msg): result._confirm_picklable(not_unpicklable_error) - @unittest.skipUnless(six.PY3 and tblib is not None, 'requires tblib to be installed') + @unittest.skipUnless(tblib is not None, 'requires tblib to be installed') def test_add_failing_subtests(self): """ Failing subtests are added correctly using addSubTest(). @@ -83,7 +80,8 @@ def test_add_failing_subtests(self): event = events[1] self.assertEqual(event[0], 'addSubTest') self.assertEqual(str(event[2]), 'dummy_test (test_runner.test_parallel.SampleFailingSubtest) (index=0)') - self.assertEqual(repr(event[3][1]), "AssertionError('0 != 1',)") + trailing_comma = '' if PY37 else ',' + self.assertEqual(repr(event[3][1]), "AssertionError('0 != 1'%s)" % trailing_comma) event = events[2] - self.assertEqual(repr(event[3][1]), "AssertionError('2 != 1',)") + self.assertEqual(repr(event[3][1]), "AssertionError('2 != 1'%s)" % trailing_comma) diff --git a/tests/test_runner/tests.py b/tests/test_runner/tests.py index d6e9af71a1b2..43c605eba64b 100644 --- a/tests/test_runner/tests.py +++ b/tests/test_runner/tests.py @@ -1,9 +1,8 @@ """ Tests for django test runner """ -from __future__ import unicode_literals - import unittest +from unittest import mock from admin_scripts.tests import AdminScriptTestCase @@ -11,14 +10,13 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.core.management import call_command -from django.test import ( - TestCase, TransactionTestCase, mock, skipUnlessDBFeature, testcases, -) +from django.core.management.base import SystemCheckError +from django.test import TransactionTestCase, skipUnlessDBFeature, testcases from django.test.runner import DiscoverRunner from django.test.testcases import connections_support_transactions from django.test.utils import dependency_ordered -from .models import Person +from .models import B, Person, Through class DependencyOrderingTests(unittest.TestCase): @@ -131,7 +129,7 @@ def test_own_alias_dependency(self): dependency_ordered(raw, dependencies=dependencies) -class MockTestRunner(object): +class MockTestRunner: def __init__(self, *args, **kwargs): pass @@ -148,11 +146,14 @@ def test_custom_test_runner(self): def test_bad_test_runner(self): with self.assertRaises(AttributeError): - call_command('test', 'sites', testrunner='test_runner.NonExistentRunner') - + call_command('test', 'sites', testrunner='test_runner.NonexistentRunner') -class CustomTestRunnerOptionsTests(AdminScriptTestCase): +class CustomTestRunnerOptionsSettingsTests(AdminScriptTestCase): + """ + Custom runners can add command line arguments. The runner is specified + through a settings file. + """ def setUp(self): settings = { 'TEST_RUNNER': '\'test_runner.runner.CustomOptionsTestRunner\'', @@ -188,6 +189,43 @@ def test_all_options_given(self): self.assertOutput(out, 'bar:foo:31337') +class CustomTestRunnerOptionsCmdlineTests(AdminScriptTestCase): + """ + Custom runners can add command line arguments when the runner is specified + using --testrunner. + """ + def setUp(self): + self.write_settings('settings.py') + + def tearDown(self): + self.remove_settings('settings.py') + + def test_testrunner_option(self): + args = [ + 'test', '--testrunner', 'test_runner.runner.CustomOptionsTestRunner', + '--option_a=bar', '--option_b=foo', '--option_c=31337' + ] + out, err = self.run_django_admin(args, 'test_project.settings') + self.assertNoOutput(err) + self.assertOutput(out, 'bar:foo:31337') + + def test_testrunner_equals(self): + args = [ + 'test', '--testrunner=test_runner.runner.CustomOptionsTestRunner', + '--option_a=bar', '--option_b=foo', '--option_c=31337' + ] + out, err = self.run_django_admin(args, 'test_project.settings') + self.assertNoOutput(err) + self.assertOutput(out, 'bar:foo:31337') + + def test_no_testrunner(self): + args = ['test', '--testrunner'] + out, err = self.run_django_admin(args, 'test_project.settings') + self.assertIn('usage', err) + self.assertNotIn('Traceback', err) + self.assertNoOutput(out) + + class Ticket17477RegressionTests(AdminScriptTestCase): def setUp(self): self.write_settings('settings.py') @@ -202,12 +240,24 @@ def test_ticket_17477(self): self.assertNoOutput(err) -class Sqlite3InMemoryTestDbs(TestCase): +class SQLiteInMemoryTestDbs(TransactionTestCase): + available_apps = ['test_runner'] + databases = {'default', 'other'} @unittest.skipUnless(all(db.connections[conn].vendor == 'sqlite' for conn in db.connections), "This is an sqlite-specific issue") def test_transaction_support(self): - """Ticket #16329: sqlite3 in-memory test databases""" + # Assert connections mocking is appropriately applied by preventing + # any attempts at calling create_test_db on the global connection + # objects. + for connection in db.connections.all(): + create_test_db = mock.patch.object( + connection.creation, + 'create_test_db', + side_effect=AssertionError("Global connection object shouldn't be manipulated.") + ) + create_test_db.start() + self.addCleanup(create_test_db.stop) for option_key, option_value in ( ('NAME', ':memory:'), ('TEST', {'NAME': ':memory:'})): tested_connections = db.ConnectionHandler({ @@ -220,16 +270,17 @@ def test_transaction_support(self): option_key: option_value, }, }) - with mock.patch('django.db.connections', new=tested_connections): - with mock.patch('django.test.testcases.connections', new=tested_connections): - other = tested_connections['other'] - DiscoverRunner(verbosity=0).setup_databases() - msg = ("DATABASES setting '%s' option set to sqlite3's ':memory:' value " - "shouldn't interfere with transaction support detection." % option_key) - # Transaction support should be properly initialized for the 'other' DB - self.assertTrue(other.features.supports_transactions, msg) - # And all the DBs should report that they support transactions - self.assertTrue(connections_support_transactions(), msg) + with mock.patch('django.test.utils.connections', new=tested_connections): + other = tested_connections['other'] + DiscoverRunner(verbosity=0).setup_databases() + msg = ( + "DATABASES setting '%s' option set to sqlite3's ':memory:' value " + "shouldn't interfere with transaction support detection." % option_key + ) + # Transaction support is properly initialized for the 'other' DB. + self.assertTrue(other.features.supports_transactions, msg) + # And all the DBs report that they support transactions. + self.assertTrue(connections_support_transactions(), msg) class DummyBackendTest(unittest.TestCase): @@ -327,26 +378,34 @@ def test_serialized_off(self): ) +@skipUnlessDBFeature('supports_sequence_reset') class AutoIncrementResetTest(TransactionTestCase): """ - Here we test creating the same model two times in different test methods, - and check that both times they get "1" as their PK value. That is, we test - that AutoField values start from 1 for each transactional test case. + Creating the same models in different test methods receive the same PK + values since the sequences are reset before each test method. """ available_apps = ['test_runner'] reset_sequences = True - @skipUnlessDBFeature('supports_sequence_reset') - def test_autoincrement_reset1(self): + def _test(self): + # Regular model p = Person.objects.create(first_name='Jack', last_name='Smith') self.assertEqual(p.pk, 1) + # Auto-created many-to-many through model + p.friends.add(Person.objects.create(first_name='Jacky', last_name='Smith')) + self.assertEqual(p.friends.through.objects.first().pk, 1) + # Many-to-many through model + b = B.objects.create() + t = Through.objects.create(person=p, b=b) + self.assertEqual(t.pk, 1) + + def test_autoincrement_reset1(self): + self._test() - @skipUnlessDBFeature('supports_sequence_reset') def test_autoincrement_reset2(self): - p = Person.objects.create(first_name='Jack', last_name='Smith') - self.assertEqual(p.pk, 1) + self._test() class EmptyDefaultDatabaseTest(unittest.TestCase): @@ -359,3 +418,59 @@ def test_empty_default_database(self): connection = testcases.connections[db.utils.DEFAULT_DB_ALIAS] self.assertEqual(connection.settings_dict['ENGINE'], 'django.db.backends.dummy') connections_support_transactions() + + +class RunTestsExceptionHandlingTests(unittest.TestCase): + def test_run_checks_raises(self): + """ + Teardown functions are run when run_checks() raises SystemCheckError. + """ + with mock.patch('django.test.runner.DiscoverRunner.setup_test_environment'), \ + mock.patch('django.test.runner.DiscoverRunner.setup_databases'), \ + mock.patch('django.test.runner.DiscoverRunner.build_suite'), \ + mock.patch('django.test.runner.DiscoverRunner.run_checks', side_effect=SystemCheckError), \ + mock.patch('django.test.runner.DiscoverRunner.teardown_databases') as teardown_databases, \ + mock.patch('django.test.runner.DiscoverRunner.teardown_test_environment') as teardown_test_environment: + runner = DiscoverRunner(verbosity=0, interactive=False) + with self.assertRaises(SystemCheckError): + runner.run_tests(['test_runner_apps.sample.tests_sample.TestDjangoTestCase']) + self.assertTrue(teardown_databases.called) + self.assertTrue(teardown_test_environment.called) + + def test_run_checks_raises_and_teardown_raises(self): + """ + SystemCheckError is surfaced when run_checks() raises SystemCheckError + and teardown databases() raises ValueError. + """ + with mock.patch('django.test.runner.DiscoverRunner.setup_test_environment'), \ + mock.patch('django.test.runner.DiscoverRunner.setup_databases'), \ + mock.patch('django.test.runner.DiscoverRunner.build_suite'), \ + mock.patch('django.test.runner.DiscoverRunner.run_checks', side_effect=SystemCheckError), \ + mock.patch('django.test.runner.DiscoverRunner.teardown_databases', side_effect=ValueError) \ + as teardown_databases, \ + mock.patch('django.test.runner.DiscoverRunner.teardown_test_environment') as teardown_test_environment: + runner = DiscoverRunner(verbosity=0, interactive=False) + with self.assertRaises(SystemCheckError): + runner.run_tests(['test_runner_apps.sample.tests_sample.TestDjangoTestCase']) + self.assertTrue(teardown_databases.called) + self.assertFalse(teardown_test_environment.called) + + def test_run_checks_passes_and_teardown_raises(self): + """ + Exceptions on teardown are surfaced if no exceptions happen during + run_checks(). + """ + with mock.patch('django.test.runner.DiscoverRunner.setup_test_environment'), \ + mock.patch('django.test.runner.DiscoverRunner.setup_databases'), \ + mock.patch('django.test.runner.DiscoverRunner.build_suite'), \ + mock.patch('django.test.runner.DiscoverRunner.run_checks'), \ + mock.patch('django.test.runner.DiscoverRunner.teardown_databases', side_effect=ValueError) \ + as teardown_databases, \ + mock.patch('django.test.runner.DiscoverRunner.teardown_test_environment') as teardown_test_environment: + runner = DiscoverRunner(verbosity=0, interactive=False) + with self.assertRaises(ValueError): + # Suppress the output when running TestDjangoTestCase. + with mock.patch('sys.stderr'): + runner.run_tests(['test_runner_apps.sample.tests_sample.TestDjangoTestCase']) + self.assertTrue(teardown_databases.called) + self.assertFalse(teardown_test_environment.called) diff --git a/tests/test_runner_apps/__init__.py b/tests/test_runner_apps/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/test_runner_apps/databases/__init__.py b/tests/test_runner_apps/databases/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/test_runner_apps/databases/tests.py b/tests/test_runner_apps/databases/tests.py new file mode 100644 index 000000000000..4be260e689d5 --- /dev/null +++ b/tests/test_runner_apps/databases/tests.py @@ -0,0 +1,18 @@ +import unittest + + +class NoDatabaseTests(unittest.TestCase): + def test_nothing(self): + pass + + +class DefaultDatabaseTests(NoDatabaseTests): + databases = {'default'} + + +class OtherDatabaseTests(NoDatabaseTests): + databases = {'other'} + + +class AllDatabasesTests(NoDatabaseTests): + databases = '__all__' diff --git a/tests/test_runner_apps/sample/__init__.py b/tests/test_runner_apps/sample/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/test_discovery_sample/doctests.py b/tests/test_runner_apps/sample/doctests.py similarity index 96% rename from tests/test_discovery_sample/doctests.py rename to tests/test_runner_apps/sample/doctests.py index 6d9403442c61..8707ecaf8606 100644 --- a/tests/test_discovery_sample/doctests.py +++ b/tests/test_runner_apps/sample/doctests.py @@ -1,6 +1,6 @@ """ Doctest example from the official Python documentation. -https://docs.python.org/3/library/doctest.html +https://docs.python.org/library/doctest.html """ diff --git a/tests/test_runner_apps/sample/empty.py b/tests/test_runner_apps/sample/empty.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/test_discovery_sample/pattern_tests.py b/tests/test_runner_apps/sample/pattern_tests.py similarity index 100% rename from tests/test_discovery_sample/pattern_tests.py rename to tests/test_runner_apps/sample/pattern_tests.py diff --git a/tests/test_runner_apps/sample/tests/__init__.py b/tests/test_runner_apps/sample/tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/test_discovery_sample/tests/tests.py b/tests/test_runner_apps/sample/tests/tests.py similarity index 100% rename from tests/test_discovery_sample/tests/tests.py rename to tests/test_runner_apps/sample/tests/tests.py diff --git a/tests/test_discovery_sample/tests_sample.py b/tests/test_runner_apps/sample/tests_sample.py similarity index 74% rename from tests/test_discovery_sample/tests_sample.py rename to tests/test_runner_apps/sample/tests_sample.py index 694b01ffd477..eb977e44fba8 100644 --- a/tests/test_discovery_sample/tests_sample.py +++ b/tests/test_runner_apps/sample/tests_sample.py @@ -1,7 +1,7 @@ import doctest from unittest import TestCase -from django.test import SimpleTestCase, TestCase as DjangoTestCase, tag +from django.test import SimpleTestCase, TestCase as DjangoTestCase from . import doctests @@ -29,18 +29,6 @@ class EmptyTestCase(TestCase): pass -@tag('slow') -class TaggedTestCase(TestCase): - - @tag('fast') - def test_single_tag(self): - self.assertEqual(1, 1) - - @tag('fast', 'core') - def test_multiple_tags(self): - self.assertEqual(1, 1) - - def load_tests(loader, tests, ignore): tests.addTests(doctest.DocTestSuite(doctests)) return tests diff --git a/tests/test_runner_apps/simple/__init__.py b/tests/test_runner_apps/simple/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/test_discovery_sample2/tests.py b/tests/test_runner_apps/simple/tests.py similarity index 100% rename from tests/test_discovery_sample2/tests.py rename to tests/test_runner_apps/simple/tests.py diff --git a/tests/test_runner_apps/tagged/__init__.py b/tests/test_runner_apps/tagged/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/test_runner_apps/tagged/tests.py b/tests/test_runner_apps/tagged/tests.py new file mode 100644 index 000000000000..d3742b3749ce --- /dev/null +++ b/tests/test_runner_apps/tagged/tests.py @@ -0,0 +1,15 @@ +from unittest import TestCase + +from django.test import tag + + +@tag('slow') +class TaggedTestCase(TestCase): + + @tag('fast') + def test_single_tag(self): + self.assertEqual(1, 1) + + @tag('fast', 'core') + def test_multiple_tags(self): + self.assertEqual(1, 1) diff --git a/tests/test_runner_apps/tagged/tests_inheritance.py b/tests/test_runner_apps/tagged/tests_inheritance.py new file mode 100644 index 000000000000..59e564202d5e --- /dev/null +++ b/tests/test_runner_apps/tagged/tests_inheritance.py @@ -0,0 +1,29 @@ +from unittest import TestCase + +from django.test import tag + + +@tag('foo') +class FooBase(TestCase): + pass + + +class Foo(FooBase): + + def test_no_new_tags(self): + pass + + @tag('baz') + def test_new_func_tag(self): + pass + + +@tag('bar') +class FooBar(FooBase): + + def test_new_class_tag_only(self): + pass + + @tag('baz') + def test_new_class_and_func_tags(self): + pass diff --git a/tests/test_utils/models.py b/tests/test_utils/models.py index 979b04855ff5..e94d44356b5a 100644 --- a/tests/test_utils/models.py +++ b/tests/test_utils/models.py @@ -1,8 +1,6 @@ from django.db import models -from django.utils.encoding import python_2_unicode_compatible -@python_2_unicode_compatible class Car(models.Model): name = models.CharField(max_length=100) @@ -10,7 +8,6 @@ def __str__(self): return self.name -@python_2_unicode_compatible class Person(models.Model): name = models.CharField(max_length=100) cars = models.ManyToManyField(Car, through='PossessedCar') @@ -19,7 +16,6 @@ def __str__(self): return self.name -@python_2_unicode_compatible class PossessedCar(models.Model): car = models.ForeignKey(Car, models.CASCADE) belongs_to = models.ForeignKey(Person, models.CASCADE) diff --git a/tests/test_utils/test_deprecated_features.py b/tests/test_utils/test_deprecated_features.py new file mode 100644 index 000000000000..fbed5e14c5b1 --- /dev/null +++ b/tests/test_utils/test_deprecated_features.py @@ -0,0 +1,64 @@ +from django.db import connections +from django.db.utils import DEFAULT_DB_ALIAS +from django.test import SimpleTestCase, TestCase, TransactionTestCase +from django.utils.deprecation import RemovedInDjango31Warning + + +class AllowDatabaseQueriesDeprecationTests(SimpleTestCase): + def test_enabled(self): + class AllowedDatabaseQueries(SimpleTestCase): + allow_database_queries = True + message = ( + '`SimpleTestCase.allow_database_queries` is deprecated. Restrict ' + 'the databases available during the execution of ' + 'test_utils.test_deprecated_features.AllowDatabaseQueriesDeprecationTests.' + 'test_enabled..AllowedDatabaseQueries with the ' + '`databases` attribute instead.' + ) + with self.assertWarnsMessage(RemovedInDjango31Warning, message): + self.assertEqual(AllowedDatabaseQueries.databases, {'default'}) + + def test_explicitly_disabled(self): + class AllowedDatabaseQueries(SimpleTestCase): + allow_database_queries = False + message = ( + '`SimpleTestCase.allow_database_queries` is deprecated. Restrict ' + 'the databases available during the execution of ' + 'test_utils.test_deprecated_features.AllowDatabaseQueriesDeprecationTests.' + 'test_explicitly_disabled..AllowedDatabaseQueries with ' + 'the `databases` attribute instead.' + ) + with self.assertWarnsMessage(RemovedInDjango31Warning, message): + self.assertEqual(AllowedDatabaseQueries.databases, set()) + + +class MultiDbDeprecationTests(SimpleTestCase): + def test_transaction_test_case(self): + class MultiDbTestCase(TransactionTestCase): + multi_db = True + message = ( + '`TransactionTestCase.multi_db` is deprecated. Databases ' + 'available during this test can be defined using ' + 'test_utils.test_deprecated_features.MultiDbDeprecationTests.' + 'test_transaction_test_case..MultiDbTestCase.databases.' + ) + with self.assertWarnsMessage(RemovedInDjango31Warning, message): + self.assertEqual(MultiDbTestCase.databases, set(connections)) + MultiDbTestCase.multi_db = False + with self.assertWarnsMessage(RemovedInDjango31Warning, message): + self.assertEqual(MultiDbTestCase.databases, {DEFAULT_DB_ALIAS}) + + def test_test_case(self): + class MultiDbTestCase(TestCase): + multi_db = True + message = ( + '`TestCase.multi_db` is deprecated. Databases available during ' + 'this test can be defined using ' + 'test_utils.test_deprecated_features.MultiDbDeprecationTests.' + 'test_test_case..MultiDbTestCase.databases.' + ) + with self.assertWarnsMessage(RemovedInDjango31Warning, message): + self.assertEqual(MultiDbTestCase.databases, set(connections)) + MultiDbTestCase.multi_db = False + with self.assertWarnsMessage(RemovedInDjango31Warning, message): + self.assertEqual(MultiDbTestCase.databases, {DEFAULT_DB_ALIAS}) diff --git a/tests/test_utils/test_testcase.py b/tests/test_utils/test_testcase.py index 8a367391cbf7..853aba7c2288 100644 --- a/tests/test_utils/test_testcase.py +++ b/tests/test_utils/test_testcase.py @@ -1,7 +1,7 @@ -from django.db import IntegrityError, transaction +from django.db import IntegrityError, connections, transaction from django.test import TestCase, skipUnlessDBFeature -from .models import PossessedCar +from .models import Car, PossessedCar class TestTestCase(TestCase): @@ -18,3 +18,23 @@ def test_fixture_teardown_checks_constraints(self): car.delete() finally: self._rollback_atomics = rollback_atomics + + def test_disallowed_database_connection(self): + message = ( + "Database connections to 'other' are not allowed in this test. " + "Add 'other' to test_utils.test_testcase.TestTestCase.databases to " + "ensure proper test isolation and silence this failure." + ) + with self.assertRaisesMessage(AssertionError, message): + connections['other'].connect() + with self.assertRaisesMessage(AssertionError, message): + connections['other'].temporary_connection() + + def test_disallowed_database_queries(self): + message = ( + "Database queries to 'other' are not allowed in this test. " + "Add 'other' to test_utils.test_testcase.TestTestCase.databases to " + "ensure proper test isolation and silence this failure." + ) + with self.assertRaisesMessage(AssertionError, message): + Car.objects.using('other').get() diff --git a/tests/test_utils/test_transactiontestcase.py b/tests/test_utils/test_transactiontestcase.py index 34588ddefd8d..3a9d173138d6 100644 --- a/tests/test_utils/test_transactiontestcase.py +++ b/tests/test_utils/test_transactiontestcase.py @@ -1,4 +1,9 @@ -from django.test import TransactionTestCase, mock +from unittest import mock + +from django.db import connections +from django.test import TestCase, TransactionTestCase, override_settings + +from .models import Car class TestSerializedRollbackInhibitsPostMigrate(TransactionTestCase): @@ -26,3 +31,32 @@ def test(self, call_command): reset_sequences=False, inhibit_post_migrate=True, database='default', verbosity=0, ) + + +@override_settings(DEBUG=True) # Enable query logging for test_queries_cleared +class TransactionTestCaseDatabasesTests(TestCase): + available_apps = [] + databases = {'default', 'other'} + + def test_queries_cleared(self): + """ + TransactionTestCase._pre_setup() clears the connections' queries_log + so that it's less likely to overflow. An overflow causes + assertNumQueries() to fail. + """ + for alias in connections: + self.assertEqual(len(connections[alias].queries_log), 0, 'Failed for alias %s' % alias) + + +class DisallowedDatabaseQueriesTests(TransactionTestCase): + available_apps = ['test_utils'] + + def test_disallowed_database_queries(self): + message = ( + "Database queries to 'other' are not allowed in this test. " + "Add 'other' to test_utils.test_transactiontestcase." + "DisallowedDatabaseQueriesTests.databases to ensure proper test " + "isolation and silence this failure." + ) + with self.assertRaisesMessage(AssertionError, message): + Car.objects.using('other').get() diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py index 54b83f524b0f..a1a113a26ec2 100644 --- a/tests/test_utils/tests.py +++ b/tests/test_utils/tests.py @@ -1,46 +1,42 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -import sys +import os import unittest import warnings +from io import StringIO +from unittest import mock -from django.conf.urls import url +from django.conf import settings from django.contrib.staticfiles.finders import get_finder, get_finders from django.contrib.staticfiles.storage import staticfiles_storage +from django.core.exceptions import ImproperlyConfigured from django.core.files.storage import default_storage -from django.db import connection, models, router +from django.db import connection, connections, models, router from django.forms import EmailField, IntegerField from django.http import HttpResponse from django.template.loader import render_to_string from django.test import ( - SimpleTestCase, TestCase, ignore_warnings, skipIfDBFeature, + SimpleTestCase, TestCase, TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature, ) from django.test.html import HTMLParseError, parse_html from django.test.utils import ( - CaptureQueriesContext, isolate_apps, override_settings, - setup_test_environment, + CaptureQueriesContext, TestContextDecorator, isolate_apps, + override_settings, setup_test_environment, ) -from django.urls import NoReverseMatch, reverse -from django.utils import six -from django.utils._os import abspathu -from django.utils.deprecation import RemovedInDjango20Warning +from django.urls import NoReverseMatch, path, reverse, reverse_lazy from .models import Car, Person, PossessedCar from .views import empty_response class SkippingTestCase(SimpleTestCase): - def _assert_skipping(self, func, expected_exc): - # We cannot simply use assertRaises because a SkipTest exception will go unnoticed + def _assert_skipping(self, func, expected_exc, msg=None): try: - func() - except expected_exc: - pass - except Exception as e: - self.fail("No %s exception should have been raised for %s." % ( - e.__class__.__name__, func.__name__)) + if msg is not None: + self.assertRaisesMessage(expected_exc, msg, func) + else: + self.assertRaises(expected_exc, func) + except unittest.SkipTest: + self.fail('%s should not result in a skipped test.' % func.__name__) def test_skip_unless_db_feature(self): """ @@ -68,6 +64,20 @@ def test_func4(): self._assert_skipping(test_func3, ValueError) self._assert_skipping(test_func4, unittest.SkipTest) + class SkipTestCase(SimpleTestCase): + @skipUnlessDBFeature('missing') + def test_foo(self): + pass + + self._assert_skipping( + SkipTestCase('test_foo').test_foo, + ValueError, + "skipUnlessDBFeature cannot be used on test_foo (test_utils.tests." + "SkippingTestCase.test_skip_unless_db_feature..SkipTestCase) " + "as SkippingTestCase.test_skip_unless_db_feature..SkipTestCase " + "doesn't allow queries against the 'default' database." + ) + def test_skip_if_db_feature(self): """ Testing the django.test.skipIfDBFeature decorator. @@ -98,17 +108,31 @@ def test_func5(): self._assert_skipping(test_func4, unittest.SkipTest) self._assert_skipping(test_func5, ValueError) + class SkipTestCase(SimpleTestCase): + @skipIfDBFeature('missing') + def test_foo(self): + pass + + self._assert_skipping( + SkipTestCase('test_foo').test_foo, + ValueError, + "skipIfDBFeature cannot be used on test_foo (test_utils.tests." + "SkippingTestCase.test_skip_if_db_feature..SkipTestCase) " + "as SkippingTestCase.test_skip_if_db_feature..SkipTestCase " + "doesn't allow queries against the 'default' database." + ) + -class SkippingClassTestCase(SimpleTestCase): +class SkippingClassTestCase(TestCase): def test_skip_class_unless_db_feature(self): @skipUnlessDBFeature("__class__") - class NotSkippedTests(unittest.TestCase): + class NotSkippedTests(TestCase): def test_dummy(self): return @skipUnlessDBFeature("missing") @skipIfDBFeature("__class__") - class SkippedTests(unittest.TestCase): + class SkippedTests(TestCase): def test_will_be_skipped(self): self.fail("We should never arrive here.") @@ -122,13 +146,34 @@ class SkippedTestsSubclass(SkippedTests): test_suite.addTest(SkippedTests('test_will_be_skipped')) test_suite.addTest(SkippedTestsSubclass('test_will_be_skipped')) except unittest.SkipTest: - self.fail("SkipTest should not be raised at this stage") - result = unittest.TextTestRunner(stream=six.StringIO()).run(test_suite) + self.fail('SkipTest should not be raised here.') + result = unittest.TextTestRunner(stream=StringIO()).run(test_suite) self.assertEqual(result.testsRun, 3) self.assertEqual(len(result.skipped), 2) self.assertEqual(result.skipped[0][1], 'Database has feature(s) __class__') self.assertEqual(result.skipped[1][1], 'Database has feature(s) __class__') + def test_missing_default_databases(self): + @skipIfDBFeature('missing') + class MissingDatabases(SimpleTestCase): + def test_assertion_error(self): + pass + + suite = unittest.TestSuite() + try: + suite.addTest(MissingDatabases('test_assertion_error')) + except unittest.SkipTest: + self.fail("SkipTest should not be raised at this stage") + runner = unittest.TextTestRunner(stream=StringIO()) + msg = ( + "skipIfDBFeature cannot be used on ." + "MissingDatabases'> as it doesn't allow queries against the " + "'default' database." + ) + with self.assertRaisesMessage(ValueError, msg): + runner.run(suite) + @override_settings(ROOT_URLCONF='test_utils.urls') class AssertNumQueriesTests(TestCase): @@ -161,10 +206,37 @@ def test_func(): self.assertNumQueries(2, test_func) +@unittest.skipUnless( + connection.vendor != 'sqlite' or not connection.is_in_memory_db(), + 'For SQLite in-memory tests, closing the connection destroys the database.' +) +class AssertNumQueriesUponConnectionTests(TransactionTestCase): + available_apps = [] + + def test_ignores_connection_configuration_queries(self): + real_ensure_connection = connection.ensure_connection + connection.close() + + def make_configuration_query(): + is_opening_connection = connection.connection is None + real_ensure_connection() + + if is_opening_connection: + # Avoid infinite recursion. Creating a cursor calls + # ensure_connection() which is currently mocked by this method. + connection.cursor().execute('SELECT 1' + connection.features.bare_select_suffix) + + ensure_connection = 'django.db.backends.base.base.BaseDatabaseWrapper.ensure_connection' + with mock.patch(ensure_connection, side_effect=make_configuration_query): + with self.assertNumQueries(1): + list(Car.objects.all()) + + class AssertQuerysetEqualTests(TestCase): - def setUp(self): - self.p1 = Person.objects.create(name='p1') - self.p2 = Person.objects.create(name='p2') + @classmethod + def setUpTestData(cls): + cls.p1 = Person.objects.create(name='p1') + cls.p2 = Person.objects.create(name='p2') def test_ordered(self): self.assertQuerysetEqual( @@ -189,7 +261,8 @@ def test_transform(self): def test_undefined_order(self): # Using an unordered queryset with more than one ordered value # is an error. - with self.assertRaises(ValueError): + msg = 'Trying to compare non-ordered queryset against more than one ordered values' + with self.assertRaisesMessage(ValueError, msg): self.assertQuerysetEqual( Person.objects.all(), [repr(self.p1), repr(self.p2)] @@ -231,8 +304,9 @@ def test_repeated_values(self): @override_settings(ROOT_URLCONF='test_utils.urls') class CaptureQueriesContextManagerTests(TestCase): - def setUp(self): - self.person_pk = six.text_type(Person.objects.create(name='test').pk) + @classmethod + def setUpTestData(cls): + cls.person_pk = str(Person.objects.create(name='test').pk) def test_simple(self): with CaptureQueriesContext(connection) as captured_queries: @@ -300,8 +374,10 @@ def test_failure(self): with self.assertRaises(AssertionError) as exc_info: with self.assertNumQueries(2): Person.objects.count() - self.assertIn("1 queries executed, 2 expected", str(exc_info.exception)) - self.assertIn("Captured queries were", str(exc_info.exception)) + exc_lines = str(exc_info.exception).split('\n') + self.assertEqual(exc_lines[0], '1 != 2 : 1 queries executed, 2 expected') + self.assertEqual(exc_lines[1], 'Captured queries were:') + self.assertTrue(exc_lines[2].startswith('1.')) # queries are numbered with self.assertRaises(TypeError): with self.assertNumQueries(4000): @@ -368,41 +444,51 @@ def test_not_used(self): pass def test_error_message(self): - with self.assertRaisesRegex(AssertionError, r'^template_used/base\.html'): + msg = 'template_used/base.html was not rendered. No template was rendered.' + with self.assertRaisesMessage(AssertionError, msg): with self.assertTemplateUsed('template_used/base.html'): pass - with self.assertRaisesRegex(AssertionError, r'^template_used/base\.html'): + with self.assertRaisesMessage(AssertionError, msg): with self.assertTemplateUsed(template_name='template_used/base.html'): pass - with self.assertRaisesRegex(AssertionError, r'^template_used/base\.html.*template_used/alternative\.html$'): + msg2 = ( + 'template_used/base.html was not rendered. Following templates ' + 'were rendered: template_used/alternative.html' + ) + with self.assertRaisesMessage(AssertionError, msg2): with self.assertTemplateUsed('template_used/base.html'): render_to_string('template_used/alternative.html') - with self.assertRaises(AssertionError) as cm: + with self.assertRaisesMessage(AssertionError, 'No templates used to render the response'): response = self.client.get('/test_utils/no_template_used/') self.assertTemplateUsed(response, 'template_used/base.html') - self.assertEqual(cm.exception.args[0], "No templates used to render the response") def test_failure(self): - with self.assertRaises(TypeError): + msg = 'response and/or template_name argument must be provided' + with self.assertRaisesMessage(TypeError, msg): with self.assertTemplateUsed(): pass - with self.assertRaises(AssertionError): + msg = 'No templates used to render the response' + with self.assertRaisesMessage(AssertionError, msg): with self.assertTemplateUsed(''): pass - with self.assertRaises(AssertionError): + with self.assertRaisesMessage(AssertionError, msg): with self.assertTemplateUsed(''): render_to_string('template_used/base.html') - with self.assertRaises(AssertionError): + with self.assertRaisesMessage(AssertionError, msg): with self.assertTemplateUsed(template_name=''): pass - with self.assertRaises(AssertionError): + msg = ( + 'template_used/base.html was not rendered. Following ' + 'templates were rendered: template_used/alternative.html' + ) + with self.assertRaisesMessage(AssertionError, msg): with self.assertTemplateUsed('template_used/base.html'): render_to_string('template_used/alternative.html') @@ -648,9 +734,6 @@ def test_parsing_errors(self): error_msg = ( "First argument is not valid HTML:\n" "('Unexpected end tag `div` (Line 1, Column 6)', (1, 6))" - ) if sys.version_info >= (3, 5) else ( - "First argument is not valid HTML:\n" - "Unexpected end tag `div` (Line 1, Column 6), at line 1, column 7" ) with self.assertRaisesMessage(AssertionError, error_msg): self.assertHTMLEqual('< div>', '
        ') @@ -659,15 +742,15 @@ def test_parsing_errors(self): def test_contains_html(self): response = HttpResponse(''' - This is a form:
        + This is a form:
        ''') self.assertNotContains(response, "") - self.assertContains(response, '
        ') + self.assertContains(response, '') self.assertContains(response, "", html=True) - self.assertNotContains(response, '', html=True) + self.assertNotContains(response, '', html=True) invalid_response = HttpResponse('''>''') @@ -801,7 +884,7 @@ class SkippingExtraTests(TestCase): def __call__(self, result=None): # Detect fixture loading by counting SQL queries, should be zero with self.assertNumQueries(0): - super(SkippingExtraTests, self).__call__(result) + super().__call__(result) @unittest.skip("Fixture loading should not be performed for skipped tests.") def test_fixtures_are_skipped(self): @@ -831,22 +914,29 @@ def func1(): with self.assertRaisesMessage(ValueError, "[.*x+]y?"): func1() - @ignore_warnings(category=RemovedInDjango20Warning) - def test_callable_obj_param(self): - # callable_obj was a documented kwarg in Django 1.8 and older. - def func1(): - raise ValueError("[.*x+]y?") - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter('always') - self.assertRaisesMessage(ValueError, "[.*x+]y?", callable_obj=func1) +class AssertWarnsMessageTests(SimpleTestCase): - self.assertEqual(len(warns), 1) - self.assertEqual( - str(warns[0].message), - 'The callable_obj kwarg is deprecated. Pass the callable ' - 'as a positional argument instead.' - ) + def test_context_manager(self): + with self.assertWarnsMessage(UserWarning, 'Expected message'): + warnings.warn('Expected message', UserWarning) + + def test_context_manager_failure(self): + msg = "Expected message' not found in 'Unexpected message'" + with self.assertRaisesMessage(AssertionError, msg): + with self.assertWarnsMessage(UserWarning, 'Expected message'): + warnings.warn('Unexpected message', UserWarning) + + def test_callable(self): + def func(): + warnings.warn('Expected message', UserWarning) + self.assertWarnsMessage(UserWarning, 'Expected message', func) + + def test_special_re_chars(self): + def func1(): + warnings.warn('[.*x+]y?', UserWarning) + with self.assertWarnsMessage(UserWarning, '[.*x+]y?'): + func1() class AssertFieldOutputTests(SimpleTestCase): @@ -871,12 +961,62 @@ class MyCustomField(IntegerField): self.assertFieldOutput(MyCustomField, {}, {}, empty_value=None) +@override_settings(ROOT_URLCONF='test_utils.urls') +class AssertURLEqualTests(SimpleTestCase): + def test_equal(self): + valid_tests = ( + ('http://example.com/?', 'http://example.com/'), + ('http://example.com/?x=1&', 'http://example.com/?x=1'), + ('http://example.com/?x=1&y=2', 'http://example.com/?y=2&x=1'), + ('http://example.com/?x=1&y=2', 'http://example.com/?y=2&x=1'), + ('http://example.com/?x=1&y=2&a=1&a=2', 'http://example.com/?a=1&a=2&y=2&x=1'), + ('/path/to/?x=1&y=2&z=3', '/path/to/?z=3&y=2&x=1'), + ('?x=1&y=2&z=3', '?z=3&y=2&x=1'), + ('/test_utils/no_template_used/', reverse_lazy('no_template_used')), + ) + for url1, url2 in valid_tests: + with self.subTest(url=url1): + self.assertURLEqual(url1, url2) + + def test_not_equal(self): + invalid_tests = ( + # Protocol must be the same. + ('http://example.com/', 'https://example.com/'), + ('http://example.com/?x=1&x=2', 'https://example.com/?x=2&x=1'), + ('http://example.com/?x=1&y=bar&x=2', 'https://example.com/?y=bar&x=2&x=1'), + # Parameters of the same name must be in the same order. + ('/path/to?a=1&a=2', '/path/to/?a=2&a=1') + ) + for url1, url2 in invalid_tests: + with self.subTest(url=url1), self.assertRaises(AssertionError): + self.assertURLEqual(url1, url2) + + def test_message(self): + msg = ( + "Expected 'http://example.com/?x=1&x=2' to equal " + "'https://example.com/?x=2&x=1'" + ) + with self.assertRaisesMessage(AssertionError, msg): + self.assertURLEqual('http://example.com/?x=1&x=2', 'https://example.com/?x=2&x=1') + + def test_msg_prefix(self): + msg = ( + "Prefix: Expected 'http://example.com/?x=1&x=2' to equal " + "'https://example.com/?x=2&x=1'" + ) + with self.assertRaisesMessage(AssertionError, msg): + self.assertURLEqual( + 'http://example.com/?x=1&x=2', 'https://example.com/?x=2&x=1', + msg_prefix='Prefix: ', + ) + + class FirstUrls: - urlpatterns = [url(r'first/$', empty_response, name='first')] + urlpatterns = [path('first/', empty_response, name='first')] class SecondUrls: - urlpatterns = [url(r'second/$', empty_response, name='second')] + urlpatterns = [path('second/', empty_response, name='second')] class SetupTestEnvironmentTests(SimpleTestCase): @@ -885,6 +1025,16 @@ def test_setup_test_environment_calling_more_than_once(self): with self.assertRaisesMessage(RuntimeError, "setup_test_environment() was already called"): setup_test_environment() + def test_allowed_hosts(self): + for type_ in (list, tuple): + with self.subTest(type_=type_): + allowed_hosts = type_('*') + with mock.patch('django.test.utils._TestState') as x: + del x.saved_data + with self.settings(ALLOWED_HOSTS=allowed_hosts): + setup_test_environment() + self.assertEqual(settings.ALLOWED_HOSTS, ['*', 'testserver']) + class OverrideSettingsTests(SimpleTestCase): @@ -967,9 +1117,9 @@ def test_override_database_routers(self): """ Overriding DATABASE_ROUTERS should update the master router. """ - test_routers = (object(),) + test_routers = [object()] with self.settings(DATABASE_ROUTERS=test_routers): - self.assertSequenceEqual(router.routers, test_routers) + self.assertEqual(router.routers, test_routers) def test_override_static_url(self): """ @@ -987,14 +1137,14 @@ def test_override_static_root(self): django.contrib.staticfiles.storage.staticfiles_storage. """ with self.settings(STATIC_ROOT='/tmp/test'): - self.assertEqual(staticfiles_storage.location, abspathu('/tmp/test')) + self.assertEqual(staticfiles_storage.location, os.path.abspath('/tmp/test')) def test_override_staticfiles_storage(self): """ Overriding the STATICFILES_STORAGE setting should be reflected in the value of django.contrib.staticfiles.storage.staticfiles_storage. """ - new_class = 'CachedStaticFilesStorage' + new_class = 'ManifestStaticFilesStorage' new_storage = 'django.contrib.staticfiles.storage.' + new_class with self.settings(STATICFILES_STORAGE=new_storage): self.assertEqual(staticfiles_storage.__class__.__name__, new_class) @@ -1036,7 +1186,7 @@ class MyException(Exception): @classmethod def setUpClass(cls): try: - super(TestBadSetUpTestData, cls).setUpClass() + super().setUpClass() except cls.MyException: cls._in_atomic_block = connection.in_atomic_block @@ -1059,34 +1209,82 @@ def test_failure_in_setUpTestData_should_rollback_transaction(self): class DisallowedDatabaseQueriesTests(SimpleTestCase): + def test_disallowed_database_connections(self): + expected_message = ( + "Database connections to 'default' are not allowed in SimpleTestCase " + "subclasses. Either subclass TestCase or TransactionTestCase to " + "ensure proper test isolation or add 'default' to " + "test_utils.tests.DisallowedDatabaseQueriesTests.databases to " + "silence this failure." + ) + with self.assertRaisesMessage(AssertionError, expected_message): + connection.connect() + with self.assertRaisesMessage(AssertionError, expected_message): + connection.temporary_connection() + def test_disallowed_database_queries(self): expected_message = ( - "Database queries aren't allowed in SimpleTestCase. " - "Either use TestCase or TransactionTestCase to ensure proper test isolation or " - "set DisallowedDatabaseQueriesTests.allow_database_queries to True to silence this failure." + "Database queries to 'default' are not allowed in SimpleTestCase " + "subclasses. Either subclass TestCase or TransactionTestCase to " + "ensure proper test isolation or add 'default' to " + "test_utils.tests.DisallowedDatabaseQueriesTests.databases to " + "silence this failure." ) with self.assertRaisesMessage(AssertionError, expected_message): Car.objects.first() - -class DisallowedDatabaseQueriesChunkedCursorsTests(SimpleTestCase): - def test_disallowed_database_queries(self): + def test_disallowed_database_chunked_cursor_queries(self): expected_message = ( - "Database queries aren't allowed in SimpleTestCase. Either use " - "TestCase or TransactionTestCase to ensure proper test isolation or " - "set DisallowedDatabaseQueriesChunkedCursorsTests.allow_database_queries " - "to True to silence this failure." + "Database queries to 'default' are not allowed in SimpleTestCase " + "subclasses. Either subclass TestCase or TransactionTestCase to " + "ensure proper test isolation or add 'default' to " + "test_utils.tests.DisallowedDatabaseQueriesTests.databases to " + "silence this failure." ) with self.assertRaisesMessage(AssertionError, expected_message): next(Car.objects.iterator()) class AllowedDatabaseQueriesTests(SimpleTestCase): - allow_database_queries = True + databases = {'default'} def test_allowed_database_queries(self): Car.objects.first() + def test_allowed_database_chunked_cursor_queries(self): + next(Car.objects.iterator(), None) + + +class DatabaseAliasTests(SimpleTestCase): + def setUp(self): + self.addCleanup(setattr, self.__class__, 'databases', self.databases) + + def test_no_close_match(self): + self.__class__.databases = {'void'} + message = ( + "test_utils.tests.DatabaseAliasTests.databases refers to 'void' which is not defined " + "in settings.DATABASES." + ) + with self.assertRaisesMessage(ImproperlyConfigured, message): + self._validate_databases() + + def test_close_match(self): + self.__class__.databases = {'defualt'} + message = ( + "test_utils.tests.DatabaseAliasTests.databases refers to 'defualt' which is not defined " + "in settings.DATABASES. Did you mean 'default'?" + ) + with self.assertRaisesMessage(ImproperlyConfigured, message): + self._validate_databases() + + def test_match(self): + self.__class__.databases = {'default', 'other'} + self.assertEqual(self._validate_databases(), frozenset({'default', 'other'})) + + def test_all(self): + self.__class__.databases = '__all__' + self.assertEqual(self._validate_databases(), frozenset(connections)) + @isolate_apps('test_utils', attr_name='class_apps') class IsolatedAppsTests(SimpleTestCase): @@ -1123,3 +1321,28 @@ class NestedContextManager(models.Model): self.assertEqual(MethodDecoration._meta.apps, method_apps) self.assertEqual(ContextManager._meta.apps, context_apps) self.assertEqual(NestedContextManager._meta.apps, nested_context_apps) + + +class DoNothingDecorator(TestContextDecorator): + def enable(self): + pass + + def disable(self): + pass + + +class TestContextDecoratorTests(SimpleTestCase): + + @mock.patch.object(DoNothingDecorator, 'disable') + def test_exception_in_setup(self, mock_disable): + """An exception is setUp() is reraised after disable() is called.""" + class ExceptionInSetUp(unittest.TestCase): + def setUp(self): + raise NotImplementedError('reraised') + + decorator = DoNothingDecorator() + decorated_test_class = decorator.__call__(ExceptionInSetUp)() + self.assertFalse(mock_disable.called) + with self.assertRaisesMessage(NotImplementedError, 'reraised'): + decorated_test_class.setUp() + self.assertTrue(mock_disable.called) diff --git a/tests/test_utils/urls.py b/tests/test_utils/urls.py index a88645172198..6b060dff9531 100644 --- a/tests/test_utils/urls.py +++ b/tests/test_utils/urls.py @@ -1,8 +1,8 @@ -from django.conf.urls import url +from django.urls import path from . import views urlpatterns = [ - url(r'^test_utils/get_person/([0-9]+)/$', views.get_person), - url(r'^test_utils/no_template_used/$', views.no_template_used), + path('test_utils/get_person//', views.get_person), + path('test_utils/no_template_used/', views.no_template_used, name='no_template_used'), ] diff --git a/tests/timezones/tests.py b/tests/timezones/tests.py index 8f9bd23241de..b092a16044fd 100644 --- a/tests/timezones/tests.py +++ b/tests/timezones/tests.py @@ -1,9 +1,6 @@ -from __future__ import unicode_literals - import datetime import re import sys -import warnings from contextlib import contextmanager from unittest import SkipTest, skipIf from xml.dom.minidom import parseString @@ -25,7 +22,7 @@ ) from django.test.utils import requires_tz_support from django.urls import reverse -from django.utils import six, timezone +from django.utils import timezone from django.utils.timezone import timedelta from .forms import ( @@ -36,9 +33,16 @@ AllDayEvent, Event, MaybeEvent, Session, SessionEvent, Timestamp, ) +try: + import yaml + HAS_YAML = True +except ImportError: + HAS_YAML = False + # These tests use the EAT (Eastern Africa Time) and ICT (Indochina Time) # who don't have Daylight Saving Time, so we can represent them easily -# with FixedOffset, and use them directly as tzinfo in the constructors. +# with fixed offset timezones and use them directly as tzinfo in the +# constructors. # settings.TIME_ZONE is forced to EAT. Most tests use a variant of # datetime.datetime(2011, 9, 1, 13, 20, 30), which translates to @@ -58,21 +62,12 @@ def test_naive_datetime(self): event = Event.objects.get() self.assertEqual(event.dt, dt) - @skipUnlessDBFeature('supports_microsecond_precision') def test_naive_datetime_with_microsecond(self): dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060) Event.objects.create(dt=dt) event = Event.objects.get() self.assertEqual(event.dt, dt) - @skipIfDBFeature('supports_microsecond_precision') - def test_naive_datetime_with_microsecond_unsupported(self): - dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060) - Event.objects.create(dt=dt) - event = Event.objects.get() - # microseconds are lost during a round-trip in the database - self.assertEqual(event.dt, dt.replace(microsecond=0)) - @skipUnlessDBFeature('supports_timezones') def test_aware_datetime_in_local_timezone(self): dt = datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT) @@ -83,7 +78,6 @@ def test_aware_datetime_in_local_timezone(self): self.assertEqual(event.dt.replace(tzinfo=EAT), dt) @skipUnlessDBFeature('supports_timezones') - @skipUnlessDBFeature('supports_microsecond_precision') def test_aware_datetime_in_local_timezone_with_microsecond(self): dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060, tzinfo=EAT) Event.objects.create(dt=dt) @@ -92,18 +86,6 @@ def test_aware_datetime_in_local_timezone_with_microsecond(self): # interpret the naive datetime in local time to get the correct value self.assertEqual(event.dt.replace(tzinfo=EAT), dt) - # This combination actually never happens. - @skipUnlessDBFeature('supports_timezones') - @skipIfDBFeature('supports_microsecond_precision') - def test_aware_datetime_in_local_timezone_with_microsecond_unsupported(self): - dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060, tzinfo=EAT) - Event.objects.create(dt=dt) - event = Event.objects.get() - self.assertIsNone(event.dt.tzinfo) - # interpret the naive datetime in local time to get the correct value - # microseconds are lost during a round-trip in the database - self.assertEqual(event.dt.replace(tzinfo=EAT), dt.replace(microsecond=0)) - @skipUnlessDBFeature('supports_timezones') def test_aware_datetime_in_utc(self): dt = datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC) @@ -125,7 +107,8 @@ def test_aware_datetime_in_other_timezone(self): @skipIfDBFeature('supports_timezones') def test_aware_datetime_unsupported(self): dt = datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT) - with self.assertRaises(ValueError): + msg = 'backend does not support timezone-aware datetimes when USE_TZ is False.' + with self.assertRaisesMessage(ValueError, msg): Event.objects.create(dt=dt) def test_auto_now_and_auto_now_add(self): @@ -183,15 +166,18 @@ def test_query_annotation(self): self.assertQuerysetEqual( Session.objects.annotate(dt=Min('events__dt')).order_by('dt'), [morning_min_dt, afternoon_min_dt], - transform=lambda d: d.dt) + transform=lambda d: d.dt, + ) self.assertQuerysetEqual( Session.objects.annotate(dt=Min('events__dt')).filter(dt__lt=afternoon_min_dt), [morning_min_dt], - transform=lambda d: d.dt) + transform=lambda d: d.dt, + ) self.assertQuerysetEqual( Session.objects.annotate(dt=Min('events__dt')).filter(dt__gte=afternoon_min_dt), [afternoon_min_dt], - transform=lambda d: d.dt) + transform=lambda d: d.dt, + ) def test_query_datetimes(self): Event.objects.create(dt=datetime.datetime(2011, 1, 1, 1, 30, 0)) @@ -219,7 +205,7 @@ def test_raw_sql(self): # Regression test for #17755 dt = datetime.datetime(2011, 9, 1, 13, 20, 30) event = Event.objects.create(dt=dt) - self.assertSequenceEqual(list(Event.objects.raw('SELECT * FROM timezones_event WHERE dt = %s', [dt])), [event]) + self.assertEqual(list(Event.objects.raw('SELECT * FROM timezones_event WHERE dt = %s', [dt])), [event]) def test_cursor_execute_accepts_naive_datetime(self): dt = datetime.datetime(2011, 9, 1, 13, 20, 30) @@ -246,17 +232,13 @@ def test_filter_date_field_with_aware_datetime(self): @override_settings(TIME_ZONE='Africa/Nairobi', USE_TZ=True) class NewDatabaseTests(TestCase): + naive_warning = 'DateTimeField Event.dt received a naive datetime' @requires_tz_support def test_naive_datetime(self): dt = datetime.datetime(2011, 9, 1, 13, 20, 30) - with warnings.catch_warnings(record=True) as recorded: - warnings.simplefilter('always') + with self.assertWarnsMessage(RuntimeWarning, self.naive_warning): Event.objects.create(dt=dt) - self.assertEqual(len(recorded), 1) - msg = str(recorded[0].message) - self.assertTrue(msg.startswith("DateTimeField Event.dt received " - "a naive datetime")) event = Event.objects.get() # naive datetimes are interpreted in local time self.assertEqual(event.dt, dt.replace(tzinfo=EAT)) @@ -264,68 +246,32 @@ def test_naive_datetime(self): @requires_tz_support def test_datetime_from_date(self): dt = datetime.date(2011, 9, 1) - with warnings.catch_warnings(record=True) as recorded: - warnings.simplefilter('always') + with self.assertWarnsMessage(RuntimeWarning, self.naive_warning): Event.objects.create(dt=dt) - self.assertEqual(len(recorded), 1) - msg = str(recorded[0].message) - self.assertTrue(msg.startswith("DateTimeField Event.dt received " - "a naive datetime")) event = Event.objects.get() self.assertEqual(event.dt, datetime.datetime(2011, 9, 1, tzinfo=EAT)) @requires_tz_support - @skipUnlessDBFeature('supports_microsecond_precision') def test_naive_datetime_with_microsecond(self): dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060) - with warnings.catch_warnings(record=True) as recorded: - warnings.simplefilter('always') + with self.assertWarnsMessage(RuntimeWarning, self.naive_warning): Event.objects.create(dt=dt) - self.assertEqual(len(recorded), 1) - msg = str(recorded[0].message) - self.assertTrue(msg.startswith("DateTimeField Event.dt received " - "a naive datetime")) event = Event.objects.get() # naive datetimes are interpreted in local time self.assertEqual(event.dt, dt.replace(tzinfo=EAT)) - @requires_tz_support - @skipIfDBFeature('supports_microsecond_precision') - def test_naive_datetime_with_microsecond_unsupported(self): - dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060) - with warnings.catch_warnings(record=True) as recorded: - warnings.simplefilter('always') - Event.objects.create(dt=dt) - self.assertEqual(len(recorded), 1) - msg = str(recorded[0].message) - self.assertTrue(msg.startswith("DateTimeField Event.dt received " - "a naive datetime")) - event = Event.objects.get() - # microseconds are lost during a round-trip in the database - # naive datetimes are interpreted in local time - self.assertEqual(event.dt, dt.replace(microsecond=0, tzinfo=EAT)) - def test_aware_datetime_in_local_timezone(self): dt = datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT) Event.objects.create(dt=dt) event = Event.objects.get() self.assertEqual(event.dt, dt) - @skipUnlessDBFeature('supports_microsecond_precision') def test_aware_datetime_in_local_timezone_with_microsecond(self): dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060, tzinfo=EAT) Event.objects.create(dt=dt) event = Event.objects.get() self.assertEqual(event.dt, dt) - @skipIfDBFeature('supports_microsecond_precision') - def test_aware_datetime_in_local_timezone_with_microsecond_unsupported(self): - dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060, tzinfo=EAT) - Event.objects.create(dt=dt) - event = Event.objects.get() - # microseconds are lost during a round-trip in the database - self.assertEqual(event.dt, dt.replace(microsecond=0)) - def test_aware_datetime_in_utc(self): dt = datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC) Event.objects.create(dt=dt) @@ -376,17 +322,13 @@ def test_query_filter_with_naive_datetime(self): dt = datetime.datetime(2011, 9, 1, 12, 20, 30, tzinfo=EAT) Event.objects.create(dt=dt) dt = dt.replace(tzinfo=None) - with warnings.catch_warnings(record=True) as recorded: - warnings.simplefilter('always') - # naive datetimes are interpreted in local time + # naive datetimes are interpreted in local time + with self.assertWarnsMessage(RuntimeWarning, self.naive_warning): self.assertEqual(Event.objects.filter(dt__exact=dt).count(), 1) + with self.assertWarnsMessage(RuntimeWarning, self.naive_warning): self.assertEqual(Event.objects.filter(dt__lte=dt).count(), 1) + with self.assertWarnsMessage(RuntimeWarning, self.naive_warning): self.assertEqual(Event.objects.filter(dt__gt=dt).count(), 0) - self.assertEqual(len(recorded), 3) - for warning in recorded: - msg = str(warning.message) - self.assertTrue(msg.startswith("DateTimeField Event.dt " - "received a naive datetime")) @skipUnlessDBFeature('has_zoneinfo_database') def test_query_datetime_lookups(self): @@ -438,15 +380,18 @@ def test_query_annotation(self): self.assertQuerysetEqual( Session.objects.annotate(dt=Min('events__dt')).order_by('dt'), [morning_min_dt, afternoon_min_dt], - transform=lambda d: d.dt) + transform=lambda d: d.dt, + ) self.assertQuerysetEqual( Session.objects.annotate(dt=Min('events__dt')).filter(dt__lt=afternoon_min_dt), [morning_min_dt], - transform=lambda d: d.dt) + transform=lambda d: d.dt, + ) self.assertQuerysetEqual( Session.objects.annotate(dt=Min('events__dt')).filter(dt__gte=afternoon_min_dt), [afternoon_min_dt], - transform=lambda d: d.dt) + transform=lambda d: d.dt, + ) @skipUnlessDBFeature('has_zoneinfo_database') def test_query_datetimes(self): @@ -598,7 +543,7 @@ def setUpClass(cls): if not connection.features.test_db_allows_multiple_connections: raise SkipTest("Database doesn't support feature(s): test_db_allows_multiple_connections") - super(ForcedTimeZoneDatabaseTests, cls).setUpClass() + super().setUpClass() @contextmanager def override_database_connection_timezone(self, timezone): @@ -649,7 +594,11 @@ def test_time_zone_parameter_not_supported_if_database_supports_timezone(self): connections.databases['tz']['TIME_ZONE'] = 'Asia/Bangkok' tz_conn = connections['tz'] try: - with self.assertRaises(ImproperlyConfigured): + msg = ( + "Connection 'tz' cannot set TIME_ZONE because its engine " + "handles time zones conversions natively." + ) + with self.assertRaisesMessage(ImproperlyConfigured, msg): tz_conn.cursor() finally: connections['tz'].close() # in case the test fails @@ -662,9 +611,10 @@ class SerializationTests(SimpleTestCase): # Backend-specific notes: # - JSON supports only milliseconds, microseconds will be truncated. - # - PyYAML dumps the UTC offset correctly for timezone-aware datetimes, - # but when it loads this representation, it subtracts the offset and - # returns a naive datetime object in UTC (http://pyyaml.org/ticket/202). + # - PyYAML dumps the UTC offset correctly for timezone-aware datetimes. + # When PyYAML < 5.3 loads this representation, it subtracts the offset + # and returns a naive datetime object in UTC. PyYAML 5.3+ loads timezones + # correctly. # Tests are adapted to take these quirks into account. def assert_python_contains_datetime(self, objects, dt): @@ -700,7 +650,7 @@ def test_naive_datetime(self): self.assertEqual(obj.dt, dt) if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer): - data = serializers.serialize('yaml', [Event(dt=dt)]) + data = serializers.serialize('yaml', [Event(dt=dt)], default_flow_style=None) self.assert_yaml_contains_datetime(data, "2011-09-01 13:20:30") obj = next(serializers.deserialize('yaml', data)).object self.assertEqual(obj.dt, dt) @@ -724,7 +674,7 @@ def test_naive_datetime_with_microsecond(self): self.assertEqual(obj.dt, dt) if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer): - data = serializers.serialize('yaml', [Event(dt=dt)]) + data = serializers.serialize('yaml', [Event(dt=dt)], default_flow_style=None) self.assert_yaml_contains_datetime(data, "2011-09-01 13:20:30.405060") obj = next(serializers.deserialize('yaml', data)).object self.assertEqual(obj.dt, dt) @@ -748,10 +698,13 @@ def test_aware_datetime_with_microsecond(self): self.assertEqual(obj.dt, dt) if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer): - data = serializers.serialize('yaml', [Event(dt=dt)]) + data = serializers.serialize('yaml', [Event(dt=dt)], default_flow_style=None) self.assert_yaml_contains_datetime(data, "2011-09-01 17:20:30.405060+07:00") obj = next(serializers.deserialize('yaml', data)).object - self.assertEqual(obj.dt.replace(tzinfo=UTC), dt) + if HAS_YAML and yaml.__version__ < '5.3': + self.assertEqual(obj.dt.replace(tzinfo=UTC), dt) + else: + self.assertEqual(obj.dt, dt) def test_aware_datetime_in_utc(self): dt = datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC) @@ -772,7 +725,7 @@ def test_aware_datetime_in_utc(self): self.assertEqual(obj.dt, dt) if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer): - data = serializers.serialize('yaml', [Event(dt=dt)]) + data = serializers.serialize('yaml', [Event(dt=dt)], default_flow_style=None) self.assert_yaml_contains_datetime(data, "2011-09-01 10:20:30+00:00") obj = next(serializers.deserialize('yaml', data)).object self.assertEqual(obj.dt.replace(tzinfo=UTC), dt) @@ -796,10 +749,13 @@ def test_aware_datetime_in_local_timezone(self): self.assertEqual(obj.dt, dt) if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer): - data = serializers.serialize('yaml', [Event(dt=dt)]) + data = serializers.serialize('yaml', [Event(dt=dt)], default_flow_style=None) self.assert_yaml_contains_datetime(data, "2011-09-01 13:20:30+03:00") obj = next(serializers.deserialize('yaml', data)).object - self.assertEqual(obj.dt.replace(tzinfo=UTC), dt) + if HAS_YAML and yaml.__version__ < '5.3': + self.assertEqual(obj.dt.replace(tzinfo=UTC), dt) + else: + self.assertEqual(obj.dt, dt) def test_aware_datetime_in_other_timezone(self): dt = datetime.datetime(2011, 9, 1, 17, 20, 30, tzinfo=ICT) @@ -820,10 +776,13 @@ def test_aware_datetime_in_other_timezone(self): self.assertEqual(obj.dt, dt) if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer): - data = serializers.serialize('yaml', [Event(dt=dt)]) + data = serializers.serialize('yaml', [Event(dt=dt)], default_flow_style=None) self.assert_yaml_contains_datetime(data, "2011-09-01 17:20:30+07:00") obj = next(serializers.deserialize('yaml', data)).object - self.assertEqual(obj.dt.replace(tzinfo=UTC), dt) + if HAS_YAML and yaml.__version__ < '5.3': + self.assertEqual(obj.dt.replace(tzinfo=UTC), dt) + else: + self.assertEqual(obj.dt, dt) @override_settings(DATETIME_FORMAT='c', TIME_ZONE='Africa/Nairobi', USE_L10N=False, USE_TZ=True) @@ -890,8 +849,8 @@ def t(*result): } } - for k1, dt in six.iteritems(datetimes): - for k2, tpl in six.iteritems(templates): + for k1, dt in datetimes.items(): + for k2, tpl in templates.items(): ctx = Context({'dt': dt, 'ICT': ICT}) actual = tpl.render(ctx) expected = results[k1][k2] @@ -903,8 +862,8 @@ def t(*result): results['ict']['notag'] = t('ict', 'eat', 'utc', 'ict') with self.settings(USE_TZ=False): - for k1, dt in six.iteritems(datetimes): - for k2, tpl in six.iteritems(templates): + for k1, dt in datetimes.items(): + for k2, tpl in templates.items(): ctx = Context({'dt': dt, 'ICT': ICT}) actual = tpl.render(ctx) expected = results[k1][k2] @@ -923,14 +882,18 @@ def test_localtime_filters_with_pytz(self): # Use a pytz timezone as argument tpl = Template("{% load tz %}{{ dt|timezone:tz }}") - ctx = Context({'dt': datetime.datetime(2011, 9, 1, 13, 20, 30), - 'tz': pytz.timezone('Europe/Paris')}) + ctx = Context({ + 'dt': datetime.datetime(2011, 9, 1, 13, 20, 30), + 'tz': pytz.timezone('Europe/Paris'), + }) self.assertEqual(tpl.render(ctx), "2011-09-01T12:20:30+02:00") # Use a pytz timezone name as argument tpl = Template("{% load tz %}{{ dt|timezone:'Europe/Paris' }}") - ctx = Context({'dt': datetime.datetime(2011, 9, 1, 13, 20, 30), - 'tz': pytz.timezone('Europe/Paris')}) + ctx = Context({ + 'dt': datetime.datetime(2011, 9, 1, 13, 20, 30), + 'tz': pytz.timezone('Europe/Paris'), + }) self.assertEqual(tpl.render(ctx), "2011-09-01T12:20:30+02:00") def test_localtime_templatetag_invalid_argument(self): @@ -970,8 +933,11 @@ def test_timezone_templatetag(self): "{% endtimezone %}" "{% endtimezone %}" ) - ctx = Context({'dt': datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC), - 'tz1': ICT, 'tz2': None}) + ctx = Context({ + 'dt': datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC), + 'tz1': ICT, + 'tz2': None, + }) self.assertEqual( tpl.render(ctx), "2011-09-01T13:20:30+03:00|2011-09-01T17:20:30+07:00|2011-09-01T13:20:30+03:00" @@ -984,13 +950,17 @@ def test_timezone_templatetag_with_pytz(self): tpl = Template("{% load tz %}{% timezone tz %}{{ dt }}{% endtimezone %}") # Use a pytz timezone as argument - ctx = Context({'dt': datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT), - 'tz': pytz.timezone('Europe/Paris')}) + ctx = Context({ + 'dt': datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT), + 'tz': pytz.timezone('Europe/Paris'), + }) self.assertEqual(tpl.render(ctx), "2011-09-01T12:20:30+02:00") # Use a pytz timezone name as argument - ctx = Context({'dt': datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT), - 'tz': 'Europe/Paris'}) + ctx = Context({ + 'dt': datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT), + 'tz': 'Europe/Paris', + }) self.assertEqual(tpl.render(ctx), "2011-09-01T12:20:30+02:00") def test_timezone_templatetag_invalid_argument(self): @@ -1035,7 +1005,8 @@ def test_get_current_timezone_templatetag_with_pytz(self): self.assertEqual(tpl.render(Context()), "Europe/Paris") def test_get_current_timezone_templatetag_invalid_argument(self): - with self.assertRaises(TemplateSyntaxError): + msg = "'get_current_timezone' requires 'as variable' (got ['get_current_timezone'])" + with self.assertRaisesMessage(TemplateSyntaxError, msg): Template("{% load tz %}{% get_current_timezone %}").render() @skipIf(sys.platform.startswith('win'), "Windows uses non-standard time zone names") diff --git a/tests/timezones/urls.py b/tests/timezones/urls.py index 84b13b593d91..3955b5354fe5 100644 --- a/tests/timezones/urls.py +++ b/tests/timezones/urls.py @@ -1,7 +1,7 @@ -from django.conf.urls import url +from django.urls import path from . import admin as tz_admin # NOQA: register tz_admin urlpatterns = [ - url(r'^admin/', tz_admin.site.urls), + path('admin/', tz_admin.site.urls), ] diff --git a/tests/transaction_hooks/models.py b/tests/transaction_hooks/models.py index cd2f22b514b9..d3abbfef91dc 100644 --- a/tests/transaction_hooks/models.py +++ b/tests/transaction_hooks/models.py @@ -1,8 +1,6 @@ from django.db import models -from django.utils.encoding import python_2_unicode_compatible -@python_2_unicode_compatible class Thing(models.Model): num = models.IntegerField() diff --git a/tests/transaction_hooks/tests.py b/tests/transaction_hooks/tests.py index 000de4104cc8..81ff0066a189 100644 --- a/tests/transaction_hooks/tests.py +++ b/tests/transaction_hooks/tests.py @@ -228,7 +228,8 @@ def should_never_be_called(): try: connection.set_autocommit(False) - with self.assertRaises(transaction.TransactionManagementError): + msg = 'on_commit() cannot be used in manual transaction management' + with self.assertRaisesMessage(transaction.TransactionManagementError, msg): transaction.on_commit(should_never_be_called) finally: connection.set_autocommit(True) diff --git a/tests/transactions/models.py b/tests/transactions/models.py index f4400363e568..2f2bac463a16 100644 --- a/tests/transactions/models.py +++ b/tests/transactions/models.py @@ -6,13 +6,9 @@ commit-on-success behavior. Alternatively, you can manage the transaction manually. """ -from __future__ import unicode_literals - from django.db import models -from django.utils.encoding import python_2_unicode_compatible -@python_2_unicode_compatible class Reporter(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) diff --git a/tests/transactions/tests.py b/tests/transactions/tests.py index 7dc21cca4f66..af3416aeaa19 100644 --- a/tests/transactions/tests.py +++ b/tests/transactions/tests.py @@ -1,7 +1,3 @@ -from __future__ import unicode_literals - -import os -import signal import sys import threading import time @@ -18,7 +14,7 @@ from .models import Reporter -@skipUnless(connection.features.uses_savepoints, "'atomic' requires transactions and savepoints.") +@skipUnlessDBFeature('uses_savepoints') class AtomicTests(TransactionTestCase): """ Tests for the atomic decorator and context manager. @@ -220,18 +216,6 @@ def test_prevent_rollback(self): transaction.savepoint_rollback(sid) self.assertQuerysetEqual(Reporter.objects.all(), ['']) - @skipIf(sys.platform.startswith('win'), "Windows doesn't have signals.") - def test_rollback_on_keyboardinterrupt(self): - try: - with transaction.atomic(): - Reporter.objects.create(first_name='Tintin') - # Send SIGINT (simulate Ctrl-C). One call isn't enough. - os.kill(os.getpid(), signal.SIGINT) - os.kill(os.getpid(), signal.SIGINT) - except KeyboardInterrupt: - pass - self.assertEqual(Reporter.objects.all().count(), 0) - class AtomicInsideTransactionTests(AtomicTests): """All basic tests for atomic should also pass within an existing transaction.""" @@ -244,10 +228,7 @@ def tearDown(self): self.atomic.__exit__(*sys.exc_info()) -@skipIf( - connection.features.autocommits_when_autocommit_is_off, - "This test requires a non-autocommit mode that doesn't autocommit." -) +@skipIfDBFeature('autocommits_when_autocommit_is_off') class AtomicWithoutAutocommitTests(AtomicTests): """All basic tests for atomic should also pass when autocommit is turned off.""" @@ -261,7 +242,7 @@ def tearDown(self): transaction.set_autocommit(True) -@skipUnless(connection.features.uses_savepoints, "'atomic' requires transactions and savepoints.") +@skipUnlessDBFeature('uses_savepoints') class AtomicMergeTests(TransactionTestCase): """Test merging transactions with savepoint=False.""" @@ -311,24 +292,25 @@ def test_merged_inner_savepoint_rollback(self): self.assertQuerysetEqual(Reporter.objects.all(), ['']) -@skipUnless(connection.features.uses_savepoints, "'atomic' requires transactions and savepoints.") +@skipUnlessDBFeature('uses_savepoints') class AtomicErrorsTests(TransactionTestCase): available_apps = ['transactions'] + forbidden_atomic_msg = "This is forbidden when an 'atomic' block is active." def test_atomic_prevents_setting_autocommit(self): autocommit = transaction.get_autocommit() with transaction.atomic(): - with self.assertRaises(transaction.TransactionManagementError): + with self.assertRaisesMessage(transaction.TransactionManagementError, self.forbidden_atomic_msg): transaction.set_autocommit(not autocommit) # Make sure autocommit wasn't changed. self.assertEqual(connection.autocommit, autocommit) def test_atomic_prevents_calling_transaction_methods(self): with transaction.atomic(): - with self.assertRaises(transaction.TransactionManagementError): + with self.assertRaisesMessage(transaction.TransactionManagementError, self.forbidden_atomic_msg): transaction.commit() - with self.assertRaises(transaction.TransactionManagementError): + with self.assertRaisesMessage(transaction.TransactionManagementError, self.forbidden_atomic_msg): transaction.rollback() def test_atomic_prevents_queries_in_broken_transaction(self): @@ -338,7 +320,11 @@ def test_atomic_prevents_queries_in_broken_transaction(self): with self.assertRaises(IntegrityError): r2.save(force_insert=True) # The transaction is marked as needing rollback. - with self.assertRaises(transaction.TransactionManagementError): + msg = ( + "An error occurred in the current transaction. You can't " + "execute queries until the end of the 'atomic' block." + ) + with self.assertRaisesMessage(transaction.TransactionManagementError, msg): r2.save(force_update=True) self.assertEqual(Reporter.objects.get(pk=r1.pk).last_name, "Haddock") @@ -377,18 +363,17 @@ class AtomicMySQLTests(TransactionTestCase): @skipIf(threading is None, "Test requires threading") def test_implicit_savepoint_rollback(self): """MySQL implicitly rolls back savepoints when it deadlocks (#22291).""" + Reporter.objects.create(id=1) + Reporter.objects.create(id=2) - other_thread_ready = threading.Event() + main_thread_ready = threading.Event() def other_thread(): try: with transaction.atomic(): - Reporter.objects.create(id=1, first_name="Tintin") - other_thread_ready.set() - # We cannot synchronize the two threads with an event here - # because the main thread locks. Sleep for a little while. - time.sleep(1) - # 2) ... and this line deadlocks. (see below for 1) + Reporter.objects.select_for_update().get(id=1) + main_thread_ready.wait() + # 1) This line locks... (see below for 2) Reporter.objects.exclude(id=1).update(id=2) finally: # This is the thread-local connection, not the main connection. @@ -396,26 +381,30 @@ def other_thread(): other_thread = threading.Thread(target=other_thread) other_thread.start() - other_thread_ready.wait() with self.assertRaisesMessage(OperationalError, 'Deadlock found'): # Double atomic to enter a transaction and create a savepoint. with transaction.atomic(): with transaction.atomic(): - # 1) This line locks... (see above for 2) - Reporter.objects.create(id=1, first_name="Tintin") + Reporter.objects.select_for_update().get(id=2) + main_thread_ready.set() + # The two threads can't be synchronized with an event here + # because the other thread locks. Sleep for a little while. + time.sleep(1) + # 2) ... and this line deadlocks. (see above for 1) + Reporter.objects.exclude(id=2).update(id=1) other_thread.join() class AtomicMiscTests(TransactionTestCase): - available_apps = [] + available_apps = ['transactions'] def test_wrap_callable_instance(self): """#20028 -- Atomic must support wrapping callable instances.""" - class Callable(object): + class Callable: def __call__(self): pass @@ -444,11 +433,54 @@ def test_atomic_does_not_leak_savepoints_on_failure(self): # This is expected to fail because the savepoint no longer exists. connection.savepoint_rollback(sid) + def test_mark_for_rollback_on_error_in_transaction(self): + with transaction.atomic(savepoint=False): -@skipIf( - connection.features.autocommits_when_autocommit_is_off, - "This test requires a non-autocommit mode that doesn't autocommit." -) + # Swallow the intentional error raised. + with self.assertRaisesMessage(Exception, "Oops"): + + # Wrap in `mark_for_rollback_on_error` to check if the transaction is marked broken. + with transaction.mark_for_rollback_on_error(): + + # Ensure that we are still in a good state. + self.assertFalse(transaction.get_rollback()) + + raise Exception("Oops") + + # Ensure that `mark_for_rollback_on_error` marked the transaction as broken … + self.assertTrue(transaction.get_rollback()) + + # … and further queries fail. + msg = "You can't execute queries until the end of the 'atomic' block." + with self.assertRaisesMessage(transaction.TransactionManagementError, msg): + Reporter.objects.create() + + # Transaction errors are reset at the end of an transaction, so this should just work. + Reporter.objects.create() + + def test_mark_for_rollback_on_error_in_autocommit(self): + self.assertTrue(transaction.get_autocommit()) + + # Swallow the intentional error raised. + with self.assertRaisesMessage(Exception, "Oops"): + + # Wrap in `mark_for_rollback_on_error` to check if the transaction is marked broken. + with transaction.mark_for_rollback_on_error(): + + # Ensure that we are still in a good state. + self.assertFalse(transaction.get_connection().needs_rollback) + + raise Exception("Oops") + + # Ensure that `mark_for_rollback_on_error` did not mark the transaction + # as broken, since we are in autocommit mode … + self.assertFalse(transaction.get_connection().needs_rollback) + + # … and further queries work nicely. + Reporter.objects.create() + + +@skipIfDBFeature('autocommits_when_autocommit_is_off') class NonAutocommitTests(TransactionTestCase): available_apps = [] diff --git a/tests/unmanaged_models/models.py b/tests/unmanaged_models/models.py index e925752a0679..c8da49aa0798 100644 --- a/tests/unmanaged_models/models.py +++ b/tests/unmanaged_models/models.py @@ -4,13 +4,10 @@ """ from django.db import models -from django.utils.encoding import python_2_unicode_compatible - # All of these models are created in the database by Django. -@python_2_unicode_compatible class A01(models.Model): f_a = models.CharField(max_length=10, db_index=True) f_b = models.IntegerField() @@ -22,7 +19,6 @@ def __str__(self): return self.f_a -@python_2_unicode_compatible class B01(models.Model): fk_a = models.ForeignKey(A01, models.CASCADE) f_a = models.CharField(max_length=10, db_index=True) @@ -37,7 +33,6 @@ def __str__(self): return self.f_a -@python_2_unicode_compatible class C01(models.Model): mm_a = models.ManyToManyField(A01, db_table='d01') f_a = models.CharField(max_length=10, db_index=True) @@ -54,7 +49,6 @@ def __str__(self): # since we have told Django they aren't managed by Django. -@python_2_unicode_compatible class A02(models.Model): f_a = models.CharField(max_length=10, db_index=True) @@ -66,7 +60,6 @@ def __str__(self): return self.f_a -@python_2_unicode_compatible class B02(models.Model): class Meta: db_table = 'b01' @@ -82,7 +75,6 @@ def __str__(self): # To re-use the many-to-many intermediate table, we need to manually set up # things up. -@python_2_unicode_compatible class C02(models.Model): mm_a = models.ManyToManyField(A02, through="Intermediate") f_a = models.CharField(max_length=10, db_index=True) diff --git a/tests/unmanaged_models/tests.py b/tests/unmanaged_models/tests.py index e98cee8ae178..250aaaa4354b 100644 --- a/tests/unmanaged_models/tests.py +++ b/tests/unmanaged_models/tests.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.db import connection from django.test import TestCase diff --git a/tests/update/models.py b/tests/update/models.py index 648a7733184c..b56dd2258ecd 100644 --- a/tests/update/models.py +++ b/tests/update/models.py @@ -4,27 +4,23 @@ """ from django.db import models -from django.utils import six -from django.utils.encoding import python_2_unicode_compatible -@python_2_unicode_compatible class DataPoint(models.Model): name = models.CharField(max_length=20) value = models.CharField(max_length=20) another_value = models.CharField(max_length=20, blank=True) def __str__(self): - return six.text_type(self.name) + return self.name -@python_2_unicode_compatible class RelatedPoint(models.Model): name = models.CharField(max_length=20) data = models.ForeignKey(DataPoint, models.CASCADE) def __str__(self): - return six.text_type(self.name) + return self.name class A(models.Model): @@ -50,3 +46,4 @@ class Foo(models.Model): class Bar(models.Model): foo = models.ForeignKey(Foo, models.CASCADE, to_field='target') + m2m_foo = models.ManyToManyField(Foo, related_name='m2m_foo') diff --git a/tests/update/tests.py b/tests/update/tests.py index ca1c5ac4f972..63e930bfa0b0 100644 --- a/tests/update/tests.py +++ b/tests/update/tests.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.core.exceptions import FieldError from django.db.models import Count, F, Max from django.test import TestCase @@ -8,12 +6,13 @@ class SimpleTest(TestCase): - def setUp(self): - self.a1 = A.objects.create() - self.a2 = A.objects.create() + @classmethod + def setUpTestData(cls): + cls.a1 = A.objects.create() + cls.a2 = A.objects.create() for x in range(20): - B.objects.create(a=self.a1) - D.objects.create(a=self.a1) + B.objects.create(a=cls.a1) + D.objects.create(a=cls.a1) def test_nonempty_update(self): """ @@ -64,11 +63,12 @@ def test_foreign_key_update_with_id(self): class AdvancedTests(TestCase): - def setUp(self): - self.d0 = DataPoint.objects.create(name="d0", value="apple") - self.d2 = DataPoint.objects.create(name="d2", value="banana") - self.d3 = DataPoint.objects.create(name="d3", value="banana") - self.r1 = RelatedPoint.objects.create(name="r1", data=self.d3) + @classmethod + def setUpTestData(cls): + cls.d0 = DataPoint.objects.create(name="d0", value="apple") + cls.d2 = DataPoint.objects.create(name="d2", value="banana") + cls.d3 = DataPoint.objects.create(name="d3", value="banana") + cls.r1 = RelatedPoint.objects.create(name="r1", data=cls.d3) def test_update(self): """ @@ -85,8 +85,7 @@ def test_update_multiple_objects(self): """ We can update multiple objects at once. """ - resp = DataPoint.objects.filter(value="banana").update( - value="pineapple") + resp = DataPoint.objects.filter(value='banana').update(value='pineapple') self.assertEqual(resp, 2) self.assertEqual(DataPoint.objects.get(name="d2").value, 'pineapple') @@ -125,7 +124,8 @@ def test_update_slice_fail(self): We do not support update on already sliced query sets. """ method = DataPoint.objects.all()[:2].update - with self.assertRaises(AssertionError): + msg = 'Cannot update a query once a slice has been taken.' + with self.assertRaisesMessage(AssertionError, msg): method(another_value='another thing') def test_update_respects_to_field(self): @@ -141,6 +141,15 @@ def test_update_respects_to_field(self): bar_qs.update(foo=b_foo) self.assertEqual(bar_qs[0].foo_id, b_foo.target) + def test_update_m2m_field(self): + msg = ( + 'Cannot update model field ' + ' ' + '(only non-relations and foreign keys permitted).' + ) + with self.assertRaisesMessage(FieldError, msg): + Bar.objects.update(m2m_foo='whatever') + def test_update_annotated_queryset(self): """ Update of a queryset that's been annotated. diff --git a/tests/update_only_fields/models.py b/tests/update_only_fields/models.py index a3be5088a28d..7308c75ea37e 100644 --- a/tests/update_only_fields/models.py +++ b/tests/update_only_fields/models.py @@ -1,19 +1,16 @@ from django.db import models -from django.utils.encoding import python_2_unicode_compatible - -GENDER_CHOICES = ( - ('M', 'Male'), - ('F', 'Female'), -) class Account(models.Model): num = models.IntegerField() -@python_2_unicode_compatible class Person(models.Model): + GENDER_CHOICES = ( + ('M', 'Male'), + ('F', 'Female'), + ) name = models.CharField(max_length=20) gender = models.CharField(max_length=1, choices=GENDER_CHOICES) pid = models.IntegerField(null=True, default=None) @@ -28,7 +25,6 @@ class Employee(Person): accounts = models.ManyToManyField('Account', related_name='employees', blank=True) -@python_2_unicode_compatible class Profile(models.Model): name = models.CharField(max_length=200) salary = models.FloatField(default=1000.0) diff --git a/tests/update_only_fields/tests.py b/tests/update_only_fields/tests.py index 7627bcd378eb..58ae94b7cc51 100644 --- a/tests/update_only_fields/tests.py +++ b/tests/update_only_fields/tests.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.db.models.signals import post_save, pre_save from django.test import TestCase @@ -7,6 +5,8 @@ class UpdateOnlyFieldsTests(TestCase): + msg = 'The following fields do not exist in this model or are m2m fields: %s' + def test_update_fields_basic(self): s = Person.objects.create(name='Sara', gender='F') self.assertEqual(s.gender, 'F') @@ -122,7 +122,7 @@ def test_update_fields_m2m(self): a2 = Account.objects.create(num=2) e1.accounts.set([a1, a2]) - with self.assertRaises(ValueError): + with self.assertRaisesMessage(ValueError, self.msg % 'accounts'): e1.save(update_fields=['accounts']) def test_update_fields_inheritance(self): @@ -203,10 +203,12 @@ def post_save_receiver(**kwargs): def test_update_fields_incorrect_params(self): s = Person.objects.create(name='Sara', gender='F') - with self.assertRaises(ValueError): + with self.assertRaisesMessage(ValueError, self.msg % 'first_name'): s.save(update_fields=['first_name']) - with self.assertRaises(ValueError): + # "name" is treated as an iterable so the output is something like + # "n, a, m, e" but the order isn't deterministic. + with self.assertRaisesMessage(ValueError, self.msg % ''): s.save(update_fields="name") def test_empty_update_fields(self): diff --git a/tests/urlpatterns/__init__.py b/tests/urlpatterns/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/urlpatterns/converter_urls.py b/tests/urlpatterns/converter_urls.py new file mode 100644 index 000000000000..0cc57bd46eee --- /dev/null +++ b/tests/urlpatterns/converter_urls.py @@ -0,0 +1,8 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path('{x}/<{x}:{x}>/'.format(x=name), views.empty_view, name=name) + for name in ('int', 'path', 'slug', 'str', 'uuid') +] diff --git a/tests/urlpatterns/converters.py b/tests/urlpatterns/converters.py new file mode 100644 index 000000000000..14a65b04833d --- /dev/null +++ b/tests/urlpatterns/converters.py @@ -0,0 +1,38 @@ +import base64 + + +class Base64Converter: + regex = r'[a-zA-Z0-9+/]*={0,2}' + + def to_python(self, value): + return base64.b64decode(value) + + def to_url(self, value): + return base64.b64encode(value).decode('ascii') + + +class DynamicConverter: + _dynamic_to_python = None + _dynamic_to_url = None + + @property + def regex(self): + return r'[0-9a-zA-Z]+' + + @regex.setter + def regex(self): + raise Exception("You can't modify the regular expression.") + + def to_python(self, value): + return type(self)._dynamic_to_python(value) + + def to_url(self, value): + return type(self)._dynamic_to_url(value) + + @classmethod + def register_to_python(cls, value): + cls._dynamic_to_python = value + + @classmethod + def register_to_url(cls, value): + cls._dynamic_to_url = value diff --git a/tests/urlpatterns/included_urls.py b/tests/urlpatterns/included_urls.py new file mode 100644 index 000000000000..76e4551f57f3 --- /dev/null +++ b/tests/urlpatterns/included_urls.py @@ -0,0 +1,8 @@ +from django.urls import include, path + +from . import views + +urlpatterns = [ + path('extra//', views.empty_view, name='inner-extra'), + path('', include('urlpatterns.more_urls')), +] diff --git a/tests/urlpatterns/more_urls.py b/tests/urlpatterns/more_urls.py new file mode 100644 index 000000000000..c7d789dda014 --- /dev/null +++ b/tests/urlpatterns/more_urls.py @@ -0,0 +1,7 @@ +from django.urls import re_path + +from . import views + +urlpatterns = [ + re_path(r'^more/(?P\w+)/$', views.empty_view, name='inner-more'), +] diff --git a/tests/urlpatterns/path_base64_urls.py b/tests/urlpatterns/path_base64_urls.py new file mode 100644 index 000000000000..afd11ac9f608 --- /dev/null +++ b/tests/urlpatterns/path_base64_urls.py @@ -0,0 +1,23 @@ +from django.urls import include, path, register_converter + +from . import converters, views + +register_converter(converters.Base64Converter, 'base64') + +subsubpatterns = [ + path('/', views.empty_view, name='subsubpattern-base64'), +] + +subpatterns = [ + path('/', views.empty_view, name='subpattern-base64'), + path( + '/', + include((subsubpatterns, 'second-layer-namespaced-base64'), 'instance-ns-base64') + ), +] + +urlpatterns = [ + path('base64//', views.empty_view, name='base64'), + path('base64//subpatterns/', include(subpatterns)), + path('base64//namespaced/', include((subpatterns, 'namespaced-base64'))), +] diff --git a/tests/urlpatterns/path_dynamic_urls.py b/tests/urlpatterns/path_dynamic_urls.py new file mode 100644 index 000000000000..0e5afe1df07a --- /dev/null +++ b/tests/urlpatterns/path_dynamic_urls.py @@ -0,0 +1,9 @@ +from django.urls import path, register_converter + +from . import converters, views + +register_converter(converters.DynamicConverter, 'dynamic') + +urlpatterns = [ + path('dynamic//', views.empty_view, name='dynamic'), +] diff --git a/tests/urlpatterns/path_urls.py b/tests/urlpatterns/path_urls.py new file mode 100644 index 000000000000..953fe6b6d735 --- /dev/null +++ b/tests/urlpatterns/path_urls.py @@ -0,0 +1,16 @@ +from django.urls import include, path, re_path + +from . import views + +urlpatterns = [ + path('articles/2003/', views.empty_view, name='articles-2003'), + path('articles//', views.empty_view, name='articles-year'), + path('articles///', views.empty_view, name='articles-year-month'), + path('articles////', views.empty_view, name='articles-year-month-day'), + path('users/', views.empty_view, name='users'), + path('users//', views.empty_view, name='user-with-id'), + path('included_urls/', include('urlpatterns.included_urls')), + re_path(r'^regex/(?P[0-9]+)/$', views.empty_view, name='regex'), + path('', include('urlpatterns.more_urls')), + path('//', views.empty_view, name='lang-and-path'), +] diff --git a/tests/urlpatterns/test_resolvers.py b/tests/urlpatterns/test_resolvers.py new file mode 100644 index 000000000000..a32e8edaf78c --- /dev/null +++ b/tests/urlpatterns/test_resolvers.py @@ -0,0 +1,15 @@ +from django.test import SimpleTestCase +from django.urls.resolvers import RegexPattern, RoutePattern +from django.utils.translation import gettext_lazy as _ + + +class RegexPatternTests(SimpleTestCase): + + def test_str(self): + self.assertEqual(str(RegexPattern(_('^translated/$'))), '^translated/$') + + +class RoutePatternTests(SimpleTestCase): + + def test_str(self): + self.assertEqual(str(RoutePattern(_('translated/'))), 'translated/') diff --git a/tests/urlpatterns/tests.py b/tests/urlpatterns/tests.py new file mode 100644 index 000000000000..f696cd531dc4 --- /dev/null +++ b/tests/urlpatterns/tests.py @@ -0,0 +1,213 @@ +import uuid + +from django.core.exceptions import ImproperlyConfigured +from django.test import SimpleTestCase +from django.test.utils import override_settings +from django.urls import Resolver404, path, resolve, reverse + +from .converters import DynamicConverter +from .views import empty_view + +included_kwargs = {'base': b'hello', 'value': b'world'} +converter_test_data = ( + # ('url', ('url_name', 'app_name', {kwargs})), + # aGVsbG8= is 'hello' encoded in base64. + ('/base64/aGVsbG8=/', ('base64', '', {'value': b'hello'})), + ('/base64/aGVsbG8=/subpatterns/d29ybGQ=/', ('subpattern-base64', '', included_kwargs)), + ('/base64/aGVsbG8=/namespaced/d29ybGQ=/', ('subpattern-base64', 'namespaced-base64', included_kwargs)), +) + + +@override_settings(ROOT_URLCONF='urlpatterns.path_urls') +class SimplifiedURLTests(SimpleTestCase): + + def test_path_lookup_without_parameters(self): + match = resolve('/articles/2003/') + self.assertEqual(match.url_name, 'articles-2003') + self.assertEqual(match.args, ()) + self.assertEqual(match.kwargs, {}) + self.assertEqual(match.route, 'articles/2003/') + + def test_path_lookup_with_typed_parameters(self): + match = resolve('/articles/2015/') + self.assertEqual(match.url_name, 'articles-year') + self.assertEqual(match.args, ()) + self.assertEqual(match.kwargs, {'year': 2015}) + self.assertEqual(match.route, 'articles//') + + def test_path_lookup_with_multiple_paramaters(self): + match = resolve('/articles/2015/04/12/') + self.assertEqual(match.url_name, 'articles-year-month-day') + self.assertEqual(match.args, ()) + self.assertEqual(match.kwargs, {'year': 2015, 'month': 4, 'day': 12}) + self.assertEqual(match.route, 'articles////') + + def test_two_variable_at_start_of_path_pattern(self): + match = resolve('/en/foo/') + self.assertEqual(match.url_name, 'lang-and-path') + self.assertEqual(match.kwargs, {'lang': 'en', 'url': 'foo'}) + self.assertEqual(match.route, '//') + + def test_re_path(self): + match = resolve('/regex/1/') + self.assertEqual(match.url_name, 'regex') + self.assertEqual(match.kwargs, {'pk': '1'}) + self.assertEqual(match.route, '^regex/(?P[0-9]+)/$') + + def test_path_lookup_with_inclusion(self): + match = resolve('/included_urls/extra/something/') + self.assertEqual(match.url_name, 'inner-extra') + self.assertEqual(match.route, 'included_urls/extra//') + + def test_path_lookup_with_empty_string_inclusion(self): + match = resolve('/more/99/') + self.assertEqual(match.url_name, 'inner-more') + self.assertEqual(match.route, r'^more/(?P\w+)/$') + + def test_path_lookup_with_double_inclusion(self): + match = resolve('/included_urls/more/some_value/') + self.assertEqual(match.url_name, 'inner-more') + self.assertEqual(match.route, r'included_urls/more/(?P\w+)/$') + + def test_path_reverse_without_parameter(self): + url = reverse('articles-2003') + self.assertEqual(url, '/articles/2003/') + + def test_path_reverse_with_parameter(self): + url = reverse('articles-year-month-day', kwargs={'year': 2015, 'month': 4, 'day': 12}) + self.assertEqual(url, '/articles/2015/4/12/') + + @override_settings(ROOT_URLCONF='urlpatterns.path_base64_urls') + def test_converter_resolve(self): + for url, (url_name, app_name, kwargs) in converter_test_data: + with self.subTest(url=url): + match = resolve(url) + self.assertEqual(match.url_name, url_name) + self.assertEqual(match.app_name, app_name) + self.assertEqual(match.kwargs, kwargs) + + @override_settings(ROOT_URLCONF='urlpatterns.path_base64_urls') + def test_converter_reverse(self): + for expected, (url_name, app_name, kwargs) in converter_test_data: + if app_name: + url_name = '%s:%s' % (app_name, url_name) + with self.subTest(url=url_name): + url = reverse(url_name, kwargs=kwargs) + self.assertEqual(url, expected) + + @override_settings(ROOT_URLCONF='urlpatterns.path_base64_urls') + def test_converter_reverse_with_second_layer_instance_namespace(self): + kwargs = included_kwargs.copy() + kwargs['last_value'] = b'world' + url = reverse('instance-ns-base64:subsubpattern-base64', kwargs=kwargs) + self.assertEqual(url, '/base64/aGVsbG8=/subpatterns/d29ybGQ=/d29ybGQ=/') + + def test_path_inclusion_is_matchable(self): + match = resolve('/included_urls/extra/something/') + self.assertEqual(match.url_name, 'inner-extra') + self.assertEqual(match.kwargs, {'extra': 'something'}) + + def test_path_inclusion_is_reversable(self): + url = reverse('inner-extra', kwargs={'extra': 'something'}) + self.assertEqual(url, '/included_urls/extra/something/') + + def test_invalid_converter(self): + msg = "URL route 'foo//' uses invalid converter 'nonexistent'." + with self.assertRaisesMessage(ImproperlyConfigured, msg): + path('foo//', empty_view) + + +@override_settings(ROOT_URLCONF='urlpatterns.converter_urls') +class ConverterTests(SimpleTestCase): + + def test_matching_urls(self): + def no_converter(x): + return x + + test_data = ( + ('int', {'0', '1', '01', 1234567890}, int), + ('str', {'abcxyz'}, no_converter), + ('path', {'allows.ANY*characters'}, no_converter), + ('slug', {'abcxyz-ABCXYZ_01234567890'}, no_converter), + ('uuid', {'39da9369-838e-4750-91a5-f7805cd82839'}, uuid.UUID), + ) + for url_name, url_suffixes, converter in test_data: + for url_suffix in url_suffixes: + url = '/%s/%s/' % (url_name, url_suffix) + with self.subTest(url=url): + match = resolve(url) + self.assertEqual(match.url_name, url_name) + self.assertEqual(match.kwargs, {url_name: converter(url_suffix)}) + # reverse() works with string parameters. + string_kwargs = {url_name: url_suffix} + self.assertEqual(reverse(url_name, kwargs=string_kwargs), url) + # reverse() also works with native types (int, UUID, etc.). + if converter is not no_converter: + # The converted value might be different for int (a + # leading zero is lost in the conversion). + converted_value = match.kwargs[url_name] + converted_url = '/%s/%s/' % (url_name, converted_value) + self.assertEqual(reverse(url_name, kwargs={url_name: converted_value}), converted_url) + + def test_nonmatching_urls(self): + test_data = ( + ('int', {'-1', 'letters'}), + ('str', {'', '/'}), + ('path', {''}), + ('slug', {'', 'stars*notallowed'}), + ('uuid', { + '', + '9da9369-838e-4750-91a5-f7805cd82839', + '39da9369-838-4750-91a5-f7805cd82839', + '39da9369-838e-475-91a5-f7805cd82839', + '39da9369-838e-4750-91a-f7805cd82839', + '39da9369-838e-4750-91a5-f7805cd8283', + }), + ) + for url_name, url_suffixes in test_data: + for url_suffix in url_suffixes: + url = '/%s/%s/' % (url_name, url_suffix) + with self.subTest(url=url), self.assertRaises(Resolver404): + resolve(url) + + +class ParameterRestrictionTests(SimpleTestCase): + def test_non_identifier_parameter_name_causes_exception(self): + msg = ( + "URL route 'hello//' uses parameter name '1' which isn't " + "a valid Python identifier." + ) + with self.assertRaisesMessage(ImproperlyConfigured, msg): + path(r'hello//', lambda r: None) + + def test_allows_non_ascii_but_valid_identifiers(self): + # \u0394 is "GREEK CAPITAL LETTER DELTA", a valid identifier. + p = path('hello//', lambda r: None) + match = p.resolve('hello/1/') + self.assertEqual(match.kwargs, {'\u0394': '1'}) + + +@override_settings(ROOT_URLCONF='urlpatterns.path_dynamic_urls') +class ConversionExceptionTests(SimpleTestCase): + """How are errors in Converter.to_python() and to_url() handled?""" + + def test_resolve_value_error_means_no_match(self): + @DynamicConverter.register_to_python + def raises_value_error(value): + raise ValueError() + with self.assertRaises(Resolver404): + resolve('/dynamic/abc/') + + def test_resolve_type_error_propagates(self): + @DynamicConverter.register_to_python + def raises_type_error(value): + raise TypeError('This type error propagates.') + with self.assertRaisesMessage(TypeError, 'This type error propagates.'): + resolve('/dynamic/abc/') + + def test_reverse_value_error_propagates(self): + @DynamicConverter.register_to_url + def raises_value_error(value): + raise ValueError('This value error propagates.') + with self.assertRaisesMessage(ValueError, 'This value error propagates.'): + reverse('dynamic', kwargs={'value': object()}) diff --git a/tests/model_permalink/views.py b/tests/urlpatterns/views.py similarity index 100% rename from tests/model_permalink/views.py rename to tests/urlpatterns/views.py diff --git a/tests/urlpatterns_reverse/erroneous_urls.py b/tests/urlpatterns_reverse/erroneous_urls.py index 4d75b49e8f61..d8ccf2fc611e 100644 --- a/tests/urlpatterns_reverse/erroneous_urls.py +++ b/tests/urlpatterns_reverse/erroneous_urls.py @@ -1,7 +1,7 @@ -from django.conf.urls import url +from django.urls import re_path from . import views urlpatterns = [ - url(r'(regex_error/$', views.empty_view), + re_path(r'(regex_error/$', views.empty_view), ] diff --git a/tests/urlpatterns_reverse/extra_urls.py b/tests/urlpatterns_reverse/extra_urls.py index d9c518c2198d..dac9a87fd208 100644 --- a/tests/urlpatterns_reverse/extra_urls.py +++ b/tests/urlpatterns_reverse/extra_urls.py @@ -2,13 +2,13 @@ Some extra URL patterns that are included at the top level. """ -from django.conf.urls import include, url +from django.urls import include, path, re_path from .views import empty_view urlpatterns = [ - url(r'^e-places/([0-9]+)/$', empty_view, name='extra-places'), - url(r'^e-people/(?P\w+)/$', empty_view, name="extra-people"), - url('', include('urlpatterns_reverse.included_urls2')), - url(r'^prefix/(?P\w+)/', include('urlpatterns_reverse.included_urls2')), + re_path('^e-places/([0-9]+)/$', empty_view, name='extra-places'), + re_path(r'^e-people/(?P\w+)/$', empty_view, name='extra-people'), + path('', include('urlpatterns_reverse.included_urls2')), + re_path(r'^prefix/(?P\w+)/', include('urlpatterns_reverse.included_urls2')), ] diff --git a/tests/urlpatterns_reverse/included_app_urls.py b/tests/urlpatterns_reverse/included_app_urls.py index 570d6fc518fa..e8c046914307 100644 --- a/tests/urlpatterns_reverse/included_app_urls.py +++ b/tests/urlpatterns_reverse/included_app_urls.py @@ -1,16 +1,16 @@ -from django.conf.urls import url +from django.urls import path, re_path from . import views app_name = 'inc-app' urlpatterns = [ - url(r'^normal/$', views.empty_view, name='inc-normal-view'), - url(r'^normal/(?P[0-9]+)/(?P[0-9]+)/$', views.empty_view, name='inc-normal-view'), + path('normal/', views.empty_view, name='inc-normal-view'), + re_path('^normal/(?P[0-9]+)/(?P[0-9]+)/$', views.empty_view, name='inc-normal-view'), - url(r'^\+\\\$\*/$', views.empty_view, name='inc-special-view'), + re_path(r'^\+\\\$\*/$', views.empty_view, name='inc-special-view'), - url(r'^mixed_args/([0-9]+)/(?P[0-9]+)/$', views.empty_view, name='inc-mixed-args'), - url(r'^no_kwargs/([0-9]+)/([0-9]+)/$', views.empty_view, name='inc-no-kwargs'), + re_path('^mixed_args/([0-9]+)/(?P[0-9]+)/$', views.empty_view, name='inc-mixed-args'), + re_path('^no_kwargs/([0-9]+)/([0-9]+)/$', views.empty_view, name='inc-no-kwargs'), - url(r'^view_class/(?P[0-9]+)/(?P[0-9]+)/$', views.view_class_instance, name='inc-view-class'), + re_path('^view_class/(?P[0-9]+)/(?P[0-9]+)/$', views.view_class_instance, name='inc-view-class'), ] diff --git a/tests/urlpatterns_reverse/included_named_urls.py b/tests/urlpatterns_reverse/included_named_urls.py index fac37ef714ea..e0b00dd4ed03 100644 --- a/tests/urlpatterns_reverse/included_named_urls.py +++ b/tests/urlpatterns_reverse/included_named_urls.py @@ -1,10 +1,10 @@ -from django.conf.urls import include, url +from django.urls import include, path, re_path from .views import empty_view urlpatterns = [ - url(r'^$', empty_view, name="named-url3"), - url(r'^extra/(?P\w+)/$', empty_view, name="named-url4"), - url(r'^(?P[0-9]+)|(?P[0-9]+)/$', empty_view), - url(r'^included/', include('urlpatterns_reverse.included_named_urls2')), + path('', empty_view, name="named-url3"), + re_path(r'^extra/(?P\w+)/$', empty_view, name="named-url4"), + re_path(r'^(?P[0-9]+)|(?P[0-9]+)/$', empty_view), + path('included/', include('urlpatterns_reverse.included_named_urls2')), ] diff --git a/tests/urlpatterns_reverse/included_named_urls2.py b/tests/urlpatterns_reverse/included_named_urls2.py index 4d617c37905c..d8103eae04c5 100644 --- a/tests/urlpatterns_reverse/included_named_urls2.py +++ b/tests/urlpatterns_reverse/included_named_urls2.py @@ -1,9 +1,9 @@ -from django.conf.urls import url +from django.urls import path, re_path from .views import empty_view urlpatterns = [ - url(r'^$', empty_view, name="named-url5"), - url(r'^extra/(?P\w+)/$', empty_view, name="named-url6"), - url(r'^(?P[0-9]+)|(?P[0-9]+)/$', empty_view), + path('', empty_view, name="named-url5"), + re_path(r'^extra/(?P\w+)/$', empty_view, name="named-url6"), + re_path(r'^(?P[0-9]+)|(?P[0-9]+)/$', empty_view), ] diff --git a/tests/urlpatterns_reverse/included_namespace_urls.py b/tests/urlpatterns_reverse/included_namespace_urls.py index 4f68e4c6025b..0b3b2b5a19ca 100644 --- a/tests/urlpatterns_reverse/included_namespace_urls.py +++ b/tests/urlpatterns_reverse/included_namespace_urls.py @@ -1,4 +1,4 @@ -from django.conf.urls import include, url +from django.urls import include, path, re_path from .utils import URLObject from .views import empty_view, view_class_instance @@ -6,19 +6,20 @@ testobj3 = URLObject('testapp', 'test-ns3') testobj4 = URLObject('testapp', 'test-ns4') +app_name = 'included_namespace_urls' urlpatterns = [ - url(r'^normal/$', empty_view, name='inc-normal-view'), - url(r'^normal/(?P[0-9]+)/(?P[0-9]+)/$', empty_view, name='inc-normal-view'), + path('normal/', empty_view, name='inc-normal-view'), + re_path('^normal/(?P[0-9]+)/(?P[0-9]+)/$', empty_view, name='inc-normal-view'), - url(r'^\+\\\$\*/$', empty_view, name='inc-special-view'), + re_path(r'^\+\\\$\*/$', empty_view, name='inc-special-view'), - url(r'^mixed_args/([0-9]+)/(?P[0-9]+)/$', empty_view, name='inc-mixed-args'), - url(r'^no_kwargs/([0-9]+)/([0-9]+)/$', empty_view, name='inc-no-kwargs'), + re_path('^mixed_args/([0-9]+)/(?P[0-9]+)/$', empty_view, name='inc-mixed-args'), + re_path('^no_kwargs/([0-9]+)/([0-9]+)/$', empty_view, name='inc-no-kwargs'), - url(r'^view_class/(?P[0-9]+)/(?P[0-9]+)/$', view_class_instance, name='inc-view-class'), + re_path('^view_class/(?P[0-9]+)/(?P[0-9]+)/$', view_class_instance, name='inc-view-class'), - url(r'^test3/', include(testobj3.urls)), - url(r'^test4/', include(testobj4.urls)), - url(r'^ns-included3/', include('urlpatterns_reverse.included_urls', namespace='inc-ns3')), - url(r'^ns-included4/', include('urlpatterns_reverse.namespace_urls', namespace='inc-ns4')), + path('test3/', include(*testobj3.urls)), + path('test4/', include(*testobj4.urls)), + path('ns-included3/', include(('urlpatterns_reverse.included_urls', 'included_urls'), namespace='inc-ns3')), + path('ns-included4/', include('urlpatterns_reverse.namespace_urls', namespace='inc-ns4')), ] diff --git a/tests/urlpatterns_reverse/included_no_kwargs_urls.py b/tests/urlpatterns_reverse/included_no_kwargs_urls.py index f124a09b2fcf..aa1a1a51a726 100644 --- a/tests/urlpatterns_reverse/included_no_kwargs_urls.py +++ b/tests/urlpatterns_reverse/included_no_kwargs_urls.py @@ -1,7 +1,7 @@ -from django.conf.urls import url +from django.urls import re_path from .views import empty_view urlpatterns = [ - url(r'^inner-no-kwargs/([0-9]+)/', empty_view, name="inner-no-kwargs") + re_path('^inner-no-kwargs/([0-9]+)/$', empty_view, name="inner-no-kwargs") ] diff --git a/tests/urlpatterns_reverse/included_urls.py b/tests/urlpatterns_reverse/included_urls.py index 240d9e56654d..f34010b28f7b 100644 --- a/tests/urlpatterns_reverse/included_urls.py +++ b/tests/urlpatterns_reverse/included_urls.py @@ -1,9 +1,9 @@ -from django.conf.urls import url +from django.urls import path, re_path from .views import empty_view urlpatterns = [ - url(r'^$', empty_view, name="inner-nothing"), - url(r'^extra/(?P\w+)/$', empty_view, name="inner-extra"), - url(r'^(?P[0-9]+)|(?P[0-9]+)/$', empty_view, name="inner-disjunction"), + path('', empty_view, name='inner-nothing'), + re_path(r'extra/(?P\w+)/$', empty_view, name='inner-extra'), + re_path(r'(?P[0-9]+)|(?P[0-9]+)/$', empty_view, name='inner-disjunction'), ] diff --git a/tests/urlpatterns_reverse/included_urls2.py b/tests/urlpatterns_reverse/included_urls2.py index 4a4aef8d955b..ec61aecce110 100644 --- a/tests/urlpatterns_reverse/included_urls2.py +++ b/tests/urlpatterns_reverse/included_urls2.py @@ -5,11 +5,11 @@ argument list. """ -from django.conf.urls import url +from django.urls import re_path from .views import empty_view urlpatterns = [ - url(r'^part/(?P\w+)/$', empty_view, name="part"), - url(r'^part2/(?:(?P\w+)/)?$', empty_view, name="part2"), + re_path(r'^part/(?P\w+)/$', empty_view, name='part'), + re_path(r'^part2/(?:(?P\w+)/)?$', empty_view, name='part2'), ] diff --git a/tests/urlpatterns_reverse/method_view_urls.py b/tests/urlpatterns_reverse/method_view_urls.py index c28958e6a96f..39c53433c8aa 100644 --- a/tests/urlpatterns_reverse/method_view_urls.py +++ b/tests/urlpatterns_reverse/method_view_urls.py @@ -1,7 +1,7 @@ -from django.conf.urls import url +from django.urls import path -class ViewContainer(object): +class ViewContainer: def method_view(self, request): pass @@ -14,6 +14,6 @@ def classmethod_view(cls, request): urlpatterns = [ - url(r'^$', view_container.method_view, name='instance-method-url'), - url(r'^$', ViewContainer.classmethod_view, name='instance-method-url'), + path('', view_container.method_view, name='instance-method-url'), + path('', ViewContainer.classmethod_view, name='instance-method-url'), ] diff --git a/tests/urlpatterns_reverse/named_urls.py b/tests/urlpatterns_reverse/named_urls.py index 647c2630cf25..06bb834dc7dd 100644 --- a/tests/urlpatterns_reverse/named_urls.py +++ b/tests/urlpatterns_reverse/named_urls.py @@ -1,10 +1,10 @@ -from django.conf.urls import include, url +from django.urls import include, path, re_path from .views import empty_view urlpatterns = [ - url(r'^$', empty_view, name="named-url1"), - url(r'^extra/(?P\w+)/$', empty_view, name="named-url2"), - url(r'^(?P[0-9]+)|(?P[0-9]+)/$', empty_view), - url(r'^included/', include('urlpatterns_reverse.included_named_urls')), + path('', empty_view, name='named-url1'), + re_path(r'^extra/(?P\w+)/$', empty_view, name='named-url2'), + re_path(r'^(?P[0-9]+)|(?P[0-9]+)/$', empty_view), + path('included/', include('urlpatterns_reverse.included_named_urls')), ] diff --git a/tests/urlpatterns_reverse/named_urls_conflict.py b/tests/urlpatterns_reverse/named_urls_conflict.py new file mode 100644 index 000000000000..b1f883271fda --- /dev/null +++ b/tests/urlpatterns_reverse/named_urls_conflict.py @@ -0,0 +1,17 @@ +from django.urls import path, re_path + +from .views import empty_view + +urlpatterns = [ + # No kwargs + path('conflict/cannot-go-here/', empty_view, name='name-conflict'), + path('conflict/', empty_view, name='name-conflict'), + # One kwarg + re_path(r'^conflict-first/(?P\w+)/$', empty_view, name='name-conflict'), + re_path(r'^conflict-cannot-go-here/(?P\w+)/$', empty_view, name='name-conflict'), + re_path(r'^conflict-middle/(?P\w+)/$', empty_view, name='name-conflict'), + re_path(r'^conflict-last/(?P\w+)/$', empty_view, name='name-conflict'), + # Two kwargs + re_path(r'^conflict/(?P\w+)/(?P\w+)/cannot-go-here/$', empty_view, name='name-conflict'), + re_path(r'^conflict/(?P\w+)/(?P\w+)/$', empty_view, name='name-conflict'), +] diff --git a/tests/urlpatterns_reverse/namespace_urls.py b/tests/urlpatterns_reverse/namespace_urls.py index fa6359dec2bf..a8fd7bb8788b 100644 --- a/tests/urlpatterns_reverse/namespace_urls.py +++ b/tests/urlpatterns_reverse/namespace_urls.py @@ -1,4 +1,4 @@ -from django.conf.urls import include, url +from django.urls import include, path, re_path from . import views from .utils import URLObject @@ -12,47 +12,50 @@ newappobj1 = URLObject('newapp') +app_name = 'namespace_urls' urlpatterns = [ - url(r'^normal/$', views.empty_view, name='normal-view'), - url(r'^normal/(?P[0-9]+)/(?P[0-9]+)/$', views.empty_view, name='normal-view'), - url(r'^resolver_match/$', views.pass_resolver_match_view, name='test-resolver-match'), + path('normal/', views.empty_view, name='normal-view'), + re_path(r'^normal/(?P[0-9]+)/(?P[0-9]+)/$', views.empty_view, name='normal-view'), + path('resolver_match/', views.pass_resolver_match_view, name='test-resolver-match'), - url(r'^\+\\\$\*/$', views.empty_view, name='special-view'), + re_path(r'^\+\\\$\*/$', views.empty_view, name='special-view'), - url(r'^mixed_args/([0-9]+)/(?P[0-9]+)/$', views.empty_view, name='mixed-args'), - url(r'^no_kwargs/([0-9]+)/([0-9]+)/$', views.empty_view, name='no-kwargs'), + re_path(r'^mixed_args/([0-9]+)/(?P[0-9]+)/$', views.empty_view, name='mixed-args'), + re_path(r'^no_kwargs/([0-9]+)/([0-9]+)/$', views.empty_view, name='no-kwargs'), - url(r'^view_class/(?P[0-9]+)/(?P[0-9]+)/$', views.view_class_instance, name='view-class'), + re_path(r'^view_class/(?P[0-9]+)/(?P[0-9]+)/$', views.view_class_instance, name='view-class'), - url(r'^unnamed/normal/(?P[0-9]+)/(?P[0-9]+)/$', views.empty_view), - url(r'^unnamed/view_class/(?P[0-9]+)/(?P[0-9]+)/$', views.view_class_instance), + re_path(r'^unnamed/normal/(?P[0-9]+)/(?P[0-9]+)/$', views.empty_view), + re_path(r'^unnamed/view_class/(?P[0-9]+)/(?P[0-9]+)/$', views.view_class_instance), - url(r'^test1/', include(testobj1.urls)), - url(r'^test2/', include(testobj2.urls)), - url(r'^default/', include(default_testobj.urls)), + path('test1/', include(*testobj1.urls)), + path('test2/', include(*testobj2.urls)), + path('default/', include(*default_testobj.urls)), - url(r'^other1/', include(otherobj1.urls)), - url(r'^other[246]/', include(otherobj2.urls)), + path('other1/', include(*otherobj1.urls)), + re_path(r'^other[246]/', include(*otherobj2.urls)), - url(r'^newapp1/', include(newappobj1.app_urls, 'new-ns1')), - url(r'^new-default/', include(newappobj1.app_urls)), + path('newapp1/', include(newappobj1.app_urls, 'new-ns1')), + path('new-default/', include(newappobj1.app_urls)), - url(r'^app-included[135]/', include('urlpatterns_reverse.included_app_urls', namespace='app-ns1')), - url(r'^app-included2/', include('urlpatterns_reverse.included_app_urls', namespace='app-ns2')), + re_path(r'^app-included[135]/', include('urlpatterns_reverse.included_app_urls', namespace='app-ns1')), + path('app-included2/', include('urlpatterns_reverse.included_app_urls', namespace='app-ns2')), - url(r'^ns-included[135]/', include('urlpatterns_reverse.included_namespace_urls', namespace='inc-ns1')), - url(r'^ns-included2/', include('urlpatterns_reverse.included_namespace_urls', namespace='inc-ns2')), + re_path(r'^ns-included[135]/', include('urlpatterns_reverse.included_namespace_urls', namespace='inc-ns1')), + path('ns-included2/', include('urlpatterns_reverse.included_namespace_urls', namespace='inc-ns2')), - url(r'^app-included/', include('urlpatterns_reverse.included_namespace_urls', 'inc-app', 'inc-app')), + path('app-included/', include('urlpatterns_reverse.included_namespace_urls', 'inc-app')), - url(r'^included/', include('urlpatterns_reverse.included_namespace_urls')), - url(r'^inc(?P[0-9]+)/', include('urlpatterns_reverse.included_urls', namespace='inc-ns5')), - url(r'^included/([0-9]+)/', include('urlpatterns_reverse.included_namespace_urls')), + path('included/', include('urlpatterns_reverse.included_namespace_urls')), + re_path( + r'^inc(?P[0-9]+)/', include(('urlpatterns_reverse.included_urls', 'included_urls'), namespace='inc-ns5') + ), + re_path(r'^included/([0-9]+)/', include('urlpatterns_reverse.included_namespace_urls')), - url( + re_path( r'^ns-outer/(?P[0-9]+)/', include('urlpatterns_reverse.included_namespace_urls', namespace='inc-outer') ), - url(r'^\+\\\$\*/', include('urlpatterns_reverse.namespace_urls', namespace='special')), + re_path(r'^\+\\\$\*/', include('urlpatterns_reverse.namespace_urls', namespace='special')), ] diff --git a/tests/urlpatterns_reverse/nested_urls.py b/tests/urlpatterns_reverse/nested_urls.py index f41b1449a87d..d5af7d0d026a 100644 --- a/tests/urlpatterns_reverse/nested_urls.py +++ b/tests/urlpatterns_reverse/nested_urls.py @@ -1,4 +1,4 @@ -from django.conf.urls import include, url +from django.urls import include, path from django.views import View @@ -15,11 +15,11 @@ class View3(View): nested = ([ - url(r'^view1/$', view1, name='view1'), - url(r'^view3/$', View3.as_view(), name='view3'), + path('view1/', view1, name='view1'), + path('view3/', View3.as_view(), name='view3'), ], 'backend') urlpatterns = [ - url(r'^some/path/', include(nested, namespace='nested')), - url(r'^view2/$', view2, name='view2'), + path('some/path/', include(nested, namespace='nested')), + path('view2/', view2, name='view2'), ] diff --git a/tests/urlpatterns_reverse/reverse_lazy_urls.py b/tests/urlpatterns_reverse/reverse_lazy_urls.py index 694b23fad699..1cbda44fe984 100644 --- a/tests/urlpatterns_reverse/reverse_lazy_urls.py +++ b/tests/urlpatterns_reverse/reverse_lazy_urls.py @@ -1,10 +1,10 @@ -from django.conf.urls import url +from django.urls import path from .views import LazyRedirectView, empty_view, login_required_view urlpatterns = [ - url(r'^redirected_to/$', empty_view, name='named-lazy-url-redirected-to'), - url(r'^login/$', empty_view, name='some-login-page'), - url(r'^login_required_view/$', login_required_view), - url(r'^redirect/$', LazyRedirectView.as_view()), + path('redirected_to/', empty_view, name='named-lazy-url-redirected-to'), + path('login/', empty_view, name='some-login-page'), + path('login_required_view/', login_required_view), + path('redirect/', LazyRedirectView.as_view()), ] diff --git a/tests/urlpatterns_reverse/test_deprecated.py b/tests/urlpatterns_reverse/test_deprecated.py deleted file mode 100644 index c918dd5a1794..000000000000 --- a/tests/urlpatterns_reverse/test_deprecated.py +++ /dev/null @@ -1,33 +0,0 @@ -import warnings - -from django.conf.urls import url -from django.test import SimpleTestCase, override_settings -from django.urls import reverse - -from .views import empty_view - -urlpatterns = [ - url(r'^(?i)CaseInsensitive/(\w+)', empty_view, name="insensitive"), - url(r'^(?i)test/2/?$', empty_view, name="test2"), -] - - -@override_settings(ROOT_URLCONF='urlpatterns_reverse.test_deprecated') -class URLPatternReverse(SimpleTestCase): - - def test_urlpattern_reverse(self): - test_data = ( - ('insensitive', '/CaseInsensitive/fred', ['fred'], {}), - ('test2', '/test/2', [], {}), - ) - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter('always') - warnings.filterwarnings( - 'ignore', 'Flags not at the start', - DeprecationWarning, module='django.urls.resolvers' - ) - for i, (name, expected, args, kwargs) in enumerate(test_data): - got = reverse(name, args=args, kwargs=kwargs) - self.assertEqual(got, expected) - msg = str(warns[i].message) - self.assertEqual(msg, 'Using (?i) in url() patterns is deprecated.') diff --git a/tests/urlpatterns_reverse/test_localeregexprovider.py b/tests/urlpatterns_reverse/test_localeregexdescriptor.py similarity index 76% rename from tests/urlpatterns_reverse/test_localeregexprovider.py rename to tests/urlpatterns_reverse/test_localeregexdescriptor.py index 401e9a1ad03f..25e6cd962a2f 100644 --- a/tests/urlpatterns_reverse/test_localeregexprovider.py +++ b/tests/urlpatterns_reverse/test_localeregexdescriptor.py @@ -1,19 +1,16 @@ -from __future__ import unicode_literals - import os +from unittest import mock from django.core.exceptions import ImproperlyConfigured -from django.test import SimpleTestCase, mock, override_settings -from django.urls import LocaleRegexProvider -from django.urls.resolvers import LocaleRegexDescriptor +from django.test import SimpleTestCase, override_settings +from django.urls.resolvers import LocaleRegexDescriptor, RegexPattern from django.utils import translation -from django.utils._os import upath -here = os.path.dirname(upath(os.path.abspath(__file__))) +here = os.path.dirname(os.path.abspath(__file__)) @override_settings(LOCALE_PATHS=[os.path.join(here, 'translations', 'locale')]) -class LocaleRegexProviderTests(SimpleTestCase): +class LocaleRegexDescriptorTests(SimpleTestCase): def setUp(self): translation.trans_real._translations = {} @@ -21,7 +18,7 @@ def tearDown(self): translation.trans_real._translations = {} def test_translated_regex_compiled_per_language(self): - provider = LocaleRegexProvider(translation.gettext_lazy('^foo/$')) + provider = RegexPattern(translation.gettext_lazy('^foo/$')) with translation.override('de'): de_compiled = provider.regex # compiled only once per language @@ -35,7 +32,7 @@ def test_translated_regex_compiled_per_language(self): self.assertEqual(de_compiled, de_compiled_2) def test_nontranslated_regex_compiled_once(self): - provider = LocaleRegexProvider('^foo/$') + provider = RegexPattern('^foo/$') with translation.override('de'): de_compiled = provider.regex with translation.override('fr'): @@ -48,10 +45,10 @@ def test_nontranslated_regex_compiled_once(self): def test_regex_compile_error(self): """Regex errors are re-raised as ImproperlyConfigured.""" - provider = LocaleRegexProvider('*') + provider = RegexPattern('*') msg = '"*" is not a valid regular expression: nothing to repeat' with self.assertRaisesMessage(ImproperlyConfigured, msg): provider.regex def test_access_locale_regex_descriptor(self): - self.assertIsInstance(LocaleRegexProvider.regex, LocaleRegexDescriptor) + self.assertIsInstance(RegexPattern.regex, LocaleRegexDescriptor) diff --git a/tests/urlpatterns_reverse/tests.py b/tests/urlpatterns_reverse/tests.py index b332ac4f7eb1..72d4016d00f4 100644 --- a/tests/urlpatterns_reverse/tests.py +++ b/tests/urlpatterns_reverse/tests.py @@ -1,33 +1,26 @@ -# -*- coding: utf-8 -*- """ Unit tests for reverse URL lookups. """ -from __future__ import unicode_literals - import sys import threading -import unittest from admin_scripts.tests import AdminScriptTestCase from django.conf import settings -from django.conf.urls import include, url from django.contrib.auth.models import User from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist from django.http import ( HttpRequest, HttpResponsePermanentRedirect, HttpResponseRedirect, ) from django.shortcuts import redirect -from django.test import ( - SimpleTestCase, TestCase, ignore_warnings, override_settings, -) +from django.test import SimpleTestCase, TestCase, override_settings from django.test.utils import override_script_prefix from django.urls import ( - NoReverseMatch, RegexURLPattern, RegexURLResolver, Resolver404, - ResolverMatch, get_callable, get_resolver, resolve, reverse, reverse_lazy, + NoReverseMatch, Resolver404, ResolverMatch, URLPattern, URLResolver, + get_callable, get_resolver, get_urlconf, include, path, re_path, resolve, + reverse, reverse_lazy, ) -from django.utils import six -from django.utils.deprecation import RemovedInDjango20Warning +from django.urls.resolvers import RegexPattern from . import middleware, urlconf_outer, views from .utils import URLObject @@ -36,104 +29,124 @@ resolve_test_data = ( # These entries are in the format: (path, url_name, app_name, namespace, view_name, func, args, kwargs) # Simple case - ('/normal/42/37/', 'normal-view', '', '', 'normal-view', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}), + ('/normal/42/37/', 'normal-view', '', '', 'normal-view', views.empty_view, (), {'arg1': '42', 'arg2': '37'}), ( - '/view_class/42/37/', 'view-class', '', '', 'view-class', views.view_class_instance, tuple(), + '/view_class/42/37/', 'view-class', '', '', 'view-class', views.view_class_instance, (), {'arg1': '42', 'arg2': '37'} ), ( - '/included/normal/42/37/', 'inc-normal-view', '', '', 'inc-normal-view', views.empty_view, tuple(), - {'arg1': '42', 'arg2': '37'} + '/included/normal/42/37/', 'inc-normal-view', 'included_namespace_urls', + 'included_namespace_urls', 'included_namespace_urls:inc-normal-view', + views.empty_view, (), {'arg1': '42', 'arg2': '37'} ), ( - '/included/view_class/42/37/', 'inc-view-class', '', '', 'inc-view-class', views.view_class_instance, tuple(), - {'arg1': '42', 'arg2': '37'} + '/included/view_class/42/37/', 'inc-view-class', 'included_namespace_urls', + 'included_namespace_urls', 'included_namespace_urls:inc-view-class', + views.view_class_instance, (), {'arg1': '42', 'arg2': '37'} ), # Unnamed args are dropped if you have *any* kwargs in a pattern - ('/mixed_args/42/37/', 'mixed-args', '', '', 'mixed-args', views.empty_view, tuple(), {'arg2': '37'}), + ('/mixed_args/42/37/', 'mixed-args', '', '', 'mixed-args', views.empty_view, (), {'arg2': '37'}), ( - '/included/mixed_args/42/37/', 'inc-mixed-args', '', '', 'inc-mixed-args', views.empty_view, tuple(), - {'arg2': '37'} + '/included/mixed_args/42/37/', 'inc-mixed-args', 'included_namespace_urls', + 'included_namespace_urls', 'included_namespace_urls:inc-mixed-args', + views.empty_view, (), {'arg2': '37'} ), ( - '/included/12/mixed_args/42/37/', 'inc-mixed-args', '', '', 'inc-mixed-args', views.empty_view, tuple(), - {'arg2': '37'} + '/included/12/mixed_args/42/37/', 'inc-mixed-args', 'included_namespace_urls', + 'included_namespace_urls', 'included_namespace_urls:inc-mixed-args', + views.empty_view, (), {'arg2': '37'} ), # Unnamed views should have None as the url_name. Regression data for #21157. ( - '/unnamed/normal/42/37/', None, '', '', 'urlpatterns_reverse.views.empty_view', views.empty_view, tuple(), + '/unnamed/normal/42/37/', None, '', '', 'urlpatterns_reverse.views.empty_view', views.empty_view, (), {'arg1': '42', 'arg2': '37'} ), ( '/unnamed/view_class/42/37/', None, '', '', 'urlpatterns_reverse.views.ViewClass', views.view_class_instance, - tuple(), {'arg1': '42', 'arg2': '37'} + (), {'arg1': '42', 'arg2': '37'} ), # If you have no kwargs, you get an args list. ('/no_kwargs/42/37/', 'no-kwargs', '', '', 'no-kwargs', views.empty_view, ('42', '37'), {}), - ('/included/no_kwargs/42/37/', 'inc-no-kwargs', '', '', 'inc-no-kwargs', views.empty_view, ('42', '37'), {}), ( - '/included/12/no_kwargs/42/37/', 'inc-no-kwargs', '', '', 'inc-no-kwargs', views.empty_view, - ('12', '42', '37'), {} + '/included/no_kwargs/42/37/', 'inc-no-kwargs', 'included_namespace_urls', + 'included_namespace_urls', 'included_namespace_urls:inc-no-kwargs', + views.empty_view, ('42', '37'), {} + ), + ( + '/included/12/no_kwargs/42/37/', 'inc-no-kwargs', 'included_namespace_urls', + 'included_namespace_urls', 'included_namespace_urls:inc-no-kwargs', + views.empty_view, ('12', '42', '37'), {} ), # Namespaces ( '/test1/inner/42/37/', 'urlobject-view', 'testapp', 'test-ns1', 'test-ns1:urlobject-view', - views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'} + views.empty_view, (), {'arg1': '42', 'arg2': '37'} ), ( - '/included/test3/inner/42/37/', 'urlobject-view', 'testapp', 'test-ns3', 'test-ns3:urlobject-view', - views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'} + '/included/test3/inner/42/37/', 'urlobject-view', 'included_namespace_urls:testapp', + 'included_namespace_urls:test-ns3', 'included_namespace_urls:test-ns3:urlobject-view', + views.empty_view, (), {'arg1': '42', 'arg2': '37'} ), ( - '/ns-included1/normal/42/37/', 'inc-normal-view', '', 'inc-ns1', 'inc-ns1:inc-normal-view', views.empty_view, - tuple(), {'arg1': '42', 'arg2': '37'} + '/ns-included1/normal/42/37/', 'inc-normal-view', 'included_namespace_urls', + 'inc-ns1', 'inc-ns1:inc-normal-view', + views.empty_view, (), {'arg1': '42', 'arg2': '37'} ), ( - '/included/test3/inner/42/37/', 'urlobject-view', 'testapp', 'test-ns3', 'test-ns3:urlobject-view', - views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'} + '/included/test3/inner/42/37/', 'urlobject-view', 'included_namespace_urls:testapp', + 'included_namespace_urls:test-ns3', 'included_namespace_urls:test-ns3:urlobject-view', + views.empty_view, (), {'arg1': '42', 'arg2': '37'} ), ( - '/default/inner/42/37/', 'urlobject-view', 'testapp', 'testapp', 'testapp:urlobject-view', views.empty_view, - tuple(), {'arg1': '42', 'arg2': '37'} + '/default/inner/42/37/', 'urlobject-view', 'testapp', 'testapp', 'testapp:urlobject-view', + views.empty_view, (), {'arg1': '42', 'arg2': '37'} ), ( '/other2/inner/42/37/', 'urlobject-view', 'nodefault', 'other-ns2', 'other-ns2:urlobject-view', - views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'} + views.empty_view, (), {'arg1': '42', 'arg2': '37'} ), ( '/other1/inner/42/37/', 'urlobject-view', 'nodefault', 'other-ns1', 'other-ns1:urlobject-view', - views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'} + views.empty_view, (), {'arg1': '42', 'arg2': '37'} ), # Nested namespaces ( - '/ns-included1/test3/inner/42/37/', 'urlobject-view', 'testapp', 'inc-ns1:test-ns3', - 'inc-ns1:test-ns3:urlobject-view', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'} + '/ns-included1/test3/inner/42/37/', 'urlobject-view', 'included_namespace_urls:testapp', + 'inc-ns1:test-ns3', 'inc-ns1:test-ns3:urlobject-view', + views.empty_view, (), {'arg1': '42', 'arg2': '37'} ), ( - '/ns-included1/ns-included4/ns-included2/test3/inner/42/37/', 'urlobject-view', 'testapp', - 'inc-ns1:inc-ns4:inc-ns2:test-ns3', 'inc-ns1:inc-ns4:inc-ns2:test-ns3:urlobject-view', views.empty_view, - tuple(), {'arg1': '42', 'arg2': '37'} + '/ns-included1/ns-included4/ns-included2/test3/inner/42/37/', 'urlobject-view', + 'included_namespace_urls:namespace_urls:included_namespace_urls:testapp', + 'inc-ns1:inc-ns4:inc-ns2:test-ns3', + 'inc-ns1:inc-ns4:inc-ns2:test-ns3:urlobject-view', + views.empty_view, (), {'arg1': '42', 'arg2': '37'} ), ( - '/app-included/test3/inner/42/37/', 'urlobject-view', 'inc-app:testapp', 'inc-app:test-ns3', - 'inc-app:test-ns3:urlobject-view', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'} + '/app-included/test3/inner/42/37/', 'urlobject-view', 'included_namespace_urls:testapp', 'inc-app:test-ns3', + 'inc-app:test-ns3:urlobject-view', views.empty_view, (), {'arg1': '42', 'arg2': '37'} ), ( - '/app-included/ns-included4/ns-included2/test3/inner/42/37/', 'urlobject-view', 'inc-app:testapp', - 'inc-app:inc-ns4:inc-ns2:test-ns3', 'inc-app:inc-ns4:inc-ns2:test-ns3:urlobject-view', views.empty_view, - tuple(), {'arg1': '42', 'arg2': '37'} + '/app-included/ns-included4/ns-included2/test3/inner/42/37/', 'urlobject-view', + 'included_namespace_urls:namespace_urls:included_namespace_urls:testapp', + 'inc-app:inc-ns4:inc-ns2:test-ns3', + 'inc-app:inc-ns4:inc-ns2:test-ns3:urlobject-view', + views.empty_view, (), {'arg1': '42', 'arg2': '37'} ), # Namespaces capturing variables - ('/inc70/', 'inner-nothing', '', 'inc-ns5', 'inc-ns5:inner-nothing', views.empty_view, tuple(), {'outer': '70'}), ( - '/inc78/extra/foobar/', 'inner-extra', '', 'inc-ns5', 'inc-ns5:inner-extra', views.empty_view, tuple(), - {'outer': '78', 'extra': 'foobar'} + '/inc70/', 'inner-nothing', 'included_urls', 'inc-ns5', 'inc-ns5:inner-nothing', + views.empty_view, (), {'outer': '70'} + ), + ( + '/inc78/extra/foobar/', 'inner-extra', 'included_urls', 'inc-ns5', 'inc-ns5:inner-extra', + views.empty_view, (), {'outer': '78', 'extra': 'foobar'} ), ) @@ -183,10 +196,10 @@ ('price2', '/price/$10/', ['10'], {}), ('price3', '/price/$10/', ['10'], {}), ('product', '/product/chocolate+($2.00)/', [], {'price': '2.00', 'product': 'chocolate'}), - ('headlines', '/headlines/2007.5.21/', [], dict(year=2007, month=5, day=21)), + ('headlines', '/headlines/2007.5.21/', [], {'year': 2007, 'month': 5, 'day': 21}), ( 'windows', r'/windows_path/C:%5CDocuments%20and%20Settings%5Cspam/', [], - dict(drive_name='C', path=r'Documents and Settings\spam') + {'drive_name': 'C', 'path': r'Documents and Settings\spam'} ), ('special', r'/special_chars/~@+%5C$*%7C/', [r'~@+\$*|'], {}), ('special', r'/special_chars/some%20resource/', [r'some resource'], {}), @@ -247,9 +260,9 @@ class NoURLPatternsTests(SimpleTestCase): def test_no_urls_exception(self): """ - RegexURLResolver should raise an exception when no urlpatterns exist. + URLResolver should raise an exception when no urlpatterns exist. """ - resolver = RegexURLResolver(r'^$', settings.ROOT_URLCONF) + resolver = URLResolver(RegexPattern(r'^$'), settings.ROOT_URLCONF) with self.assertRaisesMessage( ImproperlyConfigured, @@ -265,18 +278,24 @@ class URLPatternReverse(SimpleTestCase): def test_urlpattern_reverse(self): for name, expected, args, kwargs in test_data: - try: - got = reverse(name, args=args, kwargs=kwargs) - except NoReverseMatch: - self.assertEqual(expected, NoReverseMatch) - else: - self.assertEqual(got, expected) + with self.subTest(name=name, args=args, kwargs=kwargs): + try: + got = reverse(name, args=args, kwargs=kwargs) + except NoReverseMatch: + self.assertEqual(NoReverseMatch, expected) + else: + self.assertEqual(got, expected) def test_reverse_none(self): # Reversing None should raise an error, not return the last un-named view. with self.assertRaises(NoReverseMatch): reverse(None) + def test_mixing_args_and_kwargs(self): + msg = "Don't mix *args and **kwargs in call to reverse()!" + with self.assertRaisesMessage(ValueError, msg): + reverse('name', args=['a'], kwargs={'b': 'c'}) + @override_script_prefix('/{{invalid}}/') def test_prefix_braces(self): self.assertEqual( @@ -323,20 +342,13 @@ def test_script_name_escaping(self): '/script:name/optional/foo:bar/' ) - def test_reverse_returns_unicode(self): - name, expected, args, kwargs = test_data[0] - self.assertIsInstance( - reverse(name, args=args, kwargs=kwargs), - six.text_type - ) - def test_view_not_found_message(self): msg = ( - "Reverse for 'non-existent-view' not found. 'non-existent-view' " + "Reverse for 'nonexistent-view' not found. 'nonexistent-view' " "is not a valid view function or pattern name." ) with self.assertRaisesMessage(NoReverseMatch, msg): - reverse('non-existent-view') + reverse('nonexistent-view') def test_no_args_message(self): msg = "Reverse for 'places' with no arguments not found. 1 pattern(s) tried:" @@ -351,20 +363,19 @@ def test_illegal_args_message(self): def test_illegal_kwargs_message(self): msg = "Reverse for 'places' with keyword arguments '{'arg1': 2}' not found. 1 pattern(s) tried:" with self.assertRaisesMessage(NoReverseMatch, msg): - reverse('places', kwargs={str('arg1'): 2}) + reverse('places', kwargs={'arg1': 2}) class ResolverTests(SimpleTestCase): - @ignore_warnings(category=RemovedInDjango20Warning) def test_resolver_repr(self): """ - Test repr of RegexURLResolver, especially when urlconf_name is a list + Test repr of URLResolver, especially when urlconf_name is a list (#17892). """ # Pick a resolver from a namespaced URLconf resolver = get_resolver('urlpatterns_reverse.namespace_urls') sub_resolver = resolver.namespace_dict['test-ns1'][1] - self.assertIn('', repr(sub_resolver)) + self.assertIn('', repr(sub_resolver)) def test_reverse_lazy_object_coercion_by_resolve(self): """ @@ -378,25 +389,51 @@ def test_reverse_lazy_object_coercion_by_resolve(self): def test_resolver_reverse(self): resolver = get_resolver('urlpatterns_reverse.named_urls') - self.assertEqual(resolver.reverse('named-url1'), '') - self.assertEqual(resolver.reverse('named-url2', 'arg'), 'extra/arg/') - self.assertEqual(resolver.reverse('named-url2', extra='arg'), 'extra/arg/') + test_urls = [ + # (name, args, kwargs, expected) + ('named-url1', (), {}, ''), + ('named-url2', ('arg',), {}, 'extra/arg/'), + ('named-url2', (), {'extra': 'arg'}, 'extra/arg/'), + ] + for name, args, kwargs, expected in test_urls: + with self.subTest(name=name, args=args, kwargs=kwargs): + self.assertEqual(resolver.reverse(name, *args, **kwargs), expected) + + def test_resolver_reverse_conflict(self): + """ + URL pattern name arguments don't need to be unique. The last registered + pattern takes precedence for conflicting names. + """ + resolver = get_resolver('urlpatterns_reverse.named_urls_conflict') + test_urls = [ + # (name, args, kwargs, expected) + # Without arguments, the last URL in urlpatterns has precedence. + ('name-conflict', (), {}, 'conflict/'), + # With an arg, the last URL in urlpatterns has precedence. + ('name-conflict', ('arg',), {}, 'conflict-last/arg/'), + # With a kwarg, other URL patterns can be reversed. + ('name-conflict', (), {'first': 'arg'}, 'conflict-first/arg/'), + ('name-conflict', (), {'middle': 'arg'}, 'conflict-middle/arg/'), + ('name-conflict', (), {'last': 'arg'}, 'conflict-last/arg/'), + # The number and order of the arguments don't interfere with reversing. + ('name-conflict', ('arg', 'arg'), {}, 'conflict/arg/arg/'), + ] + for name, args, kwargs, expected in test_urls: + with self.subTest(name=name, args=args, kwargs=kwargs): + self.assertEqual(resolver.reverse(name, *args, **kwargs), expected) def test_non_regex(self): """ A Resolver404 is raised if resolving doesn't meet the basic requirements of a path to match - i.e., at the very least, it matches the root pattern '^/'. Never return None from resolve() to prevent a - TypeError from occuring later (#10834). + TypeError from occurring later (#10834). """ - with self.assertRaises(Resolver404): - resolve('') - with self.assertRaises(Resolver404): - resolve('a') - with self.assertRaises(Resolver404): - resolve('\\') - with self.assertRaises(Resolver404): - resolve('.') + test_urls = ['', 'a', '\\', '.'] + for path_ in test_urls: + with self.subTest(path=path_): + with self.assertRaises(Resolver404): + resolve(path_) def test_404_tried_urls_have_names(self): """ @@ -406,23 +443,22 @@ def test_404_tried_urls_have_names(self): """ urls = 'urlpatterns_reverse.named_urls' # this list matches the expected URL types and names returned when - # you try to resolve a non-existent URL in the first level of included - # URLs in named_urls.py (e.g., '/included/non-existent-url') + # you try to resolve a nonexistent URL in the first level of included + # URLs in named_urls.py (e.g., '/included/nonexistent-url') url_types_names = [ - [{'type': RegexURLPattern, 'name': 'named-url1'}], - [{'type': RegexURLPattern, 'name': 'named-url2'}], - [{'type': RegexURLPattern, 'name': None}], - [{'type': RegexURLResolver}, {'type': RegexURLPattern, 'name': 'named-url3'}], - [{'type': RegexURLResolver}, {'type': RegexURLPattern, 'name': 'named-url4'}], - [{'type': RegexURLResolver}, {'type': RegexURLPattern, 'name': None}], - [{'type': RegexURLResolver}, {'type': RegexURLResolver}], + [{'type': URLPattern, 'name': 'named-url1'}], + [{'type': URLPattern, 'name': 'named-url2'}], + [{'type': URLPattern, 'name': None}], + [{'type': URLResolver}, {'type': URLPattern, 'name': 'named-url3'}], + [{'type': URLResolver}, {'type': URLPattern, 'name': 'named-url4'}], + [{'type': URLResolver}, {'type': URLPattern, 'name': None}], + [{'type': URLResolver}, {'type': URLResolver}], ] - with self.assertRaisesMessage(Resolver404, b'tried' if six.PY2 else 'tried') as cm: - resolve('/included/non-existent-url', urlconf=urls) + with self.assertRaisesMessage(Resolver404, 'tried') as cm: + resolve('/included/nonexistent-url', urlconf=urls) e = cm.exception # make sure we at least matched the root ('/') url resolver: self.assertIn('tried', e.args[0]) - tried = e.args[0]['tried'] self.assertEqual( len(e.args[0]['tried']), len(url_types_names), @@ -432,16 +468,17 @@ def test_404_tried_urls_have_names(self): ) for tried, expected in zip(e.args[0]['tried'], url_types_names): for t, e in zip(tried, expected): - self.assertIsInstance(t, e['type']), str('%s is not an instance of %s') % (t, e['type']) - if 'name' in e: - if not e['name']: - self.assertIsNone(t.name, 'Expected no URL name but found %s.' % t.name) - else: - self.assertEqual( - t.name, - e['name'], - 'Wrong URL name. Expected "%s", got "%s".' % (e['name'], t.name) - ) + with self.subTest(t): + self.assertIsInstance(t, e['type']), '%s is not an instance of %s' % (t, e['type']) + if 'name' in e: + if not e['name']: + self.assertIsNone(t.name, 'Expected no URL name but found %s.' % t.name) + else: + self.assertEqual( + t.name, + e['name'], + 'Wrong URL name. Expected "%s", got "%s".' % (e['name'], t.name) + ) def test_namespaced_view_detail(self): resolver = get_resolver('urlpatterns_reverse.nested_urls') @@ -450,7 +487,6 @@ def test_namespaced_view_detail(self): self.assertTrue(resolver._is_callback('urlpatterns_reverse.nested_urls.View3')) self.assertFalse(resolver._is_callback('urlpatterns_reverse.nested_urls.blub')) - @unittest.skipIf(six.PY2, "Python 2 doesn't support __qualname__.") def test_view_detail_as_method(self): # Views which have a class name as part of their path. resolver = get_resolver('urlpatterns_reverse.method_view_urls') @@ -459,10 +495,10 @@ def test_view_detail_as_method(self): def test_populate_concurrency(self): """ - RegexURLResolver._populate() can be called concurrently, but not more + URLResolver._populate() can be called concurrently, but not more than once per thread (#26888). """ - resolver = RegexURLResolver(r'^/', 'urlpatterns_reverse.urls') + resolver = URLResolver(RegexPattern(r'^/'), 'urlpatterns_reverse.urls') resolver._local.populating = True thread = threading.Thread(target=resolver._populate) thread.start() @@ -490,11 +526,6 @@ def test_inserting_reverse_lazy_into_string(self): 'Some URL: %s' % reverse_lazy('some-login-page'), 'Some URL: /login/' ) - if six.PY2: - self.assertEqual( - b'Some URL: %s' % reverse_lazy('some-login-page'), - 'Some URL: /login/' - ) class ReverseLazySettingsTest(AdminScriptTestCase): @@ -503,9 +534,10 @@ class ReverseLazySettingsTest(AdminScriptTestCase): import error. """ def setUp(self): - self.write_settings('settings.py', extra=""" -from django.urls import reverse_lazy -LOGIN_URL = reverse_lazy('login')""") + self.write_settings( + 'settings.py', + extra="from django.urls import reverse_lazy\nLOGIN_URL = reverse_lazy('login')", + ) def tearDown(self): self.remove_settings('settings.py') @@ -520,7 +552,7 @@ class ReverseShortcutTests(SimpleTestCase): def test_redirect_to_object(self): # We don't really need a model; just something with a get_absolute_url - class FakeObj(object): + class FakeObj: def get_absolute_url(self): return "/hi-there/" @@ -576,242 +608,326 @@ def test_redirect_view_object(self): @override_settings(ROOT_URLCONF='urlpatterns_reverse.namespace_urls') -@ignore_warnings(category=RemovedInDjango20Warning) class NamespaceTests(SimpleTestCase): def test_ambiguous_object(self): - "Names deployed via dynamic URL objects that require namespaces can't be resolved" - with self.assertRaises(NoReverseMatch): - reverse('urlobject-view') - with self.assertRaises(NoReverseMatch): - reverse('urlobject-view', args=[37, 42]) - with self.assertRaises(NoReverseMatch): - reverse('urlobject-view', kwargs={'arg1': 42, 'arg2': 37}) + """ + Names deployed via dynamic URL objects that require namespaces can't + be resolved. + """ + test_urls = [ + ('urlobject-view', [], {}), + ('urlobject-view', [37, 42], {}), + ('urlobject-view', [], {'arg1': 42, 'arg2': 37}), + ] + for name, args, kwargs in test_urls: + with self.subTest(name=name, args=args, kwargs=kwargs): + with self.assertRaises(NoReverseMatch): + reverse(name, args=args, kwargs=kwargs) def test_ambiguous_urlpattern(self): - "Names deployed via dynamic URL objects that require namespaces can't be resolved" - with self.assertRaises(NoReverseMatch): - reverse('inner-nothing') - with self.assertRaises(NoReverseMatch): - reverse('inner-nothing', args=[37, 42]) - with self.assertRaises(NoReverseMatch): - reverse('inner-nothing', kwargs={'arg1': 42, 'arg2': 37}) + """ + Names deployed via dynamic URL objects that require namespaces can't + be resolved. + """ + test_urls = [ + ('inner-nothing', [], {}), + ('inner-nothing', [37, 42], {}), + ('inner-nothing', [], {'arg1': 42, 'arg2': 37}), + ] + for name, args, kwargs in test_urls: + with self.subTest(name=name, args=args, kwargs=kwargs): + with self.assertRaises(NoReverseMatch): + reverse(name, args=args, kwargs=kwargs) def test_non_existent_namespace(self): - "Non-existent namespaces raise errors" - with self.assertRaises(NoReverseMatch): - reverse('blahblah:urlobject-view') - with self.assertRaises(NoReverseMatch): - reverse('test-ns1:blahblah:urlobject-view') + """Nonexistent namespaces raise errors.""" + test_urls = [ + 'blahblah:urlobject-view', + 'test-ns1:blahblah:urlobject-view', + ] + for name in test_urls: + with self.subTest(name=name): + with self.assertRaises(NoReverseMatch): + reverse(name) def test_normal_name(self): - "Normal lookups work as expected" - self.assertEqual('/normal/', reverse('normal-view')) - self.assertEqual('/normal/37/42/', reverse('normal-view', args=[37, 42])) - self.assertEqual('/normal/42/37/', reverse('normal-view', kwargs={'arg1': 42, 'arg2': 37})) - self.assertEqual('/+%5C$*/', reverse('special-view')) + """Normal lookups work as expected.""" + test_urls = [ + ('normal-view', [], {}, '/normal/'), + ('normal-view', [37, 42], {}, '/normal/37/42/'), + ('normal-view', [], {'arg1': 42, 'arg2': 37}, '/normal/42/37/'), + ('special-view', [], {}, '/+%5C$*/'), + ] + for name, args, kwargs, expected in test_urls: + with self.subTest(name=name, args=args, kwargs=kwargs): + self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected) def test_simple_included_name(self): - "Normal lookups work on names included from other patterns" - self.assertEqual('/included/normal/', reverse('inc-normal-view')) - self.assertEqual('/included/normal/37/42/', reverse('inc-normal-view', args=[37, 42])) - self.assertEqual('/included/normal/42/37/', reverse('inc-normal-view', kwargs={'arg1': 42, 'arg2': 37})) - self.assertEqual('/included/+%5C$*/', reverse('inc-special-view')) + """Normal lookups work on names included from other patterns.""" + test_urls = [ + ('included_namespace_urls:inc-normal-view', [], {}, '/included/normal/'), + ('included_namespace_urls:inc-normal-view', [37, 42], {}, '/included/normal/37/42/'), + ('included_namespace_urls:inc-normal-view', [], {'arg1': 42, 'arg2': 37}, '/included/normal/42/37/'), + ('included_namespace_urls:inc-special-view', [], {}, '/included/+%5C$*/'), + ] + for name, args, kwargs, expected in test_urls: + with self.subTest(name=name, args=args, kwargs=kwargs): + self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected) def test_namespace_object(self): - "Dynamic URL objects can be found using a namespace" - self.assertEqual('/test1/inner/', reverse('test-ns1:urlobject-view')) - self.assertEqual('/test1/inner/37/42/', reverse('test-ns1:urlobject-view', args=[37, 42])) - self.assertEqual('/test1/inner/42/37/', reverse('test-ns1:urlobject-view', kwargs={'arg1': 42, 'arg2': 37})) - self.assertEqual('/test1/inner/+%5C$*/', reverse('test-ns1:urlobject-special-view')) + """Dynamic URL objects can be found using a namespace.""" + test_urls = [ + ('test-ns1:urlobject-view', [], {}, '/test1/inner/'), + ('test-ns1:urlobject-view', [37, 42], {}, '/test1/inner/37/42/'), + ('test-ns1:urlobject-view', [], {'arg1': 42, 'arg2': 37}, '/test1/inner/42/37/'), + ('test-ns1:urlobject-special-view', [], {}, '/test1/inner/+%5C$*/'), + ] + for name, args, kwargs, expected in test_urls: + with self.subTest(name=name, args=args, kwargs=kwargs): + self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected) def test_app_object(self): - "Dynamic URL objects can return a (pattern, app_name) 2-tuple, and include() can set the namespace" - self.assertEqual('/newapp1/inner/', reverse('new-ns1:urlobject-view')) - self.assertEqual('/newapp1/inner/37/42/', reverse('new-ns1:urlobject-view', args=[37, 42])) - self.assertEqual('/newapp1/inner/42/37/', reverse('new-ns1:urlobject-view', kwargs={'arg1': 42, 'arg2': 37})) - self.assertEqual('/newapp1/inner/+%5C$*/', reverse('new-ns1:urlobject-special-view')) + """ + Dynamic URL objects can return a (pattern, app_name) 2-tuple, and + include() can set the namespace. + """ + test_urls = [ + ('new-ns1:urlobject-view', [], {}, '/newapp1/inner/'), + ('new-ns1:urlobject-view', [37, 42], {}, '/newapp1/inner/37/42/'), + ('new-ns1:urlobject-view', [], {'arg1': 42, 'arg2': 37}, '/newapp1/inner/42/37/'), + ('new-ns1:urlobject-special-view', [], {}, '/newapp1/inner/+%5C$*/'), + ] + for name, args, kwargs, expected in test_urls: + with self.subTest(name=name, args=args, kwargs=kwargs): + self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected) def test_app_object_default_namespace(self): - "Namespace defaults to app_name when including a (pattern, app_name) 2-tuple" - self.assertEqual('/new-default/inner/', reverse('newapp:urlobject-view')) - self.assertEqual('/new-default/inner/37/42/', reverse('newapp:urlobject-view', args=[37, 42])) - self.assertEqual( - '/new-default/inner/42/37/', reverse('newapp:urlobject-view', kwargs={'arg1': 42, 'arg2': 37}) - ) - self.assertEqual('/new-default/inner/+%5C$*/', reverse('newapp:urlobject-special-view')) + """ + Namespace defaults to app_name when including a (pattern, app_name) + 2-tuple. + """ + test_urls = [ + ('newapp:urlobject-view', [], {}, '/new-default/inner/'), + ('newapp:urlobject-view', [37, 42], {}, '/new-default/inner/37/42/'), + ('newapp:urlobject-view', [], {'arg1': 42, 'arg2': 37}, '/new-default/inner/42/37/'), + ('newapp:urlobject-special-view', [], {}, '/new-default/inner/+%5C$*/'), + ] + for name, args, kwargs, expected in test_urls: + with self.subTest(name=name, args=args, kwargs=kwargs): + self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected) def test_embedded_namespace_object(self): - "Namespaces can be installed anywhere in the URL pattern tree" - self.assertEqual('/included/test3/inner/', reverse('test-ns3:urlobject-view')) - self.assertEqual('/included/test3/inner/37/42/', reverse('test-ns3:urlobject-view', args=[37, 42])) - self.assertEqual( - '/included/test3/inner/42/37/', reverse('test-ns3:urlobject-view', kwargs={'arg1': 42, 'arg2': 37}) - ) - self.assertEqual('/included/test3/inner/+%5C$*/', reverse('test-ns3:urlobject-special-view')) + """Namespaces can be installed anywhere in the URL pattern tree.""" + test_urls = [ + ('included_namespace_urls:test-ns3:urlobject-view', [], {}, '/included/test3/inner/'), + ('included_namespace_urls:test-ns3:urlobject-view', [37, 42], {}, '/included/test3/inner/37/42/'), + ( + 'included_namespace_urls:test-ns3:urlobject-view', [], {'arg1': 42, 'arg2': 37}, + '/included/test3/inner/42/37/', + ), + ('included_namespace_urls:test-ns3:urlobject-special-view', [], {}, '/included/test3/inner/+%5C$*/'), + ] + for name, args, kwargs, expected in test_urls: + with self.subTest(name=name, args=args, kwargs=kwargs): + self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected) def test_namespace_pattern(self): - "Namespaces can be applied to include()'d urlpatterns" - self.assertEqual('/ns-included1/normal/', reverse('inc-ns1:inc-normal-view')) - self.assertEqual('/ns-included1/normal/37/42/', reverse('inc-ns1:inc-normal-view', args=[37, 42])) - self.assertEqual( - '/ns-included1/normal/42/37/', reverse('inc-ns1:inc-normal-view', kwargs={'arg1': 42, 'arg2': 37}) - ) - self.assertEqual('/ns-included1/+%5C$*/', reverse('inc-ns1:inc-special-view')) + """Namespaces can be applied to include()'d urlpatterns.""" + test_urls = [ + ('inc-ns1:inc-normal-view', [], {}, '/ns-included1/normal/'), + ('inc-ns1:inc-normal-view', [37, 42], {}, '/ns-included1/normal/37/42/'), + ('inc-ns1:inc-normal-view', [], {'arg1': 42, 'arg2': 37}, '/ns-included1/normal/42/37/'), + ('inc-ns1:inc-special-view', [], {}, '/ns-included1/+%5C$*/'), + ] + for name, args, kwargs, expected in test_urls: + with self.subTest(name=name, args=args, kwargs=kwargs): + self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected) def test_app_name_pattern(self): - "Namespaces can be applied to include()'d urlpatterns that set an app_name attribute" - self.assertEqual('/app-included1/normal/', reverse('app-ns1:inc-normal-view')) - self.assertEqual('/app-included1/normal/37/42/', reverse('app-ns1:inc-normal-view', args=[37, 42])) - self.assertEqual( - '/app-included1/normal/42/37/', reverse('app-ns1:inc-normal-view', kwargs={'arg1': 42, 'arg2': 37}) - ) - self.assertEqual('/app-included1/+%5C$*/', reverse('app-ns1:inc-special-view')) + """ + Namespaces can be applied to include()'d urlpatterns that set an + app_name attribute. + """ + test_urls = [ + ('app-ns1:inc-normal-view', [], {}, '/app-included1/normal/'), + ('app-ns1:inc-normal-view', [37, 42], {}, '/app-included1/normal/37/42/'), + ('app-ns1:inc-normal-view', [], {'arg1': 42, 'arg2': 37}, '/app-included1/normal/42/37/'), + ('app-ns1:inc-special-view', [], {}, '/app-included1/+%5C$*/'), + ] + for name, args, kwargs, expected in test_urls: + with self.subTest(name=name, args=args, kwargs=kwargs): + self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected) def test_namespace_pattern_with_variable_prefix(self): - "When using an include with namespaces when there is a regex variable in front of it" - self.assertEqual('/ns-outer/42/normal/', reverse('inc-outer:inc-normal-view', kwargs={'outer': 42})) - self.assertEqual('/ns-outer/42/normal/', reverse('inc-outer:inc-normal-view', args=[42])) - self.assertEqual( - '/ns-outer/42/normal/37/4/', - reverse('inc-outer:inc-normal-view', kwargs={'outer': 42, 'arg1': 37, 'arg2': 4}) - ) - self.assertEqual('/ns-outer/42/normal/37/4/', reverse('inc-outer:inc-normal-view', args=[42, 37, 4])) - self.assertEqual('/ns-outer/42/+%5C$*/', reverse('inc-outer:inc-special-view', kwargs={'outer': 42})) - self.assertEqual('/ns-outer/42/+%5C$*/', reverse('inc-outer:inc-special-view', args=[42])) + """ + Using include() with namespaces when there is a regex variable in front + of it. + """ + test_urls = [ + ('inc-outer:inc-normal-view', [], {'outer': 42}, '/ns-outer/42/normal/'), + ('inc-outer:inc-normal-view', [42], {}, '/ns-outer/42/normal/'), + ('inc-outer:inc-normal-view', [], {'arg1': 37, 'arg2': 4, 'outer': 42}, '/ns-outer/42/normal/37/4/'), + ('inc-outer:inc-normal-view', [42, 37, 4], {}, '/ns-outer/42/normal/37/4/'), + ('inc-outer:inc-special-view', [], {'outer': 42}, '/ns-outer/42/+%5C$*/'), + ('inc-outer:inc-special-view', [42], {}, '/ns-outer/42/+%5C$*/'), + ] + for name, args, kwargs, expected in test_urls: + with self.subTest(name=name, args=args, kwargs=kwargs): + self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected) def test_multiple_namespace_pattern(self): - "Namespaces can be embedded" - self.assertEqual('/ns-included1/test3/inner/', reverse('inc-ns1:test-ns3:urlobject-view')) - self.assertEqual('/ns-included1/test3/inner/37/42/', reverse('inc-ns1:test-ns3:urlobject-view', args=[37, 42])) - self.assertEqual( - '/ns-included1/test3/inner/42/37/', - reverse('inc-ns1:test-ns3:urlobject-view', kwargs={'arg1': 42, 'arg2': 37}) - ) - self.assertEqual('/ns-included1/test3/inner/+%5C$*/', reverse('inc-ns1:test-ns3:urlobject-special-view')) + """Namespaces can be embedded.""" + test_urls = [ + ('inc-ns1:test-ns3:urlobject-view', [], {}, '/ns-included1/test3/inner/'), + ('inc-ns1:test-ns3:urlobject-view', [37, 42], {}, '/ns-included1/test3/inner/37/42/'), + ( + 'inc-ns1:test-ns3:urlobject-view', [], {'arg1': 42, 'arg2': 37}, + '/ns-included1/test3/inner/42/37/', + ), + ('inc-ns1:test-ns3:urlobject-special-view', [], {}, '/ns-included1/test3/inner/+%5C$*/'), + ] + for name, args, kwargs, expected in test_urls: + with self.subTest(name=name, args=args, kwargs=kwargs): + self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected) def test_nested_namespace_pattern(self): - "Namespaces can be nested" - self.assertEqual( - '/ns-included1/ns-included4/ns-included1/test3/inner/', - reverse('inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-view') - ) - self.assertEqual( - '/ns-included1/ns-included4/ns-included1/test3/inner/37/42/', - reverse('inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-view', args=[37, 42]) - ) - self.assertEqual( - '/ns-included1/ns-included4/ns-included1/test3/inner/42/37/', - reverse('inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-view', kwargs={'arg1': 42, 'arg2': 37}) - ) - self.assertEqual( - '/ns-included1/ns-included4/ns-included1/test3/inner/+%5C$*/', - reverse('inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-special-view') - ) + """Namespaces can be nested.""" + test_urls = [ + ( + 'inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-view', [], {}, + '/ns-included1/ns-included4/ns-included1/test3/inner/', + ), + ( + 'inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-view', [37, 42], {}, + '/ns-included1/ns-included4/ns-included1/test3/inner/37/42/', + ), + ( + 'inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-view', [], {'arg1': 42, 'arg2': 37}, + '/ns-included1/ns-included4/ns-included1/test3/inner/42/37/', + ), + ( + 'inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-special-view', [], {}, + '/ns-included1/ns-included4/ns-included1/test3/inner/+%5C$*/', + ), + ] + for name, args, kwargs, expected in test_urls: + with self.subTest(name=name, args=args, kwargs=kwargs): + self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected) def test_app_lookup_object(self): - "A default application namespace can be used for lookup" - self.assertEqual('/default/inner/', reverse('testapp:urlobject-view')) - self.assertEqual('/default/inner/37/42/', reverse('testapp:urlobject-view', args=[37, 42])) - self.assertEqual('/default/inner/42/37/', reverse('testapp:urlobject-view', kwargs={'arg1': 42, 'arg2': 37})) - self.assertEqual('/default/inner/+%5C$*/', reverse('testapp:urlobject-special-view')) + """A default application namespace can be used for lookup.""" + test_urls = [ + ('testapp:urlobject-view', [], {}, '/default/inner/'), + ('testapp:urlobject-view', [37, 42], {}, '/default/inner/37/42/'), + ('testapp:urlobject-view', [], {'arg1': 42, 'arg2': 37}, '/default/inner/42/37/'), + ('testapp:urlobject-special-view', [], {}, '/default/inner/+%5C$*/'), + ] + for name, args, kwargs, expected in test_urls: + with self.subTest(name=name, args=args, kwargs=kwargs): + self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected) def test_app_lookup_object_with_default(self): - "A default application namespace is sensitive to the 'current' app can be used for lookup" - self.assertEqual('/included/test3/inner/', reverse('testapp:urlobject-view', current_app='test-ns3')) - self.assertEqual( - '/included/test3/inner/37/42/', - reverse('testapp:urlobject-view', args=[37, 42], current_app='test-ns3') - ) - self.assertEqual( - '/included/test3/inner/42/37/', - reverse('testapp:urlobject-view', kwargs={'arg1': 42, 'arg2': 37}, current_app='test-ns3') - ) - self.assertEqual( - '/included/test3/inner/+%5C$*/', reverse('testapp:urlobject-special-view', current_app='test-ns3') - ) + """A default application namespace is sensitive to the current app.""" + test_urls = [ + ('testapp:urlobject-view', [], {}, 'test-ns3', '/default/inner/'), + ('testapp:urlobject-view', [37, 42], {}, 'test-ns3', '/default/inner/37/42/'), + ('testapp:urlobject-view', [], {'arg1': 42, 'arg2': 37}, 'test-ns3', '/default/inner/42/37/'), + ('testapp:urlobject-special-view', [], {}, 'test-ns3', '/default/inner/+%5C$*/'), + ] + for name, args, kwargs, current_app, expected in test_urls: + with self.subTest(name=name, args=args, kwargs=kwargs, current_app=current_app): + self.assertEqual(reverse(name, args=args, kwargs=kwargs, current_app=current_app), expected) def test_app_lookup_object_without_default(self): - "An application namespace without a default is sensitive to the 'current' app can be used for lookup" - self.assertEqual('/other2/inner/', reverse('nodefault:urlobject-view')) - self.assertEqual('/other2/inner/37/42/', reverse('nodefault:urlobject-view', args=[37, 42])) - self.assertEqual('/other2/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1': 42, 'arg2': 37})) - self.assertEqual('/other2/inner/+%5C$*/', reverse('nodefault:urlobject-special-view')) - - self.assertEqual('/other1/inner/', reverse('nodefault:urlobject-view', current_app='other-ns1')) - self.assertEqual( - '/other1/inner/37/42/', reverse('nodefault:urlobject-view', args=[37, 42], current_app='other-ns1') - ) - self.assertEqual( - '/other1/inner/42/37/', - reverse('nodefault:urlobject-view', kwargs={'arg1': 42, 'arg2': 37}, current_app='other-ns1') - ) - self.assertEqual('/other1/inner/+%5C$*/', reverse('nodefault:urlobject-special-view', current_app='other-ns1')) + """ + An application namespace without a default is sensitive to the current + app. + """ + test_urls = [ + ('nodefault:urlobject-view', [], {}, None, '/other2/inner/'), + ('nodefault:urlobject-view', [37, 42], {}, None, '/other2/inner/37/42/'), + ('nodefault:urlobject-view', [], {'arg1': 42, 'arg2': 37}, None, '/other2/inner/42/37/'), + ('nodefault:urlobject-special-view', [], {}, None, '/other2/inner/+%5C$*/'), + ('nodefault:urlobject-view', [], {}, 'other-ns1', '/other1/inner/'), + ('nodefault:urlobject-view', [37, 42], {}, 'other-ns1', '/other1/inner/37/42/'), + ('nodefault:urlobject-view', [], {'arg1': 42, 'arg2': 37}, 'other-ns1', '/other1/inner/42/37/'), + ('nodefault:urlobject-special-view', [], {}, 'other-ns1', '/other1/inner/+%5C$*/'), + ] + for name, args, kwargs, current_app, expected in test_urls: + with self.subTest(name=name, args=args, kwargs=kwargs, current_app=current_app): + self.assertEqual(reverse(name, args=args, kwargs=kwargs, current_app=current_app), expected) def test_special_chars_namespace(self): - self.assertEqual('/+%5C$*/included/normal/', reverse('special:inc-normal-view')) - self.assertEqual('/+%5C$*/included/normal/37/42/', reverse('special:inc-normal-view', args=[37, 42])) - self.assertEqual( - '/+%5C$*/included/normal/42/37/', - reverse('special:inc-normal-view', kwargs={'arg1': 42, 'arg2': 37}) - ) - self.assertEqual('/+%5C$*/included/+%5C$*/', reverse('special:inc-special-view')) + test_urls = [ + ('special:included_namespace_urls:inc-normal-view', [], {}, '/+%5C$*/included/normal/'), + ('special:included_namespace_urls:inc-normal-view', [37, 42], {}, '/+%5C$*/included/normal/37/42/'), + ( + 'special:included_namespace_urls:inc-normal-view', [], {'arg1': 42, 'arg2': 37}, + '/+%5C$*/included/normal/42/37/', + ), + ('special:included_namespace_urls:inc-special-view', [], {}, '/+%5C$*/included/+%5C$*/'), + ] + for name, args, kwargs, expected in test_urls: + with self.subTest(name=name, args=args, kwargs=kwargs): + self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected) def test_namespaces_with_variables(self): - "Namespace prefixes can capture variables: see #15900" - self.assertEqual('/inc70/', reverse('inc-ns5:inner-nothing', kwargs={'outer': '70'})) - self.assertEqual( - '/inc78/extra/foobar/', reverse('inc-ns5:inner-extra', kwargs={'outer': '78', 'extra': 'foobar'}) - ) - self.assertEqual('/inc70/', reverse('inc-ns5:inner-nothing', args=['70'])) - self.assertEqual('/inc78/extra/foobar/', reverse('inc-ns5:inner-extra', args=['78', 'foobar'])) + """Namespace prefixes can capture variables.""" + test_urls = [ + ('inc-ns5:inner-nothing', [], {'outer': '70'}, '/inc70/'), + ('inc-ns5:inner-extra', [], {'extra': 'foobar', 'outer': '78'}, '/inc78/extra/foobar/'), + ('inc-ns5:inner-nothing', ['70'], {}, '/inc70/'), + ('inc-ns5:inner-extra', ['78', 'foobar'], {}, '/inc78/extra/foobar/'), + ] + for name, args, kwargs, expected in test_urls: + with self.subTest(name=name, args=args, kwargs=kwargs): + self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected) def test_nested_app_lookup(self): - "A nested current_app should be split in individual namespaces (#24904)" - self.assertEqual('/ns-included1/test4/inner/', reverse('inc-ns1:testapp:urlobject-view')) - self.assertEqual('/ns-included1/test4/inner/37/42/', reverse('inc-ns1:testapp:urlobject-view', args=[37, 42])) - self.assertEqual( - '/ns-included1/test4/inner/42/37/', - reverse('inc-ns1:testapp:urlobject-view', kwargs={'arg1': 42, 'arg2': 37}) - ) - self.assertEqual('/ns-included1/test4/inner/+%5C$*/', reverse('inc-ns1:testapp:urlobject-special-view')) - - self.assertEqual( - '/ns-included1/test3/inner/', - reverse('inc-ns1:testapp:urlobject-view', current_app='inc-ns1:test-ns3') - ) - self.assertEqual( - '/ns-included1/test3/inner/37/42/', - reverse('inc-ns1:testapp:urlobject-view', args=[37, 42], current_app='inc-ns1:test-ns3') - ) - self.assertEqual( - '/ns-included1/test3/inner/42/37/', - reverse('inc-ns1:testapp:urlobject-view', kwargs={'arg1': 42, 'arg2': 37}, current_app='inc-ns1:test-ns3') - ) - self.assertEqual( - '/ns-included1/test3/inner/+%5C$*/', - reverse('inc-ns1:testapp:urlobject-special-view', current_app='inc-ns1:test-ns3') - ) + """ + A nested current_app should be split in individual namespaces (#24904). + """ + test_urls = [ + ('inc-ns1:testapp:urlobject-view', [], {}, None, '/ns-included1/test4/inner/'), + ('inc-ns1:testapp:urlobject-view', [37, 42], {}, None, '/ns-included1/test4/inner/37/42/'), + ('inc-ns1:testapp:urlobject-view', [], {'arg1': 42, 'arg2': 37}, None, '/ns-included1/test4/inner/42/37/'), + ('inc-ns1:testapp:urlobject-special-view', [], {}, None, '/ns-included1/test4/inner/+%5C$*/'), + ('inc-ns1:testapp:urlobject-view', [], {}, 'inc-ns1:test-ns3', '/ns-included1/test3/inner/'), + ('inc-ns1:testapp:urlobject-view', [37, 42], {}, 'inc-ns1:test-ns3', '/ns-included1/test3/inner/37/42/'), + ( + 'inc-ns1:testapp:urlobject-view', [], {'arg1': 42, 'arg2': 37}, 'inc-ns1:test-ns3', + '/ns-included1/test3/inner/42/37/', + ), + ( + 'inc-ns1:testapp:urlobject-special-view', [], {}, 'inc-ns1:test-ns3', + '/ns-included1/test3/inner/+%5C$*/', + ), + ] + for name, args, kwargs, current_app, expected in test_urls: + with self.subTest(name=name, args=args, kwargs=kwargs, current_app=current_app): + self.assertEqual(reverse(name, args=args, kwargs=kwargs, current_app=current_app), expected) def test_current_app_no_partial_match(self): - "current_app should either match the whole path or shouldn't be used" - self.assertEqual( - '/ns-included1/test4/inner/', - reverse('inc-ns1:testapp:urlobject-view', current_app='non-existent:test-ns3') - ) - self.assertEqual( - '/ns-included1/test4/inner/37/42/', - reverse('inc-ns1:testapp:urlobject-view', args=[37, 42], current_app='non-existent:test-ns3') - ) - self.assertEqual( - '/ns-included1/test4/inner/42/37/', - reverse('inc-ns1:testapp:urlobject-view', kwargs={'arg1': 42, 'arg2': 37}, - current_app='non-existent:test-ns3') - ) - self.assertEqual( - '/ns-included1/test4/inner/+%5C$*/', - reverse('inc-ns1:testapp:urlobject-special-view', current_app='non-existent:test-ns3') - ) + """current_app shouldn't be used unless it matches the whole path.""" + test_urls = [ + ('inc-ns1:testapp:urlobject-view', [], {}, 'nonexistent:test-ns3', '/ns-included1/test4/inner/'), + ( + 'inc-ns1:testapp:urlobject-view', [37, 42], {}, 'nonexistent:test-ns3', + '/ns-included1/test4/inner/37/42/', + ), + ( + 'inc-ns1:testapp:urlobject-view', [], {'arg1': 42, 'arg2': 37}, 'nonexistent:test-ns3', + '/ns-included1/test4/inner/42/37/', + ), + ( + 'inc-ns1:testapp:urlobject-special-view', [], {}, 'nonexistent:test-ns3', + '/ns-included1/test4/inner/+%5C$*/', + ), + ] + for name, args, kwargs, current_app, expected in test_urls: + with self.subTest(name=name, args=args, kwargs=kwargs, current_app=current_app): + self.assertEqual(reverse(name, args=args, kwargs=kwargs, current_app=current_app), expected) @override_settings(ROOT_URLCONF=urlconf_outer.__name__) @@ -883,8 +999,11 @@ def test_reverse_outer_in_response_middleware(self): Test reversing an URL from the *default* URLconf from inside a response middleware. """ - message = "Reverse for 'outer' not found." - with self.assertRaisesMessage(NoReverseMatch, message): + msg = ( + "Reverse for 'outer' not found. 'outer' is not a valid view " + "function or pattern name." + ) + with self.assertRaisesMessage(NoReverseMatch, msg): self.client.get('/second_test/') @override_settings( @@ -918,6 +1037,13 @@ def test_reverse_outer_in_streaming(self): self.client.get('/second_test/') b''.join(self.client.get('/second_test/')) + def test_urlconf_is_reset_after_request(self): + """The URLconf is reset after each request.""" + self.assertIsNone(get_urlconf()) + with override_settings(MIDDLEWARE=['%s.ChangeURLconfMiddleware' % middleware.__name__]): + self.client.get(reverse('inner')) + self.assertIsNone(get_urlconf()) + class ErrorHandlerResolutionTests(SimpleTestCase): """Tests for handler400, handler404 and handler500""" @@ -925,23 +1051,23 @@ class ErrorHandlerResolutionTests(SimpleTestCase): def setUp(self): urlconf = 'urlpatterns_reverse.urls_error_handlers' urlconf_callables = 'urlpatterns_reverse.urls_error_handlers_callables' - self.resolver = RegexURLResolver(r'^$', urlconf) - self.callable_resolver = RegexURLResolver(r'^$', urlconf_callables) + self.resolver = URLResolver(RegexPattern(r'^$'), urlconf) + self.callable_resolver = URLResolver(RegexPattern(r'^$'), urlconf_callables) def test_named_handlers(self): handler = (empty_view, {}) - self.assertEqual(self.resolver.resolve_error_handler(400), handler) - self.assertEqual(self.resolver.resolve_error_handler(404), handler) - self.assertEqual(self.resolver.resolve_error_handler(500), handler) + for code in [400, 404, 500]: + with self.subTest(code=code): + self.assertEqual(self.resolver.resolve_error_handler(code), handler) def test_callable_handlers(self): handler = (empty_view, {}) - self.assertEqual(self.callable_resolver.resolve_error_handler(400), handler) - self.assertEqual(self.callable_resolver.resolve_error_handler(404), handler) - self.assertEqual(self.callable_resolver.resolve_error_handler(500), handler) + for code in [400, 404, 500]: + with self.subTest(code=code): + self.assertEqual(self.callable_resolver.resolve_error_handler(code), handler) -@override_settings(ROOT_URLCONF='urlpatterns_reverse.urls_without_full_import') +@override_settings(ROOT_URLCONF='urlpatterns_reverse.urls_without_handlers') class DefaultErrorHandlerTests(SimpleTestCase): def test_default_handler(self): @@ -949,7 +1075,8 @@ def test_default_handler(self): response = self.client.get('/test/') self.assertEqual(response.status_code, 404) - with self.assertRaisesMessage(ValueError, "I don't think I'm getting good"): + msg = "I don't think I'm getting good value for this view" + with self.assertRaisesMessage(ValueError, msg): self.client.get('/bad_view/') @@ -958,39 +1085,41 @@ class NoRootUrlConfTests(SimpleTestCase): """Tests for handler404 and handler500 if ROOT_URLCONF is None""" def test_no_handler_exception(self): - with self.assertRaises(ImproperlyConfigured): + msg = ( + "The included URLconf 'None' does not appear to have any patterns " + "in it. If you see valid patterns in the file then the issue is " + "probably caused by a circular import." + ) + with self.assertRaisesMessage(ImproperlyConfigured, msg): self.client.get('/test/me/') @override_settings(ROOT_URLCONF='urlpatterns_reverse.namespace_urls') class ResolverMatchTests(SimpleTestCase): - @ignore_warnings(category=RemovedInDjango20Warning) def test_urlpattern_resolve(self): - for path, url_name, app_name, namespace, view_name, func, args, kwargs in resolve_test_data: - # Test legacy support for extracting "function, args, kwargs" - match_func, match_args, match_kwargs = resolve(path) - self.assertEqual(match_func, func) - self.assertEqual(match_args, args) - self.assertEqual(match_kwargs, kwargs) - - # Test ResolverMatch capabilities. - match = resolve(path) - self.assertEqual(match.__class__, ResolverMatch) - self.assertEqual(match.url_name, url_name) - self.assertEqual(match.app_name, app_name) - self.assertEqual(match.namespace, namespace) - self.assertEqual(match.view_name, view_name) - self.assertEqual(match.func, func) - self.assertEqual(match.args, args) - self.assertEqual(match.kwargs, kwargs) - - # ... and for legacy purposes: - self.assertEqual(match[0], func) - self.assertEqual(match[1], args) - self.assertEqual(match[2], kwargs) - - @ignore_warnings(category=RemovedInDjango20Warning) + for path_, url_name, app_name, namespace, view_name, func, args, kwargs in resolve_test_data: + with self.subTest(path=path_): + # Legacy support for extracting "function, args, kwargs". + match_func, match_args, match_kwargs = resolve(path_) + self.assertEqual(match_func, func) + self.assertEqual(match_args, args) + self.assertEqual(match_kwargs, kwargs) + # ResolverMatch capabilities. + match = resolve(path_) + self.assertEqual(match.__class__, ResolverMatch) + self.assertEqual(match.url_name, url_name) + self.assertEqual(match.app_name, app_name) + self.assertEqual(match.namespace, namespace) + self.assertEqual(match.view_name, view_name) + self.assertEqual(match.func, func) + self.assertEqual(match.args, args) + self.assertEqual(match.kwargs, kwargs) + # and for legacy purposes: + self.assertEqual(match[0], func) + self.assertEqual(match[1], args) + self.assertEqual(match[2], kwargs) + def test_resolver_match_on_request(self): response = self.client.get('/resolver_match/') resolver_match = response.resolver_match @@ -1000,6 +1129,14 @@ def test_resolver_match_on_request_before_resolution(self): request = HttpRequest() self.assertIsNone(request.resolver_match) + def test_repr(self): + self.assertEqual( + repr(resolve('/no_kwargs/42/37/')), + "ResolverMatch(func=urlpatterns_reverse.views.empty_view, " + "args=('42', '37'), kwargs={}, url_name=no-kwargs, app_names=[], " + "namespaces=[], route=^no_kwargs/([0-9]+)/([0-9]+)/$)", + ) + @override_settings(ROOT_URLCONF='urlpatterns_reverse.erroneous_urls') class ErroneousViewTests(SimpleTestCase): @@ -1007,7 +1144,7 @@ class ErroneousViewTests(SimpleTestCase): def test_noncallable_view(self): # View is not a callable (explicit import; arbitrary Python object) with self.assertRaisesMessage(TypeError, 'view must be a callable'): - url(r'uncallable-object/$', views.uncallable) + path('uncallable-object/', views.uncallable) def test_invalid_regex(self): # Regex contains an error (refs #6170) @@ -1019,57 +1156,79 @@ def test_invalid_regex(self): class ViewLoadingTests(SimpleTestCase): def test_view_loading(self): self.assertEqual(get_callable('urlpatterns_reverse.views.empty_view'), empty_view) - - # passing a callable should return the callable self.assertEqual(get_callable(empty_view), empty_view) - def test_exceptions(self): - # A missing view (identified by an AttributeError) should raise - # ViewDoesNotExist, ... - with self.assertRaisesMessage(ViewDoesNotExist, "View does not exist in"): + def test_view_does_not_exist(self): + msg = "View does not exist in module urlpatterns_reverse.views." + with self.assertRaisesMessage(ViewDoesNotExist, msg): get_callable('urlpatterns_reverse.views.i_should_not_exist') - # ... but if the AttributeError is caused by something else don't - # swallow it. - with self.assertRaises(AttributeError): + + def test_attributeerror_not_hidden(self): + msg = 'I am here to confuse django.urls.get_callable' + with self.assertRaisesMessage(AttributeError, msg): get_callable('urlpatterns_reverse.views_broken.i_am_broken') + def test_non_string_value(self): + msg = "'1' is not a callable or a dot-notation path" + with self.assertRaisesMessage(ViewDoesNotExist, msg): + get_callable(1) + + def test_string_without_dot(self): + msg = "Could not import 'test'. The path must be fully qualified." + with self.assertRaisesMessage(ImportError, msg): + get_callable('test') + + def test_module_does_not_exist(self): + with self.assertRaisesMessage(ImportError, "No module named 'foo'"): + get_callable('foo.bar') + + def test_parent_module_does_not_exist(self): + msg = 'Parent module urlpatterns_reverse.foo does not exist.' + with self.assertRaisesMessage(ViewDoesNotExist, msg): + get_callable('urlpatterns_reverse.foo.bar') + + def test_not_callable(self): + msg = ( + "Could not import 'urlpatterns_reverse.tests.resolve_test_data'. " + "View is not callable." + ) + with self.assertRaisesMessage(ViewDoesNotExist, msg): + get_callable('urlpatterns_reverse.tests.resolve_test_data') + class IncludeTests(SimpleTestCase): url_patterns = [ - url(r'^inner/$', views.empty_view, name='urlobject-view'), - url(r'^inner/(?P[0-9]+)/(?P[0-9]+)/$', views.empty_view, name='urlobject-view'), - url(r'^inner/\+\\\$\*/$', views.empty_view, name='urlobject-special-view'), + path('inner/', views.empty_view, name='urlobject-view'), + re_path(r'^inner/(?P[0-9]+)/(?P[0-9]+)/$', views.empty_view, name='urlobject-view'), + re_path(r'^inner/\+\\\$\*/$', views.empty_view, name='urlobject-special-view'), ] app_urls = URLObject('inc-app') - def test_include_app_name_but_no_namespace(self): - msg = "Must specify a namespace if specifying app_name." - with self.assertRaisesMessage(ValueError, msg): - include(self.url_patterns, app_name='bar') - def test_include_urls(self): self.assertEqual(include(self.url_patterns), (self.url_patterns, None, None)) - @ignore_warnings(category=RemovedInDjango20Warning) def test_include_namespace(self): - # no app_name -> deprecated - self.assertEqual(include(self.url_patterns, 'namespace'), (self.url_patterns, None, 'namespace')) - - @ignore_warnings(category=RemovedInDjango20Warning) - def test_include_namespace_app_name(self): - # app_name argument to include -> deprecated - self.assertEqual( - include(self.url_patterns, 'namespace', 'app_name'), - (self.url_patterns, 'app_name', 'namespace') + msg = ( + 'Specifying a namespace in include() without providing an ' + 'app_name is not supported.' ) + with self.assertRaisesMessage(ImproperlyConfigured, msg): + include(self.url_patterns, 'namespace') + + def test_include_4_tuple(self): + msg = 'Passing a 4-tuple to include() is not supported.' + with self.assertRaisesMessage(ImproperlyConfigured, msg): + include((self.url_patterns, 'app_name', 'namespace', 'blah')) - @ignore_warnings(category=RemovedInDjango20Warning) def test_include_3_tuple(self): - # 3-tuple -> deprecated - self.assertEqual( - include((self.url_patterns, 'app_name', 'namespace')), - (self.url_patterns, 'app_name', 'namespace') - ) + msg = 'Passing a 3-tuple to include() is not supported.' + with self.assertRaisesMessage(ImproperlyConfigured, msg): + include((self.url_patterns, 'app_name', 'namespace')) + + def test_include_3_tuple_namespace(self): + msg = 'Cannot override the namespace for a dynamic module that provides a namespace.' + with self.assertRaisesMessage(ImproperlyConfigured, msg): + include((self.url_patterns, 'app_name', 'namespace'), 'namespace') def test_include_2_tuple(self): self.assertEqual( @@ -1106,8 +1265,8 @@ def test_valid_resolve(self): '/lookbehind+/a-city/', ] for test_url in test_urls: - match = resolve(test_url) - self.assertEqual(match.kwargs, {'city': 'a-city'}) + with self.subTest(url=test_url): + self.assertEqual(resolve(test_url).kwargs, {'city': 'a-city'}) def test_invalid_resolve(self): test_urls = [ @@ -1117,27 +1276,29 @@ def test_invalid_resolve(self): '/lookbehind+/other-city/', ] for test_url in test_urls: - with self.assertRaises(Resolver404): - resolve(test_url) + with self.subTest(url=test_url): + with self.assertRaises(Resolver404): + resolve(test_url) def test_valid_reverse(self): - url = reverse('lookahead-positive', kwargs={'city': 'a-city'}) - self.assertEqual(url, '/lookahead+/a-city/') - url = reverse('lookahead-negative', kwargs={'city': 'a-city'}) - self.assertEqual(url, '/lookahead-/a-city/') - - url = reverse('lookbehind-positive', kwargs={'city': 'a-city'}) - self.assertEqual(url, '/lookbehind+/a-city/') - url = reverse('lookbehind-negative', kwargs={'city': 'a-city'}) - self.assertEqual(url, '/lookbehind-/a-city/') + test_urls = [ + ('lookahead-positive', {'city': 'a-city'}, '/lookahead+/a-city/'), + ('lookahead-negative', {'city': 'a-city'}, '/lookahead-/a-city/'), + ('lookbehind-positive', {'city': 'a-city'}, '/lookbehind+/a-city/'), + ('lookbehind-negative', {'city': 'a-city'}, '/lookbehind-/a-city/'), + ] + for name, kwargs, expected in test_urls: + with self.subTest(name=name, kwargs=kwargs): + self.assertEqual(reverse(name, kwargs=kwargs), expected) def test_invalid_reverse(self): - with self.assertRaises(NoReverseMatch): - reverse('lookahead-positive', kwargs={'city': 'other-city'}) - with self.assertRaises(NoReverseMatch): - reverse('lookahead-negative', kwargs={'city': 'not-a-city'}) - - with self.assertRaises(NoReverseMatch): - reverse('lookbehind-positive', kwargs={'city': 'other-city'}) - with self.assertRaises(NoReverseMatch): - reverse('lookbehind-negative', kwargs={'city': 'not-a-city'}) + test_urls = [ + ('lookahead-positive', {'city': 'other-city'}), + ('lookahead-negative', {'city': 'not-a-city'}), + ('lookbehind-positive', {'city': 'other-city'}), + ('lookbehind-negative', {'city': 'not-a-city'}), + ] + for name, kwargs in test_urls: + with self.subTest(name=name, kwargs=kwargs): + with self.assertRaises(NoReverseMatch): + reverse(name, kwargs=kwargs) diff --git a/tests/urlpatterns_reverse/urlconf_inner.py b/tests/urlpatterns_reverse/urlconf_inner.py index e2c7b7bf802c..6ea4e90f20ea 100644 --- a/tests/urlpatterns_reverse/urlconf_inner.py +++ b/tests/urlpatterns_reverse/urlconf_inner.py @@ -1,6 +1,6 @@ -from django.conf.urls import url from django.http import HttpResponse from django.template import Context, Template +from django.urls import path def inner_view(request): @@ -10,5 +10,5 @@ def inner_view(request): urlpatterns = [ - url(r'^second_test/$', inner_view, name='inner'), + path('second_test/', inner_view, name='inner'), ] diff --git a/tests/urlpatterns_reverse/urlconf_outer.py b/tests/urlpatterns_reverse/urlconf_outer.py index 65cf507aa48b..100b1f52b1b5 100644 --- a/tests/urlpatterns_reverse/urlconf_outer.py +++ b/tests/urlpatterns_reverse/urlconf_outer.py @@ -1,8 +1,8 @@ -from django.conf.urls import include, url +from django.urls import include, path from . import urlconf_inner urlpatterns = [ - url(r'^test/me/$', urlconf_inner.inner_view, name='outer'), - url(r'^inner_urlconf/', include(urlconf_inner.__name__)) + path('test/me/', urlconf_inner.inner_view, name='outer'), + path('inner_urlconf/', include(urlconf_inner.__name__)) ] diff --git a/tests/urlpatterns_reverse/urls.py b/tests/urlpatterns_reverse/urls.py index 731c97146b35..f3c27b8e1377 100644 --- a/tests/urlpatterns_reverse/urls.py +++ b/tests/urlpatterns_reverse/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls import include, url +from django.urls import include, path, re_path from .views import ( absolute_kwargs_view, defaults_view, empty_view, empty_view_nested_partial, @@ -6,74 +6,74 @@ ) other_patterns = [ - url(r'non_path_include/$', empty_view, name='non_path_include'), - url(r'nested_path/$', nested_view), + path('non_path_include/', empty_view, name='non_path_include'), + path('nested_path/', nested_view), ] urlpatterns = [ - url(r'^places/([0-9]+)/$', empty_view, name='places'), - url(r'^places?/$', empty_view, name="places?"), - url(r'^places+/$', empty_view, name="places+"), - url(r'^places*/$', empty_view, name="places*"), - url(r'^(?:places/)?$', empty_view, name="places2?"), - url(r'^(?:places/)+$', empty_view, name="places2+"), - url(r'^(?:places/)*$', empty_view, name="places2*"), - url(r'^places/([0-9]+|[a-z_]+)/', empty_view, name="places3"), - url(r'^places/(?P[0-9]+)/$', empty_view, name="places4"), - url(r'^people/(?P\w+)/$', empty_view, name="people"), - url(r'^people/(?:name/)', empty_view, name="people2"), - url(r'^people/(?:name/(\w+)/)?', empty_view, name="people2a"), - url(r'^people/(?P\w+)-(?P=name)/$', empty_view, name="people_backref"), - url(r'^optional/(?P.*)/(?:.+/)?', empty_view, name="optional"), - url(r'^optional/(?P\d+)/(?:(?P\d+)/)?', absolute_kwargs_view, name="named_optional"), - url(r'^optional/(?P\d+)/(?:(?P\d+)/)?$', absolute_kwargs_view, name="named_optional_terminated"), - url(r'^nested/noncapture/(?:(?P

        \w+))$', empty_view, name='nested-noncapture'), - url(r'^nested/capture/((\w+)/)?$', empty_view, name='nested-capture'), - url(r'^nested/capture/mixed/((?P

        \w+))$', empty_view, name='nested-mixedcapture'), - url(r'^nested/capture/named/(?P(?P\w+)/)?$', empty_view, name='nested-namedcapture'), - url(r'^hardcoded/$', empty_view, name="hardcoded"), - url(r'^hardcoded/doc\.pdf$', empty_view, name="hardcoded2"), - url(r'^people/(?P\w\w)/(?P\w+)/$', empty_view, name="people3"), - url(r'^people/(?P\w\w)/(?P[0-9])/$', empty_view, name="people4"), - url(r'^people/((?P\w\w)/test)?/(\w+)/$', empty_view, name="people6"), - url(r'^character_set/[abcdef0-9]/$', empty_view, name="range"), - url(r'^character_set/[\w]/$', empty_view, name="range2"), - url(r'^price/\$([0-9]+)/$', empty_view, name="price"), - url(r'^price/[$]([0-9]+)/$', empty_view, name="price2"), - url(r'^price/[\$]([0-9]+)/$', empty_view, name="price3"), - url(r'^product/(?P\w+)\+\(\$(?P[0-9]+(\.[0-9]+)?)\)/$', empty_view, name="product"), - url(r'^headlines/(?P[0-9]+)\.(?P[0-9]+)\.(?P[0-9]+)/$', empty_view, name="headlines"), - url(r'^windows_path/(?P[A-Z]):\\(?P.+)/$', empty_view, name="windows"), - url(r'^special_chars/(?P.+)/$', empty_view, name="special"), - url(r'^(?P.+)/[0-9]+/$', empty_view, name="mixed"), - url(r'^repeats/a{1,2}/$', empty_view, name="repeats"), - url(r'^repeats/a{2,4}/$', empty_view, name="repeats2"), - url(r'^repeats/a{2}/$', empty_view, name="repeats3"), - url(r'^test/1/?', empty_view, name="test"), - url(r'^outer/(?P[0-9]+)/', include('urlpatterns_reverse.included_urls')), - url(r'^outer-no-kwargs/([0-9]+)/', include('urlpatterns_reverse.included_no_kwargs_urls')), - url('', include('urlpatterns_reverse.extra_urls')), - url(r'^lookahead-/(?!not-a-city)(?P[^/]+)/$', empty_view, name='lookahead-negative'), - url(r'^lookahead\+/(?=a-city)(?P[^/]+)/$', empty_view, name='lookahead-positive'), - url(r'^lookbehind-/(?P[^/]+)(?[^/]+)(?<=a-city)/$', empty_view, name='lookbehind-positive'), + re_path(r'^places/([0-9]+)/$', empty_view, name='places'), + re_path(r'^places?/$', empty_view, name='places?'), + re_path(r'^places+/$', empty_view, name='places+'), + re_path(r'^places*/$', empty_view, name='places*'), + re_path(r'^(?:places/)?$', empty_view, name='places2?'), + re_path(r'^(?:places/)+$', empty_view, name='places2+'), + re_path(r'^(?:places/)*$', empty_view, name='places2*'), + re_path(r'^places/([0-9]+|[a-z_]+)/', empty_view, name='places3'), + re_path(r'^places/(?P[0-9]+)/$', empty_view, name='places4'), + re_path(r'^people/(?P\w+)/$', empty_view, name='people'), + re_path(r'^people/(?:name/)$', empty_view, name='people2'), + re_path(r'^people/(?:name/(\w+)/)?$', empty_view, name='people2a'), + re_path(r'^people/(?P\w+)-(?P=name)/$', empty_view, name='people_backref'), + re_path(r'^optional/(?P.*)/(?:.+/)?', empty_view, name='optional'), + re_path(r'^optional/(?P\d+)/(?:(?P\d+)/)?', absolute_kwargs_view, name='named_optional'), + re_path(r'^optional/(?P\d+)/(?:(?P\d+)/)?$', absolute_kwargs_view, name='named_optional_terminated'), + re_path(r'^nested/noncapture/(?:(?P

        \w+))$', empty_view, name='nested-noncapture'), + re_path(r'^nested/capture/((\w+)/)?$', empty_view, name='nested-capture'), + re_path(r'^nested/capture/mixed/((?P

        \w+))$', empty_view, name='nested-mixedcapture'), + re_path(r'^nested/capture/named/(?P(?P\w+)/)?$', empty_view, name='nested-namedcapture'), + re_path(r'^hardcoded/$', empty_view, name='hardcoded'), + re_path(r'^hardcoded/doc\.pdf$', empty_view, name='hardcoded2'), + re_path(r'^people/(?P\w\w)/(?P\w+)/$', empty_view, name='people3'), + re_path(r'^people/(?P\w\w)/(?P[0-9])/$', empty_view, name='people4'), + re_path(r'^people/((?P\w\w)/test)?/(\w+)/$', empty_view, name='people6'), + re_path(r'^character_set/[abcdef0-9]/$', empty_view, name='range'), + re_path(r'^character_set/[\w]/$', empty_view, name='range2'), + re_path(r'^price/\$([0-9]+)/$', empty_view, name='price'), + re_path(r'^price/[$]([0-9]+)/$', empty_view, name='price2'), + re_path(r'^price/[\$]([0-9]+)/$', empty_view, name='price3'), + re_path(r'^product/(?P\w+)\+\(\$(?P[0-9]+(\.[0-9]+)?)\)/$', empty_view, name='product'), + re_path(r'^headlines/(?P[0-9]+)\.(?P[0-9]+)\.(?P[0-9]+)/$', empty_view, name='headlines'), + re_path(r'^windows_path/(?P[A-Z]):\\(?P.+)/$', empty_view, name='windows'), + re_path(r'^special_chars/(?P.+)/$', empty_view, name='special'), + re_path(r'^(?P.+)/[0-9]+/$', empty_view, name='mixed'), + re_path(r'^repeats/a{1,2}/$', empty_view, name='repeats'), + re_path(r'^repeats/a{2,4}/$', empty_view, name='repeats2'), + re_path(r'^repeats/a{2}/$', empty_view, name='repeats3'), + re_path(r'^test/1/?', empty_view, name='test'), + re_path(r'^outer/(?P[0-9]+)/', include('urlpatterns_reverse.included_urls')), + re_path(r'^outer-no-kwargs/([0-9]+)/', include('urlpatterns_reverse.included_no_kwargs_urls')), + re_path('', include('urlpatterns_reverse.extra_urls')), + re_path(r'^lookahead-/(?!not-a-city)(?P[^/]+)/$', empty_view, name='lookahead-negative'), + re_path(r'^lookahead\+/(?=a-city)(?P[^/]+)/$', empty_view, name='lookahead-positive'), + re_path(r'^lookbehind-/(?P[^/]+)(?[^/]+)(?<=a-city)/$', empty_view, name='lookbehind-positive'), # Partials should be fine. - url(r'^partial/', empty_view_partial, name="partial"), - url(r'^partial_nested/', empty_view_nested_partial, name="partial_nested"), - url(r'^partial_wrapped/', empty_view_wrapped, name="partial_wrapped"), + path('partial/', empty_view_partial, name='partial'), + path('partial_nested/', empty_view_nested_partial, name='partial_nested'), + path('partial_wrapped/', empty_view_wrapped, name='partial_wrapped'), # This is non-reversible, but we shouldn't blow up when parsing it. - url(r'^(?:foo|bar)(\w+)/$', empty_view, name="disjunction"), + re_path(r'^(?:foo|bar)(\w+)/$', empty_view, name='disjunction'), - url(r'absolute_arg_view/$', absolute_kwargs_view), + path('absolute_arg_view/', absolute_kwargs_view), # Tests for #13154. Mixed syntax to test both ways of defining URLs. - url(r'defaults_view1/(?P[0-9]+)/', defaults_view, {'arg2': 1}, name='defaults'), - url(r'defaults_view2/(?P[0-9]+)/', defaults_view, {'arg2': 2}, 'defaults'), + re_path(r'^defaults_view1/(?P[0-9]+)/$', defaults_view, {'arg2': 1}, name='defaults'), + re_path(r'^defaults_view2/(?P[0-9]+)/$', defaults_view, {'arg2': 2}, 'defaults'), - url('^includes/', include(other_patterns)), + path('includes/', include(other_patterns)), # Security tests - url('(.+)/security/$', empty_view, name='security'), + re_path('(.+)/security/$', empty_view, name='security'), ] diff --git a/tests/urlpatterns_reverse/urls_without_full_import.py b/tests/urlpatterns_reverse/urls_without_full_import.py deleted file mode 100644 index 5bbb0955e6a2..000000000000 --- a/tests/urlpatterns_reverse/urls_without_full_import.py +++ /dev/null @@ -1,11 +0,0 @@ -# A URLs file that doesn't use the default -# from django.conf.urls import * -# import pattern. -from django.conf.urls import url - -from .views import bad_view, empty_view - -urlpatterns = [ - url(r'^test_view/$', empty_view, name="test_view"), - url(r'^bad_view/$', bad_view, name="bad_view"), -] diff --git a/tests/urlpatterns_reverse/urls_without_handlers.py b/tests/urlpatterns_reverse/urls_without_handlers.py new file mode 100644 index 000000000000..65fb054e00ac --- /dev/null +++ b/tests/urlpatterns_reverse/urls_without_handlers.py @@ -0,0 +1,9 @@ +# A URLconf that doesn't define any handlerXXX. +from django.urls import path + +from .views import bad_view, empty_view + +urlpatterns = [ + path('test_view/', empty_view, name="test_view"), + path('bad_view/', bad_view, name="bad_view"), +] diff --git a/tests/urlpatterns_reverse/utils.py b/tests/urlpatterns_reverse/utils.py index 6c3fe8a31dc2..c1f9a5591353 100644 --- a/tests/urlpatterns_reverse/utils.py +++ b/tests/urlpatterns_reverse/utils.py @@ -1,15 +1,13 @@ -from __future__ import unicode_literals - -from django.conf.urls import url +from django.urls import path, re_path from . import views -class URLObject(object): +class URLObject: urlpatterns = [ - url(r'^inner/$', views.empty_view, name='urlobject-view'), - url(r'^inner/(?P[0-9]+)/(?P[0-9]+)/$', views.empty_view, name='urlobject-view'), - url(r'^inner/\+\\\$\*/$', views.empty_view, name='urlobject-special-view'), + path('inner/', views.empty_view, name='urlobject-view'), + re_path(r'^inner/(?P[0-9]+)/(?P[0-9]+)/$', views.empty_view, name='urlobject-view'), + re_path(r'^inner/\+\\\$\*/$', views.empty_view, name='urlobject-special-view'), ] def __init__(self, app_name, namespace=None): @@ -18,7 +16,7 @@ def __init__(self, app_name, namespace=None): @property def urls(self): - return self.urlpatterns, self.app_name, self.namespace + return (self.urlpatterns, self.app_name), self.namespace @property def app_urls(self): diff --git a/tests/urlpatterns_reverse/views.py b/tests/urlpatterns_reverse/views.py index e8765367be70..584c70a6e526 100644 --- a/tests/urlpatterns_reverse/views.py +++ b/tests/urlpatterns_reverse/views.py @@ -35,7 +35,7 @@ def pass_resolver_match_view(request, *args, **kwargs): uncallable = None # neither a callable nor a string -class ViewClass(object): +class ViewClass: def __call__(self, request, *args, **kwargs): return HttpResponse('') diff --git a/tests/user_commands/management/commands/common_args.py b/tests/user_commands/management/commands/common_args.py new file mode 100644 index 000000000000..d7b288a267e4 --- /dev/null +++ b/tests/user_commands/management/commands/common_args.py @@ -0,0 +1,16 @@ +from argparse import ArgumentError + +from django.core.management.base import BaseCommand, CommandError + + +class Command(BaseCommand): + def add_arguments(self, parser): + try: + parser.add_argument('--version', action='version', version='A.B.C') + except ArgumentError: + pass + else: + raise CommandError('--version argument does no yet exist') + + def handle(self, *args, **options): + return 'Detected that --version already exists' diff --git a/tests/user_commands/management/commands/dance.py b/tests/user_commands/management/commands/dance.py index b86ba6c6222e..81d6ec9c260d 100644 --- a/tests/user_commands/management/commands/dance.py +++ b/tests/user_commands/management/commands/dance.py @@ -18,6 +18,6 @@ def handle(self, *args, **options): raise CommandError() if options['verbosity'] > 0: self.stdout.write("I don't feel like dancing %s." % options["style"]) - self.stdout.write(','.join(options.keys())) + self.stdout.write(','.join(options)) if options['integer'] > 0: self.stdout.write("You passed %d as a positional argument." % options['integer']) diff --git a/tests/user_commands/management/commands/hal.py b/tests/user_commands/management/commands/hal.py index 055f441ab4eb..120eec961fc4 100644 --- a/tests/user_commands/management/commands/hal.py +++ b/tests/user_commands/management/commands/hal.py @@ -6,7 +6,7 @@ class Command(BaseCommand): def add_arguments(self, parser): parser.add_argument('args', metavar='app_label', nargs='*', help='Specify the app label(s) to works on.') - parser.add_argument('--empty', action='store_true', dest='empty', default=False, help="Do nothing.") + parser.add_argument('--empty', action='store_true', help="Do nothing.") def handle(self, *app_labels, **options): app_labels = set(app_labels) diff --git a/tests/user_commands/management/commands/leave_locale_alone_false.py b/tests/user_commands/management/commands/leave_locale_alone_false.py deleted file mode 100644 index aa3e20664aa5..000000000000 --- a/tests/user_commands/management/commands/leave_locale_alone_false.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.core.management.base import BaseCommand -from django.utils import translation - - -class Command(BaseCommand): - - leave_locale_alone = False - - def handle(self, *args, **options): - return translation.get_language() diff --git a/tests/user_commands/management/commands/leave_locale_alone_true.py b/tests/user_commands/management/commands/no_translations.py similarity index 62% rename from tests/user_commands/management/commands/leave_locale_alone_true.py rename to tests/user_commands/management/commands/no_translations.py index 3100a2901ba9..2a8af6605be9 100644 --- a/tests/user_commands/management/commands/leave_locale_alone_true.py +++ b/tests/user_commands/management/commands/no_translations.py @@ -1,10 +1,9 @@ -from django.core.management.base import BaseCommand +from django.core.management.base import BaseCommand, no_translations from django.utils import translation class Command(BaseCommand): - leave_locale_alone = True - + @no_translations def handle(self, *args, **options): return translation.get_language() diff --git a/tests/user_commands/management/commands/required_option.py b/tests/user_commands/management/commands/required_option.py new file mode 100644 index 000000000000..3b30ed942e26 --- /dev/null +++ b/tests/user_commands/management/commands/required_option.py @@ -0,0 +1,11 @@ +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + + def add_arguments(self, parser): + parser.add_argument('-n', '--need-me', required=True) + parser.add_argument('-t', '--need-me-too', required=True, dest='needme2') + + def handle(self, *args, **options): + self.stdout.write(','.join(options)) diff --git a/tests/user_commands/management/commands/set_option.py b/tests/user_commands/management/commands/set_option.py new file mode 100644 index 000000000000..a6e3c9bb6a57 --- /dev/null +++ b/tests/user_commands/management/commands/set_option.py @@ -0,0 +1,10 @@ +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + + def add_arguments(self, parser): + parser.add_argument('--set') + + def handle(self, **options): + self.stdout.write('Set %s' % options['set']) diff --git a/tests/user_commands/management/commands/subparser.py b/tests/user_commands/management/commands/subparser.py new file mode 100644 index 000000000000..d3006bd3e81a --- /dev/null +++ b/tests/user_commands/management/commands/subparser.py @@ -0,0 +1,12 @@ +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + + def add_arguments(self, parser): + subparsers = parser.add_subparsers() + parser_foo = subparsers.add_parser('foo') + parser_foo.add_argument('bar', type=int) + + def handle(self, *args, **options): + self.stdout.write(','.join(options)) diff --git a/tests/user_commands/tests.py b/tests/user_commands/tests.py index 4e4f5dc91197..45fe0aaf46bd 100644 --- a/tests/user_commands/tests.py +++ b/tests/user_commands/tests.py @@ -1,17 +1,19 @@ import os +from io import StringIO +from unittest import mock from admin_scripts.tests import AdminScriptTestCase from django.apps import apps from django.core import management from django.core.management import BaseCommand, CommandError, find_commands -from django.core.management.utils import find_command, popen_wrapper +from django.core.management.utils import ( + find_command, get_random_secret_key, popen_wrapper, +) from django.db import connection -from django.test import SimpleTestCase, mock, override_settings +from django.test import SimpleTestCase, override_settings from django.test.utils import captured_stderr, extend_sys_path from django.utils import translation -from django.utils._os import upath -from django.utils.six import StringIO from .management.commands import dance @@ -46,7 +48,7 @@ def test_language_preserved(self): def test_explode(self): """ An unknown command raises CommandError """ - with self.assertRaises(CommandError): + with self.assertRaisesMessage(CommandError, "Unknown command: 'explode'"): management.call_command(('explode',)) def test_system_exit(self): @@ -63,17 +65,16 @@ def test_system_exit(self): dance.Command.requires_system_checks = True self.assertIn("CommandError", stderr.getvalue()) - def test_deactivate_locale_set(self): - # Deactivate translation when set to true + def test_no_translations_deactivate_translations(self): + """ + When the Command handle method is decorated with @no_translations, + translations are deactivated inside the command. + """ + current_locale = translation.get_language() with translation.override('pl'): - result = management.call_command('leave_locale_alone_false', stdout=StringIO()) + result = management.call_command('no_translations', stdout=StringIO()) self.assertIsNone(result) - - def test_configured_locale_preserved(self): - # Leaves locale from settings when set to false - with translation.override('pl'): - result = management.call_command('leave_locale_alone_true', stdout=StringIO()) - self.assertEqual(result, "pl") + self.assertEqual(translation.get_language(), current_locale) def test_find_command_without_PATH(self): """ @@ -92,7 +93,7 @@ def test_discover_commands_in_eggs(self): """ Management commands can also be loaded from Python eggs. """ - egg_dir = '%s/eggs' % os.path.dirname(upath(__file__)) + egg_dir = '%s/eggs' % os.path.dirname(__file__) egg_name = '%s/basic.egg' % egg_dir with extend_sys_path(egg_name): with self.settings(INSTALLED_APPS=['commandegg']): @@ -175,6 +176,58 @@ def test_check_migrations(self): finally: dance.Command.requires_migrations_checks = requires_migrations_checks + def test_call_command_unrecognized_option(self): + msg = ( + 'Unknown option(s) for dance command: unrecognized. Valid options ' + 'are: example, force_color, help, integer, no_color, opt_3, ' + 'option3, pythonpath, settings, skip_checks, stderr, stdout, ' + 'style, traceback, verbosity, version.' + ) + with self.assertRaisesMessage(TypeError, msg): + management.call_command('dance', unrecognized=1) + + msg = ( + 'Unknown option(s) for dance command: unrecognized, unrecognized2. ' + 'Valid options are: example, force_color, help, integer, no_color, ' + 'opt_3, option3, pythonpath, settings, skip_checks, stderr, ' + 'stdout, style, traceback, verbosity, version.' + ) + with self.assertRaisesMessage(TypeError, msg): + management.call_command('dance', unrecognized=1, unrecognized2=1) + + def test_call_command_with_required_parameters_in_options(self): + out = StringIO() + management.call_command('required_option', need_me='foo', needme2='bar', stdout=out) + self.assertIn('need_me', out.getvalue()) + self.assertIn('needme2', out.getvalue()) + + def test_call_command_with_required_parameters_in_mixed_options(self): + out = StringIO() + management.call_command('required_option', '--need-me=foo', needme2='bar', stdout=out) + self.assertIn('need_me', out.getvalue()) + self.assertIn('needme2', out.getvalue()) + + def test_command_add_arguments_after_common_arguments(self): + out = StringIO() + management.call_command('common_args', stdout=out) + self.assertIn('Detected that --version already exists', out.getvalue()) + + def test_subparser(self): + out = StringIO() + management.call_command('subparser', 'foo', 12, stdout=out) + self.assertIn('bar', out.getvalue()) + + def test_subparser_invalid_option(self): + msg = "Error: invalid choice: 'test' (choose from 'foo')" + with self.assertRaisesMessage(CommandError, msg): + management.call_command('subparser', 'test', 12) + + def test_create_parser_kwargs(self): + """BaseCommand.create_parser() passes kwargs to CommandParser.""" + epilog = 'some epilog text' + parser = BaseCommand().create_parser('prog_name', 'subcommand', epilog=epilog) + self.assertEqual(parser.epilog, epilog) + class CommandRunTests(AdminScriptTestCase): """ @@ -192,9 +245,26 @@ def test_script_prefix_set_in_commands(self): self.assertNoOutput(err) self.assertEqual(out.strip(), '/PREFIX/some/url/') + def test_disallowed_abbreviated_options(self): + """ + To avoid conflicts with custom options, commands don't allow + abbreviated forms of the --setting and --pythonpath options. + """ + self.write_settings('settings.py', apps=['user_commands']) + out, err = self.run_manage(['set_option', '--set', 'foo']) + self.assertNoOutput(err) + self.assertEqual(out.strip(), 'Set foo') + class UtilsTests(SimpleTestCase): def test_no_existent_external_program(self): - with self.assertRaises(CommandError): + msg = 'Error executing a_42_command_that_doesnt_exist_42' + with self.assertRaisesMessage(CommandError, msg): popen_wrapper(['a_42_command_that_doesnt_exist_42']) + + def test_get_random_secret_key(self): + key = get_random_secret_key() + self.assertEqual(len(key), 50) + for char in key: + self.assertIn(char, 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') diff --git a/tests/user_commands/urls.py b/tests/user_commands/urls.py index fe20693dce6e..50d7d96d51b7 100644 --- a/tests/user_commands/urls.py +++ b/tests/user_commands/urls.py @@ -1,5 +1,5 @@ -from django.conf.urls import url +from django.urls import path urlpatterns = [ - url(r'^some/url/$', lambda req:req, name='some_url'), + path('some/url/', lambda req:req, name='some_url'), ] diff --git a/tests/utils_tests/models.py b/tests/utils_tests/models.py index 97e9a97ef857..866a37debc44 100644 --- a/tests/utils_tests/models.py +++ b/tests/utils_tests/models.py @@ -4,14 +4,6 @@ class Category(models.Model): name = models.CharField(max_length=100) - def next(self): - return self - - -class Thing(models.Model): - name = models.CharField(max_length=100) - category = models.ForeignKey(Category, models.CASCADE) - class CategoryInfo(models.Model): category = models.OneToOneField(Category, models.CASCADE) diff --git a/tests/utils_tests/test_archive.py b/tests/utils_tests/test_archive.py index b207a1290acf..d58d211ae5f1 100644 --- a/tests/utils_tests/test_archive.py +++ b/tests/utils_tests/test_archive.py @@ -5,13 +5,12 @@ import tempfile import unittest -from django.utils._os import upath from django.utils.archive import Archive, extract -TEST_DIR = os.path.join(os.path.dirname(upath(__file__)), 'archives') +TEST_DIR = os.path.join(os.path.dirname(__file__), 'archives') -class ArchiveTester(object): +class ArchiveTester: archive = None def setUp(self): diff --git a/tests/utils_tests/test_autoreload.py b/tests/utils_tests/test_autoreload.py index 5d42af62c8f4..ec913593280d 100644 --- a/tests/utils_tests/test_autoreload.py +++ b/tests/utils_tests/test_autoreload.py @@ -1,253 +1,706 @@ -import gettext +import contextlib import os +import py_compile import shutil +import sys import tempfile +import threading +import time +import types +import weakref +import zipfile from importlib import import_module +from pathlib import Path +from unittest import mock, skip -from django import conf -from django.contrib import admin -from django.test import SimpleTestCase, mock, override_settings +from django.apps.registry import Apps +from django.test import SimpleTestCase from django.test.utils import extend_sys_path from django.utils import autoreload -from django.utils._os import npath -from django.utils.six.moves import _thread -from django.utils.translation import trans_real +from django.utils.autoreload import WatchmanUnavailable -LOCALE_PATH = os.path.join(os.path.dirname(__file__), 'locale') - -class TestFilenameGenerator(SimpleTestCase): +class TestIterModulesAndFiles(SimpleTestCase): + def import_and_cleanup(self, name): + import_module(name) + self.addCleanup(lambda: sys.path_importer_cache.clear()) + self.addCleanup(lambda: sys.modules.pop(name, None)) def clear_autoreload_caches(self): - autoreload._cached_modules = set() - autoreload._cached_filenames = [] + autoreload.iter_modules_and_files.cache_clear() def assertFileFound(self, filename): + # Some temp directories are symlinks. Python resolves these fully while + # importing. + resolved_filename = filename.resolve() self.clear_autoreload_caches() # Test uncached access - self.assertIn(npath(filename), autoreload.gen_filenames()) + self.assertIn(resolved_filename, list(autoreload.iter_all_python_module_files())) # Test cached access - self.assertIn(npath(filename), autoreload.gen_filenames()) + self.assertIn(resolved_filename, list(autoreload.iter_all_python_module_files())) + self.assertEqual(autoreload.iter_modules_and_files.cache_info().hits, 1) def assertFileNotFound(self, filename): + resolved_filename = filename.resolve() self.clear_autoreload_caches() # Test uncached access - self.assertNotIn(npath(filename), autoreload.gen_filenames()) - # Test cached access - self.assertNotIn(npath(filename), autoreload.gen_filenames()) - - def assertFileFoundOnlyNew(self, filename): - self.clear_autoreload_caches() - # Test uncached access - self.assertIn(npath(filename), autoreload.gen_filenames(only_new=True)) + self.assertNotIn(resolved_filename, list(autoreload.iter_all_python_module_files())) # Test cached access - self.assertNotIn(npath(filename), autoreload.gen_filenames(only_new=True)) - - def test_django_locales(self): - """ - gen_filenames() yields the built-in Django locale files. - """ - django_dir = os.path.join(os.path.dirname(conf.__file__), 'locale') - django_mo = os.path.join(django_dir, 'nl', 'LC_MESSAGES', 'django.mo') - self.assertFileFound(django_mo) - - @override_settings(LOCALE_PATHS=[LOCALE_PATH]) - def test_locale_paths_setting(self): - """ - gen_filenames also yields from LOCALE_PATHS locales. - """ - locale_paths_mo = os.path.join(LOCALE_PATH, 'nl', 'LC_MESSAGES', 'django.mo') - self.assertFileFound(locale_paths_mo) - - @override_settings(INSTALLED_APPS=[]) - def test_project_root_locale(self): - """ - gen_filenames() also yields from the current directory (project root). - """ - old_cwd = os.getcwd() - os.chdir(os.path.dirname(__file__)) - current_dir = os.path.join(os.path.dirname(__file__), 'locale') - current_dir_mo = os.path.join(current_dir, 'nl', 'LC_MESSAGES', 'django.mo') - try: - self.assertFileFound(current_dir_mo) - finally: - os.chdir(old_cwd) - - @override_settings(INSTALLED_APPS=['django.contrib.admin']) - def test_app_locales(self): - """ - gen_filenames() also yields from locale dirs in installed apps. - """ - admin_dir = os.path.join(os.path.dirname(admin.__file__), 'locale') - admin_mo = os.path.join(admin_dir, 'nl', 'LC_MESSAGES', 'django.mo') - self.assertFileFound(admin_mo) - - @override_settings(USE_I18N=False) - def test_no_i18n(self): - """ - If i18n machinery is disabled, there is no need for watching the - locale files. - """ - django_dir = os.path.join(os.path.dirname(conf.__file__), 'locale') - django_mo = os.path.join(django_dir, 'nl', 'LC_MESSAGES', 'django.mo') - self.assertFileNotFound(django_mo) - - def test_paths_are_native_strings(self): - for filename in autoreload.gen_filenames(): - self.assertIsInstance(filename, str) + self.assertNotIn(resolved_filename, list(autoreload.iter_all_python_module_files())) + self.assertEqual(autoreload.iter_modules_and_files.cache_info().hits, 1) - def test_only_new_files(self): - """ - When calling a second time gen_filenames with only_new = True, only - files from newly loaded modules should be given. - """ + def temporary_file(self, filename): dirname = tempfile.mkdtemp() - filename = os.path.join(dirname, 'test_only_new_module.py') self.addCleanup(shutil.rmtree, dirname) - with open(filename, 'w'): - pass - - # Test uncached access - self.clear_autoreload_caches() - filenames = set(autoreload.gen_filenames(only_new=True)) - filenames_reference = set(autoreload.gen_filenames()) - self.assertEqual(filenames, filenames_reference) + return Path(dirname) / filename - # Test cached access: no changes - filenames = set(autoreload.gen_filenames(only_new=True)) - self.assertEqual(filenames, set()) + def test_paths_are_pathlib_instances(self): + for filename in autoreload.iter_all_python_module_files(): + self.assertIsInstance(filename, Path) - # Test cached access: add a module - with extend_sys_path(dirname): - import_module('test_only_new_module') - filenames = set(autoreload.gen_filenames(only_new=True)) - self.assertEqual(filenames, {npath(filename)}) - - def test_deleted_removed(self): + def test_file_added(self): """ - When a file is deleted, gen_filenames() no longer returns it. + When a file is added, it's returned by iter_all_python_module_files(). """ - dirname = tempfile.mkdtemp() - filename = os.path.join(dirname, 'test_deleted_removed_module.py') - self.addCleanup(shutil.rmtree, dirname) - with open(filename, 'w'): - pass + filename = self.temporary_file('test_deleted_removed_module.py') + filename.touch() - with extend_sys_path(dirname): - import_module('test_deleted_removed_module') - self.assertFileFound(filename) + with extend_sys_path(str(filename.parent)): + self.import_and_cleanup('test_deleted_removed_module') - os.unlink(filename) - self.assertFileNotFound(filename) + self.assertFileFound(filename.absolute()) def test_check_errors(self): """ When a file containing an error is imported in a function wrapped by check_errors(), gen_filenames() returns it. """ - dirname = tempfile.mkdtemp() - filename = os.path.join(dirname, 'test_syntax_error.py') - self.addCleanup(shutil.rmtree, dirname) - with open(filename, 'w') as f: - f.write("Ceci n'est pas du Python.") + filename = self.temporary_file('test_syntax_error.py') + filename.write_text("Ceci n'est pas du Python.") - with extend_sys_path(dirname): + with extend_sys_path(str(filename.parent)): with self.assertRaises(SyntaxError): autoreload.check_errors(import_module)('test_syntax_error') self.assertFileFound(filename) - def test_check_errors_only_new(self): - """ - When a file containing an error is imported in a function wrapped by - check_errors(), gen_filenames(only_new=True) returns it. - """ - dirname = tempfile.mkdtemp() - filename = os.path.join(dirname, 'test_syntax_error.py') - self.addCleanup(shutil.rmtree, dirname) - with open(filename, 'w') as f: - f.write("Ceci n'est pas du Python.") - - with extend_sys_path(dirname): - with self.assertRaises(SyntaxError): - autoreload.check_errors(import_module)('test_syntax_error') - self.assertFileFoundOnlyNew(filename) - def test_check_errors_catches_all_exceptions(self): """ Since Python may raise arbitrary exceptions when importing code, check_errors() must catch Exception, not just some subclasses. """ - dirname = tempfile.mkdtemp() - filename = os.path.join(dirname, 'test_exception.py') - self.addCleanup(shutil.rmtree, dirname) - with open(filename, 'w') as f: - f.write("raise Exception") - - with extend_sys_path(dirname): + filename = self.temporary_file('test_exception.py') + filename.write_text('raise Exception') + with extend_sys_path(str(filename.parent)): with self.assertRaises(Exception): autoreload.check_errors(import_module)('test_exception') self.assertFileFound(filename) + def test_zip_reload(self): + """ + Modules imported from zipped files have their archive location included + in the result. + """ + zip_file = self.temporary_file('zip_import.zip') + with zipfile.ZipFile(str(zip_file), 'w', zipfile.ZIP_DEFLATED) as zipf: + zipf.writestr('test_zipped_file.py', '') + + with extend_sys_path(str(zip_file)): + self.import_and_cleanup('test_zipped_file') + self.assertFileFound(zip_file) + + def test_bytecode_conversion_to_source(self): + """.pyc and .pyo files are included in the files list.""" + filename = self.temporary_file('test_compiled.py') + filename.touch() + compiled_file = Path(py_compile.compile(str(filename), str(filename.with_suffix('.pyc')))) + filename.unlink() + with extend_sys_path(str(compiled_file.parent)): + self.import_and_cleanup('test_compiled') + self.assertFileFound(compiled_file) + + def test_weakref_in_sys_module(self): + """iter_all_python_module_file() ignores weakref modules.""" + time_proxy = weakref.proxy(time) + sys.modules['time_proxy'] = time_proxy + self.addCleanup(lambda: sys.modules.pop('time_proxy', None)) + list(autoreload.iter_all_python_module_files()) # No crash. + + def test_module_without_spec(self): + module = types.ModuleType('test_module') + del module.__spec__ + self.assertEqual(autoreload.iter_modules_and_files((module,), frozenset()), frozenset()) + + def test_main_module_is_resolved(self): + main_module = sys.modules['__main__'] + self.assertFileFound(Path(main_module.__file__)) + + def test_main_module_without_file_is_not_resolved(self): + fake_main = types.ModuleType('__main__') + self.assertEqual(autoreload.iter_modules_and_files((fake_main,), frozenset()), frozenset()) + + def test_path_with_embedded_null_bytes(self): + for path in ( + 'embedded_null_byte\x00.py', + 'di\x00rectory/embedded_null_byte.py', + ): + with self.subTest(path=path): + self.assertEqual( + autoreload.iter_modules_and_files((), frozenset([path])), + frozenset(), + ) + -class CleanFilesTests(SimpleTestCase): - TEST_MAP = { - # description: (input_file_list, expected_returned_file_list) - 'falsies': ([None, False], []), - 'pycs': (['myfile.pyc'], ['myfile.py']), - 'pyos': (['myfile.pyo'], ['myfile.py']), - '$py.class': (['myclass$py.class'], ['myclass.py']), - 'combined': ( - [None, 'file1.pyo', 'file2.pyc', 'myclass$py.class'], - ['file1.py', 'file2.py', 'myclass.py'], +class TestCommonRoots(SimpleTestCase): + def test_common_roots(self): + paths = ( + Path('/first/second'), + Path('/first/second/third'), + Path('/first/'), + Path('/root/first/'), ) - } + results = autoreload.common_roots(paths) + self.assertCountEqual(results, [Path('/first/'), Path('/root/first/')]) - def _run_tests(self, mock_files_exist=True): - with mock.patch('django.utils.autoreload.os.path.exists', return_value=mock_files_exist): - for description, values in self.TEST_MAP.items(): - filenames, expected_returned_filenames = values - self.assertEqual( - autoreload.clean_files(filenames), - expected_returned_filenames if mock_files_exist else [], - msg='{} failed for input file list: {}; returned file list: {}'.format( - description, filenames, expected_returned_filenames - ), - ) - def test_files_exist(self): - """ - If the file exists, any compiled files (pyc, pyo, $py.class) are - transformed as their source files. - """ - self._run_tests() +class TestSysPathDirectories(SimpleTestCase): + def setUp(self): + self._directory = tempfile.TemporaryDirectory() + self.directory = Path(self._directory.name).resolve().absolute() + self.file = self.directory / 'test' + self.file.touch() - def test_files_do_not_exist(self): - """ - If the files don't exist, they aren't in the returned file list. - """ - self._run_tests(mock_files_exist=False) + def tearDown(self): + self._directory.cleanup() + + def test_sys_paths_with_directories(self): + with extend_sys_path(str(self.file)): + paths = list(autoreload.sys_path_directories()) + self.assertIn(self.file.parent, paths) + + def test_sys_paths_non_existing(self): + nonexistent_file = Path(self.directory.name) / 'does_not_exist' + with extend_sys_path(str(nonexistent_file)): + paths = list(autoreload.sys_path_directories()) + self.assertNotIn(nonexistent_file, paths) + self.assertNotIn(nonexistent_file.parent, paths) + + def test_sys_paths_absolute(self): + paths = list(autoreload.sys_path_directories()) + self.assertTrue(all(p.is_absolute() for p in paths)) + + def test_sys_paths_directories(self): + with extend_sys_path(str(self.directory)): + paths = list(autoreload.sys_path_directories()) + self.assertIn(self.directory, paths) + + +class GetReloaderTests(SimpleTestCase): + @mock.patch('django.utils.autoreload.WatchmanReloader') + def test_watchman_unavailable(self, mocked_watchman): + mocked_watchman.check_availability.side_effect = WatchmanUnavailable + self.assertIsInstance(autoreload.get_reloader(), autoreload.StatReloader) + + @mock.patch.object(autoreload.WatchmanReloader, 'check_availability') + def test_watchman_available(self, mocked_available): + # If WatchmanUnavailable isn't raised, Watchman will be chosen. + mocked_available.return_value = None + result = autoreload.get_reloader() + self.assertIsInstance(result, autoreload.WatchmanReloader) + + +class RunWithReloaderTests(SimpleTestCase): + @mock.patch.dict(os.environ, {autoreload.DJANGO_AUTORELOAD_ENV: 'true'}) + @mock.patch('django.utils.autoreload.get_reloader') + def test_swallows_keyboard_interrupt(self, mocked_get_reloader): + mocked_get_reloader.side_effect = KeyboardInterrupt() + autoreload.run_with_reloader(lambda: None) # No exception + + @mock.patch.dict(os.environ, {autoreload.DJANGO_AUTORELOAD_ENV: 'false'}) + @mock.patch('django.utils.autoreload.restart_with_reloader') + def test_calls_sys_exit(self, mocked_restart_reloader): + mocked_restart_reloader.return_value = 1 + with self.assertRaises(SystemExit) as exc: + autoreload.run_with_reloader(lambda: None) + self.assertEqual(exc.exception.code, 1) + + @mock.patch.dict(os.environ, {autoreload.DJANGO_AUTORELOAD_ENV: 'true'}) + @mock.patch('django.utils.autoreload.start_django') + @mock.patch('django.utils.autoreload.get_reloader') + def test_calls_start_django(self, mocked_reloader, mocked_start_django): + mocked_reloader.return_value = mock.sentinel.RELOADER + autoreload.run_with_reloader(mock.sentinel.METHOD) + self.assertEqual(mocked_start_django.call_count, 1) + self.assertSequenceEqual( + mocked_start_django.call_args[0], + [mock.sentinel.RELOADER, mock.sentinel.METHOD] + ) + + +class StartDjangoTests(SimpleTestCase): + @mock.patch('django.utils.autoreload.StatReloader') + def test_watchman_becomes_unavailable(self, mocked_stat): + mocked_stat.should_stop.return_value = True + fake_reloader = mock.MagicMock() + fake_reloader.should_stop = False + fake_reloader.run.side_effect = autoreload.WatchmanUnavailable() + + autoreload.start_django(fake_reloader, lambda: None) + self.assertEqual(mocked_stat.call_count, 1) + + @mock.patch('django.utils.autoreload.ensure_echo_on') + def test_echo_on_called(self, mocked_echo): + fake_reloader = mock.MagicMock() + autoreload.start_django(fake_reloader, lambda: None) + self.assertEqual(mocked_echo.call_count, 1) + + @mock.patch('django.utils.autoreload.check_errors') + def test_check_errors_called(self, mocked_check_errors): + fake_method = mock.MagicMock(return_value=None) + fake_reloader = mock.MagicMock() + autoreload.start_django(fake_reloader, fake_method) + self.assertCountEqual(mocked_check_errors.call_args[0], [fake_method]) + + @mock.patch('threading.Thread') + @mock.patch('django.utils.autoreload.check_errors') + def test_starts_thread_with_args(self, mocked_check_errors, mocked_thread): + fake_reloader = mock.MagicMock() + fake_main_func = mock.MagicMock() + fake_thread = mock.MagicMock() + mocked_check_errors.return_value = fake_main_func + mocked_thread.return_value = fake_thread + autoreload.start_django(fake_reloader, fake_main_func, 123, abc=123) + self.assertEqual(mocked_thread.call_count, 1) + self.assertEqual( + mocked_thread.call_args[1], + {'target': fake_main_func, 'args': (123,), 'kwargs': {'abc': 123}, 'name': 'django-main-thread'} + ) + self.assertSequenceEqual(fake_thread.setDaemon.call_args[0], [True]) + self.assertTrue(fake_thread.start.called) + + +class TestCheckErrors(SimpleTestCase): + def test_mutates_error_files(self): + fake_method = mock.MagicMock(side_effect=RuntimeError()) + wrapped = autoreload.check_errors(fake_method) + with mock.patch.object(autoreload, '_error_files') as mocked_error_files: + with self.assertRaises(RuntimeError): + wrapped() + self.assertEqual(mocked_error_files.append.call_count, 1) -class ResetTranslationsTests(SimpleTestCase): +class TestRaiseLastException(SimpleTestCase): + @mock.patch('django.utils.autoreload._exception', None) + def test_no_exception(self): + # Should raise no exception if _exception is None + autoreload.raise_last_exception() + + def test_raises_exception(self): + class MyException(Exception): + pass + + # Create an exception + try: + raise MyException('Test Message') + except MyException: + exc_info = sys.exc_info() + + with mock.patch('django.utils.autoreload._exception', exc_info): + with self.assertRaisesMessage(MyException, 'Test Message'): + autoreload.raise_last_exception() + + def test_raises_custom_exception(self): + class MyException(Exception): + def __init__(self, msg, extra_context): + super().__init__(msg) + self.extra_context = extra_context + # Create an exception. + try: + raise MyException('Test Message', 'extra context') + except MyException: + exc_info = sys.exc_info() + + with mock.patch('django.utils.autoreload._exception', exc_info): + with self.assertRaisesMessage(MyException, 'Test Message'): + autoreload.raise_last_exception() + + def test_raises_exception_with_context(self): + try: + raise Exception(2) + except Exception as e: + try: + raise Exception(1) from e + except Exception: + exc_info = sys.exc_info() + + with mock.patch('django.utils.autoreload._exception', exc_info): + with self.assertRaises(Exception) as cm: + autoreload.raise_last_exception() + self.assertEqual(cm.exception.args[0], 1) + self.assertEqual(cm.exception.__cause__.args[0], 2) + + +class RestartWithReloaderTests(SimpleTestCase): + executable = '/usr/bin/python' + + def patch_autoreload(self, argv): + patch_call = mock.patch('django.utils.autoreload.subprocess.call', return_value=0) + patches = [ + mock.patch('django.utils.autoreload.sys.argv', argv), + mock.patch('django.utils.autoreload.sys.executable', self.executable), + mock.patch('django.utils.autoreload.sys.warnoptions', ['all']), + ] + for p in patches: + p.start() + self.addCleanup(p.stop) + mock_call = patch_call.start() + self.addCleanup(patch_call.stop) + return mock_call + + def test_manage_py(self): + argv = ['./manage.py', 'runserver'] + mock_call = self.patch_autoreload(argv) + autoreload.restart_with_reloader() + self.assertEqual(mock_call.call_count, 1) + self.assertEqual(mock_call.call_args[0][0], [self.executable, '-Wall'] + argv) + + def test_python_m_django(self): + main = '/usr/lib/pythonX.Y/site-packages/django/__main__.py' + argv = [main, 'runserver'] + mock_call = self.patch_autoreload(argv) + with mock.patch('django.__main__.__file__', main): + autoreload.restart_with_reloader() + self.assertEqual(mock_call.call_count, 1) + self.assertEqual(mock_call.call_args[0][0], [self.executable, '-Wall', '-m', 'django'] + argv[1:]) + + +class ReloaderTests(SimpleTestCase): + RELOADER_CLS = None def setUp(self): - self.gettext_translations = gettext._translations.copy() - self.trans_real_translations = trans_real._translations.copy() + self._tempdir = tempfile.TemporaryDirectory() + self.tempdir = Path(self._tempdir.name).resolve().absolute() + self.existing_file = self.ensure_file(self.tempdir / 'test.py') + self.nonexistent_file = (self.tempdir / 'does_not_exist.py').absolute() + self.reloader = self.RELOADER_CLS() def tearDown(self): - gettext._translations = self.gettext_translations - trans_real._translations = self.trans_real_translations - - def test_resets_gettext(self): - gettext._translations = {'foo': 'bar'} - autoreload.reset_translations() - self.assertEqual(gettext._translations, {}) - - def test_resets_trans_real(self): - trans_real._translations = {'foo': 'bar'} - trans_real._default = 1 - trans_real._active = False - autoreload.reset_translations() - self.assertEqual(trans_real._translations, {}) - self.assertIsNone(trans_real._default) - self.assertIsInstance(trans_real._active, _thread._local) + self._tempdir.cleanup() + self.reloader.stop() + + def ensure_file(self, path): + path.parent.mkdir(exist_ok=True, parents=True) + path.touch() + # On Linux and Windows updating the mtime of a file using touch() will set a timestamp + # value that is in the past, as the time value for the last kernel tick is used rather + # than getting the correct absolute time. + # To make testing simpler set the mtime to be the observed time when this function is + # called. + self.set_mtime(path, time.time()) + return path.absolute() + + def set_mtime(self, fp, value): + os.utime(str(fp), (value, value)) + + def increment_mtime(self, fp, by=1): + current_time = time.time() + self.set_mtime(fp, current_time + by) + + @contextlib.contextmanager + def tick_twice(self): + ticker = self.reloader.tick() + next(ticker) + yield + next(ticker) + + +class IntegrationTests: + @mock.patch('django.utils.autoreload.BaseReloader.notify_file_changed') + @mock.patch('django.utils.autoreload.iter_all_python_module_files', return_value=frozenset()) + def test_file(self, mocked_modules, notify_mock): + self.reloader.watch_file(self.existing_file) + with self.tick_twice(): + self.increment_mtime(self.existing_file) + self.assertEqual(notify_mock.call_count, 1) + self.assertCountEqual(notify_mock.call_args[0], [self.existing_file]) + + @mock.patch('django.utils.autoreload.BaseReloader.notify_file_changed') + @mock.patch('django.utils.autoreload.iter_all_python_module_files', return_value=frozenset()) + def test_glob(self, mocked_modules, notify_mock): + non_py_file = self.ensure_file(self.tempdir / 'non_py_file') + self.reloader.watch_dir(self.tempdir, '*.py') + with self.tick_twice(): + self.increment_mtime(non_py_file) + self.increment_mtime(self.existing_file) + self.assertEqual(notify_mock.call_count, 1) + self.assertCountEqual(notify_mock.call_args[0], [self.existing_file]) + + @mock.patch('django.utils.autoreload.BaseReloader.notify_file_changed') + @mock.patch('django.utils.autoreload.iter_all_python_module_files', return_value=frozenset()) + def test_multiple_globs(self, mocked_modules, notify_mock): + self.ensure_file(self.tempdir / 'x.test') + self.reloader.watch_dir(self.tempdir, '*.py') + self.reloader.watch_dir(self.tempdir, '*.test') + with self.tick_twice(): + self.increment_mtime(self.existing_file) + self.assertEqual(notify_mock.call_count, 1) + self.assertCountEqual(notify_mock.call_args[0], [self.existing_file]) + + @mock.patch('django.utils.autoreload.BaseReloader.notify_file_changed') + @mock.patch('django.utils.autoreload.iter_all_python_module_files', return_value=frozenset()) + def test_overlapping_globs(self, mocked_modules, notify_mock): + self.reloader.watch_dir(self.tempdir, '*.py') + self.reloader.watch_dir(self.tempdir, '*.p*') + with self.tick_twice(): + self.increment_mtime(self.existing_file) + self.assertEqual(notify_mock.call_count, 1) + self.assertCountEqual(notify_mock.call_args[0], [self.existing_file]) + + @mock.patch('django.utils.autoreload.BaseReloader.notify_file_changed') + @mock.patch('django.utils.autoreload.iter_all_python_module_files', return_value=frozenset()) + def test_glob_recursive(self, mocked_modules, notify_mock): + non_py_file = self.ensure_file(self.tempdir / 'dir' / 'non_py_file') + py_file = self.ensure_file(self.tempdir / 'dir' / 'file.py') + self.reloader.watch_dir(self.tempdir, '**/*.py') + with self.tick_twice(): + self.increment_mtime(non_py_file) + self.increment_mtime(py_file) + self.assertEqual(notify_mock.call_count, 1) + self.assertCountEqual(notify_mock.call_args[0], [py_file]) + + @mock.patch('django.utils.autoreload.BaseReloader.notify_file_changed') + @mock.patch('django.utils.autoreload.iter_all_python_module_files', return_value=frozenset()) + def test_multiple_recursive_globs(self, mocked_modules, notify_mock): + non_py_file = self.ensure_file(self.tempdir / 'dir' / 'test.txt') + py_file = self.ensure_file(self.tempdir / 'dir' / 'file.py') + self.reloader.watch_dir(self.tempdir, '**/*.txt') + self.reloader.watch_dir(self.tempdir, '**/*.py') + with self.tick_twice(): + self.increment_mtime(non_py_file) + self.increment_mtime(py_file) + self.assertEqual(notify_mock.call_count, 2) + self.assertCountEqual(notify_mock.call_args_list, [mock.call(py_file), mock.call(non_py_file)]) + + @mock.patch('django.utils.autoreload.BaseReloader.notify_file_changed') + @mock.patch('django.utils.autoreload.iter_all_python_module_files', return_value=frozenset()) + def test_nested_glob_recursive(self, mocked_modules, notify_mock): + inner_py_file = self.ensure_file(self.tempdir / 'dir' / 'file.py') + self.reloader.watch_dir(self.tempdir, '**/*.py') + self.reloader.watch_dir(inner_py_file.parent, '**/*.py') + with self.tick_twice(): + self.increment_mtime(inner_py_file) + self.assertEqual(notify_mock.call_count, 1) + self.assertCountEqual(notify_mock.call_args[0], [inner_py_file]) + + @mock.patch('django.utils.autoreload.BaseReloader.notify_file_changed') + @mock.patch('django.utils.autoreload.iter_all_python_module_files', return_value=frozenset()) + def test_overlapping_glob_recursive(self, mocked_modules, notify_mock): + py_file = self.ensure_file(self.tempdir / 'dir' / 'file.py') + self.reloader.watch_dir(self.tempdir, '**/*.p*') + self.reloader.watch_dir(self.tempdir, '**/*.py*') + with self.tick_twice(): + self.increment_mtime(py_file) + self.assertEqual(notify_mock.call_count, 1) + self.assertCountEqual(notify_mock.call_args[0], [py_file]) + + +class BaseReloaderTests(ReloaderTests): + RELOADER_CLS = autoreload.BaseReloader + + def test_watch_without_absolute(self): + with self.assertRaisesMessage(ValueError, 'test.py must be absolute.'): + self.reloader.watch_file('test.py') + + def test_watch_with_single_file(self): + self.reloader.watch_file(self.existing_file) + watched_files = list(self.reloader.watched_files()) + self.assertIn(self.existing_file, watched_files) + + def test_watch_dir_with_unresolvable_path(self): + path = Path('unresolvable_directory') + with mock.patch.object(Path, 'absolute', side_effect=FileNotFoundError): + self.reloader.watch_dir(path, '**/*.mo') + self.assertEqual(list(self.reloader.directory_globs), []) + + def test_watch_with_glob(self): + self.reloader.watch_dir(self.tempdir, '*.py') + watched_files = list(self.reloader.watched_files()) + self.assertIn(self.existing_file, watched_files) + + def test_watch_files_with_recursive_glob(self): + inner_file = self.ensure_file(self.tempdir / 'test' / 'test.py') + self.reloader.watch_dir(self.tempdir, '**/*.py') + watched_files = list(self.reloader.watched_files()) + self.assertIn(self.existing_file, watched_files) + self.assertIn(inner_file, watched_files) + + def test_run_loop_catches_stopiteration(self): + def mocked_tick(): + yield + + with mock.patch.object(self.reloader, 'tick', side_effect=mocked_tick) as tick: + self.reloader.run_loop() + self.assertEqual(tick.call_count, 1) + + def test_run_loop_stop_and_return(self): + def mocked_tick(*args): + yield + self.reloader.stop() + return # Raises StopIteration + + with mock.patch.object(self.reloader, 'tick', side_effect=mocked_tick) as tick: + self.reloader.run_loop() + + self.assertEqual(tick.call_count, 1) + + def test_wait_for_apps_ready_checks_for_exception(self): + app_reg = Apps() + app_reg.ready_event.set() + # thread.is_alive() is False if it's not started. + dead_thread = threading.Thread() + self.assertFalse(self.reloader.wait_for_apps_ready(app_reg, dead_thread)) + + def test_wait_for_apps_ready_without_exception(self): + app_reg = Apps() + app_reg.ready_event.set() + thread = mock.MagicMock() + thread.is_alive.return_value = True + self.assertTrue(self.reloader.wait_for_apps_ready(app_reg, thread)) + + +def skip_unless_watchman_available(): + try: + autoreload.WatchmanReloader.check_availability() + except WatchmanUnavailable as e: + return skip('Watchman unavailable: %s' % e) + return lambda func: func + + +@skip_unless_watchman_available() +class WatchmanReloaderTests(ReloaderTests, IntegrationTests): + RELOADER_CLS = autoreload.WatchmanReloader + + def setUp(self): + super().setUp() + # Shorten the timeout to speed up tests. + self.reloader.client_timeout = 0.1 + + def test_watch_glob_ignores_non_existing_directories_two_levels(self): + with mock.patch.object(self.reloader, '_subscribe') as mocked_subscribe: + self.reloader._watch_glob(self.tempdir / 'does_not_exist' / 'more', ['*']) + self.assertFalse(mocked_subscribe.called) + + def test_watch_glob_uses_existing_parent_directories(self): + with mock.patch.object(self.reloader, '_subscribe') as mocked_subscribe: + self.reloader._watch_glob(self.tempdir / 'does_not_exist', ['*']) + self.assertSequenceEqual( + mocked_subscribe.call_args[0], + [ + self.tempdir, 'glob-parent-does_not_exist:%s' % self.tempdir, + ['anyof', ['match', 'does_not_exist/*', 'wholename']] + ] + ) + + def test_watch_glob_multiple_patterns(self): + with mock.patch.object(self.reloader, '_subscribe') as mocked_subscribe: + self.reloader._watch_glob(self.tempdir, ['*', '*.py']) + self.assertSequenceEqual( + mocked_subscribe.call_args[0], + [ + self.tempdir, 'glob:%s' % self.tempdir, + ['anyof', ['match', '*', 'wholename'], ['match', '*.py', 'wholename']] + ] + ) + + def test_watched_roots_contains_files(self): + paths = self.reloader.watched_roots([self.existing_file]) + self.assertIn(self.existing_file.parent, paths) + + def test_watched_roots_contains_directory_globs(self): + self.reloader.watch_dir(self.tempdir, '*.py') + paths = self.reloader.watched_roots([]) + self.assertIn(self.tempdir, paths) + + def test_watched_roots_contains_sys_path(self): + with extend_sys_path(str(self.tempdir)): + paths = self.reloader.watched_roots([]) + self.assertIn(self.tempdir, paths) + + def test_check_server_status(self): + self.assertTrue(self.reloader.check_server_status()) + + def test_check_server_status_raises_error(self): + with mock.patch.object(self.reloader.client, 'query') as mocked_query: + mocked_query.side_effect = Exception() + with self.assertRaises(autoreload.WatchmanUnavailable): + self.reloader.check_server_status() + + @mock.patch('pywatchman.client') + def test_check_availability(self, mocked_client): + mocked_client().capabilityCheck.side_effect = Exception() + with self.assertRaisesMessage(WatchmanUnavailable, 'Cannot connect to the watchman service'): + self.RELOADER_CLS.check_availability() + + @mock.patch('pywatchman.client') + def test_check_availability_lower_version(self, mocked_client): + mocked_client().capabilityCheck.return_value = {'version': '4.8.10'} + with self.assertRaisesMessage(WatchmanUnavailable, 'Watchman 4.9 or later is required.'): + self.RELOADER_CLS.check_availability() + + def test_pywatchman_not_available(self): + with mock.patch.object(autoreload, 'pywatchman') as mocked: + mocked.__bool__.return_value = False + with self.assertRaisesMessage(WatchmanUnavailable, 'pywatchman not installed.'): + self.RELOADER_CLS.check_availability() + + def test_update_watches_raises_exceptions(self): + class TestException(Exception): + pass + + with mock.patch.object(self.reloader, '_update_watches') as mocked_watches: + with mock.patch.object(self.reloader, 'check_server_status') as mocked_server_status: + mocked_watches.side_effect = TestException() + mocked_server_status.return_value = True + with self.assertRaises(TestException): + self.reloader.update_watches() + self.assertIsInstance(mocked_server_status.call_args[0][0], TestException) + + @mock.patch.dict(os.environ, {'DJANGO_WATCHMAN_TIMEOUT': '10'}) + def test_setting_timeout_from_environment_variable(self): + self.assertEqual(self.RELOADER_CLS().client_timeout, 10) + + +class StatReloaderTests(ReloaderTests, IntegrationTests): + RELOADER_CLS = autoreload.StatReloader + + def setUp(self): + super().setUp() + # Shorten the sleep time to speed up tests. + self.reloader.SLEEP_TIME = 0.01 + + @mock.patch('django.utils.autoreload.StatReloader.notify_file_changed') + def test_tick_does_not_trigger_twice(self, mock_notify_file_changed): + with mock.patch.object(self.reloader, 'watched_files', return_value=[self.existing_file]): + ticker = self.reloader.tick() + next(ticker) + self.increment_mtime(self.existing_file) + next(ticker) + next(ticker) + self.assertEqual(mock_notify_file_changed.call_count, 1) + + def test_snapshot_files_ignores_missing_files(self): + with mock.patch.object(self.reloader, 'watched_files', return_value=[self.nonexistent_file]): + self.assertEqual(dict(self.reloader.snapshot_files()), {}) + + def test_snapshot_files_updates(self): + with mock.patch.object(self.reloader, 'watched_files', return_value=[self.existing_file]): + snapshot1 = dict(self.reloader.snapshot_files()) + self.assertIn(self.existing_file, snapshot1) + self.increment_mtime(self.existing_file) + snapshot2 = dict(self.reloader.snapshot_files()) + self.assertNotEqual(snapshot1[self.existing_file], snapshot2[self.existing_file]) + + def test_snapshot_files_with_duplicates(self): + with mock.patch.object(self.reloader, 'watched_files', return_value=[self.existing_file, self.existing_file]): + snapshot = list(self.reloader.snapshot_files()) + self.assertEqual(len(snapshot), 1) + self.assertEqual(snapshot[0][0], self.existing_file) diff --git a/tests/utils_tests/test_baseconv.py b/tests/utils_tests/test_baseconv.py index 538189bbe62a..b6bfc5ef207a 100644 --- a/tests/utils_tests/test_baseconv.py +++ b/tests/utils_tests/test_baseconv.py @@ -3,13 +3,12 @@ from django.utils.baseconv import ( BaseConverter, base2, base16, base36, base56, base62, base64, ) -from django.utils.six.moves import range class TestBaseConv(TestCase): def test_baseconv(self): - nums = [-10 ** 10, 10 ** 10] + list(range(-100, 100)) + nums = [-10 ** 10, 10 ** 10, *range(-100, 100)] for converter in [base2, base16, base36, base56, base62, base64]: for i in nums: self.assertEqual(i, converter.decode(converter.encode(i))) diff --git a/tests/utils_tests/test_crypto.py b/tests/utils_tests/test_crypto.py index d36f82e5920f..c2045fc4abb8 100644 --- a/tests/utils_tests/test_crypto.py +++ b/tests/utils_tests/test_crypto.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals - -import binascii import hashlib import unittest @@ -134,11 +131,13 @@ class TestUtilsCryptoPBKDF2(unittest.TestCase): def test_public_vectors(self): for vector in self.rfc_vectors: result = pbkdf2(**vector['args']) - self.assertEqual(binascii.hexlify(result).decode('ascii'), - vector['result']) + self.assertEqual(result.hex(), vector['result']) def test_regression_vectors(self): for vector in self.regression_vectors: result = pbkdf2(**vector['args']) - self.assertEqual(binascii.hexlify(result).decode('ascii'), - vector['result']) + self.assertEqual(result.hex(), vector['result']) + + def test_default_hmac_alg(self): + kwargs = {'password': b'password', 'salt': b'salt', 'iterations': 1, 'dklen': 20} + self.assertEqual(pbkdf2(**kwargs), hashlib.pbkdf2_hmac(hash_name=hashlib.sha256().name, **kwargs)) diff --git a/tests/utils_tests/test_datastructures.py b/tests/utils_tests/test_datastructures.py index f917a2ae4e52..0513df69b20b 100644 --- a/tests/utils_tests/test_datastructures.py +++ b/tests/utils_tests/test_datastructures.py @@ -5,10 +5,9 @@ import copy from django.test import SimpleTestCase -from django.utils import six from django.utils.datastructures import ( - DictWrapper, ImmutableList, MultiValueDict, MultiValueDictKeyError, - OrderedSet, + CaseInsensitiveMapping, DictWrapper, ImmutableList, MultiValueDict, + MultiValueDictKeyError, OrderedSet, ) @@ -33,35 +32,28 @@ def test_len(self): class MultiValueDictTests(SimpleTestCase): def test_multivaluedict(self): - d = MultiValueDict({'name': ['Adrian', 'Simon'], - 'position': ['Developer']}) - + d = MultiValueDict({'name': ['Adrian', 'Simon'], 'position': ['Developer']}) self.assertEqual(d['name'], 'Simon') self.assertEqual(d.get('name'), 'Simon') self.assertEqual(d.getlist('name'), ['Adrian', 'Simon']) self.assertEqual( - sorted(six.iteritems(d)), + sorted(d.items()), [('name', 'Simon'), ('position', 'Developer')] ) - self.assertEqual( - sorted(six.iterlists(d)), + sorted(d.lists()), [('name', ['Adrian', 'Simon']), ('position', ['Developer'])] ) - - with self.assertRaisesMessage(MultiValueDictKeyError, 'lastname'): + with self.assertRaises(MultiValueDictKeyError) as cm: d.__getitem__('lastname') - + self.assertEqual(str(cm.exception), "'lastname'") self.assertIsNone(d.get('lastname')) self.assertEqual(d.get('lastname', 'nonexistent'), 'nonexistent') self.assertEqual(d.getlist('lastname'), []) - self.assertEqual(d.getlist('doesnotexist', ['Adrian', 'Simon']), - ['Adrian', 'Simon']) - + self.assertEqual(d.getlist('doesnotexist', ['Adrian', 'Simon']), ['Adrian', 'Simon']) d.setlist('lastname', ['Holovaty', 'Willison']) self.assertEqual(d.getlist('lastname'), ['Holovaty', 'Willison']) - self.assertEqual(sorted(six.itervalues(d)), - ['Developer', 'Simon', 'Willison']) + self.assertEqual(sorted(d.values()), ['Developer', 'Simon', 'Willison']) def test_appendlist(self): d = MultiValueDict() @@ -95,8 +87,8 @@ def test_dict_translation(self): 'pm': ['Rory'], }) d = mvd.dict() - self.assertEqual(sorted(six.iterkeys(d)), sorted(six.iterkeys(mvd))) - for key in six.iterkeys(mvd): + self.assertEqual(sorted(d), sorted(mvd)) + for key in mvd: self.assertEqual(d[key], mvd[key]) self.assertEqual({}, MultiValueDict().dict()) @@ -156,3 +148,82 @@ def f(x): "Normal: %(a)s. Modified: %(xx_a)s" % d, 'Normal: a. Modified: *a' ) + + +class CaseInsensitiveMappingTests(SimpleTestCase): + def setUp(self): + self.dict1 = CaseInsensitiveMapping({ + 'Accept': 'application/json', + 'content-type': 'text/html', + }) + + def test_create_with_invalid_values(self): + msg = 'dictionary update sequence element #1 has length 4; 2 is required' + with self.assertRaisesMessage(ValueError, msg): + CaseInsensitiveMapping([('Key1', 'Val1'), 'Key2']) + + def test_create_with_invalid_key(self): + msg = 'Element key 1 invalid, only strings are allowed' + with self.assertRaisesMessage(ValueError, msg): + CaseInsensitiveMapping([(1, '2')]) + + def test_list(self): + self.assertEqual(sorted(list(self.dict1)), sorted(['Accept', 'content-type'])) + + def test_dict(self): + self.assertEqual(dict(self.dict1), {'Accept': 'application/json', 'content-type': 'text/html'}) + + def test_repr(self): + dict1 = CaseInsensitiveMapping({'Accept': 'application/json'}) + dict2 = CaseInsensitiveMapping({'content-type': 'text/html'}) + self.assertEqual(repr(dict1), repr({'Accept': 'application/json'})) + self.assertEqual(repr(dict2), repr({'content-type': 'text/html'})) + + def test_str(self): + dict1 = CaseInsensitiveMapping({'Accept': 'application/json'}) + dict2 = CaseInsensitiveMapping({'content-type': 'text/html'}) + self.assertEqual(str(dict1), str({'Accept': 'application/json'})) + self.assertEqual(str(dict2), str({'content-type': 'text/html'})) + + def test_equal(self): + self.assertEqual(self.dict1, {'Accept': 'application/json', 'content-type': 'text/html'}) + self.assertNotEqual(self.dict1, {'accept': 'application/jso', 'Content-Type': 'text/html'}) + self.assertNotEqual(self.dict1, 'string') + + def test_items(self): + other = {'Accept': 'application/json', 'content-type': 'text/html'} + self.assertEqual(sorted(self.dict1.items()), sorted(other.items())) + + def test_copy(self): + copy = self.dict1.copy() + self.assertIs(copy, self.dict1) + self.assertEqual(copy, self.dict1) + + def test_getitem(self): + self.assertEqual(self.dict1['Accept'], 'application/json') + self.assertEqual(self.dict1['accept'], 'application/json') + self.assertEqual(self.dict1['aCCept'], 'application/json') + self.assertEqual(self.dict1['content-type'], 'text/html') + self.assertEqual(self.dict1['Content-Type'], 'text/html') + self.assertEqual(self.dict1['Content-type'], 'text/html') + + def test_in(self): + self.assertIn('Accept', self.dict1) + self.assertIn('accept', self.dict1) + self.assertIn('aCCept', self.dict1) + self.assertIn('content-type', self.dict1) + self.assertIn('Content-Type', self.dict1) + + def test_del(self): + self.assertIn('Accept', self.dict1) + msg = "'CaseInsensitiveMapping' object does not support item deletion" + with self.assertRaisesMessage(TypeError, msg): + del self.dict1['Accept'] + self.assertIn('Accept', self.dict1) + + def test_set(self): + self.assertEqual(len(self.dict1), 2) + msg = "'CaseInsensitiveMapping' object does not support item assignment" + with self.assertRaisesMessage(TypeError, msg): + self.dict1['New Key'] = 1 + self.assertEqual(len(self.dict1), 2) diff --git a/tests/utils_tests/test_dateformat.py b/tests/utils_tests/test_dateformat.py index bd99b31a426e..c11dd5591d70 100644 --- a/tests/utils_tests/test_dateformat.py +++ b/tests/utils_tests/test_dateformat.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from datetime import date, datetime from django.test import SimpleTestCase, override_settings diff --git a/tests/utils_tests/test_dateparse.py b/tests/utils_tests/test_dateparse.py index 0bb81ed0b017..8d464278cec8 100644 --- a/tests/utils_tests/test_dateparse.py +++ b/tests/utils_tests/test_dateparse.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import unittest from datetime import date, datetime, time, timedelta @@ -32,35 +30,19 @@ def test_parse_time(self): parse_time('09:15:90') def test_parse_datetime(self): - # Valid inputs - self.assertEqual( - parse_datetime('2012-04-23T09:15:00'), - datetime(2012, 4, 23, 9, 15) - ) - self.assertEqual( - parse_datetime('2012-4-9 4:8:16'), - datetime(2012, 4, 9, 4, 8, 16) - ) - self.assertEqual( - parse_datetime('2012-04-23T09:15:00Z'), - datetime(2012, 4, 23, 9, 15, 0, 0, get_fixed_timezone(0)) - ) - self.assertEqual( - parse_datetime('2012-4-9 4:8:16-0320'), - datetime(2012, 4, 9, 4, 8, 16, 0, get_fixed_timezone(-200)) - ) - self.assertEqual( - parse_datetime('2012-04-23T10:20:30.400+02:30'), - datetime(2012, 4, 23, 10, 20, 30, 400000, get_fixed_timezone(150)) - ) - self.assertEqual( - parse_datetime('2012-04-23T10:20:30.400+02'), - datetime(2012, 4, 23, 10, 20, 30, 400000, get_fixed_timezone(120)) - ) - self.assertEqual( - parse_datetime('2012-04-23T10:20:30.400-02'), - datetime(2012, 4, 23, 10, 20, 30, 400000, get_fixed_timezone(-120)) + valid_inputs = ( + ('2012-04-23T09:15:00', datetime(2012, 4, 23, 9, 15)), + ('2012-4-9 4:8:16', datetime(2012, 4, 9, 4, 8, 16)), + ('2012-04-23T09:15:00Z', datetime(2012, 4, 23, 9, 15, 0, 0, get_fixed_timezone(0))), + ('2012-4-9 4:8:16-0320', datetime(2012, 4, 9, 4, 8, 16, 0, get_fixed_timezone(-200))), + ('2012-04-23T10:20:30.400+02:30', datetime(2012, 4, 23, 10, 20, 30, 400000, get_fixed_timezone(150))), + ('2012-04-23T10:20:30.400+02', datetime(2012, 4, 23, 10, 20, 30, 400000, get_fixed_timezone(120))), + ('2012-04-23T10:20:30.400-02', datetime(2012, 4, 23, 10, 20, 30, 400000, get_fixed_timezone(-120))), ) + for source, expected in valid_inputs: + with self.subTest(source=source): + self.assertEqual(parse_datetime(source), expected) + # Invalid inputs self.assertIsNone(parse_datetime('20120423091500')) with self.assertRaises(ValueError): @@ -80,7 +62,23 @@ def test_parse_python_format(self): timedelta(seconds=30), # seconds ] for delta in timedeltas: - self.assertEqual(parse_duration(format(delta)), delta) + with self.subTest(delta=delta): + self.assertEqual(parse_duration(format(delta)), delta) + + def test_parse_postgresql_format(self): + test_values = ( + ('1 day', timedelta(1)), + ('1 day 0:00:01', timedelta(days=1, seconds=1)), + ('1 day -0:00:01', timedelta(days=1, seconds=-1)), + ('-1 day -0:00:01', timedelta(days=-1, seconds=-1)), + ('-1 day +0:00:01', timedelta(days=-1, seconds=1)), + ('4 days 0:15:30.1', timedelta(days=4, minutes=15, seconds=30, milliseconds=100)), + ('4 days 0:15:30.0001', timedelta(days=4, minutes=15, seconds=30, microseconds=100)), + ('-4 days -15:00:30', timedelta(days=-4, hours=-15, seconds=-30)), + ) + for source, expected in test_values: + with self.subTest(source=source): + self.assertEqual(parse_duration(source), expected) def test_seconds(self): self.assertEqual(parse_duration('30'), timedelta(seconds=30)) @@ -99,27 +97,42 @@ def test_days(self): self.assertEqual(parse_duration('4 10:15:30'), timedelta(days=4, hours=10, minutes=15, seconds=30)) def test_fractions_of_seconds(self): - self.assertEqual(parse_duration('15:30.1'), timedelta(minutes=15, seconds=30, milliseconds=100)) - self.assertEqual(parse_duration('15:30.01'), timedelta(minutes=15, seconds=30, milliseconds=10)) - self.assertEqual(parse_duration('15:30.001'), timedelta(minutes=15, seconds=30, milliseconds=1)) - self.assertEqual(parse_duration('15:30.0001'), timedelta(minutes=15, seconds=30, microseconds=100)) - self.assertEqual(parse_duration('15:30.00001'), timedelta(minutes=15, seconds=30, microseconds=10)) - self.assertEqual(parse_duration('15:30.000001'), timedelta(minutes=15, seconds=30, microseconds=1)) + test_values = ( + ('15:30.1', timedelta(minutes=15, seconds=30, milliseconds=100)), + ('15:30.01', timedelta(minutes=15, seconds=30, milliseconds=10)), + ('15:30.001', timedelta(minutes=15, seconds=30, milliseconds=1)), + ('15:30.0001', timedelta(minutes=15, seconds=30, microseconds=100)), + ('15:30.00001', timedelta(minutes=15, seconds=30, microseconds=10)), + ('15:30.000001', timedelta(minutes=15, seconds=30, microseconds=1)), + ) + for source, expected in test_values: + with self.subTest(source=source): + self.assertEqual(parse_duration(source), expected) def test_negative(self): - self.assertEqual(parse_duration('-4 15:30'), timedelta(days=-4, minutes=15, seconds=30)) - self.assertEqual(parse_duration('-172800'), timedelta(days=-2)) - self.assertEqual(parse_duration('-15:30'), timedelta(minutes=-15, seconds=30)) - self.assertEqual(parse_duration('-1:15:30'), timedelta(hours=-1, minutes=15, seconds=30)) - self.assertEqual(parse_duration('-30.1'), timedelta(seconds=-30, milliseconds=-100)) + test_values = ( + ('-4 15:30', timedelta(days=-4, minutes=15, seconds=30)), + ('-172800', timedelta(days=-2)), + ('-15:30', timedelta(minutes=-15, seconds=30)), + ('-1:15:30', timedelta(hours=-1, minutes=15, seconds=30)), + ('-30.1', timedelta(seconds=-30, milliseconds=-100)), + ) + for source, expected in test_values: + with self.subTest(source=source): + self.assertEqual(parse_duration(source), expected) def test_iso_8601(self): - self.assertIsNone(parse_duration('P4Y')) - self.assertIsNone(parse_duration('P4M')) - self.assertIsNone(parse_duration('P4W')) - self.assertEqual(parse_duration('P4D'), timedelta(days=4)) - self.assertEqual(parse_duration('P0.5D'), timedelta(hours=12)) - self.assertEqual(parse_duration('PT5H'), timedelta(hours=5)) - self.assertEqual(parse_duration('PT5M'), timedelta(minutes=5)) - self.assertEqual(parse_duration('PT5S'), timedelta(seconds=5)) - self.assertEqual(parse_duration('PT0.000005S'), timedelta(microseconds=5)) + test_values = ( + ('P4Y', None), + ('P4M', None), + ('P4W', None), + ('P4D', timedelta(days=4)), + ('P0.5D', timedelta(hours=12)), + ('PT5H', timedelta(hours=5)), + ('PT5M', timedelta(minutes=5)), + ('PT5S', timedelta(seconds=5)), + ('PT0.000005S', timedelta(microseconds=5)), + ) + for source, expected in test_values: + with self.subTest(source=source): + self.assertEqual(parse_duration(source), expected) diff --git a/tests/utils_tests/test_datetime_safe.py b/tests/utils_tests/test_datetime_safe.py index 5fa08bc4f4ff..56eec838fa38 100644 --- a/tests/utils_tests/test_datetime_safe.py +++ b/tests/utils_tests/test_datetime_safe.py @@ -1,17 +1,18 @@ -import unittest from datetime import ( date as original_date, datetime as original_datetime, time as original_time, ) +from django.test import SimpleTestCase from django.utils.datetime_safe import date, datetime, time -class DatetimeTests(unittest.TestCase): +class DatetimeTests(SimpleTestCase): def setUp(self): - self.just_safe = (1900, 1, 1) - self.just_unsafe = (1899, 12, 31, 23, 59, 59) + self.percent_y_safe = (1900, 1, 1) # >= 1900 required on Windows. + self.just_safe = (1000, 1, 1) + self.just_unsafe = (999, 12, 31, 23, 59, 59) self.just_time = (11, 30, 59) self.really_old = (20, 1, 1) self.more_recent = (2006, 1, 1) @@ -34,21 +35,23 @@ def test_compare_datetimes(self): ) def test_safe_strftime(self): - self.assertEqual(date(*self.just_unsafe[:3]).strftime('%Y-%m-%d (weekday %w)'), '1899-12-31 (weekday 0)') - self.assertEqual(date(*self.just_safe).strftime('%Y-%m-%d (weekday %w)'), '1900-01-01 (weekday 1)') + self.assertEqual(date(*self.just_unsafe[:3]).strftime('%Y-%m-%d (weekday %w)'), '0999-12-31 (weekday 2)') + self.assertEqual(date(*self.just_safe).strftime('%Y-%m-%d (weekday %w)'), '1000-01-01 (weekday 3)') self.assertEqual( - datetime(*self.just_unsafe).strftime('%Y-%m-%d %H:%M:%S (weekday %w)'), '1899-12-31 23:59:59 (weekday 0)' + datetime(*self.just_unsafe).strftime('%Y-%m-%d %H:%M:%S (weekday %w)'), '0999-12-31 23:59:59 (weekday 2)' ) self.assertEqual( - datetime(*self.just_safe).strftime('%Y-%m-%d %H:%M:%S (weekday %w)'), '1900-01-01 00:00:00 (weekday 1)' + datetime(*self.just_safe).strftime('%Y-%m-%d %H:%M:%S (weekday %w)'), '1000-01-01 00:00:00 (weekday 3)' ) self.assertEqual(time(*self.just_time).strftime('%H:%M:%S AM'), '11:30:59 AM') # %y will error before this date - self.assertEqual(date(*self.just_safe).strftime('%y'), '00') - self.assertEqual(datetime(*self.just_safe).strftime('%y'), '00') + self.assertEqual(date(*self.percent_y_safe).strftime('%y'), '00') + self.assertEqual(datetime(*self.percent_y_safe).strftime('%y'), '00') + with self.assertRaisesMessage(TypeError, 'strftime of dates before 1000 does not handle %y'): + datetime(*self.just_unsafe).strftime('%y') self.assertEqual(date(1850, 8, 2).strftime("%Y/%m/%d was a %A"), '1850/08/02 was a Friday') diff --git a/tests/utils_tests/test_decorators.py b/tests/utils_tests/test_decorators.py index a4d080e2d7ff..fe5db876ef58 100644 --- a/tests/utils_tests/test_decorators.py +++ b/tests/utils_tests/test_decorators.py @@ -5,7 +5,7 @@ from django.utils.decorators import classproperty, decorator_from_middleware -class ProcessViewMiddleware(object): +class ProcessViewMiddleware: def process_view(self, request, view_func, view_args, view_kwargs): pass @@ -18,7 +18,7 @@ def process_view(request): return HttpResponse() -class ClassProcessView(object): +class ClassProcessView: def __call__(self, request): return HttpResponse() @@ -26,7 +26,7 @@ def __call__(self, request): class_process_view = process_view_dec(ClassProcessView()) -class FullMiddleware(object): +class FullMiddleware: def process_request(self, request): request.process_request_reached = True @@ -112,7 +112,7 @@ def template_response_view(request): class ClassPropertyTest(SimpleTestCase): def test_getter(self): - class Foo(object): + class Foo: foo_attr = 123 def __init__(self): @@ -122,7 +122,7 @@ def __init__(self): def foo(cls): return cls.foo_attr - class Bar(object): + class Bar: bar = classproperty() @bar.getter @@ -135,7 +135,7 @@ def bar(cls): self.assertEqual(Bar().bar, 123) def test_override_getter(self): - class Foo(object): + class Foo: @classproperty def foo(cls): return 123 diff --git a/tests/utils_tests/test_deprecation.py b/tests/utils_tests/test_deprecation.py deleted file mode 100644 index 56ba259a2f75..000000000000 --- a/tests/utils_tests/test_deprecation.py +++ /dev/null @@ -1,31 +0,0 @@ -from django.test import SimpleTestCase -from django.utils.deprecation import CallableFalse, CallableTrue - - -class TestCallableBool(SimpleTestCase): - def test_true(self): - self.assertTrue(CallableTrue) - self.assertEqual(CallableTrue, True) - self.assertFalse(CallableTrue != True) # noqa: E712 - self.assertNotEqual(CallableTrue, False) - - def test_false(self): - self.assertFalse(CallableFalse) - self.assertEqual(CallableFalse, False) - self.assertFalse(CallableFalse != False) # noqa: E712 - self.assertNotEqual(CallableFalse, True) - - def test_or(self): - self.assertIs(CallableTrue | CallableTrue, True) - self.assertIs(CallableTrue | CallableFalse, True) - self.assertIs(CallableFalse | CallableTrue, True) - self.assertIs(CallableFalse | CallableFalse, False) - - self.assertIs(CallableTrue | True, True) - self.assertIs(CallableTrue | False, True) - self.assertIs(CallableFalse | True, True) - self.assertFalse(CallableFalse | False, False) - - def test_set_membership(self): - self.assertIs(CallableTrue in {True}, True) - self.assertIs(CallableFalse not in {True}, True) diff --git a/tests/utils_tests/test_duration.py b/tests/utils_tests/test_duration.py index d0564f396fc9..84a3a0893fd6 100644 --- a/tests/utils_tests/test_duration.py +++ b/tests/utils_tests/test_duration.py @@ -2,7 +2,9 @@ import unittest from django.utils.dateparse import parse_duration -from django.utils.duration import duration_iso_string, duration_string +from django.utils.duration import ( + duration_iso_string, duration_microseconds, duration_string, +) class TestDurationString(unittest.TestCase): @@ -79,3 +81,17 @@ def test_microseconds(self): def test_negative(self): duration = datetime.timedelta(days=-1, hours=1, minutes=3, seconds=5) self.assertEqual(parse_duration(duration_iso_string(duration)).total_seconds(), duration.total_seconds()) + + +class TestDurationMicroseconds(unittest.TestCase): + def test(self): + deltas = [ + datetime.timedelta.max, + datetime.timedelta.min, + datetime.timedelta.resolution, + -datetime.timedelta.resolution, + datetime.timedelta(microseconds=8999999999999999), + ] + for delta in deltas: + with self.subTest(delta=delta): + self.assertEqual(datetime.timedelta(microseconds=duration_microseconds(delta)), delta) diff --git a/tests/utils_tests/test_encoding.py b/tests/utils_tests/test_encoding.py index 688b46194d67..ea7ba5f335a4 100644 --- a/tests/utils_tests/test_encoding.py +++ b/tests/utils_tests/test_encoding.py @@ -1,38 +1,43 @@ -# -*- encoding: utf-8 -*- -from __future__ import unicode_literals - import datetime +import sys import unittest +from unittest import mock +from urllib.parse import quote_plus -from django.utils import six +from django.test import SimpleTestCase from django.utils.encoding import ( - escape_uri_path, filepath_to_uri, force_bytes, force_text, iri_to_uri, - smart_text, uri_to_iri, + DjangoUnicodeDecodeError, escape_uri_path, filepath_to_uri, force_bytes, + force_text, get_system_encoding, iri_to_uri, repercent_broken_unicode, + smart_bytes, smart_text, uri_to_iri, ) from django.utils.functional import SimpleLazyObject -from django.utils.http import urlquote_plus +from django.utils.translation import gettext_lazy -class TestEncodingUtils(unittest.TestCase): +class TestEncodingUtils(SimpleTestCase): def test_force_text_exception(self): """ - Broken __unicode__/__str__ actually raises an error. + Broken __str__ actually raises an error. """ - class MyString(object): + class MyString: def __str__(self): return b'\xc3\xb6\xc3\xa4\xc3\xbc' - __unicode__ = __str__ - - # str(s) raises a TypeError on python 3 if the result is not a text type. - # python 2 fails when it tries converting from str to unicode (via ASCII). - exception = TypeError if six.PY3 else UnicodeError - with self.assertRaises(exception): + # str(s) raises a TypeError if the result is not a text type. + with self.assertRaises(TypeError): force_text(MyString()) def test_force_text_lazy(self): s = SimpleLazyObject(lambda: 'x') - self.assertTrue(issubclass(type(force_text(s)), six.text_type)) + self.assertIs(type(force_text(s)), str) + + def test_force_text_DjangoUnicodeDecodeError(self): + msg = ( + "'utf-8' codec can't decode byte 0xff in position 0: invalid " + "start byte. You passed in b'\\xff' ()" + ) + with self.assertRaisesMessage(DjangoUnicodeDecodeError, msg): + force_text(b'\xff') def test_force_bytes_exception(self): """ @@ -41,61 +46,78 @@ def test_force_bytes_exception(self): """ error_msg = "This is an exception, voilà" exc = ValueError(error_msg) - result = force_bytes(exc) - self.assertEqual(result, error_msg.encode('utf-8')) + self.assertEqual(force_bytes(exc), error_msg.encode()) + self.assertEqual(force_bytes(exc, encoding='ascii', errors='ignore'), b'This is an exception, voil') def test_force_bytes_strings_only(self): today = datetime.date.today() self.assertEqual(force_bytes(today, strings_only=True), today) + def test_force_bytes_encoding(self): + error_msg = 'This is an exception, voilà'.encode() + result = force_bytes(error_msg, encoding='ascii', errors='ignore') + self.assertEqual(result, b'This is an exception, voil') + + def test_force_bytes_memory_view(self): + data = b'abc' + result = force_bytes(memoryview(data)) + # Type check is needed because memoryview(bytes) == bytes. + self.assertIs(type(result), bytes) + self.assertEqual(result, data) + + def test_smart_bytes(self): + class Test: + def __str__(self): + return 'ŠĐĆŽćžšđ' + + lazy_func = gettext_lazy('x') + self.assertIs(smart_bytes(lazy_func), lazy_func) + self.assertEqual(smart_bytes(Test()), b'\xc5\xa0\xc4\x90\xc4\x86\xc5\xbd\xc4\x87\xc5\xbe\xc5\xa1\xc4\x91') + self.assertEqual(smart_bytes(1), b'1') + self.assertEqual(smart_bytes('foo'), b'foo') + def test_smart_text(self): class Test: - if six.PY3: - def __str__(self): - return 'ŠĐĆŽćžšđ' - else: - def __str__(self): - return 'ŠĐĆŽćžšđ'.encode('utf-8') - - class TestU: - if six.PY3: - def __str__(self): - return 'ŠĐĆŽćžšđ' - - def __bytes__(self): - return b'Foo' - else: - def __str__(self): - return b'Foo' - - def __unicode__(self): - return '\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111' + def __str__(self): + return 'ŠĐĆŽćžšđ' + lazy_func = gettext_lazy('x') + self.assertIs(smart_text(lazy_func), lazy_func) self.assertEqual(smart_text(Test()), '\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111') - self.assertEqual(smart_text(TestU()), '\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111') self.assertEqual(smart_text(1), '1') self.assertEqual(smart_text('foo'), 'foo') + def test_get_default_encoding(self): + with mock.patch('locale.getdefaultlocale', side_effect=Exception): + self.assertEqual(get_system_encoding(), 'ascii') + + def test_repercent_broken_unicode_recursion_error(self): + # Prepare a string long enough to force a recursion error if the tested + # function uses recursion. + data = b'\xfc' * sys.getrecursionlimit() + try: + self.assertEqual(repercent_broken_unicode(data), b'%FC' * sys.getrecursionlimit()) + except RecursionError: + self.fail('Unexpected RecursionError raised.') + class TestRFC3987IEncodingUtils(unittest.TestCase): def test_filepath_to_uri(self): + self.assertEqual(filepath_to_uri(None), None) self.assertEqual(filepath_to_uri('upload\\чубака.mp4'), 'upload/%D1%87%D1%83%D0%B1%D0%B0%D0%BA%D0%B0.mp4') - self.assertEqual( - filepath_to_uri('upload\\чубака.mp4'.encode('utf-8')), - 'upload/%D1%87%D1%83%D0%B1%D0%B0%D0%BA%D0%B0.mp4' - ) def test_iri_to_uri(self): cases = [ # Valid UTF-8 sequences are encoded. ('red%09rosé#red', 'red%09ros%C3%A9#red'), ('/blog/for/Jürgen Münster/', '/blog/for/J%C3%BCrgen%20M%C3%BCnster/'), - ('locations/%s' % urlquote_plus('Paris & Orléans'), 'locations/Paris+%26+Orl%C3%A9ans'), + ('locations/%s' % quote_plus('Paris & Orléans'), 'locations/Paris+%26+Orl%C3%A9ans'), # Reserved chars remain unescaped. ('%&', '%&'), ('red&♥ros%#red', 'red&%E2%99%A5ros%#red'), + (gettext_lazy('red&♥ros%#red'), 'red&%E2%99%A5ros%#red'), ] for iri, uri in cases: @@ -106,10 +128,13 @@ def test_iri_to_uri(self): def test_uri_to_iri(self): cases = [ + (None, None), # Valid UTF-8 sequences are decoded. - ('/%E2%99%A5%E2%99%A5/', '/♥♥/'), + ('/%e2%89%Ab%E2%99%a5%E2%89%aB/', '/≫♥≫/'), ('/%E2%99%A5%E2%99%A5/?utf8=%E2%9C%93', '/♥♥/?utf8=✓'), - + ('/%41%5a%6B/', '/AZk/'), + # Reserved and non-URL valid ASCII chars are not decoded. + ('/%25%20%02%41%7b/', '/%25%20%02A%7b/'), # Broken UTF-8 sequences remain escaped. ('/%AAd%AAj%AAa%AAn%AAg%AAo%AA/', '/%AAd%AAj%AAa%AAn%AAg%AAo%AA/'), ('/%E2%99%A5%E2%E2%99%A5/', '/♥%E2♥/'), @@ -126,11 +151,12 @@ def test_uri_to_iri(self): def test_complementarity(self): cases = [ - ('/blog/for/J%C3%BCrgen%20M%C3%BCnster/', '/blog/for/J\xfcrgen M\xfcnster/'), + ('/blog/for/J%C3%BCrgen%20M%C3%BCnster/', '/blog/for/J\xfcrgen%20M\xfcnster/'), ('%&', '%&'), ('red&%E2%99%A5ros%#red', 'red&♥ros%#red'), ('/%E2%99%A5%E2%99%A5/', '/♥♥/'), ('/%E2%99%A5%E2%99%A5/?utf8=%E2%9C%93', '/♥♥/?utf8=✓'), + ('/%25%20%02%7b/', '/%25%20%02%7b/'), ('/%AAd%AAj%AAa%AAn%AAg%AAo%AA/', '/%AAd%AAj%AAa%AAn%AAg%AAo%AA/'), ('/%E2%99%A5%E2%E2%99%A5/', '/♥%E2♥/'), ('/%E2%99%A5%E2%99%E2%99%A5/', '/♥%E2%99♥/'), diff --git a/tests/utils_tests/test_feedgenerator.py b/tests/utils_tests/test_feedgenerator.py index 9b249295c3f1..3847637aba78 100644 --- a/tests/utils_tests/test_feedgenerator.py +++ b/tests/utils_tests/test_feedgenerator.py @@ -1,14 +1,11 @@ -from __future__ import unicode_literals - import datetime -import unittest -from django.test import TestCase +from django.test import SimpleTestCase from django.utils import feedgenerator from django.utils.timezone import get_fixed_timezone, utc -class FeedgeneratorTest(unittest.TestCase): +class FeedgeneratorTests(SimpleTestCase): """ Tests for the low-level syndication feed framework. """ @@ -122,10 +119,17 @@ def test_feed_with_feed_url_gets_rendered_with_atom_link(self): self.assertIn('href="/feed/"', feed_content) self.assertIn('rel="self"', feed_content) + def test_atom_add_item(self): + # Not providing any optional arguments to Atom1Feed.add_item() + feed = feedgenerator.Atom1Feed('title', '/link/', 'descr') + feed.add_item('item_title', 'item_link', 'item_description') + feed.writeString('utf-8') -class FeedgeneratorDBTest(TestCase): + def test_deterministic_attribute_order(self): + feed = feedgenerator.Atom1Feed('title', '/link/', 'desc') + feed_content = feed.writeString('utf-8') + self.assertIn('href="/link/" rel="alternate"', feed_content) - # setting the timezone requires a database query on PostgreSQL. def test_latest_post_date_returns_utc_time(self): for use_tz in (True, False): with self.settings(USE_TZ=use_tz): diff --git a/tests/utils_tests/test_functional.py b/tests/utils_tests/test_functional.py index 7a633620fc30..8d26a906b9df 100644 --- a/tests/utils_tests/test_functional.py +++ b/tests/utils_tests/test_functional.py @@ -1,13 +1,11 @@ -# -*- encoding: utf-8 -*- -from __future__ import unicode_literals - import unittest -from django.utils import six +from django.test import SimpleTestCase from django.utils.functional import cached_property, lazy +from django.utils.version import PY36 -class FunctionalTestCase(unittest.TestCase): +class FunctionalTests(SimpleTestCase): def test_lazy(self): t = lazy(lambda: tuple(range(3)), list, tuple) for a, b in zip(t(), range(3)): @@ -15,7 +13,7 @@ def test_lazy(self): def test_lazy_base_class(self): """lazy also finds base class methods in the proxy object""" - class Base(object): + class Base: def base_method(self): pass @@ -27,7 +25,7 @@ class Klazz(Base): def test_lazy_base_class_override(self): """lazy finds the correct (overridden) method implementation""" - class Base(object): + class Base: def method(self): return 'Base' @@ -40,61 +38,179 @@ def method(self): def test_lazy_object_to_string(self): - class Klazz(object): - if six.PY3: - def __str__(self): - return "Î am ā Ǩlâzz." - - def __bytes__(self): - return b"\xc3\x8e am \xc4\x81 binary \xc7\xa8l\xc3\xa2zz." - else: - def __unicode__(self): - return "Î am ā Ǩlâzz." + class Klazz: + def __str__(self): + return "Î am ā Ǩlâzz." - def __str__(self): - return b"\xc3\x8e am \xc4\x81 binary \xc7\xa8l\xc3\xa2zz." + def __bytes__(self): + return b"\xc3\x8e am \xc4\x81 binary \xc7\xa8l\xc3\xa2zz." t = lazy(lambda: Klazz(), Klazz)() - self.assertEqual(six.text_type(t), "Î am ā Ǩlâzz.") - self.assertEqual(six.binary_type(t), b"\xc3\x8e am \xc4\x81 binary \xc7\xa8l\xc3\xa2zz.") + self.assertEqual(str(t), "Î am ā Ǩlâzz.") + self.assertEqual(bytes(t), b"\xc3\x8e am \xc4\x81 binary \xc7\xa8l\xc3\xa2zz.") - def test_cached_property(self): - """ - cached_property caches its value and that it behaves like a property - """ - class A(object): + def assertCachedPropertyWorks(self, attr, Class): + with self.subTest(attr=attr): + def get(source): + return getattr(source, attr) + obj = Class() + + class SubClass(Class): + pass + + subobj = SubClass() + # Docstring is preserved. + self.assertEqual(get(Class).__doc__, 'Here is the docstring...') + self.assertEqual(get(SubClass).__doc__, 'Here is the docstring...') + # It's cached. + self.assertEqual(get(obj), get(obj)) + self.assertEqual(get(subobj), get(subobj)) + # The correct value is returned. + self.assertEqual(get(obj)[0], 1) + self.assertEqual(get(subobj)[0], 1) + # State isn't shared between instances. + obj2 = Class() + subobj2 = SubClass() + self.assertNotEqual(get(obj), get(obj2)) + self.assertNotEqual(get(subobj), get(subobj2)) + # It behaves like a property when there's no instance. + self.assertIsInstance(get(Class), cached_property) + self.assertIsInstance(get(SubClass), cached_property) + # 'other_value' doesn't become a property. + self.assertTrue(callable(obj.other_value)) + self.assertTrue(callable(subobj.other_value)) + + def test_cached_property(self): + """cached_property caches its value and behaves like a property.""" + class Class: @cached_property def value(self): """Here is the docstring...""" return 1, object() + @cached_property + def __foo__(self): + """Here is the docstring...""" + return 1, object() + def other_value(self): - return 1 + """Here is the docstring...""" + return 1, object() other = cached_property(other_value, name='other') - # docstring should be preserved - self.assertEqual(A.value.__doc__, "Here is the docstring...") + attrs = ['value', 'other', '__foo__'] + for attr in attrs: + self.assertCachedPropertyWorks(attr, Class) - a = A() + @unittest.skipUnless(PY36, '__set_name__ is new in Python 3.6') + def test_cached_property_auto_name(self): + """ + cached_property caches its value and behaves like a property + on mangled methods or when the name kwarg isn't set. + """ + class Class: + @cached_property + def __value(self): + """Here is the docstring...""" + return 1, object() + + def other_value(self): + """Here is the docstring...""" + return 1, object() + + other = cached_property(other_value) + other2 = cached_property(other_value, name='different_name') + + attrs = ['_Class__value', 'other'] + for attr in attrs: + self.assertCachedPropertyWorks(attr, Class) + + # An explicit name is ignored. + obj = Class() + obj.other2 + self.assertFalse(hasattr(obj, 'different_name')) + + @unittest.skipUnless(PY36, '__set_name__ is new in Python 3.6') + def test_cached_property_reuse_different_names(self): + """Disallow this case because the decorated function wouldn't be cached.""" + with self.assertRaises(RuntimeError) as ctx: + class ReusedCachedProperty: + @cached_property + def a(self): + pass + + b = a + + self.assertEqual( + str(ctx.exception.__context__), + str(TypeError( + "Cannot assign the same cached_property to two different " + "names ('a' and 'b')." + )) + ) + + @unittest.skipUnless(PY36, '__set_name__ is new in Python 3.6') + def test_cached_property_reuse_same_name(self): + """ + Reusing a cached_property on different classes under the same name is + allowed. + """ + counter = 0 + + @cached_property + def _cp(_self): + nonlocal counter + counter += 1 + return counter - # check that it is cached - self.assertEqual(a.value, a.value) + class A: + cp = _cp - # check that it returns the right thing - self.assertEqual(a.value[0], 1) + class B: + cp = _cp - # check that state isn't shared between instances - a2 = A() - self.assertNotEqual(a.value, a2.value) + a = A() + b = B() + self.assertEqual(a.cp, 1) + self.assertEqual(b.cp, 2) + self.assertEqual(a.cp, 1) - # check that it behaves like a property when there's no instance - self.assertIsInstance(A.value, cached_property) + @unittest.skipUnless(PY36, '__set_name__ is new in Python 3.6') + def test_cached_property_set_name_not_called(self): + cp = cached_property(lambda s: None) + + class Foo: + pass + + Foo.cp = cp + msg = 'Cannot use cached_property instance without calling __set_name__() on it.' + with self.assertRaisesMessage(TypeError, msg): + Foo().cp + + @unittest.skipIf(PY36, '__set_name__ is new in Python 3.6') + def test_cached_property_mangled_error(self): + msg = ( + 'cached_property does not work with mangled methods on ' + 'Python < 3.6 without the appropriate `name` argument.' + ) + with self.assertRaisesMessage(ValueError, msg): + @cached_property + def __value(self): + pass + with self.assertRaisesMessage(ValueError, msg): + def func(self): + pass + cached_property(func, name='__value') - # check that overriding name works - self.assertEqual(a.other, 1) - self.assertTrue(callable(a.other_value)) + @unittest.skipIf(PY36, '__set_name__ is new in Python 3.6') + def test_cached_property_name_validation(self): + msg = "%s can't be used as the name of a cached_property." + with self.assertRaisesMessage(ValueError, msg % "''"): + cached_property(lambda x: None) + with self.assertRaisesMessage(ValueError, msg % 42): + cached_property(str, name=42) def test_lazy_equality(self): """ @@ -109,7 +225,7 @@ def test_lazy_equality(self): def test_lazy_repr_text(self): original_object = 'Lazy translation text' - lazy_obj = lazy(lambda: original_object, six.text_type) + lazy_obj = lazy(lambda: original_object, str) self.assertEqual(repr(original_object), repr(lazy_obj())) def test_lazy_repr_int(self): diff --git a/tests/utils_tests/test_glob.py b/tests/utils_tests/test_glob.py deleted file mode 100644 index 7e72815ef851..000000000000 --- a/tests/utils_tests/test_glob.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import unicode_literals - -from django.test import SimpleTestCase -from django.utils.glob import glob_escape - - -class TestUtilsGlob(SimpleTestCase): - def test_glob_escape(self): - filename = '/my/file?/name[with special chars*' - expected = '/my/file[?]/name[[]with special chars[*]' - filename_b = b'/my/file?/name[with special chars*' - expected_b = b'/my/file[?]/name[[]with special chars[*]' - - self.assertEqual(glob_escape(filename), expected) - self.assertEqual(glob_escape(filename_b), expected_b) diff --git a/tests/utils_tests/test_hashable.py b/tests/utils_tests/test_hashable.py new file mode 100644 index 000000000000..b4db3ef7d702 --- /dev/null +++ b/tests/utils_tests/test_hashable.py @@ -0,0 +1,35 @@ +from django.test import SimpleTestCase +from django.utils.hashable import make_hashable + + +class TestHashable(SimpleTestCase): + def test_equal(self): + tests = ( + ([], ()), + (['a', 1], ('a', 1)), + ({}, ()), + ({'a'}, ('a',)), + (frozenset({'a'}), {'a'}), + ({'a': 1}, (('a', 1),)), + (('a', ['b', 1]), ('a', ('b', 1))), + (('a', {'b': 1}), ('a', (('b', 1),))), + ) + for value, expected in tests: + with self.subTest(value=value): + self.assertEqual(make_hashable(value), expected) + + def test_count_equal(self): + tests = ( + ({'a': 1, 'b': ['a', 1]}, (('a', 1), ('b', ('a', 1)))), + ({'a': 1, 'b': ('a', [1, 2])}, (('a', 1), ('b', ('a', (1, 2))))), + ) + for value, expected in tests: + with self.subTest(value=value): + self.assertCountEqual(make_hashable(value), expected) + + def test_unhashable(self): + class Unhashable: + __hash__ = None + + with self.assertRaisesMessage(TypeError, "unhashable type: 'Unhashable'"): + make_hashable(Unhashable()) diff --git a/tests/utils_tests/test_html.py b/tests/utils_tests/test_html.py index 39d590032fb1..5cc2d9b95d63 100644 --- a/tests/utils_tests/test_html.py +++ b/tests/utils_tests/test_html.py @@ -1,14 +1,13 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import os from datetime import datetime from django.test import SimpleTestCase -from django.utils import html, safestring, six -from django.utils._os import upath -from django.utils.encoding import force_text from django.utils.functional import lazystr +from django.utils.html import ( + conditional_escape, escape, escapejs, format_html, html_safe, json_script, + linebreaks, smart_urlquote, strip_spaces_between_tags, strip_tags, urlize, +) +from django.utils.safestring import mark_safe class TestUtilsHtml(SimpleTestCase): @@ -23,7 +22,6 @@ def check_output(self, function, value, output=None): self.assertEqual(function(value), output) def test_escape(self): - f = html.escape items = ( ('&', '&'), ('<', '<'), @@ -34,39 +32,41 @@ def test_escape(self): # Substitution patterns for testing the above items. patterns = ("%s", "asdf%sfdsa", "%s1", "1%sb") for value, output in items: - for pattern in patterns: - self.check_output(f, pattern % value, pattern % output) - self.check_output(f, lazystr(pattern % value), pattern % output) - # Check repeated values. - self.check_output(f, value * 2, output * 2) + with self.subTest(value=value, output=output): + for pattern in patterns: + with self.subTest(value=value, output=output, pattern=pattern): + self.check_output(escape, pattern % value, pattern % output) + self.check_output(escape, lazystr(pattern % value), pattern % output) + # Check repeated values. + self.check_output(escape, value * 2, output * 2) # Verify it doesn't double replace &. - self.check_output(f, '<&', '<&') + self.check_output(escape, '<&', '<&') def test_format_html(self): self.assertEqual( - html.format_html("{} {} {third} {fourth}", - "< Dangerous >", - html.mark_safe("safe"), - third="< dangerous again", - fourth=html.mark_safe("safe again") - ), + format_html( + "{} {} {third} {fourth}", + "< Dangerous >", + mark_safe("safe"), + third="< dangerous again", + fourth=mark_safe("safe again"), + ), "< Dangerous > safe < dangerous again safe again" ) def test_linebreaks(self): - f = html.linebreaks items = ( ("para1\n\npara2\r\rpara3", "

        para1

        \n\n

        para2

        \n\n

        para3

        "), - ("para1\nsub1\rsub2\n\npara2", "

        para1
        sub1
        sub2

        \n\n

        para2

        "), - ("para1\r\n\r\npara2\rsub1\r\rpara4", "

        para1

        \n\n

        para2
        sub1

        \n\n

        para4

        "), + ("para1\nsub1\rsub2\n\npara2", "

        para1
        sub1
        sub2

        \n\n

        para2

        "), + ("para1\r\n\r\npara2\rsub1\r\rpara4", "

        para1

        \n\n

        para2
        sub1

        \n\n

        para4

        "), ("para1\tmore\n\npara2", "

        para1\tmore

        \n\n

        para2

        "), ) for value, output in items: - self.check_output(f, value, output) - self.check_output(f, lazystr(value), output) + with self.subTest(value=value, output=output): + self.check_output(linebreaks, value, output) + self.check_output(linebreaks, lazystr(value), output) def test_strip_tags(self): - f = html.strip_tags items = ( ('

        See: 'é is an apostrophe followed by e acute

        ', 'See: 'é is an apostrophe followed by e acute'), @@ -84,40 +84,40 @@ def test_strip_tags(self): ('de

        f', 'def'), ('foobar', 'foobar'), # caused infinite loop on Pythons not patched with - # http://bugs.python.org/issue20288 + # https://bugs.python.org/issue20288 ('&gotcha&#;<>', '&gotcha&#;<>'), + ('ript>test</script>', 'ript>test'), + ('&h', 'alert()h'), + ('>br>br>br>X', 'XX'), ) for value, output in items: - self.check_output(f, value, output) - self.check_output(f, lazystr(value), output) - - # Some convoluted syntax for which parsing may differ between python versions - output = html.strip_tags('ript>test</script>') - self.assertNotIn('&h') - self.assertNotIn('')), + # "<", ">" and "&" are quoted inside JSON objects + ( + {'a': ''}, + '' + ), + # Lazy strings are quoted + (lazystr('&<>'), ''), + ( + {'a': lazystr('')}, + '' + ), + ) + for arg, expected in tests: + with self.subTest(arg=arg): + self.assertEqual(json_script(arg, 'test_id'), expected) def test_smart_urlquote(self): - quote = html.smart_urlquote + items = ( + ('http://öäü.com/', 'http://xn--4ca9at.com/'), + ('http://öäü.com/öäü/', 'http://xn--4ca9at.com/%C3%B6%C3%A4%C3%BC/'), + # Everything unsafe is quoted, !*'();:@&=+$,/?#[]~ is considered + # safe as per RFC. + ('http://example.com/path/öäü/', 'http://example.com/path/%C3%B6%C3%A4%C3%BC/'), + ('http://example.com/%C3%B6/ä/', 'http://example.com/%C3%B6/%C3%A4/'), + ('http://example.com/?x=1&y=2+3&z=', 'http://example.com/?x=1&y=2+3&z='), + ('http://example.com/?x=<>"\'', 'http://example.com/?x=%3C%3E%22%27'), + ('http://example.com/?q=http://example.com/?x=1%26q=django', + 'http://example.com/?q=http%3A%2F%2Fexample.com%2F%3Fx%3D1%26q%3Ddjango'), + ('http://example.com/?q=http%3A%2F%2Fexample.com%2F%3Fx%3D1%26q%3Ddjango', + 'http://example.com/?q=http%3A%2F%2Fexample.com%2F%3Fx%3D1%26q%3Ddjango'), + ('http://.www.f oo.bar/', 'http://.www.f%20oo.bar/'), + ) # IDNs are properly quoted - self.assertEqual(quote('http://öäü.com/'), 'http://xn--4ca9at.com/') - self.assertEqual(quote('http://öäü.com/öäü/'), 'http://xn--4ca9at.com/%C3%B6%C3%A4%C3%BC/') - # Everything unsafe is quoted, !*'();:@&=+$,/?#[]~ is considered safe as per RFC - self.assertEqual(quote('http://example.com/path/öäü/'), 'http://example.com/path/%C3%B6%C3%A4%C3%BC/') - self.assertEqual(quote('http://example.com/%C3%B6/ä/'), 'http://example.com/%C3%B6/%C3%A4/') - self.assertEqual(quote('http://example.com/?x=1&y=2+3&z='), 'http://example.com/?x=1&y=2+3&z=') - self.assertEqual(quote('http://example.com/?x=<>"\''), 'http://example.com/?x=%3C%3E%22%27') - self.assertEqual(quote('http://example.com/?q=http://example.com/?x=1%26q=django'), - 'http://example.com/?q=http%3A%2F%2Fexample.com%2F%3Fx%3D1%26q%3Ddjango') - self.assertEqual(quote('http://example.com/?q=http%3A%2F%2Fexample.com%2F%3Fx%3D1%26q%3Ddjango'), - 'http://example.com/?q=http%3A%2F%2Fexample.com%2F%3Fx%3D1%26q%3Ddjango') + for value, output in items: + with self.subTest(value=value, output=output): + self.assertEqual(smart_urlquote(value), output) def test_conditional_escape(self): s = '

        interop

        ' - self.assertEqual(html.conditional_escape(s), - '<h1>interop</h1>') - self.assertEqual(html.conditional_escape(safestring.mark_safe(s)), s) + self.assertEqual(conditional_escape(s), '<h1>interop</h1>') + self.assertEqual(conditional_escape(mark_safe(s)), s) + self.assertEqual(conditional_escape(lazystr(mark_safe(s))), s) def test_html_safe(self): - @html.html_safe - class HtmlClass(object): - if six.PY2: - def __unicode__(self): - return "

        I'm a html class!

        " - else: - def __str__(self): - return "

        I'm a html class!

        " + @html_safe + class HtmlClass: + def __str__(self): + return "

        I'm a html class!

        " html_obj = HtmlClass() self.assertTrue(hasattr(HtmlClass, '__html__')) self.assertTrue(hasattr(html_obj, '__html__')) - self.assertEqual(force_text(html_obj), html_obj.__html__()) + self.assertEqual(str(html_obj), html_obj.__html__()) def test_html_safe_subclass(self): - if six.PY2: - class BaseClass(object): - def __html__(self): - # defines __html__ on its own - return 'some html content' - - def __unicode__(self): - return 'some non html content' - - @html.html_safe - class Subclass(BaseClass): - def __unicode__(self): - # overrides __unicode__ and is marked as html_safe - return 'some html safe content' - else: - class BaseClass(object): - def __html__(self): - # defines __html__ on its own - return 'some html content' + class BaseClass: + def __html__(self): + # defines __html__ on its own + return 'some html content' - def __str__(self): - return 'some non html content' + def __str__(self): + return 'some non html content' - @html.html_safe - class Subclass(BaseClass): - def __str__(self): - # overrides __str__ and is marked as html_safe - return 'some html safe content' + @html_safe + class Subclass(BaseClass): + def __str__(self): + # overrides __str__ and is marked as html_safe + return 'some html safe content' subclass_obj = Subclass() - self.assertEqual(force_text(subclass_obj), subclass_obj.__html__()) + self.assertEqual(str(subclass_obj), subclass_obj.__html__()) def test_html_safe_defines_html_error(self): msg = "can't apply @html_safe to HtmlClass because it defines __html__()." with self.assertRaisesMessage(ValueError, msg): - @html.html_safe - class HtmlClass(object): + @html_safe + class HtmlClass: def __html__(self): return "

        I'm a html class!

        " def test_html_safe_doesnt_define_str(self): - method_name = '__unicode__()' if six.PY2 else '__str__()' - msg = "can't apply @html_safe to HtmlClass because it doesn't define %s." % method_name + msg = "can't apply @html_safe to HtmlClass because it doesn't define __str__()." with self.assertRaisesMessage(ValueError, msg): - @html.html_safe - class HtmlClass(object): + @html_safe + class HtmlClass: pass + + def test_urlize(self): + tests = ( + ( + 'Search for google.com/?q=! and see.', + 'Search for google.com/?q=! and see.' + ), + ( + lazystr('Search for google.com/?q=!'), + 'Search for google.com/?q=!' + ), + ('foo@example.com', 'foo@example.com'), + ) + for value, output in tests: + with self.subTest(value=value): + self.assertEqual(urlize(value), output) + + def test_urlize_unchanged_inputs(self): + tests = ( + ('a' + '@a' * 50000) + 'a', # simple_email_re catastrophic test + ('a' + '.' * 1000000) + 'a', # trailing_punctuation catastrophic test + 'foo@', + '@foo.com', + 'foo@.example.com', + 'foo@localhost', + 'foo@localhost.', + ) + for value in tests: + with self.subTest(value=value): + self.assertEqual(urlize(value), value) diff --git a/tests/utils_tests/test_http.py b/tests/utils_tests/test_http.py index e90d5e397d82..1f1cc8cfe3af 100644 --- a/tests/utils_tests/test_http.py +++ b/tests/utils_tests/test_http.py @@ -1,85 +1,126 @@ -# -*- encoding: utf-8 -*- -from __future__ import unicode_literals - -import sys import unittest from datetime import datetime -from django.test import ignore_warnings -from django.utils import http, six +from django.test import SimpleTestCase, ignore_warnings from django.utils.datastructures import MultiValueDict -from django.utils.deprecation import RemovedInDjango21Warning +from django.utils.deprecation import RemovedInDjango30Warning +from django.utils.http import ( + base36_to_int, cookie_date, escape_leading_slashes, http_date, + int_to_base36, is_safe_url, is_same_domain, parse_etags, parse_http_date, + quote_etag, urlencode, urlquote, urlquote_plus, urlsafe_base64_decode, + urlsafe_base64_encode, urlunquote, urlunquote_plus, +) -class TestUtilsHttp(unittest.TestCase): +class URLEncodeTests(SimpleTestCase): + cannot_encode_none_msg = ( + 'Cannot encode None in a query string. Did you mean to pass an ' + 'empty string or omit the value?' + ) - def test_urlencode(self): - # 2-tuples (the norm) - result = http.urlencode((('a', 1), ('b', 2), ('c', 3))) - self.assertEqual(result, 'a=1&b=2&c=3') + def test_tuples(self): + self.assertEqual(urlencode((('a', 1), ('b', 2), ('c', 3))), 'a=1&b=2&c=3') - # A dictionary - result = http.urlencode({'a': 1, 'b': 2, 'c': 3}) - acceptable_results = [ - # Need to allow all of these as dictionaries have to be treated as - # unordered + def test_dict(self): + result = urlencode({'a': 1, 'b': 2, 'c': 3}) + # Dictionaries are treated as unordered. + self.assertIn(result, [ 'a=1&b=2&c=3', 'a=1&c=3&b=2', 'b=2&a=1&c=3', 'b=2&c=3&a=1', 'c=3&a=1&b=2', - 'c=3&b=2&a=1' - ] - self.assertIn(result, acceptable_results) - result = http.urlencode({'a': [1, 2]}, doseq=False) - self.assertEqual(result, 'a=%5B%271%27%2C+%272%27%5D') - result = http.urlencode({'a': [1, 2]}, doseq=True) - self.assertEqual(result, 'a=1&a=2') - result = http.urlencode({'a': []}, doseq=True) - self.assertEqual(result, '') - - # A MultiValueDict - result = http.urlencode(MultiValueDict({ + 'c=3&b=2&a=1', + ]) + + def test_dict_containing_sequence_not_doseq(self): + self.assertEqual(urlencode({'a': [1, 2]}, doseq=False), 'a=%5B%271%27%2C+%272%27%5D') + + def test_dict_containing_sequence_doseq(self): + self.assertEqual(urlencode({'a': [1, 2]}, doseq=True), 'a=1&a=2') + + def test_dict_containing_empty_sequence_doseq(self): + self.assertEqual(urlencode({'a': []}, doseq=True), '') + + def test_multivaluedict(self): + result = urlencode(MultiValueDict({ 'name': ['Adrian', 'Simon'], - 'position': ['Developer'] + 'position': ['Developer'], }), doseq=True) - acceptable_results = [ - # MultiValueDicts are similarly unordered + # MultiValueDicts are similarly unordered. + self.assertIn(result, [ 'name=Adrian&name=Simon&position=Developer', - 'position=Developer&name=Adrian&name=Simon' - ] - self.assertIn(result, acceptable_results) + 'position=Developer&name=Adrian&name=Simon', + ]) + + def test_dict_with_bytes_values(self): + self.assertEqual(urlencode({'a': b'abc'}, doseq=True), 'a=abc') + + def test_dict_with_sequence_of_bytes(self): + self.assertEqual(urlencode({'a': [b'spam', b'eggs', b'bacon']}, doseq=True), 'a=spam&a=eggs&a=bacon') + + def test_dict_with_bytearray(self): + self.assertEqual(urlencode({'a': bytearray(range(2))}, doseq=True), 'a=0&a=1') + self.assertEqual(urlencode({'a': bytearray(range(2))}, doseq=False), 'a=%5B%270%27%2C+%271%27%5D') - def test_base36(self): - # reciprocity works + def test_generator(self): + def gen(): + yield from range(2) + + self.assertEqual(urlencode({'a': gen()}, doseq=True), 'a=0&a=1') + self.assertEqual(urlencode({'a': gen()}, doseq=False), 'a=%5B%270%27%2C+%271%27%5D') + + def test_none(self): + with self.assertRaisesMessage(TypeError, self.cannot_encode_none_msg): + urlencode({'a': None}) + + def test_none_in_sequence(self): + with self.assertRaisesMessage(TypeError, self.cannot_encode_none_msg): + urlencode({'a': [None]}, doseq=True) + + def test_none_in_generator(self): + def gen(): + yield None + with self.assertRaisesMessage(TypeError, self.cannot_encode_none_msg): + urlencode({'a': gen()}, doseq=True) + + +class Base36IntTests(SimpleTestCase): + def test_roundtrip(self): for n in [0, 1, 1000, 1000000]: - self.assertEqual(n, http.base36_to_int(http.int_to_base36(n))) - if six.PY2: - self.assertEqual(sys.maxint, http.base36_to_int(http.int_to_base36(sys.maxint))) - - # bad input - with self.assertRaises(ValueError): - http.int_to_base36(-1) - if six.PY2: - with self.assertRaises(ValueError): - http.int_to_base36(sys.maxint + 1) + self.assertEqual(n, base36_to_int(int_to_base36(n))) + + def test_negative_input(self): + with self.assertRaisesMessage(ValueError, 'Negative base36 conversion input.'): + int_to_base36(-1) + + def test_to_base36_errors(self): for n in ['1', 'foo', {1: 2}, (1, 2, 3), 3.141]: with self.assertRaises(TypeError): - http.int_to_base36(n) + int_to_base36(n) + def test_invalid_literal(self): for n in ['#', ' ']: - with self.assertRaises(ValueError): - http.base36_to_int(n) + with self.assertRaisesMessage(ValueError, "invalid literal for int() with base 36: '%s'" % n): + base36_to_int(n) + + def test_input_too_large(self): + with self.assertRaisesMessage(ValueError, 'Base36 input too large'): + base36_to_int('1' * 14) + + def test_to_int_errors(self): for n in [123, {1: 2}, (1, 2, 3), 3.141]: with self.assertRaises(TypeError): - http.base36_to_int(n) + base36_to_int(n) - # more explicit output testing + def test_values(self): for n, b36 in [(0, '0'), (1, '1'), (42, '16'), (818469960, 'django')]: - self.assertEqual(http.int_to_base36(n), b36) - self.assertEqual(http.base36_to_int(b36), n) + self.assertEqual(int_to_base36(n), b36) + self.assertEqual(base36_to_int(b36), n) - def test_is_safe_url(self): + +class IsSafeURLTests(unittest.TestCase): + def test_bad_urls(self): bad_urls = ( 'http://example.com', 'http:///example.com', @@ -106,16 +147,17 @@ def test_is_safe_url(self): r'http://testserver\me:pass@example.com', r'http://testserver\@example.com', r'http:\\testserver\confirm\me@example.com', + 'http:999999999', + 'ftp:9999999999', '\n', + 'http://[2001:cdba:0000:0000:0000:0000:3257:9652/', + 'http://2001:cdba:0000:0000:0000:0000:3257:9652]/', ) for bad_url in bad_urls: - with ignore_warnings(category=RemovedInDjango21Warning): - self.assertFalse(http.is_safe_url(bad_url, host='testserver'), "%s should be blocked" % bad_url) - self.assertFalse( - http.is_safe_url(bad_url, allowed_hosts={'testserver', 'testserver2'}), - "%s should be blocked" % bad_url, - ) + with self.subTest(url=bad_url): + self.assertIs(is_safe_url(bad_url, allowed_hosts={'testserver', 'testserver2'}), False) + def test_good_urls(self): good_urls = ( '/view/?param=http://example.com', '/view/?param=https://example.com', @@ -126,67 +168,75 @@ def test_is_safe_url(self): '//testserver/', 'http://testserver/confirm?email=me@example.com', '/url%20with%20spaces/', + 'path/http:2222222222', ) for good_url in good_urls: - with ignore_warnings(category=RemovedInDjango21Warning): - self.assertTrue(http.is_safe_url(good_url, host='testserver'), "%s should be allowed" % good_url) - self.assertTrue( - http.is_safe_url(good_url, allowed_hosts={'otherserver', 'testserver'}), - "%s should be allowed" % good_url, - ) - - if six.PY2: - # Check binary URLs, regression tests for #26308 - self.assertTrue( - http.is_safe_url(b'https://testserver/', allowed_hosts={'testserver'}), - "binary URLs should be allowed on Python 2" - ) - self.assertFalse(http.is_safe_url(b'\x08//example.com', allowed_hosts={'testserver'})) - self.assertTrue(http.is_safe_url('àview/'.encode('utf-8'), allowed_hosts={'testserver'})) - self.assertFalse(http.is_safe_url('àview'.encode('latin-1'), allowed_hosts={'testserver'})) + with self.subTest(url=good_url): + self.assertIs(is_safe_url(good_url, allowed_hosts={'otherserver', 'testserver'}), True) + def test_basic_auth(self): # Valid basic auth credentials are allowed. - self.assertTrue(http.is_safe_url(r'http://user:pass@testserver/', allowed_hosts={'user:pass@testserver'})) + self.assertIs(is_safe_url(r'http://user:pass@testserver/', allowed_hosts={'user:pass@testserver'}), True) + + def test_no_allowed_hosts(self): # A path without host is allowed. - self.assertTrue(http.is_safe_url('/confirm/me@example.com')) + self.assertIs(is_safe_url('/confirm/me@example.com', allowed_hosts=None), True) # Basic auth without host is not allowed. - self.assertFalse(http.is_safe_url(r'http://testserver\@example.com')) + self.assertIs(is_safe_url(r'http://testserver\@example.com', allowed_hosts=None), False) - def test_is_safe_url_secure_param_https_urls(self): + def test_allowed_hosts_str(self): + self.assertIs(is_safe_url('http://good.com/good', allowed_hosts='good.com'), True) + self.assertIs(is_safe_url('http://good.co/evil', allowed_hosts='good.com'), False) + + def test_secure_param_https_urls(self): secure_urls = ( 'https://example.com/p', 'HTTPS://example.com/p', '/view/?param=http://example.com', ) for url in secure_urls: - self.assertTrue(http.is_safe_url(url, allowed_hosts={'example.com'}, require_https=True)) + with self.subTest(url=url): + self.assertIs(is_safe_url(url, allowed_hosts={'example.com'}, require_https=True), True) - def test_is_safe_url_secure_param_non_https_urls(self): - not_secure_urls = ( + def test_secure_param_non_https_urls(self): + insecure_urls = ( 'http://example.com/p', 'ftp://example.com/p', '//example.com/p', ) - for url in not_secure_urls: - self.assertFalse(http.is_safe_url(url, allowed_hosts={'example.com'}, require_https=True)) + for url in insecure_urls: + with self.subTest(url=url): + self.assertIs(is_safe_url(url, allowed_hosts={'example.com'}, require_https=True), False) + - def test_urlsafe_base64_roundtrip(self): +class URLSafeBase64Tests(unittest.TestCase): + def test_roundtrip(self): bytestring = b'foo' - encoded = http.urlsafe_base64_encode(bytestring) - decoded = http.urlsafe_base64_decode(encoded) + encoded = urlsafe_base64_encode(bytestring) + decoded = urlsafe_base64_decode(encoded) self.assertEqual(bytestring, decoded) - def test_urlquote(self): - self.assertEqual(http.urlquote('Paris & Orl\xe9ans'), 'Paris%20%26%20Orl%C3%A9ans') - self.assertEqual(http.urlquote('Paris & Orl\xe9ans', safe="&"), 'Paris%20&%20Orl%C3%A9ans') - self.assertEqual(http.urlunquote('Paris%20%26%20Orl%C3%A9ans'), 'Paris & Orl\xe9ans') - self.assertEqual(http.urlunquote('Paris%20&%20Orl%C3%A9ans'), 'Paris & Orl\xe9ans') - self.assertEqual(http.urlquote_plus('Paris & Orl\xe9ans'), 'Paris+%26+Orl%C3%A9ans') - self.assertEqual(http.urlquote_plus('Paris & Orl\xe9ans', safe="&"), 'Paris+&+Orl%C3%A9ans') - self.assertEqual(http.urlunquote_plus('Paris+%26+Orl%C3%A9ans'), 'Paris & Orl\xe9ans') - self.assertEqual(http.urlunquote_plus('Paris+&+Orl%C3%A9ans'), 'Paris & Orl\xe9ans') - - def test_is_same_domain_good(self): + +class URLQuoteTests(unittest.TestCase): + def test_quote(self): + self.assertEqual(urlquote('Paris & Orl\xe9ans'), 'Paris%20%26%20Orl%C3%A9ans') + self.assertEqual(urlquote('Paris & Orl\xe9ans', safe="&"), 'Paris%20&%20Orl%C3%A9ans') + + def test_unquote(self): + self.assertEqual(urlunquote('Paris%20%26%20Orl%C3%A9ans'), 'Paris & Orl\xe9ans') + self.assertEqual(urlunquote('Paris%20&%20Orl%C3%A9ans'), 'Paris & Orl\xe9ans') + + def test_quote_plus(self): + self.assertEqual(urlquote_plus('Paris & Orl\xe9ans'), 'Paris+%26+Orl%C3%A9ans') + self.assertEqual(urlquote_plus('Paris & Orl\xe9ans', safe="&"), 'Paris+&+Orl%C3%A9ans') + + def test_unquote_plus(self): + self.assertEqual(urlunquote_plus('Paris+%26+Orl%C3%A9ans'), 'Paris & Orl\xe9ans') + self.assertEqual(urlunquote_plus('Paris+&+Orl%C3%A9ans'), 'Paris & Orl\xe9ans') + + +class IsSameDomainTests(unittest.TestCase): + def test_good(self): for pair in ( ('example.com', 'example.com'), ('example.com', '.example.com'), @@ -195,51 +245,68 @@ def test_is_same_domain_good(self): ('example.com:8888', '.example.com:8888'), ('foo.example.com:8888', '.example.com:8888'), ): - self.assertTrue(http.is_same_domain(*pair)) + self.assertIs(is_same_domain(*pair), True) - def test_is_same_domain_bad(self): + def test_bad(self): for pair in ( ('example2.com', 'example.com'), ('foo.example.com', 'example.com'), ('example.com:9999', 'example.com:8888'), + ('foo.example.com:8888', ''), ): - self.assertFalse(http.is_same_domain(*pair)) + self.assertIs(is_same_domain(*pair), False) class ETagProcessingTests(unittest.TestCase): def test_parsing(self): self.assertEqual( - http.parse_etags(r'"" , "etag", "e\\tag", W/"weak"'), + parse_etags(r'"" , "etag", "e\\tag", W/"weak"'), ['""', '"etag"', r'"e\\tag"', 'W/"weak"'] ) - self.assertEqual(http.parse_etags('*'), ['*']) + self.assertEqual(parse_etags('*'), ['*']) # Ignore RFC 2616 ETags that are invalid according to RFC 7232. - self.assertEqual(http.parse_etags(r'"etag", "e\"t\"ag"'), ['"etag"']) + self.assertEqual(parse_etags(r'"etag", "e\"t\"ag"'), ['"etag"']) def test_quoting(self): - self.assertEqual(http.quote_etag('etag'), '"etag"') # unquoted - self.assertEqual(http.quote_etag('"etag"'), '"etag"') # quoted - self.assertEqual(http.quote_etag('W/"etag"'), 'W/"etag"') # quoted, weak + self.assertEqual(quote_etag('etag'), '"etag"') # unquoted + self.assertEqual(quote_etag('"etag"'), '"etag"') # quoted + self.assertEqual(quote_etag('W/"etag"'), 'W/"etag"') # quoted, weak class HttpDateProcessingTests(unittest.TestCase): def test_http_date(self): t = 1167616461.0 - self.assertEqual(http.http_date(t), 'Mon, 01 Jan 2007 01:54:21 GMT') + self.assertEqual(http_date(t), 'Mon, 01 Jan 2007 01:54:21 GMT') + @ignore_warnings(category=RemovedInDjango30Warning) def test_cookie_date(self): t = 1167616461.0 - self.assertEqual(http.cookie_date(t), 'Mon, 01-Jan-2007 01:54:21 GMT') + self.assertEqual(cookie_date(t), 'Mon, 01-Jan-2007 01:54:21 GMT') def test_parsing_rfc1123(self): - parsed = http.parse_http_date('Sun, 06 Nov 1994 08:49:37 GMT') + parsed = parse_http_date('Sun, 06 Nov 1994 08:49:37 GMT') self.assertEqual(datetime.utcfromtimestamp(parsed), datetime(1994, 11, 6, 8, 49, 37)) def test_parsing_rfc850(self): - parsed = http.parse_http_date('Sunday, 06-Nov-94 08:49:37 GMT') + parsed = parse_http_date('Sunday, 06-Nov-94 08:49:37 GMT') self.assertEqual(datetime.utcfromtimestamp(parsed), datetime(1994, 11, 6, 8, 49, 37)) def test_parsing_asctime(self): - parsed = http.parse_http_date('Sun Nov 6 08:49:37 1994') + parsed = parse_http_date('Sun Nov 6 08:49:37 1994') self.assertEqual(datetime.utcfromtimestamp(parsed), datetime(1994, 11, 6, 8, 49, 37)) + + def test_parsing_year_less_than_70(self): + parsed = parse_http_date('Sun Nov 6 08:49:37 0037') + self.assertEqual(datetime.utcfromtimestamp(parsed), datetime(2037, 11, 6, 8, 49, 37)) + + +class EscapeLeadingSlashesTests(unittest.TestCase): + def test(self): + tests = ( + ('//example.com', '/%2Fexample.com'), + ('//', '/%2F'), + ) + for url, expected in tests: + with self.subTest(url=url): + self.assertEqual(escape_leading_slashes(url), expected) diff --git a/tests/utils_tests/test_inspect.py b/tests/utils_tests/test_inspect.py index e9a2cd086fcf..3967f2c886ca 100644 --- a/tests/utils_tests/test_inspect.py +++ b/tests/utils_tests/test_inspect.py @@ -3,7 +3,7 @@ from django.utils import inspect -class Person(object): +class Person: def no_arguments(self): return None @@ -33,3 +33,9 @@ def test_func_accepts_var_args_has_var_args(self): def test_func_accepts_var_args_no_var_args(self): self.assertIs(inspect.func_accepts_var_args(Person.one_argument), False) + + def test_method_has_no_args(self): + self.assertIs(inspect.method_has_no_args(Person.no_arguments), True) + self.assertIs(inspect.method_has_no_args(Person.one_argument), False) + self.assertIs(inspect.method_has_no_args(Person().no_arguments), True) + self.assertIs(inspect.method_has_no_args(Person().one_argument), False) diff --git a/tests/utils_tests/test_ipv6.py b/tests/utils_tests/test_ipv6.py index 84630d2ba066..4e434f3c3aa0 100644 --- a/tests/utils_tests/test_ipv6.py +++ b/tests/utils_tests/test_ipv6.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import unittest from django.utils.ipv6 import clean_ipv6_address, is_valid_ipv6_address diff --git a/tests/utils_tests/test_itercompat.py b/tests/utils_tests/test_itercompat.py deleted file mode 100644 index 02d3463370b6..000000000000 --- a/tests/utils_tests/test_itercompat.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.test import TestCase - -from .models import Category, Thing - - -class TestIsIterator(TestCase): - def test_regression(self): - """This failed on Django 1.5/Py2.6 because category has a next method.""" - category = Category.objects.create(name='category') - Thing.objects.create(category=category) - Thing.objects.filter(category=category) diff --git a/tests/utils_tests/test_jslex.py b/tests/utils_tests/test_jslex.py index f77be16b7f89..0afb32918806 100644 --- a/tests/utils_tests/test_jslex.py +++ b/tests/utils_tests/test_jslex.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """Tests for jslex.""" # originally from https://bitbucket.org/ned/jslex -from __future__ import unicode_literals from django.test import SimpleTestCase from django.utils.jslex import JsLexer, prepare_js_for_gettext @@ -43,23 +41,35 @@ class JsTokensTest(SimpleTestCase): (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 http://www.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions - ("""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 }"]), + # 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 http://stackoverflow.com/questions/5533925/what-javascript-constructs-does-jslex-incorrectly-lex/5573409#5573409 # NOQA + # 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 /[\]]/"""]), @@ -67,46 +77,50 @@ class JsTokensTest(SimpleTestCase): (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""" + 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, "\\\\").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 "\\\\"', @@ -120,7 +134,7 @@ 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.assertListEqual(result, toks) + self.assertEqual(result, toks) return test_func @@ -222,7 +236,7 @@ class JsToCForGettextTest(SimpleTestCase): def make_function(js, c): def test_func(self): - self.assertMultiLineEqual(prepare_js_for_gettext(js), c) + self.assertEqual(prepare_js_for_gettext(js), c) return test_func diff --git a/tests/utils_tests/test_lazyobject.py b/tests/utils_tests/test_lazyobject.py index 9dc225b55f8a..e5bccc63622c 100644 --- a/tests/utils_tests/test_lazyobject.py +++ b/tests/utils_tests/test_lazyobject.py @@ -1,18 +1,15 @@ -from __future__ import unicode_literals - import copy import pickle import sys import warnings from unittest import TestCase -from django.utils import six from django.utils.functional import LazyObject, SimpleLazyObject, empty from .models import Category, CategoryInfo -class Foo(object): +class Foo: """ A simple class with just one attribute. """ @@ -69,13 +66,23 @@ def test_cmp(self): self.assertNotEqual(obj1, obj2) self.assertNotEqual(obj1, 'bar') + def test_lt(self): + obj1 = self.lazy_wrap(1) + obj2 = self.lazy_wrap(2) + self.assertLess(obj1, obj2) + + def test_gt(self): + obj1 = self.lazy_wrap(1) + obj2 = self.lazy_wrap(2) + self.assertGreater(obj2, obj1) + def test_bytes(self): obj = self.lazy_wrap(b'foo') self.assertEqual(bytes(obj), b'foo') def test_text(self): obj = self.lazy_wrap('foo') - self.assertEqual(six.text_type(obj), 'foo') + self.assertEqual(str(obj), 'foo') def test_bool(self): # Refs #21840 @@ -170,7 +177,7 @@ def test_iter(self): # Tests whether an object's custom `__iter__` method is being # used when iterating over it. - class IterObject(object): + class IterObject: def __init__(self, values): self.values = values @@ -187,11 +194,13 @@ def __iter__(self): def test_pickle(self): # See ticket #16563 obj = self.lazy_wrap(Foo()) + obj.bar = 'baz' pickled = pickle.dumps(obj) unpickled = pickle.loads(pickled) self.assertIsInstance(unpickled, Foo) self.assertEqual(unpickled, obj) self.assertEqual(unpickled.foo, obj.foo) + self.assertEqual(unpickled.bar, obj.bar) # Test copying lazy objects wrapping both builtin types and user-defined # classes since a lot of the relevant code does __dict__ manipulation and @@ -302,7 +311,7 @@ def test_repr(self): obj = self.lazy_wrap(42) # __repr__ contains __repr__ of setup function and does not evaluate # the SimpleLazyObject - six.assertRegex(self, repr(obj), '^ 19.""" + self.assertTrue( + words(25).startswith( + 'lorem ipsum dolor sit amet consectetur adipisicing elit sed ' + 'do eiusmod tempor incididunt ut labore et dolore magna aliqua' + ) + ) + + def test_more_words_than_common(self): + """words(n) returns n words for n > 19.""" + self.assertEqual(len(words(25).split()), 25) + + def test_common_large_number_of_words(self): + """words(n) has n words when n is greater than len(WORDS).""" + self.assertEqual(len(words(500).split()), 500) + + @mock.patch('django.utils.lorem_ipsum.random.sample') + def test_not_common_words(self, mock_sample): + """words(n, common=False) returns random words.""" + mock_sample.return_value = ['exercitationem', 'perferendis'] + self.assertEqual(words(2, common=False), 'exercitationem perferendis') + + def test_sentence_starts_with_capital(self): + """A sentence starts with a capital letter.""" + self.assertTrue(sentence()[0].isupper()) + + @mock.patch('django.utils.lorem_ipsum.random.sample') + @mock.patch('django.utils.lorem_ipsum.random.choice') + @mock.patch('django.utils.lorem_ipsum.random.randint') + def test_sentence(self, mock_randint, mock_choice, mock_sample): + """ + Sentences are built using some number of phrases and a set of words. + """ + mock_randint.return_value = 2 # Use two phrases. + mock_sample.return_value = ['exercitationem', 'perferendis'] + mock_choice.return_value = '?' + value = sentence() + self.assertEqual(mock_randint.call_count, 3) + self.assertEqual(mock_sample.call_count, 2) + self.assertEqual(mock_choice.call_count, 1) + self.assertEqual(value, 'Exercitationem perferendis, exercitationem perferendis?') + + @mock.patch('django.utils.lorem_ipsum.random.choice') + def test_sentence_ending(self, mock_choice): + """Sentences end with a question mark or a period.""" + mock_choice.return_value = '?' + self.assertIn(sentence()[-1], '?') + mock_choice.return_value = '.' + self.assertIn(sentence()[-1], '.') + + @mock.patch('django.utils.lorem_ipsum.random.sample') + @mock.patch('django.utils.lorem_ipsum.random.choice') + @mock.patch('django.utils.lorem_ipsum.random.randint') + def test_paragraph(self, mock_paragraph_randint, mock_choice, mock_sample): + """paragraph() generates a single paragraph.""" + # Make creating 2 sentences use 2 phrases. + mock_paragraph_randint.return_value = 2 + mock_sample.return_value = ['exercitationem', 'perferendis'] + mock_choice.return_value = '.' + value = paragraph() + self.assertEqual(mock_paragraph_randint.call_count, 7) + self.assertEqual(value, ( + 'Exercitationem perferendis, exercitationem perferendis. ' + 'Exercitationem perferendis, exercitationem perferendis.' + )) + + @mock.patch('django.utils.lorem_ipsum.random.sample') + @mock.patch('django.utils.lorem_ipsum.random.choice') + @mock.patch('django.utils.lorem_ipsum.random.randint') + def test_paragraphs_not_common(self, mock_randint, mock_choice, mock_sample): + """ + paragraphs(1, common=False) generating one paragraph that's not the + COMMON_P paragraph. + """ + # Make creating 2 sentences use 2 phrases. + mock_randint.return_value = 2 + mock_sample.return_value = ['exercitationem', 'perferendis'] + mock_choice.return_value = '.' + self.assertEqual( + paragraphs(1, common=False), + [ + 'Exercitationem perferendis, exercitationem perferendis. ' + 'Exercitationem perferendis, exercitationem perferendis.' + ] + ) + self.assertEqual(mock_randint.call_count, 7) + def test_paragraphs(self): + """paragraphs(1) uses the COMMON_P paragraph.""" self.assertEqual( paragraphs(1), [ 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, ' diff --git a/tests/utils_tests/test_module/__init__.py b/tests/utils_tests/test_module/__init__.py index 29ee11b722fd..d8a5fe2ed9f5 100644 --- a/tests/utils_tests/test_module/__init__.py +++ b/tests/utils_tests/test_module/__init__.py @@ -1,4 +1,4 @@ -class SiteMock(object): +class SiteMock: _registry = {} diff --git a/tests/utils_tests/test_module/child_module/__init__.py b/tests/utils_tests/test_module/child_module/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/utils_tests/test_module/child_module/grandchild_module.py b/tests/utils_tests/test_module/child_module/grandchild_module.py new file mode 100644 index 000000000000..3375c07f565d --- /dev/null +++ b/tests/utils_tests/test_module/child_module/grandchild_module.py @@ -0,0 +1 @@ +content = 'Grandchild Module' diff --git a/tests/utils_tests/test_module_loading.py b/tests/utils_tests/test_module_loading.py index 2a524a2cf2aa..ac54fd6b8e0f 100644 --- a/tests/utils_tests/test_module_loading.py +++ b/tests/utils_tests/test_module_loading.py @@ -1,24 +1,17 @@ -import imp import os import sys import unittest from importlib import import_module from zipimport import zipimporter -from django.test import SimpleTestCase, TestCase, modify_settings +from django.test import SimpleTestCase, modify_settings from django.test.utils import extend_sys_path -from django.utils._os import upath from django.utils.module_loading import ( autodiscover_modules, import_string, module_has_submodule, ) class DefaultLoader(unittest.TestCase): - def setUp(self): - sys.meta_path.insert(0, ProxyFinder()) - - def tearDown(self): - sys.meta_path.pop(0) def test_loader(self): "Normal module existence can be tested" @@ -55,10 +48,22 @@ def test_loader(self): with self.assertRaises(ImportError): import_module('utils_tests.test_no_submodule.anything') + def test_has_sumbodule_with_dotted_path(self): + """Nested module existence can be tested.""" + test_module = import_module('utils_tests.test_module') + # A grandchild that exists. + self.assertIs(module_has_submodule(test_module, 'child_module.grandchild_module'), True) + # A grandchild that doesn't exist. + self.assertIs(module_has_submodule(test_module, 'child_module.no_such_module'), False) + # A grandchild whose parent doesn't exist. + self.assertIs(module_has_submodule(test_module, 'no_such_module.grandchild_module'), False) + # A grandchild whose parent is not a package. + self.assertIs(module_has_submodule(test_module, 'good_module.no_such_module'), False) + class EggLoader(unittest.TestCase): def setUp(self): - self.egg_dir = '%s/eggs' % os.path.dirname(upath(__file__)) + self.egg_dir = '%s/eggs' % os.path.dirname(__file__) def tearDown(self): sys.path_importer_cache.clear() @@ -114,7 +119,7 @@ def test_deep_loader(self): import_module('egg_module.sub1.sub2.no_such_module') -class ModuleImportTestCase(TestCase): +class ModuleImportTests(SimpleTestCase): def test_import_string(self): cls = import_string('django.utils.module_loading.import_string') self.assertEqual(cls, import_string) @@ -146,11 +151,11 @@ def test_autodiscover_modules_not_found(self): autodiscover_modules('missing_module') def test_autodiscover_modules_found_but_bad_module(self): - with self.assertRaisesRegex(ImportError, "No module named '?a_package_name_that_does_not_exist'?"): + with self.assertRaisesMessage(ImportError, "No module named 'a_package_name_that_does_not_exist'"): autodiscover_modules('bad_module') def test_autodiscover_modules_several_one_bad_module(self): - with self.assertRaisesRegex(ImportError, "No module named '?a_package_name_that_does_not_exist'?"): + with self.assertRaisesMessage(ImportError, "No module named 'a_package_name_that_does_not_exist'"): autodiscover_modules('good_module', 'bad_module') def test_autodiscover_modules_several_found(self): @@ -179,36 +184,7 @@ def test_validate_registry_resets_after_missing_module(self): self.assertEqual(site._registry, {'lorem': 'ipsum'}) -class ProxyFinder(object): - def __init__(self): - self._cache = {} - - def find_module(self, fullname, path=None): - tail = fullname.rsplit('.', 1)[-1] - try: - fd, fn, info = imp.find_module(tail, path) - if fullname in self._cache: - old_fd = self._cache[fullname][0] - if old_fd: - old_fd.close() - self._cache[fullname] = (fd, fn, info) - except ImportError: - return None - else: - return self # this is a loader as well - - def load_module(self, fullname): - if fullname in sys.modules: - return sys.modules[fullname] - fd, fn, info = self._cache[fullname] - try: - return imp.load_module(fullname, fd, fn, info) - finally: - if fd: - fd.close() - - -class TestFinder(object): +class TestFinder: def __init__(self, *args, **kwargs): self.importer = zipimporter(*args, **kwargs) @@ -219,7 +195,7 @@ def find_module(self, path): return TestLoader(importer) -class TestLoader(object): +class TestLoader: def __init__(self, importer): self.importer = importer @@ -236,10 +212,10 @@ class CustomLoader(EggLoader): into one class, this isn't required. """ def setUp(self): - super(CustomLoader, self).setUp() + super().setUp() sys.path_hooks.insert(0, TestFinder) sys.path_importer_cache.clear() def tearDown(self): - super(CustomLoader, self).tearDown() + super().tearDown() sys.path_hooks.pop(0) diff --git a/tests/utils_tests/test_numberformat.py b/tests/utils_tests/test_numberformat.py index 3dd1b0644ff2..3d656025ab58 100644 --- a/tests/utils_tests/test_numberformat.py +++ b/tests/utils_tests/test_numberformat.py @@ -1,14 +1,11 @@ -# -*- encoding: utf-8 -*- -from __future__ import unicode_literals - from decimal import Decimal from sys import float_info -from unittest import TestCase +from django.test import SimpleTestCase from django.utils.numberformat import format as nformat -class TestNumberFormat(TestCase): +class TestNumberFormat(SimpleTestCase): def test_format_number(self): self.assertEqual(nformat(1234, '.'), '1234') @@ -17,6 +14,11 @@ def test_format_number(self): self.assertEqual(nformat(1234, '.', grouping=2, thousand_sep=','), '1234') self.assertEqual(nformat(1234, '.', grouping=2, thousand_sep=',', force_grouping=True), '12,34') self.assertEqual(nformat(-1234.33, '.', decimal_pos=1), '-1234.3') + # The use_l10n parameter can force thousand grouping behavior. + with self.settings(USE_THOUSAND_SEPARATOR=True, USE_L10N=True): + self.assertEqual(nformat(1234, '.', grouping=3, thousand_sep=',', use_l10n=False), '1234') + with self.settings(USE_THOUSAND_SEPARATOR=True, USE_L10N=False): + self.assertEqual(nformat(1234, '.', grouping=3, thousand_sep=',', use_l10n=True), '1,234') def test_format_string(self): self.assertEqual(nformat('1234', '.'), '1234') @@ -52,6 +54,12 @@ def test_large_number(self): self.assertEqual(nformat(-1 - int_max, '.'), most_max.format('-', '9')) self.assertEqual(nformat(-2 * int_max, '.'), most_max2.format('-')) + def test_float_numbers(self): + # A float without a fractional part (3.) results in a ".0" when no + # deimal_pos is given. Contrast that with the Decimal('3.') case in + # test_decimal_numbers which doesn't return a fractional part. + self.assertEqual(nformat(3., '.'), '3.0') + def test_decimal_numbers(self): self.assertEqual(nformat(Decimal('1234'), '.'), '1234') self.assertEqual(nformat(Decimal('1234.2'), '.'), '1234.2') @@ -60,6 +68,37 @@ def test_decimal_numbers(self): self.assertEqual(nformat(Decimal('1234'), '.', grouping=2, thousand_sep=',', force_grouping=True), '12,34') self.assertEqual(nformat(Decimal('-1234.33'), '.', decimal_pos=1), '-1234.3') self.assertEqual(nformat(Decimal('0.00000001'), '.', decimal_pos=8), '0.00000001') + self.assertEqual(nformat(Decimal('9e-19'), '.', decimal_pos=2), '0.00') + self.assertEqual(nformat(Decimal('.00000000000099'), '.', decimal_pos=0), '0') + self.assertEqual( + nformat(Decimal('1e16'), '.', thousand_sep=',', grouping=3, force_grouping=True), + '10,000,000,000,000,000' + ) + self.assertEqual( + nformat(Decimal('1e16'), '.', decimal_pos=2, thousand_sep=',', grouping=3, force_grouping=True), + '10,000,000,000,000,000.00' + ) + self.assertEqual(nformat(Decimal('3.'), '.'), '3') + self.assertEqual(nformat(Decimal('3.0'), '.'), '3.0') + # Very large & small numbers. + tests = [ + ('9e9999', None, '9e+9999'), + ('9e9999', 3, '9.000e+9999'), + ('9e201', None, '9e+201'), + ('9e200', None, '9e+200'), + ('1.2345e999', 2, '1.23e+999'), + ('9e-999', None, '9e-999'), + ('1e-7', 8, '0.00000010'), + ('1e-8', 8, '0.00000001'), + ('1e-9', 8, '0.00000000'), + ('1e-10', 8, '0.00000000'), + ('1e-11', 8, '0.00000000'), + ('1' + ('0' * 300), 3, '1.000e+300'), + ('0.{}1234'.format('0' * 299), 3, '1.234e-300'), + ] + for value, decimal_pos, expected_value in tests: + with self.subTest(value=value): + self.assertEqual(nformat(Decimal(value), '.', decimal_pos), expected_value) def test_decimal_subclass(self): class EuroDecimal(Decimal): @@ -67,7 +106,7 @@ class EuroDecimal(Decimal): Wrapper for Decimal which prefixes each amount with the € symbol. """ def __format__(self, specifier, **kwargs): - amount = super(EuroDecimal, self).__format__(specifier, **kwargs) + amount = super().__format__(specifier, **kwargs) return '€ {}'.format(amount) price = EuroDecimal('1.23') diff --git a/tests/utils_tests/test_regex_helper.py b/tests/utils_tests/test_regex_helper.py index f91fd1cec7ed..77bcd5bb3552 100644 --- a/tests/utils_tests/test_regex_helper.py +++ b/tests/utils_tests/test_regex_helper.py @@ -1,7 +1,4 @@ -from __future__ import unicode_literals - import unittest -import warnings from django.utils import regex_helper @@ -25,16 +22,6 @@ def test_group_positional(self): result = regex_helper.normalize(pattern) self.assertEqual(result, expected) - def test_group_ignored(self): - pattern = r"(?i)(?L)(?m)(?s)(?u)(?#)" - expected = [('', [])] - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter('always') - result = regex_helper.normalize(pattern) - self.assertEqual(result, expected) - for i, char in enumerate('iLmsu#'): - self.assertEqual(str(warns[i].message), 'Using (?%s) in url() patterns is deprecated.' % char) - def test_group_noncapturing(self): pattern = r"(?:non-capturing)" expected = [('non-capturing', [])] diff --git a/tests/utils_tests/test_safestring.py b/tests/utils_tests/test_safestring.py index 6afcb3b1f7ba..b880d19f27f5 100644 --- a/tests/utils_tests/test_safestring.py +++ b/tests/utils_tests/test_safestring.py @@ -1,19 +1,11 @@ -from __future__ import unicode_literals - from django.template import Context, Template -from django.test import SimpleTestCase, ignore_warnings -from django.utils import html, six, text -from django.utils.deprecation import RemovedInDjango20Warning -from django.utils.encoding import force_bytes +from django.test import SimpleTestCase +from django.utils import html from django.utils.functional import lazy, lazystr -from django.utils.safestring import ( - EscapeData, SafeData, mark_for_escaping, mark_safe, -) - -lazybytes = lazy(force_bytes, bytes) +from django.utils.safestring import SafeData, mark_safe -class customescape(six.text_type): +class customescape(str): def __html__(self): # implement specific and obviously wrong escaping # in order to be able to tell for sure when it runs @@ -32,6 +24,13 @@ def test_mark_safe(self): self.assertRenderEqual('{{ s }}', 'a&b', s=s) self.assertRenderEqual('{{ s|force_escape }}', 'a&b', s=s) + def test_mark_safe_str(self): + """ + Calling str() on a SafeText instance doesn't lose the safe status. + """ + s = mark_safe('a&b') + self.assertIsInstance(str(s), type(s)) + def test_mark_safe_object_implementing_dunder_html(self): e = customescape('') s = mark_safe(e) @@ -42,14 +41,12 @@ def test_mark_safe_object_implementing_dunder_html(self): def test_mark_safe_lazy(self): s = lazystr('a&b') - b = lazybytes(b'a&b') self.assertIsInstance(mark_safe(s), SafeData) - self.assertIsInstance(mark_safe(b), SafeData) self.assertRenderEqual('{{ s }}', 'a&b', s=mark_safe(s)) def test_mark_safe_object_implementing_dunder_str(self): - class Obj(object): + class Obj: def __str__(self): return '' @@ -63,40 +60,6 @@ def test_mark_safe_result_implements_dunder_html(self): def test_mark_safe_lazy_result_implements_dunder_html(self): self.assertEqual(mark_safe(lazystr('a&b')).__html__(), 'a&b') - @ignore_warnings(category=RemovedInDjango20Warning) - def test_mark_for_escaping(self): - s = mark_for_escaping('a&b') - self.assertRenderEqual('{{ s }}', 'a&b', s=s) - self.assertRenderEqual('{{ s }}', 'a&b', s=mark_for_escaping(s)) - - @ignore_warnings(category=RemovedInDjango20Warning) - def test_mark_for_escaping_object_implementing_dunder_html(self): - e = customescape('') - s = mark_for_escaping(e) - self.assertIs(s, e) - - self.assertRenderEqual('{{ s }}', '<>', s=s) - self.assertRenderEqual('{{ s|force_escape }}', '<a&b>', s=s) - - @ignore_warnings(category=RemovedInDjango20Warning) - def test_mark_for_escaping_lazy(self): - s = lazystr('a&b') - b = lazybytes(b'a&b') - - self.assertIsInstance(mark_for_escaping(s), EscapeData) - self.assertIsInstance(mark_for_escaping(b), EscapeData) - self.assertRenderEqual('{% autoescape off %}{{ s }}{% endautoescape %}', 'a&b', s=mark_for_escaping(s)) - - @ignore_warnings(category=RemovedInDjango20Warning) - def test_mark_for_escaping_object_implementing_dunder_str(self): - class Obj(object): - def __str__(self): - return '' - - s = mark_for_escaping(Obj()) - - self.assertRenderEqual('{{ s }}', '<obj>', s=s) - def test_add_lazy_safe_text_and_safe_text(self): s = html.escape(lazystr('a')) s += mark_safe('&b') @@ -106,10 +69,6 @@ def test_add_lazy_safe_text_and_safe_text(self): s += mark_safe('&b') self.assertRenderEqual('{{ s }}', 'a&b', s=s) - s = text.slugify(lazystr('a')) - s += mark_safe('&b') - self.assertRenderEqual('{{ s }}', 'a&b', s=s) - def test_mark_safe_as_decorator(self): """ mark_safe used as a decorator leaves the result of a function diff --git a/tests/utils_tests/test_simplelazyobject.py b/tests/utils_tests/test_simplelazyobject.py index bad47a2f899d..d6386fe79c39 100644 --- a/tests/utils_tests/test_simplelazyobject.py +++ b/tests/utils_tests/test_simplelazyobject.py @@ -1,31 +1,17 @@ -from __future__ import unicode_literals - import pickle from django.contrib.auth.models import User from django.test import TestCase -from django.utils import six from django.utils.functional import SimpleLazyObject class TestUtilsSimpleLazyObjectDjangoTestCase(TestCase): - def test_pickle_py2_regression(self): - # See ticket #20212 + def test_pickle(self): user = User.objects.create_user('johndoe', 'john@example.com', 'pass') x = SimpleLazyObject(lambda: user) - - # This would fail with "TypeError: can't pickle instancemethod objects", - # only on Python 2.X. pickle.dumps(x) - # Try the variant protocol levels. pickle.dumps(x, 0) pickle.dumps(x, 1) pickle.dumps(x, 2) - - if six.PY2: - import cPickle - - # This would fail with "TypeError: expected string or Unicode object, NoneType found". - cPickle.dumps(x) diff --git a/tests/utils_tests/test_text.py b/tests/utils_tests/test_text.py index 3dfc33a12060..cab324d64edb 100644 --- a/tests/utils_tests/test_text.py +++ b/tests/utils_tests/test_text.py @@ -1,13 +1,11 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import json +import sys from django.test import SimpleTestCase -from django.utils import six, text +from django.utils import text from django.utils.functional import lazystr from django.utils.text import format_lazy -from django.utils.translation import override, ugettext_lazy +from django.utils.translation import gettext_lazy, override IS_WIDE_BUILD = (len('\U0001F4A9') == 1) @@ -58,22 +56,22 @@ def test_smart_split(self): def test_truncate_chars(self): truncator = text.Truncator('The quick brown fox jumped over the lazy dog.') self.assertEqual('The quick brown fox jumped over the lazy dog.', truncator.chars(100)), - self.assertEqual('The quick brown fox ...', truncator.chars(23)), + self.assertEqual('The quick brown fox …', truncator.chars(21)), self.assertEqual('The quick brown fo.....', truncator.chars(23, '.....')), nfc = text.Truncator('o\xfco\xfco\xfco\xfc') nfd = text.Truncator('ou\u0308ou\u0308ou\u0308ou\u0308') self.assertEqual('oüoüoüoü', nfc.chars(8)) self.assertEqual('oüoüoüoü', nfd.chars(8)) - self.assertEqual('oü...', nfc.chars(5)) - self.assertEqual('oü...', nfd.chars(5)) + self.assertEqual('oü…', nfc.chars(3)) + self.assertEqual('oü…', nfd.chars(3)) # Ensure the final length is calculated correctly when there are # combining characters with no precomposed form, and that combining # characters are not split up. truncator = text.Truncator('-B\u030AB\u030A----8') - self.assertEqual('-B\u030A...', truncator.chars(5)) - self.assertEqual('-B\u030AB\u030A-...', truncator.chars(7)) + self.assertEqual('-B\u030A…', truncator.chars(3)) + self.assertEqual('-B\u030AB\u030A-…', truncator.chars(5)) self.assertEqual('-B\u030AB\u030A----8', truncator.chars(8)) # Ensure the length of the end text is correctly calculated when it @@ -84,18 +82,29 @@ def test_truncate_chars(self): # Make a best effort to shorten to the desired length, but requesting # a length shorter than the ellipsis shouldn't break - self.assertEqual('...', text.Truncator('asdf').chars(1)) + self.assertEqual('…', text.Truncator('asdf').chars(0)) # lazy strings are handled correctly - self.assertEqual(text.Truncator(lazystr('The quick brown fox')).chars(12), 'The quick...') + self.assertEqual(text.Truncator(lazystr('The quick brown fox')).chars(10), 'The quick…') + + def test_truncate_chars_html(self): + perf_test_values = [ + (('', None), + ('&' * 50000, '&' * 9 + '…'), + ('_X<<<<<<<<<<<>', None), + ] + for value, expected in perf_test_values: + with self.subTest(value=value): + truncator = text.Truncator(value) + self.assertEqual(expected if expected else value, truncator.chars(10, html=True)) def test_truncate_words(self): truncator = text.Truncator('The quick brown fox jumped over the lazy dog.') self.assertEqual('The quick brown fox jumped over the lazy dog.', truncator.words(10)) - self.assertEqual('The quick brown fox...', truncator.words(4)) + self.assertEqual('The quick brown fox…', truncator.words(4)) self.assertEqual('The quick brown fox[snip]', truncator.words(4, '[snip]')) # lazy strings are handled correctly truncator = text.Truncator(lazystr('The quick brown fox jumped over the lazy dog.')) - self.assertEqual('The quick brown fox...', truncator.words(4)) + self.assertEqual('The quick brown fox…', truncator.words(4)) def test_truncate_html_words(self): truncator = text.Truncator( @@ -106,7 +115,7 @@ def test_truncate_html_words(self): truncator.words(10, html=True) ) self.assertEqual( - '

        The quick brown fox...

        ', + '

        The quick brown fox…

        ', truncator.words(4, html=True) ) self.assertEqual( @@ -123,21 +132,31 @@ def test_truncate_html_words(self): '

        The quick brown fox jumped over the lazy dog.

        ' ) self.assertEqual( - '

        The quick brown...

        ', - truncator.words(3, '...', html=True) + '

        The quick brown…

        ', + truncator.words(3, html=True) ) # Test self-closing tags truncator = text.Truncator('
        The
        quick brown fox jumped over the lazy dog.') - self.assertEqual('
        The
        quick brown...', truncator.words(3, '...', html=True)) + self.assertEqual('
        The
        quick brown…', truncator.words(3, html=True)) truncator = text.Truncator('
        The
        quick brown fox jumped over the lazy dog.') - self.assertEqual('
        The
        quick brown...', truncator.words(3, '...', html=True)) + self.assertEqual('
        The
        quick brown…', truncator.words(3, html=True)) # Test html entities truncator = text.Truncator('Buenos días! ¿Cómo está?') - self.assertEqual('Buenos días! ¿Cómo...', truncator.words(3, '...', html=True)) + self.assertEqual('Buenos días! ¿Cómo…', truncator.words(3, html=True)) truncator = text.Truncator('

        I <3 python, what about you?

        ') - self.assertEqual('

        I <3 python...

        ', truncator.words(3, '...', html=True)) + self.assertEqual('

        I <3 python,…

        ', truncator.words(3, html=True)) + + perf_test_values = [ + ('', + '&' * 50000, + '_X<<<<<<<<<<<>', + ] + for value in perf_test_values: + with self.subTest(value=value): + truncator = text.Truncator(value) + self.assertEqual(value, truncator.words(50, html=True)) def test_wrap(self): digits = '1234 67 9' @@ -160,12 +179,6 @@ def test_normalize_newlines(self): self.assertEqual(text.normalize_newlines(""), "") self.assertEqual(text.normalize_newlines(lazystr("abc\ndef\rghi\r\n")), "abc\ndef\nghi\n") - def test_normalize_newlines_bytes(self): - """normalize_newlines should be able to handle bytes too""" - normalized = text.normalize_newlines(b"abc\ndef\rghi\r\n") - self.assertEqual(normalized, "abc\ndef\nghi\n") - self.assertIsInstance(normalized, six.text_type) - def test_phone2numeric(self): numeric = text.phone2numeric('0800 flowers') self.assertEqual(numeric, '0800 3569377') @@ -184,6 +197,8 @@ def test_slugify(self): ) for value, output, is_unicode in items: self.assertEqual(text.slugify(value, allow_unicode=is_unicode), output) + # interning the result may be useful, e.g. when fed to Path. + self.assertEqual(sys.intern(text.slugify('a')), 'a') def test_unescape_entities(self): items = [ @@ -218,7 +233,7 @@ def test_get_valid_filename(self): def test_compress_sequence(self): data = [{'key': i} for i in range(10)] seq = list(json.JSONEncoder().iterencode(data)) - seq = [s.encode('utf-8') for s in seq] + seq = [s.encode() for s in seq] actual_length = len(b''.join(seq)) out = text.compress_sequence(seq) compressed_length = len(b''.join(out)) @@ -239,8 +254,8 @@ def test_format_lazy(self): # The format string can be lazy. (string comes from contrib.admin) s = format_lazy( - ugettext_lazy("Added {name} \"{object}\"."), + gettext_lazy("Added {name} \"{object}\"."), name='article', object='My first try', ) with override('fr'): - self.assertEqual('article «\xa0My first try\xa0» ajouté.', s) + self.assertEqual('Ajout de article «\xa0My first try\xa0».', s) diff --git a/tests/utils_tests/test_timesince.py b/tests/utils_tests/test_timesince.py index 645806a96bcd..3e2c5ce56efa 100644 --- a/tests/utils_tests/test_timesince.py +++ b/tests/utils_tests/test_timesince.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import datetime import unittest diff --git a/tests/utils_tests/test_timezone.py b/tests/utils_tests/test_timezone.py index 85b396ab1205..c6e5ece6c4d1 100644 --- a/tests/utils_tests/test_timezone.py +++ b/tests/utils_tests/test_timezone.py @@ -1,10 +1,12 @@ import datetime import pickle +from unittest import mock import pytz -from django.test import SimpleTestCase, mock, override_settings +from django.test import SimpleTestCase, ignore_warnings, override_settings from django.utils import timezone +from django.utils.deprecation import RemovedInDjango31Warning CET = pytz.timezone("Europe/Paris") EAT = timezone.get_fixed_timezone(180) # Africa/Nairobi @@ -96,7 +98,7 @@ def test_override_string_tz(self): self.assertEqual(timezone.get_current_timezone_name(), 'Asia/Bangkok') def test_override_fixed_offset(self): - with timezone.override(timezone.FixedOffset(0, 'tzname')): + with timezone.override(datetime.timezone(datetime.timedelta(), 'tzname')): self.assertEqual(timezone.get_current_timezone_name(), 'tzname') def test_activate_invalid_timezone(self): @@ -189,9 +191,35 @@ def test_make_aware_pytz_non_existent(self): def test_get_default_timezone(self): self.assertEqual(timezone.get_default_timezone_name(), 'America/Chicago') + def test_get_default_timezone_utc(self): + with override_settings(USE_TZ=True, TIME_ZONE='UTC'): + self.assertIs(timezone.get_default_timezone(), timezone.utc) + def test_fixedoffset_timedelta(self): delta = datetime.timedelta(hours=1) - self.assertEqual(timezone.get_fixed_timezone(delta).utcoffset(''), delta) + self.assertEqual(timezone.get_fixed_timezone(delta).utcoffset(None), delta) + + def test_fixedoffset_negative_timedelta(self): + delta = datetime.timedelta(hours=-2) + self.assertEqual(timezone.get_fixed_timezone(delta).utcoffset(None), delta) + @ignore_warnings(category=RemovedInDjango31Warning) def test_fixedoffset_pickle(self): self.assertEqual(pickle.loads(pickle.dumps(timezone.FixedOffset(0, 'tzname'))).tzname(''), 'tzname') + + def test_fixedoffset_deprecation(self): + msg = 'FixedOffset is deprecated in favor of datetime.timezone' + with self.assertWarnsMessage(RemovedInDjango31Warning, msg) as cm: + timezone.FixedOffset() + self.assertEqual(cm.filename, __file__) + + @ignore_warnings(category=RemovedInDjango31Warning) + def test_fixedoffset_utcoffset(self): + delta = datetime.timedelta(minutes=1) + self.assertEqual(timezone.FixedOffset(1).utcoffset(None), delta) + + @ignore_warnings(category=RemovedInDjango31Warning) + def test_fixedoffset_dst(self): + ZERO = datetime.timedelta(minutes=0) + delta = datetime.timedelta(hours=0) + self.assertEqual(timezone.FixedOffset().dst(delta), ZERO) diff --git a/tests/utils_tests/test_topological_sort.py b/tests/utils_tests/test_topological_sort.py new file mode 100644 index 000000000000..ed8f9fd5a620 --- /dev/null +++ b/tests/utils_tests/test_topological_sort.py @@ -0,0 +1,24 @@ +from django.test import SimpleTestCase +from django.utils.topological_sort import ( + CyclicDependencyError, stable_topological_sort, topological_sort_as_sets, +) + + +class TopologicalSortTests(SimpleTestCase): + + def test_basic(self): + dependency_graph = { + 1: {2, 3}, + 2: set(), + 3: set(), + 4: {5, 6}, + 5: set(), + 6: {5}, + } + self.assertEqual(list(topological_sort_as_sets(dependency_graph)), [{2, 3, 5}, {1, 6}, {4}]) + self.assertEqual(stable_topological_sort([1, 2, 3, 4, 5, 6], dependency_graph), [2, 3, 5, 1, 6, 4]) + + def test_cyclic_dependency(self): + msg = 'Cyclic dependency in graph: ' + with self.assertRaisesMessage(CyclicDependencyError, msg): + list(topological_sort_as_sets({1: {2}, 2: {1}})) diff --git a/tests/utils_tests/test_tree.py b/tests/utils_tests/test_tree.py index 8ab73e2b924d..154678ff5789 100644 --- a/tests/utils_tests/test_tree.py +++ b/tests/utils_tests/test_tree.py @@ -19,6 +19,21 @@ def test_repr(self): "") self.assertEqual(repr(self.node2), "") + def test_hash(self): + node3 = Node(self.node1_children, negated=True) + node4 = Node(self.node1_children, connector='OTHER') + node5 = Node(self.node1_children) + node6 = Node([['a', 1], ['b', 2]]) + node7 = Node([('a', [1, 2])]) + node8 = Node([('a', (1, 2))]) + self.assertNotEqual(hash(self.node1), hash(self.node2)) + self.assertNotEqual(hash(self.node1), hash(node3)) + self.assertNotEqual(hash(self.node1), hash(node4)) + self.assertEqual(hash(self.node1), hash(node5)) + self.assertEqual(hash(self.node1), hash(node6)) + self.assertEqual(hash(self.node2), hash(Node())) + self.assertEqual(hash(node7), hash(node8)) + def test_len(self): self.assertEqual(len(self.node1), 2) self.assertEqual(len(self.node2), 0) @@ -55,3 +70,19 @@ def test_deepcopy(self): node5 = copy.deepcopy(self.node1) self.assertIs(self.node1.children, node4.children) self.assertIsNot(self.node1.children, node5.children) + + def test_eq_children(self): + node = Node(self.node1_children) + self.assertEqual(node, self.node1) + self.assertNotEqual(node, self.node2) + + def test_eq_connector(self): + new_node = Node(connector='NEW') + default_node = Node(connector='DEFAULT') + self.assertEqual(default_node, self.node2) + self.assertNotEqual(default_node, new_node) + + def test_eq_negated(self): + node = Node(negated=False) + negated = Node(negated=True) + self.assertNotEqual(negated, node) diff --git a/tests/validation/__init__.py b/tests/validation/__init__.py index 01575c1b106c..5d87d8c7311f 100644 --- a/tests/validation/__init__.py +++ b/tests/validation/__init__.py @@ -1,8 +1,7 @@ from django.core.exceptions import ValidationError -from django.test import TestCase -class ValidationTestCase(TestCase): +class ValidationAssertions: def assertFailsValidation(self, clean, failed_fields, **kwargs): with self.assertRaises(ValidationError) as cm: clean(**kwargs) diff --git a/tests/validation/models.py b/tests/validation/models.py index 2e2a144cb6e3..953370bc376a 100644 --- a/tests/validation/models.py +++ b/tests/validation/models.py @@ -1,10 +1,7 @@ -from __future__ import unicode_literals - from datetime import datetime from django.core.exceptions import ValidationError from django.db import models -from django.utils.encoding import python_2_unicode_compatible def validate_answer_to_universe(value): @@ -36,7 +33,7 @@ class ModelToValidate(models.Model): slug = models.SlugField(blank=True) def clean(self): - super(ModelToValidate, self).clean() + super().clean() if self.number == 11: raise ValidationError('Invalid number supplied!') @@ -91,7 +88,6 @@ def clean(self): self.pub_date = datetime.now() -@python_2_unicode_compatible class Post(models.Model): title = models.CharField(max_length=50, unique_for_date='posted', blank=True) slug = models.CharField(max_length=50, unique_for_year='posted', blank=True) @@ -134,4 +130,4 @@ class MultipleAutoFields(models.Model): auto2 = models.AutoField(primary_key=True) except AssertionError as exc: assertion_error = exc -assert str(assertion_error) == "A model can't have more than one AutoField." +assert str(assertion_error) == "Model validation.MultipleAutoFields can't have more than one AutoField." diff --git a/tests/validation/test_custom_messages.py b/tests/validation/test_custom_messages.py index b33e232e88c7..4e4897e5b489 100644 --- a/tests/validation/test_custom_messages.py +++ b/tests/validation/test_custom_messages.py @@ -1,8 +1,10 @@ -from . import ValidationTestCase +from django.test import SimpleTestCase + +from . import ValidationAssertions from .models import CustomMessagesModel -class CustomMessagesTest(ValidationTestCase): +class CustomMessagesTests(ValidationAssertions, SimpleTestCase): def test_custom_simple_validator_message(self): cmm = CustomMessagesModel(number=12) self.assertFieldFailsValidationWithMessage(cmm.full_clean, 'number', ['AAARGH']) diff --git a/tests/validation/test_error_messages.py b/tests/validation/test_error_messages.py index c71b8cd60ab5..0869d0fc10a1 100644 --- a/tests/validation/test_error_messages.py +++ b/tests/validation/test_error_messages.py @@ -1,6 +1,3 @@ -# -*- encoding: utf-8 -*- -from __future__ import unicode_literals - from unittest import TestCase from django.core.exceptions import ValidationError @@ -26,6 +23,10 @@ def test_boolean_field_raises_error_message(self): f = models.BooleanField() self._test_validation_messages(f, 'fõo', ["'fõo' value must be either True or False."]) + def test_nullable_boolean_field_raises_error_message(self): + f = models.BooleanField(null=True) + self._test_validation_messages(f, 'fõo', ["'fõo' value must be either True, False, or None."]) + def test_float_field_raises_error_message(self): f = models.FloatField() self._test_validation_messages(f, 'fõo', ["'fõo' value must be a float."]) diff --git a/tests/validation/test_unique.py b/tests/validation/test_unique.py index 0780b9ec4341..88eb94a54e86 100644 --- a/tests/validation/test_unique.py +++ b/tests/validation/test_unique.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import datetime import unittest @@ -30,7 +28,7 @@ def test_unique_together_gets_picked_up_and_converted_to_tuple(self): self.assertEqual( ([(UniqueTogetherModel, ('ifield', 'cfield')), (UniqueTogetherModel, ('ifield', 'efield')), - (UniqueTogetherModel, ('id',)), ], + (UniqueTogetherModel, ('id',))], []), m._get_unique_checks() ) @@ -49,13 +47,13 @@ def test_unique_together_normalization(self): (('foo', 'bar'), ('bar', 'baz'))), } - for test_name, (unique_together, normalized) in data.items(): + for unique_together, normalized in data.values(): class M(models.Model): foo = models.IntegerField() bar = models.IntegerField() baz = models.IntegerField() - Meta = type(str('Meta'), (), { + Meta = type('Meta', (), { 'unique_together': unique_together, 'apps': Apps() }) diff --git a/tests/validation/test_validators.py b/tests/validation/test_validators.py index 4a3c99f08b17..9817b6594b77 100644 --- a/tests/validation/test_validators.py +++ b/tests/validation/test_validators.py @@ -1,10 +1,10 @@ -from __future__ import unicode_literals +from django.test import SimpleTestCase -from . import ValidationTestCase +from . import ValidationAssertions from .models import ModelToValidate -class TestModelsWithValidators(ValidationTestCase): +class TestModelsWithValidators(ValidationAssertions, SimpleTestCase): def test_custom_validator_passes_for_correct_value(self): mtv = ModelToValidate(number=10, name='Some Name', f_with_custom_validator=42, f_with_iterable_of_validators=42) diff --git a/tests/validation/tests.py b/tests/validation/tests.py index e7a49b470d42..46fe2f0c7bcc 100644 --- a/tests/validation/tests.py +++ b/tests/validation/tests.py @@ -1,18 +1,16 @@ -from __future__ import unicode_literals - from django import forms from django.core.exceptions import NON_FIELD_ERRORS from django.test import TestCase from django.utils.functional import lazy -from . import ValidationTestCase +from . import ValidationAssertions from .models import ( Article, Author, GenericIPAddressTestModel, GenericIPAddrUnpackUniqueTest, ModelToValidate, ) -class BaseModelValidationTests(ValidationTestCase): +class BaseModelValidationTests(ValidationAssertions, TestCase): def test_missing_required_field_raises_error(self): mtv = ModelToValidate(f_with_custom_validator=42) @@ -85,8 +83,9 @@ class Meta: class ModelFormsTests(TestCase): - def setUp(self): - self.author = Author.objects.create(name='Joseph Kocherhans') + @classmethod + def setUpTestData(cls): + cls.author = Author.objects.create(name='Joseph Kocherhans') def test_partial_validation(self): # Make sure the "commit=False and set field values later" idiom still @@ -128,7 +127,7 @@ def test_validation_with_invalid_blank_field(self): self.assertEqual(list(form.errors), ['pub_date']) -class GenericIPAddressFieldTests(ValidationTestCase): +class GenericIPAddressFieldTests(ValidationAssertions, TestCase): def test_correct_generic_ip_passes(self): giptm = GenericIPAddressTestModel(generic_ip="1.2.3.4") diff --git a/tests/validators/invalid_urls.txt b/tests/validators/invalid_urls.txt index 04a0b5fb1b5f..4a092034ff66 100644 --- a/tests/validators/invalid_urls.txt +++ b/tests/validators/invalid_urls.txt @@ -57,3 +57,9 @@ http://example.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa. http://example.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa http://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaa https://test.[com +http://foo@bar@example.com +http://foo/bar@example.com +http://foo:bar:baz@example.com +http://foo:bar@baz@example.com +http://foo:bar/baz@example.com +http://invalid-.com/?m=foo@example.com diff --git a/tests/validators/tests.py b/tests/validators/tests.py index 4ef8a524b121..36d0b2a520b3 100644 --- a/tests/validators/tests.py +++ b/tests/validators/tests.py @@ -1,27 +1,22 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -import io import os import re import types from datetime import datetime, timedelta -from unittest import TestCase, skipUnless +from decimal import Decimal +from unittest import TestCase from django.core.exceptions import ValidationError from django.core.files.base import ContentFile from django.core.validators import ( BaseValidator, DecimalValidator, EmailValidator, FileExtensionValidator, MaxLengthValidator, MaxValueValidator, MinLengthValidator, - MinValueValidator, RegexValidator, URLValidator, int_list_validator, - validate_comma_separated_integer_list, validate_email, - validate_image_file_extension, validate_integer, validate_ipv4_address, - validate_ipv6_address, validate_ipv46_address, validate_slug, - validate_unicode_slug, + MinValueValidator, ProhibitNullCharactersValidator, RegexValidator, + URLValidator, int_list_validator, validate_comma_separated_integer_list, + validate_email, validate_image_file_extension, validate_integer, + validate_ipv4_address, validate_ipv6_address, validate_ipv46_address, + validate_slug, validate_unicode_slug, ) from django.test import SimpleTestCase -from django.test.utils import str_prefix -from django.utils._os import upath try: from PIL import Image # noqa @@ -208,6 +203,10 @@ (MinValueValidator(0), -1, ValidationError), (MinValueValidator(NOW), NOW - timedelta(days=1), ValidationError), + # limit_value may be a callable. + (MinValueValidator(lambda: 1), 0, ValidationError), + (MinValueValidator(lambda: 1), 1, None), + (MaxLengthValidator(10), '', None), (MaxLengthValidator(10), 10 * 'x', None), @@ -254,86 +253,96 @@ (RegexValidator('a', flags=re.IGNORECASE), 'A', None), (FileExtensionValidator(['txt']), ContentFile('contents', name='fileWithUnsupportedExt.jpg'), ValidationError), - (FileExtensionValidator(['txt']), ContentFile('contents', name='fileWithNoExtenstion'), ValidationError), + (FileExtensionValidator(['txt']), ContentFile('contents', name='fileWithUnsupportedExt.JPG'), ValidationError), + (FileExtensionValidator(['txt']), ContentFile('contents', name='fileWithNoExtension'), ValidationError), + (FileExtensionValidator(['']), ContentFile('contents', name='fileWithAnExtension.txt'), ValidationError), (FileExtensionValidator([]), ContentFile('contents', name='file.txt'), ValidationError), + + (FileExtensionValidator(['']), ContentFile('contents', name='fileWithNoExtension'), None), (FileExtensionValidator(['txt']), ContentFile('contents', name='file.txt'), None), + (FileExtensionValidator(['txt']), ContentFile('contents', name='file.TXT'), None), + (FileExtensionValidator(['TXT']), ContentFile('contents', name='file.txt'), None), (FileExtensionValidator(), ContentFile('contents', name='file.jpg'), None), + (DecimalValidator(max_digits=2, decimal_places=2), Decimal('0.99'), None), + (DecimalValidator(max_digits=2, decimal_places=1), Decimal('0.99'), ValidationError), + (DecimalValidator(max_digits=3, decimal_places=1), Decimal('999'), ValidationError), + (DecimalValidator(max_digits=4, decimal_places=1), Decimal('999'), None), + (DecimalValidator(max_digits=20, decimal_places=2), Decimal('742403889818000000'), None), + (DecimalValidator(20, 2), Decimal('7.42403889818E+17'), None), + (DecimalValidator(max_digits=20, decimal_places=2), Decimal('7424742403889818000000'), ValidationError), + (DecimalValidator(max_digits=5, decimal_places=2), Decimal('7304E-1'), None), + (DecimalValidator(max_digits=5, decimal_places=2), Decimal('7304E-3'), ValidationError), + (DecimalValidator(max_digits=5, decimal_places=5), Decimal('70E-5'), None), + (DecimalValidator(max_digits=5, decimal_places=5), Decimal('70E-6'), ValidationError), + # 'Enter a number.' errors + *[ + (DecimalValidator(decimal_places=2, max_digits=10), Decimal(value), ValidationError) + for value in ( + 'NaN', '-NaN', '+NaN', 'sNaN', '-sNaN', '+sNaN', + 'Inf', '-Inf', '+Inf', 'Infinity', '-Infinity', '-Infinity', + ) + ], + (validate_image_file_extension, ContentFile('contents', name='file.jpg'), None), (validate_image_file_extension, ContentFile('contents', name='file.png'), None), + (validate_image_file_extension, ContentFile('contents', name='file.PNG'), None), (validate_image_file_extension, ContentFile('contents', name='file.txt'), ValidationError), (validate_image_file_extension, ContentFile('contents', name='file'), ValidationError), + + (ProhibitNullCharactersValidator(), '\x00something', ValidationError), + (ProhibitNullCharactersValidator(), 'something', None), + (ProhibitNullCharactersValidator(), None, None), ] def create_path(filename): - return os.path.abspath(os.path.join(os.path.dirname(upath(__file__)), filename)) + return os.path.abspath(os.path.join(os.path.dirname(__file__), filename)) # Add valid and invalid URL tests. # This only tests the validator without extended schemes. -with io.open(create_path('valid_urls.txt'), encoding='utf8') as f: +with open(create_path('valid_urls.txt'), encoding='utf8') as f: for url in f: TEST_DATA.append((URLValidator(), url.strip(), None)) -with io.open(create_path('invalid_urls.txt'), encoding='utf8') as f: +with open(create_path('invalid_urls.txt'), encoding='utf8') as f: for url in f: TEST_DATA.append((URLValidator(), url.strip(), ValidationError)) -def create_simple_test_method(validator, expected, value, num): - if expected is not None and issubclass(expected, Exception): - test_mask = 'test_%s_raises_error_%d' - - def test_func(self): - # assertRaises not used, so as to be able to produce an error message - # containing the tested value - try: - validator(value) - except expected: - pass - else: - self.fail("%s not raised when validating '%s'" % ( - expected.__name__, value)) - else: - test_mask = 'test_%s_%d' - - def test_func(self): - try: - self.assertEqual(expected, validator(value)) - except ValidationError as e: - self.fail("Validation of '%s' failed. Error message was: %s" % ( - value, str(e))) - if isinstance(validator, types.FunctionType): - val_name = validator.__name__ - else: - val_name = validator.__class__.__name__ - test_name = test_mask % (val_name, num) - if validator is validate_image_file_extension: - SKIP_MSG = "Pillow is required to test validate_image_file_extension" - test_func = skipUnless(PILLOW_IS_INSTALLED, SKIP_MSG)(test_func) - return test_name, test_func - -# Dynamically assemble a test class with the contents of TEST_DATA - - -class TestSimpleValidators(SimpleTestCase): +class TestValidators(SimpleTestCase): + + def test_validators(self): + for validator, value, expected in TEST_DATA: + name = validator.__name__ if isinstance(validator, types.FunctionType) else validator.__class__.__name__ + exception_expected = expected is not None and issubclass(expected, Exception) + with self.subTest(name, value=value): + if validator is validate_image_file_extension and not PILLOW_IS_INSTALLED: + self.skipTest('Pillow is required to test validate_image_file_extension.') + if exception_expected: + with self.assertRaises(expected): + validator(value) + else: + self.assertEqual(expected, validator(value)) + def test_single_message(self): v = ValidationError('Not Valid') - self.assertEqual(str(v), str_prefix("[%(_)s'Not Valid']")) - self.assertEqual(repr(v), str_prefix("ValidationError([%(_)s'Not Valid'])")) + self.assertEqual(str(v), "['Not Valid']") + self.assertEqual(repr(v), "ValidationError(['Not Valid'])") def test_message_list(self): v = ValidationError(['First Problem', 'Second Problem']) - self.assertEqual(str(v), str_prefix("[%(_)s'First Problem', %(_)s'Second Problem']")) - self.assertEqual(repr(v), str_prefix("ValidationError([%(_)s'First Problem', %(_)s'Second Problem'])")) + self.assertEqual(str(v), "['First Problem', 'Second Problem']") + self.assertEqual(repr(v), "ValidationError(['First Problem', 'Second Problem'])") def test_message_dict(self): v = ValidationError({'first': ['First Problem']}) - self.assertEqual(str(v), str_prefix("{%(_)s'first': [%(_)s'First Problem']}")) - self.assertEqual(repr(v), str_prefix("ValidationError({%(_)s'first': [%(_)s'First Problem']})")) + self.assertEqual(str(v), "{'first': ['First Problem']}") + self.assertEqual(repr(v), "ValidationError({'first': ['First Problem']})") def test_regex_validator_flags(self): - with self.assertRaises(TypeError): + msg = 'If the flags are set, regex must be a regular expression string.' + with self.assertRaisesMessage(TypeError, msg): RegexValidator(re.compile('a'), flags=re.IGNORECASE) def test_max_length_validator_message(self): @@ -342,13 +351,6 @@ def test_max_length_validator_message(self): v('djangoproject.com') -test_counter = 0 -for validator, value, expected in TEST_DATA: - name, method = create_simple_test_method(validator, expected, value, test_counter) - setattr(TestSimpleValidators, name, method) - test_counter += 1 - - class TestValidatorEquality(TestCase): """ Validators have valid equality operators (#21638) @@ -458,6 +460,14 @@ def test_file_extension_equality(self): FileExtensionValidator(['txt']), FileExtensionValidator(['txt']) ) + self.assertEqual( + FileExtensionValidator(['TXT']), + FileExtensionValidator(['txt']) + ) + self.assertEqual( + FileExtensionValidator(['TXT', 'png']), + FileExtensionValidator(['txt', 'png']) + ) self.assertEqual( FileExtensionValidator(['txt']), FileExtensionValidator(['txt'], code='invalid_extension') @@ -478,3 +488,21 @@ def test_file_extension_equality(self): FileExtensionValidator(['txt']), FileExtensionValidator(['txt'], message='custom error message') ) + + def test_prohibit_null_characters_validator_equality(self): + self.assertEqual( + ProhibitNullCharactersValidator(message='message', code='code'), + ProhibitNullCharactersValidator(message='message', code='code') + ) + self.assertEqual( + ProhibitNullCharactersValidator(), + ProhibitNullCharactersValidator() + ) + self.assertNotEqual( + ProhibitNullCharactersValidator(message='message1', code='code'), + ProhibitNullCharactersValidator(message='message2', code='code') + ) + self.assertNotEqual( + ProhibitNullCharactersValidator(message='message', code='code1'), + ProhibitNullCharactersValidator(message='message', code='code2') + ) diff --git a/tests/validators/valid_urls.txt b/tests/validators/valid_urls.txt index 4bc8c03059c0..f79f94814291 100644 --- a/tests/validators/valid_urls.txt +++ b/tests/validators/valid_urls.txt @@ -48,7 +48,7 @@ http://foo.bar/?q=Test%20URL-encoded%20stuff http://مثال.إختبار http://例子.测试 http://उदाहरण.परीक्षा -http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com +http://-.~_!$&'()*+,;=%40:80%2f@example.com http://xn--7sbb4ac0ad0be6cf.xn--p1ai http://1337.net http://a.b-c.de diff --git a/tests/version/tests.py b/tests/version/tests.py index b9541cd31a3d..c0075744bc97 100644 --- a/tests/version/tests.py +++ b/tests/version/tests.py @@ -1,5 +1,6 @@ from django import get_version from django.test import SimpleTestCase +from django.utils.version import get_version_tuple class VersionTests(SimpleTestCase): @@ -22,3 +23,8 @@ def test_releases(self): ) for ver_tuple, ver_string in tuples_to_strings: self.assertEqual(get_version(ver_tuple), ver_string) + + def test_get_version_tuple(self): + self.assertEqual(get_version_tuple('1.2.3'), (1, 2, 3)) + self.assertEqual(get_version_tuple('1.2.3b2'), (1, 2, 3)) + self.assertEqual(get_version_tuple('1.2.3b2.dev0'), (1, 2, 3)) diff --git a/tests/view_tests/__init__.py b/tests/view_tests/__init__.py index 638df1b91a6b..e69de29bb2d1 100644 --- a/tests/view_tests/__init__.py +++ b/tests/view_tests/__init__.py @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - - -class BrokenException(Exception): - pass - - -except_args = (b'Broken!', # plain exception with ASCII text - '¡Broken!', # non-ASCII unicode data - '¡Broken!'.encode('utf-8'), # non-ASCII, utf-8 encoded bytestring - b'\xa1Broken!', ) # non-ASCII, latin1 bytestring diff --git a/tests/view_tests/default_urls.py b/tests/view_tests/default_urls.py index f23a28630578..beb2bdc1d402 100644 --- a/tests/view_tests/default_urls.py +++ b/tests/view_tests/default_urls.py @@ -1,7 +1,7 @@ -from django.conf.urls import url from django.contrib import admin +from django.urls import path urlpatterns = [ # This is the same as in the default project template - url(r'^admin/', admin.site.urls), + path('admin/', admin.site.urls), ] diff --git a/tests/view_tests/generic_urls.py b/tests/view_tests/generic_urls.py index 4ca2c8927adf..8befa86ff535 100644 --- a/tests/view_tests/generic_urls.py +++ b/tests/view_tests/generic_urls.py @@ -1,8 +1,5 @@ -# -*- coding:utf-8 -*- -from __future__ import unicode_literals - -from django.conf.urls import url from django.contrib.auth import views as auth_views +from django.urls import path from django.views.generic import RedirectView from . import views @@ -28,22 +25,20 @@ date_based_datefield_info_dict = dict(date_based_info_dict, queryset=DateArticle.objects.all()) urlpatterns = [ - url(r'^accounts/login/$', auth_views.LoginView.as_view(template_name='login.html')), - url(r'^accounts/logout/$', auth_views.LogoutView.as_view()), + path('accounts/login/', auth_views.LoginView.as_view(template_name='login.html')), + path('accounts/logout/', auth_views.LogoutView.as_view()), # Special URLs for particular regression cases. - url('^中文/target/$', views.index_page), + path('中文/target/', views.index_page), ] # redirects, both temporary and permanent, with non-ASCII targets urlpatterns += [ - url('^nonascii_redirect/$', RedirectView.as_view( - url='/中文/target/', permanent=False)), - url('^permanent_nonascii_redirect/$', RedirectView.as_view( - url='/中文/target/', permanent=True)), + path('nonascii_redirect/', RedirectView.as_view(url='/中文/target/', permanent=False)), + path('permanent_nonascii_redirect/', RedirectView.as_view(url='/中文/target/', permanent=True)), ] # json response urlpatterns += [ - url(r'^json/response/$', views.json_response_view), + path('json/response/', views.json_response_view), ] diff --git a/tests/view_tests/locale/de/LC_MESSAGES/djangojs.mo b/tests/view_tests/locale/de/LC_MESSAGES/djangojs.mo index 34ba691029be..4ff19729280d 100644 Binary files a/tests/view_tests/locale/de/LC_MESSAGES/djangojs.mo and b/tests/view_tests/locale/de/LC_MESSAGES/djangojs.mo differ diff --git a/tests/view_tests/locale/de/LC_MESSAGES/djangojs.po b/tests/view_tests/locale/de/LC_MESSAGES/djangojs.po index 88cc35e88f9c..ed6ed226d29f 100644 --- a/tests/view_tests/locale/de/LC_MESSAGES/djangojs.po +++ b/tests/view_tests/locale/de/LC_MESSAGES/djangojs.po @@ -11,6 +11,7 @@ msgstr "" "PO-Revision-Date: 2011-01-21 21:37-0300\n" "Last-Translator: Jannis Leidel \n" "Language-Team: de \n" +"Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -38,3 +39,6 @@ msgid "%s result" msgid_plural "%s results" msgstr[0] "%s Resultat" msgstr[1] "%s Resultate" + +msgid "Image" +msgstr "Bild" diff --git a/tests/view_tests/locale/pt/LC_MESSAGES/djangojs.mo b/tests/view_tests/locale/pt/LC_MESSAGES/djangojs.mo index 8407e242fc2a..f9639d885f51 100644 Binary files a/tests/view_tests/locale/pt/LC_MESSAGES/djangojs.mo and b/tests/view_tests/locale/pt/LC_MESSAGES/djangojs.mo differ diff --git a/tests/view_tests/locale/pt/LC_MESSAGES/djangojs.po b/tests/view_tests/locale/pt/LC_MESSAGES/djangojs.po index ca643162e98f..18c9b8aadd12 100644 --- a/tests/view_tests/locale/pt/LC_MESSAGES/djangojs.po +++ b/tests/view_tests/locale/pt/LC_MESSAGES/djangojs.po @@ -1,20 +1,16 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" +"Project-Id-Version: view_tests\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-09-15 19:15+0200\n" "PO-Revision-Date: 2010-05-12 12:41-0300\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" +"Last-Translator: Unknown\n" +"Language: pt\n" +"Language-Team: Portuguese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" msgid "{count} plural2" msgid_plural "{count} plural2s" diff --git a/tests/view_tests/locale/ru/LC_MESSAGES/djangojs.mo b/tests/view_tests/locale/ru/LC_MESSAGES/djangojs.mo index cb0a8d10db6f..c4dd04503bc0 100644 Binary files a/tests/view_tests/locale/ru/LC_MESSAGES/djangojs.mo and b/tests/view_tests/locale/ru/LC_MESSAGES/djangojs.mo differ diff --git a/tests/view_tests/locale/ru/LC_MESSAGES/djangojs.po b/tests/view_tests/locale/ru/LC_MESSAGES/djangojs.po index e9eda861920a..e3f1e8883253 100644 --- a/tests/view_tests/locale/ru/LC_MESSAGES/djangojs.po +++ b/tests/view_tests/locale/ru/LC_MESSAGES/djangojs.po @@ -1,29 +1,24 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" +"Project-Id-Version: view_tests\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-09-15 16:45+0200\n" "PO-Revision-Date: 2010-05-12 12:57-0300\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" +"Last-Translator: Unknown\n" +"Language-Team: Russian \n" +"Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n" +"%100<10 || n%100>=20) ? 1 : 2);\n" msgid "this is to be translated" msgstr "перевод" - msgid "Choose a time" msgstr "Выберите время" - msgid "{count} plural2" msgid_plural "{count} plural2s" msgstr[0] "" diff --git a/tests/view_tests/media/%2F.txt b/tests/view_tests/media/%2F.txt new file mode 100644 index 000000000000..d98b646c7c66 --- /dev/null +++ b/tests/view_tests/media/%2F.txt @@ -0,0 +1 @@ +%2F content diff --git a/tests/view_tests/media/subdir/.hidden b/tests/view_tests/media/subdir/.hidden new file mode 100644 index 000000000000..3a1a5eb607d1 --- /dev/null +++ b/tests/view_tests/media/subdir/.hidden @@ -0,0 +1 @@ +The directory_name() view ignores files that start with a dot. diff --git a/tests/view_tests/media/subdir/visible b/tests/view_tests/media/subdir/visible new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/view_tests/models.py b/tests/view_tests/models.py index c891bcadee5b..04e31c43da06 100644 --- a/tests/view_tests/models.py +++ b/tests/view_tests/models.py @@ -3,10 +3,8 @@ """ from django.db import models -from django.utils.encoding import python_2_unicode_compatible -@python_2_unicode_compatible class Author(models.Model): name = models.CharField(max_length=100) @@ -17,7 +15,6 @@ def get_absolute_url(self): return '/authors/%s/' % self.id -@python_2_unicode_compatible class BaseArticle(models.Model): """ An abstract article Model so that we can create article models with and diff --git a/tests/view_tests/regression_21530_urls.py b/tests/view_tests/regression_21530_urls.py index 706a08c88867..c30cd1ed3780 100644 --- a/tests/view_tests/regression_21530_urls.py +++ b/tests/view_tests/regression_21530_urls.py @@ -1,7 +1,7 @@ -from django.conf.urls import url +from django.urls import path from . import views urlpatterns = [ - url(r'^index/$', views.index_page, name='index'), + path('index/', views.index_page, name='index'), ] diff --git a/tests/view_tests/templates/debug/template_error.html b/tests/view_tests/templates/debug/template_error.html new file mode 100644 index 000000000000..16d8ce670050 --- /dev/null +++ b/tests/view_tests/templates/debug/template_error.html @@ -0,0 +1,2 @@ +Template with error: +{% cycle %} diff --git a/tests/view_tests/templates/debug/template_exception.html b/tests/view_tests/templates/debug/template_exception.html index c6b34a83882b..31c25418fe2f 100644 --- a/tests/view_tests/templates/debug/template_exception.html +++ b/tests/view_tests/templates/debug/template_exception.html @@ -1,2 +1,2 @@ {% load debugtags %} -{% go_boom arg %} +{% go_boom %} diff --git a/tests/view_tests/templates/jsi18n.html b/tests/view_tests/templates/jsi18n.html index d6093c8ef4ae..f0bd17c199cf 100644 --- a/tests/view_tests/templates/jsi18n.html +++ b/tests/view_tests/templates/jsi18n.html @@ -4,6 +4,14 @@ +

        + +

        +

        +

        + + +

        +

        - - -

        - -

        -

        - -

        - - diff --git a/tests/view_tests/templates/old_jsi18n.html b/tests/view_tests/templates/old_jsi18n.html deleted file mode 100644 index 03e738d86715..000000000000 --- a/tests/view_tests/templates/old_jsi18n.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - - - diff --git a/tests/view_tests/templatetags/debugtags.py b/tests/view_tests/templatetags/debugtags.py index 443a89f32caa..d08b3c079e07 100644 --- a/tests/view_tests/templatetags/debugtags.py +++ b/tests/view_tests/templatetags/debugtags.py @@ -1,10 +1,8 @@ from django import template -from ..views import BrokenException - register = template.Library() @register.simple_tag -def go_boom(arg): - raise BrokenException(arg) +def go_boom(): + raise Exception('boom') diff --git a/tests/view_tests/tests/py3_test_debug.py b/tests/view_tests/tests/py3_test_debug.py deleted file mode 100644 index 30201bae53f8..000000000000 --- a/tests/view_tests/tests/py3_test_debug.py +++ /dev/null @@ -1,45 +0,0 @@ -""" -Since this file contains Python 3 specific syntax, it's named without a test_ -prefix so the test runner won't try to import it. Instead, the test class is -imported in test_debug.py, but only on Python 3. - -This filename is also in setup.cfg flake8 exclude since the Python 2 syntax -error (raise ... from ...) can't be silenced using NOQA. -""" -import sys - -from django.test import RequestFactory, TestCase -from django.views.debug import ExceptionReporter - - -class Py3ExceptionReporterTests(TestCase): - - rf = RequestFactory() - - def test_reporting_of_nested_exceptions(self): - request = self.rf.get('/test_view/') - try: - try: - raise AttributeError('Top level') - except AttributeError as explicit: - try: - raise ValueError('Second exception') from explicit - except ValueError: - raise IndexError('Final exception') - except Exception: - # Custom exception handler, just pass it into ExceptionReporter - exc_type, exc_value, tb = sys.exc_info() - - explicit_exc = 'The above exception ({0}) was the direct cause of the following exception:' - implicit_exc = 'During handling of the above exception ({0}), another exception occurred:' - - reporter = ExceptionReporter(request, exc_type, exc_value, tb) - html = reporter.get_traceback_html() - # Both messages are twice on page -- one rendered as html, - # one as plain text (for pastebin) - self.assertEqual(2, html.count(explicit_exc.format("Top level"))) - self.assertEqual(2, html.count(implicit_exc.format("Second exception"))) - - text = reporter.get_traceback_text() - self.assertIn(explicit_exc.format("Top level"), text) - self.assertIn(implicit_exc.format("Second exception"), text) diff --git a/tests/view_tests/tests/test_csrf.py b/tests/view_tests/tests/test_csrf.py index 23dab04cd67e..4c20cb897d22 100644 --- a/tests/view_tests/tests/test_csrf.py +++ b/tests/view_tests/tests/test_csrf.py @@ -2,8 +2,6 @@ from django.test import ( Client, RequestFactory, SimpleTestCase, override_settings, ) -from django.test.utils import ignore_warnings -from django.utils.deprecation import RemovedInDjango20Warning from django.utils.translation import override from django.views.csrf import CSRF_FAILURE_TEMPLATE_NAME, csrf_failure @@ -12,7 +10,7 @@ class CsrfViewTests(SimpleTestCase): def setUp(self): - super(CsrfViewTests, self).setUp() + super().setUp() self.client = Client(enforce_csrf_checks=True) @override_settings( @@ -24,48 +22,15 @@ def setUp(self): ], ) def test_translation(self): - """ - An invalid request is rejected with a localized error message. - """ - response = self.client.post('/') - self.assertContains(response, "Forbidden", status_code=403) - self.assertContains(response, - "CSRF verification failed. Request aborted.", - status_code=403) - - with self.settings(LANGUAGE_CODE='nl'), override('en-us'): - response = self.client.post('/') - self.assertContains(response, "Verboden", status_code=403) - self.assertContains(response, - "CSRF-verificatie mislukt. Verzoek afgebroken.", - status_code=403) - - @ignore_warnings(category=RemovedInDjango20Warning) - @override_settings( - USE_I18N=True, - MIDDLEWARE=None, - MIDDLEWARE_CLASSES=[ - 'django.middleware.locale.LocaleMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - ], - ) - def test_translation_middleware_classes(self): - """ - An invalid request is rejected with a localized error message. - """ + """An invalid request is rejected with a localized error message.""" response = self.client.post('/') - self.assertContains(response, "Forbidden", status_code=403) - self.assertContains(response, - "CSRF verification failed. Request aborted.", - status_code=403) + self.assertContains(response, 'Forbidden', status_code=403) + self.assertContains(response, 'CSRF verification failed. Request aborted.', status_code=403) with self.settings(LANGUAGE_CODE='nl'), override('en-us'): response = self.client.post('/') - self.assertContains(response, "Verboden", status_code=403) - self.assertContains(response, - "CSRF-verificatie mislukt. Verzoek afgebroken.", - status_code=403) + self.assertContains(response, 'Verboden', status_code=403) + self.assertContains(response, 'CSRF-verificatie mislukt. Verzoek afgebroken.', status_code=403) @override_settings( SECURE_PROXY_SSL_HEADER=('HTTP_X_FORWARDED_PROTO', 'https') @@ -76,11 +41,27 @@ def test_no_referer(self): exception by sending an incorrect referer. """ response = self.client.post('/', HTTP_X_FORWARDED_PROTO='https') - self.assertContains(response, - "You are seeing this message because this HTTPS " - "site requires a 'Referer header' to be " - "sent by your Web browser, but none was sent.", - status_code=403) + self.assertContains( + response, + 'You are seeing this message because this HTTPS site requires a ' + ''Referer header' to be sent by your Web browser, but ' + 'none was sent.', + status_code=403, + ) + self.assertContains( + response, + '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.', + status_code=403, + ) + self.assertContains( + response, + 'If you are using the <meta name="referrer" ' + 'content="no-referrer"> tag or including the ' + ''Referrer-Policy: no-referrer' header, please remove them.', + status_code=403, + ) def test_no_cookies(self): """ @@ -88,13 +69,14 @@ def test_no_cookies(self): provide a nice error message. """ response = self.client.post('/') - self.assertContains(response, - "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.", - status_code=403) + self.assertContains( + response, + '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.', + status_code=403, + ) @override_settings(TEMPLATES=[]) def test_no_django_template_engine(self): @@ -102,7 +84,7 @@ def test_no_django_template_engine(self): The CSRF view doesn't depend on the TEMPLATES configuration (#24388). """ response = self.client.post('/') - self.assertContains(response, "Forbidden", status_code=403) + self.assertContains(response, 'Forbidden', status_code=403) @override_settings(TEMPLATES=[{ 'BACKEND': 'django.template.backends.django.DjangoTemplates', @@ -115,17 +97,13 @@ def test_no_django_template_engine(self): }, }]) def test_custom_template(self): - """ - A custom CSRF_FAILURE_TEMPLATE_NAME is used. - """ + """A custom CSRF_FAILURE_TEMPLATE_NAME is used.""" response = self.client.post('/') - self.assertContains(response, "Test template for CSRF failure", status_code=403) + self.assertContains(response, 'Test template for CSRF failure', status_code=403) def test_custom_template_does_not_exist(self): - """ - An exception is raised if a nonexistent template is supplied. - """ + """An exception is raised if a nonexistent template is supplied.""" factory = RequestFactory() request = factory.post('/') with self.assertRaises(TemplateDoesNotExist): - csrf_failure(request, template_name="nonexistent.html") + csrf_failure(request, template_name='nonexistent.html') diff --git a/tests/view_tests/tests/test_debug.py b/tests/view_tests/tests/test_debug.py index 24fa59362160..a525433dfa33 100644 --- a/tests/view_tests/tests/test_debug.py +++ b/tests/view_tests/tests/test_debug.py @@ -1,33 +1,29 @@ -# -*- coding: utf-8 -*- -# This coding header is significant for tests, as the debug view is parsing -# files to search for such a header to decode the source file content -from __future__ import unicode_literals - import importlib import inspect import os import re import sys import tempfile -from unittest import skipIf +from io import StringIO +from pathlib import Path +from unittest import mock -from django.conf.urls import url from django.core import mail from django.core.files.uploadedfile import SimpleUploadedFile from django.db import DatabaseError, connection +from django.shortcuts import render from django.template import TemplateDoesNotExist from django.test import RequestFactory, SimpleTestCase, override_settings -from django.test.utils import LoggingCaptureMixin, patch_logger -from django.urls import reverse -from django.utils import six -from django.utils.encoding import force_bytes, force_text +from django.test.utils import LoggingCaptureMixin +from django.urls import path, reverse from django.utils.functional import SimpleLazyObject +from django.utils.safestring import mark_safe +from django.utils.version import PY36 from django.views.debug import ( CLEANSED_SUBSTITUTE, CallableSettingWrapper, ExceptionReporter, - cleanse_setting, technical_500_response, + Path as DebugPath, cleanse_setting, technical_500_response, ) -from .. import BrokenException, except_args from ..views import ( custom_exception_reporter_filter_view, index_page, multivalue_dict_key_error, non_sensitive_view, paranoid_view, @@ -35,26 +31,21 @@ sensitive_method_view, sensitive_view, ) -if six.PY3: - from .py3_test_debug import Py3ExceptionReporterTests # NOQA - -PY36 = sys.version_info >= (3, 6) - -class User(object): +class User: def __str__(self): return 'jacob' class WithoutEmptyPathUrls: - urlpatterns = [url(r'url/$', index_page, name='url')] + urlpatterns = [path('url/', index_page, name='url')] class CallableSettingWrapperTests(SimpleTestCase): """ Unittests for CallableSettingWrapper """ def test_repr(self): - class WrappedCallable(object): + class WrappedCallable: def __repr__(self): return "repr from the wrapped callable" @@ -66,22 +57,25 @@ def __call__(self): @override_settings(DEBUG=True, ROOT_URLCONF='view_tests.urls') -class DebugViewTests(LoggingCaptureMixin, SimpleTestCase): +class DebugViewTests(SimpleTestCase): def test_files(self): - response = self.client.get('/raises/') + with self.assertLogs('django.request', 'ERROR'): + response = self.client.get('/raises/') self.assertEqual(response.status_code, 500) data = { 'file_data.txt': SimpleUploadedFile('file_data.txt', b'haha'), } - response = self.client.post('/raises/', data) + with self.assertLogs('django.request', 'ERROR'): + response = self.client.post('/raises/', data) self.assertContains(response, 'file_data.txt', status_code=500) self.assertNotContains(response, 'haha', status_code=500) def test_400(self): # When DEBUG=True, technical_500_template() is called. - response = self.client.get('/raises400/') + with self.assertLogs('django.security', 'WARNING'): + response = self.client.get('/raises400/') self.assertContains(response, '
        not-in-urls, didn't match", status_code=404) def test_404_not_in_urls(self): response = self.client.get('/not-in-urls') self.assertNotContains(response, "Raised by:", status_code=404) + self.assertContains(response, "Django tried these URL patterns", status_code=404) self.assertContains(response, "not-in-urls, didn't match", status_code=404) + # Pattern and view name of a RegexURLPattern appear. + self.assertContains(response, r"^regex-post/(?P<pk>[0-9]+)/$", status_code=404) + self.assertContains(response, "[name='regex-post']", status_code=404) + # Pattern and view name of a RoutePattern appear. + self.assertContains(response, r"path-post/<int:pk>/", status_code=404) + self.assertContains(response, "[name='path-post']", status_code=404) @override_settings(ROOT_URLCONF=WithoutEmptyPathUrls) def test_404_empty_path_not_in_urls(self): @@ -127,26 +125,22 @@ def test_404_empty_path_not_in_urls(self): self.assertContains(response, "The empty path didn't match any of these.", status_code=404) def test_technical_404(self): - response = self.client.get('/views/technical404/') + response = self.client.get('/technical404/') self.assertContains(response, "Raised by:", status_code=404) self.assertContains(response, "view_tests.views.technical404", status_code=404) def test_classbased_technical_404(self): - response = self.client.get('/views/classbased404/') + response = self.client.get('/classbased404/') self.assertContains(response, "Raised by:", status_code=404) self.assertContains(response, "view_tests.views.Http404View", status_code=404) - def test_view_exceptions(self): - for n in range(len(except_args)): - with self.assertRaises(BrokenException): - self.client.get(reverse('view_exception', args=(n,))) - def test_non_l10ned_numeric_ids(self): """ Numeric IDs and fancy traceback context blocks line numbers shouldn't be localized. """ with self.settings(DEBUG=True, USE_L10N=True): - response = self.client.get('/raises500/') + with self.assertLogs('django.request', 'ERROR'): + response = self.client.get('/raises500/') # We look for a HTML fragment of the form # '
        ', not '
        Congratulations on your first Django-powered page." + "

        The install worked successfully! Congratulations!

        " ) @override_settings(ROOT_URLCONF='view_tests.regression_21530_urls') @@ -231,7 +226,7 @@ def test_regression_21530(self): class DebugViewQueriesAllowedTests(SimpleTestCase): # May need a query to initialize MySQL connection - allow_database_queries = True + databases = {'default'} def test_handle_db_exception(self): """ @@ -261,9 +256,9 @@ class NonDjangoTemplatesDebugViewTests(SimpleTestCase): def test_400(self): # When DEBUG=True, technical_500_template() is called. - with patch_logger('django.security.SuspiciousOperation', 'error'): + with self.assertLogs('django.security', 'WARNING'): response = self.client.get('/raises400/') - self.assertContains(response, '
        Can't find my keys', html) self.assertIn('Request Method:', html) self.assertIn('Request URL:', html) @@ -304,6 +300,7 @@ def test_request_and_exception(self): self.assertIn('

        Traceback ', html) self.assertIn('

        Request information

        ', html) self.assertNotIn('

        Request data not supplied

        ', html) + self.assertIn('

        No POST data

        ', html) def test_no_request(self): "An exception report can be generated without request" @@ -313,7 +310,7 @@ def test_no_request(self): exc_type, exc_value, tb = sys.exc_info() reporter = ExceptionReporter(None, exc_type, exc_value, tb) html = reporter.get_traceback_html() - self.assertIn('

        ValueError

        ', html) + self.assertInHTML('

        ValueError

        ', html) self.assertIn('
        Can't find my keys
        ', html) self.assertNotIn('Request Method:', html) self.assertNotIn('Request URL:', html) @@ -326,12 +323,12 @@ def test_no_request(self): def test_eol_support(self): """The ExceptionReporter supports Unix, Windows and Macintosh EOL markers""" - LINES = list('print %d' % i for i in range(1, 6)) + LINES = ['print %d' % i for i in range(1, 6)] reporter = ExceptionReporter(None, None, None, None) for newline in ['\n', '\r\n', '\r']: fd, filename = tempfile.mkstemp(text=False) - os.write(fd, force_bytes(newline.join(LINES) + newline)) + os.write(fd, (newline.join(LINES) + newline).encode()) os.close(fd) try: @@ -347,7 +344,7 @@ def test_no_exception(self): request = self.rf.get('/test_view/') reporter = ExceptionReporter(request, None, None, None) html = reporter.get_traceback_html() - self.assertIn('

        Report at /test_view/

        ', html) + self.assertInHTML('

        Report at /test_view/

        ', html) self.assertIn('
        No exception message supplied
        ', html) self.assertIn('Request Method:', html) self.assertIn('Request URL:', html) @@ -357,12 +354,63 @@ def test_no_exception(self): self.assertIn('

        Request information

        ', html) self.assertNotIn('

        Request data not supplied

        ', html) + def test_reporting_of_nested_exceptions(self): + request = self.rf.get('/test_view/') + try: + try: + raise AttributeError(mark_safe('

        Top level

        ')) + except AttributeError as explicit: + try: + raise ValueError(mark_safe('

        Second exception

        ')) from explicit + except ValueError: + raise IndexError(mark_safe('

        Final exception

        ')) + except Exception: + # Custom exception handler, just pass it into ExceptionReporter + exc_type, exc_value, tb = sys.exc_info() + + explicit_exc = 'The above exception ({0}) was the direct cause of the following exception:' + implicit_exc = 'During handling of the above exception ({0}), another exception occurred:' + + reporter = ExceptionReporter(request, exc_type, exc_value, tb) + html = reporter.get_traceback_html() + # Both messages are twice on page -- one rendered as html, + # one as plain text (for pastebin) + self.assertEqual(2, html.count(explicit_exc.format('<p>Top level</p>'))) + self.assertEqual(2, html.count(implicit_exc.format('<p>Second exception</p>'))) + self.assertEqual(10, html.count('<p>Final exception</p>')) + + text = reporter.get_traceback_text() + self.assertIn(explicit_exc.format('

        Top level

        '), text) + self.assertIn(implicit_exc.format('

        Second exception

        '), text) + self.assertEqual(3, text.count('

        Final exception

        ')) + + def test_reporting_frames_without_source(self): + try: + source = "def funcName():\n raise Error('Whoops')\nfuncName()" + namespace = {} + code = compile(source, 'generated', 'exec') + exec(code, namespace) + except Exception: + exc_type, exc_value, tb = sys.exc_info() + request = self.rf.get('/test_view/') + reporter = ExceptionReporter(request, exc_type, exc_value, tb) + frames = reporter.get_traceback_frames() + last_frame = frames[-1] + self.assertEqual(last_frame['context_line'], '') + self.assertEqual(last_frame['filename'], 'generated') + self.assertEqual(last_frame['function'], 'funcName') + self.assertEqual(last_frame['lineno'], 2) + html = reporter.get_traceback_html() + self.assertIn('generated in funcName', html) + text = reporter.get_traceback_text() + self.assertIn('"generated" in funcName', text) + def test_request_and_message(self): "A message can be provided in addition to a request" request = self.rf.get('/test_view/') reporter = ExceptionReporter(request, None, "I'm a little teapot", None) html = reporter.get_traceback_html() - self.assertIn('

        Report at /test_view/

        ', html) + self.assertInHTML('

        Report at /test_view/

        ', html) self.assertIn('
        I'm a little teapot
        ', html) self.assertIn('Request Method:', html) self.assertIn('Request URL:', html) @@ -375,7 +423,7 @@ def test_request_and_message(self): def test_message_only(self): reporter = ExceptionReporter(None, None, "I'm a little teapot", None) html = reporter.get_traceback_html() - self.assertIn('

        Report

        ', html) + self.assertInHTML('

        Report

        ', html) self.assertIn('
        I'm a little teapot
        ', html) self.assertNotIn('Request Method:', html) self.assertNotIn('Request URL:', html) @@ -400,10 +448,20 @@ def __repr__(self): self.assertIn('VAL\\xe9VAL', html) self.assertIn('EXC\\xe9EXC', html) + def test_local_variable_escaping(self): + """Safe strings in local variables are escaped.""" + try: + local = mark_safe('

        Local variable

        ') + raise ValueError(local) + except Exception: + exc_type, exc_value, tb = sys.exc_info() + html = ExceptionReporter(None, exc_type, exc_value, tb).get_traceback_html() + self.assertIn('
        '<p>Local variable</p>'
        ', html) + def test_unprintable_values_handling(self): "Unprintable values should not make the output generation choke." try: - class OomOutput(object): + class OomOutput: def __repr__(self): raise MemoryError('OOM') oomvalue = OomOutput() # NOQA @@ -419,7 +477,7 @@ def test_too_large_values_handling(self): large = 256 * 1024 repr_of_str_adds = len(repr('')) try: - class LargeOutput(object): + class LargeOutput: def __repr__(self): return repr('A' * large) largevalue = LargeOutput() # NOQA @@ -431,11 +489,25 @@ def __repr__(self): self.assertEqual(len(html) // 1024 // 128, 0) # still fit in 128Kb self.assertIn('<trimmed %d bytes string>' % (large + repr_of_str_adds,), html) - @skipIf(six.PY2, 'Bug manifests on PY3 only') + def test_encoding_error(self): + """ + A UnicodeError displays a portion of the problematic string. HTML in + safe strings is escaped. + """ + try: + mark_safe('abcdefghijkl

        mnὀp

        qrstuwxyz').encode('ascii') + except Exception: + exc_type, exc_value, tb = sys.exc_info() + reporter = ExceptionReporter(None, exc_type, exc_value, tb) + html = reporter.get_traceback_html() + self.assertIn('

        Unicode error hint

        ', html) + self.assertIn('The string that could not be encoded/decoded was: ', html) + self.assertIn('<p>mnὀp</p>', html) + def test_unfrozen_importlib(self): """ importlib is not a frozen app, but its loader thinks it's frozen which - results in an ImportError on Python 3. Refs #21443. + results in an ImportError. Refs #21443. """ try: request = self.rf.get('/test_view/') @@ -444,7 +516,7 @@ def test_unfrozen_importlib(self): exc_type, exc_value, tb = sys.exc_info() reporter = ExceptionReporter(request, exc_type, exc_value, tb) html = reporter.get_traceback_html() - self.assertIn('

        %sError at /test_view/

        ' % 'ModuleNotFound' if PY36 else 'Import', html) + self.assertInHTML('

        %sError at /test_view/

        ' % ('ModuleNotFound' if PY36 else 'Import'), html) def test_ignore_traceback_evaluation_exceptions(self): """ @@ -483,10 +555,7 @@ def test_request_with_items_key(self): An exception report can be generated for requests with 'items' in request GET, POST, FILES, or COOKIES QueryDicts. """ - if six.PY3: - value = 'items
        'Oops'
        ' - else: - value = 'items
        u'Oops'
        ' + value = 'items
        'Oops'
        ' # GET request = self.rf.get('/test_view/?items=Oops') reporter = ExceptionReporter(request, None, None, None) @@ -498,7 +567,7 @@ def test_request_with_items_key(self): html = reporter.get_traceback_html() self.assertInHTML(value, html) # FILES - fp = six.StringIO('filecontent') + fp = StringIO('filecontent') request = self.rf.post('/test_view/', data={'name': 'filename', 'items': fp}) reporter = ExceptionReporter(request, None, None, None) html = reporter.get_traceback_html() @@ -520,7 +589,7 @@ def test_exception_fetching_user(self): The error page can be rendered if the current user can't be retrieved (such as when the database is unavailable). """ - class ExceptionUser(object): + class ExceptionUser: def __str__(self): raise Exception() @@ -534,7 +603,7 @@ def __str__(self): reporter = ExceptionReporter(request, exc_type, exc_value, tb) html = reporter.get_traceback_html() - self.assertIn('

        ValueError at /test_view/

        ', html) + self.assertInHTML('

        ValueError at /test_view/

        ', html) self.assertIn('
        Oops
        ', html) self.assertIn('

        USER

        ', html) self.assertIn('

        [unable to retrieve the current user]

        ', html) @@ -542,6 +611,20 @@ def __str__(self): text = reporter.get_traceback_text() self.assertIn('USER: [unable to retrieve the current user]', text) + def test_template_encoding(self): + """ + The templates are loaded directly, not via a template loader, and + should be opened as utf-8 charset as is the default specified on + template engines. + """ + reporter = ExceptionReporter(None, None, None, None) + with mock.patch.object(DebugPath, 'open') as m: + reporter.get_traceback_html() + m.assert_called_once_with(encoding='utf-8') + m.reset_mock() + reporter.get_traceback_text() + m.assert_called_once_with(encoding='utf-8') + class PlainTextReportTests(SimpleTestCase): rf = RequestFactory() @@ -597,27 +680,43 @@ def test_request_and_message(self): reporter = ExceptionReporter(request, None, "I'm a little teapot", None) reporter.get_traceback_text() + @override_settings(DEBUG=True) + def test_template_exception(self): + request = self.rf.get('/test_view/') + try: + render(request, 'debug/template_error.html') + except Exception: + exc_type, exc_value, tb = sys.exc_info() + reporter = ExceptionReporter(request, exc_type, exc_value, tb) + text = reporter.get_traceback_text() + templ_path = Path(Path(__file__).parent.parent, 'templates', 'debug', 'template_error.html') + self.assertIn( + 'Template error:\n' + 'In template %(path)s, error at line 2\n' + ' \'cycle\' tag requires at least two arguments\n' + ' 1 : Template with error:\n' + ' 2 : {%% cycle %%} \n' + ' 3 : ' % {'path': templ_path}, + text + ) + def test_request_with_items_key(self): """ An exception report can be generated for requests with 'items' in request GET, POST, FILES, or COOKIES QueryDicts. """ - if six.PY3: - value = "items = 'Oops'" - else: - value = "items = u'Oops'" # GET request = self.rf.get('/test_view/?items=Oops') reporter = ExceptionReporter(request, None, None, None) text = reporter.get_traceback_text() - self.assertIn(value, text) + self.assertIn("items = 'Oops'", text) # POST request = self.rf.post('/test_view/', data={'items': 'Oops'}) reporter = ExceptionReporter(request, None, None, None) text = reporter.get_traceback_text() - self.assertIn(value, text) + self.assertIn("items = 'Oops'", text) # FILES - fp = six.StringIO('filecontent') + fp = StringIO('filecontent') request = self.rf.post('/test_view/', data={'name': 'filename', 'items': fp}) reporter = ExceptionReporter(request, None, None, None) text = reporter.get_traceback_text() @@ -643,15 +742,15 @@ def test_disallowed_host(self): self.assertIn("http://evil.com/", text) -class ExceptionReportTestMixin(object): - +class ExceptionReportTestMixin: # Mixin used in the ExceptionReporterFilterTests and # AjaxResponseExceptionReporterFilter tests below - - breakfast_data = {'sausage-key': 'sausage-value', - 'baked-beans-key': 'baked-beans-value', - 'hash-brown-key': 'hash-brown-value', - 'bacon-key': 'bacon-value'} + breakfast_data = { + 'sausage-key': 'sausage-value', + 'baked-beans-key': 'baked-beans-value', + 'hash-brown-key': 'hash-brown-value', + 'bacon-key': 'bacon-value', + } def verify_unsafe_response(self, view, check_for_vars=True, check_for_POST_params=True): @@ -687,7 +786,7 @@ def verify_safe_response(self, view, check_for_vars=True, self.assertContains(response, 'sauce', status_code=500) self.assertNotContains(response, 'worcestershire', status_code=500) if check_for_POST_params: - for k, v in self.breakfast_data.items(): + for k in self.breakfast_data: # All POST parameters' names are shown. self.assertContains(response, k, status_code=500) # Non-sensitive POST parameters' values are shown. @@ -729,14 +828,14 @@ def verify_unsafe_email(self, view, check_for_POST_params=True): email = mail.outbox[0] # Frames vars are never shown in plain text email reports. - body_plain = force_text(email.body) + body_plain = str(email.body) self.assertNotIn('cooked_eggs', body_plain) self.assertNotIn('scrambled', body_plain) self.assertNotIn('sauce', body_plain) self.assertNotIn('worcestershire', body_plain) # Frames vars are shown in html email reports. - body_html = force_text(email.alternatives[0][0]) + body_html = str(email.alternatives[0][0]) self.assertIn('cooked_eggs', body_html) self.assertIn('scrambled', body_html) self.assertIn('sauce', body_html) @@ -762,21 +861,21 @@ def verify_safe_email(self, view, check_for_POST_params=True): email = mail.outbox[0] # Frames vars are never shown in plain text email reports. - body_plain = force_text(email.body) + body_plain = str(email.body) self.assertNotIn('cooked_eggs', body_plain) self.assertNotIn('scrambled', body_plain) self.assertNotIn('sauce', body_plain) self.assertNotIn('worcestershire', body_plain) # Frames vars are shown in html email reports. - body_html = force_text(email.alternatives[0][0]) + body_html = str(email.alternatives[0][0]) self.assertIn('cooked_eggs', body_html) self.assertIn('scrambled', body_html) self.assertIn('sauce', body_html) self.assertNotIn('worcestershire', body_html) if check_for_POST_params: - for k, v in self.breakfast_data.items(): + for k in self.breakfast_data: # All POST parameters' names are shown. self.assertIn(k, body_plain) # Non-sensitive POST parameters' values are shown. @@ -801,7 +900,7 @@ def verify_paranoid_email(self, view): self.assertEqual(len(mail.outbox), 1) email = mail.outbox[0] # Frames vars are never shown in plain text email reports. - body = force_text(email.body) + body = str(email.body) self.assertNotIn('cooked_eggs', body) self.assertNotIn('scrambled', body) self.assertNotIn('sauce', body) @@ -940,7 +1039,7 @@ def test_callable_settings_forbidding_to_set_attributes(self): Callable settings which forbid to set attributes should not break the debug page (#23070). """ - class CallableSettingWithSlots(object): + class CallableSettingWithSlots: __slots__ = [] def __call__(self): @@ -1052,6 +1151,11 @@ def test_custom_exception_reporter_filter(self): with self.settings(DEBUG=False): self.verify_unsafe_response(custom_exception_reporter_filter_view, check_for_vars=False) + @override_settings(DEBUG=True, ROOT_URLCONF='view_tests.urls') + def test_ajax_response_encoding(self): + response = self.client.get('/raises500/', HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response['Content-Type'], 'text/plain; charset=utf-8') + class HelperFunctionTests(SimpleTestCase): diff --git a/tests/view_tests/tests/test_default_content_type.py b/tests/view_tests/tests/test_default_content_type.py new file mode 100644 index 000000000000..ece88f4b19ad --- /dev/null +++ b/tests/view_tests/tests/test_default_content_type.py @@ -0,0 +1,58 @@ +import sys +from types import ModuleType + +from django.conf import DEFAULT_CONTENT_TYPE_DEPRECATED_MSG, Settings, settings +from django.test import SimpleTestCase, ignore_warnings +from django.utils.deprecation import RemovedInDjango30Warning + + +class DefaultContentTypeTests(SimpleTestCase): + msg = DEFAULT_CONTENT_TYPE_DEPRECATED_MSG + + @ignore_warnings(category=RemovedInDjango30Warning) + def test_default_content_type_is_text_html(self): + """ + Content-Type of the default error responses is text/html. Refs #20822. + """ + with self.settings(DEFAULT_CONTENT_TYPE='text/xml'): + response = self.client.get('/raises400/') + self.assertEqual(response['Content-Type'], 'text/html') + + response = self.client.get('/raises403/') + self.assertEqual(response['Content-Type'], 'text/html') + + response = self.client.get('/nonexistent_url/') + self.assertEqual(response['Content-Type'], 'text/html') + + response = self.client.get('/server_error/') + self.assertEqual(response['Content-Type'], 'text/html') + + def test_override_settings_warning(self): + with self.assertRaisesMessage(RemovedInDjango30Warning, self.msg): + with self.settings(DEFAULT_CONTENT_TYPE='text/xml'): + pass + + def test_settings_init_warning(self): + settings_module = ModuleType('fake_settings_module') + settings_module.DEFAULT_CONTENT_TYPE = 'text/xml' + settings_module.SECRET_KEY = 'abc' + sys.modules['fake_settings_module'] = settings_module + try: + with self.assertRaisesMessage(RemovedInDjango30Warning, self.msg): + Settings('fake_settings_module') + finally: + del sys.modules['fake_settings_module'] + + def test_access_warning(self): + with self.assertRaisesMessage(RemovedInDjango30Warning, self.msg): + settings.DEFAULT_CONTENT_TYPE + # Works a second time. + with self.assertRaisesMessage(RemovedInDjango30Warning, self.msg): + settings.DEFAULT_CONTENT_TYPE + + @ignore_warnings(category=RemovedInDjango30Warning) + def test_access(self): + with self.settings(DEFAULT_CONTENT_TYPE='text/xml'): + self.assertEqual(settings.DEFAULT_CONTENT_TYPE, 'text/xml') + # Works a second time. + self.assertEqual(settings.DEFAULT_CONTENT_TYPE, 'text/xml') diff --git a/tests/view_tests/tests/test_defaults.py b/tests/view_tests/tests/test_defaults.py index 1b5ad2510270..e93558abc1ee 100644 --- a/tests/view_tests/tests/test_defaults.py +++ b/tests/view_tests/tests/test_defaults.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import datetime from django.contrib.sites.models import Site @@ -17,8 +15,11 @@ @override_settings(ROOT_URLCONF='view_tests.urls') class DefaultsTests(TestCase): """Test django views in django/views/defaults.py""" - non_existing_urls = ['/non_existing_url/', # this is in urls.py - '/other_non_existing_url/'] # this NOT in urls.py + nonexistent_urls = [ + '/nonexistent_url/', # this is in urls.py + '/other_nonexistent_url/', # this NOT in urls.py + ] + request_factory = RequestFactory() @classmethod def setUpTestData(cls): @@ -43,7 +44,7 @@ def setUpTestData(cls): def test_page_not_found(self): "A 404 status is returned by the page_not_found view" - for url in self.non_existing_urls: + for url in self.nonexistent_urls: response = self.client.get(url) self.assertEqual(response.status_code, 404) @@ -62,16 +63,22 @@ def test_csrf_token_in_404(self): The 404 page should have the csrf_token available in the context """ # See ticket #14565 - for url in self.non_existing_urls: + for url in self.nonexistent_urls: response = self.client.get(url) - self.assertNotEqual(response.content, 'NOTPROVIDED') - self.assertNotEqual(response.content, '') + self.assertNotEqual(response.content, b'NOTPROVIDED') + self.assertNotEqual(response.content, b'') def test_server_error(self): "The server_error view raises a 500 status" response = self.client.get('/server_error/') self.assertEqual(response.status_code, 500) + def test_bad_request(self): + request = self.request_factory.get('/') + response = bad_request(request, Exception()) + self.assertEqual(response.status_code, 400) + self.assertEqual(response.content, b'

        Bad Request (400)

        ') + @override_settings(TEMPLATES=[{ 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'OPTIONS': { @@ -104,30 +111,12 @@ def test_get_absolute_url_attributes(self): self.assertTrue(getattr(article.get_absolute_url, 'purge', False), 'The attributes of the original get_absolute_url must be added.') - @override_settings(DEFAULT_CONTENT_TYPE="text/xml") - def test_default_content_type_is_text_html(self): - """ - Content-Type of the default error responses is text/html. Refs #20822. - """ - response = self.client.get('/raises400/') - self.assertEqual(response['Content-Type'], 'text/html') - - response = self.client.get('/raises403/') - self.assertEqual(response['Content-Type'], 'text/html') - - response = self.client.get('/non_existing_url/') - self.assertEqual(response['Content-Type'], 'text/html') - - response = self.client.get('/server_error/') - self.assertEqual(response['Content-Type'], 'text/html') - def test_custom_templates_wrong(self): """ Default error views should raise TemplateDoesNotExist when passed a template that doesn't exist. """ - rf = RequestFactory() - request = rf.get('/') + request = self.request_factory.get('/') with self.assertRaises(TemplateDoesNotExist): bad_request(request, Exception(), template_name='nonexistent') diff --git a/tests/view_tests/tests/test_i18n.py b/tests/view_tests/tests/test_i18n.py index bde0ad42d482..a3463a1bab47 100644 --- a/tests/view_tests/tests/test_i18n.py +++ b/tests/view_tests/tests/test_i18n.py @@ -1,30 +1,25 @@ -# -*- coding:utf-8 -*- -from __future__ import unicode_literals - import gettext import json from os import path from django.conf import settings from django.test import ( - SimpleTestCase, TestCase, modify_settings, override_settings, + RequestFactory, SimpleTestCase, TestCase, modify_settings, + override_settings, ) from django.test.selenium import SeleniumTestCase -from django.test.utils import ignore_warnings from django.urls import reverse -from django.utils import six -from django.utils._os import upath -from django.utils.deprecation import RemovedInDjango20Warning from django.utils.translation import ( LANGUAGE_SESSION_KEY, get_language, override, ) +from django.views.i18n import JavaScriptCatalog, get_formats from ..urls import locale_dir @override_settings(ROOT_URLCONF='view_tests.urls') -class I18NTests(TestCase): - """ Tests django views in django/views/i18n.py """ +class SetLanguageTests(TestCase): + """Test the django.views.i18n.set_language view.""" def _get_inactive_language_code(self): """Return language code for a language which is not activated.""" @@ -38,10 +33,16 @@ def test_setlang(self): The user is redirected to the 'next' argument if provided. """ lang_code = self._get_inactive_language_code() - post_data = dict(language=lang_code, next='/') + post_data = {'language': lang_code, 'next': '/'} response = self.client.post('/i18n/setlang/', post_data, HTTP_REFERER='/i_should_not_be_used/') self.assertRedirects(response, '/') self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code) + # The language is set in a cookie. + language_cookie = self.client.cookies[settings.LANGUAGE_COOKIE_NAME] + self.assertEqual(language_cookie.value, lang_code) + self.assertEqual(language_cookie['domain'], '') + self.assertEqual(language_cookie['path'], '/') + self.assertEqual(language_cookie['max-age'], '') def test_setlang_unsafe_next(self): """ @@ -49,7 +50,7 @@ def test_setlang_unsafe_next(self): "safe". """ lang_code = self._get_inactive_language_code() - post_data = dict(language=lang_code, next='//unsafe/redirection/') + post_data = {'language': lang_code, 'next': '//unsafe/redirection/'} response = self.client.post('/i18n/setlang/', data=post_data) self.assertEqual(response.url, '/') self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code) @@ -61,7 +62,7 @@ def test_setlang_http_next(self): """ lang_code = self._get_inactive_language_code() non_https_next_url = 'http://testserver/redirection/' - post_data = dict(language=lang_code, next=non_https_next_url) + post_data = {'language': lang_code, 'next': non_https_next_url} # Insecure URL in POST data. response = self.client.post('/i18n/setlang/', data=post_data, secure=True) self.assertEqual(response.url, '/') @@ -77,7 +78,7 @@ def test_setlang_redirect_to_referer(self): there isn't a "next" parameter. """ lang_code = self._get_inactive_language_code() - post_data = dict(language=lang_code) + post_data = {'language': lang_code} response = self.client.post('/i18n/setlang/', post_data, HTTP_REFERER='/i18n/') self.assertRedirects(response, '/i18n/', fetch_redirect_response=False) self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code) @@ -88,7 +89,7 @@ def test_setlang_default_redirect(self): "next" parameter. """ lang_code = self._get_inactive_language_code() - post_data = dict(language=lang_code) + post_data = {'language': lang_code} response = self.client.post('/i18n/setlang/', post_data) self.assertRedirects(response, '/') self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code) @@ -98,7 +99,7 @@ def test_setlang_performs_redirect_for_ajax_if_explicitly_requested(self): The set_language view redirects to the "next" parameter for AJAX calls. """ lang_code = self._get_inactive_language_code() - post_data = dict(language=lang_code, next='/') + post_data = {'language': lang_code, 'next': '/'} response = self.client.post('/i18n/setlang/', post_data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') self.assertRedirects(response, '/') self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code) @@ -109,7 +110,7 @@ def test_setlang_doesnt_perform_a_redirect_to_referer_for_ajax(self): AJAX calls. """ lang_code = self._get_inactive_language_code() - post_data = dict(language=lang_code) + post_data = {'language': lang_code} headers = {'HTTP_REFERER': '/', 'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'} response = self.client.post('/i18n/setlang/', post_data, **headers) self.assertEqual(response.status_code, 204) @@ -120,7 +121,7 @@ def test_setlang_doesnt_perform_a_default_redirect_for_ajax(self): The set_language view returns 204 for AJAX calls by default. """ lang_code = self._get_inactive_language_code() - post_data = dict(language=lang_code) + post_data = {'language': lang_code} response = self.client.post('/i18n/setlang/', post_data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') self.assertEqual(response.status_code, 204) self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code) @@ -130,7 +131,7 @@ def test_setlang_unsafe_next_for_ajax(self): The fallback to root URL for the set_language view works for AJAX calls. """ lang_code = self._get_inactive_language_code() - post_data = dict(language=lang_code, next='//unsafe/redirection/') + post_data = {'language': lang_code, 'next': '//unsafe/redirection/'} response = self.client.post('/i18n/setlang/', post_data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') self.assertEqual(response.url, '/') self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code) @@ -141,36 +142,15 @@ def test_setlang_reversal(self): def test_setlang_cookie(self): # we force saving language to a cookie rather than a session # by excluding session middleware and those which do require it - test_settings = dict( - MIDDLEWARE=['django.middleware.common.CommonMiddleware'], - LANGUAGE_COOKIE_NAME='mylanguage', - LANGUAGE_COOKIE_AGE=3600 * 7 * 2, - LANGUAGE_COOKIE_DOMAIN='.example.com', - LANGUAGE_COOKIE_PATH='/test/', - ) - with self.settings(**test_settings): - post_data = dict(language='pl', next='/views/') - response = self.client.post('/i18n/setlang/', data=post_data) - language_cookie = response.cookies.get('mylanguage') - self.assertEqual(language_cookie.value, 'pl') - self.assertEqual(language_cookie['domain'], '.example.com') - self.assertEqual(language_cookie['path'], '/test/') - self.assertEqual(language_cookie['max-age'], 3600 * 7 * 2) - - @ignore_warnings(category=RemovedInDjango20Warning) - def test_setlang_cookie_middleware_classes(self): - # we force saving language to a cookie rather than a session - # by excluding session middleware and those which do require it - test_settings = dict( - MIDDLEWARE=None, - MIDDLEWARE_CLASSES=['django.middleware.common.CommonMiddleware'], - LANGUAGE_COOKIE_NAME='mylanguage', - LANGUAGE_COOKIE_AGE=3600 * 7 * 2, - LANGUAGE_COOKIE_DOMAIN='.example.com', - LANGUAGE_COOKIE_PATH='/test/', - ) + test_settings = { + 'MIDDLEWARE': ['django.middleware.common.CommonMiddleware'], + 'LANGUAGE_COOKIE_NAME': 'mylanguage', + 'LANGUAGE_COOKIE_AGE': 3600 * 7 * 2, + 'LANGUAGE_COOKIE_DOMAIN': '.example.com', + 'LANGUAGE_COOKIE_PATH': '/test/', + } with self.settings(**test_settings): - post_data = dict(language='pl', next='/views/') + post_data = {'language': 'pl', 'next': '/views/'} response = self.client.post('/i18n/setlang/', data=post_data) language_cookie = response.cookies.get('mylanguage') self.assertEqual(language_cookie.value, 'pl') @@ -182,7 +162,7 @@ def test_setlang_decodes_http_referer_url(self): """ The set_language view decodes the HTTP_REFERER URL. """ - # The url() & view must exist for this to work as a regression test. + # The URL & view must exist for this to work as a regression test. self.assertEqual(reverse('with_parameter', kwargs={'parameter': 'x'}), '/test-setlang/x/') lang_code = self._get_inactive_language_code() encoded_url = '/test-setlang/%C3%A4/' # (%C3%A4 decodes to ä) @@ -207,45 +187,26 @@ def test_lang_from_translated_i18n_pattern(self): ) self.assertRedirects(response, '/en/translated/') - @ignore_warnings(category=RemovedInDjango20Warning) - @override_settings( - MIDDLEWARE=None, - MIDDLEWARE_CLASSES=[ - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.locale.LocaleMiddleware', - ], - ) - def test_lang_from_translated_i18n_pattern_middleware_classes(self): - response = self.client.post( - '/i18n/setlang/', data={'language': 'nl'}, - follow=True, HTTP_REFERER='/en/translated/' - ) - self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], 'nl') - self.assertRedirects(response, '/nl/vertaald/') - # And reverse - response = self.client.post( - '/i18n/setlang/', data={'language': 'en'}, - follow=True, HTTP_REFERER='/nl/vertaald/' - ) - self.assertRedirects(response, '/en/translated/') - @override_settings(ROOT_URLCONF='view_tests.urls') -class JsI18NTests(SimpleTestCase): - """ - Tests views in django/views/i18n.py that need to change - settings.LANGUAGE_CODE. - """ +class I18NViewTests(SimpleTestCase): + """Test django.views.i18n views other than set_language.""" + @override_settings(LANGUAGE_CODE='de') + def test_get_formats(self): + formats = get_formats() + # Test 3 possible types in get_formats: integer, string, and list. + self.assertEqual(formats['FIRST_DAY_OF_WEEK'], 0) + self.assertEqual(formats['DECIMAL_SEPARATOR'], '.') + self.assertEqual(formats['TIME_INPUT_FORMATS'], ['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']) + def test_jsi18n(self): """The javascript_catalog can be deployed with language settings""" for lang_code in ['es', 'fr', 'ru']: with override(lang_code): catalog = gettext.translation('djangojs', locale_dir, [lang_code]) - if six.PY3: - trans_txt = catalog.gettext('this is to be translated') - else: - trans_txt = catalog.ugettext('this is to be translated') + trans_txt = catalog.gettext('this is to be translated') response = self.client.get('/jsi18n/') + self.assertEqual(response['Content-Type'], 'text/javascript; charset="utf-8"') # response content must include a line like: # "this is to be translated": # json.dumps() is used to be able to check unicode strings @@ -267,9 +228,11 @@ def test_jsoni18n(self): """ with override('de'): response = self.client.get('/jsoni18n/') - data = json.loads(response.content.decode('utf-8')) + data = json.loads(response.content.decode()) self.assertIn('catalog', data) self.assertIn('formats', data) + self.assertEqual(data['formats']['TIME_INPUT_FORMATS'], ['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']) + self.assertEqual(data['formats']['FIRST_DAY_OF_WEEK'], 0) self.assertIn('plural', data) self.assertEqual(data['catalog']['month name\x04May'], 'Mai') self.assertIn('DATETIME_FORMAT', data['formats']) @@ -296,7 +259,7 @@ def test_jsoni18n_with_missing_en_files(self): """ with self.settings(LANGUAGE_CODE='es'), override('en-us'): response = self.client.get('/jsoni18n/') - data = json.loads(response.content.decode('utf-8')) + data = json.loads(response.content.decode()) self.assertIn('catalog', data) self.assertIn('formats', data) self.assertIn('plural', data) @@ -317,13 +280,27 @@ def test_jsi18n_fallback_language(self): def test_i18n_fallback_language_plural(self): """ The fallback to a language with less plural forms maintains the real - language's number of plural forms. + language's number of plural forms and correct translations. """ with self.settings(LANGUAGE_CODE='pt'), override('ru'): response = self.client.get('/jsi18n/') self.assertEqual( response.context['catalog']['{count} plural3'], - ['{count} plural3', '{count} plural3s', '{count} plural3 p3t'] + ['{count} plural3 p3', '{count} plural3 p3s', '{count} plural3 p3t'] + ) + self.assertEqual( + response.context['catalog']['{count} plural2'], + ['{count} plural2', '{count} plural2s', ''] + ) + with self.settings(LANGUAGE_CODE='ru'), override('pt'): + response = self.client.get('/jsi18n/') + self.assertEqual( + response.context['catalog']['{count} plural3'], + ['{count} plural3', '{count} plural3s'] + ) + self.assertEqual( + response.context['catalog']['{count} plural2'], + ['{count} plural2', '{count} plural2s'] ) def test_i18n_english_variant(self): @@ -380,13 +357,6 @@ def test_non_BMP_char(self): self.assertContains(response, 'emoji') self.assertContains(response, '\\ud83d\\udca9') - -@override_settings(ROOT_URLCONF='view_tests.urls') -class JsI18NTestsMultiPackage(SimpleTestCase): - """ - Tests views in django/views/i18n.py that need to change - settings.LANGUAGE_CODE and merge JS translation from several packages. - """ @modify_settings(INSTALLED_APPS={'append': ['view_tests.app1', 'view_tests.app2']}) def test_i18n_language_english_default(self): """ @@ -425,7 +395,7 @@ def test_i18n_different_non_english_languages(self): def test_i18n_with_locale_paths(self): extended_locale_paths = settings.LOCALE_PATHS + [ path.join( - path.dirname(path.dirname(path.abspath(upath(__file__)))), + path.dirname(path.dirname(path.abspath(__file__))), 'app3', 'locale', ), @@ -435,9 +405,19 @@ def test_i18n_with_locale_paths(self): response = self.client.get('/jsi18n/') self.assertContains(response, 'este texto de app3 debe ser traducido') + def test_i18n_unknown_package_error(self): + view = JavaScriptCatalog.as_view() + request = RequestFactory().get('/') + msg = 'Invalid package(s) provided to JavaScriptCatalog: unknown_package' + with self.assertRaisesMessage(ValueError, msg): + view(request, packages='unknown_package') + msg += ',unknown_package2' + with self.assertRaisesMessage(ValueError, msg): + view(request, packages='unknown_package+unknown_package2') + @override_settings(ROOT_URLCONF='view_tests.urls') -class JavascriptI18nTests(SeleniumTestCase): +class I18nSeleniumTests(SeleniumTestCase): # The test cases use fixtures & translations from these apps. available_apps = [ @@ -455,12 +435,19 @@ def test_javascript_gettext(self): self.assertEqual(elem.text, "1 Element") elem = self.selenium.find_element_by_id("ngettext_plur") self.assertEqual(elem.text, "455 Elemente") + elem = self.selenium.find_element_by_id("ngettext_onnonplural") + self.assertEqual(elem.text, "Bild") elem = self.selenium.find_element_by_id("pgettext") self.assertEqual(elem.text, "Kann") elem = self.selenium.find_element_by_id("npgettext_sing") self.assertEqual(elem.text, "1 Resultat") elem = self.selenium.find_element_by_id("npgettext_plur") self.assertEqual(elem.text, "455 Resultate") + elem = self.selenium.find_element_by_id("formats") + self.assertEqual( + elem.text, + "DATE_INPUT_FORMATS is an object; DECIMAL_SEPARATOR is a string; FIRST_DAY_OF_WEEK is a number;" + ) @modify_settings(INSTALLED_APPS={'append': ['view_tests.app1', 'view_tests.app2']}) @override_settings(LANGUAGE_CODE='fr') diff --git a/tests/view_tests/tests/test_i18n_deprecated.py b/tests/view_tests/tests/test_i18n_deprecated.py deleted file mode 100644 index e60a04ad0138..000000000000 --- a/tests/view_tests/tests/test_i18n_deprecated.py +++ /dev/null @@ -1,244 +0,0 @@ -# -*- coding:utf-8 -*- -from __future__ import unicode_literals - -import gettext -import json -from os import path - -from django.conf import settings -from django.test import ( - SimpleTestCase, ignore_warnings, modify_settings, override_settings, -) -from django.test.selenium import SeleniumTestCase -from django.utils import six -from django.utils._os import upath -from django.utils.deprecation import RemovedInDjango20Warning -from django.utils.translation import override - -from ..urls import locale_dir - - -@override_settings(ROOT_URLCONF='view_tests.urls') -@ignore_warnings(category=RemovedInDjango20Warning) -class JsI18NTests(SimpleTestCase): - """ - Tests deprecated django views in django/views/i18n.py - """ - def test_jsi18n(self): - """The javascript_catalog can be deployed with language settings""" - for lang_code in ['es', 'fr', 'ru']: - with override(lang_code): - catalog = gettext.translation('djangojs', locale_dir, [lang_code]) - if six.PY3: - trans_txt = catalog.gettext('this is to be translated') - else: - trans_txt = catalog.ugettext('this is to be translated') - response = self.client.get('/old_jsi18n/') - # response content must include a line like: - # "this is to be translated": - # json.dumps() is used to be able to check unicode strings - self.assertContains(response, json.dumps(trans_txt), 1) - if lang_code == 'fr': - # Message with context (msgctxt) - self.assertContains(response, '"month name\\u0004May": "mai"', 1) - - def test_jsoni18n(self): - """ - The json_catalog returns the language catalog and settings as JSON. - """ - with override('de'): - response = self.client.get('/old_jsoni18n/') - data = json.loads(response.content.decode('utf-8')) - self.assertIn('catalog', data) - self.assertIn('formats', data) - self.assertIn('plural', data) - self.assertEqual(data['catalog']['month name\x04May'], 'Mai') - self.assertIn('DATETIME_FORMAT', data['formats']) - self.assertEqual(data['plural'], '(n != 1)') - - def test_jsi18n_with_missing_en_files(self): - """ - The javascript_catalog shouldn't load the fallback language in the - case that the current selected language is actually the one translated - from, and hence missing translation files completely. - - This happens easily when you're translating from English to other - languages and you've set settings.LANGUAGE_CODE to some other language - than English. - """ - with self.settings(LANGUAGE_CODE='es'), override('en-us'): - response = self.client.get('/old_jsi18n/') - self.assertNotContains(response, 'esto tiene que ser traducido') - - def test_jsoni18n_with_missing_en_files(self): - """ - Same as above for the json_catalog view. Here we also check for the - expected JSON format. - """ - with self.settings(LANGUAGE_CODE='es'), override('en-us'): - response = self.client.get('/old_jsoni18n/') - data = json.loads(response.content.decode('utf-8')) - self.assertIn('catalog', data) - self.assertIn('formats', data) - self.assertIn('plural', data) - self.assertEqual(data['catalog'], {}) - self.assertIn('DATETIME_FORMAT', data['formats']) - self.assertIsNone(data['plural']) - - def test_jsi18n_fallback_language(self): - """ - Let's make sure that the fallback language is still working properly - in cases where the selected language cannot be found. - """ - with self.settings(LANGUAGE_CODE='fr'), override('fi'): - response = self.client.get('/old_jsi18n/') - self.assertContains(response, 'il faut le traduire') - self.assertNotContains(response, "Untranslated string") - - def test_i18n_fallback_language_plural(self): - """ - The fallback to a language with less plural forms maintains the real - language's number of plural forms. - """ - with self.settings(LANGUAGE_CODE='pt'), override('ru'): - response = self.client.get('/jsi18n/') - self.assertEqual( - response.context['catalog']['{count} plural3'], - ['{count} plural3', '{count} plural3s', '{count} plural3 p3t'] - ) - - def test_i18n_english_variant(self): - with override('en-gb'): - response = self.client.get('/old_jsi18n/') - self.assertIn( - '"this color is to be translated": "this colour is to be translated"', - response.context['catalog_str'] - ) - - def test_i18n_language_non_english_default(self): - """ - Check if the Javascript i18n view returns an empty language catalog - if the default language is non-English, the selected language - is English and there is not 'en' translation available. See #13388, - #3594 and #13726 for more details. - """ - with self.settings(LANGUAGE_CODE='fr'), override('en-us'): - response = self.client.get('/old_jsi18n/') - self.assertNotContains(response, 'Choisir une heure') - - @modify_settings(INSTALLED_APPS={'append': 'view_tests.app0'}) - def test_non_english_default_english_userpref(self): - """ - Same as above with the difference that there IS an 'en' translation - available. The Javascript i18n view must return a NON empty language catalog - with the proper English translations. See #13726 for more details. - """ - with self.settings(LANGUAGE_CODE='fr'), override('en-us'): - response = self.client.get('/old_jsi18n_english_translation/') - self.assertContains(response, 'this app0 string is to be translated') - - def test_i18n_language_non_english_fallback(self): - """ - Makes sure that the fallback language is still working properly - in cases where the selected language cannot be found. - """ - with self.settings(LANGUAGE_CODE='fr'), override('none'): - response = self.client.get('/old_jsi18n/') - self.assertContains(response, 'Choisir une heure') - - def test_escaping(self): - # Force a language via GET otherwise the gettext functions are a noop! - response = self.client.get('/old_jsi18n_admin/?language=de') - self.assertContains(response, '\\x04') - - @modify_settings(INSTALLED_APPS={'append': ['view_tests.app5']}) - def test_non_BMP_char(self): - """ - Non-BMP characters should not break the javascript_catalog (#21725). - """ - with self.settings(LANGUAGE_CODE='en-us'), override('fr'): - response = self.client.get('/old_jsi18n/app5/') - self.assertContains(response, 'emoji') - self.assertContains(response, '\\ud83d\\udca9') - - -@override_settings(ROOT_URLCONF='view_tests.urls') -@ignore_warnings(category=RemovedInDjango20Warning) -class JsI18NTestsMultiPackage(SimpleTestCase): - """ - Tests for django views in django/views/i18n.py that need to change - settings.LANGUAGE_CODE and merge JS translation from several packages. - """ - @modify_settings(INSTALLED_APPS={'append': ['view_tests.app1', 'view_tests.app2']}) - def test_i18n_language_english_default(self): - """ - Check if the JavaScript i18n view returns a complete language catalog - if the default language is en-us, the selected language has a - translation available and a catalog composed by djangojs domain - translations of multiple Python packages is requested. See #13388, - #3594 and #13514 for more details. - """ - with self.settings(LANGUAGE_CODE='en-us'), override('fr'): - response = self.client.get('/old_jsi18n_multi_packages1/') - self.assertContains(response, 'il faut traduire cette cha\\u00eene de caract\\u00e8res de app1') - - @modify_settings(INSTALLED_APPS={'append': ['view_tests.app3', 'view_tests.app4']}) - def test_i18n_different_non_english_languages(self): - """ - Similar to above but with neither default or requested language being - English. - """ - with self.settings(LANGUAGE_CODE='fr'), override('es-ar'): - response = self.client.get('/old_jsi18n_multi_packages2/') - self.assertContains(response, 'este texto de app3 debe ser traducido') - - def test_i18n_with_locale_paths(self): - extended_locale_paths = settings.LOCALE_PATHS + [ - path.join( - path.dirname(path.dirname(path.abspath(upath(__file__)))), - 'app3', - 'locale', - ), - ] - with self.settings(LANGUAGE_CODE='es-ar', LOCALE_PATHS=extended_locale_paths): - with override('es-ar'): - response = self.client.get('/old_jsi18n/') - self.assertContains(response, 'este texto de app3 debe ser traducido') - - -@override_settings(ROOT_URLCONF='view_tests.urls') -@ignore_warnings(category=RemovedInDjango20Warning) -class JavascriptI18nTests(SeleniumTestCase): - - # The test cases use fixtures & translations from these apps. - available_apps = [ - 'django.contrib.admin', 'django.contrib.auth', - 'django.contrib.contenttypes', 'view_tests', - ] - - @override_settings(LANGUAGE_CODE='de') - def test_javascript_gettext(self): - self.selenium.get('%s%s' % (self.live_server_url, '/old_jsi18n_template/')) - - elem = self.selenium.find_element_by_id("gettext") - self.assertEqual(elem.text, "Entfernen") - elem = self.selenium.find_element_by_id("ngettext_sing") - self.assertEqual(elem.text, "1 Element") - elem = self.selenium.find_element_by_id("ngettext_plur") - self.assertEqual(elem.text, "455 Elemente") - elem = self.selenium.find_element_by_id("pgettext") - self.assertEqual(elem.text, "Kann") - elem = self.selenium.find_element_by_id("npgettext_sing") - self.assertEqual(elem.text, "1 Resultat") - elem = self.selenium.find_element_by_id("npgettext_plur") - self.assertEqual(elem.text, "455 Resultate") - - @modify_settings(INSTALLED_APPS={'append': ['view_tests.app1', 'view_tests.app2']}) - @override_settings(LANGUAGE_CODE='fr') - def test_multiple_catalogs(self): - self.selenium.get('%s%s' % (self.live_server_url, '/old_jsi18n_multi_catalogs/')) - - elem = self.selenium.find_element_by_id('app1string') - self.assertEqual(elem.text, 'il faut traduire cette chaîne de caractères de app1') - elem = self.selenium.find_element_by_id('app2string') - self.assertEqual(elem.text, 'il faut traduire cette chaîne de caractères de app2') diff --git a/tests/view_tests/tests/test_json.py b/tests/view_tests/tests/test_json.py index 0d6bc09120e2..34e8fa359bb2 100644 --- a/tests/view_tests/tests/test_json.py +++ b/tests/view_tests/tests/test_json.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import json from django.test import SimpleTestCase, override_settings diff --git a/tests/view_tests/tests/test_specials.py b/tests/view_tests/tests/test_specials.py index 8ef766e86f0f..70ffb1d23ec2 100644 --- a/tests/view_tests/tests/test_specials.py +++ b/tests/view_tests/tests/test_specials.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.test import SimpleTestCase, override_settings diff --git a/tests/view_tests/tests/test_static.py b/tests/view_tests/tests/test_static.py index 457577264e4a..f4c58e061191 100644 --- a/tests/view_tests/tests/test_static.py +++ b/tests/view_tests/tests/test_static.py @@ -1,10 +1,10 @@ -from __future__ import unicode_literals - import mimetypes import unittest from os import path +from urllib.parse import quote from django.conf.urls.static import static +from django.core.exceptions import ImproperlyConfigured from django.http import FileResponse, HttpResponseNotModified from django.test import SimpleTestCase, override_settings from django.utils.http import http_date @@ -22,9 +22,9 @@ class StaticTests(SimpleTestCase): def test_serve(self): "The static view can serve static media" - media_files = ['file.txt', 'file.txt.gz'] + media_files = ['file.txt', 'file.txt.gz', '%2F.txt'] for filename in media_files: - response = self.client.get('/%s/%s' % (self.prefix, filename)) + response = self.client.get('/%s/%s' % (self.prefix, quote(filename))) response_content = b''.join(response) file_path = path.join(media_dir, filename) with open(file_path, 'rb') as fp: @@ -105,12 +105,34 @@ def test_invalid_if_modified_since2(self): self.assertEqual(len(response_content), int(response['Content-Length'])) def test_404(self): - response = self.client.get('/%s/non_existing_resource' % self.prefix) + response = self.client.get('/%s/nonexistent_resource' % self.prefix) self.assertEqual(404, response.status_code) def test_index(self): response = self.client.get('/%s/' % self.prefix) - self.assertContains(response, 'Index of /') + self.assertContains(response, 'Index of ./') + # Directories have a trailing slash. + self.assertIn('subdir/', response.context['file_list']) + + def test_index_subdir(self): + response = self.client.get('/%s/subdir/' % self.prefix) + self.assertContains(response, 'Index of subdir/') + # File with a leading dot (e.g. .hidden) aren't displayed. + self.assertEqual(response.context['file_list'], ['visible']) + + @override_settings(TEMPLATES=[{ + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'OPTIONS': { + 'loaders': [ + ('django.template.loaders.locmem.Loader', { + 'static/directory_index.html': 'Test index', + }), + ], + }, + }]) + def test_index_custom_template(self): + response = self.client.get('/%s/' % self.prefix) + self.assertEqual(response.content, b'Test index') class StaticHelperTest(StaticTests): @@ -118,14 +140,31 @@ class StaticHelperTest(StaticTests): Test case to make sure the static URL pattern helper works as expected """ def setUp(self): - super(StaticHelperTest, self).setUp() + super().setUp() self._old_views_urlpatterns = urls.urlpatterns[:] urls.urlpatterns += static('/media/', document_root=media_dir) def tearDown(self): - super(StaticHelperTest, self).tearDown() + super().tearDown() urls.urlpatterns = self._old_views_urlpatterns + def test_prefix(self): + self.assertEqual(static('test')[0].pattern.regex.pattern, '^test(?P.*)$') + + @override_settings(DEBUG=False) + def test_debug_off(self): + """No URLs are served if DEBUG=False.""" + self.assertEqual(static('test'), []) + + def test_empty_prefix(self): + with self.assertRaisesMessage(ImproperlyConfigured, 'Empty static prefix not permitted'): + static('') + + def test_special_prefix(self): + """No URLs are served if prefix contains a netloc part.""" + self.assertEqual(static('http://example.org'), []) + self.assertEqual(static('//example.org'), []) + class StaticUtilsTests(unittest.TestCase): def test_was_modified_since_fp(self): diff --git a/tests/view_tests/urls.py b/tests/view_tests/urls.py index 8d2e923a9395..34415b06e0af 100644 --- a/tests/view_tests/urls.py +++ b/tests/view_tests/urls.py @@ -1,123 +1,70 @@ -# -*- coding: utf-8 -*- +import os from functools import partial -from os import path -from django.conf.urls import include, url from django.conf.urls.i18n import i18n_patterns -from django.utils._os import upath -from django.utils.translation import ugettext_lazy as _ +from django.urls import include, path, re_path +from django.utils.translation import gettext_lazy as _ from django.views import defaults, i18n, static from . import views -base_dir = path.dirname(path.abspath(upath(__file__))) -media_dir = path.join(base_dir, 'media') -locale_dir = path.join(base_dir, 'locale') - -js_info_dict = { - 'domain': 'djangojs', - 'packages': ('view_tests',), -} - -js_info_dict_english_translation = { - 'domain': 'djangojs', - 'packages': ('view_tests.app0',), -} - -js_info_dict_multi_packages1 = { - 'domain': 'djangojs', - 'packages': ('view_tests.app1', 'view_tests.app2'), -} - -js_info_dict_multi_packages2 = { - 'domain': 'djangojs', - 'packages': ('view_tests.app3', 'view_tests.app4'), -} - -js_info_dict_admin = { - 'domain': 'djangojs', - 'packages': ('django.contrib.admin', 'view_tests'), -} - -js_info_dict_app1 = { - 'domain': 'djangojs', - 'packages': ('view_tests.app1',), -} - -js_info_dict_app2 = { - 'domain': 'djangojs', - 'packages': ('view_tests.app2',), -} - -js_info_dict_app5 = { - 'domain': 'djangojs', - 'packages': ('view_tests.app5',), -} +base_dir = os.path.dirname(os.path.abspath(__file__)) +media_dir = os.path.join(base_dir, 'media') +locale_dir = os.path.join(base_dir, 'locale') urlpatterns = [ - url(r'^$', views.index_page), + path('', views.index_page), # Default views - url(r'^non_existing_url/', partial(defaults.page_not_found, exception=None)), - url(r'^server_error/', defaults.server_error), + path('nonexistent_url/', partial(defaults.page_not_found, exception=None)), + path('server_error/', defaults.server_error), # a view that raises an exception for the debug view - url(r'raises/$', views.raises), - - url(r'raises400/$', views.raises400), - url(r'raises403/$', views.raises403), - url(r'raises404/$', views.raises404), - url(r'raises500/$', views.raises500), + path('raises/', views.raises), - url(r'technical404/$', views.technical404, name="my404"), - url(r'classbased404/$', views.Http404View.as_view()), + path('raises400/', views.raises400), + path('raises403/', views.raises403), + path('raises404/', views.raises404), + path('raises500/', views.raises500), - # deprecated i18n views - url(r'^old_jsi18n/$', i18n.javascript_catalog, js_info_dict), - url(r'^old_jsi18n/app1/$', i18n.javascript_catalog, js_info_dict_app1), - url(r'^old_jsi18n/app2/$', i18n.javascript_catalog, js_info_dict_app2), - url(r'^old_jsi18n/app5/$', i18n.javascript_catalog, js_info_dict_app5), - url(r'^old_jsi18n_english_translation/$', i18n.javascript_catalog, js_info_dict_english_translation), - url(r'^old_jsi18n_multi_packages1/$', i18n.javascript_catalog, js_info_dict_multi_packages1), - url(r'^old_jsi18n_multi_packages2/$', i18n.javascript_catalog, js_info_dict_multi_packages2), - url(r'^old_jsi18n_admin/$', i18n.javascript_catalog, js_info_dict_admin), - url(r'^old_jsi18n_template/$', views.old_jsi18n), - url(r'^old_jsi18n_multi_catalogs/$', views.old_jsi18n_multi_catalogs), - url(r'^old_jsoni18n/$', i18n.json_catalog, js_info_dict), + path('technical404/', views.technical404, name='my404'), + path('classbased404/', views.Http404View.as_view()), # i18n views - url(r'^i18n/', include('django.conf.urls.i18n')), - url(r'^jsi18n/$', i18n.JavaScriptCatalog.as_view(packages=['view_tests'])), - url(r'^jsi18n/app1/$', i18n.JavaScriptCatalog.as_view(packages=['view_tests.app1'])), - url(r'^jsi18n/app2/$', i18n.JavaScriptCatalog.as_view(packages=['view_tests.app2'])), - url(r'^jsi18n/app5/$', i18n.JavaScriptCatalog.as_view(packages=['view_tests.app5'])), - url(r'^jsi18n_english_translation/$', i18n.JavaScriptCatalog.as_view(packages=['view_tests.app0'])), - url(r'^jsi18n_multi_packages1/$', - i18n.JavaScriptCatalog.as_view(packages=['view_tests.app1', 'view_tests.app2'])), - url(r'^jsi18n_multi_packages2/$', - i18n.JavaScriptCatalog.as_view(packages=['view_tests.app3', 'view_tests.app4'])), - url(r'^jsi18n_admin/$', - i18n.JavaScriptCatalog.as_view(packages=['django.contrib.admin', 'view_tests'])), - url(r'^jsi18n_template/$', views.jsi18n), - url(r'^jsi18n_multi_catalogs/$', views.jsi18n_multi_catalogs), - url(r'^jsoni18n/$', i18n.JSONCatalog.as_view(packages=['view_tests'])), + path('i18n/', include('django.conf.urls.i18n')), + path('jsi18n/', i18n.JavaScriptCatalog.as_view(packages=['view_tests'])), + path('jsi18n/app1/', i18n.JavaScriptCatalog.as_view(packages=['view_tests.app1'])), + path('jsi18n/app2/', i18n.JavaScriptCatalog.as_view(packages=['view_tests.app2'])), + path('jsi18n/app5/', i18n.JavaScriptCatalog.as_view(packages=['view_tests.app5'])), + path('jsi18n_english_translation/', i18n.JavaScriptCatalog.as_view(packages=['view_tests.app0'])), + path('jsi18n_multi_packages1/', + i18n.JavaScriptCatalog.as_view(packages=['view_tests.app1', 'view_tests.app2'])), + path('jsi18n_multi_packages2/', + i18n.JavaScriptCatalog.as_view(packages=['view_tests.app3', 'view_tests.app4'])), + path('jsi18n_admin/', + i18n.JavaScriptCatalog.as_view(packages=['django.contrib.admin', 'view_tests'])), + path('jsi18n_template/', views.jsi18n), + path('jsi18n_multi_catalogs/', views.jsi18n_multi_catalogs), + path('jsoni18n/', i18n.JSONCatalog.as_view(packages=['view_tests'])), # Static views - url(r'^site_media/(?P.*)$', static.serve, {'document_root': media_dir, 'show_indexes': True}), + re_path(r'^site_media/(?P.*)$', static.serve, {'document_root': media_dir, 'show_indexes': True}), ] urlpatterns += i18n_patterns( - url(_(r'^translated/$'), views.index_page, name='i18n_prefixed'), + re_path(_(r'^translated/$'), views.index_page, name='i18n_prefixed'), ) urlpatterns += [ - url(r'view_exception/(?P[0-9]+)/$', views.view_exception, name='view_exception'), - url(r'template_exception/(?P[0-9]+)/$', views.template_exception, name='template_exception'), - url( - r'^raises_template_does_not_exist/(?P.+)$', + path('template_exception/', views.template_exception, name='template_exception'), + path( + 'raises_template_does_not_exist/', views.raises_template_does_not_exist, name='raises_template_does_not_exist' ), - url(r'^render_no_template/$', views.render_no_template, name='render_no_template'), - url(r'^test-setlang/(?P[^/]+)/$', views.with_parameter, name='with_parameter'), + path('render_no_template/', views.render_no_template, name='render_no_template'), + re_path(r'^test-setlang/(?P[^/]+)/$', views.with_parameter, name='with_parameter'), + # Patterns to test the technical 404. + re_path(r'^regex-post/(?P[0-9]+)/$', views.index_page, name='regex-post'), + path('path-post//', views.index_page, name='path-post'), ] diff --git a/tests/view_tests/views.py b/tests/view_tests/views.py index bfcdc20a1380..ce0079a355d4 100644 --- a/tests/view_tests/views.py +++ b/tests/view_tests/views.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import datetime import decimal import logging @@ -18,8 +16,6 @@ sensitive_post_parameters, sensitive_variables, ) -from . import BrokenException, except_args - def index_page(request): """Dummy index page""" @@ -72,30 +68,18 @@ def get(self, request): raise Http404("Testing class-based technical 404.") -def view_exception(request, n): - raise BrokenException(except_args[int(n)]) - - -def template_exception(request, n): - return render(request, 'debug/template_exception.html', {'arg': except_args[int(n)]}) +def template_exception(request): + return render(request, 'debug/template_exception.html') def jsi18n(request): return render(request, 'jsi18n.html') -def old_jsi18n(request): - return render(request, 'old_jsi18n.html') - - def jsi18n_multi_catalogs(request): return render(request, 'jsi18n-multi-catalogs.html') -def old_jsi18n_multi_catalogs(request): - return render(request, 'old_jsi18n-multi-catalogs.html') - - def raises_template_does_not_exist(request, path='i_dont_exist.html'): # We need to inspect the HTML generated by the fancy 500 debug view but # the test client ignores it, so we send it explicitly. @@ -243,7 +227,7 @@ def custom_exception_reporter_filter_view(request): return technical_500_response(request, *exc_info) -class Klass(object): +class Klass: @sensitive_variables('sauce') def method(self, request): diff --git a/tests/wsgi/tests.py b/tests/wsgi/tests.py index e07f8d247833..f8ee08e3876a 100644 --- a/tests/wsgi/tests.py +++ b/tests/wsgi/tests.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.core.exceptions import ImproperlyConfigured from django.core.servers.basehttp import get_internal_wsgi_application from django.core.signals import request_started @@ -11,6 +9,7 @@ @override_settings(ROOT_URLCONF='wsgi.urls') class WSGITest(SimpleTestCase): + request_factory = RequestFactory() def setUp(self): request_started.disconnect(close_old_connections) @@ -24,7 +23,7 @@ def test_get_wsgi_application(self): """ application = get_wsgi_application() - environ = RequestFactory()._base_environ( + environ = self.request_factory._base_environ( PATH_INFO="/", CONTENT_TYPE="text/html; charset=utf-8", REQUEST_METHOD="GET" @@ -51,11 +50,11 @@ def test_file_wrapper(self): """ FileResponse uses wsgi.file_wrapper. """ - class FileWrapper(object): + class FileWrapper: def __init__(self, filelike, blksize=8192): filelike.close() application = get_wsgi_application() - environ = RequestFactory()._base_environ( + environ = self.request_factory._base_environ( PATH_INFO='/file/', REQUEST_METHOD='GET', **{'wsgi.file_wrapper': FileWrapper} diff --git a/tests/wsgi/urls.py b/tests/wsgi/urls.py index 6d4a3b6b4aa8..57e1ebf01cb8 100644 --- a/tests/wsgi/urls.py +++ b/tests/wsgi/urls.py @@ -1,5 +1,5 @@ -from django.conf.urls import url from django.http import FileResponse, HttpResponse +from django.urls import path def helloworld(request): @@ -7,6 +7,6 @@ def helloworld(request): urlpatterns = [ - url("^$", helloworld), - url(r'^file/$', lambda x: FileResponse(open(__file__, 'rb'))), + path('', helloworld), + path('file/', lambda x: FileResponse(open(__file__, 'rb'))), ] diff --git a/tox.ini b/tox.ini index e9892a75a42b..fa0957d541d2 100644 --- a/tox.ini +++ b/tox.ini @@ -1,4 +1,4 @@ -# Tox (http://tox.testrun.org/) is a tool for running tests in multiple +# Tox (https://tox.readthedocs.io/) is a tool for running tests in multiple # virtualenvs. This configuration file helps to run the test suite on all # supported Python versions. To use it, "pip install tox" and then run "tox" # from this directory. @@ -11,10 +11,7 @@ envlist = docs isort -# Add environments to use default python2 and python3 installations -[testenv:py2] -basepython = python2 - +# Add environment to use the default python3 installation [testenv:py3] basepython = python3 @@ -24,8 +21,7 @@ passenv = DJANGO_SETTINGS_MODULE PYTHONPATH HOME DISPLAY setenv = PYTHONDONTWRITEBYTECODE=1 deps = - py{2,27}: -rtests/requirements/py2.txt - py{3,34,35,36}: -rtests/requirements/py3.txt + py{3,35,36,37,38}: -rtests/requirements/py3.txt postgres: -rtests/requirements/postgres.txt mysql: -rtests/requirements/mysql.txt oracle: -rtests/requirements/oracle.txt @@ -41,8 +37,7 @@ changedir = {toxinidir} commands = flake8 . [testenv:docs] -# On OS X, as of pyenchant 1.6.6, the docs build only works under Python 2. -basepython = python2 +basepython = python3 usedevelop = false whitelist_externals = make