supression de venv
@@ -1 +0,0 @@
|
||||
pip
|
@@ -1,27 +0,0 @@
|
||||
Copyright (c) Django Software Foundation and individual contributors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of Django nor the names of its contributors may be used
|
||||
to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@@ -1,290 +0,0 @@
|
||||
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
|
||||
==========================
|
||||
|
||||
Python was created in the early 1990s by Guido van Rossum at Stichting
|
||||
Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
|
||||
as a successor of a language called ABC. Guido remains Python's
|
||||
principal author, although it includes many contributions from others.
|
||||
|
||||
In 1995, Guido continued his work on Python at the Corporation for
|
||||
National Research Initiatives (CNRI, see http://www.cnri.reston.va.us)
|
||||
in Reston, Virginia where he released several versions of the
|
||||
software.
|
||||
|
||||
In May 2000, Guido and the Python core development team moved to
|
||||
BeOpen.com to form the BeOpen PythonLabs team. In October of the same
|
||||
year, the PythonLabs team moved to Digital Creations, which became
|
||||
Zope Corporation. In 2001, the Python Software Foundation (PSF, see
|
||||
https://www.python.org/psf/) was formed, a non-profit organization
|
||||
created specifically to own Python-related Intellectual Property.
|
||||
Zope Corporation was a sponsoring member of the PSF.
|
||||
|
||||
All Python releases are Open Source (see http://www.opensource.org for
|
||||
the Open Source Definition). Historically, most, but not all, Python
|
||||
releases have also been GPL-compatible; the table below summarizes
|
||||
the various releases.
|
||||
|
||||
Release Derived Year Owner GPL-
|
||||
from compatible? (1)
|
||||
|
||||
0.9.0 thru 1.2 1991-1995 CWI yes
|
||||
1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
|
||||
1.6 1.5.2 2000 CNRI no
|
||||
2.0 1.6 2000 BeOpen.com no
|
||||
1.6.1 1.6 2001 CNRI yes (2)
|
||||
2.1 2.0+1.6.1 2001 PSF no
|
||||
2.0.1 2.0+1.6.1 2001 PSF yes
|
||||
2.1.1 2.1+2.0.1 2001 PSF yes
|
||||
2.1.2 2.1.1 2002 PSF yes
|
||||
2.1.3 2.1.2 2002 PSF yes
|
||||
2.2 and above 2.1.1 2001-now PSF yes
|
||||
|
||||
Footnotes:
|
||||
|
||||
(1) GPL-compatible doesn't mean that we're distributing Python under
|
||||
the GPL. All Python licenses, unlike the GPL, let you distribute
|
||||
a modified version without making your changes open source. The
|
||||
GPL-compatible licenses make it possible to combine Python with
|
||||
other software that is released under the GPL; the others don't.
|
||||
|
||||
(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
|
||||
because its license has a choice of law clause. According to
|
||||
CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
|
||||
is "not incompatible" with the GPL.
|
||||
|
||||
Thanks to the many outside volunteers who have worked under Guido's
|
||||
direction to make these releases possible.
|
||||
|
||||
|
||||
B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
|
||||
===============================================================
|
||||
|
||||
Python software and documentation are licensed under the
|
||||
Python Software Foundation License Version 2.
|
||||
|
||||
Starting with Python 3.8.6, examples, recipes, and other code in
|
||||
the documentation are dual licensed under the PSF License Version 2
|
||||
and the Zero-Clause BSD license.
|
||||
|
||||
Some software incorporated into Python is under different licenses.
|
||||
The licenses are listed with code falling under that license.
|
||||
|
||||
|
||||
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
||||
--------------------------------------------
|
||||
|
||||
1. This LICENSE AGREEMENT is between the Python Software Foundation
|
||||
("PSF"), and the Individual or Organization ("Licensee") accessing and
|
||||
otherwise using this software ("Python") in source or binary form and
|
||||
its associated documentation.
|
||||
|
||||
2. Subject to the terms and conditions of this License Agreement, PSF hereby
|
||||
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
|
||||
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, 2017, 2018, 2019, 2020, 2021 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
|
||||
the derivative work available to others as provided herein, then
|
||||
Licensee hereby agrees to include in any such work a brief summary of
|
||||
the changes made to Python.
|
||||
|
||||
4. PSF is making Python available to Licensee on an "AS IS"
|
||||
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||||
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||||
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
|
||||
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
6. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
7. Nothing in this License Agreement shall be deemed to create any
|
||||
relationship of agency, partnership, or joint venture between PSF and
|
||||
Licensee. This License Agreement does not grant permission to use PSF
|
||||
trademarks or trade name in a trademark sense to endorse or promote
|
||||
products or services of Licensee, or any third party.
|
||||
|
||||
8. By copying, installing or otherwise using Python, Licensee
|
||||
agrees to be bound by the terms and conditions of this License
|
||||
Agreement.
|
||||
|
||||
|
||||
BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
|
||||
-------------------------------------------
|
||||
|
||||
BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
|
||||
|
||||
1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
|
||||
office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
|
||||
Individual or Organization ("Licensee") accessing and otherwise using
|
||||
this software in source or binary form and its associated
|
||||
documentation ("the Software").
|
||||
|
||||
2. Subject to the terms and conditions of this BeOpen Python License
|
||||
Agreement, BeOpen hereby grants Licensee a non-exclusive,
|
||||
royalty-free, world-wide license to reproduce, analyze, test, perform
|
||||
and/or display publicly, prepare derivative works, distribute, and
|
||||
otherwise use the Software alone or in any derivative version,
|
||||
provided, however, that the BeOpen Python License is retained in the
|
||||
Software, alone or in any derivative version prepared by Licensee.
|
||||
|
||||
3. BeOpen is making the Software available to Licensee on an "AS IS"
|
||||
basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
|
||||
SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
|
||||
AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
|
||||
DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
5. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
6. This License Agreement shall be governed by and interpreted in all
|
||||
respects by the law of the State of California, excluding conflict of
|
||||
law provisions. Nothing in this License Agreement shall be deemed to
|
||||
create any relationship of agency, partnership, or joint venture
|
||||
between BeOpen and Licensee. This License Agreement does not grant
|
||||
permission to use BeOpen trademarks or trade names in a trademark
|
||||
sense to endorse or promote products or services of Licensee, or any
|
||||
third party. As an exception, the "BeOpen Python" logos available at
|
||||
http://www.pythonlabs.com/logos.html may be used according to the
|
||||
permissions granted on that web page.
|
||||
|
||||
7. By copying, installing or otherwise using the software, Licensee
|
||||
agrees to be bound by the terms and conditions of this License
|
||||
Agreement.
|
||||
|
||||
|
||||
CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
|
||||
---------------------------------------
|
||||
|
||||
1. This LICENSE AGREEMENT is between the Corporation for National
|
||||
Research Initiatives, having an office at 1895 Preston White Drive,
|
||||
Reston, VA 20191 ("CNRI"), and the Individual or Organization
|
||||
("Licensee") accessing and otherwise using Python 1.6.1 software in
|
||||
source or binary form and its associated documentation.
|
||||
|
||||
2. Subject to the terms and conditions of this License Agreement, CNRI
|
||||
hereby grants Licensee a nonexclusive, royalty-free, world-wide
|
||||
license to reproduce, analyze, test, perform and/or display publicly,
|
||||
prepare derivative works, distribute, and otherwise use Python 1.6.1
|
||||
alone or in any derivative version, provided, however, that CNRI's
|
||||
License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
|
||||
1995-2001 Corporation for National Research Initiatives; All Rights
|
||||
Reserved" are retained in Python 1.6.1 alone or in any derivative
|
||||
version prepared by Licensee. Alternately, in lieu of CNRI's License
|
||||
Agreement, Licensee may substitute the following text (omitting the
|
||||
quotes): "Python 1.6.1 is made available subject to the terms and
|
||||
conditions in CNRI's License Agreement. This Agreement together with
|
||||
Python 1.6.1 may be located on the internet using the following
|
||||
unique, persistent identifier (known as a handle): 1895.22/1013. This
|
||||
Agreement may also be obtained from a proxy server on the internet
|
||||
using the following URL: http://hdl.handle.net/1895.22/1013".
|
||||
|
||||
3. In the event Licensee prepares a derivative work that is based on
|
||||
or incorporates Python 1.6.1 or any part thereof, and wants to make
|
||||
the derivative work available to others as provided herein, then
|
||||
Licensee hereby agrees to include in any such work a brief summary of
|
||||
the changes made to Python 1.6.1.
|
||||
|
||||
4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
|
||||
basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||||
1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||||
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
|
||||
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
6. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
7. This License Agreement shall be governed by the federal
|
||||
intellectual property law of the United States, including without
|
||||
limitation the federal copyright law, and, to the extent such
|
||||
U.S. federal law does not apply, by the law of the Commonwealth of
|
||||
Virginia, excluding Virginia's conflict of law provisions.
|
||||
Notwithstanding the foregoing, with regard to derivative works based
|
||||
on Python 1.6.1 that incorporate non-separable material that was
|
||||
previously distributed under the GNU General Public License (GPL), the
|
||||
law of the Commonwealth of Virginia shall govern this License
|
||||
Agreement only as to issues arising under or with respect to
|
||||
Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
|
||||
License Agreement shall be deemed to create any relationship of
|
||||
agency, partnership, or joint venture between CNRI and Licensee. This
|
||||
License Agreement does not grant permission to use CNRI trademarks or
|
||||
trade name in a trademark sense to endorse or promote products or
|
||||
services of Licensee, or any third party.
|
||||
|
||||
8. By clicking on the "ACCEPT" button where indicated, or by copying,
|
||||
installing or otherwise using Python 1.6.1, Licensee agrees to be
|
||||
bound by the terms and conditions of this License Agreement.
|
||||
|
||||
ACCEPT
|
||||
|
||||
|
||||
CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
|
||||
--------------------------------------------------
|
||||
|
||||
Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
|
||||
The Netherlands. All rights reserved.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software and its
|
||||
documentation for any purpose and without fee is hereby granted,
|
||||
provided that the above copyright notice appear in all copies and that
|
||||
both that copyright notice and this permission notice appear in
|
||||
supporting documentation, and that the name of Stichting Mathematisch
|
||||
Centrum or CWI not be used in advertising or publicity pertaining to
|
||||
distribution of the software without specific, written prior
|
||||
permission.
|
||||
|
||||
STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
|
||||
FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
@@ -1,97 +0,0 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: Django
|
||||
Version: 4.0
|
||||
Summary: A high-level Python web framework that encourages rapid development and clean, pragmatic design.
|
||||
Home-page: https://www.djangoproject.com/
|
||||
Author: Django Software Foundation
|
||||
Author-email: foundation@djangoproject.com
|
||||
License: BSD-3-Clause
|
||||
Project-URL: Documentation, https://docs.djangoproject.com/
|
||||
Project-URL: Release notes, https://docs.djangoproject.com/en/stable/releases/
|
||||
Project-URL: Funding, https://www.djangoproject.com/fundraising/
|
||||
Project-URL: Source, https://github.com/django/django
|
||||
Project-URL: Tracker, https://code.djangoproject.com/
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Environment :: Web Environment
|
||||
Classifier: Framework :: Django
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3 :: Only
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Topic :: Internet :: WWW/HTTP
|
||||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
||||
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
|
||||
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Requires-Python: >=3.8
|
||||
Requires-Dist: asgiref (<4,>=3.4.1)
|
||||
Requires-Dist: sqlparse (>=0.2.2)
|
||||
Requires-Dist: backports.zoneinfo ; python_version < "3.9"
|
||||
Requires-Dist: tzdata ; sys_platform == "win32"
|
||||
Provides-Extra: argon2
|
||||
Requires-Dist: argon2-cffi (>=19.1.0) ; extra == 'argon2'
|
||||
Provides-Extra: bcrypt
|
||||
Requires-Dist: bcrypt ; extra == 'bcrypt'
|
||||
|
||||
======
|
||||
Django
|
||||
======
|
||||
|
||||
Django is a high-level Python web framework that encourages rapid development
|
||||
and clean, pragmatic design. Thanks for checking it out.
|
||||
|
||||
All documentation is in the "``docs``" directory and online at
|
||||
https://docs.djangoproject.com/en/stable/. If you're just getting started,
|
||||
here's how we recommend you read the docs:
|
||||
|
||||
* First, read ``docs/intro/install.txt`` for instructions on installing Django.
|
||||
|
||||
* Next, work through the tutorials in order (``docs/intro/tutorial01.txt``,
|
||||
``docs/intro/tutorial02.txt``, etc.).
|
||||
|
||||
* If you want to set up an actual deployment server, read
|
||||
``docs/howto/deployment/index.txt`` for instructions.
|
||||
|
||||
* You'll probably want to read through the topical guides (in ``docs/topics``)
|
||||
next; from there you can jump to the HOWTOs (in ``docs/howto``) for specific
|
||||
problems, and check out the reference (``docs/ref``) for gory details.
|
||||
|
||||
* See ``docs/README`` for instructions on building an HTML version of the docs.
|
||||
|
||||
Docs are updated rigorously. If you find any problems in the docs, or think
|
||||
they should be clarified in any way, please take 30 seconds to fill out a
|
||||
ticket here: https://code.djangoproject.com/newticket
|
||||
|
||||
To get more help:
|
||||
|
||||
* Join the ``#django`` channel on ``irc.libera.chat``. Lots of helpful people
|
||||
hang out there. See https://web.libera.chat if you're new to IRC.
|
||||
|
||||
* Join the django-users mailing list, or read the archives, at
|
||||
https://groups.google.com/group/django-users.
|
||||
|
||||
To contribute to Django:
|
||||
|
||||
* Check out https://docs.djangoproject.com/en/dev/internals/contributing/ for
|
||||
information about getting involved.
|
||||
|
||||
To run Django's test suite:
|
||||
|
||||
* Follow the instructions in the "Unit tests" section of
|
||||
``docs/internals/contributing/writing-code/unit-tests.txt``, published online at
|
||||
https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/unit-tests/#running-the-unit-tests
|
||||
|
||||
Supporting the Development of Django
|
||||
====================================
|
||||
|
||||
Django's development depends on your contributions.
|
||||
|
||||
If you depend on Django, remember to support the Django Software Foundation: https://www.djangoproject.com/fundraising/
|
||||
|
||||
|
@@ -1,5 +0,0 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.34.2)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
@@ -1,3 +0,0 @@
|
||||
[console_scripts]
|
||||
django-admin = django.core.management:execute_from_command_line
|
||||
|
@@ -1 +0,0 @@
|
||||
django
|
@@ -1,170 +0,0 @@
|
||||
"""
|
||||
MySQLdb - A DB API v2.0 compatible interface to MySQL.
|
||||
|
||||
This package is a wrapper around _mysql, which mostly implements the
|
||||
MySQL C API.
|
||||
|
||||
connect() -- connects to server
|
||||
|
||||
See the C API specification and the MySQL documentation for more info
|
||||
on other items.
|
||||
|
||||
For information on how MySQLdb handles type conversion, see the
|
||||
MySQLdb.converters module.
|
||||
"""
|
||||
|
||||
try:
|
||||
from MySQLdb.release import version_info
|
||||
from . import _mysql
|
||||
|
||||
assert version_info == _mysql.version_info
|
||||
except Exception:
|
||||
raise ImportError(
|
||||
"this is MySQLdb version {}, but _mysql is version {!r}\n_mysql: {!r}".format(
|
||||
version_info, _mysql.version_info, _mysql.__file__
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
from ._mysql import (
|
||||
NotSupportedError,
|
||||
OperationalError,
|
||||
get_client_info,
|
||||
ProgrammingError,
|
||||
Error,
|
||||
InterfaceError,
|
||||
debug,
|
||||
IntegrityError,
|
||||
string_literal,
|
||||
MySQLError,
|
||||
DataError,
|
||||
DatabaseError,
|
||||
InternalError,
|
||||
Warning,
|
||||
)
|
||||
from MySQLdb.constants import FIELD_TYPE
|
||||
from MySQLdb.times import (
|
||||
Date,
|
||||
Time,
|
||||
Timestamp,
|
||||
DateFromTicks,
|
||||
TimeFromTicks,
|
||||
TimestampFromTicks,
|
||||
)
|
||||
|
||||
threadsafety = 1
|
||||
apilevel = "2.0"
|
||||
paramstyle = "format"
|
||||
|
||||
|
||||
class DBAPISet(frozenset):
|
||||
"""A special type of set for which A == x is true if A is a
|
||||
DBAPISet and x is a member of that set."""
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, DBAPISet):
|
||||
return not self.difference(other)
|
||||
return other in self
|
||||
|
||||
|
||||
STRING = DBAPISet([FIELD_TYPE.ENUM, FIELD_TYPE.STRING, FIELD_TYPE.VAR_STRING])
|
||||
BINARY = DBAPISet(
|
||||
[
|
||||
FIELD_TYPE.BLOB,
|
||||
FIELD_TYPE.LONG_BLOB,
|
||||
FIELD_TYPE.MEDIUM_BLOB,
|
||||
FIELD_TYPE.TINY_BLOB,
|
||||
]
|
||||
)
|
||||
NUMBER = DBAPISet(
|
||||
[
|
||||
FIELD_TYPE.DECIMAL,
|
||||
FIELD_TYPE.DOUBLE,
|
||||
FIELD_TYPE.FLOAT,
|
||||
FIELD_TYPE.INT24,
|
||||
FIELD_TYPE.LONG,
|
||||
FIELD_TYPE.LONGLONG,
|
||||
FIELD_TYPE.TINY,
|
||||
FIELD_TYPE.YEAR,
|
||||
FIELD_TYPE.NEWDECIMAL,
|
||||
]
|
||||
)
|
||||
DATE = DBAPISet([FIELD_TYPE.DATE])
|
||||
TIME = DBAPISet([FIELD_TYPE.TIME])
|
||||
TIMESTAMP = DBAPISet([FIELD_TYPE.TIMESTAMP, FIELD_TYPE.DATETIME])
|
||||
DATETIME = TIMESTAMP
|
||||
ROWID = DBAPISet()
|
||||
|
||||
|
||||
def test_DBAPISet_set_equality():
|
||||
assert STRING == STRING
|
||||
|
||||
|
||||
def test_DBAPISet_set_inequality():
|
||||
assert STRING != NUMBER
|
||||
|
||||
|
||||
def test_DBAPISet_set_equality_membership():
|
||||
assert FIELD_TYPE.VAR_STRING == STRING
|
||||
|
||||
|
||||
def test_DBAPISet_set_inequality_membership():
|
||||
assert FIELD_TYPE.DATE != STRING
|
||||
|
||||
|
||||
def Binary(x):
|
||||
return bytes(x)
|
||||
|
||||
|
||||
def Connect(*args, **kwargs):
|
||||
"""Factory function for connections.Connection."""
|
||||
from MySQLdb.connections import Connection
|
||||
|
||||
return Connection(*args, **kwargs)
|
||||
|
||||
|
||||
connect = Connection = Connect
|
||||
|
||||
__all__ = [
|
||||
"BINARY",
|
||||
"Binary",
|
||||
"Connect",
|
||||
"Connection",
|
||||
"DATE",
|
||||
"Date",
|
||||
"Time",
|
||||
"Timestamp",
|
||||
"DateFromTicks",
|
||||
"TimeFromTicks",
|
||||
"TimestampFromTicks",
|
||||
"DataError",
|
||||
"DatabaseError",
|
||||
"Error",
|
||||
"FIELD_TYPE",
|
||||
"IntegrityError",
|
||||
"InterfaceError",
|
||||
"InternalError",
|
||||
"MySQLError",
|
||||
"NUMBER",
|
||||
"NotSupportedError",
|
||||
"DBAPISet",
|
||||
"OperationalError",
|
||||
"ProgrammingError",
|
||||
"ROWID",
|
||||
"STRING",
|
||||
"TIME",
|
||||
"TIMESTAMP",
|
||||
"Warning",
|
||||
"apilevel",
|
||||
"connect",
|
||||
"connections",
|
||||
"constants",
|
||||
"converters",
|
||||
"cursors",
|
||||
"debug",
|
||||
"get_client_info",
|
||||
"paramstyle",
|
||||
"string_literal",
|
||||
"threadsafety",
|
||||
"version_info",
|
||||
]
|
@@ -1,69 +0,0 @@
|
||||
"""Exception classes for _mysql and MySQLdb.
|
||||
|
||||
These classes are dictated by the DB API v2.0:
|
||||
|
||||
https://www.python.org/dev/peps/pep-0249/
|
||||
"""
|
||||
|
||||
|
||||
class MySQLError(Exception):
|
||||
"""Exception related to operation with MySQL."""
|
||||
|
||||
|
||||
class Warning(Warning, MySQLError):
|
||||
"""Exception raised for important warnings like data truncations
|
||||
while inserting, etc."""
|
||||
|
||||
|
||||
class Error(MySQLError):
|
||||
"""Exception that is the base class of all other error exceptions
|
||||
(not Warning)."""
|
||||
|
||||
|
||||
class InterfaceError(Error):
|
||||
"""Exception raised for errors that are related to the database
|
||||
interface rather than the database itself."""
|
||||
|
||||
|
||||
class DatabaseError(Error):
|
||||
"""Exception raised for errors that are related to the
|
||||
database."""
|
||||
|
||||
|
||||
class DataError(DatabaseError):
|
||||
"""Exception raised for errors that are due to problems with the
|
||||
processed data like division by zero, numeric value out of range,
|
||||
etc."""
|
||||
|
||||
|
||||
class OperationalError(DatabaseError):
|
||||
"""Exception raised for errors that are related to the database's
|
||||
operation and not necessarily under the control of the programmer,
|
||||
e.g. an unexpected disconnect occurs, the data source name is not
|
||||
found, a transaction could not be processed, a memory allocation
|
||||
error occurred during processing, etc."""
|
||||
|
||||
|
||||
class IntegrityError(DatabaseError):
|
||||
"""Exception raised when the relational integrity of the database
|
||||
is affected, e.g. a foreign key check fails, duplicate key,
|
||||
etc."""
|
||||
|
||||
|
||||
class InternalError(DatabaseError):
|
||||
"""Exception raised when the database encounters an internal
|
||||
error, e.g. the cursor is not valid anymore, the transaction is
|
||||
out of sync, etc."""
|
||||
|
||||
|
||||
class ProgrammingError(DatabaseError):
|
||||
"""Exception raised for programming errors, e.g. table not found
|
||||
or already exists, syntax error in the SQL statement, wrong number
|
||||
of parameters specified, etc."""
|
||||
|
||||
|
||||
class NotSupportedError(DatabaseError):
|
||||
"""Exception raised in case a method or database API was used
|
||||
which is not supported by the database, e.g. requesting a
|
||||
.rollback() on a connection that does not support transaction or
|
||||
has transactions turned off."""
|
@@ -1,333 +0,0 @@
|
||||
"""
|
||||
This module implements connections for MySQLdb. Presently there is
|
||||
only one class: Connection. Others are unlikely. However, you might
|
||||
want to make your own subclasses. In most cases, you will probably
|
||||
override Connection.default_cursor with a non-standard Cursor class.
|
||||
"""
|
||||
import re
|
||||
|
||||
from . import cursors, _mysql
|
||||
from ._exceptions import (
|
||||
Warning,
|
||||
Error,
|
||||
InterfaceError,
|
||||
DataError,
|
||||
DatabaseError,
|
||||
OperationalError,
|
||||
IntegrityError,
|
||||
InternalError,
|
||||
NotSupportedError,
|
||||
ProgrammingError,
|
||||
)
|
||||
|
||||
# Mapping from MySQL charset name to Python codec name
|
||||
_charset_to_encoding = {
|
||||
"utf8mb4": "utf8",
|
||||
"utf8mb3": "utf8",
|
||||
"latin1": "cp1252",
|
||||
"koi8r": "koi8_r",
|
||||
"koi8u": "koi8_u",
|
||||
}
|
||||
|
||||
re_numeric_part = re.compile(r"^(\d+)")
|
||||
|
||||
|
||||
def numeric_part(s):
|
||||
"""Returns the leading numeric part of a string.
|
||||
|
||||
>>> numeric_part("20-alpha")
|
||||
20
|
||||
>>> numeric_part("foo")
|
||||
>>> numeric_part("16b")
|
||||
16
|
||||
"""
|
||||
|
||||
m = re_numeric_part.match(s)
|
||||
if m:
|
||||
return int(m.group(1))
|
||||
return None
|
||||
|
||||
|
||||
class Connection(_mysql.connection):
|
||||
"""MySQL Database Connection Object"""
|
||||
|
||||
default_cursor = cursors.Cursor
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Create a connection to the database. It is strongly recommended
|
||||
that you only use keyword parameters. Consult the MySQL C API
|
||||
documentation for more information.
|
||||
|
||||
:param str host: host to connect
|
||||
:param str user: user to connect as
|
||||
:param str password: password to use
|
||||
:param str passwd: alias of password (deprecated)
|
||||
:param str database: database to use
|
||||
:param str db: alias of database (deprecated)
|
||||
:param int port: TCP/IP port to connect to
|
||||
:param str unix_socket: location of unix_socket to use
|
||||
:param dict conv: conversion dictionary, see MySQLdb.converters
|
||||
:param int connect_timeout:
|
||||
number of seconds to wait before the connection attempt fails.
|
||||
|
||||
:param bool compress: if set, compression is enabled
|
||||
:param str named_pipe: if set, a named pipe is used to connect (Windows only)
|
||||
:param str init_command:
|
||||
command which is run once the connection is created
|
||||
|
||||
:param str read_default_file:
|
||||
file from which default client values are read
|
||||
|
||||
:param str read_default_group:
|
||||
configuration group to use from the default file
|
||||
|
||||
:param type cursorclass:
|
||||
class object, used to create cursors (keyword only)
|
||||
|
||||
:param bool use_unicode:
|
||||
If True, text-like columns are returned as unicode objects
|
||||
using the connection's character set. Otherwise, text-like
|
||||
columns are returned as bytes. Unicode objects will always
|
||||
be encoded to the connection's character set regardless of
|
||||
this setting.
|
||||
Default to True.
|
||||
|
||||
:param str charset:
|
||||
If supplied, the connection character set will be changed
|
||||
to this character set.
|
||||
|
||||
:param str auth_plugin:
|
||||
If supplied, the connection default authentication plugin will be
|
||||
changed to this value. Example values:
|
||||
`mysql_native_password` or `caching_sha2_password`
|
||||
|
||||
:param str sql_mode:
|
||||
If supplied, the session SQL mode will be changed to this
|
||||
setting.
|
||||
For more details and legal values, see the MySQL documentation.
|
||||
|
||||
:param int client_flag:
|
||||
flags to use or 0 (see MySQL docs or constants/CLIENTS.py)
|
||||
|
||||
:param bool multi_statements:
|
||||
If True, enable multi statements for clients >= 4.1.
|
||||
Defaults to True.
|
||||
|
||||
:param str ssl_mode:
|
||||
specify the security settings for connection to the server;
|
||||
see the MySQL documentation for more details
|
||||
(mysql_option(), MYSQL_OPT_SSL_MODE).
|
||||
Only one of 'DISABLED', 'PREFERRED', 'REQUIRED',
|
||||
'VERIFY_CA', 'VERIFY_IDENTITY' can be specified.
|
||||
|
||||
:param dict ssl:
|
||||
dictionary or mapping contains SSL connection parameters;
|
||||
see the MySQL documentation for more details
|
||||
(mysql_ssl_set()). If this is set, and the client does not
|
||||
support SSL, NotSupportedError will be raised.
|
||||
|
||||
:param bool local_infile:
|
||||
enables LOAD LOCAL INFILE; zero disables
|
||||
|
||||
:param bool autocommit:
|
||||
If False (default), autocommit is disabled.
|
||||
If True, autocommit is enabled.
|
||||
If None, autocommit isn't set and server default is used.
|
||||
|
||||
:param bool binary_prefix:
|
||||
If set, the '_binary' prefix will be used for raw byte query
|
||||
arguments (e.g. Binary). This is disabled by default.
|
||||
|
||||
There are a number of undocumented, non-standard methods. See the
|
||||
documentation for the MySQL C API for some hints on what they do.
|
||||
"""
|
||||
from MySQLdb.constants import CLIENT, FIELD_TYPE
|
||||
from MySQLdb.converters import conversions, _bytes_or_str
|
||||
from weakref import proxy
|
||||
|
||||
kwargs2 = kwargs.copy()
|
||||
|
||||
if "db" in kwargs2:
|
||||
kwargs2["database"] = kwargs2.pop("db")
|
||||
if "passwd" in kwargs2:
|
||||
kwargs2["password"] = kwargs2.pop("passwd")
|
||||
|
||||
if "conv" in kwargs:
|
||||
conv = kwargs["conv"]
|
||||
else:
|
||||
conv = conversions
|
||||
|
||||
conv2 = {}
|
||||
for k, v in conv.items():
|
||||
if isinstance(k, int) and isinstance(v, list):
|
||||
conv2[k] = v[:]
|
||||
else:
|
||||
conv2[k] = v
|
||||
kwargs2["conv"] = conv2
|
||||
|
||||
cursorclass = kwargs2.pop("cursorclass", self.default_cursor)
|
||||
charset = kwargs2.get("charset", "")
|
||||
use_unicode = kwargs2.pop("use_unicode", True)
|
||||
sql_mode = kwargs2.pop("sql_mode", "")
|
||||
self._binary_prefix = kwargs2.pop("binary_prefix", False)
|
||||
|
||||
client_flag = kwargs.get("client_flag", 0)
|
||||
client_flag |= CLIENT.MULTI_RESULTS
|
||||
multi_statements = kwargs2.pop("multi_statements", True)
|
||||
if multi_statements:
|
||||
client_flag |= CLIENT.MULTI_STATEMENTS
|
||||
kwargs2["client_flag"] = client_flag
|
||||
|
||||
# PEP-249 requires autocommit to be initially off
|
||||
autocommit = kwargs2.pop("autocommit", False)
|
||||
|
||||
super().__init__(*args, **kwargs2)
|
||||
self.cursorclass = cursorclass
|
||||
self.encoders = {k: v for k, v in conv.items() if type(k) is not int}
|
||||
|
||||
self._server_version = tuple(
|
||||
[numeric_part(n) for n in self.get_server_info().split(".")[:2]]
|
||||
)
|
||||
|
||||
self.encoding = "ascii" # overridden in set_character_set()
|
||||
|
||||
if not charset:
|
||||
charset = self.character_set_name()
|
||||
self.set_character_set(charset)
|
||||
|
||||
if sql_mode:
|
||||
self.set_sql_mode(sql_mode)
|
||||
|
||||
if use_unicode:
|
||||
for t in (
|
||||
FIELD_TYPE.STRING,
|
||||
FIELD_TYPE.VAR_STRING,
|
||||
FIELD_TYPE.VARCHAR,
|
||||
FIELD_TYPE.TINY_BLOB,
|
||||
FIELD_TYPE.MEDIUM_BLOB,
|
||||
FIELD_TYPE.LONG_BLOB,
|
||||
FIELD_TYPE.BLOB,
|
||||
):
|
||||
self.converter[t] = _bytes_or_str
|
||||
# Unlike other string/blob types, JSON is always text.
|
||||
# MySQL may return JSON with charset==binary.
|
||||
self.converter[FIELD_TYPE.JSON] = str
|
||||
|
||||
db = proxy(self)
|
||||
|
||||
def unicode_literal(u, dummy=None):
|
||||
return db.string_literal(u.encode(db.encoding))
|
||||
|
||||
self.encoders[str] = unicode_literal
|
||||
|
||||
self._transactional = self.server_capabilities & CLIENT.TRANSACTIONS
|
||||
if self._transactional:
|
||||
if autocommit is not None:
|
||||
self.autocommit(autocommit)
|
||||
self.messages = []
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.close()
|
||||
|
||||
def autocommit(self, on):
|
||||
on = bool(on)
|
||||
if self.get_autocommit() != on:
|
||||
_mysql.connection.autocommit(self, on)
|
||||
|
||||
def cursor(self, cursorclass=None):
|
||||
"""
|
||||
Create a cursor on which queries may be performed. The
|
||||
optional cursorclass parameter is used to create the
|
||||
Cursor. By default, self.cursorclass=cursors.Cursor is
|
||||
used.
|
||||
"""
|
||||
return (cursorclass or self.cursorclass)(self)
|
||||
|
||||
def query(self, query):
|
||||
# Since _mysql releases GIL while querying, we need immutable buffer.
|
||||
if isinstance(query, bytearray):
|
||||
query = bytes(query)
|
||||
_mysql.connection.query(self, query)
|
||||
|
||||
def _bytes_literal(self, bs):
|
||||
assert isinstance(bs, (bytes, bytearray))
|
||||
x = self.string_literal(bs) # x is escaped and quoted bytes
|
||||
if self._binary_prefix:
|
||||
return b"_binary" + x
|
||||
return x
|
||||
|
||||
def _tuple_literal(self, t):
|
||||
return b"(%s)" % (b",".join(map(self.literal, t)))
|
||||
|
||||
def literal(self, o):
|
||||
"""If o is a single object, returns an SQL literal as a string.
|
||||
If o is a non-string sequence, the items of the sequence are
|
||||
converted and returned as a sequence.
|
||||
|
||||
Non-standard. For internal use; do not use this in your
|
||||
applications.
|
||||
"""
|
||||
if isinstance(o, str):
|
||||
s = self.string_literal(o.encode(self.encoding))
|
||||
elif isinstance(o, bytearray):
|
||||
s = self._bytes_literal(o)
|
||||
elif isinstance(o, bytes):
|
||||
s = self._bytes_literal(o)
|
||||
elif isinstance(o, (tuple, list)):
|
||||
s = self._tuple_literal(o)
|
||||
else:
|
||||
s = self.escape(o, self.encoders)
|
||||
if isinstance(s, str):
|
||||
s = s.encode(self.encoding)
|
||||
assert isinstance(s, bytes)
|
||||
return s
|
||||
|
||||
def begin(self):
|
||||
"""Explicitly begin a connection.
|
||||
|
||||
This method is not used when autocommit=False (default).
|
||||
"""
|
||||
self.query(b"BEGIN")
|
||||
|
||||
def set_character_set(self, charset):
|
||||
"""Set the connection character set to charset."""
|
||||
super().set_character_set(charset)
|
||||
self.encoding = _charset_to_encoding.get(charset, charset)
|
||||
|
||||
def set_sql_mode(self, sql_mode):
|
||||
"""Set the connection sql_mode. See MySQL documentation for
|
||||
legal values."""
|
||||
if self._server_version < (4, 1):
|
||||
raise NotSupportedError("server is too old to set sql_mode")
|
||||
self.query("SET SESSION sql_mode='%s'" % sql_mode)
|
||||
self.store_result()
|
||||
|
||||
def show_warnings(self):
|
||||
"""Return detailed information about warnings as a
|
||||
sequence of tuples of (Level, Code, Message). This
|
||||
is only supported in MySQL-4.1 and up. If your server
|
||||
is an earlier version, an empty sequence is returned."""
|
||||
if self._server_version < (4, 1):
|
||||
return ()
|
||||
self.query("SHOW WARNINGS")
|
||||
r = self.store_result()
|
||||
warnings = r.fetch_row(0)
|
||||
return warnings
|
||||
|
||||
Warning = Warning
|
||||
Error = Error
|
||||
InterfaceError = InterfaceError
|
||||
DatabaseError = DatabaseError
|
||||
DataError = DataError
|
||||
OperationalError = OperationalError
|
||||
IntegrityError = IntegrityError
|
||||
InternalError = InternalError
|
||||
ProgrammingError = ProgrammingError
|
||||
NotSupportedError = NotSupportedError
|
||||
|
||||
|
||||
# vim: colorcolumn=100
|
@@ -1,27 +0,0 @@
|
||||
"""MySQL CLIENT constants
|
||||
|
||||
These constants are used when creating the connection. Use bitwise-OR
|
||||
(|) to combine options together, and pass them as the client_flags
|
||||
parameter to MySQLdb.Connection. For more information on these flags,
|
||||
see the MySQL C API documentation for mysql_real_connect().
|
||||
|
||||
"""
|
||||
|
||||
LONG_PASSWORD = 1
|
||||
FOUND_ROWS = 2
|
||||
LONG_FLAG = 4
|
||||
CONNECT_WITH_DB = 8
|
||||
NO_SCHEMA = 16
|
||||
COMPRESS = 32
|
||||
ODBC = 64
|
||||
LOCAL_FILES = 128
|
||||
IGNORE_SPACE = 256
|
||||
CHANGE_USER = 512
|
||||
INTERACTIVE = 1024
|
||||
SSL = 2048
|
||||
IGNORE_SIGPIPE = 4096
|
||||
TRANSACTIONS = 8192 # mysql_com.h was WRONG prior to 3.23.35
|
||||
RESERVED = 16384
|
||||
SECURE_CONNECTION = 32768
|
||||
MULTI_STATEMENTS = 65536
|
||||
MULTI_RESULTS = 131072
|
@@ -1,105 +0,0 @@
|
||||
"""MySQL Connection Errors
|
||||
|
||||
Nearly all of these raise OperationalError. COMMANDS_OUT_OF_SYNC
|
||||
raises ProgrammingError.
|
||||
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
"""
|
||||
Usage: python CR.py [/path/to/mysql/errmsg.h ...] >> CR.py
|
||||
"""
|
||||
import fileinput
|
||||
import re
|
||||
|
||||
data = {}
|
||||
error_last = None
|
||||
for line in fileinput.input():
|
||||
line = re.sub(r"/\*.*?\*/", "", line)
|
||||
m = re.match(r"^\s*#define\s+CR_([A-Z0-9_]+)\s+(\d+)(\s.*|$)", line)
|
||||
if m:
|
||||
name = m.group(1)
|
||||
value = int(m.group(2))
|
||||
if name == "ERROR_LAST":
|
||||
if error_last is None or error_last < value:
|
||||
error_last = value
|
||||
continue
|
||||
if value not in data:
|
||||
data[value] = set()
|
||||
data[value].add(name)
|
||||
for value, names in sorted(data.items()):
|
||||
for name in sorted(names):
|
||||
print("{} = {}".format(name, value))
|
||||
if error_last is not None:
|
||||
print("ERROR_LAST = %s" % error_last)
|
||||
|
||||
|
||||
ERROR_FIRST = 2000
|
||||
MIN_ERROR = 2000
|
||||
UNKNOWN_ERROR = 2000
|
||||
SOCKET_CREATE_ERROR = 2001
|
||||
CONNECTION_ERROR = 2002
|
||||
CONN_HOST_ERROR = 2003
|
||||
IPSOCK_ERROR = 2004
|
||||
UNKNOWN_HOST = 2005
|
||||
SERVER_GONE_ERROR = 2006
|
||||
VERSION_ERROR = 2007
|
||||
OUT_OF_MEMORY = 2008
|
||||
WRONG_HOST_INFO = 2009
|
||||
LOCALHOST_CONNECTION = 2010
|
||||
TCP_CONNECTION = 2011
|
||||
SERVER_HANDSHAKE_ERR = 2012
|
||||
SERVER_LOST = 2013
|
||||
COMMANDS_OUT_OF_SYNC = 2014
|
||||
NAMEDPIPE_CONNECTION = 2015
|
||||
NAMEDPIPEWAIT_ERROR = 2016
|
||||
NAMEDPIPEOPEN_ERROR = 2017
|
||||
NAMEDPIPESETSTATE_ERROR = 2018
|
||||
CANT_READ_CHARSET = 2019
|
||||
NET_PACKET_TOO_LARGE = 2020
|
||||
EMBEDDED_CONNECTION = 2021
|
||||
PROBE_SLAVE_STATUS = 2022
|
||||
PROBE_SLAVE_HOSTS = 2023
|
||||
PROBE_SLAVE_CONNECT = 2024
|
||||
PROBE_MASTER_CONNECT = 2025
|
||||
SSL_CONNECTION_ERROR = 2026
|
||||
MALFORMED_PACKET = 2027
|
||||
WRONG_LICENSE = 2028
|
||||
NULL_POINTER = 2029
|
||||
NO_PREPARE_STMT = 2030
|
||||
PARAMS_NOT_BOUND = 2031
|
||||
DATA_TRUNCATED = 2032
|
||||
NO_PARAMETERS_EXISTS = 2033
|
||||
INVALID_PARAMETER_NO = 2034
|
||||
INVALID_BUFFER_USE = 2035
|
||||
UNSUPPORTED_PARAM_TYPE = 2036
|
||||
SHARED_MEMORY_CONNECTION = 2037
|
||||
SHARED_MEMORY_CONNECT_REQUEST_ERROR = 2038
|
||||
SHARED_MEMORY_CONNECT_ANSWER_ERROR = 2039
|
||||
SHARED_MEMORY_CONNECT_FILE_MAP_ERROR = 2040
|
||||
SHARED_MEMORY_CONNECT_MAP_ERROR = 2041
|
||||
SHARED_MEMORY_FILE_MAP_ERROR = 2042
|
||||
SHARED_MEMORY_MAP_ERROR = 2043
|
||||
SHARED_MEMORY_EVENT_ERROR = 2044
|
||||
SHARED_MEMORY_CONNECT_ABANDONED_ERROR = 2045
|
||||
SHARED_MEMORY_CONNECT_SET_ERROR = 2046
|
||||
CONN_UNKNOW_PROTOCOL = 2047
|
||||
INVALID_CONN_HANDLE = 2048
|
||||
UNUSED_1 = 2049
|
||||
FETCH_CANCELED = 2050
|
||||
NO_DATA = 2051
|
||||
NO_STMT_METADATA = 2052
|
||||
NO_RESULT_SET = 2053
|
||||
NOT_IMPLEMENTED = 2054
|
||||
SERVER_LOST_EXTENDED = 2055
|
||||
STMT_CLOSED = 2056
|
||||
NEW_STMT_METADATA = 2057
|
||||
ALREADY_CONNECTED = 2058
|
||||
AUTH_PLUGIN_CANNOT_LOAD = 2059
|
||||
DUPLICATE_CONNECTION_ATTR = 2060
|
||||
AUTH_PLUGIN_ERR = 2061
|
||||
INSECURE_API_ERR = 2062
|
||||
FILE_NAME_TOO_LONG = 2063
|
||||
SSL_FIPS_MODE_ERR = 2064
|
||||
MAX_ERROR = 2999
|
||||
ERROR_LAST = 2064
|
@@ -1,827 +0,0 @@
|
||||
"""MySQL ER Constants
|
||||
|
||||
These constants are error codes for the bulk of the error conditions
|
||||
that may occur.
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
"""
|
||||
Usage: python ER.py [/path/to/mysql/mysqld_error.h ...] >> ER.py
|
||||
"""
|
||||
import fileinput
|
||||
import re
|
||||
|
||||
data = {}
|
||||
error_last = None
|
||||
for line in fileinput.input():
|
||||
line = re.sub(r"/\*.*?\*/", "", line)
|
||||
m = re.match(r"^\s*#define\s+((ER|WARN)_[A-Z0-9_]+)\s+(\d+)\s*", line)
|
||||
if m:
|
||||
name = m.group(1)
|
||||
if name.startswith("ER_"):
|
||||
name = name[3:]
|
||||
value = int(m.group(3))
|
||||
if name == "ERROR_LAST":
|
||||
if error_last is None or error_last < value:
|
||||
error_last = value
|
||||
continue
|
||||
if value not in data:
|
||||
data[value] = set()
|
||||
data[value].add(name)
|
||||
for value, names in sorted(data.items()):
|
||||
for name in sorted(names):
|
||||
print("{} = {}".format(name, value))
|
||||
if error_last is not None:
|
||||
print("ERROR_LAST = %s" % error_last)
|
||||
|
||||
|
||||
ERROR_FIRST = 1000
|
||||
NO = 1002
|
||||
YES = 1003
|
||||
CANT_CREATE_FILE = 1004
|
||||
CANT_CREATE_TABLE = 1005
|
||||
CANT_CREATE_DB = 1006
|
||||
DB_CREATE_EXISTS = 1007
|
||||
DB_DROP_EXISTS = 1008
|
||||
DB_DROP_RMDIR = 1010
|
||||
CANT_FIND_SYSTEM_REC = 1012
|
||||
CANT_GET_STAT = 1013
|
||||
CANT_LOCK = 1015
|
||||
CANT_OPEN_FILE = 1016
|
||||
FILE_NOT_FOUND = 1017
|
||||
CANT_READ_DIR = 1018
|
||||
CHECKREAD = 1020
|
||||
DUP_KEY = 1022
|
||||
ERROR_ON_READ = 1024
|
||||
ERROR_ON_RENAME = 1025
|
||||
ERROR_ON_WRITE = 1026
|
||||
FILE_USED = 1027
|
||||
FILSORT_ABORT = 1028
|
||||
GET_ERRNO = 1030
|
||||
ILLEGAL_HA = 1031
|
||||
KEY_NOT_FOUND = 1032
|
||||
NOT_FORM_FILE = 1033
|
||||
NOT_KEYFILE = 1034
|
||||
OLD_KEYFILE = 1035
|
||||
OPEN_AS_READONLY = 1036
|
||||
OUTOFMEMORY = 1037
|
||||
OUT_OF_SORTMEMORY = 1038
|
||||
CON_COUNT_ERROR = 1040
|
||||
OUT_OF_RESOURCES = 1041
|
||||
BAD_HOST_ERROR = 1042
|
||||
HANDSHAKE_ERROR = 1043
|
||||
DBACCESS_DENIED_ERROR = 1044
|
||||
ACCESS_DENIED_ERROR = 1045
|
||||
NO_DB_ERROR = 1046
|
||||
UNKNOWN_COM_ERROR = 1047
|
||||
BAD_NULL_ERROR = 1048
|
||||
BAD_DB_ERROR = 1049
|
||||
TABLE_EXISTS_ERROR = 1050
|
||||
BAD_TABLE_ERROR = 1051
|
||||
NON_UNIQ_ERROR = 1052
|
||||
SERVER_SHUTDOWN = 1053
|
||||
BAD_FIELD_ERROR = 1054
|
||||
WRONG_FIELD_WITH_GROUP = 1055
|
||||
WRONG_GROUP_FIELD = 1056
|
||||
WRONG_SUM_SELECT = 1057
|
||||
WRONG_VALUE_COUNT = 1058
|
||||
TOO_LONG_IDENT = 1059
|
||||
DUP_FIELDNAME = 1060
|
||||
DUP_KEYNAME = 1061
|
||||
DUP_ENTRY = 1062
|
||||
WRONG_FIELD_SPEC = 1063
|
||||
PARSE_ERROR = 1064
|
||||
EMPTY_QUERY = 1065
|
||||
NONUNIQ_TABLE = 1066
|
||||
INVALID_DEFAULT = 1067
|
||||
MULTIPLE_PRI_KEY = 1068
|
||||
TOO_MANY_KEYS = 1069
|
||||
TOO_MANY_KEY_PARTS = 1070
|
||||
TOO_LONG_KEY = 1071
|
||||
KEY_COLUMN_DOES_NOT_EXITS = 1072
|
||||
BLOB_USED_AS_KEY = 1073
|
||||
TOO_BIG_FIELDLENGTH = 1074
|
||||
WRONG_AUTO_KEY = 1075
|
||||
READY = 1076
|
||||
SHUTDOWN_COMPLETE = 1079
|
||||
FORCING_CLOSE = 1080
|
||||
IPSOCK_ERROR = 1081
|
||||
NO_SUCH_INDEX = 1082
|
||||
WRONG_FIELD_TERMINATORS = 1083
|
||||
BLOBS_AND_NO_TERMINATED = 1084
|
||||
TEXTFILE_NOT_READABLE = 1085
|
||||
FILE_EXISTS_ERROR = 1086
|
||||
LOAD_INFO = 1087
|
||||
ALTER_INFO = 1088
|
||||
WRONG_SUB_KEY = 1089
|
||||
CANT_REMOVE_ALL_FIELDS = 1090
|
||||
CANT_DROP_FIELD_OR_KEY = 1091
|
||||
INSERT_INFO = 1092
|
||||
UPDATE_TABLE_USED = 1093
|
||||
NO_SUCH_THREAD = 1094
|
||||
KILL_DENIED_ERROR = 1095
|
||||
NO_TABLES_USED = 1096
|
||||
TOO_BIG_SET = 1097
|
||||
NO_UNIQUE_LOGFILE = 1098
|
||||
TABLE_NOT_LOCKED_FOR_WRITE = 1099
|
||||
TABLE_NOT_LOCKED = 1100
|
||||
BLOB_CANT_HAVE_DEFAULT = 1101
|
||||
WRONG_DB_NAME = 1102
|
||||
WRONG_TABLE_NAME = 1103
|
||||
TOO_BIG_SELECT = 1104
|
||||
UNKNOWN_ERROR = 1105
|
||||
UNKNOWN_PROCEDURE = 1106
|
||||
WRONG_PARAMCOUNT_TO_PROCEDURE = 1107
|
||||
WRONG_PARAMETERS_TO_PROCEDURE = 1108
|
||||
UNKNOWN_TABLE = 1109
|
||||
FIELD_SPECIFIED_TWICE = 1110
|
||||
INVALID_GROUP_FUNC_USE = 1111
|
||||
UNSUPPORTED_EXTENSION = 1112
|
||||
TABLE_MUST_HAVE_COLUMNS = 1113
|
||||
RECORD_FILE_FULL = 1114
|
||||
UNKNOWN_CHARACTER_SET = 1115
|
||||
TOO_MANY_TABLES = 1116
|
||||
TOO_MANY_FIELDS = 1117
|
||||
TOO_BIG_ROWSIZE = 1118
|
||||
STACK_OVERRUN = 1119
|
||||
WRONG_OUTER_JOIN_UNUSED = 1120
|
||||
NULL_COLUMN_IN_INDEX = 1121
|
||||
CANT_FIND_UDF = 1122
|
||||
CANT_INITIALIZE_UDF = 1123
|
||||
UDF_NO_PATHS = 1124
|
||||
UDF_EXISTS = 1125
|
||||
CANT_OPEN_LIBRARY = 1126
|
||||
CANT_FIND_DL_ENTRY = 1127
|
||||
FUNCTION_NOT_DEFINED = 1128
|
||||
HOST_IS_BLOCKED = 1129
|
||||
HOST_NOT_PRIVILEGED = 1130
|
||||
PASSWORD_ANONYMOUS_USER = 1131
|
||||
PASSWORD_NOT_ALLOWED = 1132
|
||||
PASSWORD_NO_MATCH = 1133
|
||||
UPDATE_INFO = 1134
|
||||
CANT_CREATE_THREAD = 1135
|
||||
WRONG_VALUE_COUNT_ON_ROW = 1136
|
||||
CANT_REOPEN_TABLE = 1137
|
||||
INVALID_USE_OF_NULL = 1138
|
||||
REGEXP_ERROR = 1139
|
||||
MIX_OF_GROUP_FUNC_AND_FIELDS = 1140
|
||||
NONEXISTING_GRANT = 1141
|
||||
TABLEACCESS_DENIED_ERROR = 1142
|
||||
COLUMNACCESS_DENIED_ERROR = 1143
|
||||
ILLEGAL_GRANT_FOR_TABLE = 1144
|
||||
GRANT_WRONG_HOST_OR_USER = 1145
|
||||
NO_SUCH_TABLE = 1146
|
||||
NONEXISTING_TABLE_GRANT = 1147
|
||||
NOT_ALLOWED_COMMAND = 1148
|
||||
SYNTAX_ERROR = 1149
|
||||
ABORTING_CONNECTION = 1152
|
||||
NET_PACKET_TOO_LARGE = 1153
|
||||
NET_READ_ERROR_FROM_PIPE = 1154
|
||||
NET_FCNTL_ERROR = 1155
|
||||
NET_PACKETS_OUT_OF_ORDER = 1156
|
||||
NET_UNCOMPRESS_ERROR = 1157
|
||||
NET_READ_ERROR = 1158
|
||||
NET_READ_INTERRUPTED = 1159
|
||||
NET_ERROR_ON_WRITE = 1160
|
||||
NET_WRITE_INTERRUPTED = 1161
|
||||
TOO_LONG_STRING = 1162
|
||||
TABLE_CANT_HANDLE_BLOB = 1163
|
||||
TABLE_CANT_HANDLE_AUTO_INCREMENT = 1164
|
||||
WRONG_COLUMN_NAME = 1166
|
||||
WRONG_KEY_COLUMN = 1167
|
||||
WRONG_MRG_TABLE = 1168
|
||||
DUP_UNIQUE = 1169
|
||||
BLOB_KEY_WITHOUT_LENGTH = 1170
|
||||
PRIMARY_CANT_HAVE_NULL = 1171
|
||||
TOO_MANY_ROWS = 1172
|
||||
REQUIRES_PRIMARY_KEY = 1173
|
||||
UPDATE_WITHOUT_KEY_IN_SAFE_MODE = 1175
|
||||
KEY_DOES_NOT_EXITS = 1176
|
||||
CHECK_NO_SUCH_TABLE = 1177
|
||||
CHECK_NOT_IMPLEMENTED = 1178
|
||||
CANT_DO_THIS_DURING_AN_TRANSACTION = 1179
|
||||
ERROR_DURING_COMMIT = 1180
|
||||
ERROR_DURING_ROLLBACK = 1181
|
||||
ERROR_DURING_FLUSH_LOGS = 1182
|
||||
NEW_ABORTING_CONNECTION = 1184
|
||||
MASTER = 1188
|
||||
MASTER_NET_READ = 1189
|
||||
MASTER_NET_WRITE = 1190
|
||||
FT_MATCHING_KEY_NOT_FOUND = 1191
|
||||
LOCK_OR_ACTIVE_TRANSACTION = 1192
|
||||
UNKNOWN_SYSTEM_VARIABLE = 1193
|
||||
CRASHED_ON_USAGE = 1194
|
||||
CRASHED_ON_REPAIR = 1195
|
||||
WARNING_NOT_COMPLETE_ROLLBACK = 1196
|
||||
TRANS_CACHE_FULL = 1197
|
||||
SLAVE_NOT_RUNNING = 1199
|
||||
BAD_SLAVE = 1200
|
||||
MASTER_INFO = 1201
|
||||
SLAVE_THREAD = 1202
|
||||
TOO_MANY_USER_CONNECTIONS = 1203
|
||||
SET_CONSTANTS_ONLY = 1204
|
||||
LOCK_WAIT_TIMEOUT = 1205
|
||||
LOCK_TABLE_FULL = 1206
|
||||
READ_ONLY_TRANSACTION = 1207
|
||||
WRONG_ARGUMENTS = 1210
|
||||
NO_PERMISSION_TO_CREATE_USER = 1211
|
||||
LOCK_DEADLOCK = 1213
|
||||
TABLE_CANT_HANDLE_FT = 1214
|
||||
CANNOT_ADD_FOREIGN = 1215
|
||||
NO_REFERENCED_ROW = 1216
|
||||
ROW_IS_REFERENCED = 1217
|
||||
CONNECT_TO_MASTER = 1218
|
||||
ERROR_WHEN_EXECUTING_COMMAND = 1220
|
||||
WRONG_USAGE = 1221
|
||||
WRONG_NUMBER_OF_COLUMNS_IN_SELECT = 1222
|
||||
CANT_UPDATE_WITH_READLOCK = 1223
|
||||
MIXING_NOT_ALLOWED = 1224
|
||||
DUP_ARGUMENT = 1225
|
||||
USER_LIMIT_REACHED = 1226
|
||||
SPECIFIC_ACCESS_DENIED_ERROR = 1227
|
||||
LOCAL_VARIABLE = 1228
|
||||
GLOBAL_VARIABLE = 1229
|
||||
NO_DEFAULT = 1230
|
||||
WRONG_VALUE_FOR_VAR = 1231
|
||||
WRONG_TYPE_FOR_VAR = 1232
|
||||
VAR_CANT_BE_READ = 1233
|
||||
CANT_USE_OPTION_HERE = 1234
|
||||
NOT_SUPPORTED_YET = 1235
|
||||
MASTER_FATAL_ERROR_READING_BINLOG = 1236
|
||||
SLAVE_IGNORED_TABLE = 1237
|
||||
INCORRECT_GLOBAL_LOCAL_VAR = 1238
|
||||
WRONG_FK_DEF = 1239
|
||||
KEY_REF_DO_NOT_MATCH_TABLE_REF = 1240
|
||||
OPERAND_COLUMNS = 1241
|
||||
SUBQUERY_NO_1_ROW = 1242
|
||||
UNKNOWN_STMT_HANDLER = 1243
|
||||
CORRUPT_HELP_DB = 1244
|
||||
AUTO_CONVERT = 1246
|
||||
ILLEGAL_REFERENCE = 1247
|
||||
DERIVED_MUST_HAVE_ALIAS = 1248
|
||||
SELECT_REDUCED = 1249
|
||||
TABLENAME_NOT_ALLOWED_HERE = 1250
|
||||
NOT_SUPPORTED_AUTH_MODE = 1251
|
||||
SPATIAL_CANT_HAVE_NULL = 1252
|
||||
COLLATION_CHARSET_MISMATCH = 1253
|
||||
TOO_BIG_FOR_UNCOMPRESS = 1256
|
||||
ZLIB_Z_MEM_ERROR = 1257
|
||||
ZLIB_Z_BUF_ERROR = 1258
|
||||
ZLIB_Z_DATA_ERROR = 1259
|
||||
CUT_VALUE_GROUP_CONCAT = 1260
|
||||
WARN_TOO_FEW_RECORDS = 1261
|
||||
WARN_TOO_MANY_RECORDS = 1262
|
||||
WARN_NULL_TO_NOTNULL = 1263
|
||||
WARN_DATA_OUT_OF_RANGE = 1264
|
||||
WARN_DATA_TRUNCATED = 1265
|
||||
WARN_USING_OTHER_HANDLER = 1266
|
||||
CANT_AGGREGATE_2COLLATIONS = 1267
|
||||
REVOKE_GRANTS = 1269
|
||||
CANT_AGGREGATE_3COLLATIONS = 1270
|
||||
CANT_AGGREGATE_NCOLLATIONS = 1271
|
||||
VARIABLE_IS_NOT_STRUCT = 1272
|
||||
UNKNOWN_COLLATION = 1273
|
||||
SLAVE_IGNORED_SSL_PARAMS = 1274
|
||||
SERVER_IS_IN_SECURE_AUTH_MODE = 1275
|
||||
WARN_FIELD_RESOLVED = 1276
|
||||
BAD_SLAVE_UNTIL_COND = 1277
|
||||
MISSING_SKIP_SLAVE = 1278
|
||||
UNTIL_COND_IGNORED = 1279
|
||||
WRONG_NAME_FOR_INDEX = 1280
|
||||
WRONG_NAME_FOR_CATALOG = 1281
|
||||
BAD_FT_COLUMN = 1283
|
||||
UNKNOWN_KEY_CACHE = 1284
|
||||
WARN_HOSTNAME_WONT_WORK = 1285
|
||||
UNKNOWN_STORAGE_ENGINE = 1286
|
||||
WARN_DEPRECATED_SYNTAX = 1287
|
||||
NON_UPDATABLE_TABLE = 1288
|
||||
FEATURE_DISABLED = 1289
|
||||
OPTION_PREVENTS_STATEMENT = 1290
|
||||
DUPLICATED_VALUE_IN_TYPE = 1291
|
||||
TRUNCATED_WRONG_VALUE = 1292
|
||||
INVALID_ON_UPDATE = 1294
|
||||
UNSUPPORTED_PS = 1295
|
||||
GET_ERRMSG = 1296
|
||||
GET_TEMPORARY_ERRMSG = 1297
|
||||
UNKNOWN_TIME_ZONE = 1298
|
||||
WARN_INVALID_TIMESTAMP = 1299
|
||||
INVALID_CHARACTER_STRING = 1300
|
||||
WARN_ALLOWED_PACKET_OVERFLOWED = 1301
|
||||
CONFLICTING_DECLARATIONS = 1302
|
||||
SP_NO_RECURSIVE_CREATE = 1303
|
||||
SP_ALREADY_EXISTS = 1304
|
||||
SP_DOES_NOT_EXIST = 1305
|
||||
SP_DROP_FAILED = 1306
|
||||
SP_STORE_FAILED = 1307
|
||||
SP_LILABEL_MISMATCH = 1308
|
||||
SP_LABEL_REDEFINE = 1309
|
||||
SP_LABEL_MISMATCH = 1310
|
||||
SP_UNINIT_VAR = 1311
|
||||
SP_BADSELECT = 1312
|
||||
SP_BADRETURN = 1313
|
||||
SP_BADSTATEMENT = 1314
|
||||
UPDATE_LOG_DEPRECATED_IGNORED = 1315
|
||||
UPDATE_LOG_DEPRECATED_TRANSLATED = 1316
|
||||
QUERY_INTERRUPTED = 1317
|
||||
SP_WRONG_NO_OF_ARGS = 1318
|
||||
SP_COND_MISMATCH = 1319
|
||||
SP_NORETURN = 1320
|
||||
SP_NORETURNEND = 1321
|
||||
SP_BAD_CURSOR_QUERY = 1322
|
||||
SP_BAD_CURSOR_SELECT = 1323
|
||||
SP_CURSOR_MISMATCH = 1324
|
||||
SP_CURSOR_ALREADY_OPEN = 1325
|
||||
SP_CURSOR_NOT_OPEN = 1326
|
||||
SP_UNDECLARED_VAR = 1327
|
||||
SP_WRONG_NO_OF_FETCH_ARGS = 1328
|
||||
SP_FETCH_NO_DATA = 1329
|
||||
SP_DUP_PARAM = 1330
|
||||
SP_DUP_VAR = 1331
|
||||
SP_DUP_COND = 1332
|
||||
SP_DUP_CURS = 1333
|
||||
SP_CANT_ALTER = 1334
|
||||
SP_SUBSELECT_NYI = 1335
|
||||
STMT_NOT_ALLOWED_IN_SF_OR_TRG = 1336
|
||||
SP_VARCOND_AFTER_CURSHNDLR = 1337
|
||||
SP_CURSOR_AFTER_HANDLER = 1338
|
||||
SP_CASE_NOT_FOUND = 1339
|
||||
FPARSER_TOO_BIG_FILE = 1340
|
||||
FPARSER_BAD_HEADER = 1341
|
||||
FPARSER_EOF_IN_COMMENT = 1342
|
||||
FPARSER_ERROR_IN_PARAMETER = 1343
|
||||
FPARSER_EOF_IN_UNKNOWN_PARAMETER = 1344
|
||||
VIEW_NO_EXPLAIN = 1345
|
||||
WRONG_OBJECT = 1347
|
||||
NONUPDATEABLE_COLUMN = 1348
|
||||
VIEW_SELECT_CLAUSE = 1350
|
||||
VIEW_SELECT_VARIABLE = 1351
|
||||
VIEW_SELECT_TMPTABLE = 1352
|
||||
VIEW_WRONG_LIST = 1353
|
||||
WARN_VIEW_MERGE = 1354
|
||||
WARN_VIEW_WITHOUT_KEY = 1355
|
||||
VIEW_INVALID = 1356
|
||||
SP_NO_DROP_SP = 1357
|
||||
TRG_ALREADY_EXISTS = 1359
|
||||
TRG_DOES_NOT_EXIST = 1360
|
||||
TRG_ON_VIEW_OR_TEMP_TABLE = 1361
|
||||
TRG_CANT_CHANGE_ROW = 1362
|
||||
TRG_NO_SUCH_ROW_IN_TRG = 1363
|
||||
NO_DEFAULT_FOR_FIELD = 1364
|
||||
DIVISION_BY_ZERO = 1365
|
||||
TRUNCATED_WRONG_VALUE_FOR_FIELD = 1366
|
||||
ILLEGAL_VALUE_FOR_TYPE = 1367
|
||||
VIEW_NONUPD_CHECK = 1368
|
||||
VIEW_CHECK_FAILED = 1369
|
||||
PROCACCESS_DENIED_ERROR = 1370
|
||||
RELAY_LOG_FAIL = 1371
|
||||
UNKNOWN_TARGET_BINLOG = 1373
|
||||
IO_ERR_LOG_INDEX_READ = 1374
|
||||
BINLOG_PURGE_PROHIBITED = 1375
|
||||
FSEEK_FAIL = 1376
|
||||
BINLOG_PURGE_FATAL_ERR = 1377
|
||||
LOG_IN_USE = 1378
|
||||
LOG_PURGE_UNKNOWN_ERR = 1379
|
||||
RELAY_LOG_INIT = 1380
|
||||
NO_BINARY_LOGGING = 1381
|
||||
RESERVED_SYNTAX = 1382
|
||||
PS_MANY_PARAM = 1390
|
||||
KEY_PART_0 = 1391
|
||||
VIEW_CHECKSUM = 1392
|
||||
VIEW_MULTIUPDATE = 1393
|
||||
VIEW_NO_INSERT_FIELD_LIST = 1394
|
||||
VIEW_DELETE_MERGE_VIEW = 1395
|
||||
CANNOT_USER = 1396
|
||||
XAER_NOTA = 1397
|
||||
XAER_INVAL = 1398
|
||||
XAER_RMFAIL = 1399
|
||||
XAER_OUTSIDE = 1400
|
||||
XAER_RMERR = 1401
|
||||
XA_RBROLLBACK = 1402
|
||||
NONEXISTING_PROC_GRANT = 1403
|
||||
PROC_AUTO_GRANT_FAIL = 1404
|
||||
PROC_AUTO_REVOKE_FAIL = 1405
|
||||
DATA_TOO_LONG = 1406
|
||||
SP_BAD_SQLSTATE = 1407
|
||||
STARTUP = 1408
|
||||
LOAD_FROM_FIXED_SIZE_ROWS_TO_VAR = 1409
|
||||
CANT_CREATE_USER_WITH_GRANT = 1410
|
||||
WRONG_VALUE_FOR_TYPE = 1411
|
||||
TABLE_DEF_CHANGED = 1412
|
||||
SP_DUP_HANDLER = 1413
|
||||
SP_NOT_VAR_ARG = 1414
|
||||
SP_NO_RETSET = 1415
|
||||
CANT_CREATE_GEOMETRY_OBJECT = 1416
|
||||
BINLOG_UNSAFE_ROUTINE = 1418
|
||||
BINLOG_CREATE_ROUTINE_NEED_SUPER = 1419
|
||||
STMT_HAS_NO_OPEN_CURSOR = 1421
|
||||
COMMIT_NOT_ALLOWED_IN_SF_OR_TRG = 1422
|
||||
NO_DEFAULT_FOR_VIEW_FIELD = 1423
|
||||
SP_NO_RECURSION = 1424
|
||||
TOO_BIG_SCALE = 1425
|
||||
TOO_BIG_PRECISION = 1426
|
||||
M_BIGGER_THAN_D = 1427
|
||||
WRONG_LOCK_OF_SYSTEM_TABLE = 1428
|
||||
CONNECT_TO_FOREIGN_DATA_SOURCE = 1429
|
||||
QUERY_ON_FOREIGN_DATA_SOURCE = 1430
|
||||
FOREIGN_DATA_SOURCE_DOESNT_EXIST = 1431
|
||||
FOREIGN_DATA_STRING_INVALID_CANT_CREATE = 1432
|
||||
FOREIGN_DATA_STRING_INVALID = 1433
|
||||
TRG_IN_WRONG_SCHEMA = 1435
|
||||
STACK_OVERRUN_NEED_MORE = 1436
|
||||
TOO_LONG_BODY = 1437
|
||||
WARN_CANT_DROP_DEFAULT_KEYCACHE = 1438
|
||||
TOO_BIG_DISPLAYWIDTH = 1439
|
||||
XAER_DUPID = 1440
|
||||
DATETIME_FUNCTION_OVERFLOW = 1441
|
||||
CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG = 1442
|
||||
VIEW_PREVENT_UPDATE = 1443
|
||||
PS_NO_RECURSION = 1444
|
||||
SP_CANT_SET_AUTOCOMMIT = 1445
|
||||
VIEW_FRM_NO_USER = 1447
|
||||
VIEW_OTHER_USER = 1448
|
||||
NO_SUCH_USER = 1449
|
||||
FORBID_SCHEMA_CHANGE = 1450
|
||||
ROW_IS_REFERENCED_2 = 1451
|
||||
NO_REFERENCED_ROW_2 = 1452
|
||||
SP_BAD_VAR_SHADOW = 1453
|
||||
TRG_NO_DEFINER = 1454
|
||||
OLD_FILE_FORMAT = 1455
|
||||
SP_RECURSION_LIMIT = 1456
|
||||
SP_WRONG_NAME = 1458
|
||||
TABLE_NEEDS_UPGRADE = 1459
|
||||
SP_NO_AGGREGATE = 1460
|
||||
MAX_PREPARED_STMT_COUNT_REACHED = 1461
|
||||
VIEW_RECURSIVE = 1462
|
||||
NON_GROUPING_FIELD_USED = 1463
|
||||
TABLE_CANT_HANDLE_SPKEYS = 1464
|
||||
NO_TRIGGERS_ON_SYSTEM_SCHEMA = 1465
|
||||
REMOVED_SPACES = 1466
|
||||
AUTOINC_READ_FAILED = 1467
|
||||
USERNAME = 1468
|
||||
HOSTNAME = 1469
|
||||
WRONG_STRING_LENGTH = 1470
|
||||
NON_INSERTABLE_TABLE = 1471
|
||||
ADMIN_WRONG_MRG_TABLE = 1472
|
||||
TOO_HIGH_LEVEL_OF_NESTING_FOR_SELECT = 1473
|
||||
NAME_BECOMES_EMPTY = 1474
|
||||
AMBIGUOUS_FIELD_TERM = 1475
|
||||
FOREIGN_SERVER_EXISTS = 1476
|
||||
FOREIGN_SERVER_DOESNT_EXIST = 1477
|
||||
ILLEGAL_HA_CREATE_OPTION = 1478
|
||||
PARTITION_REQUIRES_VALUES_ERROR = 1479
|
||||
PARTITION_WRONG_VALUES_ERROR = 1480
|
||||
PARTITION_MAXVALUE_ERROR = 1481
|
||||
PARTITION_WRONG_NO_PART_ERROR = 1484
|
||||
PARTITION_WRONG_NO_SUBPART_ERROR = 1485
|
||||
WRONG_EXPR_IN_PARTITION_FUNC_ERROR = 1486
|
||||
FIELD_NOT_FOUND_PART_ERROR = 1488
|
||||
INCONSISTENT_PARTITION_INFO_ERROR = 1490
|
||||
PARTITION_FUNC_NOT_ALLOWED_ERROR = 1491
|
||||
PARTITIONS_MUST_BE_DEFINED_ERROR = 1492
|
||||
RANGE_NOT_INCREASING_ERROR = 1493
|
||||
INCONSISTENT_TYPE_OF_FUNCTIONS_ERROR = 1494
|
||||
MULTIPLE_DEF_CONST_IN_LIST_PART_ERROR = 1495
|
||||
PARTITION_ENTRY_ERROR = 1496
|
||||
MIX_HANDLER_ERROR = 1497
|
||||
PARTITION_NOT_DEFINED_ERROR = 1498
|
||||
TOO_MANY_PARTITIONS_ERROR = 1499
|
||||
SUBPARTITION_ERROR = 1500
|
||||
CANT_CREATE_HANDLER_FILE = 1501
|
||||
BLOB_FIELD_IN_PART_FUNC_ERROR = 1502
|
||||
UNIQUE_KEY_NEED_ALL_FIELDS_IN_PF = 1503
|
||||
NO_PARTS_ERROR = 1504
|
||||
PARTITION_MGMT_ON_NONPARTITIONED = 1505
|
||||
FOREIGN_KEY_ON_PARTITIONED = 1506
|
||||
DROP_PARTITION_NON_EXISTENT = 1507
|
||||
DROP_LAST_PARTITION = 1508
|
||||
COALESCE_ONLY_ON_HASH_PARTITION = 1509
|
||||
REORG_HASH_ONLY_ON_SAME_NO = 1510
|
||||
REORG_NO_PARAM_ERROR = 1511
|
||||
ONLY_ON_RANGE_LIST_PARTITION = 1512
|
||||
ADD_PARTITION_SUBPART_ERROR = 1513
|
||||
ADD_PARTITION_NO_NEW_PARTITION = 1514
|
||||
COALESCE_PARTITION_NO_PARTITION = 1515
|
||||
REORG_PARTITION_NOT_EXIST = 1516
|
||||
SAME_NAME_PARTITION = 1517
|
||||
NO_BINLOG_ERROR = 1518
|
||||
CONSECUTIVE_REORG_PARTITIONS = 1519
|
||||
REORG_OUTSIDE_RANGE = 1520
|
||||
PARTITION_FUNCTION_FAILURE = 1521
|
||||
LIMITED_PART_RANGE = 1523
|
||||
PLUGIN_IS_NOT_LOADED = 1524
|
||||
WRONG_VALUE = 1525
|
||||
NO_PARTITION_FOR_GIVEN_VALUE = 1526
|
||||
FILEGROUP_OPTION_ONLY_ONCE = 1527
|
||||
CREATE_FILEGROUP_FAILED = 1528
|
||||
DROP_FILEGROUP_FAILED = 1529
|
||||
TABLESPACE_AUTO_EXTEND_ERROR = 1530
|
||||
WRONG_SIZE_NUMBER = 1531
|
||||
SIZE_OVERFLOW_ERROR = 1532
|
||||
ALTER_FILEGROUP_FAILED = 1533
|
||||
BINLOG_ROW_LOGGING_FAILED = 1534
|
||||
EVENT_ALREADY_EXISTS = 1537
|
||||
EVENT_DOES_NOT_EXIST = 1539
|
||||
EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG = 1542
|
||||
EVENT_ENDS_BEFORE_STARTS = 1543
|
||||
EVENT_EXEC_TIME_IN_THE_PAST = 1544
|
||||
EVENT_SAME_NAME = 1551
|
||||
DROP_INDEX_FK = 1553
|
||||
WARN_DEPRECATED_SYNTAX_WITH_VER = 1554
|
||||
CANT_LOCK_LOG_TABLE = 1556
|
||||
FOREIGN_DUPLICATE_KEY_OLD_UNUSED = 1557
|
||||
COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE = 1558
|
||||
TEMP_TABLE_PREVENTS_SWITCH_OUT_OF_RBR = 1559
|
||||
STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1560
|
||||
PARTITION_NO_TEMPORARY = 1562
|
||||
PARTITION_CONST_DOMAIN_ERROR = 1563
|
||||
PARTITION_FUNCTION_IS_NOT_ALLOWED = 1564
|
||||
NULL_IN_VALUES_LESS_THAN = 1566
|
||||
WRONG_PARTITION_NAME = 1567
|
||||
CANT_CHANGE_TX_CHARACTERISTICS = 1568
|
||||
DUP_ENTRY_AUTOINCREMENT_CASE = 1569
|
||||
EVENT_SET_VAR_ERROR = 1571
|
||||
PARTITION_MERGE_ERROR = 1572
|
||||
BASE64_DECODE_ERROR = 1575
|
||||
EVENT_RECURSION_FORBIDDEN = 1576
|
||||
ONLY_INTEGERS_ALLOWED = 1578
|
||||
UNSUPORTED_LOG_ENGINE = 1579
|
||||
BAD_LOG_STATEMENT = 1580
|
||||
CANT_RENAME_LOG_TABLE = 1581
|
||||
WRONG_PARAMCOUNT_TO_NATIVE_FCT = 1582
|
||||
WRONG_PARAMETERS_TO_NATIVE_FCT = 1583
|
||||
WRONG_PARAMETERS_TO_STORED_FCT = 1584
|
||||
NATIVE_FCT_NAME_COLLISION = 1585
|
||||
DUP_ENTRY_WITH_KEY_NAME = 1586
|
||||
BINLOG_PURGE_EMFILE = 1587
|
||||
EVENT_CANNOT_CREATE_IN_THE_PAST = 1588
|
||||
EVENT_CANNOT_ALTER_IN_THE_PAST = 1589
|
||||
NO_PARTITION_FOR_GIVEN_VALUE_SILENT = 1591
|
||||
BINLOG_UNSAFE_STATEMENT = 1592
|
||||
BINLOG_FATAL_ERROR = 1593
|
||||
BINLOG_LOGGING_IMPOSSIBLE = 1598
|
||||
VIEW_NO_CREATION_CTX = 1599
|
||||
VIEW_INVALID_CREATION_CTX = 1600
|
||||
TRG_CORRUPTED_FILE = 1602
|
||||
TRG_NO_CREATION_CTX = 1603
|
||||
TRG_INVALID_CREATION_CTX = 1604
|
||||
EVENT_INVALID_CREATION_CTX = 1605
|
||||
TRG_CANT_OPEN_TABLE = 1606
|
||||
NO_FORMAT_DESCRIPTION_EVENT_BEFORE_BINLOG_STATEMENT = 1609
|
||||
SLAVE_CORRUPT_EVENT = 1610
|
||||
LOG_PURGE_NO_FILE = 1612
|
||||
XA_RBTIMEOUT = 1613
|
||||
XA_RBDEADLOCK = 1614
|
||||
NEED_REPREPARE = 1615
|
||||
WARN_NO_MASTER_INFO = 1617
|
||||
WARN_OPTION_IGNORED = 1618
|
||||
PLUGIN_DELETE_BUILTIN = 1619
|
||||
WARN_PLUGIN_BUSY = 1620
|
||||
VARIABLE_IS_READONLY = 1621
|
||||
WARN_ENGINE_TRANSACTION_ROLLBACK = 1622
|
||||
SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE = 1624
|
||||
NDB_REPLICATION_SCHEMA_ERROR = 1625
|
||||
CONFLICT_FN_PARSE_ERROR = 1626
|
||||
EXCEPTIONS_WRITE_ERROR = 1627
|
||||
TOO_LONG_TABLE_COMMENT = 1628
|
||||
TOO_LONG_FIELD_COMMENT = 1629
|
||||
FUNC_INEXISTENT_NAME_COLLISION = 1630
|
||||
DATABASE_NAME = 1631
|
||||
TABLE_NAME = 1632
|
||||
PARTITION_NAME = 1633
|
||||
SUBPARTITION_NAME = 1634
|
||||
TEMPORARY_NAME = 1635
|
||||
RENAMED_NAME = 1636
|
||||
TOO_MANY_CONCURRENT_TRXS = 1637
|
||||
WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED = 1638
|
||||
DEBUG_SYNC_TIMEOUT = 1639
|
||||
DEBUG_SYNC_HIT_LIMIT = 1640
|
||||
DUP_SIGNAL_SET = 1641
|
||||
SIGNAL_WARN = 1642
|
||||
SIGNAL_NOT_FOUND = 1643
|
||||
SIGNAL_EXCEPTION = 1644
|
||||
RESIGNAL_WITHOUT_ACTIVE_HANDLER = 1645
|
||||
SIGNAL_BAD_CONDITION_TYPE = 1646
|
||||
WARN_COND_ITEM_TRUNCATED = 1647
|
||||
COND_ITEM_TOO_LONG = 1648
|
||||
UNKNOWN_LOCALE = 1649
|
||||
SLAVE_IGNORE_SERVER_IDS = 1650
|
||||
SAME_NAME_PARTITION_FIELD = 1652
|
||||
PARTITION_COLUMN_LIST_ERROR = 1653
|
||||
WRONG_TYPE_COLUMN_VALUE_ERROR = 1654
|
||||
TOO_MANY_PARTITION_FUNC_FIELDS_ERROR = 1655
|
||||
MAXVALUE_IN_VALUES_IN = 1656
|
||||
TOO_MANY_VALUES_ERROR = 1657
|
||||
ROW_SINGLE_PARTITION_FIELD_ERROR = 1658
|
||||
FIELD_TYPE_NOT_ALLOWED_AS_PARTITION_FIELD = 1659
|
||||
PARTITION_FIELDS_TOO_LONG = 1660
|
||||
BINLOG_ROW_ENGINE_AND_STMT_ENGINE = 1661
|
||||
BINLOG_ROW_MODE_AND_STMT_ENGINE = 1662
|
||||
BINLOG_UNSAFE_AND_STMT_ENGINE = 1663
|
||||
BINLOG_ROW_INJECTION_AND_STMT_ENGINE = 1664
|
||||
BINLOG_STMT_MODE_AND_ROW_ENGINE = 1665
|
||||
BINLOG_ROW_INJECTION_AND_STMT_MODE = 1666
|
||||
BINLOG_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1667
|
||||
BINLOG_UNSAFE_LIMIT = 1668
|
||||
BINLOG_UNSAFE_SYSTEM_TABLE = 1670
|
||||
BINLOG_UNSAFE_AUTOINC_COLUMNS = 1671
|
||||
BINLOG_UNSAFE_UDF = 1672
|
||||
BINLOG_UNSAFE_SYSTEM_VARIABLE = 1673
|
||||
BINLOG_UNSAFE_SYSTEM_FUNCTION = 1674
|
||||
BINLOG_UNSAFE_NONTRANS_AFTER_TRANS = 1675
|
||||
MESSAGE_AND_STATEMENT = 1676
|
||||
SLAVE_CANT_CREATE_CONVERSION = 1678
|
||||
INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1679
|
||||
PATH_LENGTH = 1680
|
||||
WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT = 1681
|
||||
WRONG_NATIVE_TABLE_STRUCTURE = 1682
|
||||
WRONG_PERFSCHEMA_USAGE = 1683
|
||||
WARN_I_S_SKIPPED_TABLE = 1684
|
||||
INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1685
|
||||
STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1686
|
||||
SPATIAL_MUST_HAVE_GEOM_COL = 1687
|
||||
TOO_LONG_INDEX_COMMENT = 1688
|
||||
LOCK_ABORTED = 1689
|
||||
DATA_OUT_OF_RANGE = 1690
|
||||
WRONG_SPVAR_TYPE_IN_LIMIT = 1691
|
||||
BINLOG_UNSAFE_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1692
|
||||
BINLOG_UNSAFE_MIXED_STATEMENT = 1693
|
||||
INSIDE_TRANSACTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1694
|
||||
STORED_FUNCTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1695
|
||||
FAILED_READ_FROM_PAR_FILE = 1696
|
||||
VALUES_IS_NOT_INT_TYPE_ERROR = 1697
|
||||
ACCESS_DENIED_NO_PASSWORD_ERROR = 1698
|
||||
SET_PASSWORD_AUTH_PLUGIN = 1699
|
||||
TRUNCATE_ILLEGAL_FK = 1701
|
||||
PLUGIN_IS_PERMANENT = 1702
|
||||
SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN = 1703
|
||||
SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX = 1704
|
||||
STMT_CACHE_FULL = 1705
|
||||
MULTI_UPDATE_KEY_CONFLICT = 1706
|
||||
TABLE_NEEDS_REBUILD = 1707
|
||||
WARN_OPTION_BELOW_LIMIT = 1708
|
||||
INDEX_COLUMN_TOO_LONG = 1709
|
||||
ERROR_IN_TRIGGER_BODY = 1710
|
||||
ERROR_IN_UNKNOWN_TRIGGER_BODY = 1711
|
||||
INDEX_CORRUPT = 1712
|
||||
UNDO_RECORD_TOO_BIG = 1713
|
||||
BINLOG_UNSAFE_INSERT_IGNORE_SELECT = 1714
|
||||
BINLOG_UNSAFE_INSERT_SELECT_UPDATE = 1715
|
||||
BINLOG_UNSAFE_REPLACE_SELECT = 1716
|
||||
BINLOG_UNSAFE_CREATE_IGNORE_SELECT = 1717
|
||||
BINLOG_UNSAFE_CREATE_REPLACE_SELECT = 1718
|
||||
BINLOG_UNSAFE_UPDATE_IGNORE = 1719
|
||||
PLUGIN_NO_UNINSTALL = 1720
|
||||
PLUGIN_NO_INSTALL = 1721
|
||||
BINLOG_UNSAFE_WRITE_AUTOINC_SELECT = 1722
|
||||
BINLOG_UNSAFE_CREATE_SELECT_AUTOINC = 1723
|
||||
BINLOG_UNSAFE_INSERT_TWO_KEYS = 1724
|
||||
TABLE_IN_FK_CHECK = 1725
|
||||
UNSUPPORTED_ENGINE = 1726
|
||||
BINLOG_UNSAFE_AUTOINC_NOT_FIRST = 1727
|
||||
CANNOT_LOAD_FROM_TABLE_V2 = 1728
|
||||
MASTER_DELAY_VALUE_OUT_OF_RANGE = 1729
|
||||
ONLY_FD_AND_RBR_EVENTS_ALLOWED_IN_BINLOG_STATEMENT = 1730
|
||||
PARTITION_EXCHANGE_DIFFERENT_OPTION = 1731
|
||||
PARTITION_EXCHANGE_PART_TABLE = 1732
|
||||
PARTITION_EXCHANGE_TEMP_TABLE = 1733
|
||||
PARTITION_INSTEAD_OF_SUBPARTITION = 1734
|
||||
UNKNOWN_PARTITION = 1735
|
||||
TABLES_DIFFERENT_METADATA = 1736
|
||||
ROW_DOES_NOT_MATCH_PARTITION = 1737
|
||||
BINLOG_CACHE_SIZE_GREATER_THAN_MAX = 1738
|
||||
WARN_INDEX_NOT_APPLICABLE = 1739
|
||||
PARTITION_EXCHANGE_FOREIGN_KEY = 1740
|
||||
RPL_INFO_DATA_TOO_LONG = 1742
|
||||
BINLOG_STMT_CACHE_SIZE_GREATER_THAN_MAX = 1745
|
||||
CANT_UPDATE_TABLE_IN_CREATE_TABLE_SELECT = 1746
|
||||
PARTITION_CLAUSE_ON_NONPARTITIONED = 1747
|
||||
ROW_DOES_NOT_MATCH_GIVEN_PARTITION_SET = 1748
|
||||
CHANGE_RPL_INFO_REPOSITORY_FAILURE = 1750
|
||||
WARNING_NOT_COMPLETE_ROLLBACK_WITH_CREATED_TEMP_TABLE = 1751
|
||||
WARNING_NOT_COMPLETE_ROLLBACK_WITH_DROPPED_TEMP_TABLE = 1752
|
||||
MTS_FEATURE_IS_NOT_SUPPORTED = 1753
|
||||
MTS_UPDATED_DBS_GREATER_MAX = 1754
|
||||
MTS_CANT_PARALLEL = 1755
|
||||
MTS_INCONSISTENT_DATA = 1756
|
||||
FULLTEXT_NOT_SUPPORTED_WITH_PARTITIONING = 1757
|
||||
DA_INVALID_CONDITION_NUMBER = 1758
|
||||
INSECURE_PLAIN_TEXT = 1759
|
||||
INSECURE_CHANGE_MASTER = 1760
|
||||
FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO = 1761
|
||||
FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO = 1762
|
||||
SQLTHREAD_WITH_SECURE_SLAVE = 1763
|
||||
TABLE_HAS_NO_FT = 1764
|
||||
VARIABLE_NOT_SETTABLE_IN_SF_OR_TRIGGER = 1765
|
||||
VARIABLE_NOT_SETTABLE_IN_TRANSACTION = 1766
|
||||
SET_STATEMENT_CANNOT_INVOKE_FUNCTION = 1769
|
||||
GTID_NEXT_CANT_BE_AUTOMATIC_IF_GTID_NEXT_LIST_IS_NON_NULL = 1770
|
||||
MALFORMED_GTID_SET_SPECIFICATION = 1772
|
||||
MALFORMED_GTID_SET_ENCODING = 1773
|
||||
MALFORMED_GTID_SPECIFICATION = 1774
|
||||
GNO_EXHAUSTED = 1775
|
||||
BAD_SLAVE_AUTO_POSITION = 1776
|
||||
AUTO_POSITION_REQUIRES_GTID_MODE_NOT_OFF = 1777
|
||||
CANT_DO_IMPLICIT_COMMIT_IN_TRX_WHEN_GTID_NEXT_IS_SET = 1778
|
||||
GTID_MODE_ON_REQUIRES_ENFORCE_GTID_CONSISTENCY_ON = 1779
|
||||
CANT_SET_GTID_NEXT_TO_GTID_WHEN_GTID_MODE_IS_OFF = 1781
|
||||
CANT_SET_GTID_NEXT_TO_ANONYMOUS_WHEN_GTID_MODE_IS_ON = 1782
|
||||
CANT_SET_GTID_NEXT_LIST_TO_NON_NULL_WHEN_GTID_MODE_IS_OFF = 1783
|
||||
GTID_UNSAFE_NON_TRANSACTIONAL_TABLE = 1785
|
||||
GTID_UNSAFE_CREATE_SELECT = 1786
|
||||
GTID_UNSAFE_CREATE_DROP_TEMPORARY_TABLE_IN_TRANSACTION = 1787
|
||||
GTID_MODE_CAN_ONLY_CHANGE_ONE_STEP_AT_A_TIME = 1788
|
||||
MASTER_HAS_PURGED_REQUIRED_GTIDS = 1789
|
||||
CANT_SET_GTID_NEXT_WHEN_OWNING_GTID = 1790
|
||||
UNKNOWN_EXPLAIN_FORMAT = 1791
|
||||
CANT_EXECUTE_IN_READ_ONLY_TRANSACTION = 1792
|
||||
TOO_LONG_TABLE_PARTITION_COMMENT = 1793
|
||||
SLAVE_CONFIGURATION = 1794
|
||||
INNODB_FT_LIMIT = 1795
|
||||
INNODB_NO_FT_TEMP_TABLE = 1796
|
||||
INNODB_FT_WRONG_DOCID_COLUMN = 1797
|
||||
INNODB_FT_WRONG_DOCID_INDEX = 1798
|
||||
INNODB_ONLINE_LOG_TOO_BIG = 1799
|
||||
UNKNOWN_ALTER_ALGORITHM = 1800
|
||||
UNKNOWN_ALTER_LOCK = 1801
|
||||
MTS_CHANGE_MASTER_CANT_RUN_WITH_GAPS = 1802
|
||||
MTS_RECOVERY_FAILURE = 1803
|
||||
MTS_RESET_WORKERS = 1804
|
||||
COL_COUNT_DOESNT_MATCH_CORRUPTED_V2 = 1805
|
||||
SLAVE_SILENT_RETRY_TRANSACTION = 1806
|
||||
DISCARD_FK_CHECKS_RUNNING = 1807
|
||||
TABLE_SCHEMA_MISMATCH = 1808
|
||||
TABLE_IN_SYSTEM_TABLESPACE = 1809
|
||||
IO_READ_ERROR = 1810
|
||||
IO_WRITE_ERROR = 1811
|
||||
TABLESPACE_MISSING = 1812
|
||||
TABLESPACE_EXISTS = 1813
|
||||
TABLESPACE_DISCARDED = 1814
|
||||
INTERNAL_ERROR = 1815
|
||||
INNODB_IMPORT_ERROR = 1816
|
||||
INNODB_INDEX_CORRUPT = 1817
|
||||
INVALID_YEAR_COLUMN_LENGTH = 1818
|
||||
NOT_VALID_PASSWORD = 1819
|
||||
MUST_CHANGE_PASSWORD = 1820
|
||||
FK_NO_INDEX_CHILD = 1821
|
||||
FK_NO_INDEX_PARENT = 1822
|
||||
FK_FAIL_ADD_SYSTEM = 1823
|
||||
FK_CANNOT_OPEN_PARENT = 1824
|
||||
FK_INCORRECT_OPTION = 1825
|
||||
FK_DUP_NAME = 1826
|
||||
PASSWORD_FORMAT = 1827
|
||||
FK_COLUMN_CANNOT_DROP = 1828
|
||||
FK_COLUMN_CANNOT_DROP_CHILD = 1829
|
||||
FK_COLUMN_NOT_NULL = 1830
|
||||
DUP_INDEX = 1831
|
||||
FK_COLUMN_CANNOT_CHANGE = 1832
|
||||
FK_COLUMN_CANNOT_CHANGE_CHILD = 1833
|
||||
MALFORMED_PACKET = 1835
|
||||
READ_ONLY_MODE = 1836
|
||||
GTID_NEXT_TYPE_UNDEFINED_GTID = 1837
|
||||
VARIABLE_NOT_SETTABLE_IN_SP = 1838
|
||||
CANT_SET_GTID_PURGED_WHEN_GTID_EXECUTED_IS_NOT_EMPTY = 1840
|
||||
CANT_SET_GTID_PURGED_WHEN_OWNED_GTIDS_IS_NOT_EMPTY = 1841
|
||||
GTID_PURGED_WAS_CHANGED = 1842
|
||||
GTID_EXECUTED_WAS_CHANGED = 1843
|
||||
BINLOG_STMT_MODE_AND_NO_REPL_TABLES = 1844
|
||||
ALTER_OPERATION_NOT_SUPPORTED = 1845
|
||||
ALTER_OPERATION_NOT_SUPPORTED_REASON = 1846
|
||||
ALTER_OPERATION_NOT_SUPPORTED_REASON_COPY = 1847
|
||||
ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION = 1848
|
||||
ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME = 1849
|
||||
ALTER_OPERATION_NOT_SUPPORTED_REASON_COLUMN_TYPE = 1850
|
||||
ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_CHECK = 1851
|
||||
ALTER_OPERATION_NOT_SUPPORTED_REASON_NOPK = 1853
|
||||
ALTER_OPERATION_NOT_SUPPORTED_REASON_AUTOINC = 1854
|
||||
ALTER_OPERATION_NOT_SUPPORTED_REASON_HIDDEN_FTS = 1855
|
||||
ALTER_OPERATION_NOT_SUPPORTED_REASON_CHANGE_FTS = 1856
|
||||
ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS = 1857
|
||||
SQL_SLAVE_SKIP_COUNTER_NOT_SETTABLE_IN_GTID_MODE = 1858
|
||||
DUP_UNKNOWN_IN_INDEX = 1859
|
||||
IDENT_CAUSES_TOO_LONG_PATH = 1860
|
||||
ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL = 1861
|
||||
MUST_CHANGE_PASSWORD_LOGIN = 1862
|
||||
ROW_IN_WRONG_PARTITION = 1863
|
||||
MTS_EVENT_BIGGER_PENDING_JOBS_SIZE_MAX = 1864
|
||||
BINLOG_LOGICAL_CORRUPTION = 1866
|
||||
WARN_PURGE_LOG_IN_USE = 1867
|
||||
WARN_PURGE_LOG_IS_ACTIVE = 1868
|
||||
AUTO_INCREMENT_CONFLICT = 1869
|
||||
WARN_ON_BLOCKHOLE_IN_RBR = 1870
|
||||
SLAVE_MI_INIT_REPOSITORY = 1871
|
||||
SLAVE_RLI_INIT_REPOSITORY = 1872
|
||||
ACCESS_DENIED_CHANGE_USER_ERROR = 1873
|
||||
INNODB_READ_ONLY = 1874
|
||||
STOP_SLAVE_SQL_THREAD_TIMEOUT = 1875
|
||||
STOP_SLAVE_IO_THREAD_TIMEOUT = 1876
|
||||
TABLE_CORRUPT = 1877
|
||||
TEMP_FILE_WRITE_FAILURE = 1878
|
||||
INNODB_FT_AUX_NOT_HEX_ID = 1879
|
||||
OLD_TEMPORALS_UPGRADED = 1880
|
||||
INNODB_FORCED_RECOVERY = 1881
|
||||
AES_INVALID_IV = 1882
|
||||
PLUGIN_CANNOT_BE_UNINSTALLED = 1883
|
||||
GTID_UNSAFE_BINLOG_SPLITTABLE_STATEMENT_AND_ASSIGNED_GTID = 1884
|
||||
SLAVE_HAS_MORE_GTIDS_THAN_MASTER = 1885
|
||||
MISSING_KEY = 1886
|
||||
ERROR_LAST = 1973
|
@@ -1,40 +0,0 @@
|
||||
"""MySQL FIELD_TYPE Constants
|
||||
|
||||
These constants represent the various column (field) types that are
|
||||
supported by MySQL.
|
||||
"""
|
||||
|
||||
DECIMAL = 0
|
||||
TINY = 1
|
||||
SHORT = 2
|
||||
LONG = 3
|
||||
FLOAT = 4
|
||||
DOUBLE = 5
|
||||
NULL = 6
|
||||
TIMESTAMP = 7
|
||||
LONGLONG = 8
|
||||
INT24 = 9
|
||||
DATE = 10
|
||||
TIME = 11
|
||||
DATETIME = 12
|
||||
YEAR = 13
|
||||
# NEWDATE = 14 # Internal to MySQL.
|
||||
VARCHAR = 15
|
||||
BIT = 16
|
||||
# TIMESTAMP2 = 17
|
||||
# DATETIME2 = 18
|
||||
# TIME2 = 19
|
||||
JSON = 245
|
||||
NEWDECIMAL = 246
|
||||
ENUM = 247
|
||||
SET = 248
|
||||
TINY_BLOB = 249
|
||||
MEDIUM_BLOB = 250
|
||||
LONG_BLOB = 251
|
||||
BLOB = 252
|
||||
VAR_STRING = 253
|
||||
STRING = 254
|
||||
GEOMETRY = 255
|
||||
|
||||
CHAR = TINY
|
||||
INTERVAL = ENUM
|
@@ -1,23 +0,0 @@
|
||||
"""MySQL FLAG Constants
|
||||
|
||||
These flags are used along with the FIELD_TYPE to indicate various
|
||||
properties of columns in a result set.
|
||||
|
||||
"""
|
||||
|
||||
NOT_NULL = 1
|
||||
PRI_KEY = 2
|
||||
UNIQUE_KEY = 4
|
||||
MULTIPLE_KEY = 8
|
||||
BLOB = 16
|
||||
UNSIGNED = 32
|
||||
ZEROFILL = 64
|
||||
BINARY = 128
|
||||
ENUM = 256
|
||||
AUTO_INCREMENT = 512
|
||||
TIMESTAMP = 1024
|
||||
SET = 2048
|
||||
NUM = 32768
|
||||
PART_KEY = 16384
|
||||
GROUP = 32768
|
||||
UNIQUE = 65536
|
@@ -1 +0,0 @@
|
||||
__all__ = ["CR", "FIELD_TYPE", "CLIENT", "ER", "FLAG"]
|
@@ -1,139 +0,0 @@
|
||||
"""MySQLdb type conversion module
|
||||
|
||||
This module handles all the type conversions for MySQL. If the default
|
||||
type conversions aren't what you need, you can make your own. The
|
||||
dictionary conversions maps some kind of type to a conversion function
|
||||
which returns the corresponding value:
|
||||
|
||||
Key: FIELD_TYPE.* (from MySQLdb.constants)
|
||||
|
||||
Conversion function:
|
||||
|
||||
Arguments: string
|
||||
|
||||
Returns: Python object
|
||||
|
||||
Key: Python type object (from types) or class
|
||||
|
||||
Conversion function:
|
||||
|
||||
Arguments: Python object of indicated type or class AND
|
||||
conversion dictionary
|
||||
|
||||
Returns: SQL literal value
|
||||
|
||||
Notes: Most conversion functions can ignore the dictionary, but
|
||||
it is a required parameter. It is necessary for converting
|
||||
things like sequences and instances.
|
||||
|
||||
Don't modify conversions if you can avoid it. Instead, make copies
|
||||
(with the copy() method), modify the copies, and then pass them to
|
||||
MySQL.connect().
|
||||
"""
|
||||
from decimal import Decimal
|
||||
|
||||
from MySQLdb._mysql import string_literal
|
||||
from MySQLdb.constants import FIELD_TYPE, FLAG
|
||||
from MySQLdb.times import (
|
||||
Date,
|
||||
DateTimeType,
|
||||
DateTime2literal,
|
||||
DateTimeDeltaType,
|
||||
DateTimeDelta2literal,
|
||||
DateTime_or_None,
|
||||
TimeDelta_or_None,
|
||||
Date_or_None,
|
||||
)
|
||||
from MySQLdb._exceptions import ProgrammingError
|
||||
|
||||
import array
|
||||
|
||||
NoneType = type(None)
|
||||
|
||||
try:
|
||||
ArrayType = array.ArrayType
|
||||
except AttributeError:
|
||||
ArrayType = array.array
|
||||
|
||||
|
||||
def Bool2Str(s, d):
|
||||
return b"1" if s else b"0"
|
||||
|
||||
|
||||
def Set2Str(s, d):
|
||||
# Only support ascii string. Not tested.
|
||||
return string_literal(",".join(s))
|
||||
|
||||
|
||||
def Thing2Str(s, d):
|
||||
"""Convert something into a string via str()."""
|
||||
return str(s)
|
||||
|
||||
|
||||
def Float2Str(o, d):
|
||||
s = repr(o)
|
||||
if s in ("inf", "nan"):
|
||||
raise ProgrammingError("%s can not be used with MySQL" % s)
|
||||
if "e" not in s:
|
||||
s += "e0"
|
||||
return s
|
||||
|
||||
|
||||
def None2NULL(o, d):
|
||||
"""Convert None to NULL."""
|
||||
return b"NULL"
|
||||
|
||||
|
||||
def Thing2Literal(o, d):
|
||||
"""Convert something into a SQL string literal. If using
|
||||
MySQL-3.23 or newer, string_literal() is a method of the
|
||||
_mysql.MYSQL object, and this function will be overridden with
|
||||
that method when the connection is created."""
|
||||
return string_literal(o)
|
||||
|
||||
|
||||
def Decimal2Literal(o, d):
|
||||
return format(o, "f")
|
||||
|
||||
|
||||
def array2Str(o, d):
|
||||
return Thing2Literal(o.tostring(), d)
|
||||
|
||||
|
||||
# bytes or str regarding to BINARY_FLAG.
|
||||
_bytes_or_str = ((FLAG.BINARY, bytes), (None, str))
|
||||
|
||||
conversions = {
|
||||
int: Thing2Str,
|
||||
float: Float2Str,
|
||||
NoneType: None2NULL,
|
||||
ArrayType: array2Str,
|
||||
bool: Bool2Str,
|
||||
Date: Thing2Literal,
|
||||
DateTimeType: DateTime2literal,
|
||||
DateTimeDeltaType: DateTimeDelta2literal,
|
||||
set: Set2Str,
|
||||
Decimal: Decimal2Literal,
|
||||
FIELD_TYPE.TINY: int,
|
||||
FIELD_TYPE.SHORT: int,
|
||||
FIELD_TYPE.LONG: int,
|
||||
FIELD_TYPE.FLOAT: float,
|
||||
FIELD_TYPE.DOUBLE: float,
|
||||
FIELD_TYPE.DECIMAL: Decimal,
|
||||
FIELD_TYPE.NEWDECIMAL: Decimal,
|
||||
FIELD_TYPE.LONGLONG: int,
|
||||
FIELD_TYPE.INT24: int,
|
||||
FIELD_TYPE.YEAR: int,
|
||||
FIELD_TYPE.TIMESTAMP: DateTime_or_None,
|
||||
FIELD_TYPE.DATETIME: DateTime_or_None,
|
||||
FIELD_TYPE.TIME: TimeDelta_or_None,
|
||||
FIELD_TYPE.DATE: Date_or_None,
|
||||
FIELD_TYPE.TINY_BLOB: bytes,
|
||||
FIELD_TYPE.MEDIUM_BLOB: bytes,
|
||||
FIELD_TYPE.LONG_BLOB: bytes,
|
||||
FIELD_TYPE.BLOB: bytes,
|
||||
FIELD_TYPE.STRING: bytes,
|
||||
FIELD_TYPE.VAR_STRING: bytes,
|
||||
FIELD_TYPE.VARCHAR: bytes,
|
||||
FIELD_TYPE.JSON: bytes,
|
||||
}
|
@@ -1,489 +0,0 @@
|
||||
"""MySQLdb Cursors
|
||||
|
||||
This module implements Cursors of various types for MySQLdb. By
|
||||
default, MySQLdb uses the Cursor class.
|
||||
"""
|
||||
import re
|
||||
|
||||
from ._exceptions import ProgrammingError
|
||||
|
||||
|
||||
#: Regular expression for :meth:`Cursor.executemany`.
|
||||
#: executemany only supports simple bulk insert.
|
||||
#: You can use it to load large dataset.
|
||||
RE_INSERT_VALUES = re.compile(
|
||||
"".join(
|
||||
[
|
||||
r"\s*((?:INSERT|REPLACE)\b.+\bVALUES?\s*)",
|
||||
r"(\(\s*(?:%s|%\(.+\)s)\s*(?:,\s*(?:%s|%\(.+\)s)\s*)*\))",
|
||||
r"(\s*(?:ON DUPLICATE.*)?);?\s*\Z",
|
||||
]
|
||||
),
|
||||
re.IGNORECASE | re.DOTALL,
|
||||
)
|
||||
|
||||
|
||||
class BaseCursor:
|
||||
"""A base for Cursor classes. Useful attributes:
|
||||
|
||||
description
|
||||
A tuple of DB API 7-tuples describing the columns in
|
||||
the last executed query; see PEP-249 for details.
|
||||
|
||||
description_flags
|
||||
Tuple of column flags for last query, one entry per column
|
||||
in the result set. Values correspond to those in
|
||||
MySQLdb.constants.FLAG. See MySQL documentation (C API)
|
||||
for more information. Non-standard extension.
|
||||
|
||||
arraysize
|
||||
default number of rows fetchmany() will fetch
|
||||
"""
|
||||
|
||||
#: Max statement size which :meth:`executemany` generates.
|
||||
#:
|
||||
#: Max size of allowed statement is max_allowed_packet - packet_header_size.
|
||||
#: Default value of max_allowed_packet is 1048576.
|
||||
max_stmt_length = 64 * 1024
|
||||
|
||||
from ._exceptions import (
|
||||
MySQLError,
|
||||
Warning,
|
||||
Error,
|
||||
InterfaceError,
|
||||
DatabaseError,
|
||||
DataError,
|
||||
OperationalError,
|
||||
IntegrityError,
|
||||
InternalError,
|
||||
ProgrammingError,
|
||||
NotSupportedError,
|
||||
)
|
||||
|
||||
connection = None
|
||||
|
||||
def __init__(self, connection):
|
||||
self.connection = connection
|
||||
self.description = None
|
||||
self.description_flags = None
|
||||
self.rowcount = -1
|
||||
self.arraysize = 1
|
||||
self._executed = None
|
||||
|
||||
self.lastrowid = None
|
||||
self._result = None
|
||||
self.rownumber = None
|
||||
self._rows = None
|
||||
|
||||
def close(self):
|
||||
"""Close the cursor. No further queries will be possible."""
|
||||
try:
|
||||
if self.connection is None:
|
||||
return
|
||||
while self.nextset():
|
||||
pass
|
||||
finally:
|
||||
self.connection = None
|
||||
self._result = None
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *exc_info):
|
||||
del exc_info
|
||||
self.close()
|
||||
|
||||
def _escape_args(self, args, conn):
|
||||
encoding = conn.encoding
|
||||
literal = conn.literal
|
||||
|
||||
def ensure_bytes(x):
|
||||
if isinstance(x, str):
|
||||
return x.encode(encoding)
|
||||
elif isinstance(x, tuple):
|
||||
return tuple(map(ensure_bytes, x))
|
||||
elif isinstance(x, list):
|
||||
return list(map(ensure_bytes, x))
|
||||
return x
|
||||
|
||||
if isinstance(args, (tuple, list)):
|
||||
ret = tuple(literal(ensure_bytes(arg)) for arg in args)
|
||||
elif isinstance(args, dict):
|
||||
ret = {
|
||||
ensure_bytes(key): literal(ensure_bytes(val))
|
||||
for (key, val) in args.items()
|
||||
}
|
||||
else:
|
||||
# If it's not a dictionary let's try escaping it anyways.
|
||||
# Worst case it will throw a Value error
|
||||
ret = literal(ensure_bytes(args))
|
||||
|
||||
ensure_bytes = None # break circular reference
|
||||
return ret
|
||||
|
||||
def _check_executed(self):
|
||||
if not self._executed:
|
||||
raise ProgrammingError("execute() first")
|
||||
|
||||
def nextset(self):
|
||||
"""Advance to the next result set.
|
||||
|
||||
Returns None if there are no more result sets.
|
||||
"""
|
||||
if self._executed:
|
||||
self.fetchall()
|
||||
|
||||
db = self._get_db()
|
||||
nr = db.next_result()
|
||||
if nr == -1:
|
||||
return None
|
||||
self._do_get_result(db)
|
||||
self._post_get_result()
|
||||
return 1
|
||||
|
||||
def _do_get_result(self, db):
|
||||
self._result = result = self._get_result()
|
||||
if result is None:
|
||||
self.description = self.description_flags = None
|
||||
else:
|
||||
self.description = result.describe()
|
||||
self.description_flags = result.field_flags()
|
||||
|
||||
self.rowcount = db.affected_rows()
|
||||
self.rownumber = 0
|
||||
self.lastrowid = db.insert_id()
|
||||
|
||||
def _post_get_result(self):
|
||||
pass
|
||||
|
||||
def setinputsizes(self, *args):
|
||||
"""Does nothing, required by DB API."""
|
||||
|
||||
def setoutputsizes(self, *args):
|
||||
"""Does nothing, required by DB API."""
|
||||
|
||||
def _get_db(self):
|
||||
con = self.connection
|
||||
if con is None:
|
||||
raise ProgrammingError("cursor closed")
|
||||
return con
|
||||
|
||||
def execute(self, query, args=None):
|
||||
"""Execute a query.
|
||||
|
||||
query -- string, query to execute on server
|
||||
args -- optional sequence or mapping, parameters to use with query.
|
||||
|
||||
Note: If args is a sequence, then %s must be used as the
|
||||
parameter placeholder in the query. If a mapping is used,
|
||||
%(key)s must be used as the placeholder.
|
||||
|
||||
Returns integer represents rows affected, if any
|
||||
"""
|
||||
while self.nextset():
|
||||
pass
|
||||
db = self._get_db()
|
||||
|
||||
if isinstance(query, str):
|
||||
query = query.encode(db.encoding)
|
||||
|
||||
if args is not None:
|
||||
if isinstance(args, dict):
|
||||
nargs = {}
|
||||
for key, item in args.items():
|
||||
if isinstance(key, str):
|
||||
key = key.encode(db.encoding)
|
||||
nargs[key] = db.literal(item)
|
||||
args = nargs
|
||||
else:
|
||||
args = tuple(map(db.literal, args))
|
||||
try:
|
||||
query = query % args
|
||||
except TypeError as m:
|
||||
raise ProgrammingError(str(m))
|
||||
|
||||
assert isinstance(query, (bytes, bytearray))
|
||||
res = self._query(query)
|
||||
return res
|
||||
|
||||
def executemany(self, query, args):
|
||||
# type: (str, list) -> int
|
||||
"""Execute a multi-row query.
|
||||
|
||||
:param query: query to execute on server
|
||||
:param args: Sequence of sequences or mappings. It is used as parameter.
|
||||
:return: Number of rows affected, if any.
|
||||
|
||||
This method improves performance on multiple-row INSERT and
|
||||
REPLACE. Otherwise it is equivalent to looping over args with
|
||||
execute().
|
||||
"""
|
||||
if not args:
|
||||
return
|
||||
|
||||
m = RE_INSERT_VALUES.match(query)
|
||||
if m:
|
||||
q_prefix = m.group(1) % ()
|
||||
q_values = m.group(2).rstrip()
|
||||
q_postfix = m.group(3) or ""
|
||||
assert q_values[0] == "(" and q_values[-1] == ")"
|
||||
return self._do_execute_many(
|
||||
q_prefix,
|
||||
q_values,
|
||||
q_postfix,
|
||||
args,
|
||||
self.max_stmt_length,
|
||||
self._get_db().encoding,
|
||||
)
|
||||
|
||||
self.rowcount = sum(self.execute(query, arg) for arg in args)
|
||||
return self.rowcount
|
||||
|
||||
def _do_execute_many(
|
||||
self, prefix, values, postfix, args, max_stmt_length, encoding
|
||||
):
|
||||
conn = self._get_db()
|
||||
escape = self._escape_args
|
||||
if isinstance(prefix, str):
|
||||
prefix = prefix.encode(encoding)
|
||||
if isinstance(values, str):
|
||||
values = values.encode(encoding)
|
||||
if isinstance(postfix, str):
|
||||
postfix = postfix.encode(encoding)
|
||||
sql = bytearray(prefix)
|
||||
args = iter(args)
|
||||
v = values % escape(next(args), conn)
|
||||
sql += v
|
||||
rows = 0
|
||||
for arg in args:
|
||||
v = values % escape(arg, conn)
|
||||
if len(sql) + len(v) + len(postfix) + 1 > max_stmt_length:
|
||||
rows += self.execute(sql + postfix)
|
||||
sql = bytearray(prefix)
|
||||
else:
|
||||
sql += b","
|
||||
sql += v
|
||||
rows += self.execute(sql + postfix)
|
||||
self.rowcount = rows
|
||||
return rows
|
||||
|
||||
def callproc(self, procname, args=()):
|
||||
"""Execute stored procedure procname with args
|
||||
|
||||
procname -- string, name of procedure to execute on server
|
||||
|
||||
args -- Sequence of parameters to use with procedure
|
||||
|
||||
Returns the original args.
|
||||
|
||||
Compatibility warning: PEP-249 specifies that any modified
|
||||
parameters must be returned. This is currently impossible
|
||||
as they are only available by storing them in a server
|
||||
variable and then retrieved by a query. Since stored
|
||||
procedures return zero or more result sets, there is no
|
||||
reliable way to get at OUT or INOUT parameters via callproc.
|
||||
The server variables are named @_procname_n, where procname
|
||||
is the parameter above and n is the position of the parameter
|
||||
(from zero). Once all result sets generated by the procedure
|
||||
have been fetched, you can issue a SELECT @_procname_0, ...
|
||||
query using .execute() to get any OUT or INOUT values.
|
||||
|
||||
Compatibility warning: The act of calling a stored procedure
|
||||
itself creates an empty result set. This appears after any
|
||||
result sets generated by the procedure. This is non-standard
|
||||
behavior with respect to the DB-API. Be sure to use nextset()
|
||||
to advance through all result sets; otherwise you may get
|
||||
disconnected.
|
||||
"""
|
||||
db = self._get_db()
|
||||
if isinstance(procname, str):
|
||||
procname = procname.encode(db.encoding)
|
||||
if args:
|
||||
fmt = b"@_" + procname + b"_%d=%s"
|
||||
q = b"SET %s" % b",".join(
|
||||
fmt % (index, db.literal(arg)) for index, arg in enumerate(args)
|
||||
)
|
||||
self._query(q)
|
||||
self.nextset()
|
||||
|
||||
q = b"CALL %s(%s)" % (
|
||||
procname,
|
||||
b",".join([b"@_%s_%d" % (procname, i) for i in range(len(args))]),
|
||||
)
|
||||
self._query(q)
|
||||
return args
|
||||
|
||||
def _query(self, q):
|
||||
db = self._get_db()
|
||||
self._result = None
|
||||
db.query(q)
|
||||
self._do_get_result(db)
|
||||
self._post_get_result()
|
||||
self._executed = q
|
||||
return self.rowcount
|
||||
|
||||
def _fetch_row(self, size=1):
|
||||
if not self._result:
|
||||
return ()
|
||||
return self._result.fetch_row(size, self._fetch_type)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.fetchone, None)
|
||||
|
||||
Warning = Warning
|
||||
Error = Error
|
||||
InterfaceError = InterfaceError
|
||||
DatabaseError = DatabaseError
|
||||
DataError = DataError
|
||||
OperationalError = OperationalError
|
||||
IntegrityError = IntegrityError
|
||||
InternalError = InternalError
|
||||
ProgrammingError = ProgrammingError
|
||||
NotSupportedError = NotSupportedError
|
||||
|
||||
|
||||
class CursorStoreResultMixIn:
|
||||
"""This is a MixIn class which causes the entire result set to be
|
||||
stored on the client side, i.e. it uses mysql_store_result(). If the
|
||||
result set can be very large, consider adding a LIMIT clause to your
|
||||
query, or using CursorUseResultMixIn instead."""
|
||||
|
||||
def _get_result(self):
|
||||
return self._get_db().store_result()
|
||||
|
||||
def _post_get_result(self):
|
||||
self._rows = self._fetch_row(0)
|
||||
self._result = None
|
||||
|
||||
def fetchone(self):
|
||||
"""Fetches a single row from the cursor. None indicates that
|
||||
no more rows are available."""
|
||||
self._check_executed()
|
||||
if self.rownumber >= len(self._rows):
|
||||
return None
|
||||
result = self._rows[self.rownumber]
|
||||
self.rownumber = self.rownumber + 1
|
||||
return result
|
||||
|
||||
def fetchmany(self, size=None):
|
||||
"""Fetch up to size rows from the cursor. Result set may be smaller
|
||||
than size. If size is not defined, cursor.arraysize is used."""
|
||||
self._check_executed()
|
||||
end = self.rownumber + (size or self.arraysize)
|
||||
result = self._rows[self.rownumber : end]
|
||||
self.rownumber = min(end, len(self._rows))
|
||||
return result
|
||||
|
||||
def fetchall(self):
|
||||
"""Fetches all available rows from the cursor."""
|
||||
self._check_executed()
|
||||
if self.rownumber:
|
||||
result = self._rows[self.rownumber :]
|
||||
else:
|
||||
result = self._rows
|
||||
self.rownumber = len(self._rows)
|
||||
return result
|
||||
|
||||
def scroll(self, value, mode="relative"):
|
||||
"""Scroll the cursor in the result set to a new position according
|
||||
to mode.
|
||||
|
||||
If mode is 'relative' (default), value is taken as offset to
|
||||
the current position in the result set, if set to 'absolute',
|
||||
value states an absolute target position."""
|
||||
self._check_executed()
|
||||
if mode == "relative":
|
||||
r = self.rownumber + value
|
||||
elif mode == "absolute":
|
||||
r = value
|
||||
else:
|
||||
raise ProgrammingError("unknown scroll mode %s" % repr(mode))
|
||||
if r < 0 or r >= len(self._rows):
|
||||
raise IndexError("out of range")
|
||||
self.rownumber = r
|
||||
|
||||
def __iter__(self):
|
||||
self._check_executed()
|
||||
result = self.rownumber and self._rows[self.rownumber :] or self._rows
|
||||
return iter(result)
|
||||
|
||||
|
||||
class CursorUseResultMixIn:
|
||||
|
||||
"""This is a MixIn class which causes the result set to be stored
|
||||
in the server and sent row-by-row to client side, i.e. it uses
|
||||
mysql_use_result(). You MUST retrieve the entire result set and
|
||||
close() the cursor before additional queries can be performed on
|
||||
the connection."""
|
||||
|
||||
def _get_result(self):
|
||||
return self._get_db().use_result()
|
||||
|
||||
def fetchone(self):
|
||||
"""Fetches a single row from the cursor."""
|
||||
self._check_executed()
|
||||
r = self._fetch_row(1)
|
||||
if not r:
|
||||
return None
|
||||
self.rownumber = self.rownumber + 1
|
||||
return r[0]
|
||||
|
||||
def fetchmany(self, size=None):
|
||||
"""Fetch up to size rows from the cursor. Result set may be smaller
|
||||
than size. If size is not defined, cursor.arraysize is used."""
|
||||
self._check_executed()
|
||||
r = self._fetch_row(size or self.arraysize)
|
||||
self.rownumber = self.rownumber + len(r)
|
||||
return r
|
||||
|
||||
def fetchall(self):
|
||||
"""Fetches all available rows from the cursor."""
|
||||
self._check_executed()
|
||||
r = self._fetch_row(0)
|
||||
self.rownumber = self.rownumber + len(r)
|
||||
return r
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def next(self):
|
||||
row = self.fetchone()
|
||||
if row is None:
|
||||
raise StopIteration
|
||||
return row
|
||||
|
||||
__next__ = next
|
||||
|
||||
|
||||
class CursorTupleRowsMixIn:
|
||||
"""This is a MixIn class that causes all rows to be returned as tuples,
|
||||
which is the standard form required by DB API."""
|
||||
|
||||
_fetch_type = 0
|
||||
|
||||
|
||||
class CursorDictRowsMixIn:
|
||||
"""This is a MixIn class that causes all rows to be returned as
|
||||
dictionaries. This is a non-standard feature."""
|
||||
|
||||
_fetch_type = 1
|
||||
|
||||
|
||||
class Cursor(CursorStoreResultMixIn, CursorTupleRowsMixIn, BaseCursor):
|
||||
"""This is the standard Cursor class that returns rows as tuples
|
||||
and stores the result set in the client."""
|
||||
|
||||
|
||||
class DictCursor(CursorStoreResultMixIn, CursorDictRowsMixIn, BaseCursor):
|
||||
"""This is a Cursor class that returns rows as dictionaries and
|
||||
stores the result set in the client."""
|
||||
|
||||
|
||||
class SSCursor(CursorUseResultMixIn, CursorTupleRowsMixIn, BaseCursor):
|
||||
"""This is a Cursor class that returns rows as tuples and stores
|
||||
the result set in the server."""
|
||||
|
||||
|
||||
class SSDictCursor(CursorUseResultMixIn, CursorDictRowsMixIn, BaseCursor):
|
||||
"""This is a Cursor class that returns rows as dictionaries and
|
||||
stores the result set in the server."""
|
@@ -1,4 +0,0 @@
|
||||
|
||||
__author__ = "Inada Naoki <songofacandy@gmail.com>"
|
||||
version_info = (2,1,0,'final',0)
|
||||
__version__ = "2.1.0"
|
@@ -1,150 +0,0 @@
|
||||
"""times module
|
||||
|
||||
This module provides some Date and Time classes for dealing with MySQL data.
|
||||
|
||||
Use Python datetime module to handle date and time columns.
|
||||
"""
|
||||
from time import localtime
|
||||
from datetime import date, datetime, time, timedelta
|
||||
from MySQLdb._mysql import string_literal
|
||||
|
||||
Date = date
|
||||
Time = time
|
||||
TimeDelta = timedelta
|
||||
Timestamp = datetime
|
||||
|
||||
DateTimeDeltaType = timedelta
|
||||
DateTimeType = datetime
|
||||
|
||||
|
||||
def DateFromTicks(ticks):
|
||||
"""Convert UNIX ticks into a date instance."""
|
||||
return date(*localtime(ticks)[:3])
|
||||
|
||||
|
||||
def TimeFromTicks(ticks):
|
||||
"""Convert UNIX ticks into a time instance."""
|
||||
return time(*localtime(ticks)[3:6])
|
||||
|
||||
|
||||
def TimestampFromTicks(ticks):
|
||||
"""Convert UNIX ticks into a datetime instance."""
|
||||
return datetime(*localtime(ticks)[:6])
|
||||
|
||||
|
||||
format_TIME = format_DATE = str
|
||||
|
||||
|
||||
def format_TIMEDELTA(v):
|
||||
seconds = int(v.seconds) % 60
|
||||
minutes = int(v.seconds // 60) % 60
|
||||
hours = int(v.seconds // 3600) % 24
|
||||
return "%d %d:%d:%d" % (v.days, hours, minutes, seconds)
|
||||
|
||||
|
||||
def format_TIMESTAMP(d):
|
||||
"""
|
||||
:type d: datetime.datetime
|
||||
"""
|
||||
if d.microsecond:
|
||||
fmt = " ".join(
|
||||
[
|
||||
"{0.year:04}-{0.month:02}-{0.day:02}",
|
||||
"{0.hour:02}:{0.minute:02}:{0.second:02}.{0.microsecond:06}",
|
||||
]
|
||||
)
|
||||
else:
|
||||
fmt = " ".join(
|
||||
[
|
||||
"{0.year:04}-{0.month:02}-{0.day:02}",
|
||||
"{0.hour:02}:{0.minute:02}:{0.second:02}",
|
||||
]
|
||||
)
|
||||
return fmt.format(d)
|
||||
|
||||
|
||||
def DateTime_or_None(s):
|
||||
try:
|
||||
if len(s) < 11:
|
||||
return Date_or_None(s)
|
||||
|
||||
micros = s[20:]
|
||||
|
||||
if len(micros) == 0:
|
||||
# 12:00:00
|
||||
micros = 0
|
||||
elif len(micros) < 7:
|
||||
# 12:00:00.123456
|
||||
micros = int(micros) * 10 ** (6 - len(micros))
|
||||
else:
|
||||
return None
|
||||
|
||||
return datetime(
|
||||
int(s[:4]), # year
|
||||
int(s[5:7]), # month
|
||||
int(s[8:10]), # day
|
||||
int(s[11:13] or 0), # hour
|
||||
int(s[14:16] or 0), # minute
|
||||
int(s[17:19] or 0), # second
|
||||
micros, # microsecond
|
||||
)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def TimeDelta_or_None(s):
|
||||
try:
|
||||
h, m, s = s.split(":")
|
||||
if "." in s:
|
||||
s, ms = s.split(".")
|
||||
ms = ms.ljust(6, "0")
|
||||
else:
|
||||
ms = 0
|
||||
if h[0] == "-":
|
||||
negative = True
|
||||
else:
|
||||
negative = False
|
||||
h, m, s, ms = abs(int(h)), int(m), int(s), int(ms)
|
||||
td = timedelta(hours=h, minutes=m, seconds=s, microseconds=ms)
|
||||
if negative:
|
||||
return -td
|
||||
else:
|
||||
return td
|
||||
except ValueError:
|
||||
# unpacking or int/float conversion failed
|
||||
return None
|
||||
|
||||
|
||||
def Time_or_None(s):
|
||||
try:
|
||||
h, m, s = s.split(":")
|
||||
if "." in s:
|
||||
s, ms = s.split(".")
|
||||
ms = ms.ljust(6, "0")
|
||||
else:
|
||||
ms = 0
|
||||
h, m, s, ms = int(h), int(m), int(s), int(ms)
|
||||
return time(hour=h, minute=m, second=s, microsecond=ms)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def Date_or_None(s):
|
||||
try:
|
||||
return date(
|
||||
int(s[:4]),
|
||||
int(s[5:7]),
|
||||
int(s[8:10]),
|
||||
) # year # month # day
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def DateTime2literal(d, c):
|
||||
"""Format a DateTime object as an ISO timestamp."""
|
||||
return string_literal(format_TIMESTAMP(d))
|
||||
|
||||
|
||||
def DateTimeDelta2literal(d, c):
|
||||
"""Format a DateTimeDelta object as a time."""
|
||||
return string_literal(format_TIMEDELTA(d))
|
@@ -1,222 +0,0 @@
|
||||
# don't import any costly modules
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
is_pypy = '__pypy__' in sys.builtin_module_names
|
||||
|
||||
|
||||
def warn_distutils_present():
|
||||
if 'distutils' not in sys.modules:
|
||||
return
|
||||
if is_pypy and sys.version_info < (3, 7):
|
||||
# PyPy for 3.6 unconditionally imports distutils, so bypass the warning
|
||||
# https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250
|
||||
return
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"Distutils was imported before Setuptools, but importing Setuptools "
|
||||
"also replaces the `distutils` module in `sys.modules`. This may lead "
|
||||
"to undesirable behaviors or errors. To avoid these issues, avoid "
|
||||
"using distutils directly, ensure that setuptools is installed in the "
|
||||
"traditional way (e.g. not an editable install), and/or make sure "
|
||||
"that setuptools is always imported before distutils."
|
||||
)
|
||||
|
||||
|
||||
def clear_distutils():
|
||||
if 'distutils' not in sys.modules:
|
||||
return
|
||||
import warnings
|
||||
|
||||
warnings.warn("Setuptools is replacing distutils.")
|
||||
mods = [
|
||||
name
|
||||
for name in sys.modules
|
||||
if name == "distutils" or name.startswith("distutils.")
|
||||
]
|
||||
for name in mods:
|
||||
del sys.modules[name]
|
||||
|
||||
|
||||
def enabled():
|
||||
"""
|
||||
Allow selection of distutils by environment variable.
|
||||
"""
|
||||
which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'local')
|
||||
return which == 'local'
|
||||
|
||||
|
||||
def ensure_local_distutils():
|
||||
import importlib
|
||||
|
||||
clear_distutils()
|
||||
|
||||
# With the DistutilsMetaFinder in place,
|
||||
# perform an import to cause distutils to be
|
||||
# loaded from setuptools._distutils. Ref #2906.
|
||||
with shim():
|
||||
importlib.import_module('distutils')
|
||||
|
||||
# check that submodules load as expected
|
||||
core = importlib.import_module('distutils.core')
|
||||
assert '_distutils' in core.__file__, core.__file__
|
||||
assert 'setuptools._distutils.log' not in sys.modules
|
||||
|
||||
|
||||
def do_override():
|
||||
"""
|
||||
Ensure that the local copy of distutils is preferred over stdlib.
|
||||
|
||||
See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401
|
||||
for more motivation.
|
||||
"""
|
||||
if enabled():
|
||||
warn_distutils_present()
|
||||
ensure_local_distutils()
|
||||
|
||||
|
||||
class _TrivialRe:
|
||||
def __init__(self, *patterns):
|
||||
self._patterns = patterns
|
||||
|
||||
def match(self, string):
|
||||
return all(pat in string for pat in self._patterns)
|
||||
|
||||
|
||||
class DistutilsMetaFinder:
|
||||
def find_spec(self, fullname, path, target=None):
|
||||
# optimization: only consider top level modules and those
|
||||
# found in the CPython test suite.
|
||||
if path is not None and not fullname.startswith('test.'):
|
||||
return
|
||||
|
||||
method_name = 'spec_for_{fullname}'.format(**locals())
|
||||
method = getattr(self, method_name, lambda: None)
|
||||
return method()
|
||||
|
||||
def spec_for_distutils(self):
|
||||
if self.is_cpython():
|
||||
return
|
||||
|
||||
import importlib
|
||||
import importlib.abc
|
||||
import importlib.util
|
||||
|
||||
try:
|
||||
mod = importlib.import_module('setuptools._distutils')
|
||||
except Exception:
|
||||
# There are a couple of cases where setuptools._distutils
|
||||
# may not be present:
|
||||
# - An older Setuptools without a local distutils is
|
||||
# taking precedence. Ref #2957.
|
||||
# - Path manipulation during sitecustomize removes
|
||||
# setuptools from the path but only after the hook
|
||||
# has been loaded. Ref #2980.
|
||||
# In either case, fall back to stdlib behavior.
|
||||
return
|
||||
|
||||
class DistutilsLoader(importlib.abc.Loader):
|
||||
def create_module(self, spec):
|
||||
mod.__name__ = 'distutils'
|
||||
return mod
|
||||
|
||||
def exec_module(self, module):
|
||||
pass
|
||||
|
||||
return importlib.util.spec_from_loader(
|
||||
'distutils', DistutilsLoader(), origin=mod.__file__
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def is_cpython():
|
||||
"""
|
||||
Suppress supplying distutils for CPython (build and tests).
|
||||
Ref #2965 and #3007.
|
||||
"""
|
||||
return os.path.isfile('pybuilddir.txt')
|
||||
|
||||
def spec_for_pip(self):
|
||||
"""
|
||||
Ensure stdlib distutils when running under pip.
|
||||
See pypa/pip#8761 for rationale.
|
||||
"""
|
||||
if self.pip_imported_during_build():
|
||||
return
|
||||
clear_distutils()
|
||||
self.spec_for_distutils = lambda: None
|
||||
|
||||
@classmethod
|
||||
def pip_imported_during_build(cls):
|
||||
"""
|
||||
Detect if pip is being imported in a build script. Ref #2355.
|
||||
"""
|
||||
import traceback
|
||||
|
||||
return any(
|
||||
cls.frame_file_is_setup(frame) for frame, line in traceback.walk_stack(None)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def frame_file_is_setup(frame):
|
||||
"""
|
||||
Return True if the indicated frame suggests a setup.py file.
|
||||
"""
|
||||
# some frames may not have __file__ (#2940)
|
||||
return frame.f_globals.get('__file__', '').endswith('setup.py')
|
||||
|
||||
def spec_for_sensitive_tests(self):
|
||||
"""
|
||||
Ensure stdlib distutils when running select tests under CPython.
|
||||
|
||||
python/cpython#91169
|
||||
"""
|
||||
clear_distutils()
|
||||
self.spec_for_distutils = lambda: None
|
||||
|
||||
sensitive_tests = (
|
||||
[
|
||||
'test.test_distutils',
|
||||
'test.test_peg_generator',
|
||||
'test.test_importlib',
|
||||
]
|
||||
if sys.version_info < (3, 10)
|
||||
else [
|
||||
'test.test_distutils',
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
for name in DistutilsMetaFinder.sensitive_tests:
|
||||
setattr(
|
||||
DistutilsMetaFinder,
|
||||
f'spec_for_{name}',
|
||||
DistutilsMetaFinder.spec_for_sensitive_tests,
|
||||
)
|
||||
|
||||
|
||||
DISTUTILS_FINDER = DistutilsMetaFinder()
|
||||
|
||||
|
||||
def add_shim():
|
||||
DISTUTILS_FINDER in sys.meta_path or insert_shim()
|
||||
|
||||
|
||||
class shim:
|
||||
def __enter__(self):
|
||||
insert_shim()
|
||||
|
||||
def __exit__(self, exc, value, tb):
|
||||
remove_shim()
|
||||
|
||||
|
||||
def insert_shim():
|
||||
sys.meta_path.insert(0, DISTUTILS_FINDER)
|
||||
|
||||
|
||||
def remove_shim():
|
||||
try:
|
||||
sys.meta_path.remove(DISTUTILS_FINDER)
|
||||
except ValueError:
|
||||
pass
|
@@ -1 +0,0 @@
|
||||
__import__('_distutils_hack').do_override()
|
@@ -1 +0,0 @@
|
||||
pip
|
@@ -1,27 +0,0 @@
|
||||
Copyright (c) Django Software Foundation and individual contributors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of Django nor the names of its contributors may be used
|
||||
to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@@ -1,248 +0,0 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: asgiref
|
||||
Version: 3.4.1
|
||||
Summary: ASGI specs, helper code, and adapters
|
||||
Home-page: https://github.com/django/asgiref/
|
||||
Author: Django Software Foundation
|
||||
Author-email: foundation@djangoproject.com
|
||||
License: BSD
|
||||
Project-URL: Documentation, https://asgi.readthedocs.io/
|
||||
Project-URL: Further Documentation, https://docs.djangoproject.com/en/stable/topics/async/#async-adapter-functions
|
||||
Project-URL: Changelog, https://github.com/django/asgiref/blob/master/CHANGELOG.txt
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Environment :: Web Environment
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3 :: Only
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Topic :: Internet :: WWW/HTTP
|
||||
Requires-Python: >=3.6
|
||||
License-File: LICENSE
|
||||
Requires-Dist: typing-extensions ; python_version < "3.8"
|
||||
Provides-Extra: tests
|
||||
Requires-Dist: pytest ; extra == 'tests'
|
||||
Requires-Dist: pytest-asyncio ; extra == 'tests'
|
||||
Requires-Dist: mypy (>=0.800) ; extra == 'tests'
|
||||
|
||||
asgiref
|
||||
=======
|
||||
|
||||
.. image:: https://api.travis-ci.org/django/asgiref.svg
|
||||
:target: https://travis-ci.org/django/asgiref
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/asgiref.svg
|
||||
:target: https://pypi.python.org/pypi/asgiref
|
||||
|
||||
ASGI is a standard for Python asynchronous web apps and servers to communicate
|
||||
with each other, and positioned as an asynchronous successor to WSGI. You can
|
||||
read more at https://asgi.readthedocs.io/en/latest/
|
||||
|
||||
This package includes ASGI base libraries, such as:
|
||||
|
||||
* Sync-to-async and async-to-sync function wrappers, ``asgiref.sync``
|
||||
* Server base classes, ``asgiref.server``
|
||||
* A WSGI-to-ASGI adapter, in ``asgiref.wsgi``
|
||||
|
||||
|
||||
Function wrappers
|
||||
-----------------
|
||||
|
||||
These allow you to wrap or decorate async or sync functions to call them from
|
||||
the other style (so you can call async functions from a synchronous thread,
|
||||
or vice-versa).
|
||||
|
||||
In particular:
|
||||
|
||||
* AsyncToSync lets a synchronous subthread stop and wait while the async
|
||||
function is called on the main thread's event loop, and then control is
|
||||
returned to the thread when the async function is finished.
|
||||
|
||||
* SyncToAsync lets async code call a synchronous function, which is run in
|
||||
a threadpool and control returned to the async coroutine when the synchronous
|
||||
function completes.
|
||||
|
||||
The idea is to make it easier to call synchronous APIs from async code and
|
||||
asynchronous APIs from synchronous code so it's easier to transition code from
|
||||
one style to the other. In the case of Channels, we wrap the (synchronous)
|
||||
Django view system with SyncToAsync to allow it to run inside the (asynchronous)
|
||||
ASGI server.
|
||||
|
||||
Note that exactly what threads things run in is very specific, and aimed to
|
||||
keep maximum compatibility with old synchronous code. See
|
||||
"Synchronous code & Threads" below for a full explanation. By default,
|
||||
``sync_to_async`` will run all synchronous code in the program in the same
|
||||
thread for safety reasons; you can disable this for more performance with
|
||||
``@sync_to_async(thread_sensitive=False)``, but make sure that your code does
|
||||
not rely on anything bound to threads (like database connections) when you do.
|
||||
|
||||
|
||||
Threadlocal replacement
|
||||
-----------------------
|
||||
|
||||
This is a drop-in replacement for ``threading.local`` that works with both
|
||||
threads and asyncio Tasks. Even better, it will proxy values through from a
|
||||
task-local context to a thread-local context when you use ``sync_to_async``
|
||||
to run things in a threadpool, and vice-versa for ``async_to_sync``.
|
||||
|
||||
If you instead want true thread- and task-safety, you can set
|
||||
``thread_critical`` on the Local object to ensure this instead.
|
||||
|
||||
|
||||
Server base classes
|
||||
-------------------
|
||||
|
||||
Includes a ``StatelessServer`` class which provides all the hard work of
|
||||
writing a stateless server (as in, does not handle direct incoming sockets
|
||||
but instead consumes external streams or sockets to work out what is happening).
|
||||
|
||||
An example of such a server would be a chatbot server that connects out to
|
||||
a central chat server and provides a "connection scope" per user chatting to
|
||||
it. There's only one actual connection, but the server has to separate things
|
||||
into several scopes for easier writing of the code.
|
||||
|
||||
You can see an example of this being used in `frequensgi <https://github.com/andrewgodwin/frequensgi>`_.
|
||||
|
||||
|
||||
WSGI-to-ASGI adapter
|
||||
--------------------
|
||||
|
||||
Allows you to wrap a WSGI application so it appears as a valid ASGI application.
|
||||
|
||||
Simply wrap it around your WSGI application like so::
|
||||
|
||||
asgi_application = WsgiToAsgi(wsgi_application)
|
||||
|
||||
The WSGI application will be run in a synchronous threadpool, and the wrapped
|
||||
ASGI application will be one that accepts ``http`` class messages.
|
||||
|
||||
Please note that not all extended features of WSGI may be supported (such as
|
||||
file handles for incoming POST bodies).
|
||||
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
``asgiref`` requires Python 3.6 or higher.
|
||||
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
Please refer to the
|
||||
`main Channels contributing docs <https://github.com/django/channels/blob/master/CONTRIBUTING.rst>`_.
|
||||
|
||||
|
||||
Testing
|
||||
'''''''
|
||||
|
||||
To run tests, make sure you have installed the ``tests`` extra with the package::
|
||||
|
||||
cd asgiref/
|
||||
pip install -e .[tests]
|
||||
pytest
|
||||
|
||||
|
||||
Building the documentation
|
||||
''''''''''''''''''''''''''
|
||||
|
||||
The documentation uses `Sphinx <http://www.sphinx-doc.org>`_::
|
||||
|
||||
cd asgiref/docs/
|
||||
pip install sphinx
|
||||
|
||||
To build the docs, you can use the default tools::
|
||||
|
||||
sphinx-build -b html . _build/html # or `make html`, if you've got make set up
|
||||
cd _build/html
|
||||
python -m http.server
|
||||
|
||||
...or you can use ``sphinx-autobuild`` to run a server and rebuild/reload
|
||||
your documentation changes automatically::
|
||||
|
||||
pip install sphinx-autobuild
|
||||
sphinx-autobuild . _build/html
|
||||
|
||||
|
||||
Releasing
|
||||
'''''''''
|
||||
|
||||
To release, first add details to CHANGELOG.txt and update the version number in ``asgiref/__init__.py``.
|
||||
|
||||
Then, build and push the packages::
|
||||
|
||||
python -m build
|
||||
twine upload dist/*
|
||||
rm -r build/ dist/
|
||||
|
||||
|
||||
Implementation Details
|
||||
----------------------
|
||||
|
||||
Synchronous code & threads
|
||||
''''''''''''''''''''''''''
|
||||
|
||||
The ``asgiref.sync`` module provides two wrappers that let you go between
|
||||
asynchronous and synchronous code at will, while taking care of the rough edges
|
||||
for you.
|
||||
|
||||
Unfortunately, the rough edges are numerous, and the code has to work especially
|
||||
hard to keep things in the same thread as much as possible. Notably, the
|
||||
restrictions we are working with are:
|
||||
|
||||
* All synchronous code called through ``SyncToAsync`` and marked with
|
||||
``thread_sensitive`` should run in the same thread as each other (and if the
|
||||
outer layer of the program is synchronous, the main thread)
|
||||
|
||||
* If a thread already has a running async loop, ``AsyncToSync`` can't run things
|
||||
on that loop if it's blocked on synchronous code that is above you in the
|
||||
call stack.
|
||||
|
||||
The first compromise you get to might be that ``thread_sensitive`` code should
|
||||
just run in the same thread and not spawn in a sub-thread, fulfilling the first
|
||||
restriction, but that immediately runs you into the second restriction.
|
||||
|
||||
The only real solution is to essentially have a variant of ThreadPoolExecutor
|
||||
that executes any ``thread_sensitive`` code on the outermost synchronous
|
||||
thread - either the main thread, or a single spawned subthread.
|
||||
|
||||
This means you now have two basic states:
|
||||
|
||||
* If the outermost layer of your program is synchronous, then all async code
|
||||
run through ``AsyncToSync`` will run in a per-call event loop in arbitrary
|
||||
sub-threads, while all ``thread_sensitive`` code will run in the main thread.
|
||||
|
||||
* If the outermost layer of your program is asynchronous, then all async code
|
||||
runs on the main thread's event loop, and all ``thread_sensitive`` synchronous
|
||||
code will run in a single shared sub-thread.
|
||||
|
||||
Crucially, this means that in both cases there is a thread which is a shared
|
||||
resource that all ``thread_sensitive`` code must run on, and there is a chance
|
||||
that this thread is currently blocked on its own ``AsyncToSync`` call. Thus,
|
||||
``AsyncToSync`` needs to act as an executor for thread code while it's blocking.
|
||||
|
||||
The ``CurrentThreadExecutor`` class provides this functionality; rather than
|
||||
simply waiting on a Future, you can call its ``run_until_future`` method and
|
||||
it will run submitted code until that Future is done. This means that code
|
||||
inside the call can then run code on your thread.
|
||||
|
||||
|
||||
Maintenance and Security
|
||||
------------------------
|
||||
|
||||
To report security issues, please contact security@djangoproject.com. For GPG
|
||||
signatures and more security process information, see
|
||||
https://docs.djangoproject.com/en/dev/internals/security/.
|
||||
|
||||
To report bugs or request new features, please open a new GitHub issue.
|
||||
|
||||
This repository is part of the Channels project. For the shepherd and maintenance team, please see the
|
||||
`main Channels readme <https://github.com/django/channels/blob/master/README.rst>`_.
|
||||
|
||||
|
@@ -1,29 +0,0 @@
|
||||
asgiref-3.4.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
asgiref-3.4.1.dist-info/LICENSE,sha256=uEZBXRtRTpwd_xSiLeuQbXlLxUbKYSn5UKGM0JHipmk,1552
|
||||
asgiref-3.4.1.dist-info/METADATA,sha256=TZrVDUz2BP8ewHAkOyGQ8izCsiaq6YHMvj_TW5W7i2E,9162
|
||||
asgiref-3.4.1.dist-info/RECORD,,
|
||||
asgiref-3.4.1.dist-info/WHEEL,sha256=OqRkF0eY5GHssMorFjlbTIq072vpHpF60fIQA6lS9xA,92
|
||||
asgiref-3.4.1.dist-info/top_level.txt,sha256=bokQjCzwwERhdBiPdvYEZa4cHxT4NCeAffQNUqJ8ssg,8
|
||||
asgiref/__init__.py,sha256=z3MJNttjzZJkd4Yv_Ut_X2qO_gIKi4TijrHVpefXuRM,22
|
||||
asgiref/__pycache__/__init__.cpython-310.pyc,,
|
||||
asgiref/__pycache__/_pep562.cpython-310.pyc,,
|
||||
asgiref/__pycache__/compatibility.cpython-310.pyc,,
|
||||
asgiref/__pycache__/current_thread_executor.cpython-310.pyc,,
|
||||
asgiref/__pycache__/local.cpython-310.pyc,,
|
||||
asgiref/__pycache__/server.cpython-310.pyc,,
|
||||
asgiref/__pycache__/sync.cpython-310.pyc,,
|
||||
asgiref/__pycache__/testing.cpython-310.pyc,,
|
||||
asgiref/__pycache__/timeout.cpython-310.pyc,,
|
||||
asgiref/__pycache__/typing.cpython-310.pyc,,
|
||||
asgiref/__pycache__/wsgi.cpython-310.pyc,,
|
||||
asgiref/_pep562.py,sha256=fyD3JhfLtViIGeXBtvhhbnbQ-R_8-nmwzbXHhncY6ow,2684
|
||||
asgiref/compatibility.py,sha256=4Plx8PT3wlDzZeuCN2cATfaXq6rya1OuSdJ262I5S6Y,2022
|
||||
asgiref/current_thread_executor.py,sha256=oeH8zv2tTmcbpxdUmOSMzbEXzeY5nJzIMFvzprE95gA,2801
|
||||
asgiref/local.py,sha256=D9kRIDARSUixNbxK8HL2O8vFhRCx_fc3fFB9uv0vG-g,4892
|
||||
asgiref/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
asgiref/server.py,sha256=lAxZxOxkdvxB073ZtYOAzN1JZ8aV-DOiFpVQJZ0X2FI,6018
|
||||
asgiref/sync.py,sha256=LCEHMPNiuoVtKrmI0kksoHeFjoSS5zLEG-n95UNd91Q,20310
|
||||
asgiref/testing.py,sha256=3byNRV7Oto_Fg8Z-fErQJ3yGf7OQlcUexbN_cDQugzQ,3119
|
||||
asgiref/timeout.py,sha256=UUYuUSY30dsqBsVzVAS7z9raQ9ntZGktScJw_Y_9iSU,3889
|
||||
asgiref/typing.py,sha256=-2wmtHqkhzV52rbMfipGTJmo8jUoU0i5AQECFH6y7aY,6722
|
||||
asgiref/wsgi.py,sha256=-L0eo_uK_dq7EPjv1meW1BRGytURaO9NPESxnJc9CtA,6575
|
@@ -1,5 +0,0 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.36.2)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
@@ -1 +0,0 @@
|
||||
asgiref
|
@@ -1 +0,0 @@
|
||||
__version__ = "3.4.1"
|
@@ -1,61 +0,0 @@
|
||||
"""
|
||||
Backport of PEP 562.
|
||||
https://pypi.org/search/?q=pep562
|
||||
Licensed under MIT
|
||||
Copyright (c) 2018 Isaac Muse <isaacmuse@gmail.com>
|
||||
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.
|
||||
"""
|
||||
import sys
|
||||
from typing import Any, Callable, List, Optional
|
||||
|
||||
|
||||
class Pep562:
|
||||
"""
|
||||
Backport of PEP 562 <https://pypi.org/search/?q=pep562>.
|
||||
Wraps the module in a class that exposes the mechanics to override `__dir__` and `__getattr__`.
|
||||
The given module will be searched for overrides of `__dir__` and `__getattr__` and use them when needed.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str) -> None:
|
||||
"""Acquire `__getattr__` and `__dir__`, but only replace module for versions less than Python 3.7."""
|
||||
|
||||
self._module = sys.modules[name]
|
||||
self._get_attr = getattr(self._module, "__getattr__", None)
|
||||
self._get_dir: Optional[Callable[..., List[str]]] = getattr(
|
||||
self._module, "__dir__", None
|
||||
)
|
||||
sys.modules[name] = self # type: ignore[assignment]
|
||||
|
||||
def __dir__(self) -> List[str]:
|
||||
"""Return the overridden `dir` if one was provided, else apply `dir` to the module."""
|
||||
|
||||
return self._get_dir() if self._get_dir else dir(self._module)
|
||||
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
"""
|
||||
Attempt to retrieve the attribute from the module, and if missing, use the overridden function if present.
|
||||
"""
|
||||
|
||||
try:
|
||||
return getattr(self._module, name)
|
||||
except AttributeError:
|
||||
if self._get_attr:
|
||||
return self._get_attr(name)
|
||||
raise
|
||||
|
||||
|
||||
def pep562(module_name: str) -> None:
|
||||
"""Helper function to apply PEP 562."""
|
||||
|
||||
if sys.version_info < (3, 7):
|
||||
Pep562(module_name)
|
@@ -1,61 +0,0 @@
|
||||
import asyncio
|
||||
import inspect
|
||||
import sys
|
||||
|
||||
|
||||
def is_double_callable(application):
|
||||
"""
|
||||
Tests to see if an application is a legacy-style (double-callable) application.
|
||||
"""
|
||||
# Look for a hint on the object first
|
||||
if getattr(application, "_asgi_single_callable", False):
|
||||
return False
|
||||
if getattr(application, "_asgi_double_callable", False):
|
||||
return True
|
||||
# Uninstanted classes are double-callable
|
||||
if inspect.isclass(application):
|
||||
return True
|
||||
# Instanted classes depend on their __call__
|
||||
if hasattr(application, "__call__"):
|
||||
# We only check to see if its __call__ is a coroutine function -
|
||||
# if it's not, it still might be a coroutine function itself.
|
||||
if asyncio.iscoroutinefunction(application.__call__):
|
||||
return False
|
||||
# Non-classes we just check directly
|
||||
return not asyncio.iscoroutinefunction(application)
|
||||
|
||||
|
||||
def double_to_single_callable(application):
|
||||
"""
|
||||
Transforms a double-callable ASGI application into a single-callable one.
|
||||
"""
|
||||
|
||||
async def new_application(scope, receive, send):
|
||||
instance = application(scope)
|
||||
return await instance(receive, send)
|
||||
|
||||
return new_application
|
||||
|
||||
|
||||
def guarantee_single_callable(application):
|
||||
"""
|
||||
Takes either a single- or double-callable application and always returns it
|
||||
in single-callable style. Use this to add backwards compatibility for ASGI
|
||||
2.0 applications to your server/test harness/etc.
|
||||
"""
|
||||
if is_double_callable(application):
|
||||
application = double_to_single_callable(application)
|
||||
return application
|
||||
|
||||
|
||||
if sys.version_info >= (3, 7):
|
||||
# these were introduced in 3.7
|
||||
get_running_loop = asyncio.get_running_loop
|
||||
run_future = asyncio.run
|
||||
create_task = asyncio.create_task
|
||||
else:
|
||||
# marked as deprecated in 3.10, did not exist before 3.7
|
||||
get_running_loop = asyncio.get_event_loop
|
||||
run_future = asyncio.ensure_future
|
||||
# does nothing, this is fine for <3.7
|
||||
create_task = lambda task: task
|
@@ -1,81 +0,0 @@
|
||||
import queue
|
||||
import threading
|
||||
from concurrent.futures import Executor, Future
|
||||
|
||||
|
||||
class _WorkItem:
|
||||
"""
|
||||
Represents an item needing to be run in the executor.
|
||||
Copied from ThreadPoolExecutor (but it's private, so we're not going to rely on importing it)
|
||||
"""
|
||||
|
||||
def __init__(self, future, fn, args, kwargs):
|
||||
self.future = future
|
||||
self.fn = fn
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def run(self):
|
||||
if not self.future.set_running_or_notify_cancel():
|
||||
return
|
||||
try:
|
||||
result = self.fn(*self.args, **self.kwargs)
|
||||
except BaseException as exc:
|
||||
self.future.set_exception(exc)
|
||||
# Break a reference cycle with the exception 'exc'
|
||||
self = None
|
||||
else:
|
||||
self.future.set_result(result)
|
||||
|
||||
|
||||
class CurrentThreadExecutor(Executor):
|
||||
"""
|
||||
An Executor that actually runs code in the thread it is instantiated in.
|
||||
Passed to other threads running async code, so they can run sync code in
|
||||
the thread they came from.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._work_thread = threading.current_thread()
|
||||
self._work_queue = queue.Queue()
|
||||
self._broken = False
|
||||
|
||||
def run_until_future(self, future):
|
||||
"""
|
||||
Runs the code in the work queue until a result is available from the future.
|
||||
Should be run from the thread the executor is initialised in.
|
||||
"""
|
||||
# Check we're in the right thread
|
||||
if threading.current_thread() != self._work_thread:
|
||||
raise RuntimeError(
|
||||
"You cannot run CurrentThreadExecutor from a different thread"
|
||||
)
|
||||
future.add_done_callback(self._work_queue.put)
|
||||
# Keep getting and running work items until we get the future we're waiting for
|
||||
# back via the future's done callback.
|
||||
try:
|
||||
while True:
|
||||
# Get a work item and run it
|
||||
work_item = self._work_queue.get()
|
||||
if work_item is future:
|
||||
return
|
||||
work_item.run()
|
||||
del work_item
|
||||
finally:
|
||||
self._broken = True
|
||||
|
||||
def submit(self, fn, *args, **kwargs):
|
||||
# Check they're not submitting from the same thread
|
||||
if threading.current_thread() == self._work_thread:
|
||||
raise RuntimeError(
|
||||
"You cannot submit onto CurrentThreadExecutor from its own thread"
|
||||
)
|
||||
# Check they're not too late or the executor errored
|
||||
if self._broken:
|
||||
raise RuntimeError("CurrentThreadExecutor already quit or is broken")
|
||||
# Add to work queue
|
||||
f = Future()
|
||||
work_item = _WorkItem(f, fn, args, kwargs)
|
||||
self._work_queue.put(work_item)
|
||||
# Return the future
|
||||
return f
|
@@ -1,122 +0,0 @@
|
||||
import random
|
||||
import string
|
||||
import sys
|
||||
import threading
|
||||
import weakref
|
||||
|
||||
|
||||
class Local:
|
||||
"""
|
||||
A drop-in replacement for threading.locals that also works with asyncio
|
||||
Tasks (via the current_task asyncio method), and passes locals through
|
||||
sync_to_async and async_to_sync.
|
||||
|
||||
Specifically:
|
||||
- Locals work per-coroutine on any thread not spawned using asgiref
|
||||
- Locals work per-thread on any thread not spawned using asgiref
|
||||
- Locals are shared with the parent coroutine when using sync_to_async
|
||||
- Locals are shared with the parent thread when using async_to_sync
|
||||
(and if that thread was launched using sync_to_async, with its parent
|
||||
coroutine as well, with this working for indefinite levels of nesting)
|
||||
|
||||
Set thread_critical to True to not allow locals to pass from an async Task
|
||||
to a thread it spawns. This is needed for code that truly needs
|
||||
thread-safety, as opposed to things used for helpful context (e.g. sqlite
|
||||
does not like being called from a different thread to the one it is from).
|
||||
Thread-critical code will still be differentiated per-Task within a thread
|
||||
as it is expected it does not like concurrent access.
|
||||
|
||||
This doesn't use contextvars as it needs to support 3.6. Once it can support
|
||||
3.7 only, we can then reimplement the storage more nicely.
|
||||
"""
|
||||
|
||||
CLEANUP_INTERVAL = 60 # seconds
|
||||
|
||||
def __init__(self, thread_critical: bool = False) -> None:
|
||||
self._thread_critical = thread_critical
|
||||
self._thread_lock = threading.RLock()
|
||||
self._context_refs: "weakref.WeakSet[object]" = weakref.WeakSet()
|
||||
# Random suffixes stop accidental reuse between different Locals,
|
||||
# though we try to force deletion as well.
|
||||
self._attr_name = "_asgiref_local_impl_{}_{}".format(
|
||||
id(self),
|
||||
"".join(random.choice(string.ascii_letters) for i in range(8)),
|
||||
)
|
||||
|
||||
def _get_context_id(self):
|
||||
"""
|
||||
Get the ID we should use for looking up variables
|
||||
"""
|
||||
# Prevent a circular reference
|
||||
from .sync import AsyncToSync, SyncToAsync
|
||||
|
||||
# First, pull the current task if we can
|
||||
context_id = SyncToAsync.get_current_task()
|
||||
context_is_async = True
|
||||
# OK, let's try for a thread ID
|
||||
if context_id is None:
|
||||
context_id = threading.current_thread()
|
||||
context_is_async = False
|
||||
# If we're thread-critical, we stop here, as we can't share contexts.
|
||||
if self._thread_critical:
|
||||
return context_id
|
||||
# Now, take those and see if we can resolve them through the launch maps
|
||||
for i in range(sys.getrecursionlimit()):
|
||||
try:
|
||||
if context_is_async:
|
||||
# Tasks have a source thread in AsyncToSync
|
||||
context_id = AsyncToSync.launch_map[context_id]
|
||||
context_is_async = False
|
||||
else:
|
||||
# Threads have a source task in SyncToAsync
|
||||
context_id = SyncToAsync.launch_map[context_id]
|
||||
context_is_async = True
|
||||
except KeyError:
|
||||
break
|
||||
else:
|
||||
# Catch infinite loops (they happen if you are screwing around
|
||||
# with AsyncToSync implementations)
|
||||
raise RuntimeError("Infinite launch_map loops")
|
||||
return context_id
|
||||
|
||||
def _get_storage(self):
|
||||
context_obj = self._get_context_id()
|
||||
if not hasattr(context_obj, self._attr_name):
|
||||
setattr(context_obj, self._attr_name, {})
|
||||
self._context_refs.add(context_obj)
|
||||
return getattr(context_obj, self._attr_name)
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
for context_obj in self._context_refs:
|
||||
try:
|
||||
delattr(context_obj, self._attr_name)
|
||||
except AttributeError:
|
||||
pass
|
||||
except TypeError:
|
||||
# WeakSet.__iter__ can crash when interpreter is shutting down due
|
||||
# to _IterationGuard being None.
|
||||
pass
|
||||
|
||||
def __getattr__(self, key):
|
||||
with self._thread_lock:
|
||||
storage = self._get_storage()
|
||||
if key in storage:
|
||||
return storage[key]
|
||||
else:
|
||||
raise AttributeError(f"{self!r} object has no attribute {key!r}")
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
if key in ("_context_refs", "_thread_critical", "_thread_lock", "_attr_name"):
|
||||
return super().__setattr__(key, value)
|
||||
with self._thread_lock:
|
||||
storage = self._get_storage()
|
||||
storage[key] = value
|
||||
|
||||
def __delattr__(self, key):
|
||||
with self._thread_lock:
|
||||
storage = self._get_storage()
|
||||
if key in storage:
|
||||
del storage[key]
|
||||
else:
|
||||
raise AttributeError(f"{self!r} object has no attribute {key!r}")
|
@@ -1,157 +0,0 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from .compatibility import get_running_loop, guarantee_single_callable, run_future
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StatelessServer:
|
||||
"""
|
||||
Base server class that handles basic concepts like application instance
|
||||
creation/pooling, exception handling, and similar, for stateless protocols
|
||||
(i.e. ones without actual incoming connections to the process)
|
||||
|
||||
Your code should override the handle() method, doing whatever it needs to,
|
||||
and calling get_or_create_application_instance with a unique `scope_id`
|
||||
and `scope` for the scope it wants to get.
|
||||
|
||||
If an application instance is found with the same `scope_id`, you are
|
||||
given its input queue, otherwise one is made for you with the scope provided
|
||||
and you are given that fresh new input queue. Either way, you should do
|
||||
something like:
|
||||
|
||||
input_queue = self.get_or_create_application_instance(
|
||||
"user-123456",
|
||||
{"type": "testprotocol", "user_id": "123456", "username": "andrew"},
|
||||
)
|
||||
input_queue.put_nowait(message)
|
||||
|
||||
If you try and create an application instance and there are already
|
||||
`max_application` instances, the oldest/least recently used one will be
|
||||
reclaimed and shut down to make space.
|
||||
|
||||
Application coroutines that error will be found periodically (every 100ms
|
||||
by default) and have their exceptions printed to the console. Override
|
||||
application_exception() if you want to do more when this happens.
|
||||
|
||||
If you override run(), make sure you handle things like launching the
|
||||
application checker.
|
||||
"""
|
||||
|
||||
application_checker_interval = 0.1
|
||||
|
||||
def __init__(self, application, max_applications=1000):
|
||||
# Parameters
|
||||
self.application = application
|
||||
self.max_applications = max_applications
|
||||
# Initialisation
|
||||
self.application_instances = {}
|
||||
|
||||
### Mainloop and handling
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Runs the asyncio event loop with our handler loop.
|
||||
"""
|
||||
event_loop = get_running_loop()
|
||||
asyncio.ensure_future(self.application_checker())
|
||||
try:
|
||||
event_loop.run_until_complete(self.handle())
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Exiting due to Ctrl-C/interrupt")
|
||||
|
||||
async def handle(self):
|
||||
raise NotImplementedError("You must implement handle()")
|
||||
|
||||
async def application_send(self, scope, message):
|
||||
"""
|
||||
Receives outbound sends from applications and handles them.
|
||||
"""
|
||||
raise NotImplementedError("You must implement application_send()")
|
||||
|
||||
### Application instance management
|
||||
|
||||
def get_or_create_application_instance(self, scope_id, scope):
|
||||
"""
|
||||
Creates an application instance and returns its queue.
|
||||
"""
|
||||
if scope_id in self.application_instances:
|
||||
self.application_instances[scope_id]["last_used"] = time.time()
|
||||
return self.application_instances[scope_id]["input_queue"]
|
||||
# See if we need to delete an old one
|
||||
while len(self.application_instances) > self.max_applications:
|
||||
self.delete_oldest_application_instance()
|
||||
# Make an instance of the application
|
||||
input_queue = asyncio.Queue()
|
||||
application_instance = guarantee_single_callable(self.application)
|
||||
# Run it, and stash the future for later checking
|
||||
future = run_future(
|
||||
application_instance(
|
||||
scope=scope,
|
||||
receive=input_queue.get,
|
||||
send=lambda message: self.application_send(scope, message),
|
||||
),
|
||||
)
|
||||
self.application_instances[scope_id] = {
|
||||
"input_queue": input_queue,
|
||||
"future": future,
|
||||
"scope": scope,
|
||||
"last_used": time.time(),
|
||||
}
|
||||
return input_queue
|
||||
|
||||
def delete_oldest_application_instance(self):
|
||||
"""
|
||||
Finds and deletes the oldest application instance
|
||||
"""
|
||||
oldest_time = min(
|
||||
details["last_used"] for details in self.application_instances.values()
|
||||
)
|
||||
for scope_id, details in self.application_instances.items():
|
||||
if details["last_used"] == oldest_time:
|
||||
self.delete_application_instance(scope_id)
|
||||
# Return to make sure we only delete one in case two have
|
||||
# the same oldest time
|
||||
return
|
||||
|
||||
def delete_application_instance(self, scope_id):
|
||||
"""
|
||||
Removes an application instance (makes sure its task is stopped,
|
||||
then removes it from the current set)
|
||||
"""
|
||||
details = self.application_instances[scope_id]
|
||||
del self.application_instances[scope_id]
|
||||
if not details["future"].done():
|
||||
details["future"].cancel()
|
||||
|
||||
async def application_checker(self):
|
||||
"""
|
||||
Goes through the set of current application instance Futures and cleans up
|
||||
any that are done/prints exceptions for any that errored.
|
||||
"""
|
||||
while True:
|
||||
await asyncio.sleep(self.application_checker_interval)
|
||||
for scope_id, details in list(self.application_instances.items()):
|
||||
if details["future"].done():
|
||||
exception = details["future"].exception()
|
||||
if exception:
|
||||
await self.application_exception(exception, details)
|
||||
try:
|
||||
del self.application_instances[scope_id]
|
||||
except KeyError:
|
||||
# Exception handling might have already got here before us. That's fine.
|
||||
pass
|
||||
|
||||
async def application_exception(self, exception, application_details):
|
||||
"""
|
||||
Called whenever an application coroutine has an exception.
|
||||
"""
|
||||
logging.error(
|
||||
"Exception inside application: %s\n%s%s",
|
||||
exception,
|
||||
"".join(traceback.format_tb(exception.__traceback__)),
|
||||
f" {exception}",
|
||||
)
|
@@ -1,548 +0,0 @@
|
||||
import asyncio.coroutines
|
||||
import functools
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import warnings
|
||||
import weakref
|
||||
from concurrent.futures import Future, ThreadPoolExecutor
|
||||
from typing import Any, Callable, Dict, Optional, overload
|
||||
|
||||
from .compatibility import get_running_loop
|
||||
from .current_thread_executor import CurrentThreadExecutor
|
||||
from .local import Local
|
||||
|
||||
if sys.version_info >= (3, 7):
|
||||
import contextvars
|
||||
else:
|
||||
contextvars = None
|
||||
|
||||
|
||||
def _restore_context(context):
|
||||
# Check for changes in contextvars, and set them to the current
|
||||
# context for downstream consumers
|
||||
for cvar in context:
|
||||
try:
|
||||
if cvar.get() != context.get(cvar):
|
||||
cvar.set(context.get(cvar))
|
||||
except LookupError:
|
||||
cvar.set(context.get(cvar))
|
||||
|
||||
|
||||
def _iscoroutinefunction_or_partial(func: Any) -> bool:
|
||||
# Python < 3.8 does not correctly determine partially wrapped
|
||||
# coroutine functions are coroutine functions, hence the need for
|
||||
# this to exist. Code taken from CPython.
|
||||
if sys.version_info >= (3, 8):
|
||||
return asyncio.iscoroutinefunction(func)
|
||||
else:
|
||||
while inspect.ismethod(func):
|
||||
func = func.__func__
|
||||
while isinstance(func, functools.partial):
|
||||
func = func.func
|
||||
|
||||
return asyncio.iscoroutinefunction(func)
|
||||
|
||||
|
||||
class ThreadSensitiveContext:
|
||||
"""Async context manager to manage context for thread sensitive mode
|
||||
|
||||
This context manager controls which thread pool executor is used when in
|
||||
thread sensitive mode. By default, a single thread pool executor is shared
|
||||
within a process.
|
||||
|
||||
In Python 3.7+, the ThreadSensitiveContext() context manager may be used to
|
||||
specify a thread pool per context.
|
||||
|
||||
In Python 3.6, usage of this context manager has no effect.
|
||||
|
||||
This context manager is re-entrant, so only the outer-most call to
|
||||
ThreadSensitiveContext will set the context.
|
||||
|
||||
Usage:
|
||||
|
||||
>>> import time
|
||||
>>> async with ThreadSensitiveContext():
|
||||
... await sync_to_async(time.sleep, 1)()
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.token = None
|
||||
|
||||
if contextvars:
|
||||
|
||||
async def __aenter__(self):
|
||||
try:
|
||||
SyncToAsync.thread_sensitive_context.get()
|
||||
except LookupError:
|
||||
self.token = SyncToAsync.thread_sensitive_context.set(self)
|
||||
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc, value, tb):
|
||||
if not self.token:
|
||||
return
|
||||
|
||||
executor = SyncToAsync.context_to_thread_executor.pop(self, None)
|
||||
if executor:
|
||||
executor.shutdown()
|
||||
SyncToAsync.thread_sensitive_context.reset(self.token)
|
||||
|
||||
else:
|
||||
|
||||
async def __aenter__(self):
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc, value, tb):
|
||||
pass
|
||||
|
||||
|
||||
class AsyncToSync:
|
||||
"""
|
||||
Utility class which turns an awaitable that only works on the thread with
|
||||
the event loop into a synchronous callable that works in a subthread.
|
||||
|
||||
If the call stack contains an async loop, the code runs there.
|
||||
Otherwise, the code runs in a new loop in a new thread.
|
||||
|
||||
Either way, this thread then pauses and waits to run any thread_sensitive
|
||||
code called from further down the call stack using SyncToAsync, before
|
||||
finally exiting once the async task returns.
|
||||
"""
|
||||
|
||||
# Maps launched Tasks to the threads that launched them (for locals impl)
|
||||
launch_map: "Dict[asyncio.Task[object], threading.Thread]" = {}
|
||||
|
||||
# Keeps track of which CurrentThreadExecutor to use. This uses an asgiref
|
||||
# Local, not a threadlocal, so that tasks can work out what their parent used.
|
||||
executors = Local()
|
||||
|
||||
def __init__(self, awaitable, force_new_loop=False):
|
||||
if not callable(awaitable) or not _iscoroutinefunction_or_partial(awaitable):
|
||||
# Python does not have very reliable detection of async functions
|
||||
# (lots of false negatives) so this is just a warning.
|
||||
warnings.warn("async_to_sync was passed a non-async-marked callable")
|
||||
self.awaitable = awaitable
|
||||
try:
|
||||
self.__self__ = self.awaitable.__self__
|
||||
except AttributeError:
|
||||
pass
|
||||
if force_new_loop:
|
||||
# They have asked that we always run in a new sub-loop.
|
||||
self.main_event_loop = None
|
||||
else:
|
||||
try:
|
||||
self.main_event_loop = get_running_loop()
|
||||
except RuntimeError:
|
||||
# There's no event loop in this thread. Look for the threadlocal if
|
||||
# we're inside SyncToAsync
|
||||
main_event_loop_pid = getattr(
|
||||
SyncToAsync.threadlocal, "main_event_loop_pid", None
|
||||
)
|
||||
# We make sure the parent loop is from the same process - if
|
||||
# they've forked, this is not going to be valid any more (#194)
|
||||
if main_event_loop_pid and main_event_loop_pid == os.getpid():
|
||||
self.main_event_loop = getattr(
|
||||
SyncToAsync.threadlocal, "main_event_loop", None
|
||||
)
|
||||
else:
|
||||
self.main_event_loop = None
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
# You can't call AsyncToSync from a thread with a running event loop
|
||||
try:
|
||||
event_loop = get_running_loop()
|
||||
except RuntimeError:
|
||||
pass
|
||||
else:
|
||||
if event_loop.is_running():
|
||||
raise RuntimeError(
|
||||
"You cannot use AsyncToSync in the same thread as an async event loop - "
|
||||
"just await the async function directly."
|
||||
)
|
||||
|
||||
if contextvars is not None:
|
||||
# Wrapping context in list so it can be reassigned from within
|
||||
# `main_wrap`.
|
||||
context = [contextvars.copy_context()]
|
||||
else:
|
||||
context = None
|
||||
|
||||
# Make a future for the return information
|
||||
call_result = Future()
|
||||
# Get the source thread
|
||||
source_thread = threading.current_thread()
|
||||
# Make a CurrentThreadExecutor we'll use to idle in this thread - we
|
||||
# need one for every sync frame, even if there's one above us in the
|
||||
# same thread.
|
||||
if hasattr(self.executors, "current"):
|
||||
old_current_executor = self.executors.current
|
||||
else:
|
||||
old_current_executor = None
|
||||
current_executor = CurrentThreadExecutor()
|
||||
self.executors.current = current_executor
|
||||
# Use call_soon_threadsafe to schedule a synchronous callback on the
|
||||
# main event loop's thread if it's there, otherwise make a new loop
|
||||
# in this thread.
|
||||
try:
|
||||
awaitable = self.main_wrap(
|
||||
args, kwargs, call_result, source_thread, sys.exc_info(), context
|
||||
)
|
||||
|
||||
if not (self.main_event_loop and self.main_event_loop.is_running()):
|
||||
# Make our own event loop - in a new thread - and run inside that.
|
||||
loop = asyncio.new_event_loop()
|
||||
loop_executor = ThreadPoolExecutor(max_workers=1)
|
||||
loop_future = loop_executor.submit(
|
||||
self._run_event_loop, loop, awaitable
|
||||
)
|
||||
if current_executor:
|
||||
# Run the CurrentThreadExecutor until the future is done
|
||||
current_executor.run_until_future(loop_future)
|
||||
# Wait for future and/or allow for exception propagation
|
||||
loop_future.result()
|
||||
else:
|
||||
# Call it inside the existing loop
|
||||
self.main_event_loop.call_soon_threadsafe(
|
||||
self.main_event_loop.create_task, awaitable
|
||||
)
|
||||
if current_executor:
|
||||
# Run the CurrentThreadExecutor until the future is done
|
||||
current_executor.run_until_future(call_result)
|
||||
finally:
|
||||
# Clean up any executor we were running
|
||||
if hasattr(self.executors, "current"):
|
||||
del self.executors.current
|
||||
if old_current_executor:
|
||||
self.executors.current = old_current_executor
|
||||
if contextvars is not None:
|
||||
_restore_context(context[0])
|
||||
|
||||
# Wait for results from the future.
|
||||
return call_result.result()
|
||||
|
||||
def _run_event_loop(self, loop, coro):
|
||||
"""
|
||||
Runs the given event loop (designed to be called in a thread).
|
||||
"""
|
||||
asyncio.set_event_loop(loop)
|
||||
try:
|
||||
loop.run_until_complete(coro)
|
||||
finally:
|
||||
try:
|
||||
# mimic asyncio.run() behavior
|
||||
# cancel unexhausted async generators
|
||||
if sys.version_info >= (3, 7, 0):
|
||||
tasks = asyncio.all_tasks(loop)
|
||||
else:
|
||||
tasks = asyncio.Task.all_tasks(loop)
|
||||
for task in tasks:
|
||||
task.cancel()
|
||||
|
||||
async def gather():
|
||||
await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
loop.run_until_complete(gather())
|
||||
for task in tasks:
|
||||
if task.cancelled():
|
||||
continue
|
||||
if task.exception() is not None:
|
||||
loop.call_exception_handler(
|
||||
{
|
||||
"message": "unhandled exception during loop shutdown",
|
||||
"exception": task.exception(),
|
||||
"task": task,
|
||||
}
|
||||
)
|
||||
if hasattr(loop, "shutdown_asyncgens"):
|
||||
loop.run_until_complete(loop.shutdown_asyncgens())
|
||||
finally:
|
||||
loop.close()
|
||||
asyncio.set_event_loop(self.main_event_loop)
|
||||
|
||||
def __get__(self, parent, objtype):
|
||||
"""
|
||||
Include self for methods
|
||||
"""
|
||||
func = functools.partial(self.__call__, parent)
|
||||
return functools.update_wrapper(func, self.awaitable)
|
||||
|
||||
async def main_wrap(
|
||||
self, args, kwargs, call_result, source_thread, exc_info, context
|
||||
):
|
||||
"""
|
||||
Wraps the awaitable with something that puts the result into the
|
||||
result/exception future.
|
||||
"""
|
||||
if context is not None:
|
||||
_restore_context(context[0])
|
||||
|
||||
current_task = SyncToAsync.get_current_task()
|
||||
self.launch_map[current_task] = source_thread
|
||||
try:
|
||||
# If we have an exception, run the function inside the except block
|
||||
# after raising it so exc_info is correctly populated.
|
||||
if exc_info[1]:
|
||||
try:
|
||||
raise exc_info[1]
|
||||
except BaseException:
|
||||
result = await self.awaitable(*args, **kwargs)
|
||||
else:
|
||||
result = await self.awaitable(*args, **kwargs)
|
||||
except BaseException as e:
|
||||
call_result.set_exception(e)
|
||||
else:
|
||||
call_result.set_result(result)
|
||||
finally:
|
||||
del self.launch_map[current_task]
|
||||
|
||||
if context is not None:
|
||||
context[0] = contextvars.copy_context()
|
||||
|
||||
|
||||
class SyncToAsync:
|
||||
"""
|
||||
Utility class which turns a synchronous callable into an awaitable that
|
||||
runs in a threadpool. It also sets a threadlocal inside the thread so
|
||||
calls to AsyncToSync can escape it.
|
||||
|
||||
If thread_sensitive is passed, the code will run in the same thread as any
|
||||
outer code. This is needed for underlying Python code that is not
|
||||
threadsafe (for example, code which handles SQLite database connections).
|
||||
|
||||
If the outermost program is async (i.e. SyncToAsync is outermost), then
|
||||
this will be a dedicated single sub-thread that all sync code runs in,
|
||||
one after the other. If the outermost program is sync (i.e. AsyncToSync is
|
||||
outermost), this will just be the main thread. This is achieved by idling
|
||||
with a CurrentThreadExecutor while AsyncToSync is blocking its sync parent,
|
||||
rather than just blocking.
|
||||
|
||||
If executor is passed in, that will be used instead of the loop's default executor.
|
||||
In order to pass in an executor, thread_sensitive must be set to False, otherwise
|
||||
a TypeError will be raised.
|
||||
"""
|
||||
|
||||
# If they've set ASGI_THREADS, update the default asyncio executor for now
|
||||
if "ASGI_THREADS" in os.environ:
|
||||
loop = get_running_loop()
|
||||
loop.set_default_executor(
|
||||
ThreadPoolExecutor(max_workers=int(os.environ["ASGI_THREADS"]))
|
||||
)
|
||||
|
||||
# Maps launched threads to the coroutines that spawned them
|
||||
launch_map: "Dict[threading.Thread, asyncio.Task[object]]" = {}
|
||||
|
||||
# Storage for main event loop references
|
||||
threadlocal = threading.local()
|
||||
|
||||
# Single-thread executor for thread-sensitive code
|
||||
single_thread_executor = ThreadPoolExecutor(max_workers=1)
|
||||
|
||||
# Maintain a contextvar for the current execution context. Optionally used
|
||||
# for thread sensitive mode.
|
||||
if sys.version_info >= (3, 7):
|
||||
thread_sensitive_context: "contextvars.ContextVar[str]" = (
|
||||
contextvars.ContextVar("thread_sensitive_context")
|
||||
)
|
||||
else:
|
||||
thread_sensitive_context: None = None
|
||||
|
||||
# Contextvar that is used to detect if the single thread executor
|
||||
# would be awaited on while already being used in the same context
|
||||
if sys.version_info >= (3, 7):
|
||||
deadlock_context: "contextvars.ContextVar[bool]" = contextvars.ContextVar(
|
||||
"deadlock_context"
|
||||
)
|
||||
else:
|
||||
deadlock_context: None = None
|
||||
|
||||
# Maintaining a weak reference to the context ensures that thread pools are
|
||||
# erased once the context goes out of scope. This terminates the thread pool.
|
||||
context_to_thread_executor: "weakref.WeakKeyDictionary[object, ThreadPoolExecutor]" = (
|
||||
weakref.WeakKeyDictionary()
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
func: Callable[..., Any],
|
||||
thread_sensitive: bool = True,
|
||||
executor: Optional["ThreadPoolExecutor"] = None,
|
||||
) -> None:
|
||||
if not callable(func) or _iscoroutinefunction_or_partial(func):
|
||||
raise TypeError("sync_to_async can only be applied to sync functions.")
|
||||
self.func = func
|
||||
functools.update_wrapper(self, func)
|
||||
self._thread_sensitive = thread_sensitive
|
||||
self._is_coroutine = asyncio.coroutines._is_coroutine # type: ignore
|
||||
if thread_sensitive and executor is not None:
|
||||
raise TypeError("executor must not be set when thread_sensitive is True")
|
||||
self._executor = executor
|
||||
try:
|
||||
self.__self__ = func.__self__ # type: ignore
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
async def __call__(self, *args, **kwargs):
|
||||
loop = get_running_loop()
|
||||
|
||||
# Work out what thread to run the code in
|
||||
if self._thread_sensitive:
|
||||
if hasattr(AsyncToSync.executors, "current"):
|
||||
# If we have a parent sync thread above somewhere, use that
|
||||
executor = AsyncToSync.executors.current
|
||||
elif self.thread_sensitive_context and self.thread_sensitive_context.get(
|
||||
None
|
||||
):
|
||||
# If we have a way of retrieving the current context, attempt
|
||||
# to use a per-context thread pool executor
|
||||
thread_sensitive_context = self.thread_sensitive_context.get()
|
||||
|
||||
if thread_sensitive_context in self.context_to_thread_executor:
|
||||
# Re-use thread executor in current context
|
||||
executor = self.context_to_thread_executor[thread_sensitive_context]
|
||||
else:
|
||||
# Create new thread executor in current context
|
||||
executor = ThreadPoolExecutor(max_workers=1)
|
||||
self.context_to_thread_executor[thread_sensitive_context] = executor
|
||||
elif self.deadlock_context and self.deadlock_context.get(False):
|
||||
raise RuntimeError(
|
||||
"Single thread executor already being used, would deadlock"
|
||||
)
|
||||
else:
|
||||
# Otherwise, we run it in a fixed single thread
|
||||
executor = self.single_thread_executor
|
||||
if self.deadlock_context:
|
||||
self.deadlock_context.set(True)
|
||||
else:
|
||||
# Use the passed in executor, or the loop's default if it is None
|
||||
executor = self._executor
|
||||
|
||||
if contextvars is not None:
|
||||
context = contextvars.copy_context()
|
||||
child = functools.partial(self.func, *args, **kwargs)
|
||||
func = context.run
|
||||
args = (child,)
|
||||
kwargs = {}
|
||||
else:
|
||||
func = self.func
|
||||
|
||||
try:
|
||||
# Run the code in the right thread
|
||||
future = loop.run_in_executor(
|
||||
executor,
|
||||
functools.partial(
|
||||
self.thread_handler,
|
||||
loop,
|
||||
self.get_current_task(),
|
||||
sys.exc_info(),
|
||||
func,
|
||||
*args,
|
||||
**kwargs,
|
||||
),
|
||||
)
|
||||
ret = await asyncio.wait_for(future, timeout=None)
|
||||
|
||||
finally:
|
||||
if contextvars is not None:
|
||||
_restore_context(context)
|
||||
if self.deadlock_context:
|
||||
self.deadlock_context.set(False)
|
||||
|
||||
return ret
|
||||
|
||||
def __get__(self, parent, objtype):
|
||||
"""
|
||||
Include self for methods
|
||||
"""
|
||||
return functools.partial(self.__call__, parent)
|
||||
|
||||
def thread_handler(self, loop, source_task, exc_info, func, *args, **kwargs):
|
||||
"""
|
||||
Wraps the sync application with exception handling.
|
||||
"""
|
||||
# Set the threadlocal for AsyncToSync
|
||||
self.threadlocal.main_event_loop = loop
|
||||
self.threadlocal.main_event_loop_pid = os.getpid()
|
||||
# Set the task mapping (used for the locals module)
|
||||
current_thread = threading.current_thread()
|
||||
if AsyncToSync.launch_map.get(source_task) == current_thread:
|
||||
# Our parent task was launched from this same thread, so don't make
|
||||
# a launch map entry - let it shortcut over us! (and stop infinite loops)
|
||||
parent_set = False
|
||||
else:
|
||||
self.launch_map[current_thread] = source_task
|
||||
parent_set = True
|
||||
# Run the function
|
||||
try:
|
||||
# If we have an exception, run the function inside the except block
|
||||
# after raising it so exc_info is correctly populated.
|
||||
if exc_info[1]:
|
||||
try:
|
||||
raise exc_info[1]
|
||||
except BaseException:
|
||||
return func(*args, **kwargs)
|
||||
else:
|
||||
return func(*args, **kwargs)
|
||||
finally:
|
||||
# Only delete the launch_map parent if we set it, otherwise it is
|
||||
# from someone else.
|
||||
if parent_set:
|
||||
del self.launch_map[current_thread]
|
||||
|
||||
@staticmethod
|
||||
def get_current_task():
|
||||
"""
|
||||
Cross-version implementation of asyncio.current_task()
|
||||
|
||||
Returns None if there is no task.
|
||||
"""
|
||||
try:
|
||||
if hasattr(asyncio, "current_task"):
|
||||
# Python 3.7 and up
|
||||
return asyncio.current_task()
|
||||
else:
|
||||
# Python 3.6
|
||||
return asyncio.Task.current_task()
|
||||
except RuntimeError:
|
||||
return None
|
||||
|
||||
|
||||
# Lowercase aliases (and decorator friendliness)
|
||||
async_to_sync = AsyncToSync
|
||||
|
||||
|
||||
@overload
|
||||
def sync_to_async(
|
||||
func: None = None,
|
||||
thread_sensitive: bool = True,
|
||||
executor: Optional["ThreadPoolExecutor"] = None,
|
||||
) -> Callable[[Callable[..., Any]], SyncToAsync]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def sync_to_async(
|
||||
func: Callable[..., Any],
|
||||
thread_sensitive: bool = True,
|
||||
executor: Optional["ThreadPoolExecutor"] = None,
|
||||
) -> SyncToAsync:
|
||||
...
|
||||
|
||||
|
||||
def sync_to_async(
|
||||
func=None,
|
||||
thread_sensitive=True,
|
||||
executor=None,
|
||||
):
|
||||
if func is None:
|
||||
return lambda f: SyncToAsync(
|
||||
f,
|
||||
thread_sensitive=thread_sensitive,
|
||||
executor=executor,
|
||||
)
|
||||
return SyncToAsync(
|
||||
func,
|
||||
thread_sensitive=thread_sensitive,
|
||||
executor=executor,
|
||||
)
|
@@ -1,97 +0,0 @@
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
from .compatibility import guarantee_single_callable
|
||||
from .timeout import timeout as async_timeout
|
||||
|
||||
|
||||
class ApplicationCommunicator:
|
||||
"""
|
||||
Runs an ASGI application in a test mode, allowing sending of
|
||||
messages to it and retrieval of messages it sends.
|
||||
"""
|
||||
|
||||
def __init__(self, application, scope):
|
||||
self.application = guarantee_single_callable(application)
|
||||
self.scope = scope
|
||||
self.input_queue = asyncio.Queue()
|
||||
self.output_queue = asyncio.Queue()
|
||||
self.future = asyncio.ensure_future(
|
||||
self.application(scope, self.input_queue.get, self.output_queue.put)
|
||||
)
|
||||
|
||||
async def wait(self, timeout=1):
|
||||
"""
|
||||
Waits for the application to stop itself and returns any exceptions.
|
||||
"""
|
||||
try:
|
||||
async with async_timeout(timeout):
|
||||
try:
|
||||
await self.future
|
||||
self.future.result()
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
finally:
|
||||
if not self.future.done():
|
||||
self.future.cancel()
|
||||
try:
|
||||
await self.future
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
def stop(self, exceptions=True):
|
||||
if not self.future.done():
|
||||
self.future.cancel()
|
||||
elif exceptions:
|
||||
# Give a chance to raise any exceptions
|
||||
self.future.result()
|
||||
|
||||
def __del__(self):
|
||||
# Clean up on deletion
|
||||
try:
|
||||
self.stop(exceptions=False)
|
||||
except RuntimeError:
|
||||
# Event loop already stopped
|
||||
pass
|
||||
|
||||
async def send_input(self, message):
|
||||
"""
|
||||
Sends a single message to the application
|
||||
"""
|
||||
# Give it the message
|
||||
await self.input_queue.put(message)
|
||||
|
||||
async def receive_output(self, timeout=1):
|
||||
"""
|
||||
Receives a single message from the application, with optional timeout.
|
||||
"""
|
||||
# Make sure there's not an exception to raise from the task
|
||||
if self.future.done():
|
||||
self.future.result()
|
||||
# Wait and receive the message
|
||||
try:
|
||||
async with async_timeout(timeout):
|
||||
return await self.output_queue.get()
|
||||
except asyncio.TimeoutError as e:
|
||||
# See if we have another error to raise inside
|
||||
if self.future.done():
|
||||
self.future.result()
|
||||
else:
|
||||
self.future.cancel()
|
||||
try:
|
||||
await self.future
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
raise e
|
||||
|
||||
async def receive_nothing(self, timeout=0.1, interval=0.01):
|
||||
"""
|
||||
Checks that there is no message to receive in the given time.
|
||||
"""
|
||||
# `interval` has precedence over `timeout`
|
||||
start = time.monotonic()
|
||||
while time.monotonic() - start < timeout:
|
||||
if not self.output_queue.empty():
|
||||
return False
|
||||
await asyncio.sleep(interval)
|
||||
return self.output_queue.empty()
|
@@ -1,127 +0,0 @@
|
||||
# This code is originally sourced from the aio-libs project "async_timeout",
|
||||
# under the Apache 2.0 license. You may see the original project at
|
||||
# https://github.com/aio-libs/async-timeout
|
||||
|
||||
# It is vendored here to reduce chain-dependencies on this library, and
|
||||
# modified slightly to remove some features we don't use.
|
||||
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
from types import TracebackType
|
||||
from typing import Any, Optional, Type
|
||||
|
||||
|
||||
class timeout:
|
||||
"""timeout context manager.
|
||||
|
||||
Useful in cases when you want to apply timeout logic around block
|
||||
of code or in cases when asyncio.wait_for is not suitable. For example:
|
||||
|
||||
>>> with timeout(0.001):
|
||||
... async with aiohttp.get('https://github.com') as r:
|
||||
... await r.text()
|
||||
|
||||
|
||||
timeout - value in seconds or None to disable timeout logic
|
||||
loop - asyncio compatible event loop
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
timeout: Optional[float],
|
||||
*,
|
||||
loop: Optional[asyncio.AbstractEventLoop] = None,
|
||||
) -> None:
|
||||
self._timeout = timeout
|
||||
if loop is None:
|
||||
loop = asyncio.get_event_loop()
|
||||
self._loop = loop
|
||||
self._task = None # type: Optional[asyncio.Task[Any]]
|
||||
self._cancelled = False
|
||||
self._cancel_handler = None # type: Optional[asyncio.Handle]
|
||||
self._cancel_at = None # type: Optional[float]
|
||||
|
||||
def __enter__(self) -> "timeout":
|
||||
return self._do_enter()
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: Type[BaseException],
|
||||
exc_val: BaseException,
|
||||
exc_tb: TracebackType,
|
||||
) -> Optional[bool]:
|
||||
self._do_exit(exc_type)
|
||||
return None
|
||||
|
||||
async def __aenter__(self) -> "timeout":
|
||||
return self._do_enter()
|
||||
|
||||
async def __aexit__(
|
||||
self,
|
||||
exc_type: Type[BaseException],
|
||||
exc_val: BaseException,
|
||||
exc_tb: TracebackType,
|
||||
) -> None:
|
||||
self._do_exit(exc_type)
|
||||
|
||||
@property
|
||||
def expired(self) -> bool:
|
||||
return self._cancelled
|
||||
|
||||
@property
|
||||
def remaining(self) -> Optional[float]:
|
||||
if self._cancel_at is not None:
|
||||
return max(self._cancel_at - self._loop.time(), 0.0)
|
||||
else:
|
||||
return None
|
||||
|
||||
def _do_enter(self) -> "timeout":
|
||||
# Support Tornado 5- without timeout
|
||||
# Details: https://github.com/python/asyncio/issues/392
|
||||
if self._timeout is None:
|
||||
return self
|
||||
|
||||
self._task = current_task(self._loop)
|
||||
if self._task is None:
|
||||
raise RuntimeError(
|
||||
"Timeout context manager should be used " "inside a task"
|
||||
)
|
||||
|
||||
if self._timeout <= 0:
|
||||
self._loop.call_soon(self._cancel_task)
|
||||
return self
|
||||
|
||||
self._cancel_at = self._loop.time() + self._timeout
|
||||
self._cancel_handler = self._loop.call_at(self._cancel_at, self._cancel_task)
|
||||
return self
|
||||
|
||||
def _do_exit(self, exc_type: Type[BaseException]) -> None:
|
||||
if exc_type is asyncio.CancelledError and self._cancelled:
|
||||
self._cancel_handler = None
|
||||
self._task = None
|
||||
raise asyncio.TimeoutError
|
||||
if self._timeout is not None and self._cancel_handler is not None:
|
||||
self._cancel_handler.cancel()
|
||||
self._cancel_handler = None
|
||||
self._task = None
|
||||
return None
|
||||
|
||||
def _cancel_task(self) -> None:
|
||||
if self._task is not None:
|
||||
self._task.cancel()
|
||||
self._cancelled = True
|
||||
|
||||
|
||||
def current_task(loop: asyncio.AbstractEventLoop) -> "Optional[asyncio.Task[Any]]":
|
||||
if sys.version_info >= (3, 7):
|
||||
task = asyncio.current_task(loop=loop)
|
||||
else:
|
||||
task = asyncio.Task.current_task(loop=loop)
|
||||
if task is None:
|
||||
# this should be removed, tokio must use register_task and family API
|
||||
fn = getattr(loop, "current_task", None)
|
||||
if fn is not None:
|
||||
task = fn()
|
||||
|
||||
return task
|
@@ -1,287 +0,0 @@
|
||||
import sys
|
||||
import warnings
|
||||
from typing import (
|
||||
Any,
|
||||
Awaitable,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
List,
|
||||
Optional,
|
||||
Tuple,
|
||||
Type,
|
||||
Union,
|
||||
)
|
||||
|
||||
from asgiref._pep562 import pep562
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
from typing import Literal, Protocol, TypedDict
|
||||
else:
|
||||
from typing_extensions import Literal, Protocol, TypedDict
|
||||
|
||||
__all__ = (
|
||||
"ASGIVersions",
|
||||
"HTTPScope",
|
||||
"WebSocketScope",
|
||||
"LifespanScope",
|
||||
"WWWScope",
|
||||
"Scope",
|
||||
"HTTPRequestEvent",
|
||||
"HTTPResponseStartEvent",
|
||||
"HTTPResponseBodyEvent",
|
||||
"HTTPServerPushEvent",
|
||||
"HTTPDisconnectEvent",
|
||||
"WebSocketConnectEvent",
|
||||
"WebSocketAcceptEvent",
|
||||
"WebSocketReceiveEvent",
|
||||
"WebSocketSendEvent",
|
||||
"WebSocketResponseStartEvent",
|
||||
"WebSocketResponseBodyEvent",
|
||||
"WebSocketDisconnectEvent",
|
||||
"WebSocketCloseEvent",
|
||||
"LifespanStartupEvent",
|
||||
"LifespanShutdownEvent",
|
||||
"LifespanStartupCompleteEvent",
|
||||
"LifespanStartupFailedEvent",
|
||||
"LifespanShutdownCompleteEvent",
|
||||
"LifespanShutdownFailedEvent",
|
||||
"ASGIReceiveEvent",
|
||||
"ASGISendEvent",
|
||||
"ASGIReceiveCallable",
|
||||
"ASGISendCallable",
|
||||
"ASGI2Protocol",
|
||||
"ASGI2Application",
|
||||
"ASGI3Application",
|
||||
"ASGIApplication",
|
||||
)
|
||||
|
||||
|
||||
class ASGIVersions(TypedDict):
|
||||
spec_version: str
|
||||
version: Union[Literal["2.0"], Literal["3.0"]]
|
||||
|
||||
|
||||
class HTTPScope(TypedDict):
|
||||
type: Literal["http"]
|
||||
asgi: ASGIVersions
|
||||
http_version: str
|
||||
method: str
|
||||
scheme: str
|
||||
path: str
|
||||
raw_path: bytes
|
||||
query_string: bytes
|
||||
root_path: str
|
||||
headers: Iterable[Tuple[bytes, bytes]]
|
||||
client: Optional[Tuple[str, int]]
|
||||
server: Optional[Tuple[str, Optional[int]]]
|
||||
extensions: Optional[Dict[str, Dict[object, object]]]
|
||||
|
||||
|
||||
class WebSocketScope(TypedDict):
|
||||
type: Literal["websocket"]
|
||||
asgi: ASGIVersions
|
||||
http_version: str
|
||||
scheme: str
|
||||
path: str
|
||||
raw_path: bytes
|
||||
query_string: bytes
|
||||
root_path: str
|
||||
headers: Iterable[Tuple[bytes, bytes]]
|
||||
client: Optional[Tuple[str, int]]
|
||||
server: Optional[Tuple[str, Optional[int]]]
|
||||
subprotocols: Iterable[str]
|
||||
extensions: Optional[Dict[str, Dict[object, object]]]
|
||||
|
||||
|
||||
class LifespanScope(TypedDict):
|
||||
type: Literal["lifespan"]
|
||||
asgi: ASGIVersions
|
||||
|
||||
|
||||
WWWScope = Union[HTTPScope, WebSocketScope]
|
||||
Scope = Union[HTTPScope, WebSocketScope, LifespanScope]
|
||||
|
||||
|
||||
class HTTPRequestEvent(TypedDict):
|
||||
type: Literal["http.request"]
|
||||
body: bytes
|
||||
more_body: bool
|
||||
|
||||
|
||||
class HTTPResponseStartEvent(TypedDict):
|
||||
type: Literal["http.response.start"]
|
||||
status: int
|
||||
headers: Iterable[Tuple[bytes, bytes]]
|
||||
|
||||
|
||||
class HTTPResponseBodyEvent(TypedDict):
|
||||
type: Literal["http.response.body"]
|
||||
body: bytes
|
||||
more_body: bool
|
||||
|
||||
|
||||
class HTTPServerPushEvent(TypedDict):
|
||||
type: Literal["http.response.push"]
|
||||
path: str
|
||||
headers: Iterable[Tuple[bytes, bytes]]
|
||||
|
||||
|
||||
class HTTPDisconnectEvent(TypedDict):
|
||||
type: Literal["http.disconnect"]
|
||||
|
||||
|
||||
class WebSocketConnectEvent(TypedDict):
|
||||
type: Literal["websocket.connect"]
|
||||
|
||||
|
||||
class WebSocketAcceptEvent(TypedDict):
|
||||
type: Literal["websocket.accept"]
|
||||
subprotocol: Optional[str]
|
||||
headers: Iterable[Tuple[bytes, bytes]]
|
||||
|
||||
|
||||
class WebSocketReceiveEvent(TypedDict):
|
||||
type: Literal["websocket.receive"]
|
||||
bytes: Optional[bytes]
|
||||
text: Optional[str]
|
||||
|
||||
|
||||
class WebSocketSendEvent(TypedDict):
|
||||
type: Literal["websocket.send"]
|
||||
bytes: Optional[bytes]
|
||||
text: Optional[str]
|
||||
|
||||
|
||||
class WebSocketResponseStartEvent(TypedDict):
|
||||
type: Literal["websocket.http.response.start"]
|
||||
status: int
|
||||
headers: Iterable[Tuple[bytes, bytes]]
|
||||
|
||||
|
||||
class WebSocketResponseBodyEvent(TypedDict):
|
||||
type: Literal["websocket.http.response.body"]
|
||||
body: bytes
|
||||
more_body: bool
|
||||
|
||||
|
||||
class WebSocketDisconnectEvent(TypedDict):
|
||||
type: Literal["websocket.disconnect"]
|
||||
code: int
|
||||
|
||||
|
||||
class WebSocketCloseEvent(TypedDict):
|
||||
type: Literal["websocket.close"]
|
||||
code: int
|
||||
reason: Optional[str]
|
||||
|
||||
|
||||
class LifespanStartupEvent(TypedDict):
|
||||
type: Literal["lifespan.startup"]
|
||||
|
||||
|
||||
class LifespanShutdownEvent(TypedDict):
|
||||
type: Literal["lifespan.shutdown"]
|
||||
|
||||
|
||||
class LifespanStartupCompleteEvent(TypedDict):
|
||||
type: Literal["lifespan.startup.complete"]
|
||||
|
||||
|
||||
class LifespanStartupFailedEvent(TypedDict):
|
||||
type: Literal["lifespan.startup.failed"]
|
||||
message: str
|
||||
|
||||
|
||||
class LifespanShutdownCompleteEvent(TypedDict):
|
||||
type: Literal["lifespan.shutdown.complete"]
|
||||
|
||||
|
||||
class LifespanShutdownFailedEvent(TypedDict):
|
||||
type: Literal["lifespan.shutdown.failed"]
|
||||
message: str
|
||||
|
||||
|
||||
ASGIReceiveEvent = Union[
|
||||
HTTPRequestEvent,
|
||||
HTTPDisconnectEvent,
|
||||
WebSocketConnectEvent,
|
||||
WebSocketReceiveEvent,
|
||||
WebSocketDisconnectEvent,
|
||||
LifespanStartupEvent,
|
||||
LifespanShutdownEvent,
|
||||
]
|
||||
|
||||
|
||||
ASGISendEvent = Union[
|
||||
HTTPResponseStartEvent,
|
||||
HTTPResponseBodyEvent,
|
||||
HTTPServerPushEvent,
|
||||
HTTPDisconnectEvent,
|
||||
WebSocketAcceptEvent,
|
||||
WebSocketSendEvent,
|
||||
WebSocketResponseStartEvent,
|
||||
WebSocketResponseBodyEvent,
|
||||
WebSocketCloseEvent,
|
||||
LifespanStartupCompleteEvent,
|
||||
LifespanStartupFailedEvent,
|
||||
LifespanShutdownCompleteEvent,
|
||||
LifespanShutdownFailedEvent,
|
||||
]
|
||||
|
||||
|
||||
ASGIReceiveCallable = Callable[[], Awaitable[ASGIReceiveEvent]]
|
||||
ASGISendCallable = Callable[[ASGISendEvent], Awaitable[None]]
|
||||
|
||||
|
||||
class ASGI2Protocol(Protocol):
|
||||
def __init__(self, scope: Scope) -> None:
|
||||
...
|
||||
|
||||
async def __call__(
|
||||
self, receive: ASGIReceiveCallable, send: ASGISendCallable
|
||||
) -> None:
|
||||
...
|
||||
|
||||
|
||||
ASGI2Application = Type[ASGI2Protocol]
|
||||
ASGI3Application = Callable[
|
||||
[
|
||||
Scope,
|
||||
ASGIReceiveCallable,
|
||||
ASGISendCallable,
|
||||
],
|
||||
Awaitable[None],
|
||||
]
|
||||
ASGIApplication = Union[ASGI2Application, ASGI3Application]
|
||||
|
||||
__deprecated__ = {
|
||||
"WebsocketConnectEvent": WebSocketConnectEvent,
|
||||
"WebsocketAcceptEvent": WebSocketAcceptEvent,
|
||||
"WebsocketReceiveEvent": WebSocketReceiveEvent,
|
||||
"WebsocketSendEvent": WebSocketSendEvent,
|
||||
"WebsocketResponseStartEvent": WebSocketResponseStartEvent,
|
||||
"WebsocketResponseBodyEvent": WebSocketResponseBodyEvent,
|
||||
"WebsocketDisconnectEvent": WebSocketDisconnectEvent,
|
||||
"WebsocketCloseEvent": WebSocketCloseEvent,
|
||||
}
|
||||
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
deprecated = __deprecated__.get(name)
|
||||
if deprecated:
|
||||
stacklevel = 3 if sys.version_info >= (3, 7) else 4
|
||||
warnings.warn(
|
||||
f"'{name}' is deprecated. Use '{deprecated.__name__}' instead.",
|
||||
category=DeprecationWarning,
|
||||
stacklevel=stacklevel,
|
||||
)
|
||||
return deprecated
|
||||
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
||||
|
||||
|
||||
def __dir__() -> List[str]:
|
||||
return sorted(list(__all__) + list(__deprecated__.keys()))
|
||||
|
||||
|
||||
pep562(__name__)
|
@@ -1,162 +0,0 @@
|
||||
from io import BytesIO
|
||||
from tempfile import SpooledTemporaryFile
|
||||
|
||||
from asgiref.sync import AsyncToSync, sync_to_async
|
||||
|
||||
|
||||
class WsgiToAsgi:
|
||||
"""
|
||||
Wraps a WSGI application to make it into an ASGI application.
|
||||
"""
|
||||
|
||||
def __init__(self, wsgi_application):
|
||||
self.wsgi_application = wsgi_application
|
||||
|
||||
async def __call__(self, scope, receive, send):
|
||||
"""
|
||||
ASGI application instantiation point.
|
||||
We return a new WsgiToAsgiInstance here with the WSGI app
|
||||
and the scope, ready to respond when it is __call__ed.
|
||||
"""
|
||||
await WsgiToAsgiInstance(self.wsgi_application)(scope, receive, send)
|
||||
|
||||
|
||||
class WsgiToAsgiInstance:
|
||||
"""
|
||||
Per-socket instance of a wrapped WSGI application
|
||||
"""
|
||||
|
||||
def __init__(self, wsgi_application):
|
||||
self.wsgi_application = wsgi_application
|
||||
self.response_started = False
|
||||
self.response_content_length = None
|
||||
|
||||
async def __call__(self, scope, receive, send):
|
||||
if scope["type"] != "http":
|
||||
raise ValueError("WSGI wrapper received a non-HTTP scope")
|
||||
self.scope = scope
|
||||
with SpooledTemporaryFile(max_size=65536) as body:
|
||||
# Alright, wait for the http.request messages
|
||||
while True:
|
||||
message = await receive()
|
||||
if message["type"] != "http.request":
|
||||
raise ValueError("WSGI wrapper received a non-HTTP-request message")
|
||||
body.write(message.get("body", b""))
|
||||
if not message.get("more_body"):
|
||||
break
|
||||
body.seek(0)
|
||||
# Wrap send so it can be called from the subthread
|
||||
self.sync_send = AsyncToSync(send)
|
||||
# Call the WSGI app
|
||||
await self.run_wsgi_app(body)
|
||||
|
||||
def build_environ(self, scope, body):
|
||||
"""
|
||||
Builds a scope and request body into a WSGI environ object.
|
||||
"""
|
||||
environ = {
|
||||
"REQUEST_METHOD": scope["method"],
|
||||
"SCRIPT_NAME": scope.get("root_path", "").encode("utf8").decode("latin1"),
|
||||
"PATH_INFO": scope["path"].encode("utf8").decode("latin1"),
|
||||
"QUERY_STRING": scope["query_string"].decode("ascii"),
|
||||
"SERVER_PROTOCOL": "HTTP/%s" % scope["http_version"],
|
||||
"wsgi.version": (1, 0),
|
||||
"wsgi.url_scheme": scope.get("scheme", "http"),
|
||||
"wsgi.input": body,
|
||||
"wsgi.errors": BytesIO(),
|
||||
"wsgi.multithread": True,
|
||||
"wsgi.multiprocess": True,
|
||||
"wsgi.run_once": False,
|
||||
}
|
||||
# Get server name and port - required in WSGI, not in ASGI
|
||||
if "server" in scope:
|
||||
environ["SERVER_NAME"] = scope["server"][0]
|
||||
environ["SERVER_PORT"] = str(scope["server"][1])
|
||||
else:
|
||||
environ["SERVER_NAME"] = "localhost"
|
||||
environ["SERVER_PORT"] = "80"
|
||||
|
||||
if "client" in scope:
|
||||
environ["REMOTE_ADDR"] = scope["client"][0]
|
||||
|
||||
# Go through headers and make them into environ entries
|
||||
for name, value in self.scope.get("headers", []):
|
||||
name = name.decode("latin1")
|
||||
if name == "content-length":
|
||||
corrected_name = "CONTENT_LENGTH"
|
||||
elif name == "content-type":
|
||||
corrected_name = "CONTENT_TYPE"
|
||||
else:
|
||||
corrected_name = "HTTP_%s" % name.upper().replace("-", "_")
|
||||
# HTTPbis say only ASCII chars are allowed in headers, but we latin1 just in case
|
||||
value = value.decode("latin1")
|
||||
if corrected_name in environ:
|
||||
value = environ[corrected_name] + "," + value
|
||||
environ[corrected_name] = value
|
||||
return environ
|
||||
|
||||
def start_response(self, status, response_headers, exc_info=None):
|
||||
"""
|
||||
WSGI start_response callable.
|
||||
"""
|
||||
# Don't allow re-calling once response has begun
|
||||
if self.response_started:
|
||||
raise exc_info[1].with_traceback(exc_info[2])
|
||||
# Don't allow re-calling without exc_info
|
||||
if hasattr(self, "response_start") and exc_info is None:
|
||||
raise ValueError(
|
||||
"You cannot call start_response a second time without exc_info"
|
||||
)
|
||||
# Extract status code
|
||||
status_code, _ = status.split(" ", 1)
|
||||
status_code = int(status_code)
|
||||
# Extract headers
|
||||
headers = [
|
||||
(name.lower().encode("ascii"), value.encode("ascii"))
|
||||
for name, value in response_headers
|
||||
]
|
||||
# Extract content-length
|
||||
self.response_content_length = None
|
||||
for name, value in response_headers:
|
||||
if name.lower() == "content-length":
|
||||
self.response_content_length = int(value)
|
||||
# Build and send response start message.
|
||||
self.response_start = {
|
||||
"type": "http.response.start",
|
||||
"status": status_code,
|
||||
"headers": headers,
|
||||
}
|
||||
|
||||
@sync_to_async
|
||||
def run_wsgi_app(self, body):
|
||||
"""
|
||||
Called in a subthread to run the WSGI app. We encapsulate like
|
||||
this so that the start_response callable is called in the same thread.
|
||||
"""
|
||||
# Translate the scope and incoming request body into a WSGI environ
|
||||
environ = self.build_environ(self.scope, body)
|
||||
# Run the WSGI app
|
||||
bytes_sent = 0
|
||||
for output in self.wsgi_application(environ, self.start_response):
|
||||
# If this is the first response, include the response headers
|
||||
if not self.response_started:
|
||||
self.response_started = True
|
||||
self.sync_send(self.response_start)
|
||||
# If the application supplies a Content-Length header
|
||||
if self.response_content_length is not None:
|
||||
# The server should not transmit more bytes to the client than the header allows
|
||||
bytes_allowed = self.response_content_length - bytes_sent
|
||||
if len(output) > bytes_allowed:
|
||||
output = output[:bytes_allowed]
|
||||
self.sync_send(
|
||||
{"type": "http.response.body", "body": output, "more_body": True}
|
||||
)
|
||||
bytes_sent += len(output)
|
||||
# The server should stop iterating over the response when enough data has been sent
|
||||
if bytes_sent == self.response_content_length:
|
||||
break
|
||||
# Close connection
|
||||
if not self.response_started:
|
||||
self.response_started = True
|
||||
self.sync_send(self.response_start)
|
||||
self.sync_send({"type": "http.response.body"})
|
@@ -1,46 +0,0 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin.filters import (
|
||||
SimpleListFilter,
|
||||
AllValuesFieldListFilter,
|
||||
ChoicesFieldListFilter,
|
||||
RelatedFieldListFilter,
|
||||
RelatedOnlyFieldListFilter
|
||||
)
|
||||
|
||||
|
||||
class InputFilter(admin.SimpleListFilter):
|
||||
template = 'baton/filters/input_filter.html'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
# Dummy, required to show the filter.
|
||||
return ((),)
|
||||
|
||||
def choices(self, changelist):
|
||||
# Grab only the "all" option.
|
||||
all_choice = next(super(InputFilter, self).choices(changelist))
|
||||
all_choice['query_parts'] = (
|
||||
(k, v)
|
||||
for k, v in changelist.get_filters_params().items()
|
||||
if k != self.parameter_name
|
||||
)
|
||||
yield all_choice
|
||||
|
||||
|
||||
class SimpleDropdownFilter(SimpleListFilter):
|
||||
template = 'baton/filters/dropdown_filter.html'
|
||||
|
||||
|
||||
class DropdownFilter(AllValuesFieldListFilter):
|
||||
template = 'baton/filters/dropdown_filter.html'
|
||||
|
||||
|
||||
class ChoicesDropdownFilter(ChoicesFieldListFilter):
|
||||
template = 'baton/filters/dropdown_filter.html'
|
||||
|
||||
|
||||
class RelatedDropdownFilter(RelatedFieldListFilter):
|
||||
template = 'baton/filters/dropdown_filter.html'
|
||||
|
||||
|
||||
class RelatedOnlyDropdownFilter(RelatedOnlyFieldListFilter):
|
||||
template = 'baton/filters/dropdown_filter.html'
|
@@ -1,7 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class BatonConfig(AppConfig):
|
||||
name = 'baton'
|
@@ -1,41 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.conf import settings
|
||||
from django.utils.html import mark_safe
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
default_config = {
|
||||
'SITE_TITLE': 'Baton',
|
||||
'SITE_HEADER': '<img src="%sbaton/img/logo.png" />' % settings.STATIC_URL,
|
||||
'INDEX_TITLE': _('Site administration'),
|
||||
'MENU_TITLE': _('Menu'),
|
||||
'SUPPORT_HREF': 'https://github.com/otto-torino/django-baton/issues',
|
||||
'COPYRIGHT': 'copyright © 2020 <a href="https://www.otto.to.it">Otto srl</a>', # noqa
|
||||
'POWERED_BY': '<a href="https://www.otto.to.it">Otto srl</a>',
|
||||
'CONFIRM_UNSAVED_CHANGES': True,
|
||||
'SHOW_MULTIPART_UPLOADING': True,
|
||||
'ENABLE_IMAGES_PREVIEW': True,
|
||||
'COLLAPSABLE_USER_AREA': False,
|
||||
'CHANGELIST_FILTERS_IN_MODAL': False,
|
||||
'CHANGELIST_FILTERS_ALWAYS_OPEN': False,
|
||||
'CHANGELIST_FILTERS_FORM': False,
|
||||
'MENU_ALWAYS_COLLAPSED': False,
|
||||
'MESSAGES_TOASTS': False,
|
||||
'GRAVATAR_DEFAULT_IMG': 'retro',
|
||||
'LOGIN_SPLASH': None,
|
||||
'SEARCH_FIELD': None,
|
||||
}
|
||||
|
||||
|
||||
def get_config(key):
|
||||
safe = ['SITE_HEADER', 'COPYRIGHT', 'POWERED_BY', ]
|
||||
user_settings = getattr(settings, 'BATON', None)
|
||||
|
||||
if user_settings is None:
|
||||
value = default_config.get(key, None)
|
||||
else:
|
||||
value = user_settings.get(key, default_config.get(key, None))
|
||||
|
||||
if key in safe:
|
||||
return mark_safe(value)
|
||||
|
||||
return value
|
@@ -1,134 +0,0 @@
|
||||
# baton translations
|
||||
# Copyright (C) 2017 Otto srl
|
||||
# This file is distributed under the same license as the django-baton package.
|
||||
# Stefano Contini <abidibo@gmail.com>, 2017.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.1.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-12-03 16:44+0100\n"
|
||||
"PO-Revision-Date: 2017-02-12 17:00+0100\n"
|
||||
"Last-Translator: Stefano Contini <abidibo@gmail.com>\n"
|
||||
"Language-Team: it <LL@li.org>\n"
|
||||
"Language: it\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"
|
||||
|
||||
#: config.py:9
|
||||
msgid "Site administration"
|
||||
msgstr "Amministrazione sito"
|
||||
|
||||
#: config.py:10
|
||||
msgid "Menu"
|
||||
msgstr "Menu"
|
||||
|
||||
#: templates/admin/base_site.html:5
|
||||
msgid "Django site admin"
|
||||
msgstr "Amministrazione sito django"
|
||||
|
||||
#: templates/admin/base_site.html:44
|
||||
msgid "Django administration"
|
||||
msgstr "Amministrazione django"
|
||||
|
||||
#: templates/admin/base_site.html:63
|
||||
msgid "close"
|
||||
msgstr "chiudi"
|
||||
|
||||
#: templates/admin/base_site.html:64
|
||||
msgid "save"
|
||||
msgstr "salva"
|
||||
|
||||
#: templates/admin/filer/folder/directory_listing.html:4
|
||||
msgid "Folders"
|
||||
msgstr "Cartelle"
|
||||
|
||||
#: templates/baton/analytics.html:24
|
||||
msgid "last 15 days"
|
||||
msgstr "ultimi 15 giorni"
|
||||
|
||||
#: templates/baton/analytics.html:25
|
||||
msgid "last month"
|
||||
msgstr "ultimo mese"
|
||||
|
||||
#: templates/baton/analytics.html:26
|
||||
msgid "last three months"
|
||||
msgstr "ultimi tre mesi"
|
||||
|
||||
#: templates/baton/analytics.html:27
|
||||
msgid "last year"
|
||||
msgstr "ultimo anno"
|
||||
|
||||
#: templates/baton/analytics.html:34
|
||||
msgid "Traffic"
|
||||
msgstr "Traffico"
|
||||
|
||||
#: templates/baton/analytics.html:35
|
||||
msgid "Sessions and Users"
|
||||
msgstr "Sessioni e utenti"
|
||||
|
||||
#: templates/baton/analytics.html:41
|
||||
msgid "Popular"
|
||||
msgstr "Popolare"
|
||||
|
||||
#: templates/baton/analytics.html:42
|
||||
msgid "Page views"
|
||||
msgstr "Visualizzazioni di pagina"
|
||||
|
||||
#: templates/baton/analytics.html:50
|
||||
msgid "Browsers"
|
||||
msgstr "Browser"
|
||||
|
||||
#: templates/baton/analytics.html:51
|
||||
msgid "Top used"
|
||||
msgstr "Più utilizzati"
|
||||
|
||||
#: templates/baton/analytics.html:57
|
||||
msgid "Acquisition"
|
||||
msgstr "Acquisizione"
|
||||
|
||||
#: templates/baton/analytics.html:58
|
||||
msgid "Referral Traffic"
|
||||
msgstr "Traffico da ricerca"
|
||||
|
||||
#: templates/baton/analytics.html:66
|
||||
msgid "Audience"
|
||||
msgstr "Visitatori"
|
||||
|
||||
#: templates/baton/analytics.html:67
|
||||
msgid "Countries"
|
||||
msgstr "Paesi"
|
||||
|
||||
#: templates/baton/analytics.html:73
|
||||
msgid "Social"
|
||||
msgstr "Social"
|
||||
|
||||
#: templates/baton/analytics.html:74
|
||||
msgid "Interactions"
|
||||
msgstr "Interazioni"
|
||||
|
||||
#: templates/baton/filters/dropdown_filter.html:3
|
||||
#: templates/baton/filters/input_filter.html:3
|
||||
#, python-format
|
||||
msgid " By %(filter_title)s "
|
||||
msgstr "Per %(filter_title)s"
|
||||
|
||||
#: templates/baton/filters/input_filter.html:16
|
||||
msgid "type and press enter..."
|
||||
msgstr "digita e premi invio..."
|
||||
|
||||
#: templates/baton/filters/input_filter.html:20
|
||||
msgid "Remove"
|
||||
msgstr "Rimuovi"
|
||||
|
||||
#: templates/baton/footer.html:7
|
||||
msgid "Support"
|
||||
msgstr "Supporto"
|
||||
|
||||
#: templates/baton/footer.html:18
|
||||
#, python-format
|
||||
msgid "Developed by %(powered_by)s"
|
||||
msgstr "Sviluppato da %(powered_by)s"
|
@@ -1,52 +0,0 @@
|
||||
/* VARIABLE DEFINITIONS */
|
||||
:root {
|
||||
--primary: #79aec8;
|
||||
--secondary: #417690;
|
||||
--accent: #f5dd5d;
|
||||
--primary-fg: #fff;
|
||||
|
||||
--body-fg: #333;
|
||||
--body-bg: #fff;
|
||||
--body-quiet-color: #666;
|
||||
--body-loud-color: #000;
|
||||
|
||||
--header-color: #ffc;
|
||||
--header-branding-color: var(--accent);
|
||||
--header-bg: var(--secondary);
|
||||
--header-link-color: var(--primary-fg);
|
||||
|
||||
--breadcrumbs-fg: #c4dce8;
|
||||
--breadcrumbs-link-fg: var(--body-bg);
|
||||
--breadcrumbs-bg: var(--primary);
|
||||
|
||||
--link-fg: #447e9b;
|
||||
--link-hover-color: #036;
|
||||
--link-selected-fg: #5b80b2;
|
||||
|
||||
--hairline-color: #e8e8e8;
|
||||
--border-color: #ccc;
|
||||
|
||||
--error-fg: #ba2121;
|
||||
|
||||
--message-success-bg: #dfd;
|
||||
--message-warning-bg: #ffc;
|
||||
--message-error-bg: #ffefef;
|
||||
|
||||
--darkened-bg: #f8f8f8; /* A bit darker than --body-bg */
|
||||
--selected-bg: #e4e4e4; /* E.g. selected table cells */
|
||||
--selected-row: #ffc;
|
||||
|
||||
--button-fg: #fff;
|
||||
--button-bg: var(--primary);
|
||||
--button-hover-bg: #609ab6;
|
||||
--default-button-bg: var(--secondary);
|
||||
--default-button-hover-bg: #205067;
|
||||
--close-button-bg: #888; /* Previously #bbb, contrast 1.92 */
|
||||
--close-button-hover-bg: #747474;
|
||||
--delete-button-bg: #ba2121;
|
||||
--delete-button-hover-bg: #a41515;
|
||||
|
||||
--object-tools-fg: var(--button-fg);
|
||||
--object-tools-bg: var(--close-button-bg);
|
||||
--object-tools-hover-bg: var(--close-button-hover-bg);
|
||||
}
|
@@ -1 +0,0 @@
|
||||
/* empty */
|
@@ -1 +0,0 @@
|
||||
/* empty */
|
@@ -1 +0,0 @@
|
||||
/* empty */
|
@@ -1 +0,0 @@
|
||||
/* empty */
|
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"presets": ["@babel/preset-env"]
|
||||
}
|
@@ -1,2 +0,0 @@
|
||||
node_modules/**
|
||||
dist/**
|
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"parser" : "babel-eslint",
|
||||
"extends" : [
|
||||
"standard"
|
||||
],
|
||||
"plugins" : [
|
||||
"flow-vars"
|
||||
],
|
||||
"env" : {
|
||||
"browser" : true
|
||||
},
|
||||
"globals" : {
|
||||
"Baton": {},
|
||||
"jQuery": {},
|
||||
"$": {},
|
||||
},
|
||||
"rules": {
|
||||
"semi" : [2, "never"],
|
||||
"max-len": [2, 120, 2],
|
||||
"comma-dangle": [2, "only-multiline"],
|
||||
"flow-vars/define-flow-type": 1,
|
||||
"flow-vars/use-flow-type": 1
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"libs": [
|
||||
"browser",
|
||||
"ecma5",
|
||||
"ecma6"
|
||||
],
|
||||
"plugins": {
|
||||
"node": { },
|
||||
"modules": { },
|
||||
"complete_strings": {},
|
||||
"es_modules": {}
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 898 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="15" height="15" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="#555555" d="M1216 832q0-185-131.5-316.5t-316.5-131.5-316.5 131.5-131.5 316.5 131.5 316.5 316.5 131.5 316.5-131.5 131.5-316.5zm512 832q0 52-38 90t-90 38q-54 0-90-38l-343-342q-179 124-399 124-143 0-273.5-55.5t-225-150-150-225-55.5-273.5 55.5-273.5 150-225 225-150 273.5-55.5 273.5 55.5 225 150 150 225 55.5 273.5q0 220-124 399l343 343q37 37 37 90z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 458 B |
Before Width: | Height: | Size: 730 KiB |
@@ -1,34 +0,0 @@
|
||||
<svg width="16" height="192" viewBox="0 0 1792 21504" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<g id="up">
|
||||
<path d="M1412 895q0-27-18-45l-362-362-91-91q-18-18-45-18t-45 18l-91 91-362 362q-18 18-18 45t18 45l91 91q18 18 45 18t45-18l189-189v502q0 26 19 45t45 19h128q26 0 45-19t19-45v-502l189 189q19 19 45 19t45-19l91-91q18-18 18-45zm252 1q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
|
||||
</g>
|
||||
<g id="down">
|
||||
<path d="M1412 897q0-27-18-45l-91-91q-18-18-45-18t-45 18l-189 189v-502q0-26-19-45t-45-19h-128q-26 0-45 19t-19 45v502l-189-189q-19-19-45-19t-45 19l-91 91q-18 18-18 45t18 45l362 362 91 91q18 18 45 18t45-18l91-91 362-362q18-18 18-45zm252-1q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
|
||||
</g>
|
||||
<g id="left">
|
||||
<path d="M1408 960v-128q0-26-19-45t-45-19h-502l189-189q19-19 19-45t-19-45l-91-91q-18-18-45-18t-45 18l-362 362-91 91q-18 18-18 45t18 45l91 91 362 362q18 18 45 18t45-18l91-91q18-18 18-45t-18-45l-189-189h502q26 0 45-19t19-45zm256-64q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
|
||||
</g>
|
||||
<g id="right">
|
||||
<path d="M1413 896q0-27-18-45l-91-91-362-362q-18-18-45-18t-45 18l-91 91q-18 18-18 45t18 45l189 189h-502q-26 0-45 19t-19 45v128q0 26 19 45t45 19h502l-189 189q-19 19-19 45t19 45l91 91q18 18 45 18t45-18l362-362 91-91q18-18 18-45zm251 0q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
|
||||
</g>
|
||||
<g id="clearall">
|
||||
<path transform="translate(336, 336) scale(0.75)" d="M1037 1395l102-102q19-19 19-45t-19-45l-307-307 307-307q19-19 19-45t-19-45l-102-102q-19-19-45-19t-45 19l-454 454q-19 19-19 45t19 45l454 454q19 19 45 19t45-19zm627-499q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
|
||||
</g>
|
||||
<g id="chooseall">
|
||||
<path transform="translate(336, 336) scale(0.75)" d="M845 1395l454-454q19-19 19-45t-19-45l-454-454q-19-19-45-19t-45 19l-102 102q-19 19-19 45t19 45l307 307-307 307q-19 19-19 45t19 45l102 102q19 19 45 19t45-19zm819-499q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
|
||||
</g>
|
||||
</defs>
|
||||
<use xlink:href="#up" x="0" y="0" fill="#666666" />
|
||||
<use xlink:href="#up" x="0" y="1792" fill="#447e9b" />
|
||||
<use xlink:href="#down" x="0" y="3584" fill="#666666" />
|
||||
<use xlink:href="#down" x="0" y="5376" fill="#447e9b" />
|
||||
<use xlink:href="#left" x="0" y="7168" fill="#666666" />
|
||||
<use xlink:href="#left" x="0" y="8960" fill="#447e9b" />
|
||||
<use xlink:href="#right" x="0" y="10752" fill="#666666" />
|
||||
<use xlink:href="#right" x="0" y="12544" fill="#447e9b" />
|
||||
<use xlink:href="#clearall" x="0" y="14336" fill="#666666" />
|
||||
<use xlink:href="#clearall" x="0" y="16128" fill="#447e9b" />
|
||||
<use xlink:href="#chooseall" x="0" y="17920" fill="#666666" />
|
||||
<use xlink:href="#chooseall" x="0" y="19712" fill="#447e9b" />
|
||||
</svg>
|
Before Width: | Height: | Size: 3.2 KiB |
@@ -1,19 +0,0 @@
|
||||
<svg width="14" height="84" viewBox="0 0 1792 10752" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<g id="sort">
|
||||
<path d="M1408 1088q0 26-19 45l-448 448q-19 19-45 19t-45-19l-448-448q-19-19-19-45t19-45 45-19h896q26 0 45 19t19 45zm0-384q0 26-19 45t-45 19h-896q-26 0-45-19t-19-45 19-45l448-448q19-19 45-19t45 19l448 448q19 19 19 45z"/>
|
||||
</g>
|
||||
<g id="ascending">
|
||||
<path d="M1408 1216q0 26-19 45t-45 19h-896q-26 0-45-19t-19-45 19-45l448-448q19-19 45-19t45 19l448 448q19 19 19 45z"/>
|
||||
</g>
|
||||
<g id="descending">
|
||||
<path d="M1408 704q0 26-19 45l-448 448q-19 19-45 19t-45-19l-448-448q-19-19-19-45t19-45 45-19h896q26 0 45 19t19 45z"/>
|
||||
</g>
|
||||
</defs>
|
||||
<use xlink:href="#sort" x="0" y="0" fill="#999999" />
|
||||
<use xlink:href="#sort" x="0" y="1792" fill="#447e9b" />
|
||||
<use xlink:href="#ascending" x="0" y="3584" fill="#999999" />
|
||||
<use xlink:href="#ascending" x="0" y="5376" fill="#447e9b" />
|
||||
<use xlink:href="#descending" x="0" y="7168" fill="#999999" />
|
||||
<use xlink:href="#descending" x="0" y="8960" fill="#447e9b" />
|
||||
</svg>
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,14 +0,0 @@
|
||||
<svg width="15" height="60" viewBox="0 0 1792 7168" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<g id="previous">
|
||||
<path d="M1037 1395l102-102q19-19 19-45t-19-45l-307-307 307-307q19-19 19-45t-19-45l-102-102q-19-19-45-19t-45 19l-454 454q-19 19-19 45t19 45l454 454q19 19 45 19t45-19zm627-499q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
|
||||
</g>
|
||||
<g id="next">
|
||||
<path d="M845 1395l454-454q19-19 19-45t-19-45l-454-454q-19-19-45-19t-45 19l-102 102q-19 19-19 45t19 45l307 307-307 307q-19 19-19 45t19 45l102 102q19 19 45 19t45-19zm819-499q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
|
||||
</g>
|
||||
</defs>
|
||||
<use xlink:href="#previous" x="0" y="0" fill="#333333" />
|
||||
<use xlink:href="#previous" x="0" y="1792" fill="#000000" />
|
||||
<use xlink:href="#next" x="0" y="3584" fill="#333333" />
|
||||
<use xlink:href="#next" x="0" y="5376" fill="#000000" />
|
||||
</svg>
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,30 +0,0 @@
|
||||
/*!
|
||||
* Bootstrap v5.0.1 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
|
||||
/*!
|
||||
* Sizzle CSS Selector Engine v2.3.6
|
||||
* https://sizzlejs.com/
|
||||
*
|
||||
* Copyright JS Foundation and other contributors
|
||||
* Released under the MIT license
|
||||
* https://js.foundation/
|
||||
*
|
||||
* Date: 2021-02-16
|
||||
*/
|
||||
|
||||
/*!
|
||||
* jQuery JavaScript Library v3.6.0
|
||||
* https://jquery.com/
|
||||
*
|
||||
* Includes Sizzle.js
|
||||
* https://sizzlejs.com/
|
||||
*
|
||||
* Copyright OpenJS Foundation and other contributors
|
||||
* Released under the MIT license
|
||||
* https://jquery.org/license
|
||||
*
|
||||
* Date: 2021-03-02T17:08Z
|
||||
*/
|
@@ -1,3 +0,0 @@
|
||||
<svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="#666666" d="M1024 1376v-192q0-14-9-23t-23-9h-192q-14 0-23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23-9t9-23zm256-672q0-88-55.5-163t-138.5-116-170-41q-243 0-371 213-15 24 8 42l132 100q7 6 19 6 16 0 25-12 53-68 86-92 34-24 86-24 48 0 85.5 26t37.5 59q0 38-20 61t-68 45q-63 28-115.5 86.5t-52.5 125.5v36q0 14 9 23t23 9h192q14 0 23-9t9-23q0-19 21.5-49.5t54.5-49.5q32-18 49-28.5t46-35 44.5-48 28-60.5 12.5-81zm384 192q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 655 B |
@@ -1,54 +0,0 @@
|
||||
{
|
||||
"name": "baton",
|
||||
"version": "2.2.3",
|
||||
"description": "Django Baton App",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"stats": "npx webpack --config ./webpack.prod.js --json > stats.json",
|
||||
"compile": "BATON_REVISION=$(git rev-parse HEAD) npx webpack --config ./webpack.prod.js",
|
||||
"watch": "npx webpack --watch --config ./webpack.dev.js",
|
||||
"dev": "BATON_REVISION=$(git rev-parse HEAD) webpack serve --host 0.0.0.0 --progress --config ./webpack.dev.js",
|
||||
"lint": "./node_modules/eslint/bin/eslint.js ."
|
||||
},
|
||||
"author": "abidibo",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.14.3",
|
||||
"@babel/preset-env": "^7.14.4",
|
||||
"@babel/register": "^7.13.16",
|
||||
"@fortawesome/fontawesome-free": "^5.15.3",
|
||||
"autoprefixer": "^10.2.6",
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-register": "^6.26.0",
|
||||
"bootstrap": "^5.0.1",
|
||||
"css-loader": "^5.2.6",
|
||||
"file-loader": "^6.2.0",
|
||||
"ini": "^2.0.0",
|
||||
"jquery": "^3.6.0",
|
||||
"js-event-dispatcher": "^0.1.0",
|
||||
"loader": "^2.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
"mini-svg-data-uri": "^1.3.3",
|
||||
"node-sass": "^6.0.0",
|
||||
"postcss-loader": "^5.3.0",
|
||||
"sass": "^1.34.0",
|
||||
"sass-loader": "^11.1.1",
|
||||
"style-loader": "^2.0.0",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.38.1",
|
||||
"webpack-dev-server": "^3.11.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^7.27.0",
|
||||
"eslint-config-standard": "^16.0.3",
|
||||
"eslint-plugin-babel": "^5.3.1",
|
||||
"eslint-plugin-flow-vars": "^0.5.0",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-standard": "^5.0.0",
|
||||
"webpack-bundle-analyzer": "^4.4.2",
|
||||
"webpack-cli": "^4.7.0",
|
||||
"webpack-merge": "^5.7.3"
|
||||
}
|
||||
}
|
@@ -1,5 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require('autoprefixer')
|
||||
]
|
||||
}
|
@@ -1,6 +0,0 @@
|
||||
set smarttab
|
||||
set shiftwidth=2
|
||||
set softtabstop=2
|
||||
set tabstop=2
|
||||
set expandtab
|
||||
let syntastic_scss_checkers = ['scss_lint']
|
@@ -1,14 +0,0 @@
|
||||
import $ from 'jquery'
|
||||
|
||||
let ActionResult = {
|
||||
/**
|
||||
* Menu component
|
||||
*
|
||||
* Adds a sidebar menu to the document
|
||||
*/
|
||||
init: function () {
|
||||
$('body').addClass('actionresult')
|
||||
}
|
||||
}
|
||||
|
||||
export default ActionResult
|
@@ -1,15 +0,0 @@
|
||||
import $ from 'jquery'
|
||||
|
||||
let AdminDocs = {
|
||||
/**
|
||||
* Footer component
|
||||
*
|
||||
* Moves the footer inside the main external container
|
||||
*/
|
||||
init: function (opts) {
|
||||
let container = $('<div />', {'class': 'admindocs-body'})
|
||||
container.append($('#content > *:not(h1):not(.breadcrumbs)')).appendTo($('#content'))
|
||||
}
|
||||
}
|
||||
|
||||
export default AdminDocs
|
@@ -1,214 +0,0 @@
|
||||
import Translator from './i18n'
|
||||
|
||||
class Analytics {
|
||||
constructor (gapi, token, viewId, domIds) {
|
||||
this.gapi = gapi
|
||||
this.token = token
|
||||
this.domIds = domIds
|
||||
this.viewId = viewId
|
||||
this.t = new Translator($('html').attr('lang'))
|
||||
}
|
||||
|
||||
init (timedelta) {
|
||||
let self = this
|
||||
this.gapi.analytics.ready(function () { self.run(timedelta) })
|
||||
}
|
||||
|
||||
run (timedelta) {
|
||||
let self = this
|
||||
let gapi = this.gapi
|
||||
|
||||
let spinner = $('<div />').css({
|
||||
textAlign: 'center',
|
||||
padding: '3rem 0',
|
||||
color: '#aaa',
|
||||
}).append($('<i />', {'class': 'fa fa-spinner fa-spin fa-3x fa-fw'}))
|
||||
|
||||
for (let prop in this.domIds) {
|
||||
if (['traffic', 'popular', 'browsers', 'acquisition', 'audience', 'social'].indexOf(prop) !== -1) {
|
||||
$('#' + this.domIds[prop]).append(spinner.clone())
|
||||
}
|
||||
}
|
||||
|
||||
let errorCb = containerId => () => {
|
||||
$('#' + containerId).empty()
|
||||
|
||||
let message = $('<div />').css({
|
||||
textAlign: 'center',
|
||||
padding: '3rem 0',
|
||||
color: '#aaa',
|
||||
}).text(this.t.get('retrieveDataError'))
|
||||
|
||||
$('#' + containerId).append(message)
|
||||
}
|
||||
|
||||
gapi.analytics.ready(function () {
|
||||
/**
|
||||
* Authorize the user with an access token obtained server side.
|
||||
*/
|
||||
gapi.analytics.auth.authorize({
|
||||
'serverAuth': {
|
||||
'access_token': self.token
|
||||
}
|
||||
})
|
||||
/**
|
||||
* Create a new ViewSelector2 instance to be rendered inside of an
|
||||
* element with the id "view-selector-container".
|
||||
*/
|
||||
var viewSelector = new gapi.analytics.ViewSelector({
|
||||
container: self.domIds.viewSelector,
|
||||
})
|
||||
viewSelector.execute()
|
||||
|
||||
let baseQuery = {
|
||||
'ids': 'ga:' + self.viewId,
|
||||
'start-date': timedelta,
|
||||
'end-date': 'yesterday'
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DataChart instance showing sessions over the past 15 days.
|
||||
*/
|
||||
let dataChart1 = new gapi.analytics.googleCharts.DataChart({
|
||||
query: {
|
||||
...baseQuery,
|
||||
'metrics': 'ga:sessions,ga:users',
|
||||
'dimensions': 'ga:date'
|
||||
},
|
||||
chart: {
|
||||
'container': self.domIds.traffic,
|
||||
'type': 'LINE',
|
||||
'options': {
|
||||
'width': '100%'
|
||||
}
|
||||
}
|
||||
})
|
||||
let trafficTimeout = setTimeout(errorCb(self.domIds.traffic), 20000)
|
||||
dataChart1.on('error', errorCb(self.domIds.traffic))
|
||||
dataChart1.on('success', () => clearTimeout(trafficTimeout))
|
||||
dataChart1.execute()
|
||||
/**
|
||||
* Creates a new DataChart instance showing top 5 most popular pages
|
||||
*/
|
||||
var dataChart2 = new gapi.analytics.googleCharts.DataChart({
|
||||
query: {
|
||||
...baseQuery,
|
||||
'metrics': 'ga:pageviews',
|
||||
'dimensions': 'ga:pagePath',
|
||||
'sort': '-ga:pageviews',
|
||||
'max-results': 7
|
||||
},
|
||||
chart: {
|
||||
'container': self.domIds.popular,
|
||||
'type': 'PIE',
|
||||
'options': {
|
||||
'width': '100%',
|
||||
'pieHole': 4 / 9,
|
||||
}
|
||||
}
|
||||
})
|
||||
let popularTimeout = setTimeout(errorCb(self.domIds.popular), 20000)
|
||||
dataChart2.on('error', errorCb(self.domIds.popular))
|
||||
dataChart2.on('success', () => clearTimeout(popularTimeout))
|
||||
dataChart2.execute()
|
||||
/**
|
||||
* Creates a new DataChart instance showing top borwsers
|
||||
*/
|
||||
var dataChart3 = new gapi.analytics.googleCharts.DataChart({
|
||||
query: {
|
||||
...baseQuery,
|
||||
'metrics': 'ga:sessions',
|
||||
'dimensions': 'ga:browser',
|
||||
'sort': '-ga:sessions',
|
||||
'max-results': 7
|
||||
},
|
||||
chart: {
|
||||
'container': self.domIds.browsers,
|
||||
'type': 'PIE',
|
||||
'options': {
|
||||
'width': '100%',
|
||||
'pieHole': 4 / 9,
|
||||
}
|
||||
}
|
||||
})
|
||||
let browsersTimeout = setTimeout(errorCb(self.domIds.browsers), 20000)
|
||||
dataChart3.on('error', errorCb(self.domIds.browsers))
|
||||
dataChart3.on('success', () => clearTimeout(browsersTimeout))
|
||||
dataChart3.execute()
|
||||
/**
|
||||
* Creates a new DataChart instance showing top referral
|
||||
*/
|
||||
var dataChart4 = new gapi.analytics.googleCharts.DataChart({
|
||||
query: {
|
||||
...baseQuery,
|
||||
'metrics': 'ga:sessions',
|
||||
'dimensions': 'ga:source',
|
||||
'sort': '-ga:sessions',
|
||||
'max-results': 7
|
||||
},
|
||||
chart: {
|
||||
'container': self.domIds.acquisition,
|
||||
'type': 'PIE',
|
||||
'options': {
|
||||
'width': '100%',
|
||||
'pieHole': 4 / 9,
|
||||
}
|
||||
}
|
||||
})
|
||||
let acquisitionTimeout = setTimeout(errorCb(self.domIds.acquisition), 20000)
|
||||
dataChart4.on('error', errorCb(self.domIds.acquisition))
|
||||
dataChart4.on('success', () => clearTimeout(acquisitionTimeout))
|
||||
dataChart4.execute()
|
||||
/**
|
||||
* Creates a new DataChart instance showing top visitors continents
|
||||
*/
|
||||
var dataChart5 = new gapi.analytics.googleCharts.DataChart({
|
||||
query: {
|
||||
...baseQuery,
|
||||
'metrics': 'ga:sessions',
|
||||
'dimensions': 'ga:country',
|
||||
'sort': '-ga:sessions',
|
||||
'max-results': 7
|
||||
},
|
||||
chart: {
|
||||
'container': self.domIds.audience,
|
||||
'type': 'PIE',
|
||||
'options': {
|
||||
'width': '100%',
|
||||
'pieHole': 4 / 9,
|
||||
}
|
||||
}
|
||||
})
|
||||
let audienceTimeout = setTimeout(errorCb(self.domIds.audience), 20000)
|
||||
dataChart5.on('error', errorCb(self.domIds.audience))
|
||||
dataChart5.on('success', () => clearTimeout(audienceTimeout))
|
||||
dataChart5.execute()
|
||||
/**
|
||||
* Creates a new DataChart instance showing social interactions over the past 15 days.
|
||||
*/
|
||||
var dataChart6 = new gapi.analytics.googleCharts.DataChart({
|
||||
query: {
|
||||
...baseQuery,
|
||||
'metrics': 'ga:socialInteractions',
|
||||
'dimensions': 'ga:socialInteractionNetwork',
|
||||
'sort': '-ga:socialInteractions',
|
||||
'max-results': 7
|
||||
},
|
||||
chart: {
|
||||
'container': self.domIds.social,
|
||||
'type': 'PIE',
|
||||
'options': {
|
||||
'width': '100%',
|
||||
'pieHole': 4 / 9,
|
||||
}
|
||||
}
|
||||
})
|
||||
let socialTimeout = setTimeout(errorCb(self.domIds.social), 20000)
|
||||
dataChart6.on('error', errorCb(self.domIds.social))
|
||||
dataChart6.on('success', () => clearTimeout(socialTimeout))
|
||||
dataChart6.execute()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default Analytics
|
@@ -1,7 +0,0 @@
|
||||
export default {
|
||||
sm: 576,
|
||||
md: 768,
|
||||
lg: 992,
|
||||
xl: 1200,
|
||||
xxl: 1400
|
||||
}
|
@@ -1,193 +0,0 @@
|
||||
import $ from 'jquery'
|
||||
import Translator from './i18n'
|
||||
|
||||
let ChangeForm = {
|
||||
/**
|
||||
* ChangeForm component
|
||||
*
|
||||
* Alert unsaved changes stuff
|
||||
* Display loading spinner if multipart
|
||||
*/
|
||||
init: function (opts) {
|
||||
var self = this
|
||||
this.form = $('#content-main form')
|
||||
if (opts.confirmUnsavedChanges) {
|
||||
this.formSubmitting = false
|
||||
this.t = new Translator($('html').attr('lang'))
|
||||
// wait for django SelectFilter to do its job
|
||||
setTimeout(function () {
|
||||
self.initData = self.serializeData()
|
||||
self.activate()
|
||||
}, 500)
|
||||
}
|
||||
if (opts.showMultipartUploading) {
|
||||
this.spinner()
|
||||
}
|
||||
self.fixNewlines()
|
||||
setTimeout(function () {
|
||||
self.fixNewlines() // js inserted ones
|
||||
}, 50)
|
||||
this.fixWrappedFields()
|
||||
if (opts.enableImagesPreview) {
|
||||
this.lazyLoadImages()
|
||||
}
|
||||
this.activateEntryCollapsing()
|
||||
this.changeFieldsetCollapseStyle()
|
||||
this.fixExpandFirstErrorCollapsing()
|
||||
this.initTemplates()
|
||||
},
|
||||
activate: function () {
|
||||
this.form.on('submit', () => (this.formSubmitting = true))
|
||||
$(window).on('beforeunload', this.alertDirty.bind(this))
|
||||
},
|
||||
serializeData: function () {
|
||||
// form serialize does not detect filter_horizontal controllers because
|
||||
// in that case options are not selected, just added to the list of options,
|
||||
// and jquery form serialize only serializes values which are set!
|
||||
let data = this.form.serialize()
|
||||
$('select.filtered[multiple][id$=_to]').each(function (k, select) {
|
||||
let optionsValues = []
|
||||
$(select).children('option').each(function (kk, option) {
|
||||
optionsValues.push($(option).attr('value'))
|
||||
})
|
||||
data += `&${jQuery(select).attr('name')}=${optionsValues.sort().join(',')}`
|
||||
})
|
||||
return data
|
||||
},
|
||||
isDirty: function () {
|
||||
return this.serializeData() !== this.initData
|
||||
},
|
||||
alertDirty: function (e) {
|
||||
if (this.formSubmitting || !this.isDirty()) {
|
||||
return undefined
|
||||
}
|
||||
let confirmationMessage = this.t.get('unsavedChangesAlert');
|
||||
(e || window.event).returnValue = confirmationMessage // Gecko + IE
|
||||
return confirmationMessage // Gecko + Webkit, Safari, Chrome etc.
|
||||
},
|
||||
spinner: function () {
|
||||
if (this.form.attr('enctype') === 'multipart/form-data') {
|
||||
this.form.on('submit', () => this.showSpinner())
|
||||
}
|
||||
},
|
||||
showSpinner: function () {
|
||||
let run = false
|
||||
let inputFiles = $('input[type=file]')
|
||||
inputFiles.each(function (index, input) {
|
||||
if (input.files.length !== 0) {
|
||||
run = true
|
||||
}
|
||||
})
|
||||
if (run) {
|
||||
let overlay = $('<div />', {'class': 'spinner-overlay'}).appendTo(document.body)
|
||||
let spinner = $('<i />', {'class': 'fa fa-spinner fa-spin fa-3x fa-fw'})
|
||||
$('<div />').append(
|
||||
$('<p />').text(this.t.get('uploading')),
|
||||
spinner
|
||||
).appendTo(overlay)
|
||||
}
|
||||
},
|
||||
fixWrappedFields: function () {
|
||||
this.form.find('.form-row').each(function (index, row) {
|
||||
var fieldBoxes = $(row).children('.fieldBox')
|
||||
fieldBoxes.each(function (index, fbox) {
|
||||
if ($(fbox).hasClass('errors')) {
|
||||
$(row).addClass('errors')
|
||||
}
|
||||
})
|
||||
fieldBoxes.wrapAll('<div class="wrapped-fields-container" />')
|
||||
if (fieldBoxes.length) {
|
||||
$(row).addClass('with-wrapped-fields')
|
||||
}
|
||||
})
|
||||
// this.form.find('.wrapped-fields-container > .fieldBox:first-child').children().unwrap()
|
||||
},
|
||||
fixNewlines: function () {
|
||||
$('.form-row br').replaceWith('<span class="newline"></span>')
|
||||
},
|
||||
lazyLoadImages: function () {
|
||||
$('.file-upload').each(function (index, p) {
|
||||
let cur = $(p).find('a')
|
||||
if (cur.length) {
|
||||
let url = cur.attr('href')
|
||||
let ext = url.split('?')[0].split('.').pop()
|
||||
if (['jpg', 'jpeg', 'png', 'bmp', 'svg', 'gif', 'tif', 'webp'].indexOf(ext) !== -1) {
|
||||
let spinner = $('<i />', {'class': 'fa fa-spinner fa-spin fa-2x fa-fw'}).css('color', '#aaa')
|
||||
let preview = $('<div />', {'class': 'py-2'}).append(spinner)
|
||||
$(p).prepend(preview)
|
||||
let image = new Image()
|
||||
image.onload = function () {
|
||||
spinner.replaceWith($(image).addClass('baton-image-preview'))
|
||||
}
|
||||
image.onerror = function () {
|
||||
preview.remove()
|
||||
}
|
||||
image.src = url
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
activateEntryCollapsing: function () {
|
||||
$('.collapse-entry h3')
|
||||
.addClass('entry-collapsed entry-collapse-full-toggler')
|
||||
.append('<span />') // just to have the toggler right aligned
|
||||
.append('<span class="entry-collapse-toggler" />')
|
||||
$('.collapse-entry')
|
||||
.click(function (e) {
|
||||
let target = $(e.target)
|
||||
if (target.hasClass('entry-collapse-full-toggler')) {
|
||||
target.toggleClass('entry-collapsed')
|
||||
} else if (target.parent('.entry-collapse-full-toggler').length > 0) {
|
||||
target.parent('.entry-collapse-full-toggler').toggleClass('entry-collapsed')
|
||||
}
|
||||
})
|
||||
$('.form-row.errors').each(function (index, el) {
|
||||
if ($(el).parent('fieldset').prev('h3.entry-collapsed')) {
|
||||
$(el).parent('fieldset').prev('h3.entry-collapsed').removeClass('entry-collapsed')
|
||||
}
|
||||
})
|
||||
},
|
||||
fixExpandFirstErrorCollapsing: function () {
|
||||
$('.expand-first').each(function (index, el) {
|
||||
if ($(el).find('.inline-related[id$=0] .form-row.errors').length) {
|
||||
// inverse logic
|
||||
$(el).find('.inline-related[id$=0] .form-row.errors').parent('fieldset').prev('h3').addClass('entry-collapsed')
|
||||
}
|
||||
})
|
||||
},
|
||||
changeFieldsetCollapseStyle: function () {
|
||||
$(window).on('load', function () {
|
||||
$('fieldset.collapse > h2').each(function (index, title) {
|
||||
let text = $(title).text().replace(/\(.*\)/, '')
|
||||
setTimeout(function () {
|
||||
$(title).html(text).on('click', function () {
|
||||
$(this).parent('.collapse').toggleClass('collapsed')
|
||||
})
|
||||
}, 100)
|
||||
})
|
||||
})
|
||||
},
|
||||
initTemplates: function () {
|
||||
const positionMap = {
|
||||
above: 'before',
|
||||
below: 'after',
|
||||
top: 'prepend',
|
||||
bottom: 'append',
|
||||
right: 'after'
|
||||
}
|
||||
$('template').each(function (index, template) {
|
||||
let field = $(template).attr('id').replace('template-', '')
|
||||
let position = positionMap[$(template).attr('data-position')]
|
||||
if (position !== undefined) {
|
||||
let el = $(template).attr('data-position') === 'right'
|
||||
? $('.form-row.field-' + field + ' #id_' + field)
|
||||
: $('.form-row.field-' + field)
|
||||
el[position]($(template).html())
|
||||
} else {
|
||||
console.error('Baton: wrong form include position detected')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default ChangeForm
|
@@ -1,203 +0,0 @@
|
||||
import $ from 'jquery'
|
||||
import Translator from './i18n'
|
||||
import Modal from './Modal'
|
||||
import breakpoints from './Breakpoints'
|
||||
|
||||
let ChangeList = {
|
||||
/**
|
||||
* ChangeList component
|
||||
*
|
||||
* Filtering stuff
|
||||
*/
|
||||
init: function (opts) {
|
||||
this._filtersDiv = $('#changelist-filter')
|
||||
this.t = new Translator($('html').attr('lang'))
|
||||
this.filtersForm = opts.changelistFiltersForm
|
||||
this.filtersInModal = opts.changelistFiltersInModal
|
||||
this.filtersAlwaysOpen = opts.changelistFiltersAlwaysOpen
|
||||
this.initTemplates()
|
||||
if (this._filtersDiv.length) {
|
||||
var self = this
|
||||
setTimeout(function () {
|
||||
self.activate()
|
||||
}, 200) // select2
|
||||
this.fixRangeFilter()
|
||||
}
|
||||
},
|
||||
activate: function () {
|
||||
if ($('.changelist-form-container').length) {
|
||||
// django >= 3.1
|
||||
$('#changelist-filter').appendTo($('.changelist-form-container'))
|
||||
}
|
||||
let isModal = false
|
||||
if (this.filtersAlwaysOpen) {
|
||||
$(document.body).addClass(
|
||||
'changelist-filter-active changelist-filter-always-open'
|
||||
)
|
||||
} else {
|
||||
// filters active?
|
||||
let _activeFilters = /__[^=]+=/.test(location.search)
|
||||
// actions ?
|
||||
let _activeActions = $('#changelist-form > .actions').length !== 0
|
||||
let _changelistForm = $('#changelist-form')
|
||||
let _filtersToggler = $('<a />', {
|
||||
class:
|
||||
'changelist-filter-toggler' +
|
||||
(_activeFilters ? ' active' : '') +
|
||||
(_activeActions ? ' with-actions' : '')
|
||||
}).html(
|
||||
'<i class="fa fa-filter"></i> <span>' + this.t.get('filter') + '</span>'
|
||||
)
|
||||
|
||||
if (this.filtersInModal || parseInt($(window).width()) < breakpoints.lg) {
|
||||
let self = this
|
||||
isModal = true
|
||||
// wait for filters used js to exec
|
||||
$('#changelist-filter').prop('id', 'changelist-filter-modal')
|
||||
let titleEl = $('#changelist-filter-modal > h2')
|
||||
let title = titleEl.html()
|
||||
titleEl.remove()
|
||||
let content = $('#changelist-filter-modal')
|
||||
// remove from dom
|
||||
this.modal = new Modal({
|
||||
title,
|
||||
content,
|
||||
size: 'md',
|
||||
hideFooter: !this.filtersForm,
|
||||
actionBtnLabel: this.t.get('filter'),
|
||||
actionBtnCb: function () { self.filter(content) }
|
||||
})
|
||||
_filtersToggler.click(() => {
|
||||
self.modal.toggle()
|
||||
})
|
||||
} else {
|
||||
_filtersToggler.click(() => {
|
||||
$(document.body).toggleClass('changelist-filter-active')
|
||||
if (parseInt(this._filtersDiv.css('max-width')) === 100) {
|
||||
// diff between mobile and lg
|
||||
$('html,body').animate({
|
||||
scrollTop: this._filtersDiv.offset().top
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
_changelistForm.prepend(_filtersToggler)
|
||||
}
|
||||
|
||||
if (!isModal && this.filtersForm) {
|
||||
// add filters button
|
||||
let btn = $('<a />', {'class': 'btn btn-primary'}).html(this.t.get('filter'))
|
||||
.on('click', () => this.filter($('#changelist-filter')))
|
||||
$('#changelist-filter').append($('<div />', {'class': 'text-center mb-3'}).append(btn))
|
||||
}
|
||||
|
||||
if (/_popup=1/.test(location.href)) {
|
||||
$('#changelist-form .results').css('padding-top', '78px')
|
||||
}
|
||||
},
|
||||
getDropdownValue: function (dropdown) {
|
||||
let items = $(dropdown).find('option').attr('value').substr(1).split('&')
|
||||
let values = $(dropdown).val().substr(1).split('&').filter(item => items.indexOf(item) === -1)
|
||||
return values.length ? values.join('&') : null
|
||||
},
|
||||
filter: function (wrapper) {
|
||||
var self = this
|
||||
let qs = []
|
||||
|
||||
let dropdowns = wrapper.find('select')
|
||||
let textInputs = wrapper.find('input').not('[type=hidden]')
|
||||
|
||||
dropdowns
|
||||
.toArray()
|
||||
.map(el => self.getDropdownValue(el))
|
||||
.filter(v => v !== null)
|
||||
.forEach(v => qs.push(v))
|
||||
|
||||
textInputs.each((idx, el) => el.value !== '' ? qs.push(`${el.name}=${el.value}`) : null)
|
||||
|
||||
// console.log(location.pathname + (qs.length ? '?' + qs.filter(q => q !== '').join('&') : ''), qs)
|
||||
location.href = location.pathname + (qs.length ? '?' + qs.filter(q => q !== '').join('&') : '')
|
||||
},
|
||||
initTemplates: function () {
|
||||
const positionMap = {
|
||||
above: 'before',
|
||||
below: 'after',
|
||||
top: 'prepend',
|
||||
bottom: 'append'
|
||||
}
|
||||
|
||||
$('template[data-type=include]').each(function (index, template) {
|
||||
let position = positionMap[$(template).attr('data-position')]
|
||||
if (position !== undefined) {
|
||||
let el = $('#changelist-form')
|
||||
el[position]($(template).html())
|
||||
} else {
|
||||
console.error('Baton: wrong changelist include position detected')
|
||||
}
|
||||
})
|
||||
|
||||
$('template[data-type=filters-include]').each(function (index, template) {
|
||||
let position = positionMap[$(template).attr('data-position')]
|
||||
if (
|
||||
position !== undefined &&
|
||||
position !== 'before' &&
|
||||
position !== 'after'
|
||||
) {
|
||||
if (position === 'prepend' && $('#changelist-filter-clear').length) {
|
||||
$('#changelist-filter-clear').after($(template).html())
|
||||
} else if (
|
||||
position === 'prepend' &&
|
||||
$('#changelist-filter > h2').length
|
||||
) {
|
||||
$('#changelist-filter > h2').after($(template).html())
|
||||
} else {
|
||||
let el = $('#changelist-filter')
|
||||
el[position]($(template).html())
|
||||
}
|
||||
} else {
|
||||
console.error(
|
||||
'Baton: wrong changelist filters include position detected'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
$('template[data-type=attributes]').each(function (index, template) {
|
||||
try {
|
||||
let data = JSON.parse($(template).html())
|
||||
|
||||
for (let key in data) {
|
||||
if (data.hasOwnProperty(key)) {
|
||||
let selector
|
||||
let getParent = 'tr'
|
||||
if (data[key]['selector']) {
|
||||
selector = data[key]['selector']
|
||||
delete data[key]['selector']
|
||||
} else {
|
||||
selector =
|
||||
'#result_list tr input[name=_selected_action][value=' +
|
||||
key +
|
||||
']'
|
||||
}
|
||||
if (data[key]['getParent'] !== undefined) {
|
||||
getParent = data[key]['getParent']
|
||||
delete data[key]['getParent']
|
||||
}
|
||||
|
||||
let el = getParent ? $(selector).parents(getParent) : $(selector)
|
||||
el.attr(data[key])
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
})
|
||||
},
|
||||
fixRangeFilter: function () {
|
||||
if (this.filtersForm) {
|
||||
$('.admindatefilter .controls').remove()
|
||||
$('.admindatefilter form').onSubmit = function () { return false }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ChangeList
|
@@ -1,30 +0,0 @@
|
||||
import $ from 'jquery'
|
||||
import { copyTextToClipboard } from './Utils'
|
||||
|
||||
let Filer = {
|
||||
/**
|
||||
* ChangeList component
|
||||
*
|
||||
* Filtering stuff
|
||||
*/
|
||||
init: function (opts) {
|
||||
this.fixIcons()
|
||||
this.fixCopyToClipboard()
|
||||
},
|
||||
fixIcons: function () {
|
||||
$('.fa-pencil').addClass('fa-pencil-alt')
|
||||
},
|
||||
fixCopyToClipboard: function () {
|
||||
let copyBtns = $('.action-button .fa-link')
|
||||
copyBtns.on('click', function (evt) {
|
||||
evt.preventDefault()
|
||||
var link = $(this).parent('.action-button').attr('href')
|
||||
if (!link) {
|
||||
link = $(this).parent('.action-button').next('.action-button').attr('href')
|
||||
}
|
||||
copyTextToClipboard(link)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default Filer
|
@@ -1,17 +0,0 @@
|
||||
import $ from 'jquery'
|
||||
|
||||
let Footer = {
|
||||
/**
|
||||
* Footer component
|
||||
*
|
||||
* Moves the footer inside the main external container
|
||||
*/
|
||||
init: function (opts) {
|
||||
$('#footer').appendTo('#content')
|
||||
if (opts.remove) {
|
||||
$('#footer').remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Footer
|
@@ -1,47 +0,0 @@
|
||||
import $ from 'jquery'
|
||||
|
||||
const Login = {
|
||||
init: function (config) {
|
||||
// splash
|
||||
if (config.loginSplash) {
|
||||
$('body.login').css({
|
||||
background: `url(${config.loginSplash}) no-repeat center center`,
|
||||
backgroundSize: 'cover'
|
||||
})
|
||||
}
|
||||
// form
|
||||
let inputUsername = $('#id_username')
|
||||
let inputPassword = $('#id_password')
|
||||
|
||||
const usernameField = $('<div />', { class: 'input-group mb-2' })
|
||||
.append(
|
||||
$('<span />', { class: 'input-group-text' }).append(
|
||||
'<i class="fa fa-user"></i>'
|
||||
)
|
||||
)
|
||||
.append(inputUsername.clone())
|
||||
|
||||
inputUsername.replaceWith(usernameField)
|
||||
|
||||
// adds show/hide password functionality
|
||||
let passwordInputField = inputPassword.clone()
|
||||
let viewPasswordIcon = $('<i />', {'class': 'fa fa-eye pwd-visibility-toggle'}).on('click', function () {
|
||||
let visible = $(this).hasClass('fa-eye-slash')
|
||||
$(this)[visible ? 'removeClass' : 'addClass']('fa-eye-slash')
|
||||
passwordInputField.attr('type', visible ? 'password' : 'text')
|
||||
})
|
||||
|
||||
const passwordField = $('<div />', { class: 'input-group mb-2' })
|
||||
.append(
|
||||
$('<span />', { class: 'input-group-text' }).append(
|
||||
'<i class="fa fa-key"></i>'
|
||||
)
|
||||
)
|
||||
.append(passwordInputField)
|
||||
.append(viewPasswordIcon)
|
||||
|
||||
inputPassword.replaceWith(passwordField)
|
||||
}
|
||||
}
|
||||
|
||||
export default Login
|
@@ -1,375 +0,0 @@
|
||||
import $ from 'jquery'
|
||||
import Translator from './i18n'
|
||||
|
||||
let Menu = {
|
||||
/**
|
||||
* Menu component
|
||||
*
|
||||
* Adds a sidebar menu to the document
|
||||
*/
|
||||
init: function (config, Dispatcher) {
|
||||
this.Dispatcher = Dispatcher
|
||||
this.t = new Translator($('html').attr('lang'))
|
||||
this.collapsableUserArea = config.collapsableUserArea
|
||||
this.menuTitle = config.menuTitle
|
||||
this.searchField = config.searchField
|
||||
this.appListUrl = config.api.app_list
|
||||
this.gravatarUrl = config.api.gravatar
|
||||
this.gravatarDefaultImg = config.gravatarDefaultImg
|
||||
this.alwaysCollapsed = $('#header').hasClass('menu-always-collapsed')
|
||||
this.fixNodes()
|
||||
this.brandingClone = $('#branding').clone()
|
||||
this.manageBrandingUserTools()
|
||||
this.manageSearchField()
|
||||
this.fetchData()
|
||||
this.setHeight()
|
||||
let self = this
|
||||
$(window).on('resize', function () {
|
||||
self.setHeight()
|
||||
self.manageBrandingUserTools()
|
||||
})
|
||||
},
|
||||
fixNodes: function () {
|
||||
let container = $('<div/>', { class: 'container-fluid' })
|
||||
$('#footer').before(container)
|
||||
let row = $('<div/>', { class: 'row' }).appendTo(container)
|
||||
this.menu = $('<nav/>', { class: 'col-lg-2 sidebar-menu' }).appendTo(row)
|
||||
$('#content')
|
||||
.addClass('col-lg-10')
|
||||
.prepend($('.breadcrumbs'))
|
||||
.appendTo(row)
|
||||
|
||||
$('#content > h1').after($('.messagelist'))
|
||||
|
||||
let title = $('<h1 />', { class: 'd-block d-lg-none' }).text(
|
||||
this.menuTitle ? this.menuTitle : 'Menu'
|
||||
)
|
||||
$('<i/>', { class: 'fa fa-times' })
|
||||
.click(() => {
|
||||
$(document.body).removeClass('menu-open')
|
||||
})
|
||||
.appendTo(title)
|
||||
this.menu.append(title)
|
||||
|
||||
if (this.alwaysCollapsed) {
|
||||
let close = $('<i />', { class: 'fa fa-times toggle-menu' })
|
||||
.appendTo(this.menu)
|
||||
.click(() => {
|
||||
$(document.body).removeClass('menu-open')
|
||||
})
|
||||
}
|
||||
},
|
||||
manageBrandingUserTools: function () {
|
||||
if (parseInt($(window).width()) >= 992) {
|
||||
// move user tools
|
||||
this.menu.prepend($('#user-tools'))
|
||||
if (this.alwaysCollapsed) {
|
||||
// copy branding
|
||||
this.menu.prepend(this.brandingClone)
|
||||
} else {
|
||||
// move branding
|
||||
this.menu.prepend($('#branding'))
|
||||
}
|
||||
if ($('#user-tools-sidebar').length === 0) {
|
||||
this.renderUserTools()
|
||||
}
|
||||
} else {
|
||||
$('#header').append($('#user-tools'))
|
||||
if (this.alwaysCollapsed) {
|
||||
this.menu.find('#branding').remove()
|
||||
} else {
|
||||
$('#header .navbar-toggler').after($('#branding'))
|
||||
}
|
||||
if ($('#user-tools-sidebar').length === 0) {
|
||||
this.removeUserTools()
|
||||
}
|
||||
}
|
||||
},
|
||||
manageSearchField () {
|
||||
// unset
|
||||
if (!this.searchField || !this.searchField.url) {
|
||||
return
|
||||
}
|
||||
|
||||
let container = $('<div />', { class: 'search-field-tool' })
|
||||
|
||||
let field = $('<input />', {
|
||||
class: 'form-control form-control-sm',
|
||||
type: 'text',
|
||||
list: 'admin-search-datalist',
|
||||
placeholder: this.searchField.label || this.t('search')
|
||||
})
|
||||
let dataList = $('<div />', { id: 'admin-search-datalist' }).on('mouseover', e => {
|
||||
if ($(e.target).hasClass('datalist-option') || $(e.target).parent('.datalist-option').length) {
|
||||
dataList.find('.datalist-option').removeClass('selected')
|
||||
let item = $(e.target).hasClass('datalist-option') ? $(e.target) : $(e.target).parent('.datalist-option')
|
||||
item.addClass('selected')
|
||||
}
|
||||
})
|
||||
|
||||
let navigateDataList = code => {
|
||||
let target
|
||||
let active = dataList.find('.datalist-option.selected').first()
|
||||
if (!active.length) {
|
||||
target = dataList.find('.datalist-option')[code === 40 ? 'first' : 'last']()
|
||||
} else {
|
||||
if (code === 40) {
|
||||
let next = active.next()
|
||||
target = next.length ? next : dataList.find('.datalist-option').first()
|
||||
} else {
|
||||
let prev = active.prev()
|
||||
target = prev.length ? prev : dataList.find('.datalist-option').last()
|
||||
}
|
||||
}
|
||||
if (target) {
|
||||
active.removeClass('selected')
|
||||
$(target).addClass('selected')
|
||||
target[0].scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'end',
|
||||
inline: 'nearest'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
field.on('blur', e => setTimeout(() => dataList.hide(), 150))
|
||||
field.on('focus', e => dataList.show())
|
||||
field.on('keyup', e => {
|
||||
var code = e.keyCode || e.which
|
||||
|
||||
if (code === 13) {
|
||||
// goto url if there is an active voice
|
||||
let active = dataList.find('.datalist-option.selected').first()
|
||||
if (active.length) {
|
||||
location.href = active.attr('data-url')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if ([40, 38].indexOf(code) !== -1) {
|
||||
// move
|
||||
navigateDataList(code)
|
||||
} else {
|
||||
// search
|
||||
if ($(field).val().length < 1) {
|
||||
dataList.empty()
|
||||
return
|
||||
}
|
||||
|
||||
container.addClass('loading')
|
||||
$.getJSON(this.searchField.url, { text: $(field).val() })
|
||||
.done(data => {
|
||||
container.removeClass('loading')
|
||||
dataList.empty()
|
||||
data.data.forEach((r, index) => dataList.append(`
|
||||
<div class="datalist-option${index === 0 ? ' selected' : ''}" onclick="location.href='${r.url}'" data-url="${r.url}"><a href="${r.url}">${r.label}</a>${r.icon ? `<i onclick="location.href='${r.url}'" class="${r.icon}"></i>` : ''}</div>`)
|
||||
)
|
||||
})
|
||||
.fail((jqxhr, textStatus, err) => {
|
||||
console.log(err)
|
||||
container.removeClass('loading')
|
||||
dataList.empty()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
$('#user-tools-sidebar').after(container.append([field, dataList]))
|
||||
},
|
||||
renderUserTools: function () {
|
||||
let self = this
|
||||
let container = $('<div />', { id: 'user-tools-sidebar' })
|
||||
let expandUserArea = $('<i />', {'class': 'fa fa-angle-down user-area-toggler'}).on('click', function () {
|
||||
$(this).toggleClass('fa-angle-up')
|
||||
container.toggleClass('collapsed')
|
||||
})
|
||||
if (this.collapsableUserArea) {
|
||||
container.addClass('collapsed')
|
||||
}
|
||||
container.insertAfter('#user-tools')
|
||||
let userInfo = $('<div />', { class: 'user-info' })
|
||||
.html(
|
||||
'<div class="spinner-border text-primary" role="status"><span class="sr-only">Loading...</span></div><div>' +
|
||||
$('#user-tools .dropdown-toggle').text() +
|
||||
'</div>'
|
||||
)
|
||||
.appendTo(container)
|
||||
// gravatar
|
||||
$.getJSON(this.gravatarUrl, function (data) {
|
||||
let img = $('<img />', {
|
||||
class: 'gravatar-icon',
|
||||
src: 'https://www.gravatar.com/avatar/{hash}?s=50&d={default}'
|
||||
.replace('{hash}', data.hash)
|
||||
.replace('{default}', self.gravatarDefaultImg)
|
||||
})
|
||||
userInfo.find('.spinner-border').replaceWith(img)
|
||||
if (self.collapsableUserArea) {
|
||||
img.after(expandUserArea)
|
||||
}
|
||||
}).fail(function (err) {
|
||||
console.error(err.responseText)
|
||||
let img = $('<img />', {
|
||||
class: 'gravatar-icon',
|
||||
src: 'https://www.gravatar.com/avatar/{hash}?s=50&d={default}'
|
||||
.replace('{hash}', '')
|
||||
.replace('{default}', self.gravatarDefaultImg)
|
||||
})
|
||||
userInfo.find('.spinner-border').replaceWith(img)
|
||||
if (self.collapsableUserArea) {
|
||||
img.after(expandUserArea)
|
||||
}
|
||||
})
|
||||
let linksContainer = $('<div />', { class: 'user-links' }).appendTo(
|
||||
container
|
||||
)
|
||||
$('#user-tools .dropdown-menu a').each(function (index, el) {
|
||||
let cls = 'view-site'
|
||||
if (/password_change/.test($(el).attr('href'))) {
|
||||
cls = 'password'
|
||||
} else if (/logout/.test($(el).attr('href'))) {
|
||||
cls = 'logout'
|
||||
}
|
||||
let text = $(el).text()
|
||||
let clone = $(el)
|
||||
.clone()
|
||||
.html('')
|
||||
.attr('class', cls)
|
||||
.attr('title', text)
|
||||
if (cls === 'view-site') {
|
||||
clone.attr('target', '_blank')
|
||||
}
|
||||
linksContainer.append(clone)
|
||||
})
|
||||
},
|
||||
removeUserTools: function () {
|
||||
$('#user-tools-sidebar').remove()
|
||||
},
|
||||
fetchData: function () {
|
||||
let self = this
|
||||
$.getJSON(this.appListUrl, function (data) {
|
||||
self.render(data)
|
||||
self.Dispatcher.emit('onMenuReady')
|
||||
}).fail(function (err) {
|
||||
console.error(err.responseText)
|
||||
self.menu.remove()
|
||||
$('#content')
|
||||
.removeClass('col-md-9')
|
||||
.removeClass('col-lg-10')
|
||||
.css('flex-grow', 1)
|
||||
self.Dispatcher.emit('onMenuError')
|
||||
})
|
||||
},
|
||||
setHeight: function () {
|
||||
let height = $(window).height() - $('#header').height() - 19 // nav padding and border
|
||||
this.menu.css('min-height', height + 'px')
|
||||
$('#content').css('padding-bottom', $('#footer').height() + 20 + 'px')
|
||||
},
|
||||
render: function (data) {
|
||||
let self = this
|
||||
let mainUl = $('<ul/>', { class: 'depth-0' }).appendTo(self.menu)
|
||||
data.forEach((voice, index) => {
|
||||
let active = false
|
||||
if (voice.type === 'free') {
|
||||
if (voice.re) {
|
||||
let re = new RegExp(voice.re)
|
||||
active = re.test(location.pathname)
|
||||
} else {
|
||||
active = location.pathname === voice.url
|
||||
}
|
||||
} else {
|
||||
if (voice.url) {
|
||||
let pathRexp = new RegExp(voice.url)
|
||||
active = pathRexp.test(location.pathname)
|
||||
}
|
||||
}
|
||||
let li = $('<li />', {
|
||||
class:
|
||||
'top-level ' +
|
||||
voice.type +
|
||||
(voice.defaultOpen ? ' default-open' : '') +
|
||||
(active ? ' active' : '')
|
||||
})
|
||||
let a = $('<' + (voice.url ? 'a' : 'span') + ' />', {
|
||||
href: voice.url || '#'
|
||||
})
|
||||
.text(voice.label)
|
||||
.appendTo(li)
|
||||
// icon
|
||||
if (voice.icon) {
|
||||
$('<i />', { class: voice.icon }).prependTo(a)
|
||||
}
|
||||
let subUl
|
||||
if (voice.children && voice.children.length) {
|
||||
subUl = $('<ul />', { class: 'depth-1' }).appendTo(li)
|
||||
a.addClass('has-children')
|
||||
|
||||
voice.children.forEach((model, i) => {
|
||||
let active = false
|
||||
if (model.type === 'free') {
|
||||
if (model.re) {
|
||||
let re = new RegExp(model.re)
|
||||
active = re.test(location.pathname)
|
||||
} else {
|
||||
active = location.pathname === model.url
|
||||
}
|
||||
} else if (model.url) {
|
||||
let pathRexp = new RegExp(model.url)
|
||||
active = pathRexp.test(location.pathname)
|
||||
}
|
||||
let subLi = $('<li />')
|
||||
if (active) {
|
||||
subLi.addClass('active')
|
||||
li.addClass('with-active')
|
||||
}
|
||||
let a = $('<a />', {
|
||||
href: model.url
|
||||
})
|
||||
.text(model.label)
|
||||
.appendTo(subLi)
|
||||
// icon
|
||||
if (model.icon) {
|
||||
$('<i />', { class: model.icon }).prependTo(a)
|
||||
}
|
||||
subLi.appendTo(subUl)
|
||||
})
|
||||
}
|
||||
li.appendTo(mainUl)
|
||||
})
|
||||
|
||||
$('.has-children').on('click', function (evt) {
|
||||
evt.preventDefault()
|
||||
let self = this
|
||||
let p = $(this).parent()
|
||||
let depth0 = $('.depth-0')
|
||||
let depth1 = p.children('ul')
|
||||
if (
|
||||
p.hasClass('open') ||
|
||||
(p.hasClass('default-open') && !$('body').hasClass('menu-open'))
|
||||
) {
|
||||
p.removeClass('open default-open')
|
||||
depth1.children('.nav-back').remove()
|
||||
depth0.css('overflow', 'auto')
|
||||
} else {
|
||||
if (p.hasClass('top-level')) {
|
||||
$('.top-level').removeClass('open')
|
||||
$('.top-level')
|
||||
.find('.nav-back')
|
||||
.remove()
|
||||
}
|
||||
p.addClass('open')
|
||||
let back = $(
|
||||
'<li class="nav-item nav-back"><a href="#"><i class="fa fa-angle-double-left"></i> ' + // eslint-disable-line
|
||||
$(this).text() +
|
||||
'</a></li>'
|
||||
)
|
||||
back.on('click', function () {
|
||||
$(self).trigger('click')
|
||||
})
|
||||
depth1.prepend(back)
|
||||
depth0.css('overflow', 'hidden')
|
||||
depth0.scrollTop(0) // return to top
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default Menu
|
@@ -1,72 +0,0 @@
|
||||
import $ from 'jquery'
|
||||
import bootstrap from 'bootstrap/dist/js/bootstrap.bundle'
|
||||
|
||||
let Messages = {
|
||||
/**
|
||||
* Messages
|
||||
*
|
||||
* If toast are enabled, moves messages ul lis in toasts
|
||||
*/
|
||||
init: function (opts) {
|
||||
if (opts.messagesToasts) {
|
||||
let toasts = []
|
||||
let all = true
|
||||
$('.messagelist li').each((index, el) => {
|
||||
let lv = $(el).attr('class')
|
||||
if (opts.messagesToasts === true || opts.messagesToasts.indexOf(lv) !== -1) {
|
||||
toasts.push(this.createToast($(el).attr('class'), $(el).html()))
|
||||
$(el).remove()
|
||||
} else {
|
||||
all = false
|
||||
}
|
||||
})
|
||||
if (toasts.length) {
|
||||
$('<div />', {'class': 'toast-container position-absolute top-0 end-0 p-3'})
|
||||
.append(toasts).appendTo($(document.body))
|
||||
}
|
||||
if (all) {
|
||||
$('.messagelist').remove()
|
||||
}
|
||||
}
|
||||
const toastElList = [].slice.call(document.querySelectorAll('.toast'))
|
||||
toastElList.map(function (toastEl) {
|
||||
new bootstrap.Toast(toastEl, { autohide: false }).show()
|
||||
})
|
||||
},
|
||||
levelsMap: {
|
||||
info: {
|
||||
bg: 'info',
|
||||
icon: 'fa fa-info-circle',
|
||||
iconColor: '#fff'
|
||||
},
|
||||
success: {
|
||||
bg: 'success',
|
||||
icon: 'fa fa-check-circle',
|
||||
iconColor: '#fff'
|
||||
},
|
||||
warning: {
|
||||
bg: 'warning',
|
||||
icon: 'fa fa-exclamation-circle',
|
||||
iconColor: '#fff'
|
||||
},
|
||||
error: {
|
||||
bg: 'danger',
|
||||
icon: 'fa fa-exclamation-circle',
|
||||
iconColor: '#fff'
|
||||
}
|
||||
},
|
||||
createToast (level, content) {
|
||||
let toast = `
|
||||
<div class="toast d-flex align-items-center text-white bg-${this.levelsMap[level].bg} border-0" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="toast-body">
|
||||
<i class="${this.levelsMap[level].icon}" style="color: ${this.levelsMap[level].iconColor}; margin-right: .5rem"></i>
|
||||
${content}
|
||||
</div>
|
||||
<button type="button" class="btn-close btn-close-white ms-auto me-2" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
`
|
||||
return $(toast)
|
||||
}
|
||||
}
|
||||
|
||||
export default Messages
|
@@ -1,155 +0,0 @@
|
||||
import $ from 'jquery'
|
||||
import Translator from './i18n'
|
||||
import bootstrap from 'bootstrap/dist/js/bootstrap.bundle'
|
||||
|
||||
class Modal {
|
||||
constructor (config) {
|
||||
this.t = new Translator($('html').attr('lang'))
|
||||
|
||||
this.opts = {
|
||||
subtitle: '',
|
||||
hideFooter: false,
|
||||
showBackBtn: false,
|
||||
backBtnCb: function () {},
|
||||
actionBtnLabel: this.t.get('save'),
|
||||
actionBtnCb: null,
|
||||
onUrlLoaded: function () {},
|
||||
size: 'lg',
|
||||
onClose: function () {}
|
||||
}
|
||||
|
||||
this.isOpen = false
|
||||
this.create() // adds modal, modalObj and events
|
||||
this.update(config)
|
||||
}
|
||||
|
||||
create () {
|
||||
this.modalObj = $('<div />', {'class': 'modal fade'}).appendTo(document.body)
|
||||
this.modalObj.html(`
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"></h5>
|
||||
<div style="display: flex;">
|
||||
<button type="button" class="back me-1" aria-label="Back">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</button>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">${this.t.get('close')}</button>
|
||||
<button type="button" class="btn btn-primary btn-action"></button>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
`)
|
||||
|
||||
var self = this
|
||||
this.modalObj.on('hidden.bs.modal', function () {
|
||||
self.close()
|
||||
})
|
||||
|
||||
this.modal = new bootstrap.Modal(this.modalObj[0])
|
||||
}
|
||||
|
||||
update (config) {
|
||||
this.options = $.extend({}, this.opts, config)
|
||||
this.setSize()
|
||||
this.setHeader()
|
||||
this.setTitle()
|
||||
this.setSubtitle()
|
||||
this.setContent()
|
||||
this.setButtons()
|
||||
}
|
||||
|
||||
setSize () {
|
||||
this.modalObj.find('.modal-dialog').addClass('modal-' + this.options.size)
|
||||
}
|
||||
|
||||
setHeader () {
|
||||
if (this.options.showBackBtn) {
|
||||
this.modalObj.find('.modal-header .back').show()
|
||||
this.modalObj
|
||||
.find('.modal-header .back')
|
||||
.on('click', this.options.backBtnCb)
|
||||
} else {
|
||||
this.modalObj.find('.modal-header .back').hide()
|
||||
}
|
||||
}
|
||||
|
||||
setTitle () {
|
||||
if (typeof this.options.title !== 'undefined') {
|
||||
this.modalObj.find('.modal-title').html(this.options.title)
|
||||
}
|
||||
}
|
||||
|
||||
setSubtitle () {
|
||||
if (this.options.subtitle) {
|
||||
this.modalObj
|
||||
.find('.modal-subtitle')
|
||||
.show()
|
||||
.html(this.options.subtitle)
|
||||
} else {
|
||||
this.modalObj
|
||||
.find('.modal-subtitle')
|
||||
.hide()
|
||||
.html('')
|
||||
}
|
||||
}
|
||||
|
||||
setContent () {
|
||||
var self = this
|
||||
if (typeof this.options.url !== 'undefined') {
|
||||
this.method = 'request'
|
||||
$.get(this.options.url, function (response) {
|
||||
self.modalObj.find('.modal-body').html(response)
|
||||
self.options.onUrlLoaded(self)
|
||||
})
|
||||
} else if (this.options.content instanceof jQuery) {
|
||||
self.modalObj.find('.modal-body').append(this.options.content)
|
||||
} else if (typeof this.options.content !== 'undefined') {
|
||||
self.modalObj.find('.modal-body').html(this.options.content)
|
||||
}
|
||||
};
|
||||
|
||||
setButtons () {
|
||||
if (this.options.hideFooter) {
|
||||
this.modalObj.find('.modal-footer').hide()
|
||||
} else {
|
||||
if (this.options.actionBtnCb) {
|
||||
this.modalObj.find('.btn-action').text(this.options.actionBtnLabel)
|
||||
this.modalObj.find('.btn-action').on('click', this.options.actionBtnCb)
|
||||
} else {
|
||||
this.modalObj.find('.btn-action').hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open () {
|
||||
if (this.isOpen) {
|
||||
return
|
||||
}
|
||||
this.toggle()
|
||||
this.isOpen = true
|
||||
}
|
||||
|
||||
toggle () {
|
||||
this.modal[this.isOpen ? 'hide' : 'show']()
|
||||
this.isOpen = !this.isOpen
|
||||
}
|
||||
|
||||
close () {
|
||||
if (!this.isOpen) {
|
||||
return
|
||||
}
|
||||
|
||||
this.modal.hide()
|
||||
this.options.onClose()
|
||||
this.isOpen = false
|
||||
}
|
||||
}
|
||||
|
||||
export default Modal
|
@@ -1,44 +0,0 @@
|
||||
import $ from 'jquery'
|
||||
|
||||
let Navbar = {
|
||||
/**
|
||||
* Navbar component
|
||||
*
|
||||
* Adds a menu toggler for mobile and does some styling
|
||||
*/
|
||||
init: function (config) {
|
||||
this.menuAlwaysCollapsed = config.menuAlwaysCollapsed
|
||||
this.fixNodes()
|
||||
},
|
||||
fixNodes: function () {
|
||||
if (!this.menuAlwaysCollapsed) {
|
||||
$('#header').addClass('expand')
|
||||
} else {
|
||||
$('#header').addClass('menu-always-collapsed')
|
||||
}
|
||||
// insert burger
|
||||
$('#branding').before(
|
||||
$('<button/>', {
|
||||
'class': 'navbar-toggler navbar-toggler-right',
|
||||
'data-bs-toggle': 'collapse'
|
||||
}).html('<i class="fa fa-bars"></i>')
|
||||
.click(() => $(document.body).addClass('menu-open')))
|
||||
// remove only text
|
||||
$('#user-tools')
|
||||
.contents().filter(function () {
|
||||
return (this.nodeType === 3)
|
||||
}).remove()
|
||||
// dropdown
|
||||
let dropdown = $('<div/>', { 'class': 'dropdown' }).appendTo($('#user-tools'))
|
||||
let dropdownMenu = $('<div/>', { 'class': 'dropdown-menu dropdown-menu-right' }).appendTo(dropdown)
|
||||
$('#user-tools strong')
|
||||
.addClass('dropdown-toggle btn btn-default')
|
||||
.attr('data-bs-toggle', 'dropdown')
|
||||
.prependTo(dropdown)
|
||||
// @TODO find a way to mv view site from dropdown
|
||||
// password change view doesn't have it so breaks things
|
||||
$('#user-tools > a').addClass('dropdown-item').appendTo(dropdownMenu)
|
||||
}
|
||||
}
|
||||
|
||||
export default Navbar
|
@@ -1,9 +0,0 @@
|
||||
import $ from 'jquery'
|
||||
|
||||
let PasswordChange = {
|
||||
init: function () {
|
||||
$('body').addClass('passwordchange')
|
||||
}
|
||||
}
|
||||
|
||||
export default PasswordChange
|
@@ -1,163 +0,0 @@
|
||||
import $ from 'jquery'
|
||||
import bootstrap from 'bootstrap/dist/js/bootstrap.bundle'
|
||||
|
||||
let Tabs = {
|
||||
/**
|
||||
* Tabs component
|
||||
*/
|
||||
init: function (Dispatcher) {
|
||||
this.Dispatcher = Dispatcher
|
||||
if (this.shouldRun()) {
|
||||
console.info('Baton:', 'generating tabs')
|
||||
this.main.attr('data-baton-tab', 'main-tab')
|
||||
this.createNav()
|
||||
this.createPanes()
|
||||
this.checkHash()
|
||||
this.showErrors()
|
||||
this.Dispatcher.emit('onTabsReady')
|
||||
}
|
||||
},
|
||||
shouldRun: function () {
|
||||
this.main = $('#content-main form .baton-tabs-init')
|
||||
return this.main.length === 1
|
||||
},
|
||||
createNav: function () {
|
||||
this.mainOrder = 0
|
||||
this.tabsEl = []
|
||||
this.domTabsEl = []
|
||||
let classes = this.main.attr('class')
|
||||
classes.split(' ').forEach((cl) => {
|
||||
if (/baton-tab-/.test(cl)) {
|
||||
this.tabsEl.push(cl.substring(10))
|
||||
}
|
||||
if (/order-/.test(cl)) {
|
||||
this.mainOrder = parseInt(cl.replace('order-', ''))
|
||||
}
|
||||
})
|
||||
|
||||
let currentOrder = this.mainOrder ? 0 : this.mainOrder + 1
|
||||
|
||||
this.nav = $('<ul />', { 'class': 'nav nav-tabs' })
|
||||
$('<li />', { 'class': 'nav-item' })
|
||||
.css('order', this.mainOrder)
|
||||
.append($('<a />', {
|
||||
'class': 'nav-link' + (this.mainOrder === 0 ? ' active' : ''),
|
||||
'data-bs-toggle': 'tab',
|
||||
'data-bs-target': '#main-tab'
|
||||
// href: '#main-tab'
|
||||
}).text(this.main.children('h2').hide().text()))
|
||||
.appendTo(this.nav)
|
||||
|
||||
this.tabsEl.forEach((el) => {
|
||||
let domEl
|
||||
if (/^group-/.test(el)) {
|
||||
domEl = $('<div />').attr('data-baton-tab', el)
|
||||
let items = el.substr(6).split('--')
|
||||
items.forEach((item) => {
|
||||
let e
|
||||
if (/^inline-/.test(item)) {
|
||||
e = this.createInlineEl(item)
|
||||
} else {
|
||||
e = this.createFieldsetEl(item)
|
||||
}
|
||||
domEl.append(e)
|
||||
})
|
||||
} else if (/^inline-/.test(el)) {
|
||||
domEl = this.createInlineEl(el, true)
|
||||
} else {
|
||||
domEl = this.createFieldsetEl(el, true)
|
||||
}
|
||||
this.domTabsEl.push(domEl)
|
||||
$('<li />', { 'class': 'nav-item ' })
|
||||
.css('order', currentOrder)
|
||||
.append($('<a />', {
|
||||
'class': 'nav-link ' + (currentOrder === 0 ? ' active' : ''),
|
||||
'data-bs-toggle': 'tab',
|
||||
'data-bs-target': '#' + el
|
||||
// href: '#' + el
|
||||
}).text(domEl.find('h2:first-child').first().hide().text()))
|
||||
.appendTo(this.nav)
|
||||
currentOrder += 1
|
||||
if (currentOrder === this.mainOrder) {
|
||||
currentOrder += 1
|
||||
}
|
||||
})
|
||||
|
||||
this.main.before(this.nav)
|
||||
|
||||
$('a[data-bs-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
// add hash to stay in same tab when save and continue
|
||||
const hash = $(e.target).attr('data-bs-target')
|
||||
window.location.replace(hash) // adding with replace won't add an history entry
|
||||
let tooltipTriggerList = [].slice.call($('[title]:not(iframe)'))
|
||||
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl)
|
||||
})
|
||||
})
|
||||
},
|
||||
createInlineEl: function (el, setDataTab = false) {
|
||||
let domEl
|
||||
if ($('#' + el.substring(7) + '_set-group').length) { // no related_name
|
||||
domEl = $('#' + el.substring(7) + '_set-group')
|
||||
} else {
|
||||
domEl = $('#' + el.substring(7) + '-group')
|
||||
}
|
||||
if (setDataTab) {
|
||||
domEl.attr('data-baton-tab', el)
|
||||
}
|
||||
return domEl
|
||||
},
|
||||
createFieldsetEl: function (el, setDataTab = false) {
|
||||
let domEl = $('.tab-' + el)
|
||||
if (setDataTab) {
|
||||
domEl.attr('data-baton-tab', el)
|
||||
}
|
||||
return domEl
|
||||
},
|
||||
createPanes: function () {
|
||||
let self = this
|
||||
this.tabContent = $('<div />', { 'class': 'tab-content' })
|
||||
this.tabMain = $('<div />', {
|
||||
'class': 'tab-pane fade' + (this.mainOrder === 0 ? ' active show' : ''),
|
||||
'id': 'main-tab'
|
||||
}).appendTo(this.tabContent)
|
||||
this.main.parent().children(':not(.nav-tabs):not(.submit-row):not(.errornote):not(.tab-fs-none)')
|
||||
.each((index, el) => {
|
||||
$(el).appendTo(self.tabMain)
|
||||
})
|
||||
this.nav.after(this.tabContent)
|
||||
|
||||
let currentOrder = this.mainOrder ? 0 : this.mainOrder + 1
|
||||
|
||||
this.domTabsEl.forEach((el, index) => {
|
||||
let tabPane = $('<div />', {
|
||||
'class': 'tab-pane' + (currentOrder === 0 ? ' active show' : ''),
|
||||
'id': self.tabsEl[index]
|
||||
}).appendTo(this.tabContent)
|
||||
el.appendTo(tabPane)
|
||||
currentOrder += 1
|
||||
if (currentOrder === this.mainOrder) {
|
||||
currentOrder += 1
|
||||
}
|
||||
})
|
||||
},
|
||||
showErrors: function () {
|
||||
let els = [this.main, ...this.domTabsEl]
|
||||
for (let i = 0, len = els.length; i < len; i++) {
|
||||
let el = els[i]
|
||||
if (el.find('.form-row.errors, .errorlist').length) {
|
||||
const tab = new bootstrap.Tab(this.nav.find('a[data-bs-target="#' + el.attr('data-baton-tab') + '"]')[0])
|
||||
tab.show()
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
checkHash: function () {
|
||||
if (location.hash && this.nav.find('a[data-bs-target="' + location.hash + '"]').length) {
|
||||
const tab = new bootstrap.Tab(this.nav.find('a[data-bs-target="' + location.hash + '"]')[0])
|
||||
tab.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Tabs
|
@@ -1,600 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
// minimal template polyfill
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var needsTemplate = (typeof HTMLTemplateElement === 'undefined');
|
||||
var brokenDocFragment = !(document.createDocumentFragment().cloneNode() instanceof DocumentFragment);
|
||||
var needsDocFrag = false;
|
||||
|
||||
// NOTE: Replace DocumentFragment to work around IE11 bug that
|
||||
// causes children of a document fragment modified while
|
||||
// there is a mutation observer to not have a parentNode, or
|
||||
// have a broken parentNode (!?!)
|
||||
if (/Trident/.test(navigator.userAgent)) {
|
||||
(function() {
|
||||
|
||||
needsDocFrag = true;
|
||||
|
||||
var origCloneNode = Node.prototype.cloneNode;
|
||||
Node.prototype.cloneNode = function cloneNode(deep) {
|
||||
var newDom = origCloneNode.call(this, deep);
|
||||
if (this instanceof DocumentFragment) {
|
||||
newDom.__proto__ = DocumentFragment.prototype;
|
||||
}
|
||||
return newDom;
|
||||
};
|
||||
|
||||
// IE's DocumentFragment querySelector code doesn't work when
|
||||
// called on an element instance
|
||||
DocumentFragment.prototype.querySelectorAll = HTMLElement.prototype.querySelectorAll;
|
||||
DocumentFragment.prototype.querySelector = HTMLElement.prototype.querySelector;
|
||||
|
||||
Object.defineProperties(DocumentFragment.prototype, {
|
||||
'nodeType': {
|
||||
get: function () {
|
||||
return Node.DOCUMENT_FRAGMENT_NODE;
|
||||
},
|
||||
configurable: true
|
||||
},
|
||||
|
||||
'localName': {
|
||||
get: function () {
|
||||
return undefined;
|
||||
},
|
||||
configurable: true
|
||||
},
|
||||
|
||||
'nodeName': {
|
||||
get: function () {
|
||||
return '#document-fragment';
|
||||
},
|
||||
configurable: true
|
||||
}
|
||||
});
|
||||
|
||||
var origInsertBefore = Node.prototype.insertBefore;
|
||||
function insertBefore(newNode, refNode) {
|
||||
if (newNode instanceof DocumentFragment) {
|
||||
var child;
|
||||
while ((child = newNode.firstChild)) {
|
||||
origInsertBefore.call(this, child, refNode);
|
||||
}
|
||||
} else {
|
||||
origInsertBefore.call(this, newNode, refNode);
|
||||
}
|
||||
return newNode;
|
||||
}
|
||||
Node.prototype.insertBefore = insertBefore;
|
||||
|
||||
var origAppendChild = Node.prototype.appendChild;
|
||||
Node.prototype.appendChild = function appendChild(child) {
|
||||
if (child instanceof DocumentFragment) {
|
||||
insertBefore.call(this, child, null);
|
||||
} else {
|
||||
origAppendChild.call(this, child);
|
||||
}
|
||||
return child;
|
||||
};
|
||||
|
||||
var origRemoveChild = Node.prototype.removeChild;
|
||||
var origReplaceChild = Node.prototype.replaceChild;
|
||||
Node.prototype.replaceChild = function replaceChild(newChild, oldChild) {
|
||||
if (newChild instanceof DocumentFragment) {
|
||||
insertBefore.call(this, newChild, oldChild);
|
||||
origRemoveChild.call(this, oldChild);
|
||||
} else {
|
||||
origReplaceChild.call(this, newChild, oldChild);
|
||||
}
|
||||
return oldChild;
|
||||
};
|
||||
|
||||
Document.prototype.createDocumentFragment = function createDocumentFragment() {
|
||||
var frag = this.createElement('df');
|
||||
frag.__proto__ = DocumentFragment.prototype;
|
||||
return frag;
|
||||
};
|
||||
|
||||
var origImportNode = Document.prototype.importNode;
|
||||
Document.prototype.importNode = function importNode(impNode, deep) {
|
||||
deep = deep || false;
|
||||
var newNode = origImportNode.call(this, impNode, deep);
|
||||
if (impNode instanceof DocumentFragment) {
|
||||
newNode.__proto__ = DocumentFragment.prototype;
|
||||
}
|
||||
return newNode;
|
||||
};
|
||||
})();
|
||||
}
|
||||
|
||||
// NOTE: we rely on this cloneNode not causing element upgrade.
|
||||
// This means this polyfill must load before the CE polyfill and
|
||||
// this would need to be re-worked if a browser supports native CE
|
||||
// but not <template>.
|
||||
var capturedCloneNode = Node.prototype.cloneNode;
|
||||
var capturedCreateElement = Document.prototype.createElement;
|
||||
var capturedImportNode = Document.prototype.importNode;
|
||||
var capturedRemoveChild = Node.prototype.removeChild;
|
||||
var capturedAppendChild = Node.prototype.appendChild;
|
||||
var capturedReplaceChild = Node.prototype.replaceChild;
|
||||
var capturedParseFromString = DOMParser.prototype.parseFromString;
|
||||
var capturedHTMLElementInnerHTML = Object.getOwnPropertyDescriptor(window.HTMLElement.prototype, 'innerHTML') || {
|
||||
/**
|
||||
* @this {!HTMLElement}
|
||||
* @return {string}
|
||||
*/
|
||||
get: function() {
|
||||
return this.innerHTML;
|
||||
},
|
||||
/**
|
||||
* @this {!HTMLElement}
|
||||
* @param {string}
|
||||
*/
|
||||
set: function(text) {
|
||||
this.innerHTML = text;
|
||||
}
|
||||
};
|
||||
var capturedChildNodes = Object.getOwnPropertyDescriptor(window.Node.prototype, 'childNodes') || {
|
||||
/**
|
||||
* @this {!Node}
|
||||
* @return {!NodeList}
|
||||
*/
|
||||
get: function() {
|
||||
return this.childNodes;
|
||||
}
|
||||
};
|
||||
|
||||
var elementQuerySelectorAll = Element.prototype.querySelectorAll;
|
||||
var docQuerySelectorAll = Document.prototype.querySelectorAll;
|
||||
var fragQuerySelectorAll = DocumentFragment.prototype.querySelectorAll;
|
||||
|
||||
var scriptSelector = 'script:not([type]),script[type="application/javascript"],script[type="text/javascript"]';
|
||||
|
||||
function QSA(node, selector) {
|
||||
// IE 11 throws a SyntaxError with `scriptSelector` if the node has no children due to the `:not([type])` syntax
|
||||
if (!node.childNodes.length) {
|
||||
return [];
|
||||
}
|
||||
switch (node.nodeType) {
|
||||
case Node.DOCUMENT_NODE:
|
||||
return docQuerySelectorAll.call(node, selector);
|
||||
case Node.DOCUMENT_FRAGMENT_NODE:
|
||||
return fragQuerySelectorAll.call(node, selector);
|
||||
default:
|
||||
return elementQuerySelectorAll.call(node, selector);
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if nested templates cannot be cloned (they cannot be on
|
||||
// some impl's like Safari 8 and Edge)
|
||||
// OR if cloning a document fragment does not result in a document fragment
|
||||
var needsCloning = (function() {
|
||||
if (!needsTemplate) {
|
||||
var t = document.createElement('template');
|
||||
var t2 = document.createElement('template');
|
||||
t2.content.appendChild(document.createElement('div'));
|
||||
t.content.appendChild(t2);
|
||||
var clone = t.cloneNode(true);
|
||||
return (clone.content.childNodes.length === 0 || clone.content.firstChild.content.childNodes.length === 0
|
||||
|| brokenDocFragment);
|
||||
}
|
||||
})();
|
||||
|
||||
var TEMPLATE_TAG = 'template';
|
||||
var PolyfilledHTMLTemplateElement = function() {};
|
||||
|
||||
if (needsTemplate) {
|
||||
|
||||
var contentDoc = document.implementation.createHTMLDocument('template');
|
||||
var canDecorate = true;
|
||||
|
||||
var templateStyle = document.createElement('style');
|
||||
templateStyle.textContent = TEMPLATE_TAG + '{display:none;}';
|
||||
|
||||
var head = document.head;
|
||||
head.insertBefore(templateStyle, head.firstElementChild);
|
||||
|
||||
/**
|
||||
Provides a minimal shim for the <template> element.
|
||||
*/
|
||||
PolyfilledHTMLTemplateElement.prototype = Object.create(HTMLElement.prototype);
|
||||
|
||||
|
||||
// if elements do not have `innerHTML` on instances, then
|
||||
// templates can be patched by swizzling their prototypes.
|
||||
var canProtoPatch =
|
||||
!(document.createElement('div').hasOwnProperty('innerHTML'));
|
||||
|
||||
/**
|
||||
The `decorate` method moves element children to the template's `content`.
|
||||
NOTE: there is no support for dynamically adding elements to templates.
|
||||
*/
|
||||
PolyfilledHTMLTemplateElement.decorate = function(template) {
|
||||
// if the template is decorated or not in HTML namespace, return fast
|
||||
if (template.content ||
|
||||
template.namespaceURI !== document.documentElement.namespaceURI) {
|
||||
return;
|
||||
}
|
||||
template.content = contentDoc.createDocumentFragment();
|
||||
var child;
|
||||
while ((child = template.firstChild)) {
|
||||
capturedAppendChild.call(template.content, child);
|
||||
}
|
||||
// NOTE: prefer prototype patching for performance and
|
||||
// because on some browsers (IE11), re-defining `innerHTML`
|
||||
// can result in intermittent errors.
|
||||
if (canProtoPatch) {
|
||||
template.__proto__ = PolyfilledHTMLTemplateElement.prototype;
|
||||
} else {
|
||||
template.cloneNode = function(deep) {
|
||||
return PolyfilledHTMLTemplateElement._cloneNode(this, deep);
|
||||
};
|
||||
// add innerHTML to template, if possible
|
||||
// Note: this throws on Safari 7
|
||||
if (canDecorate) {
|
||||
try {
|
||||
defineInnerHTML(template);
|
||||
defineOuterHTML(template);
|
||||
} catch (err) {
|
||||
canDecorate = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// bootstrap recursively
|
||||
PolyfilledHTMLTemplateElement.bootstrap(template.content);
|
||||
};
|
||||
|
||||
// Taken from https://github.com/jquery/jquery/blob/73d7e6259c63ac45f42c6593da8c2796c6ce9281/src/manipulation/wrapMap.js
|
||||
var topLevelWrappingMap = {
|
||||
'option': ['select'],
|
||||
'thead': ['table'],
|
||||
'col': ['colgroup', 'table'],
|
||||
'tr': ['tbody', 'table'],
|
||||
'th': ['tr', 'tbody', 'table'],
|
||||
'td': ['tr', 'tbody', 'table']
|
||||
};
|
||||
|
||||
var getTagName = function(text) {
|
||||
// Taken from https://github.com/jquery/jquery/blob/73d7e6259c63ac45f42c6593da8c2796c6ce9281/src/manipulation/var/rtagName.js
|
||||
return ( /<([a-z][^/\0>\x20\t\r\n\f]+)/i.exec(text) || ['', ''])[1].toLowerCase();
|
||||
};
|
||||
|
||||
var defineInnerHTML = function defineInnerHTML(obj) {
|
||||
Object.defineProperty(obj, 'innerHTML', {
|
||||
get: function() {
|
||||
return getInnerHTML(this);
|
||||
},
|
||||
set: function(text) {
|
||||
// For IE11, wrap the text in the correct (table) context
|
||||
var wrap = topLevelWrappingMap[getTagName(text)];
|
||||
if (wrap) {
|
||||
for (var i = 0; i < wrap.length; i++) {
|
||||
text = '<' + wrap[i] + '>' + text + '</' + wrap[i] + '>';
|
||||
}
|
||||
}
|
||||
contentDoc.body.innerHTML = text;
|
||||
PolyfilledHTMLTemplateElement.bootstrap(contentDoc);
|
||||
while (this.content.firstChild) {
|
||||
capturedRemoveChild.call(this.content, this.content.firstChild);
|
||||
}
|
||||
var body = contentDoc.body;
|
||||
// If we had wrapped, get back to the original node
|
||||
if (wrap) {
|
||||
for (var j = 0; j < wrap.length; j++) {
|
||||
body = body.lastChild;
|
||||
}
|
||||
}
|
||||
while (body.firstChild) {
|
||||
capturedAppendChild.call(this.content, body.firstChild);
|
||||
}
|
||||
},
|
||||
configurable: true
|
||||
});
|
||||
};
|
||||
|
||||
var defineOuterHTML = function defineOuterHTML(obj) {
|
||||
Object.defineProperty(obj, 'outerHTML', {
|
||||
get: function() {
|
||||
return '<' + TEMPLATE_TAG + '>' + this.innerHTML + '</' + TEMPLATE_TAG + '>';
|
||||
},
|
||||
set: function(innerHTML) {
|
||||
if (this.parentNode) {
|
||||
contentDoc.body.innerHTML = innerHTML;
|
||||
var docFrag = this.ownerDocument.createDocumentFragment();
|
||||
while (contentDoc.body.firstChild) {
|
||||
capturedAppendChild.call(docFrag, contentDoc.body.firstChild);
|
||||
}
|
||||
capturedReplaceChild.call(this.parentNode, docFrag, this);
|
||||
} else {
|
||||
throw new Error("Failed to set the 'outerHTML' property on 'Element': This element has no parent node.");
|
||||
}
|
||||
},
|
||||
configurable: true
|
||||
});
|
||||
};
|
||||
|
||||
defineInnerHTML(PolyfilledHTMLTemplateElement.prototype);
|
||||
defineOuterHTML(PolyfilledHTMLTemplateElement.prototype);
|
||||
|
||||
/**
|
||||
The `bootstrap` method is called automatically and "fixes" all
|
||||
<template> elements in the document referenced by the `doc` argument.
|
||||
*/
|
||||
PolyfilledHTMLTemplateElement.bootstrap = function bootstrap(doc) {
|
||||
var templates = QSA(doc, TEMPLATE_TAG);
|
||||
for (var i=0, l=templates.length, t; (i<l) && (t=templates[i]); i++) {
|
||||
PolyfilledHTMLTemplateElement.decorate(t);
|
||||
}
|
||||
};
|
||||
|
||||
// auto-bootstrapping for main document
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
PolyfilledHTMLTemplateElement.bootstrap(document);
|
||||
});
|
||||
|
||||
// Patch document.createElement to ensure newly created templates have content
|
||||
Document.prototype.createElement = function createElement() {
|
||||
var el = capturedCreateElement.apply(this, arguments);
|
||||
if (el.localName === 'template') {
|
||||
PolyfilledHTMLTemplateElement.decorate(el);
|
||||
}
|
||||
return el;
|
||||
};
|
||||
|
||||
DOMParser.prototype.parseFromString = function() {
|
||||
var el = capturedParseFromString.apply(this, arguments);
|
||||
PolyfilledHTMLTemplateElement.bootstrap(el);
|
||||
return el;
|
||||
};
|
||||
|
||||
Object.defineProperty(HTMLElement.prototype, 'innerHTML', {
|
||||
get: function() {
|
||||
return getInnerHTML(this);
|
||||
},
|
||||
set: function(text) {
|
||||
capturedHTMLElementInnerHTML.set.call(this, text);
|
||||
PolyfilledHTMLTemplateElement.bootstrap(this);
|
||||
},
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
});
|
||||
|
||||
// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#escapingString
|
||||
var escapeAttrRegExp = /[&\u00A0"]/g;
|
||||
var escapeDataRegExp = /[&\u00A0<>]/g;
|
||||
|
||||
var escapeReplace = function(c) {
|
||||
switch (c) {
|
||||
case '&':
|
||||
return '&';
|
||||
case '<':
|
||||
return '<';
|
||||
case '>':
|
||||
return '>';
|
||||
case '"':
|
||||
return '"';
|
||||
case '\u00A0':
|
||||
return ' ';
|
||||
}
|
||||
};
|
||||
|
||||
var escapeAttr = function(s) {
|
||||
return s.replace(escapeAttrRegExp, escapeReplace);
|
||||
};
|
||||
|
||||
var escapeData = function(s) {
|
||||
return s.replace(escapeDataRegExp, escapeReplace);
|
||||
};
|
||||
|
||||
var makeSet = function(arr) {
|
||||
var set = {};
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
set[arr[i]] = true;
|
||||
}
|
||||
return set;
|
||||
};
|
||||
|
||||
// http://www.whatwg.org/specs/web-apps/current-work/#void-elements
|
||||
var voidElements = makeSet([
|
||||
'area',
|
||||
'base',
|
||||
'br',
|
||||
'col',
|
||||
'command',
|
||||
'embed',
|
||||
'hr',
|
||||
'img',
|
||||
'input',
|
||||
'keygen',
|
||||
'link',
|
||||
'meta',
|
||||
'param',
|
||||
'source',
|
||||
'track',
|
||||
'wbr'
|
||||
]);
|
||||
|
||||
var plaintextParents = makeSet([
|
||||
'style',
|
||||
'script',
|
||||
'xmp',
|
||||
'iframe',
|
||||
'noembed',
|
||||
'noframes',
|
||||
'plaintext',
|
||||
'noscript'
|
||||
]);
|
||||
|
||||
/**
|
||||
* @param {Node} node
|
||||
* @param {Node} parentNode
|
||||
* @param {Function=} callback
|
||||
*/
|
||||
var getOuterHTML = function(node, parentNode, callback) {
|
||||
switch (node.nodeType) {
|
||||
case Node.ELEMENT_NODE: {
|
||||
var tagName = node.localName;
|
||||
var s = '<' + tagName;
|
||||
var attrs = node.attributes;
|
||||
for (var i = 0, attr; (attr = attrs[i]); i++) {
|
||||
s += ' ' + attr.name + '="' + escapeAttr(attr.value) + '"';
|
||||
}
|
||||
s += '>';
|
||||
if (voidElements[tagName]) {
|
||||
return s;
|
||||
}
|
||||
return s + getInnerHTML(node, callback) + '</' + tagName + '>';
|
||||
}
|
||||
case Node.TEXT_NODE: {
|
||||
var data = /** @type {Text} */ (node).data;
|
||||
if (parentNode && plaintextParents[parentNode.localName]) {
|
||||
return data;
|
||||
}
|
||||
return escapeData(data);
|
||||
}
|
||||
case Node.COMMENT_NODE: {
|
||||
return '<!--' + /** @type {Comment} */ (node).data + '-->';
|
||||
}
|
||||
default: {
|
||||
window.console.error(node);
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Node} node
|
||||
* @param {Function=} callback
|
||||
*/
|
||||
var getInnerHTML = function(node, callback) {
|
||||
if (node.localName === 'template') {
|
||||
node = /** @type {HTMLTemplateElement} */ (node).content;
|
||||
}
|
||||
var s = '';
|
||||
var c$ = callback ? callback(node) : capturedChildNodes.get.call(node);
|
||||
for (var i=0, l=c$.length, child; (i<l) && (child=c$[i]); i++) {
|
||||
s += getOuterHTML(child, node, callback);
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// make cloning/importing work!
|
||||
if (needsTemplate || needsCloning) {
|
||||
|
||||
PolyfilledHTMLTemplateElement._cloneNode = function _cloneNode(template, deep) {
|
||||
var clone = capturedCloneNode.call(template, false);
|
||||
// NOTE: decorate doesn't auto-fix children because they are already
|
||||
// decorated so they need special clone fixup.
|
||||
if (this.decorate) {
|
||||
this.decorate(clone);
|
||||
}
|
||||
if (deep) {
|
||||
// NOTE: use native clone node to make sure CE's wrapped
|
||||
// cloneNode does not cause elements to upgrade.
|
||||
capturedAppendChild.call(clone.content, capturedCloneNode.call(template.content, true));
|
||||
// now ensure nested templates are cloned correctly.
|
||||
fixClonedDom(clone.content, template.content);
|
||||
}
|
||||
return clone;
|
||||
};
|
||||
|
||||
// Given a source and cloned subtree, find <template>'s in the cloned
|
||||
// subtree and replace them with cloned <template>'s from source.
|
||||
// We must do this because only the source templates have proper .content.
|
||||
var fixClonedDom = function fixClonedDom(clone, source) {
|
||||
// do nothing if cloned node is not an element
|
||||
if (!source.querySelectorAll) return;
|
||||
// these two lists should be coincident
|
||||
var s$ = QSA(source, TEMPLATE_TAG);
|
||||
if (s$.length === 0) {
|
||||
return;
|
||||
}
|
||||
var t$ = QSA(clone, TEMPLATE_TAG);
|
||||
for (var i=0, l=t$.length, t, s; i<l; i++) {
|
||||
s = s$[i];
|
||||
t = t$[i];
|
||||
if (PolyfilledHTMLTemplateElement && PolyfilledHTMLTemplateElement.decorate) {
|
||||
PolyfilledHTMLTemplateElement.decorate(s);
|
||||
}
|
||||
capturedReplaceChild.call(t.parentNode, cloneNode.call(s, true), t);
|
||||
}
|
||||
};
|
||||
|
||||
// make sure scripts inside of a cloned template are executable
|
||||
var fixClonedScripts = function fixClonedScripts(fragment) {
|
||||
var scripts = QSA(fragment, scriptSelector);
|
||||
for (var ns, s, i = 0; i < scripts.length; i++) {
|
||||
s = scripts[i];
|
||||
ns = capturedCreateElement.call(document, 'script');
|
||||
ns.textContent = s.textContent;
|
||||
var attrs = s.attributes;
|
||||
for (var ai = 0, a; ai < attrs.length; ai++) {
|
||||
a = attrs[ai];
|
||||
ns.setAttribute(a.name, a.value);
|
||||
}
|
||||
capturedReplaceChild.call(s.parentNode, ns, s);
|
||||
}
|
||||
};
|
||||
|
||||
// override all cloning to fix the cloned subtree to contain properly
|
||||
// cloned templates.
|
||||
var cloneNode = Node.prototype.cloneNode = function cloneNode(deep) {
|
||||
var dom;
|
||||
// workaround for Edge bug cloning documentFragments
|
||||
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8619646/
|
||||
if (!needsDocFrag && brokenDocFragment && this instanceof DocumentFragment) {
|
||||
if (!deep) {
|
||||
return this.ownerDocument.createDocumentFragment();
|
||||
} else {
|
||||
dom = importNode.call(this.ownerDocument, this, true);
|
||||
}
|
||||
} else if (this.nodeType === Node.ELEMENT_NODE &&
|
||||
this.localName === TEMPLATE_TAG &&
|
||||
this.namespaceURI == document.documentElement.namespaceURI) {
|
||||
dom = PolyfilledHTMLTemplateElement._cloneNode(this, deep);
|
||||
} else {
|
||||
dom = capturedCloneNode.call(this, deep);
|
||||
}
|
||||
// template.content is cloned iff `deep`.
|
||||
if (deep) {
|
||||
fixClonedDom(dom, this);
|
||||
}
|
||||
return dom;
|
||||
};
|
||||
|
||||
// NOTE: we are cloning instead of importing <template>'s.
|
||||
// However, the ownerDocument of the cloned template will be correct!
|
||||
// This is because the native import node creates the right document owned
|
||||
// subtree and `fixClonedDom` inserts cloned templates into this subtree,
|
||||
// thus updating the owner doc.
|
||||
var importNode = Document.prototype.importNode = function importNode(element, deep) {
|
||||
deep = deep || false;
|
||||
if (element.localName === TEMPLATE_TAG) {
|
||||
return PolyfilledHTMLTemplateElement._cloneNode(element, deep);
|
||||
} else {
|
||||
var dom = capturedImportNode.call(this, element, deep);
|
||||
if (deep) {
|
||||
fixClonedDom(dom, element);
|
||||
fixClonedScripts(dom);
|
||||
}
|
||||
return dom;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (needsTemplate) {
|
||||
window.HTMLTemplateElement = PolyfilledHTMLTemplateElement;
|
||||
}
|
||||
|
||||
})();
|