remove old webui
@ -1,621 +0,0 @@
|
|||||||
GNU GENERAL PUBLIC LICENSE
|
|
||||||
Version 3, 29 June 2007
|
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
|
||||||
of this license document, but changing it is not allowed.
|
|
||||||
|
|
||||||
Preamble
|
|
||||||
|
|
||||||
The GNU General Public License is a free, copyleft license for
|
|
||||||
software and other kinds of works.
|
|
||||||
|
|
||||||
The licenses for most software and other practical works are designed
|
|
||||||
to take away your freedom to share and change the works. By contrast,
|
|
||||||
the GNU General Public License is intended to guarantee your freedom to
|
|
||||||
share and change all versions of a program--to make sure it remains free
|
|
||||||
software for all its users. We, the Free Software Foundation, use the
|
|
||||||
GNU General Public License for most of our software; it applies also to
|
|
||||||
any other work released this way by its authors. You can apply it to
|
|
||||||
your programs, too.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
|
||||||
have the freedom to distribute copies of free software (and charge for
|
|
||||||
them if you wish), that you receive source code or can get it if you
|
|
||||||
want it, that you can change the software or use pieces of it in new
|
|
||||||
free programs, and that you know you can do these things.
|
|
||||||
|
|
||||||
To protect your rights, we need to prevent others from denying you
|
|
||||||
these rights or asking you to surrender the rights. Therefore, you have
|
|
||||||
certain responsibilities if you distribute copies of the software, or if
|
|
||||||
you modify it: responsibilities to respect the freedom of others.
|
|
||||||
|
|
||||||
For example, if you distribute copies of such a program, whether
|
|
||||||
gratis or for a fee, you must pass on to the recipients the same
|
|
||||||
freedoms that you received. You must make sure that they, too, receive
|
|
||||||
or can get the source code. And you must show them these terms so they
|
|
||||||
know their rights.
|
|
||||||
|
|
||||||
Developers that use the GNU GPL protect your rights with two steps:
|
|
||||||
(1) assert copyright on the software, and (2) offer you this License
|
|
||||||
giving you legal permission to copy, distribute and/or modify it.
|
|
||||||
|
|
||||||
For the developers' and authors' protection, the GPL clearly explains
|
|
||||||
that there is no warranty for this free software. For both users' and
|
|
||||||
authors' sake, the GPL requires that modified versions be marked as
|
|
||||||
changed, so that their problems will not be attributed erroneously to
|
|
||||||
authors of previous versions.
|
|
||||||
|
|
||||||
Some devices are designed to deny users access to install or run
|
|
||||||
modified versions of the software inside them, although the manufacturer
|
|
||||||
can do so. This is fundamentally incompatible with the aim of
|
|
||||||
protecting users' freedom to change the software. The systematic
|
|
||||||
pattern of such abuse occurs in the area of products for individuals to
|
|
||||||
use, which is precisely where it is most unacceptable. Therefore, we
|
|
||||||
have designed this version of the GPL to prohibit the practice for those
|
|
||||||
products. If such problems arise substantially in other domains, we
|
|
||||||
stand ready to extend this provision to those domains in future versions
|
|
||||||
of the GPL, as needed to protect the freedom of users.
|
|
||||||
|
|
||||||
Finally, every program is threatened constantly by software patents.
|
|
||||||
States should not allow patents to restrict development and use of
|
|
||||||
software on general-purpose computers, but in those that do, we wish to
|
|
||||||
avoid the special danger that patents applied to a free program could
|
|
||||||
make it effectively proprietary. To prevent this, the GPL assures that
|
|
||||||
patents cannot be used to render the program non-free.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
|
||||||
modification follow.
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
0. Definitions.
|
|
||||||
|
|
||||||
"This License" refers to version 3 of the GNU General Public License.
|
|
||||||
|
|
||||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
|
||||||
works, such as semiconductor masks.
|
|
||||||
|
|
||||||
"The Program" refers to any copyrightable work licensed under this
|
|
||||||
License. Each licensee is addressed as "you". "Licensees" and
|
|
||||||
"recipients" may be individuals or organizations.
|
|
||||||
|
|
||||||
To "modify" a work means to copy from or adapt all or part of the work
|
|
||||||
in a fashion requiring copyright permission, other than the making of an
|
|
||||||
exact copy. The resulting work is called a "modified version" of the
|
|
||||||
earlier work or a work "based on" the earlier work.
|
|
||||||
|
|
||||||
A "covered work" means either the unmodified Program or a work based
|
|
||||||
on the Program.
|
|
||||||
|
|
||||||
To "propagate" a work means to do anything with it that, without
|
|
||||||
permission, would make you directly or secondarily liable for
|
|
||||||
infringement under applicable copyright law, except executing it on a
|
|
||||||
computer or modifying a private copy. Propagation includes copying,
|
|
||||||
distribution (with or without modification), making available to the
|
|
||||||
public, and in some countries other activities as well.
|
|
||||||
|
|
||||||
To "convey" a work means any kind of propagation that enables other
|
|
||||||
parties to make or receive copies. Mere interaction with a user through
|
|
||||||
a computer network, with no transfer of a copy, is not conveying.
|
|
||||||
|
|
||||||
An interactive user interface displays "Appropriate Legal Notices"
|
|
||||||
to the extent that it includes a convenient and prominently visible
|
|
||||||
feature that (1) displays an appropriate copyright notice, and (2)
|
|
||||||
tells the user that there is no warranty for the work (except to the
|
|
||||||
extent that warranties are provided), that licensees may convey the
|
|
||||||
work under this License, and how to view a copy of this License. If
|
|
||||||
the interface presents a list of user commands or options, such as a
|
|
||||||
menu, a prominent item in the list meets this criterion.
|
|
||||||
|
|
||||||
1. Source Code.
|
|
||||||
|
|
||||||
The "source code" for a work means the preferred form of the work
|
|
||||||
for making modifications to it. "Object code" means any non-source
|
|
||||||
form of a work.
|
|
||||||
|
|
||||||
A "Standard Interface" means an interface that either is an official
|
|
||||||
standard defined by a recognized standards body, or, in the case of
|
|
||||||
interfaces specified for a particular programming language, one that
|
|
||||||
is widely used among developers working in that language.
|
|
||||||
|
|
||||||
The "System Libraries" of an executable work include anything, other
|
|
||||||
than the work as a whole, that (a) is included in the normal form of
|
|
||||||
packaging a Major Component, but which is not part of that Major
|
|
||||||
Component, and (b) serves only to enable use of the work with that
|
|
||||||
Major Component, or to implement a Standard Interface for which an
|
|
||||||
implementation is available to the public in source code form. A
|
|
||||||
"Major Component", in this context, means a major essential component
|
|
||||||
(kernel, window system, and so on) of the specific operating system
|
|
||||||
(if any) on which the executable work runs, or a compiler used to
|
|
||||||
produce the work, or an object code interpreter used to run it.
|
|
||||||
|
|
||||||
The "Corresponding Source" for a work in object code form means all
|
|
||||||
the source code needed to generate, install, and (for an executable
|
|
||||||
work) run the object code and to modify the work, including scripts to
|
|
||||||
control those activities. However, it does not include the work's
|
|
||||||
System Libraries, or general-purpose tools or generally available free
|
|
||||||
programs which are used unmodified in performing those activities but
|
|
||||||
which are not part of the work. For example, Corresponding Source
|
|
||||||
includes interface definition files associated with source files for
|
|
||||||
the work, and the source code for shared libraries and dynamically
|
|
||||||
linked subprograms that the work is specifically designed to require,
|
|
||||||
such as by intimate data communication or control flow between those
|
|
||||||
subprograms and other parts of the work.
|
|
||||||
|
|
||||||
The Corresponding Source need not include anything that users
|
|
||||||
can regenerate automatically from other parts of the Corresponding
|
|
||||||
Source.
|
|
||||||
|
|
||||||
The Corresponding Source for a work in source code form is that
|
|
||||||
same work.
|
|
||||||
|
|
||||||
2. Basic Permissions.
|
|
||||||
|
|
||||||
All rights granted under this License are granted for the term of
|
|
||||||
copyright on the Program, and are irrevocable provided the stated
|
|
||||||
conditions are met. This License explicitly affirms your unlimited
|
|
||||||
permission to run the unmodified Program. The output from running a
|
|
||||||
covered work is covered by this License only if the output, given its
|
|
||||||
content, constitutes a covered work. This License acknowledges your
|
|
||||||
rights of fair use or other equivalent, as provided by copyright law.
|
|
||||||
|
|
||||||
You may make, run and propagate covered works that you do not
|
|
||||||
convey, without conditions so long as your license otherwise remains
|
|
||||||
in force. You may convey covered works to others for the sole purpose
|
|
||||||
of having them make modifications exclusively for you, or provide you
|
|
||||||
with facilities for running those works, provided that you comply with
|
|
||||||
the terms of this License in conveying all material for which you do
|
|
||||||
not control copyright. Those thus making or running the covered works
|
|
||||||
for you must do so exclusively on your behalf, under your direction
|
|
||||||
and control, on terms that prohibit them from making any copies of
|
|
||||||
your copyrighted material outside their relationship with you.
|
|
||||||
|
|
||||||
Conveying under any other circumstances is permitted solely under
|
|
||||||
the conditions stated below. Sublicensing is not allowed; section 10
|
|
||||||
makes it unnecessary.
|
|
||||||
|
|
||||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
|
||||||
|
|
||||||
No covered work shall be deemed part of an effective technological
|
|
||||||
measure under any applicable law fulfilling obligations under article
|
|
||||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
|
||||||
similar laws prohibiting or restricting circumvention of such
|
|
||||||
measures.
|
|
||||||
|
|
||||||
When you convey a covered work, you waive any legal power to forbid
|
|
||||||
circumvention of technological measures to the extent such circumvention
|
|
||||||
is effected by exercising rights under this License with respect to
|
|
||||||
the covered work, and you disclaim any intention to limit operation or
|
|
||||||
modification of the work as a means of enforcing, against the work's
|
|
||||||
users, your or third parties' legal rights to forbid circumvention of
|
|
||||||
technological measures.
|
|
||||||
|
|
||||||
4. Conveying Verbatim Copies.
|
|
||||||
|
|
||||||
You may convey verbatim copies of the Program's source code as you
|
|
||||||
receive it, in any medium, provided that you conspicuously and
|
|
||||||
appropriately publish on each copy an appropriate copyright notice;
|
|
||||||
keep intact all notices stating that this License and any
|
|
||||||
non-permissive terms added in accord with section 7 apply to the code;
|
|
||||||
keep intact all notices of the absence of any warranty; and give all
|
|
||||||
recipients a copy of this License along with the Program.
|
|
||||||
|
|
||||||
You may charge any price or no price for each copy that you convey,
|
|
||||||
and you may offer support or warranty protection for a fee.
|
|
||||||
|
|
||||||
5. Conveying Modified Source Versions.
|
|
||||||
|
|
||||||
You may convey a work based on the Program, or the modifications to
|
|
||||||
produce it from the Program, in the form of source code under the
|
|
||||||
terms of section 4, provided that you also meet all of these conditions:
|
|
||||||
|
|
||||||
a) The work must carry prominent notices stating that you modified
|
|
||||||
it, and giving a relevant date.
|
|
||||||
|
|
||||||
b) The work must carry prominent notices stating that it is
|
|
||||||
released under this License and any conditions added under section
|
|
||||||
7. This requirement modifies the requirement in section 4 to
|
|
||||||
"keep intact all notices".
|
|
||||||
|
|
||||||
c) You must license the entire work, as a whole, under this
|
|
||||||
License to anyone who comes into possession of a copy. This
|
|
||||||
License will therefore apply, along with any applicable section 7
|
|
||||||
additional terms, to the whole of the work, and all its parts,
|
|
||||||
regardless of how they are packaged. This License gives no
|
|
||||||
permission to license the work in any other way, but it does not
|
|
||||||
invalidate such permission if you have separately received it.
|
|
||||||
|
|
||||||
d) If the work has interactive user interfaces, each must display
|
|
||||||
Appropriate Legal Notices; however, if the Program has interactive
|
|
||||||
interfaces that do not display Appropriate Legal Notices, your
|
|
||||||
work need not make them do so.
|
|
||||||
|
|
||||||
A compilation of a covered work with other separate and independent
|
|
||||||
works, which are not by their nature extensions of the covered work,
|
|
||||||
and which are not combined with it such as to form a larger program,
|
|
||||||
in or on a volume of a storage or distribution medium, is called an
|
|
||||||
"aggregate" if the compilation and its resulting copyright are not
|
|
||||||
used to limit the access or legal rights of the compilation's users
|
|
||||||
beyond what the individual works permit. Inclusion of a covered work
|
|
||||||
in an aggregate does not cause this License to apply to the other
|
|
||||||
parts of the aggregate.
|
|
||||||
|
|
||||||
6. Conveying Non-Source Forms.
|
|
||||||
|
|
||||||
You may convey a covered work in object code form under the terms
|
|
||||||
of sections 4 and 5, provided that you also convey the
|
|
||||||
machine-readable Corresponding Source under the terms of this License,
|
|
||||||
in one of these ways:
|
|
||||||
|
|
||||||
a) Convey the object code in, or embodied in, a physical product
|
|
||||||
(including a physical distribution medium), accompanied by the
|
|
||||||
Corresponding Source fixed on a durable physical medium
|
|
||||||
customarily used for software interchange.
|
|
||||||
|
|
||||||
b) Convey the object code in, or embodied in, a physical product
|
|
||||||
(including a physical distribution medium), accompanied by a
|
|
||||||
written offer, valid for at least three years and valid for as
|
|
||||||
long as you offer spare parts or customer support for that product
|
|
||||||
model, to give anyone who possesses the object code either (1) a
|
|
||||||
copy of the Corresponding Source for all the software in the
|
|
||||||
product that is covered by this License, on a durable physical
|
|
||||||
medium customarily used for software interchange, for a price no
|
|
||||||
more than your reasonable cost of physically performing this
|
|
||||||
conveying of source, or (2) access to copy the
|
|
||||||
Corresponding Source from a network server at no charge.
|
|
||||||
|
|
||||||
c) Convey individual copies of the object code with a copy of the
|
|
||||||
written offer to provide the Corresponding Source. This
|
|
||||||
alternative is allowed only occasionally and noncommercially, and
|
|
||||||
only if you received the object code with such an offer, in accord
|
|
||||||
with subsection 6b.
|
|
||||||
|
|
||||||
d) Convey the object code by offering access from a designated
|
|
||||||
place (gratis or for a charge), and offer equivalent access to the
|
|
||||||
Corresponding Source in the same way through the same place at no
|
|
||||||
further charge. You need not require recipients to copy the
|
|
||||||
Corresponding Source along with the object code. If the place to
|
|
||||||
copy the object code is a network server, the Corresponding Source
|
|
||||||
may be on a different server (operated by you or a third party)
|
|
||||||
that supports equivalent copying facilities, provided you maintain
|
|
||||||
clear directions next to the object code saying where to find the
|
|
||||||
Corresponding Source. Regardless of what server hosts the
|
|
||||||
Corresponding Source, you remain obligated to ensure that it is
|
|
||||||
available for as long as needed to satisfy these requirements.
|
|
||||||
|
|
||||||
e) Convey the object code using peer-to-peer transmission, provided
|
|
||||||
you inform other peers where the object code and Corresponding
|
|
||||||
Source of the work are being offered to the general public at no
|
|
||||||
charge under subsection 6d.
|
|
||||||
|
|
||||||
A separable portion of the object code, whose source code is excluded
|
|
||||||
from the Corresponding Source as a System Library, need not be
|
|
||||||
included in conveying the object code work.
|
|
||||||
|
|
||||||
A "User Product" is either (1) a "consumer product", which means any
|
|
||||||
tangible personal property which is normally used for personal, family,
|
|
||||||
or household purposes, or (2) anything designed or sold for incorporation
|
|
||||||
into a dwelling. In determining whether a product is a consumer product,
|
|
||||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
|
||||||
product received by a particular user, "normally used" refers to a
|
|
||||||
typical or common use of that class of product, regardless of the status
|
|
||||||
of the particular user or of the way in which the particular user
|
|
||||||
actually uses, or expects or is expected to use, the product. A product
|
|
||||||
is a consumer product regardless of whether the product has substantial
|
|
||||||
commercial, industrial or non-consumer uses, unless such uses represent
|
|
||||||
the only significant mode of use of the product.
|
|
||||||
|
|
||||||
"Installation Information" for a User Product means any methods,
|
|
||||||
procedures, authorization keys, or other information required to install
|
|
||||||
and execute modified versions of a covered work in that User Product from
|
|
||||||
a modified version of its Corresponding Source. The information must
|
|
||||||
suffice to ensure that the continued functioning of the modified object
|
|
||||||
code is in no case prevented or interfered with solely because
|
|
||||||
modification has been made.
|
|
||||||
|
|
||||||
If you convey an object code work under this section in, or with, or
|
|
||||||
specifically for use in, a User Product, and the conveying occurs as
|
|
||||||
part of a transaction in which the right of possession and use of the
|
|
||||||
User Product is transferred to the recipient in perpetuity or for a
|
|
||||||
fixed term (regardless of how the transaction is characterized), the
|
|
||||||
Corresponding Source conveyed under this section must be accompanied
|
|
||||||
by the Installation Information. But this requirement does not apply
|
|
||||||
if neither you nor any third party retains the ability to install
|
|
||||||
modified object code on the User Product (for example, the work has
|
|
||||||
been installed in ROM).
|
|
||||||
|
|
||||||
The requirement to provide Installation Information does not include a
|
|
||||||
requirement to continue to provide support service, warranty, or updates
|
|
||||||
for a work that has been modified or installed by the recipient, or for
|
|
||||||
the User Product in which it has been modified or installed. Access to a
|
|
||||||
network may be denied when the modification itself materially and
|
|
||||||
adversely affects the operation of the network or violates the rules and
|
|
||||||
protocols for communication across the network.
|
|
||||||
|
|
||||||
Corresponding Source conveyed, and Installation Information provided,
|
|
||||||
in accord with this section must be in a format that is publicly
|
|
||||||
documented (and with an implementation available to the public in
|
|
||||||
source code form), and must require no special password or key for
|
|
||||||
unpacking, reading or copying.
|
|
||||||
|
|
||||||
7. Additional Terms.
|
|
||||||
|
|
||||||
"Additional permissions" are terms that supplement the terms of this
|
|
||||||
License by making exceptions from one or more of its conditions.
|
|
||||||
Additional permissions that are applicable to the entire Program shall
|
|
||||||
be treated as though they were included in this License, to the extent
|
|
||||||
that they are valid under applicable law. If additional permissions
|
|
||||||
apply only to part of the Program, that part may be used separately
|
|
||||||
under those permissions, but the entire Program remains governed by
|
|
||||||
this License without regard to the additional permissions.
|
|
||||||
|
|
||||||
When you convey a copy of a covered work, you may at your option
|
|
||||||
remove any additional permissions from that copy, or from any part of
|
|
||||||
it. (Additional permissions may be written to require their own
|
|
||||||
removal in certain cases when you modify the work.) You may place
|
|
||||||
additional permissions on material, added by you to a covered work,
|
|
||||||
for which you have or can give appropriate copyright permission.
|
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, for material you
|
|
||||||
add to a covered work, you may (if authorized by the copyright holders of
|
|
||||||
that material) supplement the terms of this License with terms:
|
|
||||||
|
|
||||||
a) Disclaiming warranty or limiting liability differently from the
|
|
||||||
terms of sections 15 and 16 of this License; or
|
|
||||||
|
|
||||||
b) Requiring preservation of specified reasonable legal notices or
|
|
||||||
author attributions in that material or in the Appropriate Legal
|
|
||||||
Notices displayed by works containing it; or
|
|
||||||
|
|
||||||
c) Prohibiting misrepresentation of the origin of that material, or
|
|
||||||
requiring that modified versions of such material be marked in
|
|
||||||
reasonable ways as different from the original version; or
|
|
||||||
|
|
||||||
d) Limiting the use for publicity purposes of names of licensors or
|
|
||||||
authors of the material; or
|
|
||||||
|
|
||||||
e) Declining to grant rights under trademark law for use of some
|
|
||||||
trade names, trademarks, or service marks; or
|
|
||||||
|
|
||||||
f) Requiring indemnification of licensors and authors of that
|
|
||||||
material by anyone who conveys the material (or modified versions of
|
|
||||||
it) with contractual assumptions of liability to the recipient, for
|
|
||||||
any liability that these contractual assumptions directly impose on
|
|
||||||
those licensors and authors.
|
|
||||||
|
|
||||||
All other non-permissive additional terms are considered "further
|
|
||||||
restrictions" within the meaning of section 10. If the Program as you
|
|
||||||
received it, or any part of it, contains a notice stating that it is
|
|
||||||
governed by this License along with a term that is a further
|
|
||||||
restriction, you may remove that term. If a license document contains
|
|
||||||
a further restriction but permits relicensing or conveying under this
|
|
||||||
License, you may add to a covered work material governed by the terms
|
|
||||||
of that license document, provided that the further restriction does
|
|
||||||
not survive such relicensing or conveying.
|
|
||||||
|
|
||||||
If you add terms to a covered work in accord with this section, you
|
|
||||||
must place, in the relevant source files, a statement of the
|
|
||||||
additional terms that apply to those files, or a notice indicating
|
|
||||||
where to find the applicable terms.
|
|
||||||
|
|
||||||
Additional terms, permissive or non-permissive, may be stated in the
|
|
||||||
form of a separately written license, or stated as exceptions;
|
|
||||||
the above requirements apply either way.
|
|
||||||
|
|
||||||
8. Termination.
|
|
||||||
|
|
||||||
You may not propagate or modify a covered work except as expressly
|
|
||||||
provided under this License. Any attempt otherwise to propagate or
|
|
||||||
modify it is void, and will automatically terminate your rights under
|
|
||||||
this License (including any patent licenses granted under the third
|
|
||||||
paragraph of section 11).
|
|
||||||
|
|
||||||
However, if you cease all violation of this License, then your
|
|
||||||
license from a particular copyright holder is reinstated (a)
|
|
||||||
provisionally, unless and until the copyright holder explicitly and
|
|
||||||
finally terminates your license, and (b) permanently, if the copyright
|
|
||||||
holder fails to notify you of the violation by some reasonable means
|
|
||||||
prior to 60 days after the cessation.
|
|
||||||
|
|
||||||
Moreover, your license from a particular copyright holder is
|
|
||||||
reinstated permanently if the copyright holder notifies you of the
|
|
||||||
violation by some reasonable means, this is the first time you have
|
|
||||||
received notice of violation of this License (for any work) from that
|
|
||||||
copyright holder, and you cure the violation prior to 30 days after
|
|
||||||
your receipt of the notice.
|
|
||||||
|
|
||||||
Termination of your rights under this section does not terminate the
|
|
||||||
licenses of parties who have received copies or rights from you under
|
|
||||||
this License. If your rights have been terminated and not permanently
|
|
||||||
reinstated, you do not qualify to receive new licenses for the same
|
|
||||||
material under section 10.
|
|
||||||
|
|
||||||
9. Acceptance Not Required for Having Copies.
|
|
||||||
|
|
||||||
You are not required to accept this License in order to receive or
|
|
||||||
run a copy of the Program. Ancillary propagation of a covered work
|
|
||||||
occurring solely as a consequence of using peer-to-peer transmission
|
|
||||||
to receive a copy likewise does not require acceptance. However,
|
|
||||||
nothing other than this License grants you permission to propagate or
|
|
||||||
modify any covered work. These actions infringe copyright if you do
|
|
||||||
not accept this License. Therefore, by modifying or propagating a
|
|
||||||
covered work, you indicate your acceptance of this License to do so.
|
|
||||||
|
|
||||||
10. Automatic Licensing of Downstream Recipients.
|
|
||||||
|
|
||||||
Each time you convey a covered work, the recipient automatically
|
|
||||||
receives a license from the original licensors, to run, modify and
|
|
||||||
propagate that work, subject to this License. You are not responsible
|
|
||||||
for enforcing compliance by third parties with this License.
|
|
||||||
|
|
||||||
An "entity transaction" is a transaction transferring control of an
|
|
||||||
organization, or substantially all assets of one, or subdividing an
|
|
||||||
organization, or merging organizations. If propagation of a covered
|
|
||||||
work results from an entity transaction, each party to that
|
|
||||||
transaction who receives a copy of the work also receives whatever
|
|
||||||
licenses to the work the party's predecessor in interest had or could
|
|
||||||
give under the previous paragraph, plus a right to possession of the
|
|
||||||
Corresponding Source of the work from the predecessor in interest, if
|
|
||||||
the predecessor has it or can get it with reasonable efforts.
|
|
||||||
|
|
||||||
You may not impose any further restrictions on the exercise of the
|
|
||||||
rights granted or affirmed under this License. For example, you may
|
|
||||||
not impose a license fee, royalty, or other charge for exercise of
|
|
||||||
rights granted under this License, and you may not initiate litigation
|
|
||||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
|
||||||
any patent claim is infringed by making, using, selling, offering for
|
|
||||||
sale, or importing the Program or any portion of it.
|
|
||||||
|
|
||||||
11. Patents.
|
|
||||||
|
|
||||||
A "contributor" is a copyright holder who authorizes use under this
|
|
||||||
License of the Program or a work on which the Program is based. The
|
|
||||||
work thus licensed is called the contributor's "contributor version".
|
|
||||||
|
|
||||||
A contributor's "essential patent claims" are all patent claims
|
|
||||||
owned or controlled by the contributor, whether already acquired or
|
|
||||||
hereafter acquired, that would be infringed by some manner, permitted
|
|
||||||
by this License, of making, using, or selling its contributor version,
|
|
||||||
but do not include claims that would be infringed only as a
|
|
||||||
consequence of further modification of the contributor version. For
|
|
||||||
purposes of this definition, "control" includes the right to grant
|
|
||||||
patent sublicenses in a manner consistent with the requirements of
|
|
||||||
this License.
|
|
||||||
|
|
||||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
|
||||||
patent license under the contributor's essential patent claims, to
|
|
||||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
|
||||||
propagate the contents of its contributor version.
|
|
||||||
|
|
||||||
In the following three paragraphs, a "patent license" is any express
|
|
||||||
agreement or commitment, however denominated, not to enforce a patent
|
|
||||||
(such as an express permission to practice a patent or covenant not to
|
|
||||||
sue for patent infringement). To "grant" such a patent license to a
|
|
||||||
party means to make such an agreement or commitment not to enforce a
|
|
||||||
patent against the party.
|
|
||||||
|
|
||||||
If you convey a covered work, knowingly relying on a patent license,
|
|
||||||
and the Corresponding Source of the work is not available for anyone
|
|
||||||
to copy, free of charge and under the terms of this License, through a
|
|
||||||
publicly available network server or other readily accessible means,
|
|
||||||
then you must either (1) cause the Corresponding Source to be so
|
|
||||||
available, or (2) arrange to deprive yourself of the benefit of the
|
|
||||||
patent license for this particular work, or (3) arrange, in a manner
|
|
||||||
consistent with the requirements of this License, to extend the patent
|
|
||||||
license to downstream recipients. "Knowingly relying" means you have
|
|
||||||
actual knowledge that, but for the patent license, your conveying the
|
|
||||||
covered work in a country, or your recipient's use of the covered work
|
|
||||||
in a country, would infringe one or more identifiable patents in that
|
|
||||||
country that you have reason to believe are valid.
|
|
||||||
|
|
||||||
If, pursuant to or in connection with a single transaction or
|
|
||||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
|
||||||
covered work, and grant a patent license to some of the parties
|
|
||||||
receiving the covered work authorizing them to use, propagate, modify
|
|
||||||
or convey a specific copy of the covered work, then the patent license
|
|
||||||
you grant is automatically extended to all recipients of the covered
|
|
||||||
work and works based on it.
|
|
||||||
|
|
||||||
A patent license is "discriminatory" if it does not include within
|
|
||||||
the scope of its coverage, prohibits the exercise of, or is
|
|
||||||
conditioned on the non-exercise of one or more of the rights that are
|
|
||||||
specifically granted under this License. You may not convey a covered
|
|
||||||
work if you are a party to an arrangement with a third party that is
|
|
||||||
in the business of distributing software, under which you make payment
|
|
||||||
to the third party based on the extent of your activity of conveying
|
|
||||||
the work, and under which the third party grants, to any of the
|
|
||||||
parties who would receive the covered work from you, a discriminatory
|
|
||||||
patent license (a) in connection with copies of the covered work
|
|
||||||
conveyed by you (or copies made from those copies), or (b) primarily
|
|
||||||
for and in connection with specific products or compilations that
|
|
||||||
contain the covered work, unless you entered into that arrangement,
|
|
||||||
or that patent license was granted, prior to 28 March 2007.
|
|
||||||
|
|
||||||
Nothing in this License shall be construed as excluding or limiting
|
|
||||||
any implied license or other defenses to infringement that may
|
|
||||||
otherwise be available to you under applicable patent law.
|
|
||||||
|
|
||||||
12. No Surrender of Others' Freedom.
|
|
||||||
|
|
||||||
If conditions are imposed on you (whether by court order, agreement or
|
|
||||||
otherwise) that contradict the conditions of this License, they do not
|
|
||||||
excuse you from the conditions of this License. If you cannot convey a
|
|
||||||
covered work so as to satisfy simultaneously your obligations under this
|
|
||||||
License and any other pertinent obligations, then as a consequence you may
|
|
||||||
not convey it at all. For example, if you agree to terms that obligate you
|
|
||||||
to collect a royalty for further conveying from those to whom you convey
|
|
||||||
the Program, the only way you could satisfy both those terms and this
|
|
||||||
License would be to refrain entirely from conveying the Program.
|
|
||||||
|
|
||||||
13. Use with the GNU Affero General Public License.
|
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, you have
|
|
||||||
permission to link or combine any covered work with a work licensed
|
|
||||||
under version 3 of the GNU Affero General Public License into a single
|
|
||||||
combined work, and to convey the resulting work. The terms of this
|
|
||||||
License will continue to apply to the part which is the covered work,
|
|
||||||
but the special requirements of the GNU Affero General Public License,
|
|
||||||
section 13, concerning interaction through a network will apply to the
|
|
||||||
combination as such.
|
|
||||||
|
|
||||||
14. Revised Versions of this License.
|
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of
|
|
||||||
the GNU General Public License from time to time. Such new versions will
|
|
||||||
be similar in spirit to the present version, but may differ in detail to
|
|
||||||
address new problems or concerns.
|
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
|
||||||
Program specifies that a certain numbered version of the GNU General
|
|
||||||
Public License "or any later version" applies to it, you have the
|
|
||||||
option of following the terms and conditions either of that numbered
|
|
||||||
version or of any later version published by the Free Software
|
|
||||||
Foundation. If the Program does not specify a version number of the
|
|
||||||
GNU General Public License, you may choose any version ever published
|
|
||||||
by the Free Software Foundation.
|
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future
|
|
||||||
versions of the GNU General Public License can be used, that proxy's
|
|
||||||
public statement of acceptance of a version permanently authorizes you
|
|
||||||
to choose that version for the Program.
|
|
||||||
|
|
||||||
Later license versions may give you additional or different
|
|
||||||
permissions. However, no additional obligations are imposed on any
|
|
||||||
author or copyright holder as a result of your choosing to follow a
|
|
||||||
later version.
|
|
||||||
|
|
||||||
15. Disclaimer of Warranty.
|
|
||||||
|
|
||||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
|
||||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
|
||||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
|
||||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
||||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
|
||||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
|
||||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
|
||||||
|
|
||||||
16. Limitation of Liability.
|
|
||||||
|
|
||||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
|
||||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
|
||||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
|
||||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
|
||||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
|
||||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
|
||||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
|
||||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
|
||||||
SUCH DAMAGES.
|
|
||||||
|
|
||||||
17. Interpretation of Sections 15 and 16.
|
|
||||||
|
|
||||||
If the disclaimer of warranty and limitation of liability provided
|
|
||||||
above cannot be given local legal effect according to their terms,
|
|
||||||
reviewing courts shall apply local law that most closely approximates
|
|
||||||
an absolute waiver of all civil liability in connection with the
|
|
||||||
Program, unless a warranty or assumption of liability accompanies a
|
|
||||||
copy of the Program in return for a fee.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
After labels:
|
|
||||||
*templates : remove advanced , rename deluge to classic , result = classic-templ -> no-javascript ; white-templ -> static enhanced with js ; ajax-templ -> json api
|
|
||||||
*white temnplate: add deluge icon.
|
|
||||||
*fix all comments from IRC-nonicknamename2.
|
|
||||||
|
|
||||||
0.6 RC:
|
|
||||||
*Fix IE7 for advanced/white template.
|
|
||||||
*white template : add auto-refresh (with a nice js-progress-bar)
|
|
||||||
*gettext : update template_strings.py + add all .py files to gettext input files
|
|
||||||
|
|
||||||
after 0.6 /before 1.0:
|
|
||||||
*hide-option for details iframe.
|
|
||||||
*white template:better css fluid layout or switch to tables.
|
|
||||||
*rethink details iframe.
|
|
||||||
*persistent sessions
|
|
||||||
|
|
||||||
maybe/ideas/after 0.6.1:
|
|
||||||
*checkboxes for multiple select?
|
|
||||||
*switch to webpy 0.3
|
|
||||||
*right-click menu on torrent-rows
|
|
||||||
*multi row template like transmission etc.
|
|
||||||
|
|
||||||
Half-done:
|
|
||||||
*labels (organize-plugin)
|
|
||||||
*add new major features in available gtk ui but not in webui.
|
|
||||||
|
|
||||||
Done:
|
|
||||||
*plugin-config + (re)enable webui plugins.
|
|
||||||
*white-template : green-iframe ->make blue
|
|
||||||
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# Copyright (C) Martijn Voncken 2007 <mvoncken@gmail.com>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 3, or (at your option)
|
|
||||||
# any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, write to:
|
|
||||||
# The Free Software Foundation, Inc.,
|
|
||||||
# 51 Franklin Street, Fifth Floor
|
|
||||||
# Boston, MA 02110-1301, USA.
|
|
||||||
#
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# Copyright (C) Martijn Voncken 2008 <mvoncken@gmail.com>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 3, or (at your option)
|
|
||||||
# any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, write to:
|
|
||||||
# The Free Software Foundation, Inc.,
|
|
||||||
# 51 Franklin Street, Fifth Floor
|
|
||||||
# Boston, MA 02110-1301, USA.
|
|
||||||
#
|
|
||||||
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
|
||||||
this is an ugly hack, and it will be changed.
|
|
||||||
for experimental use only!!
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
|
|
||||||
def get_wsgi_application(base_url, config_dir):
|
|
||||||
|
|
||||||
#monkeypatch:
|
|
||||||
from deluge import common
|
|
||||||
def get_config_dir(filename = ""):
|
|
||||||
return os.path.join(config_dir, filename)
|
|
||||||
common.get_config_dir = get_config_dir
|
|
||||||
#/monkeypatch
|
|
||||||
|
|
||||||
from deluge.configmanager import ConfigManager
|
|
||||||
from deluge.ui.webui import deluge_webserver
|
|
||||||
from deluge.ui.webui import utils
|
|
||||||
|
|
||||||
config = ConfigManager("webui06.conf")
|
|
||||||
|
|
||||||
utils.set_config_defaults()
|
|
||||||
|
|
||||||
config['base'] = '/deluge'
|
|
||||||
config['disallow'] = {
|
|
||||||
'/daemon/control':'running as an apache user',
|
|
||||||
'/config/server':'running as an apache-user'
|
|
||||||
}
|
|
||||||
|
|
||||||
utils.apply_config()
|
|
||||||
|
|
||||||
return deluge_webserver.WsgiApplication()
|
|
||||||
@ -1,212 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# Copyright (C) Martijn Voncken 2008 <mvoncken@gmail.com>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 3, or (at your option)
|
|
||||||
# any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, write to:
|
|
||||||
# The Free Software Foundation, Inc.,
|
|
||||||
# 51 Franklin Street, Fifth Floor
|
|
||||||
# Boston, MA 02110-1301, USA.
|
|
||||||
#
|
|
||||||
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
|
||||||
deluge components.
|
|
||||||
|
|
||||||
MenuManager:add torrent-menu-items and torrent-detail tabs.
|
|
||||||
PageManager: add pages(urls)
|
|
||||||
PluginManager: deluge plugin manager
|
|
||||||
ConfigPageManager: add config pages(tabs)
|
|
||||||
|
|
||||||
These managers/components are accesible as:
|
|
||||||
|
|
||||||
from deluge import component
|
|
||||||
manager = component.get("ClassName")
|
|
||||||
|
|
||||||
"""
|
|
||||||
from deluge import component
|
|
||||||
import lib.newforms_plus as forms
|
|
||||||
from lib.egg_handler import egg_handler
|
|
||||||
from lib.egg_render import egg_render
|
|
||||||
from deluge.ui.client import aclient
|
|
||||||
from deluge import component, pluginmanagerbase
|
|
||||||
from deluge.configmanager import ConfigManager
|
|
||||||
from deluge.log import LOG as log
|
|
||||||
|
|
||||||
class TOOLBAR_FLAGS:
|
|
||||||
generic = 0
|
|
||||||
torrent = 1
|
|
||||||
torrent_list = 2
|
|
||||||
|
|
||||||
class MenuManager(component.Component):
|
|
||||||
TOOLBAR_FLAGS = TOOLBAR_FLAGS
|
|
||||||
def __init__(self):
|
|
||||||
component.Component.__init__(self, "MenuManager")
|
|
||||||
self.admin_pages = [] #[(title, url),..]
|
|
||||||
self.detail_tabs = [] #[(title, url),..]
|
|
||||||
self.toolbar_items = [] #((id,title ,flag ,method ,url ,image ),.. )
|
|
||||||
|
|
||||||
|
|
||||||
#register vars in template.
|
|
||||||
from render import template
|
|
||||||
template.Template.globals["admin_pages"] = self.admin_pages
|
|
||||||
template.Template.globals["detail_tabs"] = self.detail_tabs
|
|
||||||
template.Template.globals["toolbar_items"] = self.toolbar_items
|
|
||||||
|
|
||||||
|
|
||||||
def register_toolbar_item(self, id, title, image, flag, method, url, important):
|
|
||||||
self.toolbar_items.append((id, title, image, flag, method, url, important))
|
|
||||||
|
|
||||||
def deregister_toolbar_item(self, item_id):
|
|
||||||
for (i, toolbar) in enumerate(admin_pages):
|
|
||||||
if toolbar[0] == item_id:
|
|
||||||
del self.toolbar_items[i]
|
|
||||||
|
|
||||||
#admin:
|
|
||||||
def register_admin_page(self, id, title, url):
|
|
||||||
self.admin_pages.append((id, title, url))
|
|
||||||
|
|
||||||
def deregister_admin_page(page_id):
|
|
||||||
for (i, (id, title, url)) in list(enumerate(admin_pages)):
|
|
||||||
if id == page_id:
|
|
||||||
del self.admin_pages[i]
|
|
||||||
return
|
|
||||||
|
|
||||||
#detail:
|
|
||||||
def register_detail_tab(self, id, title, page):
|
|
||||||
self.detail_tabs.append((id, title, page))
|
|
||||||
|
|
||||||
def deregister_detail_tab(self, tab_id):
|
|
||||||
for (i, (id, title, tab)) in list(enumerate(detail_tabs)):
|
|
||||||
if id == tab_id:
|
|
||||||
del self.detail_tabs[i]
|
|
||||||
return
|
|
||||||
|
|
||||||
class PageManager(component.Component):
|
|
||||||
"""
|
|
||||||
web,py 0.2 mapping hack..
|
|
||||||
see deluge_webserver.py
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
component.Component.__init__(self, "PageManager")
|
|
||||||
self.page_classes = {}
|
|
||||||
self.urls = []
|
|
||||||
self.include_javascript = []
|
|
||||||
self.ajax_javascript = []
|
|
||||||
|
|
||||||
def register_pages(self, url_list, class_list):
|
|
||||||
self.urls += url_list
|
|
||||||
self.page_classes.update(class_list)
|
|
||||||
|
|
||||||
def register_page(self, url, klass, use_module=True):
|
|
||||||
if use_module:
|
|
||||||
name = klass.__module__ + "." + klass.__name__
|
|
||||||
else:
|
|
||||||
name = klass.__name__
|
|
||||||
self.urls.append(url)
|
|
||||||
self.urls.append(name)
|
|
||||||
self.page_classes[name] = klass
|
|
||||||
|
|
||||||
def deregister_page(self, url):
|
|
||||||
raise NotImplemenetedError()
|
|
||||||
#self.page_classes[klass.__name__] = None
|
|
||||||
|
|
||||||
class PluginManager(pluginmanagerbase.PluginManagerBase,
|
|
||||||
component.Component):
|
|
||||||
def __init__(self):
|
|
||||||
component.Component.__init__(self, "WebPluginManager")
|
|
||||||
self.config = ConfigManager("webui06.conf")
|
|
||||||
pluginmanagerbase.PluginManagerBase.__init__(
|
|
||||||
self, "webui06.conf", "deluge.plugin.webui")
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
"""Start the plugin manager"""
|
|
||||||
# Update the enabled_plugins from the core
|
|
||||||
log.debug("start webui plugin manager")
|
|
||||||
aclient.get_enabled_plugins(self._on_get_enabled_plugins)
|
|
||||||
aclient.force_call(block=True)
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
# Disable the plugins
|
|
||||||
self.disable_plugins()
|
|
||||||
|
|
||||||
def _on_get_enabled_plugins(self, enabled_plugins):
|
|
||||||
|
|
||||||
log.debug("..Webui has these plugins enabled: %s", enabled_plugins)
|
|
||||||
|
|
||||||
log.debug("abababab")
|
|
||||||
log.debug(self.config)
|
|
||||||
|
|
||||||
self.config.config["enabled_plugins"] = enabled_plugins
|
|
||||||
|
|
||||||
# Enable the plugins that are enabled in the config and core
|
|
||||||
log.debug(self.enable_plugins)
|
|
||||||
try:
|
|
||||||
self.enable_plugins()
|
|
||||||
except Exception, e:
|
|
||||||
log.debug(e)
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigPageManager(component.Component):
|
|
||||||
def __init__(self):
|
|
||||||
component.Component.__init__(self, "ConfigPageManager")
|
|
||||||
self.groups = []
|
|
||||||
self.blocks = forms.django.utils.datastructures.SortedDict()
|
|
||||||
|
|
||||||
def register(self, group, name, form):
|
|
||||||
if not group in self.groups:
|
|
||||||
self.groups.append(group)
|
|
||||||
form.group = group
|
|
||||||
self.blocks[name] = form
|
|
||||||
|
|
||||||
def deregister(self, name):
|
|
||||||
del self.blocks[name]
|
|
||||||
|
|
||||||
class PluginApi(component.Component):
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
component.Component.__init__(self, "WebPluginApi")
|
|
||||||
import web
|
|
||||||
from render import render
|
|
||||||
import page_decorators as deco
|
|
||||||
import utils
|
|
||||||
import lib.newforms_plus as forms
|
|
||||||
|
|
||||||
self.egg_handler = egg_handler
|
|
||||||
self.egg_render = egg_render
|
|
||||||
self.render = render
|
|
||||||
self.web = web
|
|
||||||
self.deco = deco
|
|
||||||
self.forms = forms
|
|
||||||
self.page_manager = component.get("PageManager")
|
|
||||||
self.config_page_manager = component.get("ConfigPageManager")
|
|
||||||
self.menu_manager = component.get("MenuManager")
|
|
||||||
self.utils = utils
|
|
||||||
|
|
||||||
def register():
|
|
||||||
__page_manager = PageManager()
|
|
||||||
__plugin_manager = PluginManager()
|
|
||||||
__menu_manager = MenuManager()
|
|
||||||
__config_page_manager = ConfigPageManager()
|
|
||||||
__plugin_api = PluginApi()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
register()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,103 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright (C) Martijn Voncken 2008 <mvoncken@gmail.com>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 3, or (at your option)
|
|
||||||
# any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, write to:
|
|
||||||
# The Free Software Foundation, Inc.,
|
|
||||||
# 51 Franklin Street, Fifth Floor
|
|
||||||
# Boston, MA 02110-1301, USA.
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
import lib.newforms_plus as forms
|
|
||||||
import page_decorators as deco
|
|
||||||
import web
|
|
||||||
from deluge.ui.client import sclient as proxy
|
|
||||||
from deluge.log import LOG as log
|
|
||||||
|
|
||||||
from render import render
|
|
||||||
from utils import seeother
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import utils
|
|
||||||
|
|
||||||
from deluge import component
|
|
||||||
from deluge.configmanager import ConfigManager
|
|
||||||
|
|
||||||
config = ConfigManager("webui06.conf")
|
|
||||||
config_page_manager = component.get("ConfigPageManager")
|
|
||||||
|
|
||||||
class WebCfgForm(forms.Form):
|
|
||||||
"config base for webui"
|
|
||||||
def initial_data(self):
|
|
||||||
return config.config
|
|
||||||
|
|
||||||
def save(self, data):
|
|
||||||
utils.validate_config(data)
|
|
||||||
for key, value in data.iteritems():
|
|
||||||
config[key] = value
|
|
||||||
config.save()
|
|
||||||
|
|
||||||
class CfgForm(forms.Form):
|
|
||||||
"config base for deluge-cfg"
|
|
||||||
def initial_data(self):
|
|
||||||
return proxy.get_config()
|
|
||||||
def save(self, data):
|
|
||||||
proxy.set_config(dict(data))
|
|
||||||
|
|
||||||
class config_page:
|
|
||||||
"""
|
|
||||||
web.py config page
|
|
||||||
"""
|
|
||||||
def get_form_class(self,name):
|
|
||||||
try:
|
|
||||||
return config_page_manager.blocks[name]
|
|
||||||
except KeyError:
|
|
||||||
raise Exception('no config page named:"%s"' % name)
|
|
||||||
|
|
||||||
@deco.deluge_page
|
|
||||||
def GET(self, name):
|
|
||||||
if name == '':
|
|
||||||
return seeother('/config/template')
|
|
||||||
|
|
||||||
form_class = self.get_form_class(name)
|
|
||||||
f = form_class()
|
|
||||||
f.full_clean()
|
|
||||||
return self.render(f , name)
|
|
||||||
|
|
||||||
@deco.deluge_page
|
|
||||||
def POST(self,name):
|
|
||||||
|
|
||||||
form_class = self.get_form_class(name)
|
|
||||||
form_data = web.Storage(utils.get_newforms_data(form_class))
|
|
||||||
form = form_class(form_data)
|
|
||||||
if form.is_valid():
|
|
||||||
log.debug('save config %s' % form_data)
|
|
||||||
try:
|
|
||||||
form.start_save()
|
|
||||||
return self.render(form , name, _('These changes were saved'))
|
|
||||||
except forms.ValidationError, e:
|
|
||||||
log.debug(e.message)
|
|
||||||
return self.render(form , name, error = e.message)
|
|
||||||
else:
|
|
||||||
return self.render(form , name,
|
|
||||||
error= _('Correct the errors above and try again'))
|
|
||||||
|
|
||||||
def render(self, f , name , message = '' , error=''):
|
|
||||||
return render.config(config_page_manager.groups, config_page_manager.blocks, f, name , message , error)
|
|
||||||
|
|
||||||
def register():
|
|
||||||
component.get("PageManager").register_page("/config/(.*)", config_page)
|
|
||||||
|
|
||||||
@ -1,215 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# deluge_webserver.py
|
|
||||||
#
|
|
||||||
# Copyright (C) Martijn Voncken 2008 <mvoncken@gmail.com>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 3, or (at your option)
|
|
||||||
# any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, write to:
|
|
||||||
# The Free Software Foundation, Inc.,
|
|
||||||
# 51 Franklin Street, Fifth Floor
|
|
||||||
# Boston, MA 02110-1301, USA.
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
from deluge.ui.client import sclient
|
|
||||||
from deluge.log import LOG as log
|
|
||||||
|
|
||||||
import utils
|
|
||||||
|
|
||||||
import lib.newforms_plus as forms
|
|
||||||
import config_forms
|
|
||||||
from deluge import component
|
|
||||||
|
|
||||||
config_page = component.get("ConfigPageManager")
|
|
||||||
|
|
||||||
class NetworkPorts(config_forms.CfgForm ):
|
|
||||||
title = _("Ports")
|
|
||||||
_port_from = forms.IntegerField(label= _("From"),min_value = 0, max_value=65535)
|
|
||||||
_port_to = forms.IntegerField(label = _("To"),min_value = 0, max_value=65535)
|
|
||||||
random_port = forms.CheckBox(label = _("Random"))
|
|
||||||
|
|
||||||
def initial_data(self):
|
|
||||||
data = config_forms.CfgForm.initial_data(self)
|
|
||||||
data['_port_from'] , data['_port_to'] = data['listen_ports']
|
|
||||||
return data
|
|
||||||
|
|
||||||
def save(self,data):
|
|
||||||
data['listen_ports'] = [data['_port_from'] , data['_port_to'] ]
|
|
||||||
del(data['_port_from'])
|
|
||||||
del(data['_port_to'])
|
|
||||||
config_forms.CfgForm.save(self, data)
|
|
||||||
|
|
||||||
def validate(self, data):
|
|
||||||
if (data['_port_to'] < data['_port_from']):
|
|
||||||
raise forms.ValidationError('"Port from" must be greater than "Port to"')
|
|
||||||
|
|
||||||
def post_html(self):
|
|
||||||
return """
|
|
||||||
<ul>
|
|
||||||
<li>Active port:%(active_port)s </li>
|
|
||||||
<li><a href="http://deluge-torrent.org/test-port.php?port=%(active_port)s">Test active port</li>
|
|
||||||
</ul>
|
|
||||||
""" % {'active_port':sclient.get_listen_port()}
|
|
||||||
|
|
||||||
config_page.register('network','ports', NetworkPorts)
|
|
||||||
|
|
||||||
class NetworkExtra(config_forms.CfgForm ):
|
|
||||||
title = _("Extra's")
|
|
||||||
dht = forms.CheckBox(_("Mainline DHT"))
|
|
||||||
upnp = forms.CheckBox(_("UpNP"))
|
|
||||||
natpmp = forms.CheckBox(_("NAT-PMP"))
|
|
||||||
utpex = forms.CheckBox(_("Peer-Exchange"))
|
|
||||||
lsd = forms.CheckBox(_("LSD"))
|
|
||||||
|
|
||||||
config_page.register('network','extra', NetworkExtra)
|
|
||||||
|
|
||||||
class NetworkEnc(config_forms.CfgForm ):
|
|
||||||
title = _("Encryption")
|
|
||||||
|
|
||||||
_enc_choices = list(enumerate([_("Forced"),_("Enabled"),_("Disabled")]))
|
|
||||||
_level_choices = list(enumerate([_("Handshake"), _("Full") , _("Either")]))
|
|
||||||
|
|
||||||
enc_in_policy = forms.IntChoiceField(_("Inbound"), _enc_choices)
|
|
||||||
enc_out_policy = forms.IntChoiceField(_("Outbound"), _enc_choices)
|
|
||||||
enc_level = forms.IntChoiceField(_("Level"), _level_choices)
|
|
||||||
enc_prefer_rc4 = forms.CheckBox("Prefer to encrypt entire stream")
|
|
||||||
|
|
||||||
config_page.register('network','encryption', NetworkEnc)
|
|
||||||
|
|
||||||
class Proxy(config_forms.CfgForm):
|
|
||||||
title = _("Proxy")
|
|
||||||
_type_choices = list(enumerate(
|
|
||||||
[_("None"), _("Socksv4"), _("Socksv5"), _("Socksv5 W/ Auth"),_("HTTP"), _("HTTP W/ Auth")]))
|
|
||||||
|
|
||||||
proxy_type = forms.IntChoiceField(_("Type"), _type_choices)
|
|
||||||
proxy_server =forms.CharField(label= _("Host"),required=False)
|
|
||||||
proxy_port = forms.IntegerField(label= _("Port"),min_value = 0, max_value=65535 , required=False)
|
|
||||||
proxy_username = forms.CharField(label= _("Username"), required=False)
|
|
||||||
proxy_password = forms.Password(label= _("Password"), required=False)
|
|
||||||
|
|
||||||
config_page.register('network','proxy', Proxy)
|
|
||||||
|
|
||||||
class BandwithGlobal(config_forms.CfgForm):
|
|
||||||
title = _("Global")
|
|
||||||
info = _("-1 = Unlimited")
|
|
||||||
max_connections_global = forms.DelugeInt(_("Maximum Connections"))
|
|
||||||
max_download_speed = forms.DelugeFloat(_("Maximum Download Speed (Kib/s)"))
|
|
||||||
max_upload_speed = forms.DelugeFloat(_("Maximum Upload Speed (Kib/s)"))
|
|
||||||
max_upload_slots_global = forms.DelugeInt(_("Maximum Upload Slots"))
|
|
||||||
|
|
||||||
max_half_open_connections = forms.DelugeInt(_("Maximum Half-Open Connections"))
|
|
||||||
max_connections_per_second = forms.DelugeInt(_("Maximum Connection Attempts per Second"))
|
|
||||||
ignore_limits_on_local_network = forms.CheckBox(_("Ignore limits on local network"))
|
|
||||||
rate_limit_ip_overhead = forms.CheckBox(_("Rate Limit IP Overhead"))
|
|
||||||
|
|
||||||
|
|
||||||
config_page.register('bandwidth','global', BandwithGlobal)
|
|
||||||
|
|
||||||
class BandwithTorrent(config_forms.CfgForm):
|
|
||||||
title = _("Per Torrent")
|
|
||||||
info = _("-1 = Unlimited")
|
|
||||||
max_connections_per_torrent = forms.DelugeInt(_("Maximum Connections"))
|
|
||||||
max_download_speed_per_torrent = forms.DelugeFloat(_("Maximum Download Speed (Kib/s)"))
|
|
||||||
max_upload_speed_per_torrent = forms.DelugeFloat(_("Maximum Upload Speed (Kib/s)"))
|
|
||||||
max_upload_slots_per_torrent = forms.DelugeInt(_("Maximum Upload Slots"))
|
|
||||||
|
|
||||||
config_page.register('bandwidth','torrent', BandwithTorrent)
|
|
||||||
|
|
||||||
class Download(config_forms.CfgForm):
|
|
||||||
title = _("Download")
|
|
||||||
download_location = forms.ServerFolder(_("Store all downoads in"))
|
|
||||||
torrentfiles_location = forms.ServerFolder(_("Save .torrent files to"))
|
|
||||||
autoadd_location = forms.ServerFolder(_("Auto Add folder"), required=False)
|
|
||||||
autoadd_enable = forms.CheckBox(_("Auto Add enabled"))
|
|
||||||
compact_allocation = forms.CheckBox(_('Use Compact Allocation'))
|
|
||||||
prioritize_first_last_pieces = forms.CheckBox(_('Prioritize first and last pieces'))
|
|
||||||
#default_private = forms.CheckBox(_('Set private flag by default'))
|
|
||||||
|
|
||||||
config_page.register('deluge','download', Download)
|
|
||||||
|
|
||||||
class Daemon(config_forms.CfgForm):
|
|
||||||
title = _("Daemon")
|
|
||||||
info = _("Restart daemon and webui after changing these settings")
|
|
||||||
daemon_port = forms.IntegerField(_("Port"))
|
|
||||||
allow_remote = forms.CheckBox(_("Allow Remote Connections"))
|
|
||||||
|
|
||||||
config_page.register('deluge','daemon', Daemon)
|
|
||||||
|
|
||||||
class Queue(config_forms.CfgForm):
|
|
||||||
title = _("Queue")
|
|
||||||
info = _("-1 = unlimited")
|
|
||||||
|
|
||||||
queue_new_to_top = forms.CheckBox(_("Queue new torrents to top"))
|
|
||||||
|
|
||||||
|
|
||||||
#total_downloading = forms.DelugeInt(_("Total active downloading"))
|
|
||||||
max_active_limit = forms.DelugeInt(_("Total active torrents"))
|
|
||||||
max_active_downloading = forms.DelugeInt(_("Total active downloading"))
|
|
||||||
max_active_seeding = forms.DelugeInt(_("Total active seeding"))
|
|
||||||
|
|
||||||
|
|
||||||
share_ratio_limit = forms.FloatField(min_value=-1)
|
|
||||||
seed_time_ratio_limit = forms.FloatField(min_value=-1)
|
|
||||||
seed_time_limit = forms.FloatField(min_value=-1)
|
|
||||||
|
|
||||||
stop_seed_at_ratio = forms.CheckBox(_("Stop seeding when ratio reaches"))
|
|
||||||
#stop_ratio = forms.FloatField(min_value=-1)
|
|
||||||
remove_seed_at_ratio = forms.CheckBox(_("Remove torrent when ratio reached"))
|
|
||||||
stop_seed_ratio = forms.FloatField(min_value=-1)
|
|
||||||
|
|
||||||
config_page.register('deluge','queue', Queue)
|
|
||||||
|
|
||||||
"""
|
|
||||||
Will become a plugin, saved for later use.
|
|
||||||
class Notification(config_forms.CfgForm):
|
|
||||||
title = _("Notification")
|
|
||||||
_security_choices = [(t,t) for t in [None,"SSL","TLS"]]
|
|
||||||
ntf_email = forms.EmailField(label=_("Email"), required=False)
|
|
||||||
ntf_server =forms.CharField(label= _("Server"), required=False)
|
|
||||||
ntf_username = forms.CharField(label= _("Username"), required=False)
|
|
||||||
ntf_password = forms.CharField(label= _("Password"), required=False)
|
|
||||||
ntf_security = forms.ChoiceField( label=_("Security"), choices = _security_choices )
|
|
||||||
|
|
||||||
config_page.register('deluge','notification', Notification)
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Plugins(forms.Form):
|
|
||||||
title = _("Enabled Plugins")
|
|
||||||
|
|
||||||
enabled_plugins = forms.LazyMultipleChoice(
|
|
||||||
choices_getter = lambda: [(p,p) for p in sclient.get_available_plugins()]
|
|
||||||
)
|
|
||||||
|
|
||||||
def initial_data(self):
|
|
||||||
return {'enabled_plugins':sclient.get_enabled_plugins()}
|
|
||||||
|
|
||||||
def save(self, data):
|
|
||||||
new_plugins = data.enabled_plugins
|
|
||||||
old_plugins = sclient.get_enabled_plugins()
|
|
||||||
|
|
||||||
enable = [p for p in new_plugins if p not in old_plugins]
|
|
||||||
disable = [p for p in old_plugins if p not in new_plugins]
|
|
||||||
|
|
||||||
|
|
||||||
plugin_manager = component.get("WebPluginManager")
|
|
||||||
for p in enable:
|
|
||||||
sclient.enable_plugin(p)
|
|
||||||
plugin_manager.enable_plugin(p)
|
|
||||||
|
|
||||||
for p in disable:
|
|
||||||
sclient.disable_plugin(p)
|
|
||||||
plugin_manager.disable_plugin(p)
|
|
||||||
|
|
||||||
config_page.register('deluge','plugins', Plugins)
|
|
||||||
|
|
||||||
@ -1,112 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# deluge_webserver.py
|
|
||||||
#
|
|
||||||
# Copyright (C) Martijn Voncken 2008 <mvoncken@gmail.com>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 3, or (at your option)
|
|
||||||
# any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, write to:
|
|
||||||
# The Free Software Foundation, Inc.,
|
|
||||||
# 51 Franklin Street, Fifth Floor
|
|
||||||
# Boston, MA 02110-1301, USA.
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
from deluge.ui.client import sclient as proxy
|
|
||||||
from deluge.log import LOG as log
|
|
||||||
|
|
||||||
|
|
||||||
import utils
|
|
||||||
import lib.newforms_plus as forms
|
|
||||||
import config_forms
|
|
||||||
from deluge import component
|
|
||||||
from render import render
|
|
||||||
|
|
||||||
config_page = component.get("ConfigPageManager")
|
|
||||||
plugins = component.get("WebPluginManager")
|
|
||||||
|
|
||||||
|
|
||||||
class Template(config_forms.WebCfgForm):
|
|
||||||
title = _("Template")
|
|
||||||
|
|
||||||
_templates = [(t,t) for t in render.get_templates()]
|
|
||||||
_button_choices = enumerate([_('Text and image'), _('Image Only')
|
|
||||||
, _('Text Only')])
|
|
||||||
|
|
||||||
template = forms.ChoiceField( label=_("Template"), choices = _templates)
|
|
||||||
button_style = forms.IntChoiceField(_("Button style"),_button_choices)
|
|
||||||
refresh_secs = forms.IntegerField(label= _("Auto refresh (seconds)"), min_value=2, max_value=60*60)
|
|
||||||
cache_templates = forms.CheckBox(_("Cache templates"))
|
|
||||||
|
|
||||||
def post_save(self):
|
|
||||||
from render import render
|
|
||||||
render.apply_cfg()
|
|
||||||
|
|
||||||
class Server(config_forms.WebCfgForm):
|
|
||||||
title = _("Server")
|
|
||||||
info = _("Manually restart webui to apply changes.")
|
|
||||||
|
|
||||||
port = forms.IntegerField(label = _("Port"),min_value=80)
|
|
||||||
https = forms.CheckBox(_("Https"))
|
|
||||||
|
|
||||||
def validate(self, data):
|
|
||||||
import os
|
|
||||||
from deluge.common import get_default_config_dir
|
|
||||||
|
|
||||||
if data.https:
|
|
||||||
cert_path = os.path.join(get_default_config_dir("ssl") ,"deluge.cert.pem" )
|
|
||||||
if not os.path.exists (cert_path):
|
|
||||||
raise forms.ValidationError(_("Certificate not found at '%s'" % cert_path))
|
|
||||||
key_path = os.path.join(get_default_config_dir("ssl") ,"deluge.key.pem" )
|
|
||||||
if not os.path.exists (key_path):
|
|
||||||
raise forms.ValidationError(_("Key not found at '%s'" % key_path))
|
|
||||||
|
|
||||||
|
|
||||||
def post_save(self):
|
|
||||||
pass
|
|
||||||
#raise forms.ValidationError(
|
|
||||||
# )
|
|
||||||
|
|
||||||
class Password(forms.Form):
|
|
||||||
title = _("Password")
|
|
||||||
|
|
||||||
old_pwd = forms.Password(_("Current Password"))
|
|
||||||
new1 = forms.Password(_("New Password"))
|
|
||||||
new2 = forms.Password(_("New Password (Confirm)"))
|
|
||||||
|
|
||||||
def save(self,data):
|
|
||||||
utils.update_pwd(data.new1)
|
|
||||||
|
|
||||||
def validate(self, data):
|
|
||||||
if not utils.check_pwd(data.old_pwd):
|
|
||||||
raise forms.ValidationError(_("Old password is invalid"))
|
|
||||||
if data.new1 <> data.new2:
|
|
||||||
raise forms.ValidationError(
|
|
||||||
_("New Password is not equal to New Password(confirm)"))
|
|
||||||
|
|
||||||
def post_save(self):
|
|
||||||
utils.end_session()
|
|
||||||
#raise forms.ValidationError(_("Password changed,please login again"))
|
|
||||||
|
|
||||||
class Sidebar(config_forms.WebCfgForm):
|
|
||||||
title = _("Sidebar")
|
|
||||||
show_sidebar = forms.CheckBox(_("Show sidebar"))
|
|
||||||
sidebar_show_zero = forms.CheckBox(_("Show zero hits"))
|
|
||||||
sidebar_show_trackers = forms.CheckBox(_("Show trackers"))
|
|
||||||
show_keyword_search = forms.CheckBox(_("Show keyword search"))
|
|
||||||
|
|
||||||
config_page.register('webui','template', Template)
|
|
||||||
config_page.register('webui','server',Server)
|
|
||||||
config_page.register('webui','password',Password)
|
|
||||||
config_page.register('webui','sidebar',Sidebar)
|
|
||||||
@ -1,411 +0,0 @@
|
|||||||
"""
|
|
||||||
adapted for deluge-webui:
|
|
||||||
-edit-box with traceback for cut+paste.
|
|
||||||
-pretty errors for well known exceptions.
|
|
||||||
|
|
||||||
|
|
||||||
debugerror.py :
|
|
||||||
This is adapted from Django <djangoproject.com>
|
|
||||||
with modifications Copyright (C) Martijn Voncken 2008 <mvoncken@gmail.com>
|
|
||||||
Copyright (c) 2005, the Lawrence Journal-World
|
|
||||||
Used under the modified BSD license:
|
|
||||||
http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
|
|
||||||
"""
|
|
||||||
__all__ = ["debugerror", "djangoerror"]
|
|
||||||
|
|
||||||
import utils
|
|
||||||
|
|
||||||
pretty_errors_str = {
|
|
||||||
"org.freedesktop.DBus.Error.ServiceUnknown":
|
|
||||||
""" Webui Lost the connection to deluge <br \>
|
|
||||||
Unable to reconnect, please restart deluge.
|
|
||||||
<!--<a href="/kill">click here to stop the webui</a>-->""",
|
|
||||||
"InvalidUniqueIDError:":
|
|
||||||
"""
|
|
||||||
this torrent was removed,
|
|
||||||
<a href="/home">click here to go to the torrent-list</a>
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pretty_errors_cls = {
|
|
||||||
type(utils.UnknownTorrentError):"""
|
|
||||||
this torrent was removed,
|
|
||||||
<a href="/home">click here to go to the torrent-list</a>
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
|
|
||||||
import sys, urlparse, pprint
|
|
||||||
from web import websafe
|
|
||||||
from web import template
|
|
||||||
import web #import lib.webpy022.webapi as web
|
|
||||||
import webserver_common as ws
|
|
||||||
from traceback import format_tb
|
|
||||||
from deluge import common
|
|
||||||
|
|
||||||
Template = template.Template
|
|
||||||
|
|
||||||
import os, os.path
|
|
||||||
whereami = os.path.join(os.getcwd(), __file__)
|
|
||||||
whereami = os.path.sep.join(whereami.split(os.path.sep)[:-1])
|
|
||||||
djangoerror_t = """\
|
|
||||||
$def with (exception_type, exception_value, frames, exception_message, version_info, tback_txt)
|
|
||||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
|
||||||
<meta name="robots" content="NONE,NOARCHIVE" />
|
|
||||||
<title>$exception_type at $ctx.path</title>
|
|
||||||
<style type="text/css">
|
|
||||||
html * { padding:0; margin:0; }
|
|
||||||
body * { padding:10px 20px; }
|
|
||||||
body * * { padding:0; }
|
|
||||||
body { font:small sans-serif; }
|
|
||||||
body>div { border-bottom:1px solid #ddd; }
|
|
||||||
h1 { font-weight:normal; }
|
|
||||||
h2 { margin-bottom:.8em; }
|
|
||||||
h2 span { font-size:80%; color:#666; font-weight:normal; }
|
|
||||||
h3 { margin:1em 0 .5em 0; }
|
|
||||||
h4 { margin:0 0 .5em 0; font-weight: normal; }
|
|
||||||
table {
|
|
||||||
border:1px solid #ccc; border-collapse: collapse; background:white; }
|
|
||||||
tbody td, tbody th { vertical-align:top; padding:2px 3px; }
|
|
||||||
thead th {
|
|
||||||
padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
|
|
||||||
font-weight:normal; font-size:11px; border:1px solid #ddd; }
|
|
||||||
tbody th { text-align:right; color:#666; padding-right:.5em; }
|
|
||||||
table.vars { margin:5px 0 2px 40px; }
|
|
||||||
table.vars td, table.req td { font-family:monospace; }
|
|
||||||
table td.code { width:100%;}
|
|
||||||
table td.code div { overflow:hidden; }
|
|
||||||
table.source th { color:#666; }
|
|
||||||
table.source td {
|
|
||||||
font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
|
|
||||||
ul.traceback { list-style-type:none; }
|
|
||||||
ul.traceback li.frame { margin-bottom:1em; }
|
|
||||||
div.context { margin: 10px 0; }
|
|
||||||
div.context ol {
|
|
||||||
padding-left:30px; margin:0 10px; list-style-position: inside; }
|
|
||||||
div.context ol li {
|
|
||||||
font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
|
|
||||||
div.context ol.context-line li { color:black; background-color:#ccc; }
|
|
||||||
div.context ol.context-line li span { float: right; }
|
|
||||||
div.commands { margin-left: 40px; }
|
|
||||||
div.commands a { color:black; text-decoration:none; }
|
|
||||||
#summary { background: #ffc; }
|
|
||||||
#summary h2 { font-weight: normal; color: #666; }
|
|
||||||
#explanation { background:#eee; }
|
|
||||||
#template, #template-not-exist { background:#f6f6f6; }
|
|
||||||
#template-not-exist ul { margin: 0 0 0 20px; }
|
|
||||||
#traceback { background:#eee; }
|
|
||||||
#requestinfo { background:#f6f6f6; padding-left:120px; }
|
|
||||||
#summary table { border:none; background:transparent; }
|
|
||||||
#requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
|
|
||||||
#requestinfo h3 { margin-bottom:-1em; }
|
|
||||||
.error { background: #ffc; }
|
|
||||||
.specific { color:#cc3300; font-weight:bold; }
|
|
||||||
</style>
|
|
||||||
<script type="text/javascript">
|
|
||||||
//<!--
|
|
||||||
function getElementsByClassName(oElm, strTagName, strClassName){
|
|
||||||
// Written by Jonathan Snook, http://www.snook.ca/jon;
|
|
||||||
// Add-ons by Robert Nyman, http://www.robertnyman.com
|
|
||||||
var arrElements = (strTagName == "*" && document.all)? document.all :
|
|
||||||
oElm.getElementsByTagName(strTagName);
|
|
||||||
var arrReturnElements = new Array();
|
|
||||||
strClassName = strClassName.replace(/\-/g, "\\-");
|
|
||||||
var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
|
|
||||||
var oElement;
|
|
||||||
for(var i=0; i<arrElements.length; i++){
|
|
||||||
oElement = arrElements[i];
|
|
||||||
if(oRegExp.test(oElement.className)){
|
|
||||||
arrReturnElements.push(oElement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (arrReturnElements)
|
|
||||||
}
|
|
||||||
function hideAll(elems) {
|
|
||||||
for (var e = 0; e < elems.length; e++) {
|
|
||||||
elems[e].style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
window.onload = function() {
|
|
||||||
hideAll(getElementsByClassName(document, 'table', 'vars'));
|
|
||||||
hideAll(getElementsByClassName(document, 'ol', 'pre-context'));
|
|
||||||
hideAll(getElementsByClassName(document, 'ol', 'post-context'));
|
|
||||||
}
|
|
||||||
function toggle() {
|
|
||||||
for (var i = 0; i < arguments.length; i++) {
|
|
||||||
var e = document.getElementById(arguments[i]);
|
|
||||||
if (e) {
|
|
||||||
e.style.display = e.style.display == 'none' ? 'block' : 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
function varToggle(link, id) {
|
|
||||||
toggle('v' + id);
|
|
||||||
var s = link.getElementsByTagName('span')[0];
|
|
||||||
var uarr = String.fromCharCode(0x25b6);
|
|
||||||
var darr = String.fromCharCode(0x25bc);
|
|
||||||
s.innerHTML = s.innerHTML == uarr ? darr : uarr;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
//-->
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<table width="100%"><tr><td>
|
|
||||||
<img src="/static/images/debugerror.png">
|
|
||||||
</td><td>
|
|
||||||
<p>
|
|
||||||
<!--ERROR-MARKER-->
|
|
||||||
<h1>Oops, Deluge Broke ...</h1>
|
|
||||||
You might have found a bug, or you did something really stupid ;-).
|
|
||||||
<br />If the error persists :<br />
|
|
||||||
Read the <a href="http://dev.deluge-torrent.org/wiki/Faq">Faq</a>.<br />
|
|
||||||
Try downloading the latest version at
|
|
||||||
<a href="http://deluge-torrent.org">deluge-torrent.org</a>
|
|
||||||
<br />Visit the <a href="http://forum.deluge-torrent.org">forum</a>
|
|
||||||
or the <a href="http://dev.deluge-torrent.org/query">buglist</a> for more info.
|
|
||||||
</p><br /><br /><br /><br /><br />
|
|
||||||
</td></tr></table>
|
|
||||||
<div id="summary">
|
|
||||||
<h1>$exception_type : $exception_value</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="summary">
|
|
||||||
Paste the contents of this text-box when you are asked for a traceback.<br>
|
|
||||||
Try to explain what you where doing,
|
|
||||||
and how you could work around the problem.<br>
|
|
||||||
Don't paste without context and expect us to know what went wrong.
|
|
||||||
<br>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
<form action="http://pastebin.ca/index.php" method=POST>
|
|
||||||
|
|
||||||
option to paste to a pastebin disabled, need to find out about pastebin etiquette first.
|
|
||||||
Or ask markybob to make a deluge pastebin.
|
|
||||||
--->
|
|
||||||
<textarea cols=80 rows=20 name="content">
|
|
||||||
--Deluge Error--
|
|
||||||
$exception_type : $exception_value
|
|
||||||
path : $ctx.path
|
|
||||||
file : $frames[0].filename in $frames[0].function, line $frames[0].lineno
|
|
||||||
|
|
||||||
--Input--
|
|
||||||
$web.input()
|
|
||||||
|
|
||||||
--Versions--
|
|
||||||
$version_info
|
|
||||||
|
|
||||||
--Traceback--
|
|
||||||
$tback_txt
|
|
||||||
|
|
||||||
|
|
||||||
</textarea><br />
|
|
||||||
<font color=red>Use a <a href="http://pastebin.ca/">pastebin</a> on IRC!</font><br>
|
|
||||||
|
|
||||||
|
|
||||||
<!--
|
|
||||||
<input type=submit name="Submit" value="Click here to paste to http://pastebin.ca">
|
|
||||||
-->
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div id="traceback">
|
|
||||||
<h2>Traceback <span>(innermost first)</span></h2>
|
|
||||||
<ul class="traceback">
|
|
||||||
$for frame in frames:
|
|
||||||
<li class="frame">
|
|
||||||
<code>$frame.filename</code> in <code>$frame.function</code>
|
|
||||||
$if frame.context_line:
|
|
||||||
<div class="context" id="c$frame.id">
|
|
||||||
$if frame.pre_context:
|
|
||||||
<ol start="$frame.pre_context_lineno" class="pre-context" id="pre$frame.id">
|
|
||||||
$for line in frame.pre_context:
|
|
||||||
<li onclick="toggle('pre$frame.id', 'post$frame.id')">$line</li>
|
|
||||||
</ol>
|
|
||||||
<ol start="$frame.lineno" class="context-line"><li onclick="toggle('pre$frame.id', 'post$frame.id')">$frame.context_line <span>...</span></li></ol>
|
|
||||||
$if frame.post_context:
|
|
||||||
<ol start='${frame.lineno + 1}' class="post-context" id="post$frame.id">
|
|
||||||
$for line in frame.post_context:
|
|
||||||
<li onclick="toggle('pre$frame.id', 'post$frame.id')">$line</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
$if frame.vars:
|
|
||||||
<div class="commands">
|
|
||||||
<a href='#' onclick="return varToggle(this, '$frame.id')"><span>▶</span> Local vars</a>
|
|
||||||
$# $inspect.formatargvalues(*inspect.getargvalues(frame['tb'].tb_frame))
|
|
||||||
</div>
|
|
||||||
$:dicttable(frame.vars, kls='vars', id=('v' + str(frame.id)))
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="requestinfo">
|
|
||||||
$if ctx.output or ctx.headers:
|
|
||||||
<h2>Response so far</h2>
|
|
||||||
<h3>HEADERS</h3>
|
|
||||||
<p class="req"><code>
|
|
||||||
$for kv in ctx.headers:
|
|
||||||
$kv[0]: $kv[1]<br />
|
|
||||||
$else:
|
|
||||||
[no headers]
|
|
||||||
</code></p>
|
|
||||||
|
|
||||||
<h3>BODY</h3>
|
|
||||||
<p class="req" style="padding-bottom: 2em"><code>
|
|
||||||
$ctx.output
|
|
||||||
</code></p>
|
|
||||||
|
|
||||||
<h2>Request information</h2>
|
|
||||||
|
|
||||||
<h3>INPUT</h3>
|
|
||||||
$:dicttable(web.input())
|
|
||||||
|
|
||||||
<h3 id="cookie-info">COOKIES</h3>
|
|
||||||
$:dicttable(web.cookies())
|
|
||||||
|
|
||||||
<h3 id="meta-info">META</h3>
|
|
||||||
$ newctx = []
|
|
||||||
$# ) and (k not in ['env', 'output', 'headers', 'environ', 'status', 'db_execute']):
|
|
||||||
$for k, v in ctx.iteritems():
|
|
||||||
$if not k.startswith('_') and (k in x):
|
|
||||||
$newctx.append(kv)
|
|
||||||
$:dicttable(dict(newctx))
|
|
||||||
|
|
||||||
<h3 id="meta-info">ENVIRONMENT</h3>
|
|
||||||
$:dicttable(ctx.env)
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
dicttable_t = r"""$def with (d, kls='req', id=None)
|
|
||||||
$if d:
|
|
||||||
<table class="$kls"\
|
|
||||||
$if id: id="$id"\
|
|
||||||
><thead><tr><th>Variable</th><th>Value</th></tr></thead>
|
|
||||||
<tbody>
|
|
||||||
$ temp = d.items()
|
|
||||||
$temp.sort()
|
|
||||||
$for kv in temp:
|
|
||||||
<tr><td>$kv[0]</td><td class="code"><div>$prettify(kv[1])</div></td></tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
$else:
|
|
||||||
<p>No data.</p>
|
|
||||||
"""
|
|
||||||
|
|
||||||
dicttable_r = Template(dicttable_t, filter=websafe)
|
|
||||||
djangoerror_r = Template(djangoerror_t, filter=websafe)
|
|
||||||
|
|
||||||
def djangoerror():
|
|
||||||
def _get_lines_from_file(filename, lineno, context_lines):
|
|
||||||
"""
|
|
||||||
Returns context_lines before and after lineno from file.
|
|
||||||
Returns (pre_context_lineno, pre_context, context_line, post_context).
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
source = open(filename).readlines()
|
|
||||||
lower_bound = max(0, lineno - context_lines)
|
|
||||||
upper_bound = lineno + context_lines
|
|
||||||
|
|
||||||
pre_context = \
|
|
||||||
[line.strip('\n') for line in source[lower_bound:lineno]]
|
|
||||||
context_line = source[lineno].strip('\n')
|
|
||||||
post_context = \
|
|
||||||
[line.strip('\n') for line in source[lineno + 1:upper_bound]]
|
|
||||||
|
|
||||||
return lower_bound, pre_context, context_line, post_context
|
|
||||||
except (OSError, IOError):
|
|
||||||
return None, [], None, []
|
|
||||||
|
|
||||||
exception_type, exception_value, tback = sys.exc_info()
|
|
||||||
|
|
||||||
exception_message = 'Error'
|
|
||||||
try:
|
|
||||||
exception_message = exception_value.message
|
|
||||||
except AttributeError:
|
|
||||||
exception_message = 'no message'
|
|
||||||
exception_type = exception_type.__name__
|
|
||||||
|
|
||||||
"""
|
|
||||||
for err_str in pretty_errors:
|
|
||||||
if err_str in exception_message:
|
|
||||||
#from render import render
|
|
||||||
return render.error(pretty_errors[err_str])
|
|
||||||
"""
|
|
||||||
if exception_type in pretty_errors_cls:
|
|
||||||
from render import render
|
|
||||||
return render.error(pretty_errors_cls[exception_type])
|
|
||||||
|
|
||||||
version_info = "WebUi : %sr%s\nPython %s:" % ( common.get_version() ,common.get_revision(),sys.version)
|
|
||||||
|
|
||||||
tback_txt = ''.join(format_tb(tback))
|
|
||||||
|
|
||||||
|
|
||||||
frames = []
|
|
||||||
while tback is not None:
|
|
||||||
filename = tback.tb_frame.f_code.co_filename
|
|
||||||
function = tback.tb_frame.f_code.co_name
|
|
||||||
lineno = tback.tb_lineno - 1
|
|
||||||
pre_context_lineno, pre_context, context_line, post_context = \
|
|
||||||
_get_lines_from_file(filename, lineno, 7)
|
|
||||||
frames.append(web.storage({
|
|
||||||
'tback': tback,
|
|
||||||
'filename': filename,
|
|
||||||
'function': function,
|
|
||||||
'lineno': lineno,
|
|
||||||
'vars': tback.tb_frame.f_locals,
|
|
||||||
'id': id(tback),
|
|
||||||
'pre_context': pre_context,
|
|
||||||
'context_line': context_line,
|
|
||||||
'post_context': post_context,
|
|
||||||
'pre_context_lineno': pre_context_lineno,
|
|
||||||
}))
|
|
||||||
tback = tback.tb_next
|
|
||||||
frames.reverse()
|
|
||||||
urljoin = urlparse.urljoin
|
|
||||||
def prettify(x):
|
|
||||||
try:
|
|
||||||
out = pprint.pformat(x)
|
|
||||||
except Exception, e:
|
|
||||||
out = '[could not display: <' + e.__class__.__name__ + \
|
|
||||||
': '+str(e)+'>]'
|
|
||||||
return out
|
|
||||||
dt = dicttable_r
|
|
||||||
dt.globals = {'prettify': prettify}
|
|
||||||
t = djangoerror_r
|
|
||||||
t.globals = {'ctx': web.ctx, 'web':web, 'dicttable':dt, 'dict':dict, 'str':str}
|
|
||||||
return t(exception_type, exception_value, frames, exception_message, version_info, tback_txt)
|
|
||||||
|
|
||||||
def deluge_debugerror():
|
|
||||||
"""
|
|
||||||
A replacement for `internalerror` that presents a nice page with lots
|
|
||||||
of debug information for the programmer.
|
|
||||||
|
|
||||||
(Based on the beautiful 500 page from [Django](http://djangoproject.com/),
|
|
||||||
designed by [Wilson Miner](http://wilsonminer.com/).)
|
|
||||||
"""
|
|
||||||
web.ctx.headers = [
|
|
||||||
('Content-Type', 'text/html')
|
|
||||||
]
|
|
||||||
web.ctx.output = djangoerror()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
urls = (
|
|
||||||
'/', 'index'
|
|
||||||
)
|
|
||||||
|
|
||||||
class index:
|
|
||||||
def GET(self):
|
|
||||||
thisdoesnotexist
|
|
||||||
|
|
||||||
web.internalerror = web.debugerror
|
|
||||||
web.run(urls)
|
|
||||||
@ -1,135 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (C) Martijn Voncken 2007 <mvoncken@gmail.com>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 3, or (at your option)
|
|
||||||
# any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, write to:
|
|
||||||
# The Free Software Foundation, Inc.,
|
|
||||||
# 51 Franklin Street, Fifth Floor
|
|
||||||
# Boston, MA 02110-1301, USA.
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
import web
|
|
||||||
import random
|
|
||||||
import gettext
|
|
||||||
import locale
|
|
||||||
import deluge.common
|
|
||||||
from deluge.configmanager import ConfigManager
|
|
||||||
import pkg_resources
|
|
||||||
from deluge.ui.client import sclient
|
|
||||||
import components
|
|
||||||
from deluge.log import LOG as log
|
|
||||||
from webserver_common import CONFIG_DEFAULTS
|
|
||||||
|
|
||||||
config = ConfigManager("webui06.conf", CONFIG_DEFAULTS)
|
|
||||||
|
|
||||||
# Initialize gettext
|
|
||||||
try:
|
|
||||||
locale.setlocale(locale.LC_ALL, '')
|
|
||||||
if hasattr(locale, "bindtextdomain"):
|
|
||||||
locale.bindtextdomain("deluge", pkg_resources.resource_filename("deluge", "i18n"))
|
|
||||||
if hasattr(locale, "textdomain"):
|
|
||||||
locale.textdomain("deluge")
|
|
||||||
gettext.bindtextdomain("deluge", pkg_resources.resource_filename("deluge", "i18n"))
|
|
||||||
gettext.textdomain("deluge")
|
|
||||||
gettext.install("deluge", pkg_resources.resource_filename("deluge", "i18n"))
|
|
||||||
except Exception, e:
|
|
||||||
log.error("Unable to initialize gettext/locale: %s", e)
|
|
||||||
|
|
||||||
components.register() #after gettext!!
|
|
||||||
|
|
||||||
from debugerror import deluge_debugerror
|
|
||||||
from render import render
|
|
||||||
import utils
|
|
||||||
|
|
||||||
|
|
||||||
## Init ##
|
|
||||||
random.seed()
|
|
||||||
web.webapi.internalerror = deluge_debugerror
|
|
||||||
|
|
||||||
#self registering pages etc.
|
|
||||||
import pages
|
|
||||||
import config_tabs_webui #auto registers in ConfigUiManager
|
|
||||||
import config_tabs_deluge #auto registers in ConfigUiManager
|
|
||||||
import register_menu #auto registers.
|
|
||||||
#manual register:
|
|
||||||
import torrent_add
|
|
||||||
torrent_add.register()
|
|
||||||
import torrent_options
|
|
||||||
torrent_options.register()
|
|
||||||
import torrent_move
|
|
||||||
torrent_move.register()
|
|
||||||
import config_forms
|
|
||||||
config_forms.register()
|
|
||||||
import json_api
|
|
||||||
json_api.register()
|
|
||||||
#/self registering pages.
|
|
||||||
|
|
||||||
|
|
||||||
def WsgiApplication(middleware = None):
|
|
||||||
from web import webpyfunc, wsgifunc
|
|
||||||
from deluge import component
|
|
||||||
|
|
||||||
pagemanager = component.get("PageManager")
|
|
||||||
if not middleware:
|
|
||||||
middleware = []
|
|
||||||
|
|
||||||
return wsgifunc(webpyfunc(pagemanager.urls, pagemanager.page_classes, False), *middleware)
|
|
||||||
|
|
||||||
def create_webserver(debug = False, base_url =None):
|
|
||||||
"starts builtin webserver"
|
|
||||||
import web
|
|
||||||
|
|
||||||
utils.set_config_defaults()
|
|
||||||
if base_url:
|
|
||||||
config['base'] = base_url
|
|
||||||
else:
|
|
||||||
config['base'] = ''
|
|
||||||
config['disallow'] = {}
|
|
||||||
utils.apply_config()
|
|
||||||
|
|
||||||
|
|
||||||
from lib.webpy022.wsgiserver import CherryPyWSGIServer
|
|
||||||
|
|
||||||
middleware = None
|
|
||||||
if debug:
|
|
||||||
middleware = [web.reloader]
|
|
||||||
|
|
||||||
wsgi_app = WsgiApplication(middleware)
|
|
||||||
|
|
||||||
server_address=("0.0.0.0", int(config['port']))
|
|
||||||
server = CherryPyWSGIServer(server_address, wsgi_app, server_name="localhost")
|
|
||||||
|
|
||||||
https = False
|
|
||||||
if config["https"]:
|
|
||||||
import os
|
|
||||||
from deluge.common import get_default_config_dir
|
|
||||||
cert_path = os.path.join(get_default_config_dir("ssl") ,"deluge.cert.pem" )
|
|
||||||
key_path = os.path.join(get_default_config_dir("ssl") ,"deluge.key.pem" )
|
|
||||||
if os.path.exists (key_path) and os.path.exists (cert_path):
|
|
||||||
server.ssl_certificate = cert_path
|
|
||||||
server.ssl_private_key = key_path
|
|
||||||
https = True
|
|
||||||
|
|
||||||
if https:
|
|
||||||
log.info("https://%s:%d/" % server_address)
|
|
||||||
else:
|
|
||||||
log.info("http://%s:%d/" % server_address)
|
|
||||||
return server
|
|
||||||
|
|
||||||
def run(debug = False, base_url = ""):
|
|
||||||
server = create_webserver(debug, base_url)
|
|
||||||
try:
|
|
||||||
server.start()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
server.stop()
|
|
||||||
@ -1,257 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# webserver_framework.py
|
|
||||||
#
|
|
||||||
# Copyright (C) Martijn Voncken 2008 <mvoncken@gmail.com>
|
|
||||||
# Copyright (C) Damien Churchill 2008 <damoxc@gmail.com>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 3, or (at your option)
|
|
||||||
# any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, write to:
|
|
||||||
# The Free Software Foundation, Inc.,
|
|
||||||
# 51 Franklin Street, Fifth Floor
|
|
||||||
# Boston, MA 02110-1301, USA.
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
|
||||||
json api.
|
|
||||||
|
|
||||||
design:
|
|
||||||
== Full client api ==
|
|
||||||
* url : /json/rpc/
|
|
||||||
* rpc-api : http://en.wikipedia.org/wiki/JSON-RPC
|
|
||||||
* methods : http://dev.deluge-torrent.org/wiki/Development/UiClient#Remoteapi
|
|
||||||
|
|
||||||
"""
|
|
||||||
from traceback import format_exc
|
|
||||||
import web
|
|
||||||
from web import webapi
|
|
||||||
import page_decorators as deco
|
|
||||||
from web import cookies, setcookie as w_setcookie
|
|
||||||
import utils
|
|
||||||
from render import render
|
|
||||||
from utils import dict_cb
|
|
||||||
|
|
||||||
try:
|
|
||||||
from json import dumps, loads
|
|
||||||
except ImportError:
|
|
||||||
from lib.json import write as dumps, read as loads
|
|
||||||
|
|
||||||
from deluge.ui import common
|
|
||||||
from deluge.ui.client import sclient,aclient
|
|
||||||
from deluge.log import LOG as log
|
|
||||||
from deluge import component
|
|
||||||
|
|
||||||
def json_response(result, id):
|
|
||||||
print dumps({
|
|
||||||
"version":"1.1",
|
|
||||||
"result":result,
|
|
||||||
"id":id
|
|
||||||
})
|
|
||||||
|
|
||||||
def json_error(message , id=-1, msg_number=123):
|
|
||||||
log.error("JSON-error:%s" % message)
|
|
||||||
print dumps({
|
|
||||||
"version":"1.1",
|
|
||||||
"id":id,
|
|
||||||
"error":{
|
|
||||||
"number":msg_number,
|
|
||||||
"message":message,
|
|
||||||
"error":message
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
class json_rpc:
|
|
||||||
"""
|
|
||||||
== Full client api ==
|
|
||||||
* url : /json/rpc
|
|
||||||
* rpc-api : http://en.wikipedia.org/wiki/JSON-RPC#Version_1.0
|
|
||||||
* methods : http://dev.deluge-torrent.org/wiki/Development/UiClient#Remoteapi
|
|
||||||
"""
|
|
||||||
#extra exposed methods
|
|
||||||
json_exposed = ["update_ui","system_listMethods", "download_torrent_from_url",
|
|
||||||
"get_webui_config","set_webui_config","get_webui_templates", "get_torrent_info",
|
|
||||||
"add_torrents"]
|
|
||||||
cache = {}
|
|
||||||
|
|
||||||
def GET(self):
|
|
||||||
return json_error("only POST is supported")
|
|
||||||
|
|
||||||
def POST(self , name=None):
|
|
||||||
web.header("Content-Type", "application/x-json")
|
|
||||||
ck = cookies()
|
|
||||||
id = 0
|
|
||||||
if not(ck.has_key("session_id") and ck["session_id"] in utils.config["sessions"]):
|
|
||||||
return json_error("not authenticated", id)
|
|
||||||
|
|
||||||
try:
|
|
||||||
log.debug("json-data:")
|
|
||||||
log.debug(webapi.data())
|
|
||||||
json_data = loads(webapi.data())
|
|
||||||
id = json_data["id"]
|
|
||||||
method = json_data["method"].replace(".", "_")
|
|
||||||
params = json_data["params"]
|
|
||||||
|
|
||||||
if method.startswith('_'):
|
|
||||||
raise Exception('_ methods are illegal.')
|
|
||||||
|
|
||||||
elif method in self.json_exposed:
|
|
||||||
func = getattr(self, method)
|
|
||||||
result = func(*params)
|
|
||||||
else:
|
|
||||||
result = self.exec_client_method(method, params)
|
|
||||||
|
|
||||||
#log.debug("JSON-result:%s(%s)[%s] = %s" % (method, params, id, result))
|
|
||||||
|
|
||||||
return json_response(result, id)
|
|
||||||
|
|
||||||
except Exception,e:
|
|
||||||
#verbose because you don't want exeptions in the error-handler.
|
|
||||||
message = ""
|
|
||||||
if hasattr(e,"message"):
|
|
||||||
message = e.message
|
|
||||||
log.debug(format_exc())
|
|
||||||
return json_error("%s:%s" % (e, message), id)
|
|
||||||
|
|
||||||
def exec_client_method(self, method, params):
|
|
||||||
if not hasattr(sclient,method):
|
|
||||||
raise Exception('Unknown method:%s', method)
|
|
||||||
|
|
||||||
#Call:
|
|
||||||
func = getattr(sclient, method)
|
|
||||||
return func(*params)
|
|
||||||
|
|
||||||
#
|
|
||||||
#Extra exposed methods:
|
|
||||||
#
|
|
||||||
def system_listMethods(self):
|
|
||||||
"system.listMethods() see json/xmlrpc docs"
|
|
||||||
return sclient.list_methods() + self.json_exposed
|
|
||||||
|
|
||||||
|
|
||||||
def update_ui(self, keys ,filter_dict , cache_id = None ):
|
|
||||||
"""
|
|
||||||
Composite call.
|
|
||||||
Goal : limit the number of ajax calls
|
|
||||||
|
|
||||||
input:
|
|
||||||
keys: see get_torrents_status
|
|
||||||
filter_dict: see get_torrents_status
|
|
||||||
cache_id: # todo
|
|
||||||
returns:
|
|
||||||
{
|
|
||||||
"torrents": see get_torrent_status
|
|
||||||
"filters": see get_filter_tree
|
|
||||||
"stats": see get_stats
|
|
||||||
"cache_id":int # todo
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
filters = sclient.get_filter_tree()
|
|
||||||
return {
|
|
||||||
"torrents":sclient.get_torrents_status(filter_dict , keys),
|
|
||||||
"filters":filters,
|
|
||||||
"stats":sclient.get_stats(),
|
|
||||||
"cache_id":-1
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_webui_config(self):
|
|
||||||
return dict([x for x in utils.config.config.iteritems() if not x[0].startswith("pwd")])
|
|
||||||
|
|
||||||
def set_webui_config(self, data):
|
|
||||||
utils.validate_config(data)
|
|
||||||
if "pwd" in data:
|
|
||||||
utils.update_pwd(pwd)
|
|
||||||
del data["pwd"]
|
|
||||||
for key, value in data.iteritems():
|
|
||||||
utils.config[key] = value
|
|
||||||
utils.config.save()
|
|
||||||
utils.apply_config()
|
|
||||||
|
|
||||||
def get_webui_templates(self):
|
|
||||||
return render.get_templates()
|
|
||||||
|
|
||||||
def download_torrent_from_url(self, url):
|
|
||||||
"""
|
|
||||||
input:
|
|
||||||
url: the url of the torrent to download
|
|
||||||
|
|
||||||
returns:
|
|
||||||
filename: the temporary file name of the torrent file
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import urllib
|
|
||||||
import tempfile
|
|
||||||
tmp_file = os.path.join(tempfile.gettempdir(), url.split("/")[-1])
|
|
||||||
filename, headers = urllib.urlretrieve(url, tmp_file)
|
|
||||||
log.debug("filename: %s", filename)
|
|
||||||
return filename
|
|
||||||
|
|
||||||
def get_torrent_info(self, filename):
|
|
||||||
"""
|
|
||||||
Goal:
|
|
||||||
allow the webui to retrieve data about the torrent
|
|
||||||
|
|
||||||
input:
|
|
||||||
filename: the filename of the torrent to gather info about
|
|
||||||
|
|
||||||
returns:
|
|
||||||
{
|
|
||||||
"filename": the torrent file
|
|
||||||
"name": the torrent name
|
|
||||||
"size": the total size of the torrent
|
|
||||||
"files": the files the torrent contains
|
|
||||||
"info_hash" the torrents info_hash
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
return common.get_torrent_info(filename.strip())
|
|
||||||
|
|
||||||
def add_torrents(self, torrents):
|
|
||||||
"""
|
|
||||||
input:
|
|
||||||
torrents [{
|
|
||||||
path: the path of the torrent file,
|
|
||||||
options: the torrent options
|
|
||||||
}]
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
|
|
||||||
for torrent in torrents:
|
|
||||||
filename = os.path.basename(torrent['path'])
|
|
||||||
fdump = open(torrent['path'], 'r').read()
|
|
||||||
aclient.add_torrent_file_binary(filename, fdump, torrent['options'])
|
|
||||||
aclient.force_call()
|
|
||||||
|
|
||||||
class json_upload:
|
|
||||||
def GET(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@deco.check_session
|
|
||||||
def POST(self, name=None):
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import tempfile
|
|
||||||
vars = web.input(torrentFile = {})
|
|
||||||
vars.torrentFile
|
|
||||||
path = os.path.join(tempfile.gettempdir(), vars.torrentFile.filename)
|
|
||||||
tmp_file = open(path, 'w')
|
|
||||||
shutil.copyfileobj(vars.torrentFile.file, tmp_file)
|
|
||||||
tmp_file.close()
|
|
||||||
print path
|
|
||||||
|
|
||||||
|
|
||||||
def register():
|
|
||||||
component.get("PageManager").register_page("/json/rpc",json_rpc)
|
|
||||||
component.get("PageManager").register_page("/json/upload",json_upload)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
print "todo: tests"
|
|
||||||
@ -1,61 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
#(c) Martijn Voncken, mvoncken@gmail.com
|
|
||||||
#Same Licence as web.py 0.22
|
|
||||||
#
|
|
||||||
"""
|
|
||||||
static fileserving for web.py
|
|
||||||
serves 1 directory from a packed egg, using pkg_resourses
|
|
||||||
"""
|
|
||||||
import web
|
|
||||||
from web import url
|
|
||||||
|
|
||||||
import posixpath
|
|
||||||
import urlparse
|
|
||||||
import urllib
|
|
||||||
import mimetypes
|
|
||||||
import os
|
|
||||||
import datetime
|
|
||||||
import cgi
|
|
||||||
from StringIO import StringIO
|
|
||||||
mimetypes.init() # try to read system mime.types
|
|
||||||
import pkg_resources
|
|
||||||
|
|
||||||
class egg_handler:
|
|
||||||
"""
|
|
||||||
serves files directly from an egg
|
|
||||||
"""
|
|
||||||
resource = "label"
|
|
||||||
base_path = "data"
|
|
||||||
extensions_map = mimetypes.types_map
|
|
||||||
|
|
||||||
def GET(self, path):
|
|
||||||
path = os.path.join(self.base_path, path)
|
|
||||||
ctype = self.guess_type(path)
|
|
||||||
|
|
||||||
data = pkg_resources.resource_string(self.resource,path)
|
|
||||||
|
|
||||||
web.header("Content-type", ctype)
|
|
||||||
web.header("Cache-Control" , "public, must-revalidate, max-age=86400")
|
|
||||||
#web.lastmodified(datetime.datetime.fromtimestamp(fs.st_mtime))
|
|
||||||
print data
|
|
||||||
|
|
||||||
def guess_type(self, path):
|
|
||||||
base, ext = posixpath.splitext(path)
|
|
||||||
if ext in self.extensions_map:
|
|
||||||
return self.extensions_map[ext]
|
|
||||||
ext = ext.lower()
|
|
||||||
if ext in self.extensions_map:
|
|
||||||
return self.extensions_map[ext]
|
|
||||||
else:
|
|
||||||
return 'application/octet-stream'
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
#example:
|
|
||||||
class usr_static(egg_handler):
|
|
||||||
resource = "label"
|
|
||||||
base_path = "data"
|
|
||||||
|
|
||||||
urls = ('/relative/(.*)','static_handler',
|
|
||||||
'/(.*)','usr_static')
|
|
||||||
|
|
||||||
web.run(urls,globals())
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
#(c) Martijn Voncken, mvoncken@gmail.com
|
|
||||||
#Same Licence as web.py 0.22
|
|
||||||
"""
|
|
||||||
render object for web.py
|
|
||||||
renders from egg instead of directory.
|
|
||||||
"""
|
|
||||||
from web import template
|
|
||||||
import pkg_resources
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
class egg_render:
|
|
||||||
"""
|
|
||||||
templates directly from an egg
|
|
||||||
"""
|
|
||||||
def __init__(self, resource, base_path , cache=False):
|
|
||||||
self.resource = resource
|
|
||||||
self.base_path = base_path
|
|
||||||
self.cache = cache
|
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
filename = attr + ".html" #<--bug, not consistent with the web.py renderer, that renderer ignores extensions.
|
|
||||||
template_data = pkg_resources.resource_string(self.resource, os.path.join(self.base_path, filename))
|
|
||||||
c = template.Template(template_data, filename = filename)
|
|
||||||
if self.cache:
|
|
||||||
setattr(self, attr, c)
|
|
||||||
return c
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
#example:
|
|
||||||
pass
|
|
||||||
@ -1,815 +0,0 @@
|
|||||||
#mvoncken 2008: added complete LGPL to this file to ease distribution
|
|
||||||
import string
|
|
||||||
import types
|
|
||||||
|
|
||||||
## json.py implements a JSON (http://json.org) reader and writer.
|
|
||||||
## Copyright (C) 2005 Patrick D. Logan
|
|
||||||
## Contact mailto:patrickdlogan@stardecisions.com
|
|
||||||
##
|
|
||||||
## This library is free software; you can redistribute it and/or
|
|
||||||
## modify it under the terms of the GNU Lesser General Public
|
|
||||||
## License as published by the Free Software Foundation; either
|
|
||||||
## version 2.1 of the License, or (at your option) any later version.
|
|
||||||
##
|
|
||||||
## This library is distributed in the hope that it will be useful,
|
|
||||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
## Lesser General Public License for more details.
|
|
||||||
##
|
|
||||||
## You should have received a copy of the GNU Lesser General Public
|
|
||||||
## License along with this library; if not, write to the Free Software
|
|
||||||
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
"""
|
|
||||||
GNU LESSER GENERAL PUBLIC LICENSE
|
|
||||||
Version 2.1, February 1999
|
|
||||||
|
|
||||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
|
||||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
|
||||||
of this license document, but changing it is not allowed.
|
|
||||||
|
|
||||||
[This is the first released version of the Lesser GPL. It also counts
|
|
||||||
as the successor of the GNU Library Public License, version 2, hence
|
|
||||||
the version number 2.1.]
|
|
||||||
|
|
||||||
Preamble
|
|
||||||
|
|
||||||
The licenses for most software are designed to take away your
|
|
||||||
freedom to share and change it. By contrast, the GNU General Public
|
|
||||||
Licenses are intended to guarantee your freedom to share and change
|
|
||||||
free software--to make sure the software is free for all its users.
|
|
||||||
|
|
||||||
This license, the Lesser General Public License, applies to some
|
|
||||||
specially designated software packages--typically libraries--of the
|
|
||||||
Free Software Foundation and other authors who decide to use it. You
|
|
||||||
can use it too, but we suggest you first think carefully about whether
|
|
||||||
this license or the ordinary General Public License is the better
|
|
||||||
strategy to use in any particular case, based on the explanations below.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom of use,
|
|
||||||
not price. Our General Public Licenses are designed to make sure that
|
|
||||||
you have the freedom to distribute copies of free software (and charge
|
|
||||||
for this service if you wish); that you receive source code or can get
|
|
||||||
it if you want it; that you can change the software and use pieces of
|
|
||||||
it in new free programs; and that you are informed that you can do
|
|
||||||
these things.
|
|
||||||
|
|
||||||
To protect your rights, we need to make restrictions that forbid
|
|
||||||
distributors to deny you these rights or to ask you to surrender these
|
|
||||||
rights. These restrictions translate to certain responsibilities for
|
|
||||||
you if you distribute copies of the library or if you modify it.
|
|
||||||
|
|
||||||
For example, if you distribute copies of the library, whether gratis
|
|
||||||
or for a fee, you must give the recipients all the rights that we gave
|
|
||||||
you. You must make sure that they, too, receive or can get the source
|
|
||||||
code. If you link other code with the library, you must provide
|
|
||||||
complete object files to the recipients, so that they can relink them
|
|
||||||
with the library after making changes to the library and recompiling
|
|
||||||
it. And you must show them these terms so they know their rights.
|
|
||||||
|
|
||||||
We protect your rights with a two-step method: (1) we copyright the
|
|
||||||
library, and (2) we offer you this license, which gives you legal
|
|
||||||
permission to copy, distribute and/or modify the library.
|
|
||||||
|
|
||||||
To protect each distributor, we want to make it very clear that
|
|
||||||
there is no warranty for the free library. Also, if the library is
|
|
||||||
modified by someone else and passed on, the recipients should know
|
|
||||||
that what they have is not the original version, so that the original
|
|
||||||
author's reputation will not be affected by problems that might be
|
|
||||||
introduced by others.
|
|
||||||
|
|
||||||
Finally, software patents pose a constant threat to the existence of
|
|
||||||
any free program. We wish to make sure that a company cannot
|
|
||||||
effectively restrict the users of a free program by obtaining a
|
|
||||||
restrictive license from a patent holder. Therefore, we insist that
|
|
||||||
any patent license obtained for a version of the library must be
|
|
||||||
consistent with the full freedom of use specified in this license.
|
|
||||||
|
|
||||||
Most GNU software, including some libraries, is covered by the
|
|
||||||
ordinary GNU General Public License. This license, the GNU Lesser
|
|
||||||
General Public License, applies to certain designated libraries, and
|
|
||||||
is quite different from the ordinary General Public License. We use
|
|
||||||
this license for certain libraries in order to permit linking those
|
|
||||||
libraries into non-free programs.
|
|
||||||
|
|
||||||
When a program is linked with a library, whether statically or using
|
|
||||||
a shared library, the combination of the two is legally speaking a
|
|
||||||
combined work, a derivative of the original library. The ordinary
|
|
||||||
General Public License therefore permits such linking only if the
|
|
||||||
entire combination fits its criteria of freedom. The Lesser General
|
|
||||||
Public License permits more lax criteria for linking other code with
|
|
||||||
the library.
|
|
||||||
|
|
||||||
We call this license the "Lesser" General Public License because it
|
|
||||||
does Less to protect the user's freedom than the ordinary General
|
|
||||||
Public License. It also provides other free software developers Less
|
|
||||||
of an advantage over competing non-free programs. These disadvantages
|
|
||||||
are the reason we use the ordinary General Public License for many
|
|
||||||
libraries. However, the Lesser license provides advantages in certain
|
|
||||||
special circumstances.
|
|
||||||
|
|
||||||
For example, on rare occasions, there may be a special need to
|
|
||||||
encourage the widest possible use of a certain library, so that it becomes
|
|
||||||
a de-facto standard. To achieve this, non-free programs must be
|
|
||||||
allowed to use the library. A more frequent case is that a free
|
|
||||||
library does the same job as widely used non-free libraries. In this
|
|
||||||
case, there is little to gain by limiting the free library to free
|
|
||||||
software only, so we use the Lesser General Public License.
|
|
||||||
|
|
||||||
In other cases, permission to use a particular library in non-free
|
|
||||||
programs enables a greater number of people to use a large body of
|
|
||||||
free software. For example, permission to use the GNU C Library in
|
|
||||||
non-free programs enables many more people to use the whole GNU
|
|
||||||
operating system, as well as its variant, the GNU/Linux operating
|
|
||||||
system.
|
|
||||||
|
|
||||||
Although the Lesser General Public License is Less protective of the
|
|
||||||
users' freedom, it does ensure that the user of a program that is
|
|
||||||
linked with the Library has the freedom and the wherewithal to run
|
|
||||||
that program using a modified version of the Library.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
|
||||||
modification follow. Pay close attention to the difference between a
|
|
||||||
"work based on the library" and a "work that uses the library". The
|
|
||||||
former contains code derived from the library, whereas the latter must
|
|
||||||
be combined with the library in order to run.
|
|
||||||
|
|
||||||
GNU LESSER GENERAL PUBLIC LICENSE
|
|
||||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
||||||
|
|
||||||
0. This License Agreement applies to any software library or other
|
|
||||||
program which contains a notice placed by the copyright holder or
|
|
||||||
other authorized party saying it may be distributed under the terms of
|
|
||||||
this Lesser General Public License (also called "this License").
|
|
||||||
Each licensee is addressed as "you".
|
|
||||||
|
|
||||||
A "library" means a collection of software functions and/or data
|
|
||||||
prepared so as to be conveniently linked with application programs
|
|
||||||
(which use some of those functions and data) to form executables.
|
|
||||||
|
|
||||||
The "Library", below, refers to any such software library or work
|
|
||||||
which has been distributed under these terms. A "work based on the
|
|
||||||
Library" means either the Library or any derivative work under
|
|
||||||
copyright law: that is to say, a work containing the Library or a
|
|
||||||
portion of it, either verbatim or with modifications and/or translated
|
|
||||||
straightforwardly into another language. (Hereinafter, translation is
|
|
||||||
included without limitation in the term "modification".)
|
|
||||||
|
|
||||||
"Source code" for a work means the preferred form of the work for
|
|
||||||
making modifications to it. For a library, complete source code means
|
|
||||||
all the source code for all modules it contains, plus any associated
|
|
||||||
interface definition files, plus the scripts used to control compilation
|
|
||||||
and installation of the library.
|
|
||||||
|
|
||||||
Activities other than copying, distribution and modification are not
|
|
||||||
covered by this License; they are outside its scope. The act of
|
|
||||||
running a program using the Library is not restricted, and output from
|
|
||||||
such a program is covered only if its contents constitute a work based
|
|
||||||
on the Library (independent of the use of the Library in a tool for
|
|
||||||
writing it). Whether that is true depends on what the Library does
|
|
||||||
and what the program that uses the Library does.
|
|
||||||
|
|
||||||
1. You may copy and distribute verbatim copies of the Library's
|
|
||||||
complete source code as you receive it, in any medium, provided that
|
|
||||||
you conspicuously and appropriately publish on each copy an
|
|
||||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
|
||||||
all the notices that refer to this License and to the absence of any
|
|
||||||
warranty; and distribute a copy of this License along with the
|
|
||||||
Library.
|
|
||||||
|
|
||||||
You may charge a fee for the physical act of transferring a copy,
|
|
||||||
and you may at your option offer warranty protection in exchange for a
|
|
||||||
fee.
|
|
||||||
|
|
||||||
2. You may modify your copy or copies of the Library or any portion
|
|
||||||
of it, thus forming a work based on the Library, and copy and
|
|
||||||
distribute such modifications or work under the terms of Section 1
|
|
||||||
above, provided that you also meet all of these conditions:
|
|
||||||
|
|
||||||
a) The modified work must itself be a software library.
|
|
||||||
|
|
||||||
b) You must cause the files modified to carry prominent notices
|
|
||||||
stating that you changed the files and the date of any change.
|
|
||||||
|
|
||||||
c) You must cause the whole of the work to be licensed at no
|
|
||||||
charge to all third parties under the terms of this License.
|
|
||||||
|
|
||||||
d) If a facility in the modified Library refers to a function or a
|
|
||||||
table of data to be supplied by an application program that uses
|
|
||||||
the facility, other than as an argument passed when the facility
|
|
||||||
is invoked, then you must make a good faith effort to ensure that,
|
|
||||||
in the event an application does not supply such function or
|
|
||||||
table, the facility still operates, and performs whatever part of
|
|
||||||
its purpose remains meaningful.
|
|
||||||
|
|
||||||
(For example, a function in a library to compute square roots has
|
|
||||||
a purpose that is entirely well-defined independent of the
|
|
||||||
application. Therefore, Subsection 2d requires that any
|
|
||||||
application-supplied function or table used by this function must
|
|
||||||
be optional: if the application does not supply it, the square
|
|
||||||
root function must still compute square roots.)
|
|
||||||
|
|
||||||
These requirements apply to the modified work as a whole. If
|
|
||||||
identifiable sections of that work are not derived from the Library,
|
|
||||||
and can be reasonably considered independent and separate works in
|
|
||||||
themselves, then this License, and its terms, do not apply to those
|
|
||||||
sections when you distribute them as separate works. But when you
|
|
||||||
distribute the same sections as part of a whole which is a work based
|
|
||||||
on the Library, the distribution of the whole must be on the terms of
|
|
||||||
this License, whose permissions for other licensees extend to the
|
|
||||||
entire whole, and thus to each and every part regardless of who wrote
|
|
||||||
it.
|
|
||||||
|
|
||||||
Thus, it is not the intent of this section to claim rights or contest
|
|
||||||
your rights to work written entirely by you; rather, the intent is to
|
|
||||||
exercise the right to control the distribution of derivative or
|
|
||||||
collective works based on the Library.
|
|
||||||
|
|
||||||
In addition, mere aggregation of another work not based on the Library
|
|
||||||
with the Library (or with a work based on the Library) on a volume of
|
|
||||||
a storage or distribution medium does not bring the other work under
|
|
||||||
the scope of this License.
|
|
||||||
|
|
||||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
|
||||||
License instead of this License to a given copy of the Library. To do
|
|
||||||
this, you must alter all the notices that refer to this License, so
|
|
||||||
that they refer to the ordinary GNU General Public License, version 2,
|
|
||||||
instead of to this License. (If a newer version than version 2 of the
|
|
||||||
ordinary GNU General Public License has appeared, then you can specify
|
|
||||||
that version instead if you wish.) Do not make any other change in
|
|
||||||
these notices.
|
|
||||||
|
|
||||||
Once this change is made in a given copy, it is irreversible for
|
|
||||||
that copy, so the ordinary GNU General Public License applies to all
|
|
||||||
subsequent copies and derivative works made from that copy.
|
|
||||||
|
|
||||||
This option is useful when you wish to copy part of the code of
|
|
||||||
the Library into a program that is not a library.
|
|
||||||
|
|
||||||
4. You may copy and distribute the Library (or a portion or
|
|
||||||
derivative of it, under Section 2) in object code or executable form
|
|
||||||
under the terms of Sections 1 and 2 above provided that you accompany
|
|
||||||
it with the complete corresponding machine-readable source code, which
|
|
||||||
must be distributed under the terms of Sections 1 and 2 above on a
|
|
||||||
medium customarily used for software interchange.
|
|
||||||
|
|
||||||
If distribution of object code is made by offering access to copy
|
|
||||||
from a designated place, then offering equivalent access to copy the
|
|
||||||
source code from the same place satisfies the requirement to
|
|
||||||
distribute the source code, even though third parties are not
|
|
||||||
compelled to copy the source along with the object code.
|
|
||||||
|
|
||||||
5. A program that contains no derivative of any portion of the
|
|
||||||
Library, but is designed to work with the Library by being compiled or
|
|
||||||
linked with it, is called a "work that uses the Library". Such a
|
|
||||||
work, in isolation, is not a derivative work of the Library, and
|
|
||||||
therefore falls outside the scope of this License.
|
|
||||||
|
|
||||||
However, linking a "work that uses the Library" with the Library
|
|
||||||
creates an executable that is a derivative of the Library (because it
|
|
||||||
contains portions of the Library), rather than a "work that uses the
|
|
||||||
library". The executable is therefore covered by this License.
|
|
||||||
Section 6 states terms for distribution of such executables.
|
|
||||||
|
|
||||||
When a "work that uses the Library" uses material from a header file
|
|
||||||
that is part of the Library, the object code for the work may be a
|
|
||||||
derivative work of the Library even though the source code is not.
|
|
||||||
Whether this is true is especially significant if the work can be
|
|
||||||
linked without the Library, or if the work is itself a library. The
|
|
||||||
threshold for this to be true is not precisely defined by law.
|
|
||||||
|
|
||||||
If such an object file uses only numerical parameters, data
|
|
||||||
structure layouts and accessors, and small macros and small inline
|
|
||||||
functions (ten lines or less in length), then the use of the object
|
|
||||||
file is unrestricted, regardless of whether it is legally a derivative
|
|
||||||
work. (Executables containing this object code plus portions of the
|
|
||||||
Library will still fall under Section 6.)
|
|
||||||
|
|
||||||
Otherwise, if the work is a derivative of the Library, you may
|
|
||||||
distribute the object code for the work under the terms of Section 6.
|
|
||||||
Any executables containing that work also fall under Section 6,
|
|
||||||
whether or not they are linked directly with the Library itself.
|
|
||||||
|
|
||||||
6. As an exception to the Sections above, you may also combine or
|
|
||||||
link a "work that uses the Library" with the Library to produce a
|
|
||||||
work containing portions of the Library, and distribute that work
|
|
||||||
under terms of your choice, provided that the terms permit
|
|
||||||
modification of the work for the customer's own use and reverse
|
|
||||||
engineering for debugging such modifications.
|
|
||||||
|
|
||||||
You must give prominent notice with each copy of the work that the
|
|
||||||
Library is used in it and that the Library and its use are covered by
|
|
||||||
this License. You must supply a copy of this License. If the work
|
|
||||||
during execution displays copyright notices, you must include the
|
|
||||||
copyright notice for the Library among them, as well as a reference
|
|
||||||
directing the user to the copy of this License. Also, you must do one
|
|
||||||
of these things:
|
|
||||||
|
|
||||||
a) Accompany the work with the complete corresponding
|
|
||||||
machine-readable source code for the Library including whatever
|
|
||||||
changes were used in the work (which must be distributed under
|
|
||||||
Sections 1 and 2 above); and, if the work is an executable linked
|
|
||||||
with the Library, with the complete machine-readable "work that
|
|
||||||
uses the Library", as object code and/or source code, so that the
|
|
||||||
user can modify the Library and then relink to produce a modified
|
|
||||||
executable containing the modified Library. (It is understood
|
|
||||||
that the user who changes the contents of definitions files in the
|
|
||||||
Library will not necessarily be able to recompile the application
|
|
||||||
to use the modified definitions.)
|
|
||||||
|
|
||||||
b) Use a suitable shared library mechanism for linking with the
|
|
||||||
Library. A suitable mechanism is one that (1) uses at run time a
|
|
||||||
copy of the library already present on the user's computer system,
|
|
||||||
rather than copying library functions into the executable, and (2)
|
|
||||||
will operate properly with a modified version of the library, if
|
|
||||||
the user installs one, as long as the modified version is
|
|
||||||
interface-compatible with the version that the work was made with.
|
|
||||||
|
|
||||||
c) Accompany the work with a written offer, valid for at
|
|
||||||
least three years, to give the same user the materials
|
|
||||||
specified in Subsection 6a, above, for a charge no more
|
|
||||||
than the cost of performing this distribution.
|
|
||||||
|
|
||||||
d) If distribution of the work is made by offering access to copy
|
|
||||||
from a designated place, offer equivalent access to copy the above
|
|
||||||
specified materials from the same place.
|
|
||||||
|
|
||||||
e) Verify that the user has already received a copy of these
|
|
||||||
materials or that you have already sent this user a copy.
|
|
||||||
|
|
||||||
For an executable, the required form of the "work that uses the
|
|
||||||
Library" must include any data and utility programs needed for
|
|
||||||
reproducing the executable from it. However, as a special exception,
|
|
||||||
the materials to be distributed need not include anything that is
|
|
||||||
normally distributed (in either source or binary form) with the major
|
|
||||||
components (compiler, kernel, and so on) of the operating system on
|
|
||||||
which the executable runs, unless that component itself accompanies
|
|
||||||
the executable.
|
|
||||||
|
|
||||||
It may happen that this requirement contradicts the license
|
|
||||||
restrictions of other proprietary libraries that do not normally
|
|
||||||
accompany the operating system. Such a contradiction means you cannot
|
|
||||||
use both them and the Library together in an executable that you
|
|
||||||
distribute.
|
|
||||||
|
|
||||||
7. You may place library facilities that are a work based on the
|
|
||||||
Library side-by-side in a single library together with other library
|
|
||||||
facilities not covered by this License, and distribute such a combined
|
|
||||||
library, provided that the separate distribution of the work based on
|
|
||||||
the Library and of the other library facilities is otherwise
|
|
||||||
permitted, and provided that you do these two things:
|
|
||||||
|
|
||||||
a) Accompany the combined library with a copy of the same work
|
|
||||||
based on the Library, uncombined with any other library
|
|
||||||
facilities. This must be distributed under the terms of the
|
|
||||||
Sections above.
|
|
||||||
|
|
||||||
b) Give prominent notice with the combined library of the fact
|
|
||||||
that part of it is a work based on the Library, and explaining
|
|
||||||
where to find the accompanying uncombined form of the same work.
|
|
||||||
|
|
||||||
8. You may not copy, modify, sublicense, link with, or distribute
|
|
||||||
the Library except as expressly provided under this License. Any
|
|
||||||
attempt otherwise to copy, modify, sublicense, link with, or
|
|
||||||
distribute the Library is void, and will automatically terminate your
|
|
||||||
rights under this License. However, parties who have received copies,
|
|
||||||
or rights, from you under this License will not have their licenses
|
|
||||||
terminated so long as such parties remain in full compliance.
|
|
||||||
|
|
||||||
9. You are not required to accept this License, since you have not
|
|
||||||
signed it. However, nothing else grants you permission to modify or
|
|
||||||
distribute the Library or its derivative works. These actions are
|
|
||||||
prohibited by law if you do not accept this License. Therefore, by
|
|
||||||
modifying or distributing the Library (or any work based on the
|
|
||||||
Library), you indicate your acceptance of this License to do so, and
|
|
||||||
all its terms and conditions for copying, distributing or modifying
|
|
||||||
the Library or works based on it.
|
|
||||||
|
|
||||||
10. Each time you redistribute the Library (or any work based on the
|
|
||||||
Library), the recipient automatically receives a license from the
|
|
||||||
original licensor to copy, distribute, link with or modify the Library
|
|
||||||
subject to these terms and conditions. You may not impose any further
|
|
||||||
restrictions on the recipients' exercise of the rights granted herein.
|
|
||||||
You are not responsible for enforcing compliance by third parties with
|
|
||||||
this License.
|
|
||||||
|
|
||||||
11. If, as a consequence of a court judgment or allegation of patent
|
|
||||||
infringement or for any other reason (not limited to patent issues),
|
|
||||||
conditions are imposed on you (whether by court order, agreement or
|
|
||||||
otherwise) that contradict the conditions of this License, they do not
|
|
||||||
excuse you from the conditions of this License. If you cannot
|
|
||||||
distribute so as to satisfy simultaneously your obligations under this
|
|
||||||
License and any other pertinent obligations, then as a consequence you
|
|
||||||
may not distribute the Library at all. For example, if a patent
|
|
||||||
license would not permit royalty-free redistribution of the Library by
|
|
||||||
all those who receive copies directly or indirectly through you, then
|
|
||||||
the only way you could satisfy both it and this License would be to
|
|
||||||
refrain entirely from distribution of the Library.
|
|
||||||
|
|
||||||
If any portion of this section is held invalid or unenforceable under any
|
|
||||||
particular circumstance, the balance of the section is intended to apply,
|
|
||||||
and the section as a whole is intended to apply in other circumstances.
|
|
||||||
|
|
||||||
It is not the purpose of this section to induce you to infringe any
|
|
||||||
patents or other property right claims or to contest validity of any
|
|
||||||
such claims; this section has the sole purpose of protecting the
|
|
||||||
integrity of the free software distribution system which is
|
|
||||||
implemented by public license practices. Many people have made
|
|
||||||
generous contributions to the wide range of software distributed
|
|
||||||
through that system in reliance on consistent application of that
|
|
||||||
system; it is up to the author/donor to decide if he or she is willing
|
|
||||||
to distribute software through any other system and a licensee cannot
|
|
||||||
impose that choice.
|
|
||||||
|
|
||||||
This section is intended to make thoroughly clear what is believed to
|
|
||||||
be a consequence of the rest of this License.
|
|
||||||
|
|
||||||
12. If the distribution and/or use of the Library is restricted in
|
|
||||||
certain countries either by patents or by copyrighted interfaces, the
|
|
||||||
original copyright holder who places the Library under this License may add
|
|
||||||
an explicit geographical distribution limitation excluding those countries,
|
|
||||||
so that distribution is permitted only in or among countries not thus
|
|
||||||
excluded. In such case, this License incorporates the limitation as if
|
|
||||||
written in the body of this License.
|
|
||||||
|
|
||||||
13. The Free Software Foundation may publish revised and/or new
|
|
||||||
versions of the Lesser General Public License from time to time.
|
|
||||||
Such new versions will be similar in spirit to the present version,
|
|
||||||
but may differ in detail to address new problems or concerns.
|
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the Library
|
|
||||||
specifies a version number of this License which applies to it and
|
|
||||||
"any later version", you have the option of following the terms and
|
|
||||||
conditions either of that version or of any later version published by
|
|
||||||
the Free Software Foundation. If the Library does not specify a
|
|
||||||
license version number, you may choose any version ever published by
|
|
||||||
the Free Software Foundation.
|
|
||||||
|
|
||||||
14. If you wish to incorporate parts of the Library into other free
|
|
||||||
programs whose distribution conditions are incompatible with these,
|
|
||||||
write to the author to ask for permission. For software which is
|
|
||||||
copyrighted by the Free Software Foundation, write to the Free
|
|
||||||
Software Foundation; we sometimes make exceptions for this. Our
|
|
||||||
decision will be guided by the two goals of preserving the free status
|
|
||||||
of all derivatives of our free software and of promoting the sharing
|
|
||||||
and reuse of software generally.
|
|
||||||
|
|
||||||
NO WARRANTY
|
|
||||||
|
|
||||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
|
||||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
|
||||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
|
||||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
|
||||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
||||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
|
||||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
|
||||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
|
||||||
|
|
||||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
|
||||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
|
||||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
|
||||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
|
||||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
|
||||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
|
||||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
|
||||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
|
||||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
|
||||||
DAMAGES.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
How to Apply These Terms to Your New Libraries
|
|
||||||
|
|
||||||
If you develop a new library, and you want it to be of the greatest
|
|
||||||
possible use to the public, we recommend making it free software that
|
|
||||||
everyone can redistribute and change. You can do so by permitting
|
|
||||||
redistribution under these terms (or, alternatively, under the terms of the
|
|
||||||
ordinary General Public License).
|
|
||||||
|
|
||||||
To apply these terms, attach the following notices to the library. It is
|
|
||||||
safest to attach them to the start of each source file to most effectively
|
|
||||||
convey the exclusion of warranty; and each file should have at least the
|
|
||||||
"copyright" line and a pointer to where the full notice is found.
|
|
||||||
|
|
||||||
<one line to give the library's name and a brief idea of what it does.>
|
|
||||||
Copyright (C) <year> <name of author>
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
|
||||||
modify it under the terms of the GNU Lesser General Public
|
|
||||||
License as published by the Free Software Foundation; either
|
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public
|
|
||||||
License along with this library; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or your
|
|
||||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
|
||||||
necessary. Here is a sample; alter the names:
|
|
||||||
|
|
||||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
|
||||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
|
||||||
|
|
||||||
<signature of Ty Coon>, 1 April 1990
|
|
||||||
Ty Coon, President of Vice
|
|
||||||
|
|
||||||
That's all there is to it!
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class _StringGenerator(object):
|
|
||||||
def __init__(self, string):
|
|
||||||
self.string = string
|
|
||||||
self.index = -1
|
|
||||||
def peek(self):
|
|
||||||
i = self.index + 1
|
|
||||||
if i < len(self.string):
|
|
||||||
return self.string[i]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
def next(self):
|
|
||||||
self.index += 1
|
|
||||||
if self.index < len(self.string):
|
|
||||||
return self.string[self.index]
|
|
||||||
else:
|
|
||||||
raise StopIteration
|
|
||||||
def all(self):
|
|
||||||
return self.string
|
|
||||||
|
|
||||||
class WriteException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class ReadException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class JsonReader(object):
|
|
||||||
hex_digits = {'A': 10,'B': 11,'C': 12,'D': 13,'E': 14,'F':15}
|
|
||||||
escapes = {'t':'\t','n':'\n','f':'\f','r':'\r','b':'\b'}
|
|
||||||
|
|
||||||
def read(self, s):
|
|
||||||
self._generator = _StringGenerator(s)
|
|
||||||
result = self._read()
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _read(self):
|
|
||||||
self._eatWhitespace()
|
|
||||||
peek = self._peek()
|
|
||||||
if peek is None:
|
|
||||||
raise ReadException, "Nothing to read: '%s'" % self._generator.all()
|
|
||||||
if peek == '{':
|
|
||||||
return self._readObject()
|
|
||||||
elif peek == '[':
|
|
||||||
return self._readArray()
|
|
||||||
elif peek == '"':
|
|
||||||
return self._readString()
|
|
||||||
elif peek == '-' or peek.isdigit():
|
|
||||||
return self._readNumber()
|
|
||||||
elif peek == 't':
|
|
||||||
return self._readTrue()
|
|
||||||
elif peek == 'f':
|
|
||||||
return self._readFalse()
|
|
||||||
elif peek == 'n':
|
|
||||||
return self._readNull()
|
|
||||||
elif peek == '/':
|
|
||||||
self._readComment()
|
|
||||||
return self._read()
|
|
||||||
else:
|
|
||||||
raise ReadException, "Input is not valid JSON: '%s'" % self._generator.all()
|
|
||||||
|
|
||||||
def _readTrue(self):
|
|
||||||
self._assertNext('t', "true")
|
|
||||||
self._assertNext('r', "true")
|
|
||||||
self._assertNext('u', "true")
|
|
||||||
self._assertNext('e', "true")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _readFalse(self):
|
|
||||||
self._assertNext('f', "false")
|
|
||||||
self._assertNext('a', "false")
|
|
||||||
self._assertNext('l', "false")
|
|
||||||
self._assertNext('s', "false")
|
|
||||||
self._assertNext('e', "false")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _readNull(self):
|
|
||||||
self._assertNext('n', "null")
|
|
||||||
self._assertNext('u', "null")
|
|
||||||
self._assertNext('l', "null")
|
|
||||||
self._assertNext('l', "null")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _assertNext(self, ch, target):
|
|
||||||
if self._next() != ch:
|
|
||||||
raise ReadException, "Trying to read %s: '%s'" % (target, self._generator.all())
|
|
||||||
|
|
||||||
def _readNumber(self):
|
|
||||||
isfloat = False
|
|
||||||
result = self._next()
|
|
||||||
peek = self._peek()
|
|
||||||
while peek is not None and (peek.isdigit() or peek == "."):
|
|
||||||
isfloat = isfloat or peek == "."
|
|
||||||
result = result + self._next()
|
|
||||||
peek = self._peek()
|
|
||||||
try:
|
|
||||||
if isfloat:
|
|
||||||
return float(result)
|
|
||||||
else:
|
|
||||||
return int(result)
|
|
||||||
except ValueError:
|
|
||||||
raise ReadException, "Not a valid JSON number: '%s'" % result
|
|
||||||
|
|
||||||
def _readString(self):
|
|
||||||
result = ""
|
|
||||||
assert self._next() == '"'
|
|
||||||
try:
|
|
||||||
while self._peek() != '"':
|
|
||||||
ch = self._next()
|
|
||||||
if ch == "\\":
|
|
||||||
ch = self._next()
|
|
||||||
if ch in 'brnft':
|
|
||||||
ch = self.escapes[ch]
|
|
||||||
elif ch == "u":
|
|
||||||
ch4096 = self._next()
|
|
||||||
ch256 = self._next()
|
|
||||||
ch16 = self._next()
|
|
||||||
ch1 = self._next()
|
|
||||||
n = 4096 * self._hexDigitToInt(ch4096)
|
|
||||||
n += 256 * self._hexDigitToInt(ch256)
|
|
||||||
n += 16 * self._hexDigitToInt(ch16)
|
|
||||||
n += self._hexDigitToInt(ch1)
|
|
||||||
ch = unichr(n)
|
|
||||||
elif ch not in '"/\\':
|
|
||||||
raise ReadException, "Not a valid escaped JSON character: '%s' in %s" % (ch, self._generator.all())
|
|
||||||
result = result + ch
|
|
||||||
except StopIteration:
|
|
||||||
raise ReadException, "Not a valid JSON string: '%s'" % self._generator.all()
|
|
||||||
assert self._next() == '"'
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _hexDigitToInt(self, ch):
|
|
||||||
try:
|
|
||||||
result = self.hex_digits[ch.upper()]
|
|
||||||
except KeyError:
|
|
||||||
try:
|
|
||||||
result = int(ch)
|
|
||||||
except ValueError:
|
|
||||||
raise ReadException, "The character %s is not a hex digit." % ch
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _readComment(self):
|
|
||||||
assert self._next() == "/"
|
|
||||||
second = self._next()
|
|
||||||
if second == "/":
|
|
||||||
self._readDoubleSolidusComment()
|
|
||||||
elif second == '*':
|
|
||||||
self._readCStyleComment()
|
|
||||||
else:
|
|
||||||
raise ReadException, "Not a valid JSON comment: %s" % self._generator.all()
|
|
||||||
|
|
||||||
def _readCStyleComment(self):
|
|
||||||
try:
|
|
||||||
done = False
|
|
||||||
while not done:
|
|
||||||
ch = self._next()
|
|
||||||
done = (ch == "*" and self._peek() == "/")
|
|
||||||
if not done and ch == "/" and self._peek() == "*":
|
|
||||||
raise ReadException, "Not a valid JSON comment: %s, '/*' cannot be embedded in the comment." % self._generator.all()
|
|
||||||
self._next()
|
|
||||||
except StopIteration:
|
|
||||||
raise ReadException, "Not a valid JSON comment: %s, expected */" % self._generator.all()
|
|
||||||
|
|
||||||
def _readDoubleSolidusComment(self):
|
|
||||||
try:
|
|
||||||
ch = self._next()
|
|
||||||
while ch != "\r" and ch != "\n":
|
|
||||||
ch = self._next()
|
|
||||||
except StopIteration:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _readArray(self):
|
|
||||||
result = []
|
|
||||||
assert self._next() == '['
|
|
||||||
done = self._peek() == ']'
|
|
||||||
while not done:
|
|
||||||
item = self._read()
|
|
||||||
result.append(item)
|
|
||||||
self._eatWhitespace()
|
|
||||||
done = self._peek() == ']'
|
|
||||||
if not done:
|
|
||||||
ch = self._next()
|
|
||||||
if ch != ",":
|
|
||||||
raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch)
|
|
||||||
assert ']' == self._next()
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _readObject(self):
|
|
||||||
result = {}
|
|
||||||
assert self._next() == '{'
|
|
||||||
done = self._peek() == '}'
|
|
||||||
while not done:
|
|
||||||
key = self._read()
|
|
||||||
if type(key) is not types.StringType:
|
|
||||||
raise ReadException, "Not a valid JSON object key (should be a string): %s" % key
|
|
||||||
self._eatWhitespace()
|
|
||||||
ch = self._next()
|
|
||||||
if ch != ":":
|
|
||||||
raise ReadException, "Not a valid JSON object: '%s' due to: '%s'" % (self._generator.all(), ch)
|
|
||||||
self._eatWhitespace()
|
|
||||||
val = self._read()
|
|
||||||
result[key] = val
|
|
||||||
self._eatWhitespace()
|
|
||||||
done = self._peek() == '}'
|
|
||||||
if not done:
|
|
||||||
ch = self._next()
|
|
||||||
if ch != ",":
|
|
||||||
raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch)
|
|
||||||
assert self._next() == "}"
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _eatWhitespace(self):
|
|
||||||
p = self._peek()
|
|
||||||
while p is not None and p in string.whitespace or p == '/':
|
|
||||||
if p == '/':
|
|
||||||
self._readComment()
|
|
||||||
else:
|
|
||||||
self._next()
|
|
||||||
p = self._peek()
|
|
||||||
|
|
||||||
def _peek(self):
|
|
||||||
return self._generator.peek()
|
|
||||||
|
|
||||||
def _next(self):
|
|
||||||
return self._generator.next()
|
|
||||||
|
|
||||||
class JsonWriter(object):
|
|
||||||
|
|
||||||
def _append(self, s):
|
|
||||||
self._results.append(s)
|
|
||||||
|
|
||||||
def write(self, obj, escaped_forward_slash=False):
|
|
||||||
self._escaped_forward_slash = escaped_forward_slash
|
|
||||||
self._results = []
|
|
||||||
self._write(obj)
|
|
||||||
return "".join(self._results)
|
|
||||||
|
|
||||||
def _write(self, obj):
|
|
||||||
ty = type(obj)
|
|
||||||
if ty is types.DictType:
|
|
||||||
n = len(obj)
|
|
||||||
self._append("{")
|
|
||||||
for k, v in obj.items():
|
|
||||||
self._write(k)
|
|
||||||
self._append(":")
|
|
||||||
self._write(v)
|
|
||||||
n = n - 1
|
|
||||||
if n > 0:
|
|
||||||
self._append(",")
|
|
||||||
self._append("}")
|
|
||||||
elif ty is types.ListType or ty is types.TupleType:
|
|
||||||
n = len(obj)
|
|
||||||
self._append("[")
|
|
||||||
for item in obj:
|
|
||||||
self._write(item)
|
|
||||||
n = n - 1
|
|
||||||
if n > 0:
|
|
||||||
self._append(",")
|
|
||||||
self._append("]")
|
|
||||||
elif ty is types.StringType or ty is types.UnicodeType:
|
|
||||||
self._append('"')
|
|
||||||
obj = obj.replace('\\', r'\\')
|
|
||||||
if self._escaped_forward_slash:
|
|
||||||
obj = obj.replace('/', r'\/')
|
|
||||||
obj = obj.replace('"', r'\"')
|
|
||||||
obj = obj.replace('\b', r'\b')
|
|
||||||
obj = obj.replace('\f', r'\f')
|
|
||||||
obj = obj.replace('\n', r'\n')
|
|
||||||
obj = obj.replace('\r', r'\r')
|
|
||||||
obj = obj.replace('\t', r'\t')
|
|
||||||
self._append(obj)
|
|
||||||
self._append('"')
|
|
||||||
elif ty is types.IntType or ty is types.LongType:
|
|
||||||
self._append(str(obj))
|
|
||||||
elif ty is types.FloatType:
|
|
||||||
self._append("%f" % obj)
|
|
||||||
elif obj is True:
|
|
||||||
self._append("true")
|
|
||||||
elif obj is False:
|
|
||||||
self._append("false")
|
|
||||||
elif obj is None:
|
|
||||||
self._append("null")
|
|
||||||
else:
|
|
||||||
raise WriteException, "Cannot write in JSON: %s" % repr(obj)
|
|
||||||
|
|
||||||
def write(obj, escaped_forward_slash=False):
|
|
||||||
return JsonWriter().write(obj, escaped_forward_slash)
|
|
||||||
|
|
||||||
def read(s):
|
|
||||||
return JsonReader().read(s)
|
|
||||||
@ -1,255 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright (C) Martijn Voncken 2008 <mvoncken@gmail.com>
|
|
||||||
#
|
|
||||||
# FilteredForm contains code copied from django newforms :
|
|
||||||
# Copyright (c) 2005, the Lawrence Journal-World
|
|
||||||
#
|
|
||||||
# Django Licence, see ./newforms_portable/LICENCE
|
|
||||||
#
|
|
||||||
|
|
||||||
from newforms_portable import *
|
|
||||||
import newforms_portable as newforms
|
|
||||||
from newforms_portable.forms import BoundField
|
|
||||||
from newforms_portable.util import ErrorList, escape
|
|
||||||
|
|
||||||
|
|
||||||
import sys, os
|
|
||||||
|
|
||||||
import web
|
|
||||||
|
|
||||||
#Form
|
|
||||||
class FilteredForm(newforms.Form):
|
|
||||||
"""
|
|
||||||
used to enable more complex layouts.
|
|
||||||
the filter argument contains the names of the fields to render.
|
|
||||||
"""
|
|
||||||
def _html_output_filtered(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row, filter):
|
|
||||||
"""
|
|
||||||
Helper function for outputting HTML. Used by as_table(), as_ul(), as_p().
|
|
||||||
newforms_plus: 99% c&p from newforms, added filter.
|
|
||||||
"""
|
|
||||||
top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
|
|
||||||
output, hidden_fields = [], []
|
|
||||||
for name, field in self.fields.items():
|
|
||||||
#FilteredForm
|
|
||||||
if (filter != None) and (not name in filter):
|
|
||||||
continue
|
|
||||||
#/FilteredForm
|
|
||||||
bf = BoundField(self, field, name)
|
|
||||||
bf_errors = ErrorList([escape(error) for error in bf.errors]) # Escape and cache in local variable.
|
|
||||||
if bf.is_hidden:
|
|
||||||
if bf_errors:
|
|
||||||
top_errors.extend(['(Hidden field %s) %s' % (name, e) for e in bf_errors])
|
|
||||||
hidden_fields.append(unicode(bf))
|
|
||||||
else:
|
|
||||||
if errors_on_separate_row and bf_errors:
|
|
||||||
output.append(error_row % bf_errors)
|
|
||||||
label = bf.label and bf.label_tag(escape(bf.label + ':')) or ''
|
|
||||||
if field.help_text:
|
|
||||||
help_text = help_text_html % field.help_text
|
|
||||||
else:
|
|
||||||
help_text = u''
|
|
||||||
output.append(normal_row % {'errors': bf_errors, 'label': label, 'field': unicode(bf), 'help_text': help_text})
|
|
||||||
if top_errors:
|
|
||||||
output.insert(0, error_row % top_errors)
|
|
||||||
if hidden_fields: # Insert any hidden fields in the last row.
|
|
||||||
str_hidden = u''.join(hidden_fields)
|
|
||||||
if output:
|
|
||||||
last_row = output[-1]
|
|
||||||
# Chop off the trailing row_ender (e.g. '</td></tr>') and insert the hidden fields.
|
|
||||||
output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
|
|
||||||
else: # If there aren't any rows in the output, just append the hidden fields.
|
|
||||||
output.append(str_hidden)
|
|
||||||
return u'\n'.join(output)
|
|
||||||
|
|
||||||
def as_table(self , filter = None): #add class="newforms"
|
|
||||||
"Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
|
|
||||||
return self._html_output_filtered(u'<tr><th class="newforms">%(label)s</th><td class="newforms">%(errors)s%(field)s%(help_text)s</td></tr>', u'<tr><td colspan="2">%s</td></tr>', '</td></tr>', u'<br />%s', False, filter)
|
|
||||||
|
|
||||||
def as_ul(self, filter = None):
|
|
||||||
"Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
|
|
||||||
return self._html_output_filtered(u'<li>%(errors)s%(label)s %(field)s%(help_text)s</li>', u'<li>%s</li>', '</li>', u' %s', False , filter)
|
|
||||||
|
|
||||||
def as_p(self , filter = None):
|
|
||||||
"Returns this form rendered as HTML <p>s."
|
|
||||||
return self._html_output_filtered(u'<p>%(label)s %(field)s%(help_text)s</p>', u'<p>%s</p>', '</p>', u' %s', True, filter)
|
|
||||||
|
|
||||||
class Form(FilteredForm):
|
|
||||||
info = ""
|
|
||||||
title = "No Title"
|
|
||||||
|
|
||||||
def __init__(self,data = None):
|
|
||||||
if data == None:
|
|
||||||
data = self.initial_data()
|
|
||||||
newforms.Form.__init__(self,data)
|
|
||||||
|
|
||||||
def initial_data(self):
|
|
||||||
"override in subclass"
|
|
||||||
return None
|
|
||||||
|
|
||||||
def start_save(self):
|
|
||||||
"called by config_page"
|
|
||||||
data = web.Storage(self.cleaned_data)
|
|
||||||
self.validate(data)
|
|
||||||
self.save(data)
|
|
||||||
self.post_save()
|
|
||||||
|
|
||||||
def save(self, vars):
|
|
||||||
"override in subclass"
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def post_save(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def validate(self, data):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def pre_html(self):
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def post_html(self):
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
#convenience Input Fields.
|
|
||||||
class CheckBox(newforms.BooleanField):
|
|
||||||
"Non Required BooleanField,why the f is it required by default?"
|
|
||||||
def __init__(self,label, **kwargs):
|
|
||||||
newforms.BooleanField.__init__(self,label=label,required=False,**kwargs)
|
|
||||||
|
|
||||||
class IntChoiceField(newforms.ChoiceField):
|
|
||||||
"""same as ChoiceField, but returns an int
|
|
||||||
hint : Use IntChoiceField(choices=enumerate("list","of","strings"]))
|
|
||||||
for index-based values on a list of strings.
|
|
||||||
"""
|
|
||||||
def __init__(self, label, choices, **kwargs):
|
|
||||||
newforms.ChoiceField.__init__(self, label=label, choices=choices,**kwargs)
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
return int(newforms.ChoiceField.clean(self, value))
|
|
||||||
|
|
||||||
class ServerFolder(newforms.CharField):
|
|
||||||
def __init__(self, label, **kwargs):
|
|
||||||
newforms.CharField.__init__(self, label=label,**kwargs)
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
if value == None:
|
|
||||||
value = ""
|
|
||||||
value = value.rstrip('/').rstrip('\\')
|
|
||||||
self.validate(value)
|
|
||||||
return newforms.CharField.clean(self, value)
|
|
||||||
|
|
||||||
def validate(self, value):
|
|
||||||
if (value and not os.path.isdir(value)):
|
|
||||||
raise newforms.ValidationError(_("This folder does not exist."))
|
|
||||||
|
|
||||||
class Password(newforms.CharField):
|
|
||||||
def __init__(self, label, **kwargs):
|
|
||||||
newforms.CharField.__init__(self, label=label, widget=newforms.PasswordInput,
|
|
||||||
**kwargs)
|
|
||||||
|
|
||||||
#Lazy multiple select:
|
|
||||||
class _LazyCheckboxSelectMultiple(newforms.CheckboxSelectMultiple):
|
|
||||||
"""
|
|
||||||
choices are not know at define-time
|
|
||||||
choices_getter returns self.choices.
|
|
||||||
"""
|
|
||||||
def __init__(self, attrs=None,choices_getter = None):
|
|
||||||
self.choices_getter = choices_getter
|
|
||||||
newforms.CheckboxSelectMultiple.__init__(self,attrs)
|
|
||||||
|
|
||||||
def render(self, name, value, attrs=None, choices=()):
|
|
||||||
self.choices = self.choices_getter()
|
|
||||||
return newforms.CheckboxSelectMultiple.render(self, name, value, attrs, choices)
|
|
||||||
|
|
||||||
|
|
||||||
class LazyMultipleChoice(newforms.MultipleChoiceField):
|
|
||||||
"""
|
|
||||||
choices are not know at define-time
|
|
||||||
choices_getter returns self.choices.
|
|
||||||
defaults to non-required.
|
|
||||||
"""
|
|
||||||
def __init__(self, label = "",widget=_LazyCheckboxSelectMultiple,
|
|
||||||
choices_getter = None, **kwargs):
|
|
||||||
|
|
||||||
self.choices_getter = choices_getter
|
|
||||||
#default to non-required
|
|
||||||
if not 'required' in kwargs:
|
|
||||||
kwargs['required'] = False
|
|
||||||
#init, and pass get_choices to the widget.
|
|
||||||
newforms.MultipleChoiceField.__init__(self, label=label,
|
|
||||||
widget=widget(choices_getter=choices_getter),**kwargs)
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
self.choices = self.choices_getter()
|
|
||||||
return newforms.MultipleChoiceField.clean(self, value)
|
|
||||||
|
|
||||||
#Deluge specific:
|
|
||||||
class _DelugeIntInputWidget(newforms.TextInput):
|
|
||||||
"""
|
|
||||||
because deluge-floats are edited as ints.
|
|
||||||
"""
|
|
||||||
def render(self, name, value, attrs=None):
|
|
||||||
try:
|
|
||||||
value = int(float(value))
|
|
||||||
if value == -1 or value == None:
|
|
||||||
value = _("Unlimited")
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return newforms.TextInput.render(self, name, value, attrs)
|
|
||||||
|
|
||||||
class IntegerField(newforms.IntegerField):
|
|
||||||
def widget_attrs(self, widget):
|
|
||||||
return {'size': "8"}
|
|
||||||
|
|
||||||
class FloatField(newforms.FloatField):
|
|
||||||
def widget_attrs(self, widget):
|
|
||||||
return {'size': "8"}
|
|
||||||
|
|
||||||
class DelugeInt(IntegerField):
|
|
||||||
def __init__(self, label , **kwargs):
|
|
||||||
newforms.IntegerField.__init__(self, label=label, min_value=-1,
|
|
||||||
max_value=sys.maxint, widget=_DelugeIntInputWidget, **kwargs)
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
if str(value).lower() == _('Unlimited').lower():
|
|
||||||
value = -1
|
|
||||||
return int(newforms.IntegerField.clean(self, value))
|
|
||||||
|
|
||||||
class DelugeFloat(DelugeInt):
|
|
||||||
def clean(self, value):
|
|
||||||
try:
|
|
||||||
value = int(float(value)) #float->int
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return float(DelugeInt.clean(self, value))
|
|
||||||
|
|
||||||
|
|
||||||
class StringList(Field):
|
|
||||||
"""for a list of strings """
|
|
||||||
def __init__(self, label, *args, **kwargs):
|
|
||||||
if not "widget" in kwargs:
|
|
||||||
kwargs["widget"] = StringListWidget
|
|
||||||
newforms.Field.__init__(self, *args, **kwargs)
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
if type(value) in [str, unicode]: #bug
|
|
||||||
return value.split("/n")
|
|
||||||
|
|
||||||
class StringListWidget(newforms.Textarea):
|
|
||||||
"""for a list of strings """
|
|
||||||
def __init__(self, attrs=None):
|
|
||||||
newforms.Textarea.__init__(self, attrs)
|
|
||||||
|
|
||||||
def render(self, name, value, attrs=None):
|
|
||||||
if type(value) in [list, tuple]: #bug
|
|
||||||
value = "\n".join(value)
|
|
||||||
return newforms.Textarea.render(self, name, value, attrs)
|
|
||||||
|
|
||||||
#/fields
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
Copyright (c) 2005, the Lawrence Journal-World
|
|
||||||
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,18 +0,0 @@
|
|||||||
"""
|
|
||||||
Django validation and HTML form handling.
|
|
||||||
|
|
||||||
TODO:
|
|
||||||
Default value for field
|
|
||||||
Field labels
|
|
||||||
Nestable Forms
|
|
||||||
FatalValidationError -- short-circuits all other validators on a form
|
|
||||||
ValidationWarning
|
|
||||||
"This form field requires foo.js" and form.js_includes()
|
|
||||||
"""
|
|
||||||
|
|
||||||
from util import ValidationError
|
|
||||||
from widgets import *
|
|
||||||
from fields import *
|
|
||||||
from forms import *
|
|
||||||
from models import *
|
|
||||||
import django
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
based on django rev.7350
|
|
||||||
,/django/ contains the parts of django required to run newforms.
|
|
||||||
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
"Global Django exceptions"
|
|
||||||
|
|
||||||
class ObjectDoesNotExist(Exception):
|
|
||||||
"The requested object does not exist"
|
|
||||||
silent_variable_failure = True
|
|
||||||
|
|
||||||
class MultipleObjectsReturned(Exception):
|
|
||||||
"The query returned multiple objects when only one was expected."
|
|
||||||
pass
|
|
||||||
|
|
||||||
class SuspiciousOperation(Exception):
|
|
||||||
"The user did something suspicious"
|
|
||||||
pass
|
|
||||||
|
|
||||||
class PermissionDenied(Exception):
|
|
||||||
"The user did not have permission to do that"
|
|
||||||
pass
|
|
||||||
|
|
||||||
class ViewDoesNotExist(Exception):
|
|
||||||
"The requested view does not exist"
|
|
||||||
pass
|
|
||||||
|
|
||||||
class MiddlewareNotUsed(Exception):
|
|
||||||
"This middleware is not used in this server configuration"
|
|
||||||
pass
|
|
||||||
|
|
||||||
class ImproperlyConfigured(Exception):
|
|
||||||
"Django is somehow improperly configured"
|
|
||||||
pass
|
|
||||||
@ -1,345 +0,0 @@
|
|||||||
class MergeDict(object):
|
|
||||||
"""
|
|
||||||
A simple class for creating new "virtual" dictionaries that actually look
|
|
||||||
up values in more than one dictionary, passed in the constructor.
|
|
||||||
|
|
||||||
If a key appears in more than one of the given dictionaries, only the
|
|
||||||
first occurrence will be used.
|
|
||||||
"""
|
|
||||||
def __init__(self, *dicts):
|
|
||||||
self.dicts = dicts
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
for dict_ in self.dicts:
|
|
||||||
try:
|
|
||||||
return dict_[key]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
raise KeyError
|
|
||||||
|
|
||||||
def __copy__(self):
|
|
||||||
return self.__class__(*self.dicts)
|
|
||||||
|
|
||||||
def get(self, key, default=None):
|
|
||||||
try:
|
|
||||||
return self[key]
|
|
||||||
except KeyError:
|
|
||||||
return default
|
|
||||||
|
|
||||||
def getlist(self, key):
|
|
||||||
for dict_ in self.dicts:
|
|
||||||
if key in dict_.keys():
|
|
||||||
return dict_.getlist(key)
|
|
||||||
return []
|
|
||||||
|
|
||||||
def items(self):
|
|
||||||
item_list = []
|
|
||||||
for dict_ in self.dicts:
|
|
||||||
item_list.extend(dict_.items())
|
|
||||||
return item_list
|
|
||||||
|
|
||||||
def has_key(self, key):
|
|
||||||
for dict_ in self.dicts:
|
|
||||||
if key in dict_:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
__contains__ = has_key
|
|
||||||
|
|
||||||
def copy(self):
|
|
||||||
"""Returns a copy of this object."""
|
|
||||||
return self.__copy__()
|
|
||||||
|
|
||||||
class SortedDict(dict):
|
|
||||||
"""
|
|
||||||
A dictionary that keeps its keys in the order in which they're inserted.
|
|
||||||
"""
|
|
||||||
def __init__(self, data=None):
|
|
||||||
if data is None:
|
|
||||||
data = {}
|
|
||||||
super(SortedDict, self).__init__(data)
|
|
||||||
if isinstance(data, dict):
|
|
||||||
self.keyOrder = data.keys()
|
|
||||||
else:
|
|
||||||
self.keyOrder = []
|
|
||||||
for key, value in data:
|
|
||||||
if key not in self.keyOrder:
|
|
||||||
self.keyOrder.append(key)
|
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
|
||||||
from copy import deepcopy
|
|
||||||
return self.__class__([(key, deepcopy(value, memo))
|
|
||||||
for key, value in self.iteritems()])
|
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
|
||||||
super(SortedDict, self).__setitem__(key, value)
|
|
||||||
if key not in self.keyOrder:
|
|
||||||
self.keyOrder.append(key)
|
|
||||||
|
|
||||||
def __delitem__(self, key):
|
|
||||||
super(SortedDict, self).__delitem__(key)
|
|
||||||
self.keyOrder.remove(key)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
for k in self.keyOrder:
|
|
||||||
yield k
|
|
||||||
|
|
||||||
def pop(self, k, *args):
|
|
||||||
result = super(SortedDict, self).pop(k, *args)
|
|
||||||
try:
|
|
||||||
self.keyOrder.remove(k)
|
|
||||||
except ValueError:
|
|
||||||
# Key wasn't in the dictionary in the first place. No problem.
|
|
||||||
pass
|
|
||||||
return result
|
|
||||||
|
|
||||||
def popitem(self):
|
|
||||||
result = super(SortedDict, self).popitem()
|
|
||||||
self.keyOrder.remove(result[0])
|
|
||||||
return result
|
|
||||||
|
|
||||||
def items(self):
|
|
||||||
return zip(self.keyOrder, self.values())
|
|
||||||
|
|
||||||
def iteritems(self):
|
|
||||||
for key in self.keyOrder:
|
|
||||||
yield key, super(SortedDict, self).__getitem__(key)
|
|
||||||
|
|
||||||
def keys(self):
|
|
||||||
return self.keyOrder[:]
|
|
||||||
|
|
||||||
def iterkeys(self):
|
|
||||||
return iter(self.keyOrder)
|
|
||||||
|
|
||||||
def values(self):
|
|
||||||
return [super(SortedDict, self).__getitem__(k) for k in self.keyOrder]
|
|
||||||
|
|
||||||
def itervalues(self):
|
|
||||||
for key in self.keyOrder:
|
|
||||||
yield super(SortedDict, self).__getitem__(key)
|
|
||||||
|
|
||||||
def update(self, dict_):
|
|
||||||
for k, v in dict_.items():
|
|
||||||
self.__setitem__(k, v)
|
|
||||||
|
|
||||||
def setdefault(self, key, default):
|
|
||||||
if key not in self.keyOrder:
|
|
||||||
self.keyOrder.append(key)
|
|
||||||
return super(SortedDict, self).setdefault(key, default)
|
|
||||||
|
|
||||||
def value_for_index(self, index):
|
|
||||||
"""Returns the value of the item at the given zero-based index."""
|
|
||||||
return self[self.keyOrder[index]]
|
|
||||||
|
|
||||||
def insert(self, index, key, value):
|
|
||||||
"""Inserts the key, value pair before the item with the given index."""
|
|
||||||
if key in self.keyOrder:
|
|
||||||
n = self.keyOrder.index(key)
|
|
||||||
del self.keyOrder[n]
|
|
||||||
if n < index:
|
|
||||||
index -= 1
|
|
||||||
self.keyOrder.insert(index, key)
|
|
||||||
super(SortedDict, self).__setitem__(key, value)
|
|
||||||
|
|
||||||
def copy(self):
|
|
||||||
"""Returns a copy of this object."""
|
|
||||||
# This way of initializing the copy means it works for subclasses, too.
|
|
||||||
obj = self.__class__(self)
|
|
||||||
obj.keyOrder = self.keyOrder[:]
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
"""
|
|
||||||
Replaces the normal dict.__repr__ with a version that returns the keys
|
|
||||||
in their sorted order.
|
|
||||||
"""
|
|
||||||
return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()])
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
super(SortedDict, self).clear()
|
|
||||||
self.keyOrder = []
|
|
||||||
|
|
||||||
class MultiValueDictKeyError(KeyError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class MultiValueDict(dict):
|
|
||||||
"""
|
|
||||||
A subclass of dictionary customized to handle multiple values for the
|
|
||||||
same key.
|
|
||||||
|
|
||||||
>>> d = MultiValueDict({'name': ['Adrian', 'Simon'], 'position': ['Developer']})
|
|
||||||
>>> d['name']
|
|
||||||
'Simon'
|
|
||||||
>>> d.getlist('name')
|
|
||||||
['Adrian', 'Simon']
|
|
||||||
>>> d.get('lastname', 'nonexistent')
|
|
||||||
'nonexistent'
|
|
||||||
>>> d.setlist('lastname', ['Holovaty', 'Willison'])
|
|
||||||
|
|
||||||
This class exists to solve the irritating problem raised by cgi.parse_qs,
|
|
||||||
which returns a list for every key, even though most Web forms submit
|
|
||||||
single name-value pairs.
|
|
||||||
"""
|
|
||||||
def __init__(self, key_to_list_mapping=()):
|
|
||||||
super(MultiValueDict, self).__init__(key_to_list_mapping)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<%s: %s>" % (self.__class__.__name__,
|
|
||||||
super(MultiValueDict, self).__repr__())
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
"""
|
|
||||||
Returns the last data value for this key, or [] if it's an empty list;
|
|
||||||
raises KeyError if not found.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
list_ = super(MultiValueDict, self).__getitem__(key)
|
|
||||||
except KeyError:
|
|
||||||
raise MultiValueDictKeyError, "Key %r not found in %r" % (key, self)
|
|
||||||
try:
|
|
||||||
return list_[-1]
|
|
||||||
except IndexError:
|
|
||||||
return []
|
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
|
||||||
super(MultiValueDict, self).__setitem__(key, [value])
|
|
||||||
|
|
||||||
def __copy__(self):
|
|
||||||
return self.__class__(super(MultiValueDict, self).items())
|
|
||||||
|
|
||||||
def __deepcopy__(self, memo=None):
|
|
||||||
import copy
|
|
||||||
if memo is None:
|
|
||||||
memo = {}
|
|
||||||
result = self.__class__()
|
|
||||||
memo[id(self)] = result
|
|
||||||
for key, value in dict.items(self):
|
|
||||||
dict.__setitem__(result, copy.deepcopy(key, memo),
|
|
||||||
copy.deepcopy(value, memo))
|
|
||||||
return result
|
|
||||||
|
|
||||||
def get(self, key, default=None):
|
|
||||||
"""
|
|
||||||
Returns the last data value for the passed key. If key doesn't exist
|
|
||||||
or value is an empty list, then default is returned.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
val = self[key]
|
|
||||||
except KeyError:
|
|
||||||
return default
|
|
||||||
if val == []:
|
|
||||||
return default
|
|
||||||
return val
|
|
||||||
|
|
||||||
def getlist(self, key):
|
|
||||||
"""
|
|
||||||
Returns the list of values for the passed key. If key doesn't exist,
|
|
||||||
then an empty list is returned.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return super(MultiValueDict, self).__getitem__(key)
|
|
||||||
except KeyError:
|
|
||||||
return []
|
|
||||||
|
|
||||||
def setlist(self, key, list_):
|
|
||||||
super(MultiValueDict, self).__setitem__(key, list_)
|
|
||||||
|
|
||||||
def setdefault(self, key, default=None):
|
|
||||||
if key not in self:
|
|
||||||
self[key] = default
|
|
||||||
return self[key]
|
|
||||||
|
|
||||||
def setlistdefault(self, key, default_list=()):
|
|
||||||
if key not in self:
|
|
||||||
self.setlist(key, default_list)
|
|
||||||
return self.getlist(key)
|
|
||||||
|
|
||||||
def appendlist(self, key, value):
|
|
||||||
"""Appends an item to the internal list associated with key."""
|
|
||||||
self.setlistdefault(key, [])
|
|
||||||
super(MultiValueDict, self).__setitem__(key, self.getlist(key) + [value])
|
|
||||||
|
|
||||||
def items(self):
|
|
||||||
"""
|
|
||||||
Returns a list of (key, value) pairs, where value is the last item in
|
|
||||||
the list associated with the key.
|
|
||||||
"""
|
|
||||||
return [(key, self[key]) for key in self.keys()]
|
|
||||||
|
|
||||||
def lists(self):
|
|
||||||
"""Returns a list of (key, list) pairs."""
|
|
||||||
return super(MultiValueDict, self).items()
|
|
||||||
|
|
||||||
def values(self):
|
|
||||||
"""Returns a list of the last value on every key list."""
|
|
||||||
return [self[key] for key in self.keys()]
|
|
||||||
|
|
||||||
def copy(self):
|
|
||||||
"""Returns a copy of this object."""
|
|
||||||
return self.__deepcopy__()
|
|
||||||
|
|
||||||
def update(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
update() extends rather than replaces existing key lists.
|
|
||||||
Also accepts keyword args.
|
|
||||||
"""
|
|
||||||
if len(args) > 1:
|
|
||||||
raise TypeError, "update expected at most 1 arguments, got %d" % len(args)
|
|
||||||
if args:
|
|
||||||
other_dict = args[0]
|
|
||||||
if isinstance(other_dict, MultiValueDict):
|
|
||||||
for key, value_list in other_dict.lists():
|
|
||||||
self.setlistdefault(key, []).extend(value_list)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
for key, value in other_dict.items():
|
|
||||||
self.setlistdefault(key, []).append(value)
|
|
||||||
except TypeError:
|
|
||||||
raise ValueError, "MultiValueDict.update() takes either a MultiValueDict or dictionary"
|
|
||||||
for key, value in kwargs.iteritems():
|
|
||||||
self.setlistdefault(key, []).append(value)
|
|
||||||
|
|
||||||
class DotExpandedDict(dict):
|
|
||||||
"""
|
|
||||||
A special dictionary constructor that takes a dictionary in which the keys
|
|
||||||
may contain dots to specify inner dictionaries. It's confusing, but this
|
|
||||||
example should make sense.
|
|
||||||
|
|
||||||
>>> d = DotExpandedDict({'person.1.firstname': ['Simon'], \
|
|
||||||
'person.1.lastname': ['Willison'], \
|
|
||||||
'person.2.firstname': ['Adrian'], \
|
|
||||||
'person.2.lastname': ['Holovaty']})
|
|
||||||
>>> d
|
|
||||||
{'person': {'1': {'lastname': ['Willison'], 'firstname': ['Simon']}, '2': {'lastname': ['Holovaty'], 'firstname': ['Adrian']}}}
|
|
||||||
>>> d['person']
|
|
||||||
{'1': {'lastname': ['Willison'], 'firstname': ['Simon']}, '2': {'lastname': ['Holovaty'], 'firstname': ['Adrian']}}
|
|
||||||
>>> d['person']['1']
|
|
||||||
{'lastname': ['Willison'], 'firstname': ['Simon']}
|
|
||||||
|
|
||||||
# Gotcha: Results are unpredictable if the dots are "uneven":
|
|
||||||
>>> DotExpandedDict({'c.1': 2, 'c.2': 3, 'c': 1})
|
|
||||||
{'c': 1}
|
|
||||||
"""
|
|
||||||
def __init__(self, key_to_list_mapping):
|
|
||||||
for k, v in key_to_list_mapping.items():
|
|
||||||
current = self
|
|
||||||
bits = k.split('.')
|
|
||||||
for bit in bits[:-1]:
|
|
||||||
current = current.setdefault(bit, {})
|
|
||||||
# Now assign value to current position
|
|
||||||
try:
|
|
||||||
current[bits[-1]] = v
|
|
||||||
except TypeError: # Special-case if current isn't a dict.
|
|
||||||
current = {bits[-1]: v}
|
|
||||||
|
|
||||||
class FileDict(dict):
|
|
||||||
"""
|
|
||||||
A dictionary used to hold uploaded file contents. The only special feature
|
|
||||||
here is that repr() of this object won't dump the entire contents of the
|
|
||||||
file to the output. A handy safeguard for a large file upload.
|
|
||||||
"""
|
|
||||||
def __repr__(self):
|
|
||||||
if 'content' in self:
|
|
||||||
d = dict(self, content='<omitted>')
|
|
||||||
return dict.__repr__(d)
|
|
||||||
return dict.__repr__(self)
|
|
||||||
@ -1,102 +0,0 @@
|
|||||||
import types
|
|
||||||
import urllib
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from functional import Promise
|
|
||||||
from safestring import SafeData, mark_safe
|
|
||||||
|
|
||||||
class DjangoUnicodeDecodeError(UnicodeDecodeError):
|
|
||||||
def __init__(self, obj, *args):
|
|
||||||
self.obj = obj
|
|
||||||
UnicodeDecodeError.__init__(self, *args)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
original = UnicodeDecodeError.__str__(self)
|
|
||||||
return '%s. You passed in %r (%s)' % (original, self.obj,
|
|
||||||
type(self.obj))
|
|
||||||
|
|
||||||
class StrAndUnicode(object):
|
|
||||||
"""
|
|
||||||
A class whose __str__ returns its __unicode__ as a UTF-8 bytestring.
|
|
||||||
|
|
||||||
Useful as a mix-in.
|
|
||||||
"""
|
|
||||||
def __str__(self):
|
|
||||||
return self.__unicode__().encode('utf-8')
|
|
||||||
|
|
||||||
def smart_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
|
|
||||||
"""
|
|
||||||
Returns a unicode object representing 's'. Treats bytestrings using the
|
|
||||||
'encoding' codec.
|
|
||||||
|
|
||||||
If strings_only is True, don't convert (some) non-string-like objects.
|
|
||||||
"""
|
|
||||||
if isinstance(s, Promise):
|
|
||||||
# The input is the result of a gettext_lazy() call.
|
|
||||||
return s
|
|
||||||
return force_unicode(s, encoding, strings_only, errors)
|
|
||||||
|
|
||||||
def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
|
|
||||||
"""
|
|
||||||
Similar to smart_unicode, except that lazy instances are resolved to
|
|
||||||
strings, rather than kept as lazy objects.
|
|
||||||
|
|
||||||
If strings_only is True, don't convert (some) non-string-like objects.
|
|
||||||
"""
|
|
||||||
if strings_only and isinstance(s, (types.NoneType, int, long, datetime.datetime, datetime.date, datetime.time, float)):
|
|
||||||
return s
|
|
||||||
try:
|
|
||||||
if not isinstance(s, basestring,):
|
|
||||||
if hasattr(s, '__unicode__'):
|
|
||||||
s = unicode(s)
|
|
||||||
else:
|
|
||||||
s = unicode(str(s), encoding, errors)
|
|
||||||
elif not isinstance(s, unicode):
|
|
||||||
# Note: We use .decode() here, instead of unicode(s, encoding,
|
|
||||||
# errors), so that if s is a SafeString, it ends up being a
|
|
||||||
# SafeUnicode at the end.
|
|
||||||
s = s.decode(encoding, errors)
|
|
||||||
except UnicodeDecodeError, e:
|
|
||||||
raise DjangoUnicodeDecodeError(s, *e.args)
|
|
||||||
return s
|
|
||||||
|
|
||||||
def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'):
|
|
||||||
"""
|
|
||||||
Returns a bytestring version of 's', encoded as specified in 'encoding'.
|
|
||||||
|
|
||||||
If strings_only is True, don't convert (some) non-string-like objects.
|
|
||||||
"""
|
|
||||||
if strings_only and isinstance(s, (types.NoneType, int)):
|
|
||||||
return s
|
|
||||||
if isinstance(s, Promise):
|
|
||||||
return unicode(s).encode(encoding, errors)
|
|
||||||
elif not isinstance(s, basestring):
|
|
||||||
try:
|
|
||||||
return str(s)
|
|
||||||
except UnicodeEncodeError:
|
|
||||||
return unicode(s).encode(encoding, errors)
|
|
||||||
elif isinstance(s, unicode):
|
|
||||||
return s.encode(encoding, errors)
|
|
||||||
elif s and encoding != 'utf-8':
|
|
||||||
return s.decode('utf-8', errors).encode(encoding, errors)
|
|
||||||
else:
|
|
||||||
return s
|
|
||||||
|
|
||||||
def iri_to_uri(iri):
|
|
||||||
"""
|
|
||||||
Convert an Internationalized Resource Identifier (IRI) portion to a URI
|
|
||||||
portion that is suitable for inclusion in a URL.
|
|
||||||
|
|
||||||
This is the algorithm from section 3.1 of RFC 3987. However, since we are
|
|
||||||
assuming input is either UTF-8 or unicode already, we can simplify things a
|
|
||||||
little from the full method.
|
|
||||||
|
|
||||||
Returns an ASCII string containing the encoded result.
|
|
||||||
"""
|
|
||||||
# The list of safe characters here is constructed from the printable ASCII
|
|
||||||
# characters that are not explicitly excluded by the list at the end of
|
|
||||||
# section 3.1 of RFC 3987.
|
|
||||||
if iri is None:
|
|
||||||
return iri
|
|
||||||
return urllib.quote(smart_str(iri), safe='/#%[]=:;$&()+,!?*')
|
|
||||||
|
|
||||||
@ -1,241 +0,0 @@
|
|||||||
# License for code in this file that was taken from Python 2.5.
|
|
||||||
|
|
||||||
# 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 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.
|
|
||||||
|
|
||||||
|
|
||||||
def curry(_curried_func, *args, **kwargs):
|
|
||||||
def _curried(*moreargs, **morekwargs):
|
|
||||||
return _curried_func(*(args+moreargs), **dict(kwargs, **morekwargs))
|
|
||||||
return _curried
|
|
||||||
|
|
||||||
### Begin from Python 2.5 functools.py ########################################
|
|
||||||
|
|
||||||
# Summary of changes made to the Python 2.5 code below:
|
|
||||||
# * swapped ``partial`` for ``curry`` to maintain backwards-compatibility
|
|
||||||
# in Django.
|
|
||||||
# * Wrapped the ``setattr`` call in ``update_wrapper`` with a try-except
|
|
||||||
# block to make it compatible with Python 2.3, which doesn't allow
|
|
||||||
# assigning to ``__name__``.
|
|
||||||
|
|
||||||
# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007 Python Software Foundation.
|
|
||||||
# All Rights Reserved.
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
# update_wrapper() and wraps() are tools to help write
|
|
||||||
# wrapper functions that can handle naive introspection
|
|
||||||
|
|
||||||
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')
|
|
||||||
WRAPPER_UPDATES = ('__dict__',)
|
|
||||||
def update_wrapper(wrapper,
|
|
||||||
wrapped,
|
|
||||||
assigned = WRAPPER_ASSIGNMENTS,
|
|
||||||
updated = WRAPPER_UPDATES):
|
|
||||||
"""Update a wrapper function to look like the wrapped function
|
|
||||||
|
|
||||||
wrapper is the function to be updated
|
|
||||||
wrapped is the original function
|
|
||||||
assigned is a tuple naming the attributes assigned directly
|
|
||||||
from the wrapped function to the wrapper function (defaults to
|
|
||||||
functools.WRAPPER_ASSIGNMENTS)
|
|
||||||
updated is a tuple naming the attributes off the wrapper that
|
|
||||||
are updated with the corresponding attribute from the wrapped
|
|
||||||
function (defaults to functools.WRAPPER_UPDATES)
|
|
||||||
"""
|
|
||||||
for attr in assigned:
|
|
||||||
try:
|
|
||||||
setattr(wrapper, attr, getattr(wrapped, attr))
|
|
||||||
except TypeError: # Python 2.3 doesn't allow assigning to __name__.
|
|
||||||
pass
|
|
||||||
for attr in updated:
|
|
||||||
getattr(wrapper, attr).update(getattr(wrapped, attr))
|
|
||||||
# Return the wrapper so this can be used as a decorator via curry()
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
def wraps(wrapped,
|
|
||||||
assigned = WRAPPER_ASSIGNMENTS,
|
|
||||||
updated = WRAPPER_UPDATES):
|
|
||||||
"""Decorator factory to apply update_wrapper() to a wrapper function
|
|
||||||
|
|
||||||
Returns a decorator that invokes update_wrapper() with the decorated
|
|
||||||
function as the wrapper argument and the arguments to wraps() as the
|
|
||||||
remaining arguments. Default arguments are as for update_wrapper().
|
|
||||||
This is a convenience function to simplify applying curry() to
|
|
||||||
update_wrapper().
|
|
||||||
"""
|
|
||||||
return curry(update_wrapper, wrapped=wrapped,
|
|
||||||
assigned=assigned, updated=updated)
|
|
||||||
|
|
||||||
### End from Python 2.5 functools.py ##########################################
|
|
||||||
|
|
||||||
def memoize(func, cache, num_args):
|
|
||||||
"""
|
|
||||||
Wrap a function so that results for any argument tuple are stored in
|
|
||||||
'cache'. Note that the args to the function must be usable as dictionary
|
|
||||||
keys.
|
|
||||||
|
|
||||||
Only the first num_args are considered when creating the key.
|
|
||||||
"""
|
|
||||||
def wrapper(*args):
|
|
||||||
mem_args = args[:num_args]
|
|
||||||
if mem_args in cache:
|
|
||||||
return cache[mem_args]
|
|
||||||
result = func(*args)
|
|
||||||
cache[mem_args] = result
|
|
||||||
return result
|
|
||||||
return wraps(func)(wrapper)
|
|
||||||
|
|
||||||
class Promise(object):
|
|
||||||
"""
|
|
||||||
This is just a base class for the proxy class created in
|
|
||||||
the closure of the lazy function. It can be used to recognize
|
|
||||||
promises in code.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def lazy(func, *resultclasses):
|
|
||||||
"""
|
|
||||||
Turns any callable into a lazy evaluated callable. You need to give result
|
|
||||||
classes or types -- at least one is needed so that the automatic forcing of
|
|
||||||
the lazy evaluation code is triggered. Results are not memoized; the
|
|
||||||
function is evaluated on every access.
|
|
||||||
"""
|
|
||||||
class __proxy__(Promise):
|
|
||||||
# This inner class encapsulates the code that should be evaluated
|
|
||||||
# lazily. On calling of one of the magic methods it will force
|
|
||||||
# the evaluation and store the result. Afterwards, the result
|
|
||||||
# is delivered directly. So the result is memoized.
|
|
||||||
def __init__(self, args, kw):
|
|
||||||
self.__func = func
|
|
||||||
self.__args = args
|
|
||||||
self.__kw = kw
|
|
||||||
self.__dispatch = {}
|
|
||||||
for resultclass in resultclasses:
|
|
||||||
self.__dispatch[resultclass] = {}
|
|
||||||
for (k, v) in resultclass.__dict__.items():
|
|
||||||
setattr(self, k, self.__promise__(resultclass, k, v))
|
|
||||||
self._delegate_str = str in resultclasses
|
|
||||||
self._delegate_unicode = unicode in resultclasses
|
|
||||||
assert not (self._delegate_str and self._delegate_unicode), "Cannot call lazy() with both str and unicode return types."
|
|
||||||
if self._delegate_unicode:
|
|
||||||
# Each call to lazy() makes a new __proxy__ object, so this
|
|
||||||
# doesn't interfere with any other lazy() results.
|
|
||||||
__proxy__.__unicode__ = __proxy__.__unicode_cast
|
|
||||||
elif self._delegate_str:
|
|
||||||
__proxy__.__str__ = __proxy__.__str_cast
|
|
||||||
|
|
||||||
def __promise__(self, klass, funcname, func):
|
|
||||||
# Builds a wrapper around some magic method and registers that magic
|
|
||||||
# method for the given type and method name.
|
|
||||||
def __wrapper__(*args, **kw):
|
|
||||||
# Automatically triggers the evaluation of a lazy value and
|
|
||||||
# applies the given magic method of the result type.
|
|
||||||
res = self.__func(*self.__args, **self.__kw)
|
|
||||||
return self.__dispatch[type(res)][funcname](res, *args, **kw)
|
|
||||||
|
|
||||||
if klass not in self.__dispatch:
|
|
||||||
self.__dispatch[klass] = {}
|
|
||||||
self.__dispatch[klass][funcname] = func
|
|
||||||
return __wrapper__
|
|
||||||
|
|
||||||
def __unicode_cast(self):
|
|
||||||
return self.__func(*self.__args, **self.__kw)
|
|
||||||
|
|
||||||
def __str_cast(self):
|
|
||||||
return str(self.__func(*self.__args, **self.__kw))
|
|
||||||
|
|
||||||
def __cmp__(self, rhs):
|
|
||||||
if self._delegate_str:
|
|
||||||
s = str(self.__func(*self.__args, **self.__kw))
|
|
||||||
elif self._delegate_unicode:
|
|
||||||
s = unicode(self.__func(*self.__args, **self.__kw))
|
|
||||||
else:
|
|
||||||
s = self.__func(*self.__args, **self.__kw)
|
|
||||||
if isinstance(rhs, Promise):
|
|
||||||
return -cmp(rhs, s)
|
|
||||||
else:
|
|
||||||
return cmp(s, rhs)
|
|
||||||
|
|
||||||
def __mod__(self, rhs):
|
|
||||||
if self._delegate_str:
|
|
||||||
return str(self) % rhs
|
|
||||||
elif self._delegate_unicode:
|
|
||||||
return unicode(self) % rhs
|
|
||||||
else:
|
|
||||||
raise AssertionError('__mod__ not supported for non-string types')
|
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
|
||||||
# Instances of this class are effectively immutable. It's just a
|
|
||||||
# collection of functions. So we don't need to do anything
|
|
||||||
# complicated for copying.
|
|
||||||
memo[id(self)] = self
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __wrapper__(*args, **kw):
|
|
||||||
# Creates the proxy object, instead of the actual value.
|
|
||||||
return __proxy__(args, kw)
|
|
||||||
|
|
||||||
return wraps(func)(__wrapper__)
|
|
||||||
|
|
||||||
def allow_lazy(func, *resultclasses):
|
|
||||||
"""
|
|
||||||
A decorator that allows a function to be called with one or more lazy
|
|
||||||
arguments. If none of the args are lazy, the function is evaluated
|
|
||||||
immediately, otherwise a __proxy__ is returned that will evaluate the
|
|
||||||
function when needed.
|
|
||||||
"""
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
for arg in list(args) + kwargs.values():
|
|
||||||
if isinstance(arg, Promise):
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
return lazy(func, *resultclasses)(*args, **kwargs)
|
|
||||||
return wraps(func)(wrapper)
|
|
||||||
@ -1,163 +0,0 @@
|
|||||||
"""HTML utilities suitable for global use."""
|
|
||||||
|
|
||||||
import re
|
|
||||||
import string
|
|
||||||
|
|
||||||
from safestring import SafeData, mark_safe
|
|
||||||
from encoding import force_unicode
|
|
||||||
from functional import allow_lazy
|
|
||||||
from http import urlquote
|
|
||||||
|
|
||||||
# Configuration for urlize() function.
|
|
||||||
LEADING_PUNCTUATION = ['(', '<', '<']
|
|
||||||
TRAILING_PUNCTUATION = ['.', ',', ')', '>', '\n', '>']
|
|
||||||
|
|
||||||
# List of possible strings used for bullets in bulleted lists.
|
|
||||||
DOTS = ['·', '*', u'\xe2\x80\xa2', '•', '•', '•']
|
|
||||||
|
|
||||||
unencoded_ampersands_re = re.compile(r'&(?!(\w+|#\d+);)')
|
|
||||||
word_split_re = re.compile(r'(\s+)')
|
|
||||||
punctuation_re = re.compile('^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % \
|
|
||||||
('|'.join([re.escape(x) for x in LEADING_PUNCTUATION]),
|
|
||||||
'|'.join([re.escape(x) for x in TRAILING_PUNCTUATION])))
|
|
||||||
simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
|
|
||||||
link_target_attribute_re = re.compile(r'(<a [^>]*?)target=[^\s>]+')
|
|
||||||
html_gunk_re = re.compile(r'(?:<br clear="all">|<i><\/i>|<b><\/b>|<em><\/em>|<strong><\/strong>|<\/?smallcaps>|<\/?uppercase>)', re.IGNORECASE)
|
|
||||||
hard_coded_bullets_re = re.compile(r'((?:<p>(?:%s).*?[a-zA-Z].*?</p>\s*)+)' % '|'.join([re.escape(x) for x in DOTS]), re.DOTALL)
|
|
||||||
trailing_empty_content_re = re.compile(r'(?:<p>(?: |\s|<br \/>)*?</p>\s*)+\Z')
|
|
||||||
del x # Temporary variable
|
|
||||||
|
|
||||||
def escape(html):
|
|
||||||
"""Returns the given HTML with ampersands, quotes and carets encoded."""
|
|
||||||
return mark_safe(force_unicode(html).replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", '''))
|
|
||||||
escape = allow_lazy(escape, unicode)
|
|
||||||
|
|
||||||
def conditional_escape(html):
|
|
||||||
"""
|
|
||||||
Similar to escape(), except that it doesn't operate on pre-escaped strings.
|
|
||||||
"""
|
|
||||||
if isinstance(html, SafeData):
|
|
||||||
return html
|
|
||||||
else:
|
|
||||||
return escape(html)
|
|
||||||
|
|
||||||
def linebreaks(value, autoescape=False):
|
|
||||||
"""Converts newlines into <p> and <br />s."""
|
|
||||||
value = re.sub(r'\r\n|\r|\n', '\n', force_unicode(value)) # normalize newlines
|
|
||||||
paras = re.split('\n{2,}', value)
|
|
||||||
if autoescape:
|
|
||||||
paras = [u'<p>%s</p>' % escape(p.strip()).replace('\n', '<br />') for p in paras]
|
|
||||||
else:
|
|
||||||
paras = [u'<p>%s</p>' % p.strip().replace('\n', '<br />') for p in paras]
|
|
||||||
return u'\n\n'.join(paras)
|
|
||||||
linebreaks = allow_lazy(linebreaks, unicode)
|
|
||||||
|
|
||||||
def strip_tags(value):
|
|
||||||
"""Returns the given HTML with all tags stripped."""
|
|
||||||
return re.sub(r'<[^>]*?>', '', force_unicode(value))
|
|
||||||
strip_tags = allow_lazy(strip_tags)
|
|
||||||
|
|
||||||
def strip_spaces_between_tags(value):
|
|
||||||
"""Returns the given HTML with spaces between tags removed."""
|
|
||||||
return re.sub(r'>\s+<', '><', force_unicode(value))
|
|
||||||
strip_spaces_between_tags = allow_lazy(strip_spaces_between_tags, unicode)
|
|
||||||
|
|
||||||
def strip_entities(value):
|
|
||||||
"""Returns the given HTML with all entities (&something;) stripped."""
|
|
||||||
return re.sub(r'&(?:\w+|#\d+);', '', force_unicode(value))
|
|
||||||
strip_entities = allow_lazy(strip_entities, unicode)
|
|
||||||
|
|
||||||
def fix_ampersands(value):
|
|
||||||
"""Returns the given HTML with all unencoded ampersands encoded correctly."""
|
|
||||||
return unencoded_ampersands_re.sub('&', force_unicode(value))
|
|
||||||
fix_ampersands = allow_lazy(fix_ampersands, unicode)
|
|
||||||
|
|
||||||
def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False):
|
|
||||||
"""
|
|
||||||
Converts any URLs in text into clickable links.
|
|
||||||
|
|
||||||
Works on http://, https://, and www. links. Links can have trailing
|
|
||||||
punctuation (periods, commas, close-parens) and leading punctuation
|
|
||||||
(opening parens) and it'll still do the right thing.
|
|
||||||
|
|
||||||
If trim_url_limit is not None, the URLs in link text longer than this limit
|
|
||||||
will truncated to trim_url_limit-3 characters and appended with an elipsis.
|
|
||||||
|
|
||||||
If nofollow is True, the URLs in link text will get a rel="nofollow"
|
|
||||||
attribute.
|
|
||||||
"""
|
|
||||||
if autoescape:
|
|
||||||
trim_url = lambda x, limit=trim_url_limit: conditional_escape(limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x)
|
|
||||||
else:
|
|
||||||
trim_url = lambda x, limit=trim_url_limit: limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x
|
|
||||||
safe_input = isinstance(text, SafeData)
|
|
||||||
words = word_split_re.split(force_unicode(text))
|
|
||||||
nofollow_attr = nofollow and ' rel="nofollow"' or ''
|
|
||||||
for i, word in enumerate(words):
|
|
||||||
match = punctuation_re.match(word)
|
|
||||||
if match:
|
|
||||||
lead, middle, trail = match.groups()
|
|
||||||
if safe_input:
|
|
||||||
middle = mark_safe(middle)
|
|
||||||
if middle.startswith('www.') or ('@' not in middle and not middle.startswith('http://') and \
|
|
||||||
len(middle) > 0 and middle[0] in string.ascii_letters + string.digits and \
|
|
||||||
(middle.endswith('.org') or middle.endswith('.net') or middle.endswith('.com'))):
|
|
||||||
middle = 'http://%s' % middle
|
|
||||||
if middle.startswith('http://') or middle.startswith('https://'):
|
|
||||||
url = urlquote(middle, safe='/&=:;#?+*')
|
|
||||||
if autoescape and not safe_input:
|
|
||||||
url = escape(url)
|
|
||||||
trimmed_url = trim_url(middle)
|
|
||||||
middle = '<a href="%s"%s>%s</a>' % (url, nofollow_attr,
|
|
||||||
trimmed_url)
|
|
||||||
elif '@' in middle and not middle.startswith('www.') and \
|
|
||||||
not ':' in middle and simple_email_re.match(middle):
|
|
||||||
if autoescape:
|
|
||||||
middle = conditional_escape(middle)
|
|
||||||
middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
|
|
||||||
if lead + middle + trail != word:
|
|
||||||
if autoescape and not safe_input:
|
|
||||||
lead, trail = escape(lead), escape(trail)
|
|
||||||
words[i] = mark_safe('%s%s%s' % (lead, middle, trail))
|
|
||||||
elif autoescape and not safe_input:
|
|
||||||
words[i] = escape(word)
|
|
||||||
elif safe_input:
|
|
||||||
words[i] = mark_safe(word)
|
|
||||||
elif autoescape:
|
|
||||||
words[i] = escape(word)
|
|
||||||
return u''.join(words)
|
|
||||||
urlize = allow_lazy(urlize, unicode)
|
|
||||||
|
|
||||||
def clean_html(text):
|
|
||||||
"""
|
|
||||||
Clean the given HTML. Specifically, do the following:
|
|
||||||
* Convert <b> and <i> to <strong> and <em>.
|
|
||||||
* Encode all ampersands correctly.
|
|
||||||
* Remove all "target" attributes from <a> tags.
|
|
||||||
* Remove extraneous HTML, such as presentational tags that open and
|
|
||||||
immediately close and <br clear="all">.
|
|
||||||
* Convert hard-coded bullets into HTML unordered lists.
|
|
||||||
* Remove stuff like "<p> </p>", but only if it's at the
|
|
||||||
bottom of the text.
|
|
||||||
"""
|
|
||||||
from django.utils.text import normalize_newlines
|
|
||||||
text = normalize_newlines(force_unicode(text))
|
|
||||||
text = re.sub(r'<(/?)\s*b\s*>', '<\\1strong>', text)
|
|
||||||
text = re.sub(r'<(/?)\s*i\s*>', '<\\1em>', text)
|
|
||||||
text = fix_ampersands(text)
|
|
||||||
# Remove all target="" attributes from <a> tags.
|
|
||||||
text = link_target_attribute_re.sub('\\1', text)
|
|
||||||
# Trim stupid HTML such as <br clear="all">.
|
|
||||||
text = html_gunk_re.sub('', text)
|
|
||||||
# Convert hard-coded bullets into HTML unordered lists.
|
|
||||||
def replace_p_tags(match):
|
|
||||||
s = match.group().replace('</p>', '</li>')
|
|
||||||
for d in DOTS:
|
|
||||||
s = s.replace('<p>%s' % d, '<li>')
|
|
||||||
return u'<ul>\n%s\n</ul>' % s
|
|
||||||
text = hard_coded_bullets_re.sub(replace_p_tags, text)
|
|
||||||
# Remove stuff like "<p> </p>", but only if it's at the bottom
|
|
||||||
# of the text.
|
|
||||||
text = trailing_empty_content_re.sub('', text)
|
|
||||||
return text
|
|
||||||
clean_html = allow_lazy(clean_html, unicode)
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
import urllib
|
|
||||||
from email.Utils import formatdate
|
|
||||||
|
|
||||||
from encoding import smart_str, force_unicode
|
|
||||||
from functional import allow_lazy
|
|
||||||
|
|
||||||
def urlquote(url, safe='/'):
|
|
||||||
"""
|
|
||||||
A version of Python's urllib.quote() function that can operate on unicode
|
|
||||||
strings. The url is first UTF-8 encoded before quoting. The returned string
|
|
||||||
can safely be used as part of an argument to a subsequent iri_to_uri() call
|
|
||||||
without double-quoting occurring.
|
|
||||||
"""
|
|
||||||
return force_unicode(urllib.quote(smart_str(url), safe))
|
|
||||||
|
|
||||||
urlquote = allow_lazy(urlquote, unicode)
|
|
||||||
|
|
||||||
def urlquote_plus(url, safe=''):
|
|
||||||
"""
|
|
||||||
A version of Python's urllib.quote_plus() function that can operate on
|
|
||||||
unicode strings. The url is first UTF-8 encoded before quoting. The
|
|
||||||
returned string can safely be used as part of an argument to a subsequent
|
|
||||||
iri_to_uri() call without double-quoting occurring.
|
|
||||||
"""
|
|
||||||
return force_unicode(urllib.quote_plus(smart_str(url), safe))
|
|
||||||
urlquote_plus = allow_lazy(urlquote_plus, unicode)
|
|
||||||
|
|
||||||
def urlencode(query, doseq=0):
|
|
||||||
"""
|
|
||||||
A version of Python's urllib.urlencode() function that can operate on
|
|
||||||
unicode strings. The parameters are first case to UTF-8 encoded strings and
|
|
||||||
then encoded as per normal.
|
|
||||||
"""
|
|
||||||
if hasattr(query, 'items'):
|
|
||||||
query = query.items()
|
|
||||||
return urllib.urlencode(
|
|
||||||
[(smart_str(k),
|
|
||||||
isinstance(v, (list,tuple)) and [smart_str(i) for i in v] or smart_str(v))
|
|
||||||
for k, v in query],
|
|
||||||
doseq)
|
|
||||||
|
|
||||||
def cookie_date(epoch_seconds=None):
|
|
||||||
"""
|
|
||||||
Formats the time to ensure compatibility with Netscape's cookie standard.
|
|
||||||
|
|
||||||
Accepts a floating point number expressed in seconds since the epoch, in
|
|
||||||
UTC - such as that outputted by time.time(). If set to None, defaults to
|
|
||||||
the current time.
|
|
||||||
|
|
||||||
Outputs a string in the format 'Wdy, DD-Mon-YYYY HH:MM:SS GMT'.
|
|
||||||
"""
|
|
||||||
rfcdate = formatdate(epoch_seconds)
|
|
||||||
return '%s-%s-%s GMT' % (rfcdate[:7], rfcdate[8:11], rfcdate[12:25])
|
|
||||||
|
|
||||||
def http_date(epoch_seconds=None):
|
|
||||||
"""
|
|
||||||
Formats the time to match the RFC1123 date format as specified by HTTP
|
|
||||||
RFC2616 section 3.3.1.
|
|
||||||
|
|
||||||
Accepts a floating point number expressed in seconds since the epoch, in
|
|
||||||
UTC - such as that outputted by time.time(). If set to None, defaults to
|
|
||||||
the current time.
|
|
||||||
|
|
||||||
Outputs a string in the format 'Wdy, DD Mon YYYY HH:MM:SS GMT'.
|
|
||||||
"""
|
|
||||||
rfcdate = formatdate(epoch_seconds)
|
|
||||||
return '%s GMT' % rfcdate[:25]
|
|
||||||
@ -1,119 +0,0 @@
|
|||||||
"""
|
|
||||||
Functions for working with "safe strings": strings that can be displayed safely
|
|
||||||
without further escaping in HTML. Marking something as a "safe string" means
|
|
||||||
that the producer of the string has already turned characters that should not
|
|
||||||
be interpreted by the HTML engine (e.g. '<') into the appropriate entities.
|
|
||||||
"""
|
|
||||||
from functional import curry, Promise
|
|
||||||
|
|
||||||
class EscapeData(object):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class EscapeString(str, EscapeData):
|
|
||||||
"""
|
|
||||||
A string that should be HTML-escaped when output.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class EscapeUnicode(unicode, EscapeData):
|
|
||||||
"""
|
|
||||||
A unicode object that should be HTML-escaped when output.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class SafeData(object):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class SafeString(str, SafeData):
|
|
||||||
"""
|
|
||||||
A string subclass that has been specifically marked as "safe" (requires no
|
|
||||||
further escaping) for HTML output purposes.
|
|
||||||
"""
|
|
||||||
def __add__(self, rhs):
|
|
||||||
"""
|
|
||||||
Concatenating a safe string with another safe string or safe unicode
|
|
||||||
object is safe. Otherwise, the result is no longer safe.
|
|
||||||
"""
|
|
||||||
t = super(SafeString, self).__add__(rhs)
|
|
||||||
if isinstance(rhs, SafeUnicode):
|
|
||||||
return SafeUnicode(t)
|
|
||||||
elif isinstance(rhs, SafeString):
|
|
||||||
return SafeString(t)
|
|
||||||
return t
|
|
||||||
|
|
||||||
def _proxy_method(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Wrap a call to a normal unicode method up so that we return safe
|
|
||||||
results. The method that is being wrapped is passed in the 'method'
|
|
||||||
argument.
|
|
||||||
"""
|
|
||||||
method = kwargs.pop('method')
|
|
||||||
data = method(self, *args, **kwargs)
|
|
||||||
if isinstance(data, str):
|
|
||||||
return SafeString(data)
|
|
||||||
else:
|
|
||||||
return SafeUnicode(data)
|
|
||||||
|
|
||||||
decode = curry(_proxy_method, method = str.decode)
|
|
||||||
|
|
||||||
class SafeUnicode(unicode, SafeData):
|
|
||||||
"""
|
|
||||||
A unicode subclass that has been specifically marked as "safe" for HTML
|
|
||||||
output purposes.
|
|
||||||
"""
|
|
||||||
def __add__(self, rhs):
|
|
||||||
"""
|
|
||||||
Concatenating a safe unicode object with another safe string or safe
|
|
||||||
unicode object is safe. Otherwise, the result is no longer safe.
|
|
||||||
"""
|
|
||||||
t = super(SafeUnicode, self).__add__(rhs)
|
|
||||||
if isinstance(rhs, SafeData):
|
|
||||||
return SafeUnicode(t)
|
|
||||||
return t
|
|
||||||
|
|
||||||
def _proxy_method(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Wrap a call to a normal unicode method up so that we return safe
|
|
||||||
results. The method that is being wrapped is passed in the 'method'
|
|
||||||
argument.
|
|
||||||
"""
|
|
||||||
method = kwargs.pop('method')
|
|
||||||
data = method(self, *args, **kwargs)
|
|
||||||
if isinstance(data, str):
|
|
||||||
return SafeString(data)
|
|
||||||
else:
|
|
||||||
return SafeUnicode(data)
|
|
||||||
|
|
||||||
encode = curry(_proxy_method, method = unicode.encode)
|
|
||||||
|
|
||||||
def mark_safe(s):
|
|
||||||
"""
|
|
||||||
Explicitly mark a string as safe for (HTML) output purposes. The returned
|
|
||||||
object can be used everywhere a string or unicode object is appropriate.
|
|
||||||
|
|
||||||
Can be called multiple times on a single string.
|
|
||||||
"""
|
|
||||||
if isinstance(s, SafeData):
|
|
||||||
return s
|
|
||||||
if isinstance(s, str) or (isinstance(s, Promise) and s._delegate_str):
|
|
||||||
return SafeString(s)
|
|
||||||
if isinstance(s, (unicode, Promise)):
|
|
||||||
return SafeUnicode(s)
|
|
||||||
return SafeString(str(s))
|
|
||||||
|
|
||||||
def mark_for_escaping(s):
|
|
||||||
"""
|
|
||||||
Explicitly mark a string as requiring HTML escaping upon output. Has no
|
|
||||||
effect on SafeData subclasses.
|
|
||||||
|
|
||||||
Can be called multiple times on a single string (the resulting escaping is
|
|
||||||
only applied once).
|
|
||||||
"""
|
|
||||||
if isinstance(s, (SafeData, EscapeData)):
|
|
||||||
return s
|
|
||||||
if isinstance(s, str) or (isinstance(s, Promise) and s._delegate_str):
|
|
||||||
return EscapeString(s)
|
|
||||||
if isinstance(s, (unicode, Promise)):
|
|
||||||
return EscapeUnicode(s)
|
|
||||||
return EscapeString(str(s))
|
|
||||||
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
try:
|
|
||||||
_('translate something')
|
|
||||||
except:
|
|
||||||
import gettext
|
|
||||||
gettext.install('locale')
|
|
||||||
|
|
||||||
ugettext = _
|
|
||||||
ugettext_lazy = _
|
|
||||||
|
|
||||||
@ -1,784 +0,0 @@
|
|||||||
"""
|
|
||||||
Field classes.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import copy
|
|
||||||
import datetime
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import time
|
|
||||||
# Python 2.3 fallbacks
|
|
||||||
try:
|
|
||||||
from decimal import Decimal, DecimalException
|
|
||||||
except ImportError:
|
|
||||||
from django.utils._decimal import Decimal, DecimalException
|
|
||||||
try:
|
|
||||||
set
|
|
||||||
except NameError:
|
|
||||||
from sets import Set as set
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.utils.encoding import StrAndUnicode, smart_unicode, smart_str
|
|
||||||
|
|
||||||
from util import ErrorList, ValidationError
|
|
||||||
from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
|
||||||
'Field', 'CharField', 'IntegerField',
|
|
||||||
'DEFAULT_DATE_INPUT_FORMATS', 'DateField',
|
|
||||||
'DEFAULT_TIME_INPUT_FORMATS', 'TimeField',
|
|
||||||
'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField',
|
|
||||||
'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
|
|
||||||
'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
|
|
||||||
'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
|
|
||||||
'SplitDateTimeField', 'IPAddressField', 'FilePathField',
|
|
||||||
)
|
|
||||||
|
|
||||||
# These values, if given to to_python(), will trigger the self.required check.
|
|
||||||
EMPTY_VALUES = (None, '')
|
|
||||||
|
|
||||||
|
|
||||||
class Field(object):
|
|
||||||
widget = TextInput # Default widget to use when rendering this type of Field.
|
|
||||||
hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden".
|
|
||||||
default_error_messages = {
|
|
||||||
'required': _(u'This field is required.'),
|
|
||||||
'invalid': _(u'Enter a valid value.'),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Tracks each time a Field instance is created. Used to retain order.
|
|
||||||
creation_counter = 0
|
|
||||||
|
|
||||||
def __init__(self, required=True, widget=None, label=None, initial=None,
|
|
||||||
help_text=None, error_messages=None):
|
|
||||||
# required -- Boolean that specifies whether the field is required.
|
|
||||||
# True by default.
|
|
||||||
# widget -- A Widget class, or instance of a Widget class, that should
|
|
||||||
# be used for this Field when displaying it. Each Field has a
|
|
||||||
# default Widget that it'll use if you don't specify this. In
|
|
||||||
# most cases, the default widget is TextInput.
|
|
||||||
# label -- A verbose name for this field, for use in displaying this
|
|
||||||
# field in a form. By default, Django will use a "pretty"
|
|
||||||
# version of the form field name, if the Field is part of a
|
|
||||||
# Form.
|
|
||||||
# initial -- A value to use in this Field's initial display. This value
|
|
||||||
# is *not* used as a fallback if data isn't given.
|
|
||||||
# help_text -- An optional string to use as "help text" for this Field.
|
|
||||||
if label is not None:
|
|
||||||
label = smart_unicode(label)
|
|
||||||
self.required, self.label, self.initial = required, label, initial
|
|
||||||
self.help_text = smart_unicode(help_text or '')
|
|
||||||
widget = widget or self.widget
|
|
||||||
if isinstance(widget, type):
|
|
||||||
widget = widget()
|
|
||||||
|
|
||||||
# Hook into self.widget_attrs() for any Field-specific HTML attributes.
|
|
||||||
extra_attrs = self.widget_attrs(widget)
|
|
||||||
if extra_attrs:
|
|
||||||
widget.attrs.update(extra_attrs)
|
|
||||||
|
|
||||||
self.widget = widget
|
|
||||||
|
|
||||||
# Increase the creation counter, and save our local copy.
|
|
||||||
self.creation_counter = Field.creation_counter
|
|
||||||
Field.creation_counter += 1
|
|
||||||
|
|
||||||
def set_class_error_messages(messages, klass):
|
|
||||||
for base_class in klass.__bases__:
|
|
||||||
set_class_error_messages(messages, base_class)
|
|
||||||
messages.update(getattr(klass, 'default_error_messages', {}))
|
|
||||||
|
|
||||||
messages = {}
|
|
||||||
set_class_error_messages(messages, self.__class__)
|
|
||||||
messages.update(error_messages or {})
|
|
||||||
self.error_messages = messages
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
"""
|
|
||||||
Validates the given value and returns its "cleaned" value as an
|
|
||||||
appropriate Python object.
|
|
||||||
|
|
||||||
Raises ValidationError for any errors.
|
|
||||||
"""
|
|
||||||
if self.required and value in EMPTY_VALUES:
|
|
||||||
raise ValidationError(self.error_messages['required'])
|
|
||||||
return value
|
|
||||||
|
|
||||||
def widget_attrs(self, widget):
|
|
||||||
"""
|
|
||||||
Given a Widget instance (*not* a Widget class), returns a dictionary of
|
|
||||||
any HTML attributes that should be added to the Widget, based on this
|
|
||||||
Field.
|
|
||||||
"""
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
|
||||||
result = copy.copy(self)
|
|
||||||
memo[id(self)] = result
|
|
||||||
result.widget = copy.deepcopy(self.widget, memo)
|
|
||||||
return result
|
|
||||||
|
|
||||||
class CharField(Field):
|
|
||||||
default_error_messages = {
|
|
||||||
'max_length': _(u'Ensure this value has at most %(max)d characters (it has %(length)d).'),
|
|
||||||
'min_length': _(u'Ensure this value has at least %(min)d characters (it has %(length)d).'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, max_length=None, min_length=None, *args, **kwargs):
|
|
||||||
self.max_length, self.min_length = max_length, min_length
|
|
||||||
super(CharField, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
"Validates max_length and min_length. Returns a Unicode object."
|
|
||||||
super(CharField, self).clean(value)
|
|
||||||
if value in EMPTY_VALUES:
|
|
||||||
return u''
|
|
||||||
value = smart_unicode(value)
|
|
||||||
value_length = len(value)
|
|
||||||
if self.max_length is not None and value_length > self.max_length:
|
|
||||||
raise ValidationError(self.error_messages['max_length'] % {'max': self.max_length, 'length': value_length})
|
|
||||||
if self.min_length is not None and value_length < self.min_length:
|
|
||||||
raise ValidationError(self.error_messages['min_length'] % {'min': self.min_length, 'length': value_length})
|
|
||||||
return value
|
|
||||||
|
|
||||||
def widget_attrs(self, widget):
|
|
||||||
if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)):
|
|
||||||
# The HTML attribute is maxlength, not max_length.
|
|
||||||
return {'maxlength': str(self.max_length)}
|
|
||||||
|
|
||||||
class IntegerField(Field):
|
|
||||||
default_error_messages = {
|
|
||||||
'invalid': _(u'Enter a whole number.'),
|
|
||||||
'max_value': _(u'Ensure this value is less than or equal to %s.'),
|
|
||||||
'min_value': _(u'Ensure this value is greater than or equal to %s.'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, max_value=None, min_value=None, *args, **kwargs):
|
|
||||||
self.max_value, self.min_value = max_value, min_value
|
|
||||||
super(IntegerField, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
"""
|
|
||||||
Validates that int() can be called on the input. Returns the result
|
|
||||||
of int(). Returns None for empty values.
|
|
||||||
"""
|
|
||||||
super(IntegerField, self).clean(value)
|
|
||||||
if value in EMPTY_VALUES:
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
value = int(str(value))
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
raise ValidationError(self.error_messages['invalid'])
|
|
||||||
if self.max_value is not None and value > self.max_value:
|
|
||||||
raise ValidationError(self.error_messages['max_value'] % self.max_value)
|
|
||||||
if self.min_value is not None and value < self.min_value:
|
|
||||||
raise ValidationError(self.error_messages['min_value'] % self.min_value)
|
|
||||||
return value
|
|
||||||
|
|
||||||
class FloatField(Field):
|
|
||||||
default_error_messages = {
|
|
||||||
'invalid': _(u'Enter a number.'),
|
|
||||||
'max_value': _(u'Ensure this value is less than or equal to %s.'),
|
|
||||||
'min_value': _(u'Ensure this value is greater than or equal to %s.'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, max_value=None, min_value=None, *args, **kwargs):
|
|
||||||
self.max_value, self.min_value = max_value, min_value
|
|
||||||
Field.__init__(self, *args, **kwargs)
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
"""
|
|
||||||
Validates that float() can be called on the input. Returns a float.
|
|
||||||
Returns None for empty values.
|
|
||||||
"""
|
|
||||||
super(FloatField, self).clean(value)
|
|
||||||
if not self.required and value in EMPTY_VALUES:
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
value = float(value)
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
raise ValidationError(self.error_messages['invalid'])
|
|
||||||
if self.max_value is not None and value > self.max_value:
|
|
||||||
raise ValidationError(self.error_messages['max_value'] % self.max_value)
|
|
||||||
if self.min_value is not None and value < self.min_value:
|
|
||||||
raise ValidationError(self.error_messages['min_value'] % self.min_value)
|
|
||||||
return value
|
|
||||||
|
|
||||||
class DecimalField(Field):
|
|
||||||
default_error_messages = {
|
|
||||||
'invalid': _(u'Enter a number.'),
|
|
||||||
'max_value': _(u'Ensure this value is less than or equal to %s.'),
|
|
||||||
'min_value': _(u'Ensure this value is greater than or equal to %s.'),
|
|
||||||
'max_digits': _('Ensure that there are no more than %s digits in total.'),
|
|
||||||
'max_decimal_places': _('Ensure that there are no more than %s decimal places.'),
|
|
||||||
'max_whole_digits': _('Ensure that there are no more than %s digits before the decimal point.')
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs):
|
|
||||||
self.max_value, self.min_value = max_value, min_value
|
|
||||||
self.max_digits, self.decimal_places = max_digits, decimal_places
|
|
||||||
Field.__init__(self, *args, **kwargs)
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
"""
|
|
||||||
Validates that the input is a decimal number. Returns a Decimal
|
|
||||||
instance. Returns None for empty values. Ensures that there are no more
|
|
||||||
than max_digits in the number, and no more than decimal_places digits
|
|
||||||
after the decimal point.
|
|
||||||
"""
|
|
||||||
super(DecimalField, self).clean(value)
|
|
||||||
if not self.required and value in EMPTY_VALUES:
|
|
||||||
return None
|
|
||||||
value = smart_str(value).strip()
|
|
||||||
try:
|
|
||||||
value = Decimal(value)
|
|
||||||
except DecimalException:
|
|
||||||
raise ValidationError(self.error_messages['invalid'])
|
|
||||||
pieces = str(value).lstrip("-").split('.')
|
|
||||||
decimals = (len(pieces) == 2) and len(pieces[1]) or 0
|
|
||||||
digits = len(pieces[0])
|
|
||||||
if self.max_value is not None and value > self.max_value:
|
|
||||||
raise ValidationError(self.error_messages['max_value'] % self.max_value)
|
|
||||||
if self.min_value is not None and value < self.min_value:
|
|
||||||
raise ValidationError(self.error_messages['min_value'] % self.min_value)
|
|
||||||
if self.max_digits is not None and (digits + decimals) > self.max_digits:
|
|
||||||
raise ValidationError(self.error_messages['max_digits'] % self.max_digits)
|
|
||||||
if self.decimal_places is not None and decimals > self.decimal_places:
|
|
||||||
raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places)
|
|
||||||
if self.max_digits is not None and self.decimal_places is not None and digits > (self.max_digits - self.decimal_places):
|
|
||||||
raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places))
|
|
||||||
return value
|
|
||||||
|
|
||||||
DEFAULT_DATE_INPUT_FORMATS = (
|
|
||||||
'%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
|
|
||||||
'%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006'
|
|
||||||
'%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 2006'
|
|
||||||
'%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006'
|
|
||||||
'%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006'
|
|
||||||
)
|
|
||||||
|
|
||||||
class DateField(Field):
|
|
||||||
default_error_messages = {
|
|
||||||
'invalid': _(u'Enter a valid date.'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, input_formats=None, *args, **kwargs):
|
|
||||||
super(DateField, self).__init__(*args, **kwargs)
|
|
||||||
self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
"""
|
|
||||||
Validates that the input can be converted to a date. Returns a Python
|
|
||||||
datetime.date object.
|
|
||||||
"""
|
|
||||||
super(DateField, self).clean(value)
|
|
||||||
if value in EMPTY_VALUES:
|
|
||||||
return None
|
|
||||||
if isinstance(value, datetime.datetime):
|
|
||||||
return value.date()
|
|
||||||
if isinstance(value, datetime.date):
|
|
||||||
return value
|
|
||||||
for format in self.input_formats:
|
|
||||||
try:
|
|
||||||
return datetime.date(*time.strptime(value, format)[:3])
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
raise ValidationError(self.error_messages['invalid'])
|
|
||||||
|
|
||||||
DEFAULT_TIME_INPUT_FORMATS = (
|
|
||||||
'%H:%M:%S', # '14:30:59'
|
|
||||||
'%H:%M', # '14:30'
|
|
||||||
)
|
|
||||||
|
|
||||||
class TimeField(Field):
|
|
||||||
default_error_messages = {
|
|
||||||
'invalid': _(u'Enter a valid time.')
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, input_formats=None, *args, **kwargs):
|
|
||||||
super(TimeField, self).__init__(*args, **kwargs)
|
|
||||||
self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
"""
|
|
||||||
Validates that the input can be converted to a time. Returns a Python
|
|
||||||
datetime.time object.
|
|
||||||
"""
|
|
||||||
super(TimeField, self).clean(value)
|
|
||||||
if value in EMPTY_VALUES:
|
|
||||||
return None
|
|
||||||
if isinstance(value, datetime.time):
|
|
||||||
return value
|
|
||||||
for format in self.input_formats:
|
|
||||||
try:
|
|
||||||
return datetime.time(*time.strptime(value, format)[3:6])
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
raise ValidationError(self.error_messages['invalid'])
|
|
||||||
|
|
||||||
DEFAULT_DATETIME_INPUT_FORMATS = (
|
|
||||||
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
|
|
||||||
'%Y-%m-%d %H:%M', # '2006-10-25 14:30'
|
|
||||||
'%Y-%m-%d', # '2006-10-25'
|
|
||||||
'%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59'
|
|
||||||
'%m/%d/%Y %H:%M', # '10/25/2006 14:30'
|
|
||||||
'%m/%d/%Y', # '10/25/2006'
|
|
||||||
'%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59'
|
|
||||||
'%m/%d/%y %H:%M', # '10/25/06 14:30'
|
|
||||||
'%m/%d/%y', # '10/25/06'
|
|
||||||
)
|
|
||||||
|
|
||||||
class DateTimeField(Field):
|
|
||||||
widget = DateTimeInput
|
|
||||||
default_error_messages = {
|
|
||||||
'invalid': _(u'Enter a valid date/time.'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, input_formats=None, *args, **kwargs):
|
|
||||||
super(DateTimeField, self).__init__(*args, **kwargs)
|
|
||||||
self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
"""
|
|
||||||
Validates that the input can be converted to a datetime. Returns a
|
|
||||||
Python datetime.datetime object.
|
|
||||||
"""
|
|
||||||
super(DateTimeField, self).clean(value)
|
|
||||||
if value in EMPTY_VALUES:
|
|
||||||
return None
|
|
||||||
if isinstance(value, datetime.datetime):
|
|
||||||
return value
|
|
||||||
if isinstance(value, datetime.date):
|
|
||||||
return datetime.datetime(value.year, value.month, value.day)
|
|
||||||
if isinstance(value, list):
|
|
||||||
# Input comes from a SplitDateTimeWidget, for example. So, it's two
|
|
||||||
# components: date and time.
|
|
||||||
if len(value) != 2:
|
|
||||||
raise ValidationError(self.error_messages['invalid'])
|
|
||||||
value = '%s %s' % tuple(value)
|
|
||||||
for format in self.input_formats:
|
|
||||||
try:
|
|
||||||
return datetime.datetime(*time.strptime(value, format)[:6])
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
raise ValidationError(self.error_messages['invalid'])
|
|
||||||
|
|
||||||
class RegexField(CharField):
|
|
||||||
def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
regex can be either a string or a compiled regular expression object.
|
|
||||||
error_message is an optional error message to use, if
|
|
||||||
'Enter a valid value' is too generic for you.
|
|
||||||
"""
|
|
||||||
# error_message is just kept for backwards compatibility:
|
|
||||||
if error_message:
|
|
||||||
error_messages = kwargs.get('error_messages') or {}
|
|
||||||
error_messages['invalid'] = error_message
|
|
||||||
kwargs['error_messages'] = error_messages
|
|
||||||
super(RegexField, self).__init__(max_length, min_length, *args, **kwargs)
|
|
||||||
if isinstance(regex, basestring):
|
|
||||||
regex = re.compile(regex)
|
|
||||||
self.regex = regex
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
"""
|
|
||||||
Validates that the input matches the regular expression. Returns a
|
|
||||||
Unicode object.
|
|
||||||
"""
|
|
||||||
value = super(RegexField, self).clean(value)
|
|
||||||
if value == u'':
|
|
||||||
return value
|
|
||||||
if not self.regex.search(value):
|
|
||||||
raise ValidationError(self.error_messages['invalid'])
|
|
||||||
return value
|
|
||||||
|
|
||||||
email_re = re.compile(
|
|
||||||
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom
|
|
||||||
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string
|
|
||||||
r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain
|
|
||||||
|
|
||||||
class EmailField(RegexField):
|
|
||||||
default_error_messages = {
|
|
||||||
'invalid': _(u'Enter a valid e-mail address.'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, max_length=None, min_length=None, *args, **kwargs):
|
|
||||||
RegexField.__init__(self, email_re, max_length, min_length, *args,
|
|
||||||
**kwargs)
|
|
||||||
|
|
||||||
try:
|
|
||||||
from django.conf import settings
|
|
||||||
URL_VALIDATOR_USER_AGENT = settings.URL_VALIDATOR_USER_AGENT
|
|
||||||
except ImportError:
|
|
||||||
# It's OK if Django settings aren't configured.
|
|
||||||
URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)'
|
|
||||||
|
|
||||||
class UploadedFile(StrAndUnicode):
|
|
||||||
"A wrapper for files uploaded in a FileField"
|
|
||||||
def __init__(self, filename, content):
|
|
||||||
self.filename = filename
|
|
||||||
self.content = content
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
"""
|
|
||||||
The unicode representation is the filename, so that the pre-database-insertion
|
|
||||||
logic can use UploadedFile objects
|
|
||||||
"""
|
|
||||||
return self.filename
|
|
||||||
|
|
||||||
class FileField(Field):
|
|
||||||
widget = FileInput
|
|
||||||
default_error_messages = {
|
|
||||||
'invalid': _(u"No file was submitted. Check the encoding type on the form."),
|
|
||||||
'missing': _(u"No file was submitted."),
|
|
||||||
'empty': _(u"The submitted file is empty."),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(FileField, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def clean(self, data, initial=None):
|
|
||||||
super(FileField, self).clean(initial or data)
|
|
||||||
if not self.required and data in EMPTY_VALUES:
|
|
||||||
return None
|
|
||||||
elif not data and initial:
|
|
||||||
return initial
|
|
||||||
try:
|
|
||||||
f = UploadedFile(data['filename'], data['content'])
|
|
||||||
except TypeError:
|
|
||||||
raise ValidationError(self.error_messages['invalid'])
|
|
||||||
except KeyError:
|
|
||||||
raise ValidationError(self.error_messages['missing'])
|
|
||||||
if not f.content:
|
|
||||||
raise ValidationError(self.error_messages['empty'])
|
|
||||||
return f
|
|
||||||
|
|
||||||
class ImageField(FileField):
|
|
||||||
default_error_messages = {
|
|
||||||
'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."),
|
|
||||||
}
|
|
||||||
|
|
||||||
def clean(self, data, initial=None):
|
|
||||||
"""
|
|
||||||
Checks that the file-upload field data contains a valid image (GIF, JPG,
|
|
||||||
PNG, possibly others -- whatever the Python Imaging Library supports).
|
|
||||||
"""
|
|
||||||
f = super(ImageField, self).clean(data, initial)
|
|
||||||
if f is None:
|
|
||||||
return None
|
|
||||||
elif not data and initial:
|
|
||||||
return initial
|
|
||||||
from PIL import Image
|
|
||||||
from cStringIO import StringIO
|
|
||||||
try:
|
|
||||||
# load() is the only method that can spot a truncated JPEG,
|
|
||||||
# but it cannot be called sanely after verify()
|
|
||||||
trial_image = Image.open(StringIO(f.content))
|
|
||||||
trial_image.load()
|
|
||||||
# verify() is the only method that can spot a corrupt PNG,
|
|
||||||
# but it must be called immediately after the constructor
|
|
||||||
trial_image = Image.open(StringIO(f.content))
|
|
||||||
trial_image.verify()
|
|
||||||
except Exception: # Python Imaging Library doesn't recognize it as an image
|
|
||||||
raise ValidationError(self.error_messages['invalid_image'])
|
|
||||||
return f
|
|
||||||
|
|
||||||
url_re = re.compile(
|
|
||||||
r'^https?://' # http:// or https://
|
|
||||||
r'(?:(?:[A-Z0-9-]+\.)+[A-Z]{2,6}|' #domain...
|
|
||||||
r'localhost|' #localhost...
|
|
||||||
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
|
|
||||||
r'(?::\d+)?' # optional port
|
|
||||||
r'(?:/?|/\S+)$', re.IGNORECASE)
|
|
||||||
|
|
||||||
class URLField(RegexField):
|
|
||||||
default_error_messages = {
|
|
||||||
'invalid': _(u'Enter a valid URL.'),
|
|
||||||
'invalid_link': _(u'This URL appears to be a broken link.'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, max_length=None, min_length=None, verify_exists=False,
|
|
||||||
validator_user_agent=URL_VALIDATOR_USER_AGENT, *args, **kwargs):
|
|
||||||
super(URLField, self).__init__(url_re, max_length, min_length, *args,
|
|
||||||
**kwargs)
|
|
||||||
self.verify_exists = verify_exists
|
|
||||||
self.user_agent = validator_user_agent
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
# If no URL scheme given, assume http://
|
|
||||||
if value and '://' not in value:
|
|
||||||
value = u'http://%s' % value
|
|
||||||
value = super(URLField, self).clean(value)
|
|
||||||
if value == u'':
|
|
||||||
return value
|
|
||||||
if self.verify_exists:
|
|
||||||
import urllib2
|
|
||||||
from django.conf import settings
|
|
||||||
headers = {
|
|
||||||
"Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
|
|
||||||
"Accept-Language": "en-us,en;q=0.5",
|
|
||||||
"Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
|
|
||||||
"Connection": "close",
|
|
||||||
"User-Agent": self.user_agent,
|
|
||||||
}
|
|
||||||
try:
|
|
||||||
req = urllib2.Request(value, None, headers)
|
|
||||||
u = urllib2.urlopen(req)
|
|
||||||
except ValueError:
|
|
||||||
raise ValidationError(self.error_messages['invalid'])
|
|
||||||
except: # urllib2.URLError, httplib.InvalidURL, etc.
|
|
||||||
raise ValidationError(self.error_messages['invalid_link'])
|
|
||||||
return value
|
|
||||||
|
|
||||||
class BooleanField(Field):
|
|
||||||
widget = CheckboxInput
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
"""Returns a Python boolean object."""
|
|
||||||
super(BooleanField, self).clean(value)
|
|
||||||
# Explicitly check for the string 'False', which is what a hidden field
|
|
||||||
# will submit for False. Because bool("True") == True, we don't need to
|
|
||||||
# handle that explicitly.
|
|
||||||
if value == 'False':
|
|
||||||
return False
|
|
||||||
return bool(value)
|
|
||||||
|
|
||||||
class NullBooleanField(BooleanField):
|
|
||||||
"""
|
|
||||||
A field whose valid values are None, True and False. Invalid values are
|
|
||||||
cleaned to None.
|
|
||||||
"""
|
|
||||||
widget = NullBooleanSelect
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
return {True: True, False: False}.get(value, None)
|
|
||||||
|
|
||||||
class ChoiceField(Field):
|
|
||||||
widget = Select
|
|
||||||
default_error_messages = {
|
|
||||||
'invalid_choice': _(u'Select a valid choice. That choice is not one of the available choices.'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, choices=(), required=True, widget=None, label=None,
|
|
||||||
initial=None, help_text=None, *args, **kwargs):
|
|
||||||
super(ChoiceField, self).__init__(required, widget, label, initial,
|
|
||||||
help_text, *args, **kwargs)
|
|
||||||
self.choices = choices
|
|
||||||
|
|
||||||
def _get_choices(self):
|
|
||||||
return self._choices
|
|
||||||
|
|
||||||
def _set_choices(self, value):
|
|
||||||
# Setting choices also sets the choices on the widget.
|
|
||||||
# choices can be any iterable, but we call list() on it because
|
|
||||||
# it will be consumed more than once.
|
|
||||||
self._choices = self.widget.choices = list(value)
|
|
||||||
|
|
||||||
choices = property(_get_choices, _set_choices)
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
"""
|
|
||||||
Validates that the input is in self.choices.
|
|
||||||
"""
|
|
||||||
value = super(ChoiceField, self).clean(value)
|
|
||||||
if value in EMPTY_VALUES:
|
|
||||||
value = u''
|
|
||||||
value = smart_unicode(value)
|
|
||||||
if value == u'':
|
|
||||||
return value
|
|
||||||
valid_values = set([smart_unicode(k) for k, v in self.choices])
|
|
||||||
if value not in valid_values:
|
|
||||||
raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
|
|
||||||
return value
|
|
||||||
|
|
||||||
class MultipleChoiceField(ChoiceField):
|
|
||||||
hidden_widget = MultipleHiddenInput
|
|
||||||
widget = SelectMultiple
|
|
||||||
default_error_messages = {
|
|
||||||
'invalid_choice': _(u'Select a valid choice. %(value)s is not one of the available choices.'),
|
|
||||||
'invalid_list': _(u'Enter a list of values.'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
"""
|
|
||||||
Validates that the input is a list or tuple.
|
|
||||||
"""
|
|
||||||
if self.required and not value:
|
|
||||||
raise ValidationError(self.error_messages['required'])
|
|
||||||
elif not self.required and not value:
|
|
||||||
return []
|
|
||||||
if not isinstance(value, (list, tuple)):
|
|
||||||
raise ValidationError(self.error_messages['invalid_list'])
|
|
||||||
new_value = [smart_unicode(val) for val in value]
|
|
||||||
# Validate that each value in the value list is in self.choices.
|
|
||||||
valid_values = set([smart_unicode(k) for k, v in self.choices])
|
|
||||||
for val in new_value:
|
|
||||||
if val not in valid_values:
|
|
||||||
raise ValidationError(self.error_messages['invalid_choice'] % {'value': val})
|
|
||||||
return new_value
|
|
||||||
|
|
||||||
class ComboField(Field):
|
|
||||||
"""
|
|
||||||
A Field whose clean() method calls multiple Field clean() methods.
|
|
||||||
"""
|
|
||||||
def __init__(self, fields=(), *args, **kwargs):
|
|
||||||
super(ComboField, self).__init__(*args, **kwargs)
|
|
||||||
# Set 'required' to False on the individual fields, because the
|
|
||||||
# required validation will be handled by ComboField, not by those
|
|
||||||
# individual fields.
|
|
||||||
for f in fields:
|
|
||||||
f.required = False
|
|
||||||
self.fields = fields
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
"""
|
|
||||||
Validates the given value against all of self.fields, which is a
|
|
||||||
list of Field instances.
|
|
||||||
"""
|
|
||||||
super(ComboField, self).clean(value)
|
|
||||||
for field in self.fields:
|
|
||||||
value = field.clean(value)
|
|
||||||
return value
|
|
||||||
|
|
||||||
class MultiValueField(Field):
|
|
||||||
"""
|
|
||||||
A Field that aggregates the logic of multiple Fields.
|
|
||||||
|
|
||||||
Its clean() method takes a "decompressed" list of values, which are then
|
|
||||||
cleaned into a single value according to self.fields. Each value in
|
|
||||||
this list is cleaned by the corresponding field -- the first value is
|
|
||||||
cleaned by the first field, the second value is cleaned by the second
|
|
||||||
field, etc. Once all fields are cleaned, the list of clean values is
|
|
||||||
"compressed" into a single value.
|
|
||||||
|
|
||||||
Subclasses should not have to implement clean(). Instead, they must
|
|
||||||
implement compress(), which takes a list of valid values and returns a
|
|
||||||
"compressed" version of those values -- a single value.
|
|
||||||
|
|
||||||
You'll probably want to use this with MultiWidget.
|
|
||||||
"""
|
|
||||||
default_error_messages = {
|
|
||||||
'invalid': _(u'Enter a list of values.'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, fields=(), *args, **kwargs):
|
|
||||||
super(MultiValueField, self).__init__(*args, **kwargs)
|
|
||||||
# Set 'required' to False on the individual fields, because the
|
|
||||||
# required validation will be handled by MultiValueField, not by those
|
|
||||||
# individual fields.
|
|
||||||
for f in fields:
|
|
||||||
f.required = False
|
|
||||||
self.fields = fields
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
"""
|
|
||||||
Validates every value in the given list. A value is validated against
|
|
||||||
the corresponding Field in self.fields.
|
|
||||||
|
|
||||||
For example, if this MultiValueField was instantiated with
|
|
||||||
fields=(DateField(), TimeField()), clean() would call
|
|
||||||
DateField.clean(value[0]) and TimeField.clean(value[1]).
|
|
||||||
"""
|
|
||||||
cleaned_data = []
|
|
||||||
errors = ErrorList()
|
|
||||||
if not value or isinstance(value, (list, tuple)):
|
|
||||||
if not value or not [v for v in value if v not in EMPTY_VALUES]:
|
|
||||||
if self.required:
|
|
||||||
raise ValidationError(self.error_messages['required'])
|
|
||||||
else:
|
|
||||||
return self.compress([])
|
|
||||||
else:
|
|
||||||
raise ValidationError(self.error_messages['invalid'])
|
|
||||||
for i, field in enumerate(self.fields):
|
|
||||||
try:
|
|
||||||
field_value = value[i]
|
|
||||||
except IndexError:
|
|
||||||
field_value = None
|
|
||||||
if self.required and field_value in EMPTY_VALUES:
|
|
||||||
raise ValidationError(self.error_messages['required'])
|
|
||||||
try:
|
|
||||||
cleaned_data.append(field.clean(field_value))
|
|
||||||
except ValidationError, e:
|
|
||||||
# Collect all validation errors in a single list, which we'll
|
|
||||||
# raise at the end of clean(), rather than raising a single
|
|
||||||
# exception for the first error we encounter.
|
|
||||||
errors.extend(e.messages)
|
|
||||||
if errors:
|
|
||||||
raise ValidationError(errors)
|
|
||||||
return self.compress(cleaned_data)
|
|
||||||
|
|
||||||
def compress(self, data_list):
|
|
||||||
"""
|
|
||||||
Returns a single value for the given list of values. The values can be
|
|
||||||
assumed to be valid.
|
|
||||||
|
|
||||||
For example, if this MultiValueField was instantiated with
|
|
||||||
fields=(DateField(), TimeField()), this might return a datetime
|
|
||||||
object created by combining the date and time in data_list.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError('Subclasses must implement this method.')
|
|
||||||
|
|
||||||
class FilePathField(ChoiceField):
|
|
||||||
def __init__(self, path, match=None, recursive=False, required=True,
|
|
||||||
widget=Select, label=None, initial=None, help_text=None,
|
|
||||||
*args, **kwargs):
|
|
||||||
self.path, self.match, self.recursive = path, match, recursive
|
|
||||||
super(FilePathField, self).__init__(choices=(), required=required,
|
|
||||||
widget=widget, label=label, initial=initial, help_text=help_text,
|
|
||||||
*args, **kwargs)
|
|
||||||
self.choices = []
|
|
||||||
if self.match is not None:
|
|
||||||
self.match_re = re.compile(self.match)
|
|
||||||
if recursive:
|
|
||||||
for root, dirs, files in os.walk(self.path):
|
|
||||||
for f in files:
|
|
||||||
if self.match is None or self.match_re.search(f):
|
|
||||||
f = os.path.join(root, f)
|
|
||||||
self.choices.append((f, f.replace(path, "", 1)))
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
for f in os.listdir(self.path):
|
|
||||||
full_file = os.path.join(self.path, f)
|
|
||||||
if os.path.isfile(full_file) and (self.match is None or self.match_re.search(f)):
|
|
||||||
self.choices.append((full_file, f))
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
self.widget.choices = self.choices
|
|
||||||
|
|
||||||
class SplitDateTimeField(MultiValueField):
|
|
||||||
default_error_messages = {
|
|
||||||
'invalid_date': _(u'Enter a valid date.'),
|
|
||||||
'invalid_time': _(u'Enter a valid time.'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
errors = self.default_error_messages.copy()
|
|
||||||
if 'error_messages' in kwargs:
|
|
||||||
errors.update(kwargs['error_messages'])
|
|
||||||
fields = (
|
|
||||||
DateField(error_messages={'invalid': errors['invalid_date']}),
|
|
||||||
TimeField(error_messages={'invalid': errors['invalid_time']}),
|
|
||||||
)
|
|
||||||
super(SplitDateTimeField, self).__init__(fields, *args, **kwargs)
|
|
||||||
|
|
||||||
def compress(self, data_list):
|
|
||||||
if data_list:
|
|
||||||
# Raise a validation error if time or date is empty
|
|
||||||
# (possible if SplitDateTimeField has required=False).
|
|
||||||
if data_list[0] in EMPTY_VALUES:
|
|
||||||
raise ValidationError(self.error_messages['invalid_date'])
|
|
||||||
if data_list[1] in EMPTY_VALUES:
|
|
||||||
raise ValidationError(self.error_messages['invalid_time'])
|
|
||||||
return datetime.datetime.combine(*data_list)
|
|
||||||
return None
|
|
||||||
|
|
||||||
ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
|
|
||||||
|
|
||||||
class IPAddressField(RegexField):
|
|
||||||
default_error_messages = {
|
|
||||||
'invalid': _(u'Enter a valid IPv4 address.'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(IPAddressField, self).__init__(ipv4_re, *args, **kwargs)
|
|
||||||
@ -1,350 +0,0 @@
|
|||||||
"""
|
|
||||||
Form classes
|
|
||||||
"""
|
|
||||||
|
|
||||||
from copy import deepcopy
|
|
||||||
|
|
||||||
from django.utils.datastructures import SortedDict
|
|
||||||
from django.utils.html import escape
|
|
||||||
from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
|
|
||||||
from django.utils.safestring import mark_safe
|
|
||||||
|
|
||||||
from fields import Field, FileField
|
|
||||||
from widgets import TextInput, Textarea
|
|
||||||
from util import flatatt, ErrorDict, ErrorList, ValidationError
|
|
||||||
|
|
||||||
__all__ = ('BaseForm', 'Form')
|
|
||||||
|
|
||||||
NON_FIELD_ERRORS = '__all__'
|
|
||||||
|
|
||||||
def pretty_name(name):
|
|
||||||
"Converts 'first_name' to 'First name'"
|
|
||||||
name = name[0].upper() + name[1:]
|
|
||||||
return name.replace('_', ' ')
|
|
||||||
|
|
||||||
def get_declared_fields(bases, attrs, with_base_fields=True):
|
|
||||||
"""
|
|
||||||
Create a list of form field instances from the passed in 'attrs', plus any
|
|
||||||
similar fields on the base classes (in 'bases'). This is used by both the
|
|
||||||
Form and ModelForm metclasses.
|
|
||||||
|
|
||||||
If 'with_base_fields' is True, all fields from the bases are used.
|
|
||||||
Otherwise, only fields in the 'declared_fields' attribute on the bases are
|
|
||||||
used. The distinction is useful in ModelForm subclassing.
|
|
||||||
"""
|
|
||||||
fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
|
|
||||||
fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
|
|
||||||
|
|
||||||
# If this class is subclassing another Form, add that Form's fields.
|
|
||||||
# Note that we loop over the bases in *reverse*. This is necessary in
|
|
||||||
# order to preserve the correct order of fields.
|
|
||||||
if with_base_fields:
|
|
||||||
for base in bases[::-1]:
|
|
||||||
if hasattr(base, 'base_fields'):
|
|
||||||
fields = base.base_fields.items() + fields
|
|
||||||
else:
|
|
||||||
for base in bases[::-1]:
|
|
||||||
if hasattr(base, 'declared_fields'):
|
|
||||||
fields = base.declared_fields.items() + fields
|
|
||||||
|
|
||||||
return SortedDict(fields)
|
|
||||||
|
|
||||||
class DeclarativeFieldsMetaclass(type):
|
|
||||||
"""
|
|
||||||
Metaclass that converts Field attributes to a dictionary called
|
|
||||||
'base_fields', taking into account parent class 'base_fields' as well.
|
|
||||||
"""
|
|
||||||
def __new__(cls, name, bases, attrs):
|
|
||||||
attrs['base_fields'] = get_declared_fields(bases, attrs)
|
|
||||||
return type.__new__(cls, name, bases, attrs)
|
|
||||||
|
|
||||||
class BaseForm(StrAndUnicode):
|
|
||||||
# This is the main implementation of all the Form logic. Note that this
|
|
||||||
# class is different than Form. See the comments by the Form class for more
|
|
||||||
# information. Any improvements to the form API should be made to *this*
|
|
||||||
# class, not to the Form class.
|
|
||||||
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
|
|
||||||
initial=None, error_class=ErrorList, label_suffix=':'):
|
|
||||||
self.is_bound = data is not None or files is not None
|
|
||||||
self.data = data or {}
|
|
||||||
self.files = files or {}
|
|
||||||
self.auto_id = auto_id
|
|
||||||
self.prefix = prefix
|
|
||||||
self.initial = initial or {}
|
|
||||||
self.error_class = error_class
|
|
||||||
self.label_suffix = label_suffix
|
|
||||||
self._errors = None # Stores the errors after clean() has been called.
|
|
||||||
|
|
||||||
# The base_fields class attribute is the *class-wide* definition of
|
|
||||||
# fields. Because a particular *instance* of the class might want to
|
|
||||||
# alter self.fields, we create self.fields here by copying base_fields.
|
|
||||||
# Instances should always modify self.fields; they should not modify
|
|
||||||
# self.base_fields.
|
|
||||||
self.fields = deepcopy(self.base_fields)
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return self.as_table()
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
for name, field in self.fields.items():
|
|
||||||
yield BoundField(self, field, name)
|
|
||||||
|
|
||||||
def __getitem__(self, name):
|
|
||||||
"Returns a BoundField with the given name."
|
|
||||||
try:
|
|
||||||
field = self.fields[name]
|
|
||||||
except KeyError:
|
|
||||||
raise KeyError('Key %r not found in Form' % name)
|
|
||||||
return BoundField(self, field, name)
|
|
||||||
|
|
||||||
def _get_errors(self):
|
|
||||||
"Returns an ErrorDict for the data provided for the form"
|
|
||||||
if self._errors is None:
|
|
||||||
self.full_clean()
|
|
||||||
return self._errors
|
|
||||||
errors = property(_get_errors)
|
|
||||||
|
|
||||||
def is_valid(self):
|
|
||||||
"""
|
|
||||||
Returns True if the form has no errors. Otherwise, False. If errors are
|
|
||||||
being ignored, returns False.
|
|
||||||
"""
|
|
||||||
return self.is_bound and not bool(self.errors)
|
|
||||||
|
|
||||||
def add_prefix(self, field_name):
|
|
||||||
"""
|
|
||||||
Returns the field name with a prefix appended, if this Form has a
|
|
||||||
prefix set.
|
|
||||||
|
|
||||||
Subclasses may wish to override.
|
|
||||||
"""
|
|
||||||
return self.prefix and ('%s-%s' % (self.prefix, field_name)) or field_name
|
|
||||||
|
|
||||||
def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row):
|
|
||||||
"Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()."
|
|
||||||
top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
|
|
||||||
output, hidden_fields = [], []
|
|
||||||
for name, field in self.fields.items():
|
|
||||||
bf = BoundField(self, field, name)
|
|
||||||
bf_errors = self.error_class([escape(error) for error in bf.errors]) # Escape and cache in local variable.
|
|
||||||
if bf.is_hidden:
|
|
||||||
if bf_errors:
|
|
||||||
top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors])
|
|
||||||
hidden_fields.append(unicode(bf))
|
|
||||||
else:
|
|
||||||
if errors_on_separate_row and bf_errors:
|
|
||||||
output.append(error_row % force_unicode(bf_errors))
|
|
||||||
if bf.label:
|
|
||||||
label = escape(force_unicode(bf.label))
|
|
||||||
# Only add the suffix if the label does not end in
|
|
||||||
# punctuation.
|
|
||||||
if self.label_suffix:
|
|
||||||
if label[-1] not in ':?.!':
|
|
||||||
label += self.label_suffix
|
|
||||||
label = bf.label_tag(label) or ''
|
|
||||||
else:
|
|
||||||
label = ''
|
|
||||||
if field.help_text:
|
|
||||||
help_text = help_text_html % force_unicode(field.help_text)
|
|
||||||
else:
|
|
||||||
help_text = u''
|
|
||||||
output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label), 'field': unicode(bf), 'help_text': help_text})
|
|
||||||
if top_errors:
|
|
||||||
output.insert(0, error_row % force_unicode(top_errors))
|
|
||||||
if hidden_fields: # Insert any hidden fields in the last row.
|
|
||||||
str_hidden = u''.join(hidden_fields)
|
|
||||||
if output:
|
|
||||||
last_row = output[-1]
|
|
||||||
# Chop off the trailing row_ender (e.g. '</td></tr>') and
|
|
||||||
# insert the hidden fields.
|
|
||||||
output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
|
|
||||||
else:
|
|
||||||
# If there aren't any rows in the output, just append the
|
|
||||||
# hidden fields.
|
|
||||||
output.append(str_hidden)
|
|
||||||
return mark_safe(u'\n'.join(output))
|
|
||||||
|
|
||||||
def as_table(self):
|
|
||||||
"Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
|
|
||||||
return self._html_output(u'<tr><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>', u'<tr><td colspan="2">%s</td></tr>', '</td></tr>', u'<br />%s', False)
|
|
||||||
|
|
||||||
def as_ul(self):
|
|
||||||
"Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
|
|
||||||
return self._html_output(u'<li>%(errors)s%(label)s %(field)s%(help_text)s</li>', u'<li>%s</li>', '</li>', u' %s', False)
|
|
||||||
|
|
||||||
def as_p(self):
|
|
||||||
"Returns this form rendered as HTML <p>s."
|
|
||||||
return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>', u'%s', '</p>', u' %s', True)
|
|
||||||
|
|
||||||
def non_field_errors(self):
|
|
||||||
"""
|
|
||||||
Returns an ErrorList of errors that aren't associated with a particular
|
|
||||||
field -- i.e., from Form.clean(). Returns an empty ErrorList if there
|
|
||||||
are none.
|
|
||||||
"""
|
|
||||||
return self.errors.get(NON_FIELD_ERRORS, self.error_class())
|
|
||||||
|
|
||||||
def full_clean(self):
|
|
||||||
"""
|
|
||||||
Cleans all of self.data and populates self._errors and
|
|
||||||
self.cleaned_data.
|
|
||||||
"""
|
|
||||||
self._errors = ErrorDict()
|
|
||||||
if not self.is_bound: # Stop further processing.
|
|
||||||
return
|
|
||||||
self.cleaned_data = {}
|
|
||||||
for name, field in self.fields.items():
|
|
||||||
# value_from_datadict() gets the data from the data dictionaries.
|
|
||||||
# Each widget type knows how to retrieve its own data, because some
|
|
||||||
# widgets split data over several HTML fields.
|
|
||||||
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
|
|
||||||
try:
|
|
||||||
if isinstance(field, FileField):
|
|
||||||
initial = self.initial.get(name, field.initial)
|
|
||||||
value = field.clean(value, initial)
|
|
||||||
else:
|
|
||||||
value = field.clean(value)
|
|
||||||
self.cleaned_data[name] = value
|
|
||||||
if hasattr(self, 'clean_%s' % name):
|
|
||||||
value = getattr(self, 'clean_%s' % name)()
|
|
||||||
self.cleaned_data[name] = value
|
|
||||||
except ValidationError, e:
|
|
||||||
self._errors[name] = e.messages
|
|
||||||
if name in self.cleaned_data:
|
|
||||||
del self.cleaned_data[name]
|
|
||||||
try:
|
|
||||||
self.cleaned_data = self.clean()
|
|
||||||
except ValidationError, e:
|
|
||||||
self._errors[NON_FIELD_ERRORS] = e.messages
|
|
||||||
if self._errors:
|
|
||||||
delattr(self, 'cleaned_data')
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
"""
|
|
||||||
Hook for doing any extra form-wide cleaning after Field.clean() been
|
|
||||||
called on every field. Any ValidationError raised by this method will
|
|
||||||
not be associated with a particular field; it will have a special-case
|
|
||||||
association with the field named '__all__'.
|
|
||||||
"""
|
|
||||||
return self.cleaned_data
|
|
||||||
|
|
||||||
def is_multipart(self):
|
|
||||||
"""
|
|
||||||
Returns True if the form needs to be multipart-encrypted, i.e. it has
|
|
||||||
FileInput. Otherwise, False.
|
|
||||||
"""
|
|
||||||
for field in self.fields.values():
|
|
||||||
if field.widget.needs_multipart_form:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
class Form(BaseForm):
|
|
||||||
"A collection of Fields, plus their associated data."
|
|
||||||
# This is a separate class from BaseForm in order to abstract the way
|
|
||||||
# self.fields is specified. This class (Form) is the one that does the
|
|
||||||
# fancy metaclass stuff purely for the semantic sugar -- it allows one
|
|
||||||
# to define a form using declarative syntax.
|
|
||||||
# BaseForm itself has no way of designating self.fields.
|
|
||||||
__metaclass__ = DeclarativeFieldsMetaclass
|
|
||||||
|
|
||||||
class BoundField(StrAndUnicode):
|
|
||||||
"A Field plus data"
|
|
||||||
def __init__(self, form, field, name):
|
|
||||||
self.form = form
|
|
||||||
self.field = field
|
|
||||||
self.name = name
|
|
||||||
self.html_name = form.add_prefix(name)
|
|
||||||
if self.field.label is None:
|
|
||||||
self.label = pretty_name(name)
|
|
||||||
else:
|
|
||||||
self.label = self.field.label
|
|
||||||
self.help_text = field.help_text or ''
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
"""Renders this field as an HTML widget."""
|
|
||||||
return self.as_widget()
|
|
||||||
|
|
||||||
def _errors(self):
|
|
||||||
"""
|
|
||||||
Returns an ErrorList for this field. Returns an empty ErrorList
|
|
||||||
if there are none.
|
|
||||||
"""
|
|
||||||
return self.form.errors.get(self.name, self.form.error_class())
|
|
||||||
errors = property(_errors)
|
|
||||||
|
|
||||||
def as_widget(self, widget=None, attrs=None):
|
|
||||||
"""
|
|
||||||
Renders the field by rendering the passed widget, adding any HTML
|
|
||||||
attributes passed as attrs. If no widget is specified, then the
|
|
||||||
field's default widget will be used.
|
|
||||||
"""
|
|
||||||
if not widget:
|
|
||||||
widget = self.field.widget
|
|
||||||
attrs = attrs or {}
|
|
||||||
auto_id = self.auto_id
|
|
||||||
if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
|
|
||||||
attrs['id'] = auto_id
|
|
||||||
if not self.form.is_bound:
|
|
||||||
data = self.form.initial.get(self.name, self.field.initial)
|
|
||||||
if callable(data):
|
|
||||||
data = data()
|
|
||||||
else:
|
|
||||||
data = self.data
|
|
||||||
return widget.render(self.html_name, data, attrs=attrs)
|
|
||||||
|
|
||||||
def as_text(self, attrs=None):
|
|
||||||
"""
|
|
||||||
Returns a string of HTML for representing this as an <input type="text">.
|
|
||||||
"""
|
|
||||||
return self.as_widget(TextInput(), attrs)
|
|
||||||
|
|
||||||
def as_textarea(self, attrs=None):
|
|
||||||
"Returns a string of HTML for representing this as a <textarea>."
|
|
||||||
return self.as_widget(Textarea(), attrs)
|
|
||||||
|
|
||||||
def as_hidden(self, attrs=None):
|
|
||||||
"""
|
|
||||||
Returns a string of HTML for representing this as an <input type="hidden">.
|
|
||||||
"""
|
|
||||||
return self.as_widget(self.field.hidden_widget(), attrs)
|
|
||||||
|
|
||||||
def _data(self):
|
|
||||||
"""
|
|
||||||
Returns the data for this BoundField, or None if it wasn't given.
|
|
||||||
"""
|
|
||||||
return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
|
|
||||||
data = property(_data)
|
|
||||||
|
|
||||||
def label_tag(self, contents=None, attrs=None):
|
|
||||||
"""
|
|
||||||
Wraps the given contents in a <label>, if the field has an ID attribute.
|
|
||||||
Does not HTML-escape the contents. If contents aren't given, uses the
|
|
||||||
field's HTML-escaped label.
|
|
||||||
|
|
||||||
If attrs are given, they're used as HTML attributes on the <label> tag.
|
|
||||||
"""
|
|
||||||
contents = contents or escape(self.label)
|
|
||||||
widget = self.field.widget
|
|
||||||
id_ = widget.attrs.get('id') or self.auto_id
|
|
||||||
if id_:
|
|
||||||
attrs = attrs and flatatt(attrs) or ''
|
|
||||||
contents = '<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, contents)
|
|
||||||
return mark_safe(contents)
|
|
||||||
|
|
||||||
def _is_hidden(self):
|
|
||||||
"Returns True if this BoundField's widget is hidden."
|
|
||||||
return self.field.widget.is_hidden
|
|
||||||
is_hidden = property(_is_hidden)
|
|
||||||
|
|
||||||
def _auto_id(self):
|
|
||||||
"""
|
|
||||||
Calculates and returns the ID attribute for this BoundField, if the
|
|
||||||
associated Form has specified auto_id. Returns an empty string otherwise.
|
|
||||||
"""
|
|
||||||
auto_id = self.form.auto_id
|
|
||||||
if auto_id and '%s' in smart_unicode(auto_id):
|
|
||||||
return smart_unicode(auto_id) % self.html_name
|
|
||||||
elif auto_id:
|
|
||||||
return self.html_name
|
|
||||||
return ''
|
|
||||||
auto_id = property(_auto_id)
|
|
||||||
@ -1,398 +0,0 @@
|
|||||||
"""
|
|
||||||
Helper functions for creating Form classes from Django models
|
|
||||||
and database field objects.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from warnings import warn
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.utils.encoding import smart_unicode
|
|
||||||
from django.utils.datastructures import SortedDict
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
|
||||||
|
|
||||||
from util import ValidationError, ErrorList
|
|
||||||
from forms import BaseForm, get_declared_fields
|
|
||||||
from fields import Field, ChoiceField, EMPTY_VALUES
|
|
||||||
from widgets import Select, SelectMultiple, MultipleHiddenInput
|
|
||||||
|
|
||||||
__all__ = (
|
|
||||||
'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
|
|
||||||
'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields',
|
|
||||||
'ModelChoiceField', 'ModelMultipleChoiceField'
|
|
||||||
)
|
|
||||||
|
|
||||||
def save_instance(form, instance, fields=None, fail_message='saved',
|
|
||||||
commit=True):
|
|
||||||
"""
|
|
||||||
Saves bound Form ``form``'s cleaned_data into model instance ``instance``.
|
|
||||||
|
|
||||||
If commit=True, then the changes to ``instance`` will be saved to the
|
|
||||||
database. Returns ``instance``.
|
|
||||||
"""
|
|
||||||
from django.db import models
|
|
||||||
opts = instance.__class__._meta
|
|
||||||
if form.errors:
|
|
||||||
raise ValueError("The %s could not be %s because the data didn't"
|
|
||||||
" validate." % (opts.object_name, fail_message))
|
|
||||||
cleaned_data = form.cleaned_data
|
|
||||||
for f in opts.fields:
|
|
||||||
if not f.editable or isinstance(f, models.AutoField) \
|
|
||||||
or not f.name in cleaned_data:
|
|
||||||
continue
|
|
||||||
if fields and f.name not in fields:
|
|
||||||
continue
|
|
||||||
f.save_form_data(instance, cleaned_data[f.name])
|
|
||||||
# Wrap up the saving of m2m data as a function.
|
|
||||||
def save_m2m():
|
|
||||||
opts = instance.__class__._meta
|
|
||||||
cleaned_data = form.cleaned_data
|
|
||||||
for f in opts.many_to_many:
|
|
||||||
if fields and f.name not in fields:
|
|
||||||
continue
|
|
||||||
if f.name in cleaned_data:
|
|
||||||
f.save_form_data(instance, cleaned_data[f.name])
|
|
||||||
if commit:
|
|
||||||
# If we are committing, save the instance and the m2m data immediately.
|
|
||||||
instance.save()
|
|
||||||
save_m2m()
|
|
||||||
else:
|
|
||||||
# We're not committing. Add a method to the form to allow deferred
|
|
||||||
# saving of m2m data.
|
|
||||||
form.save_m2m = save_m2m
|
|
||||||
return instance
|
|
||||||
|
|
||||||
def make_model_save(model, fields, fail_message):
|
|
||||||
"""Returns the save() method for a Form."""
|
|
||||||
def save(self, commit=True):
|
|
||||||
return save_instance(self, model(), fields, fail_message, commit)
|
|
||||||
return save
|
|
||||||
|
|
||||||
def make_instance_save(instance, fields, fail_message):
|
|
||||||
"""Returns the save() method for a Form."""
|
|
||||||
def save(self, commit=True):
|
|
||||||
return save_instance(self, instance, fields, fail_message, commit)
|
|
||||||
return save
|
|
||||||
|
|
||||||
def form_for_model(model, form=BaseForm, fields=None,
|
|
||||||
formfield_callback=lambda f: f.formfield()):
|
|
||||||
"""
|
|
||||||
Returns a Form class for the given Django model class.
|
|
||||||
|
|
||||||
Provide ``form`` if you want to use a custom BaseForm subclass.
|
|
||||||
|
|
||||||
Provide ``formfield_callback`` if you want to define different logic for
|
|
||||||
determining the formfield for a given database field. It's a callable that
|
|
||||||
takes a database Field instance and returns a form Field instance.
|
|
||||||
"""
|
|
||||||
warn("form_for_model is deprecated. Use ModelForm instead.",
|
|
||||||
PendingDeprecationWarning, stacklevel=3)
|
|
||||||
opts = model._meta
|
|
||||||
field_list = []
|
|
||||||
for f in opts.fields + opts.many_to_many:
|
|
||||||
if not f.editable:
|
|
||||||
continue
|
|
||||||
if fields and not f.name in fields:
|
|
||||||
continue
|
|
||||||
formfield = formfield_callback(f)
|
|
||||||
if formfield:
|
|
||||||
field_list.append((f.name, formfield))
|
|
||||||
base_fields = SortedDict(field_list)
|
|
||||||
return type(opts.object_name + 'Form', (form,),
|
|
||||||
{'base_fields': base_fields, '_model': model,
|
|
||||||
'save': make_model_save(model, fields, 'created')})
|
|
||||||
|
|
||||||
def form_for_instance(instance, form=BaseForm, fields=None,
|
|
||||||
formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
|
|
||||||
"""
|
|
||||||
Returns a Form class for the given Django model instance.
|
|
||||||
|
|
||||||
Provide ``form`` if you want to use a custom BaseForm subclass.
|
|
||||||
|
|
||||||
Provide ``formfield_callback`` if you want to define different logic for
|
|
||||||
determining the formfield for a given database field. It's a callable that
|
|
||||||
takes a database Field instance, plus **kwargs, and returns a form Field
|
|
||||||
instance with the given kwargs (i.e. 'initial').
|
|
||||||
"""
|
|
||||||
warn("form_for_instance is deprecated. Use ModelForm instead.",
|
|
||||||
PendingDeprecationWarning, stacklevel=3)
|
|
||||||
model = instance.__class__
|
|
||||||
opts = model._meta
|
|
||||||
field_list = []
|
|
||||||
for f in opts.fields + opts.many_to_many:
|
|
||||||
if not f.editable:
|
|
||||||
continue
|
|
||||||
if fields and not f.name in fields:
|
|
||||||
continue
|
|
||||||
current_value = f.value_from_object(instance)
|
|
||||||
formfield = formfield_callback(f, initial=current_value)
|
|
||||||
if formfield:
|
|
||||||
field_list.append((f.name, formfield))
|
|
||||||
base_fields = SortedDict(field_list)
|
|
||||||
return type(opts.object_name + 'InstanceForm', (form,),
|
|
||||||
{'base_fields': base_fields, '_model': model,
|
|
||||||
'save': make_instance_save(instance, fields, 'changed')})
|
|
||||||
|
|
||||||
def form_for_fields(field_list):
|
|
||||||
"""
|
|
||||||
Returns a Form class for the given list of Django database field instances.
|
|
||||||
"""
|
|
||||||
fields = SortedDict([(f.name, f.formfield())
|
|
||||||
for f in field_list if f.editable])
|
|
||||||
return type('FormForFields', (BaseForm,), {'base_fields': fields})
|
|
||||||
|
|
||||||
|
|
||||||
# ModelForms #################################################################
|
|
||||||
|
|
||||||
def model_to_dict(instance, fields=None, exclude=None):
|
|
||||||
"""
|
|
||||||
Returns a dict containing the data in ``instance`` suitable for passing as
|
|
||||||
a Form's ``initial`` keyword argument.
|
|
||||||
|
|
||||||
``fields`` is an optional list of field names. If provided, only the named
|
|
||||||
fields will be included in the returned dict.
|
|
||||||
|
|
||||||
``exclude`` is an optional list of field names. If provided, the named
|
|
||||||
fields will be excluded from the returned dict, even if they are listed in
|
|
||||||
the ``fields`` argument.
|
|
||||||
"""
|
|
||||||
# avoid a circular import
|
|
||||||
from django.db.models.fields.related import ManyToManyField
|
|
||||||
opts = instance._meta
|
|
||||||
data = {}
|
|
||||||
for f in opts.fields + opts.many_to_many:
|
|
||||||
if not f.editable:
|
|
||||||
continue
|
|
||||||
if fields and not f.name in fields:
|
|
||||||
continue
|
|
||||||
if exclude and f.name in exclude:
|
|
||||||
continue
|
|
||||||
if isinstance(f, ManyToManyField):
|
|
||||||
# If the object doesn't have a primry key yet, just use an empty
|
|
||||||
# list for its m2m fields. Calling f.value_from_object will raise
|
|
||||||
# an exception.
|
|
||||||
if instance.pk is None:
|
|
||||||
data[f.name] = []
|
|
||||||
else:
|
|
||||||
# MultipleChoiceWidget needs a list of pks, not object instances.
|
|
||||||
data[f.name] = [obj.pk for obj in f.value_from_object(instance)]
|
|
||||||
else:
|
|
||||||
data[f.name] = f.value_from_object(instance)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def fields_for_model(model, fields=None, exclude=None, formfield_callback=lambda f: f.formfield()):
|
|
||||||
"""
|
|
||||||
Returns a ``SortedDict`` containing form fields for the given model.
|
|
||||||
|
|
||||||
``fields`` is an optional list of field names. If provided, only the named
|
|
||||||
fields will be included in the returned fields.
|
|
||||||
|
|
||||||
``exclude`` is an optional list of field names. If provided, the named
|
|
||||||
fields will be excluded from the returned fields, even if they are listed
|
|
||||||
in the ``fields`` argument.
|
|
||||||
"""
|
|
||||||
# TODO: if fields is provided, it would be nice to return fields in that order
|
|
||||||
field_list = []
|
|
||||||
opts = model._meta
|
|
||||||
for f in opts.fields + opts.many_to_many:
|
|
||||||
if not f.editable:
|
|
||||||
continue
|
|
||||||
if fields and not f.name in fields:
|
|
||||||
continue
|
|
||||||
if exclude and f.name in exclude:
|
|
||||||
continue
|
|
||||||
formfield = formfield_callback(f)
|
|
||||||
if formfield:
|
|
||||||
field_list.append((f.name, formfield))
|
|
||||||
return SortedDict(field_list)
|
|
||||||
|
|
||||||
class ModelFormOptions(object):
|
|
||||||
def __init__(self, options=None):
|
|
||||||
self.model = getattr(options, 'model', None)
|
|
||||||
self.fields = getattr(options, 'fields', None)
|
|
||||||
self.exclude = getattr(options, 'exclude', None)
|
|
||||||
|
|
||||||
|
|
||||||
class ModelFormMetaclass(type):
|
|
||||||
def __new__(cls, name, bases, attrs,
|
|
||||||
formfield_callback=lambda f: f.formfield()):
|
|
||||||
try:
|
|
||||||
parents = [b for b in bases if issubclass(b, ModelForm)]
|
|
||||||
except NameError:
|
|
||||||
# We are defining ModelForm itself.
|
|
||||||
parents = None
|
|
||||||
if not parents:
|
|
||||||
return super(ModelFormMetaclass, cls).__new__(cls, name, bases,
|
|
||||||
attrs)
|
|
||||||
|
|
||||||
new_class = type.__new__(cls, name, bases, attrs)
|
|
||||||
declared_fields = get_declared_fields(bases, attrs, False)
|
|
||||||
opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None))
|
|
||||||
if opts.model:
|
|
||||||
# If a model is defined, extract form fields from it.
|
|
||||||
fields = fields_for_model(opts.model, opts.fields,
|
|
||||||
opts.exclude, formfield_callback)
|
|
||||||
# Override default model fields with any custom declared ones
|
|
||||||
# (plus, include all the other declared fields).
|
|
||||||
fields.update(declared_fields)
|
|
||||||
else:
|
|
||||||
fields = declared_fields
|
|
||||||
new_class.declared_fields = declared_fields
|
|
||||||
new_class.base_fields = fields
|
|
||||||
return new_class
|
|
||||||
|
|
||||||
class BaseModelForm(BaseForm):
|
|
||||||
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
|
|
||||||
initial=None, error_class=ErrorList, label_suffix=':',
|
|
||||||
instance=None):
|
|
||||||
opts = self._meta
|
|
||||||
if instance is None:
|
|
||||||
# if we didn't get an instance, instantiate a new one
|
|
||||||
self.instance = opts.model()
|
|
||||||
object_data = {}
|
|
||||||
else:
|
|
||||||
self.instance = instance
|
|
||||||
object_data = model_to_dict(instance, opts.fields, opts.exclude)
|
|
||||||
# if initial was provided, it should override the values from instance
|
|
||||||
if initial is not None:
|
|
||||||
object_data.update(initial)
|
|
||||||
BaseForm.__init__(self, data, files, auto_id, prefix, object_data, error_class, label_suffix)
|
|
||||||
|
|
||||||
def save(self, commit=True):
|
|
||||||
"""
|
|
||||||
Saves this ``form``'s cleaned_data into model instance
|
|
||||||
``self.instance``.
|
|
||||||
|
|
||||||
If commit=True, then the changes to ``instance`` will be saved to the
|
|
||||||
database. Returns ``instance``.
|
|
||||||
"""
|
|
||||||
if self.instance.pk is None:
|
|
||||||
fail_message = 'created'
|
|
||||||
else:
|
|
||||||
fail_message = 'changed'
|
|
||||||
return save_instance(self, self.instance, self._meta.fields, fail_message, commit)
|
|
||||||
|
|
||||||
class ModelForm(BaseModelForm):
|
|
||||||
__metaclass__ = ModelFormMetaclass
|
|
||||||
|
|
||||||
|
|
||||||
# Fields #####################################################################
|
|
||||||
|
|
||||||
class ModelChoiceIterator(object):
|
|
||||||
def __init__(self, field):
|
|
||||||
self.field = field
|
|
||||||
self.queryset = field.queryset
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
if self.field.empty_label is not None:
|
|
||||||
yield (u"", self.field.empty_label)
|
|
||||||
for obj in self.queryset:
|
|
||||||
yield (obj.pk, self.field.label_from_instance(obj))
|
|
||||||
# Clear the QuerySet cache if required.
|
|
||||||
if not self.field.cache_choices:
|
|
||||||
self.queryset._result_cache = None
|
|
||||||
|
|
||||||
class ModelChoiceField(ChoiceField):
|
|
||||||
"""A ChoiceField whose choices are a model QuerySet."""
|
|
||||||
# This class is a subclass of ChoiceField for purity, but it doesn't
|
|
||||||
# actually use any of ChoiceField's implementation.
|
|
||||||
default_error_messages = {
|
|
||||||
'invalid_choice': _(u'Select a valid choice. That choice is not one of'
|
|
||||||
u' the available choices.'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, queryset, empty_label=u"---------", cache_choices=False,
|
|
||||||
required=True, widget=Select, label=None, initial=None,
|
|
||||||
help_text=None, *args, **kwargs):
|
|
||||||
self.empty_label = empty_label
|
|
||||||
self.cache_choices = cache_choices
|
|
||||||
|
|
||||||
# Call Field instead of ChoiceField __init__() because we don't need
|
|
||||||
# ChoiceField.__init__().
|
|
||||||
Field.__init__(self, required, widget, label, initial, help_text,
|
|
||||||
*args, **kwargs)
|
|
||||||
self.queryset = queryset
|
|
||||||
|
|
||||||
def _get_queryset(self):
|
|
||||||
return self._queryset
|
|
||||||
|
|
||||||
def _set_queryset(self, queryset):
|
|
||||||
self._queryset = queryset
|
|
||||||
self.widget.choices = self.choices
|
|
||||||
|
|
||||||
queryset = property(_get_queryset, _set_queryset)
|
|
||||||
|
|
||||||
# this method will be used to create object labels by the QuerySetIterator.
|
|
||||||
# Override it to customize the label.
|
|
||||||
def label_from_instance(self, obj):
|
|
||||||
"""
|
|
||||||
This method is used to convert objects into strings; it's used to
|
|
||||||
generate the labels for the choices presented by this object. Subclasses
|
|
||||||
can override this method to customize the display of the choices.
|
|
||||||
"""
|
|
||||||
return smart_unicode(obj)
|
|
||||||
|
|
||||||
def _get_choices(self):
|
|
||||||
# If self._choices is set, then somebody must have manually set
|
|
||||||
# the property self.choices. In this case, just return self._choices.
|
|
||||||
if hasattr(self, '_choices'):
|
|
||||||
return self._choices
|
|
||||||
|
|
||||||
# Otherwise, execute the QuerySet in self.queryset to determine the
|
|
||||||
# choices dynamically. Return a fresh QuerySetIterator that has not been
|
|
||||||
# consumed. Note that we're instantiating a new QuerySetIterator *each*
|
|
||||||
# time _get_choices() is called (and, thus, each time self.choices is
|
|
||||||
# accessed) so that we can ensure the QuerySet has not been consumed. This
|
|
||||||
# construct might look complicated but it allows for lazy evaluation of
|
|
||||||
# the queryset.
|
|
||||||
return ModelChoiceIterator(self)
|
|
||||||
|
|
||||||
def _set_choices(self, value):
|
|
||||||
# This method is copied from ChoiceField._set_choices(). It's necessary
|
|
||||||
# because property() doesn't allow a subclass to overwrite only
|
|
||||||
# _get_choices without implementing _set_choices.
|
|
||||||
self._choices = self.widget.choices = list(value)
|
|
||||||
|
|
||||||
choices = property(_get_choices, _set_choices)
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
Field.clean(self, value)
|
|
||||||
if value in EMPTY_VALUES:
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
value = self.queryset.get(pk=value)
|
|
||||||
except self.queryset.model.DoesNotExist:
|
|
||||||
raise ValidationError(self.error_messages['invalid_choice'])
|
|
||||||
return value
|
|
||||||
|
|
||||||
class ModelMultipleChoiceField(ModelChoiceField):
|
|
||||||
"""A MultipleChoiceField whose choices are a model QuerySet."""
|
|
||||||
hidden_widget = MultipleHiddenInput
|
|
||||||
default_error_messages = {
|
|
||||||
'list': _(u'Enter a list of values.'),
|
|
||||||
'invalid_choice': _(u'Select a valid choice. %s is not one of the'
|
|
||||||
u' available choices.'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, queryset, cache_choices=False, required=True,
|
|
||||||
widget=SelectMultiple, label=None, initial=None,
|
|
||||||
help_text=None, *args, **kwargs):
|
|
||||||
super(ModelMultipleChoiceField, self).__init__(queryset, None,
|
|
||||||
cache_choices, required, widget, label, initial, help_text,
|
|
||||||
*args, **kwargs)
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
if self.required and not value:
|
|
||||||
raise ValidationError(self.error_messages['required'])
|
|
||||||
elif not self.required and not value:
|
|
||||||
return []
|
|
||||||
if not isinstance(value, (list, tuple)):
|
|
||||||
raise ValidationError(self.error_messages['list'])
|
|
||||||
final_values = []
|
|
||||||
for val in value:
|
|
||||||
try:
|
|
||||||
obj = self.queryset.get(pk=val)
|
|
||||||
except self.queryset.model.DoesNotExist:
|
|
||||||
raise ValidationError(self.error_messages['invalid_choice'] % val)
|
|
||||||
else:
|
|
||||||
final_values.append(obj)
|
|
||||||
return final_values
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
from django.utils.html import escape
|
|
||||||
from django.utils.encoding import smart_unicode, StrAndUnicode, force_unicode
|
|
||||||
from django.utils.functional import Promise
|
|
||||||
from django.utils.safestring import mark_safe
|
|
||||||
|
|
||||||
def flatatt(attrs):
|
|
||||||
"""
|
|
||||||
Convert a dictionary of attributes to a single string.
|
|
||||||
The returned string will contain a leading space followed by key="value",
|
|
||||||
XML-style pairs. It is assumed that the keys do not need to be XML-escaped.
|
|
||||||
If the passed dictionary is empty, then return an empty string.
|
|
||||||
"""
|
|
||||||
return u''.join([u' %s="%s"' % (k, escape(v)) for k, v in attrs.items()])
|
|
||||||
|
|
||||||
class ErrorDict(dict, StrAndUnicode):
|
|
||||||
"""
|
|
||||||
A collection of errors that knows how to display itself in various formats.
|
|
||||||
|
|
||||||
The dictionary keys are the field names, and the values are the errors.
|
|
||||||
"""
|
|
||||||
def __unicode__(self):
|
|
||||||
return self.as_ul()
|
|
||||||
|
|
||||||
def as_ul(self):
|
|
||||||
if not self: return u''
|
|
||||||
return mark_safe(u'<ul class="errorlist">%s</ul>'
|
|
||||||
% ''.join([u'<li>%s%s</li>' % (k, force_unicode(v))
|
|
||||||
for k, v in self.items()]))
|
|
||||||
|
|
||||||
def as_text(self):
|
|
||||||
return u'\n'.join([u'* %s\n%s' % (k, u'\n'.join([u' * %s' % force_unicode(i) for i in v])) for k, v in self.items()])
|
|
||||||
|
|
||||||
class ErrorList(list, StrAndUnicode):
|
|
||||||
"""
|
|
||||||
A collection of errors that knows how to display itself in various formats.
|
|
||||||
"""
|
|
||||||
def __unicode__(self):
|
|
||||||
return self.as_ul()
|
|
||||||
|
|
||||||
def as_ul(self):
|
|
||||||
if not self: return u''
|
|
||||||
return mark_safe(u'<ul class="errorlist">%s</ul>'
|
|
||||||
% ''.join([u'<li>%s</li>' % force_unicode(e) for e in self]))
|
|
||||||
|
|
||||||
def as_text(self):
|
|
||||||
if not self: return u''
|
|
||||||
return u'\n'.join([u'* %s' % force_unicode(e) for e in self])
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return repr([force_unicode(e) for e in self])
|
|
||||||
|
|
||||||
class ValidationError(Exception):
|
|
||||||
def __init__(self, message):
|
|
||||||
"""
|
|
||||||
ValidationError can be passed any object that can be printed (usually
|
|
||||||
a string) or a list of objects.
|
|
||||||
"""
|
|
||||||
self.message = message #newforms_portable!
|
|
||||||
if isinstance(message, list):
|
|
||||||
self.messages = ErrorList([smart_unicode(msg) for msg in message])
|
|
||||||
else:
|
|
||||||
message = smart_unicode(message)
|
|
||||||
self.messages = ErrorList([message])
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
# This is needed because, without a __str__(), printing an exception
|
|
||||||
# instance would result in this:
|
|
||||||
# AttributeError: ValidationError instance has no attribute 'args'
|
|
||||||
# See http://www.python.org/doc/current/tut/node10.html#handling
|
|
||||||
return repr(self.messages)
|
|
||||||
@ -1,471 +0,0 @@
|
|||||||
"""
|
|
||||||
HTML Widget classes
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
set
|
|
||||||
except NameError:
|
|
||||||
from sets import Set as set # Python 2.3 fallback
|
|
||||||
|
|
||||||
import copy
|
|
||||||
from itertools import chain
|
|
||||||
|
|
||||||
from django.utils.datastructures import MultiValueDict
|
|
||||||
from django.utils.html import escape, conditional_escape
|
|
||||||
from django.utils.translation import ugettext
|
|
||||||
from django.utils.encoding import StrAndUnicode, force_unicode
|
|
||||||
from django.utils.safestring import mark_safe
|
|
||||||
from util import flatatt
|
|
||||||
|
|
||||||
__all__ = (
|
|
||||||
'Widget', 'TextInput', 'PasswordInput',
|
|
||||||
'HiddenInput', 'MultipleHiddenInput',
|
|
||||||
'FileInput', 'DateTimeInput', 'Textarea', 'CheckboxInput',
|
|
||||||
'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
|
|
||||||
'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget',
|
|
||||||
)
|
|
||||||
|
|
||||||
class Widget(object):
|
|
||||||
is_hidden = False # Determines whether this corresponds to an <input type="hidden">.
|
|
||||||
needs_multipart_form = False # Determines does this widget need multipart-encrypted form
|
|
||||||
|
|
||||||
def __init__(self, attrs=None):
|
|
||||||
if attrs is not None:
|
|
||||||
self.attrs = attrs.copy()
|
|
||||||
else:
|
|
||||||
self.attrs = {}
|
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
|
||||||
obj = copy.copy(self)
|
|
||||||
obj.attrs = self.attrs.copy()
|
|
||||||
memo[id(self)] = obj
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def render(self, name, value, attrs=None):
|
|
||||||
"""
|
|
||||||
Returns this Widget rendered as HTML, as a Unicode string.
|
|
||||||
|
|
||||||
The 'value' given is not guaranteed to be valid input, so subclass
|
|
||||||
implementations should program defensively.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def build_attrs(self, extra_attrs=None, **kwargs):
|
|
||||||
"Helper function for building an attribute dictionary."
|
|
||||||
attrs = dict(self.attrs, **kwargs)
|
|
||||||
if extra_attrs:
|
|
||||||
attrs.update(extra_attrs)
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
def value_from_datadict(self, data, files, name):
|
|
||||||
"""
|
|
||||||
Given a dictionary of data and this widget's name, returns the value
|
|
||||||
of this widget. Returns None if it's not provided.
|
|
||||||
"""
|
|
||||||
return data.get(name, None)
|
|
||||||
|
|
||||||
def id_for_label(self, id_):
|
|
||||||
"""
|
|
||||||
Returns the HTML ID attribute of this Widget for use by a <label>,
|
|
||||||
given the ID of the field. Returns None if no ID is available.
|
|
||||||
|
|
||||||
This hook is necessary because some widgets have multiple HTML
|
|
||||||
elements and, thus, multiple IDs. In that case, this method should
|
|
||||||
return an ID value that corresponds to the first ID in the widget's
|
|
||||||
tags.
|
|
||||||
"""
|
|
||||||
return id_
|
|
||||||
id_for_label = classmethod(id_for_label)
|
|
||||||
|
|
||||||
class Input(Widget):
|
|
||||||
"""
|
|
||||||
Base class for all <input> widgets (except type='checkbox' and
|
|
||||||
type='radio', which are special).
|
|
||||||
"""
|
|
||||||
input_type = None # Subclasses must define this.
|
|
||||||
|
|
||||||
def render(self, name, value, attrs=None):
|
|
||||||
if value is None: value = ''
|
|
||||||
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
|
|
||||||
if value != '':
|
|
||||||
# Only add the 'value' attribute if a value is non-empty.
|
|
||||||
final_attrs['value'] = force_unicode(value)
|
|
||||||
return mark_safe(u'<input%s />' % flatatt(final_attrs))
|
|
||||||
|
|
||||||
class TextInput(Input):
|
|
||||||
input_type = 'text'
|
|
||||||
|
|
||||||
class PasswordInput(Input):
|
|
||||||
input_type = 'password'
|
|
||||||
|
|
||||||
def __init__(self, attrs=None, render_value=True):
|
|
||||||
super(PasswordInput, self).__init__(attrs)
|
|
||||||
self.render_value = render_value
|
|
||||||
|
|
||||||
def render(self, name, value, attrs=None):
|
|
||||||
if not self.render_value: value=None
|
|
||||||
return super(PasswordInput, self).render(name, value, attrs)
|
|
||||||
|
|
||||||
class HiddenInput(Input):
|
|
||||||
input_type = 'hidden'
|
|
||||||
is_hidden = True
|
|
||||||
|
|
||||||
class MultipleHiddenInput(HiddenInput):
|
|
||||||
"""
|
|
||||||
A widget that handles <input type="hidden"> for fields that have a list
|
|
||||||
of values.
|
|
||||||
"""
|
|
||||||
def __init__(self, attrs=None, choices=()):
|
|
||||||
super(MultipleHiddenInput, self).__init__(attrs)
|
|
||||||
# choices can be any iterable
|
|
||||||
self.choices = choices
|
|
||||||
|
|
||||||
def render(self, name, value, attrs=None, choices=()):
|
|
||||||
if value is None: value = []
|
|
||||||
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
|
|
||||||
return mark_safe(u'\n'.join([(u'<input%s />' %
|
|
||||||
flatatt(dict(value=force_unicode(v), **final_attrs)))
|
|
||||||
for v in value]))
|
|
||||||
|
|
||||||
def value_from_datadict(self, data, files, name):
|
|
||||||
if isinstance(data, MultiValueDict):
|
|
||||||
return data.getlist(name)
|
|
||||||
return data.get(name, None)
|
|
||||||
|
|
||||||
class FileInput(Input):
|
|
||||||
input_type = 'file'
|
|
||||||
needs_multipart_form = True
|
|
||||||
|
|
||||||
def render(self, name, value, attrs=None):
|
|
||||||
return super(FileInput, self).render(name, None, attrs=attrs)
|
|
||||||
|
|
||||||
def value_from_datadict(self, data, files, name):
|
|
||||||
"File widgets take data from FILES, not POST"
|
|
||||||
return files.get(name, None)
|
|
||||||
|
|
||||||
class Textarea(Widget):
|
|
||||||
def __init__(self, attrs=None):
|
|
||||||
# The 'rows' and 'cols' attributes are required for HTML correctness.
|
|
||||||
self.attrs = {'cols': '40', 'rows': '10'}
|
|
||||||
if attrs:
|
|
||||||
self.attrs.update(attrs)
|
|
||||||
|
|
||||||
def render(self, name, value, attrs=None):
|
|
||||||
if value is None: value = ''
|
|
||||||
value = force_unicode(value)
|
|
||||||
final_attrs = self.build_attrs(attrs, name=name)
|
|
||||||
return mark_safe(u'<textarea%s>%s</textarea>' % (flatatt(final_attrs),
|
|
||||||
conditional_escape(force_unicode(value))))
|
|
||||||
|
|
||||||
class DateTimeInput(Input):
|
|
||||||
input_type = 'text'
|
|
||||||
format = '%Y-%m-%d %H:%M:%S' # '2006-10-25 14:30:59'
|
|
||||||
|
|
||||||
def __init__(self, attrs=None, format=None):
|
|
||||||
super(DateTimeInput, self).__init__(attrs)
|
|
||||||
if format:
|
|
||||||
self.format = format
|
|
||||||
|
|
||||||
def render(self, name, value, attrs=None):
|
|
||||||
if value is None:
|
|
||||||
value = ''
|
|
||||||
elif hasattr(value, 'strftime'):
|
|
||||||
value = value.strftime(self.format)
|
|
||||||
return super(DateTimeInput, self).render(name, value, attrs)
|
|
||||||
|
|
||||||
class CheckboxInput(Widget):
|
|
||||||
def __init__(self, attrs=None, check_test=bool):
|
|
||||||
super(CheckboxInput, self).__init__(attrs)
|
|
||||||
# check_test is a callable that takes a value and returns True
|
|
||||||
# if the checkbox should be checked for that value.
|
|
||||||
self.check_test = check_test
|
|
||||||
|
|
||||||
def render(self, name, value, attrs=None):
|
|
||||||
final_attrs = self.build_attrs(attrs, type='checkbox', name=name)
|
|
||||||
try:
|
|
||||||
result = self.check_test(value)
|
|
||||||
except: # Silently catch exceptions
|
|
||||||
result = False
|
|
||||||
if result:
|
|
||||||
final_attrs['checked'] = 'checked'
|
|
||||||
if value not in ('', True, False, None):
|
|
||||||
# Only add the 'value' attribute if a value is non-empty.
|
|
||||||
final_attrs['value'] = force_unicode(value)
|
|
||||||
return mark_safe(u'<input%s />' % flatatt(final_attrs))
|
|
||||||
|
|
||||||
def value_from_datadict(self, data, files, name):
|
|
||||||
if name not in data:
|
|
||||||
# A missing value means False because HTML form submission does not
|
|
||||||
# send results for unselected checkboxes.
|
|
||||||
return False
|
|
||||||
return super(CheckboxInput, self).value_from_datadict(data, files, name)
|
|
||||||
|
|
||||||
class Select(Widget):
|
|
||||||
def __init__(self, attrs=None, choices=()):
|
|
||||||
super(Select, self).__init__(attrs)
|
|
||||||
# choices can be any iterable, but we may need to render this widget
|
|
||||||
# multiple times. Thus, collapse it into a list so it can be consumed
|
|
||||||
# more than once.
|
|
||||||
self.choices = list(choices)
|
|
||||||
|
|
||||||
def render(self, name, value, attrs=None, choices=()):
|
|
||||||
if value is None: value = ''
|
|
||||||
final_attrs = self.build_attrs(attrs, name=name)
|
|
||||||
output = [u'<select%s>' % flatatt(final_attrs)]
|
|
||||||
# Normalize to string.
|
|
||||||
str_value = force_unicode(value)
|
|
||||||
for option_value, option_label in chain(self.choices, choices):
|
|
||||||
option_value = force_unicode(option_value)
|
|
||||||
selected_html = (option_value == str_value) and u' selected="selected"' or ''
|
|
||||||
output.append(u'<option value="%s"%s>%s</option>' % (
|
|
||||||
escape(option_value), selected_html,
|
|
||||||
conditional_escape(force_unicode(option_label))))
|
|
||||||
output.append(u'</select>')
|
|
||||||
return mark_safe(u'\n'.join(output))
|
|
||||||
|
|
||||||
class NullBooleanSelect(Select):
|
|
||||||
"""
|
|
||||||
A Select Widget intended to be used with NullBooleanField.
|
|
||||||
"""
|
|
||||||
def __init__(self, attrs=None):
|
|
||||||
choices = ((u'1', ugettext('Unknown')), (u'2', ugettext('Yes')), (u'3', ugettext('No')))
|
|
||||||
super(NullBooleanSelect, self).__init__(attrs, choices)
|
|
||||||
|
|
||||||
def render(self, name, value, attrs=None, choices=()):
|
|
||||||
try:
|
|
||||||
value = {True: u'2', False: u'3', u'2': u'2', u'3': u'3'}[value]
|
|
||||||
except KeyError:
|
|
||||||
value = u'1'
|
|
||||||
return super(NullBooleanSelect, self).render(name, value, attrs, choices)
|
|
||||||
|
|
||||||
def value_from_datadict(self, data, files, name):
|
|
||||||
value = data.get(name, None)
|
|
||||||
return {u'2': True, u'3': False, True: True, False: False}.get(value, None)
|
|
||||||
|
|
||||||
class SelectMultiple(Widget):
|
|
||||||
def __init__(self, attrs=None, choices=()):
|
|
||||||
super(SelectMultiple, self).__init__(attrs)
|
|
||||||
# choices can be any iterable
|
|
||||||
self.choices = choices
|
|
||||||
|
|
||||||
def render(self, name, value, attrs=None, choices=()):
|
|
||||||
if value is None: value = []
|
|
||||||
final_attrs = self.build_attrs(attrs, name=name)
|
|
||||||
output = [u'<select multiple="multiple"%s>' % flatatt(final_attrs)]
|
|
||||||
str_values = set([force_unicode(v) for v in value]) # Normalize to strings.
|
|
||||||
for option_value, option_label in chain(self.choices, choices):
|
|
||||||
option_value = force_unicode(option_value)
|
|
||||||
selected_html = (option_value in str_values) and ' selected="selected"' or ''
|
|
||||||
output.append(u'<option value="%s"%s>%s</option>' % (
|
|
||||||
escape(option_value), selected_html,
|
|
||||||
conditional_escape(force_unicode(option_label))))
|
|
||||||
output.append(u'</select>')
|
|
||||||
return mark_safe(u'\n'.join(output))
|
|
||||||
|
|
||||||
def value_from_datadict(self, data, files, name):
|
|
||||||
if isinstance(data, MultiValueDict):
|
|
||||||
return data.getlist(name)
|
|
||||||
return data.get(name, None)
|
|
||||||
|
|
||||||
class RadioInput(StrAndUnicode):
|
|
||||||
"""
|
|
||||||
An object used by RadioFieldRenderer that represents a single
|
|
||||||
<input type='radio'>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name, value, attrs, choice, index):
|
|
||||||
self.name, self.value = name, value
|
|
||||||
self.attrs = attrs
|
|
||||||
self.choice_value = force_unicode(choice[0])
|
|
||||||
self.choice_label = force_unicode(choice[1])
|
|
||||||
self.index = index
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return mark_safe(u'<label>%s %s</label>' % (self.tag(),
|
|
||||||
conditional_escape(force_unicode(self.choice_label))))
|
|
||||||
|
|
||||||
def is_checked(self):
|
|
||||||
return self.value == self.choice_value
|
|
||||||
|
|
||||||
def tag(self):
|
|
||||||
if 'id' in self.attrs:
|
|
||||||
self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index)
|
|
||||||
final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value)
|
|
||||||
if self.is_checked():
|
|
||||||
final_attrs['checked'] = 'checked'
|
|
||||||
return mark_safe(u'<input%s />' % flatatt(final_attrs))
|
|
||||||
|
|
||||||
class RadioFieldRenderer(StrAndUnicode):
|
|
||||||
"""
|
|
||||||
An object used by RadioSelect to enable customization of radio widgets.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name, value, attrs, choices):
|
|
||||||
self.name, self.value, self.attrs = name, value, attrs
|
|
||||||
self.choices = choices
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
for i, choice in enumerate(self.choices):
|
|
||||||
yield RadioInput(self.name, self.value, self.attrs.copy(), choice, i)
|
|
||||||
|
|
||||||
def __getitem__(self, idx):
|
|
||||||
choice = self.choices[idx] # Let the IndexError propogate
|
|
||||||
return RadioInput(self.name, self.value, self.attrs.copy(), choice, idx)
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return self.render()
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
"""Outputs a <ul> for this set of radio fields."""
|
|
||||||
return mark_safe(u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>'
|
|
||||||
% force_unicode(w) for w in self]))
|
|
||||||
|
|
||||||
class RadioSelect(Select):
|
|
||||||
renderer = RadioFieldRenderer
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
# Override the default renderer if we were passed one.
|
|
||||||
renderer = kwargs.pop('renderer', None)
|
|
||||||
if renderer:
|
|
||||||
self.renderer = renderer
|
|
||||||
super(RadioSelect, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def get_renderer(self, name, value, attrs=None, choices=()):
|
|
||||||
"""Returns an instance of the renderer."""
|
|
||||||
if value is None: value = ''
|
|
||||||
str_value = force_unicode(value) # Normalize to string.
|
|
||||||
final_attrs = self.build_attrs(attrs)
|
|
||||||
choices = list(chain(self.choices, choices))
|
|
||||||
return self.renderer(name, str_value, final_attrs, choices)
|
|
||||||
|
|
||||||
def render(self, name, value, attrs=None, choices=()):
|
|
||||||
return self.get_renderer(name, value, attrs, choices).render()
|
|
||||||
|
|
||||||
def id_for_label(self, id_):
|
|
||||||
# RadioSelect is represented by multiple <input type="radio"> fields,
|
|
||||||
# each of which has a distinct ID. The IDs are made distinct by a "_X"
|
|
||||||
# suffix, where X is the zero-based index of the radio field. Thus,
|
|
||||||
# the label for a RadioSelect should reference the first one ('_0').
|
|
||||||
if id_:
|
|
||||||
id_ += '_0'
|
|
||||||
return id_
|
|
||||||
id_for_label = classmethod(id_for_label)
|
|
||||||
|
|
||||||
class CheckboxSelectMultiple(SelectMultiple):
|
|
||||||
def render(self, name, value, attrs=None, choices=()):
|
|
||||||
if value is None: value = []
|
|
||||||
has_id = attrs and 'id' in attrs
|
|
||||||
final_attrs = self.build_attrs(attrs, name=name)
|
|
||||||
output = [u'<ul>']
|
|
||||||
# Normalize to strings
|
|
||||||
str_values = set([force_unicode(v) for v in value])
|
|
||||||
for i, (option_value, option_label) in enumerate(chain(self.choices, choices)):
|
|
||||||
# If an ID attribute was given, add a numeric index as a suffix,
|
|
||||||
# so that the checkboxes don't all have the same ID attribute.
|
|
||||||
if has_id:
|
|
||||||
final_attrs = dict(final_attrs, id='%s_%s' % (attrs['id'], i))
|
|
||||||
cb = CheckboxInput(final_attrs, check_test=lambda value: value in str_values)
|
|
||||||
option_value = force_unicode(option_value)
|
|
||||||
rendered_cb = cb.render(name, option_value)
|
|
||||||
output.append(u'<li><label>%s %s</label></li>' % (rendered_cb,
|
|
||||||
conditional_escape(force_unicode(option_label))))
|
|
||||||
output.append(u'</ul>')
|
|
||||||
return mark_safe(u'\n'.join(output))
|
|
||||||
|
|
||||||
def id_for_label(self, id_):
|
|
||||||
# See the comment for RadioSelect.id_for_label()
|
|
||||||
if id_:
|
|
||||||
id_ += '_0'
|
|
||||||
return id_
|
|
||||||
id_for_label = classmethod(id_for_label)
|
|
||||||
|
|
||||||
class MultiWidget(Widget):
|
|
||||||
"""
|
|
||||||
A widget that is composed of multiple widgets.
|
|
||||||
|
|
||||||
Its render() method is different than other widgets', because it has to
|
|
||||||
figure out how to split a single value for display in multiple widgets.
|
|
||||||
The ``value`` argument can be one of two things:
|
|
||||||
|
|
||||||
* A list.
|
|
||||||
* A normal value (e.g., a string) that has been "compressed" from
|
|
||||||
a list of values.
|
|
||||||
|
|
||||||
In the second case -- i.e., if the value is NOT a list -- render() will
|
|
||||||
first "decompress" the value into a list before rendering it. It does so by
|
|
||||||
calling the decompress() method, which MultiWidget subclasses must
|
|
||||||
implement. This method takes a single "compressed" value and returns a
|
|
||||||
list.
|
|
||||||
|
|
||||||
When render() does its HTML rendering, each value in the list is rendered
|
|
||||||
with the corresponding widget -- the first value is rendered in the first
|
|
||||||
widget, the second value is rendered in the second widget, etc.
|
|
||||||
|
|
||||||
Subclasses may implement format_output(), which takes the list of rendered
|
|
||||||
widgets and returns a string of HTML that formats them any way you'd like.
|
|
||||||
|
|
||||||
You'll probably want to use this class with MultiValueField.
|
|
||||||
"""
|
|
||||||
def __init__(self, widgets, attrs=None):
|
|
||||||
self.widgets = [isinstance(w, type) and w() or w for w in widgets]
|
|
||||||
super(MultiWidget, self).__init__(attrs)
|
|
||||||
|
|
||||||
def render(self, name, value, attrs=None):
|
|
||||||
# value is a list of values, each corresponding to a widget
|
|
||||||
# in self.widgets.
|
|
||||||
if not isinstance(value, list):
|
|
||||||
value = self.decompress(value)
|
|
||||||
output = []
|
|
||||||
final_attrs = self.build_attrs(attrs)
|
|
||||||
id_ = final_attrs.get('id', None)
|
|
||||||
for i, widget in enumerate(self.widgets):
|
|
||||||
try:
|
|
||||||
widget_value = value[i]
|
|
||||||
except IndexError:
|
|
||||||
widget_value = None
|
|
||||||
if id_:
|
|
||||||
final_attrs = dict(final_attrs, id='%s_%s' % (id_, i))
|
|
||||||
output.append(widget.render(name + '_%s' % i, widget_value, final_attrs))
|
|
||||||
return mark_safe(self.format_output(output))
|
|
||||||
|
|
||||||
def id_for_label(self, id_):
|
|
||||||
# See the comment for RadioSelect.id_for_label()
|
|
||||||
if id_:
|
|
||||||
id_ += '_0'
|
|
||||||
return id_
|
|
||||||
id_for_label = classmethod(id_for_label)
|
|
||||||
|
|
||||||
def value_from_datadict(self, data, files, name):
|
|
||||||
return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)]
|
|
||||||
|
|
||||||
def format_output(self, rendered_widgets):
|
|
||||||
"""
|
|
||||||
Given a list of rendered widgets (as strings), returns a Unicode string
|
|
||||||
representing the HTML for the whole lot.
|
|
||||||
|
|
||||||
This hook allows you to format the HTML design of the widgets, if
|
|
||||||
needed.
|
|
||||||
"""
|
|
||||||
return u''.join(rendered_widgets)
|
|
||||||
|
|
||||||
def decompress(self, value):
|
|
||||||
"""
|
|
||||||
Returns a list of decompressed values for the given compressed value.
|
|
||||||
The given value can be assumed to be valid, but not necessarily
|
|
||||||
non-empty.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError('Subclasses must implement this method.')
|
|
||||||
|
|
||||||
class SplitDateTimeWidget(MultiWidget):
|
|
||||||
"""
|
|
||||||
A Widget that splits datetime input into two <input type="text"> boxes.
|
|
||||||
"""
|
|
||||||
def __init__(self, attrs=None):
|
|
||||||
widgets = (TextInput(attrs=attrs), TextInput(attrs=attrs))
|
|
||||||
super(SplitDateTimeWidget, self).__init__(widgets, attrs)
|
|
||||||
|
|
||||||
def decompress(self, value):
|
|
||||||
if value:
|
|
||||||
return [value.date(), value.time().replace(microsecond=0)]
|
|
||||||
return [None, None]
|
|
||||||
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
This folder may only contain general purpose utilities/files/tools.
|
|
||||||
They should be usable outside of deluge.
|
|
||||||
|
|
||||||
Disclaimer:
|
|
||||||
|
|
||||||
Some may have been adapted to work better with deluge.
|
|
||||||
But they will not import other parts of deluge or Webui.
|
|
||||||
|
|
||||||
LICENCE:
|
|
||||||
All components are GPL compatible.
|
|
||||||
All these components are Licensed under their original license.
|
|
||||||
See docstring or LICENSE files.
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,137 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
#(c) Martijn Voncken, mvoncken@gmail.com
|
|
||||||
#Same Licence as web.py 0.22
|
|
||||||
#
|
|
||||||
"""
|
|
||||||
static fileserving for web.py
|
|
||||||
without the need for wsgi wrapper magic.
|
|
||||||
"""
|
|
||||||
import web
|
|
||||||
from web import url
|
|
||||||
|
|
||||||
import posixpath
|
|
||||||
import urlparse
|
|
||||||
import urllib
|
|
||||||
import mimetypes
|
|
||||||
import os
|
|
||||||
import datetime
|
|
||||||
import cgi
|
|
||||||
from StringIO import StringIO
|
|
||||||
mimetypes.init() # try to read system mime.types
|
|
||||||
|
|
||||||
class static_handler:
|
|
||||||
"""
|
|
||||||
mostly c&p from SimpleHttpServer
|
|
||||||
serves relative from start location
|
|
||||||
"""
|
|
||||||
base_dir = './'
|
|
||||||
extensions_map = mimetypes.types_map
|
|
||||||
|
|
||||||
def get_base_dir(self):
|
|
||||||
#override this if you have a config that changes the base dir at runtime
|
|
||||||
#deluge on windows :(
|
|
||||||
return self.base_dir
|
|
||||||
|
|
||||||
def GET(self, path):
|
|
||||||
path = self.translate_path(path)
|
|
||||||
if os.path.isdir(path):
|
|
||||||
if not path.endswith('/'):
|
|
||||||
path += "/"
|
|
||||||
return self.list_directory(path)
|
|
||||||
|
|
||||||
ctype = self.guess_type(path)
|
|
||||||
|
|
||||||
try:
|
|
||||||
f = open(path, 'rb')
|
|
||||||
except IOError:
|
|
||||||
raise Exception('file not found:%s' % path)
|
|
||||||
#web.header("404", "File not found")
|
|
||||||
#return
|
|
||||||
web.header("Content-type", ctype)
|
|
||||||
fs = os.fstat(f.fileno())
|
|
||||||
web.header("Content-Length", str(fs[6]))
|
|
||||||
web.header("Cache-Control" , "public, must-revalidate, max-age=86400")
|
|
||||||
#web.lastmodified(datetime.datetime.fromtimestamp(fs.st_mtime))
|
|
||||||
print f.read()
|
|
||||||
|
|
||||||
def translate_path(self, path):
|
|
||||||
"""Translate a /-separated PATH to the local filename syntax.
|
|
||||||
|
|
||||||
Components that mean special things to the local file system
|
|
||||||
(e.g. drive or directory names) are ignored. (XXX They should
|
|
||||||
probably be diagnosed.)
|
|
||||||
|
|
||||||
"""
|
|
||||||
# abandon query parameters
|
|
||||||
path = urlparse.urlparse(path)[2]
|
|
||||||
path = posixpath.normpath(urllib.unquote(path))
|
|
||||||
words = path.split('/')
|
|
||||||
words = filter(None, words)
|
|
||||||
path = self.get_base_dir()
|
|
||||||
for word in words:
|
|
||||||
drive, word = os.path.splitdrive(word)
|
|
||||||
head, word = os.path.split(word)
|
|
||||||
if word in (os.curdir, os.pardir): continue
|
|
||||||
path = os.path.join(path, word)
|
|
||||||
return path
|
|
||||||
|
|
||||||
def guess_type(self, path):
|
|
||||||
base, ext = posixpath.splitext(path)
|
|
||||||
if ext in self.extensions_map:
|
|
||||||
return self.extensions_map[ext]
|
|
||||||
ext = ext.lower()
|
|
||||||
if ext in self.extensions_map:
|
|
||||||
return self.extensions_map[ext]
|
|
||||||
else:
|
|
||||||
return 'application/octet-stream'
|
|
||||||
|
|
||||||
|
|
||||||
def list_directory(self, path):
|
|
||||||
"""Helper to produce a directory listing (absent index.html).
|
|
||||||
|
|
||||||
Return value is either a file object, or None (indicating an
|
|
||||||
error). In either case, the headers are sent, making the
|
|
||||||
interface the same as for send_head().
|
|
||||||
#TODO ->use web.py +template!
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
list = os.listdir(path)
|
|
||||||
except os.error:
|
|
||||||
web.header('404', "No permission to list directory")
|
|
||||||
return None
|
|
||||||
list.sort(key=lambda a: a.lower())
|
|
||||||
f = StringIO()
|
|
||||||
displaypath = cgi.escape(urllib.unquote(path))
|
|
||||||
f.write("<title>Directory listing for %s</title>\n" % displaypath)
|
|
||||||
f.write("<h2>Directory listing for %s</h2>\n" % displaypath)
|
|
||||||
f.write("<hr>\n<ul>\n")
|
|
||||||
for name in list:
|
|
||||||
fullname = os.path.join(path, name)
|
|
||||||
displayname = linkname = name
|
|
||||||
# Append / for directories or @ for symbolic links
|
|
||||||
if os.path.isdir(fullname):
|
|
||||||
displayname = name + "/"
|
|
||||||
linkname = name + "/"
|
|
||||||
if os.path.islink(fullname):
|
|
||||||
displayname = name + "@"
|
|
||||||
# Note: a link to a directory displays with @ and links with /
|
|
||||||
f.write('<li><a href="%s">%s</a>\n'
|
|
||||||
% (urllib.quote(linkname), cgi.escape(displayname)))
|
|
||||||
f.write("</ul>\n<hr>\n")
|
|
||||||
length = f.tell()
|
|
||||||
f.seek(0)
|
|
||||||
|
|
||||||
web.header("Content-type", "text/html")
|
|
||||||
web.header("Content-Length", str(length))
|
|
||||||
print f.read()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
#example:
|
|
||||||
class usr_static(static_handler):
|
|
||||||
base_dir = os.path.expanduser('~')
|
|
||||||
|
|
||||||
urls = ('/relative/(.*)','static_handler',
|
|
||||||
'/(.*)','usr_static')
|
|
||||||
|
|
||||||
web.run(urls,globals())
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
#compatibility: use the included version/release of web.py.
|
|
||||||
from webpy022 import *
|
|
||||||
@ -1 +0,0 @@
|
|||||||
http://webpy.org/
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
web.py is public domain.
|
|
||||||
see : http://webpy.org/
|
|
||||||
|
|
||||||
@ -1,62 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
from __future__ import generators
|
|
||||||
|
|
||||||
"""web.py: makes web apps (http://webpy.org)"""
|
|
||||||
__version__ = "0.22"
|
|
||||||
__revision__ = "$Rev: 183 $"
|
|
||||||
__author__ = "Aaron Swartz <me@aaronsw.com>"
|
|
||||||
__license__ = "public domain"
|
|
||||||
__contributors__ = "see http://webpy.org/changes"
|
|
||||||
|
|
||||||
# todo:
|
|
||||||
# - some sort of accounts system
|
|
||||||
|
|
||||||
import utils, db, net, wsgi, http, webapi, request, httpserver, debugerror
|
|
||||||
import template, form
|
|
||||||
|
|
||||||
from utils import *
|
|
||||||
from db import *
|
|
||||||
from net import *
|
|
||||||
from wsgi import *
|
|
||||||
from http import *
|
|
||||||
from webapi import *
|
|
||||||
from request import *
|
|
||||||
from httpserver import *
|
|
||||||
from debugerror import *
|
|
||||||
|
|
||||||
try:
|
|
||||||
import cheetah
|
|
||||||
from cheetah import *
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def main():
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod(utils)
|
|
||||||
doctest.testmod(db)
|
|
||||||
doctest.testmod(net)
|
|
||||||
doctest.testmod(wsgi)
|
|
||||||
doctest.testmod(http)
|
|
||||||
doctest.testmod(webapi)
|
|
||||||
doctest.testmod(request)
|
|
||||||
|
|
||||||
try:
|
|
||||||
doctest.testmod(cheetah)
|
|
||||||
except NameError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
template.test()
|
|
||||||
|
|
||||||
import sys
|
|
||||||
urls = ('/web.py', 'source')
|
|
||||||
class source:
|
|
||||||
def GET(self):
|
|
||||||
header('Content-Type', 'text/python')
|
|
||||||
print open(sys.argv[0]).read()
|
|
||||||
|
|
||||||
if listget(sys.argv, 1) != 'test':
|
|
||||||
run(urls, locals())
|
|
||||||
|
|
||||||
if __name__ == "__main__": main()
|
|
||||||
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
1:Commented out some code to enable a relative redirect.
|
|
||||||
This is not according to HTTP/1.1 Spec
|
|
||||||
But many deluge users will want to route the webui through firewalls/routers or use apache redirects.
|
|
||||||
|
|
||||||
2:Disabled logging in the builtin http-server.
|
|
||||||
@ -1,98 +0,0 @@
|
|||||||
"""
|
|
||||||
Cheetah API
|
|
||||||
(from web.py)
|
|
||||||
"""
|
|
||||||
|
|
||||||
__all__ = ["render"]
|
|
||||||
|
|
||||||
import re, urlparse, pprint, traceback, sys
|
|
||||||
from Cheetah.Compiler import Compiler
|
|
||||||
from Cheetah.Filters import Filter
|
|
||||||
from utils import re_compile, memoize, dictadd
|
|
||||||
from net import htmlquote, websafe
|
|
||||||
from webapi import ctx, header, output, input, cookies, loadhooks
|
|
||||||
|
|
||||||
def upvars(level=2):
|
|
||||||
"""Guido van Rossum sez: don't use this function."""
|
|
||||||
return dictadd(
|
|
||||||
sys._getframe(level).f_globals,
|
|
||||||
sys._getframe(level).f_locals)
|
|
||||||
|
|
||||||
r_include = re_compile(r'(?!\\)#include \"(.*?)\"($|#)', re.M)
|
|
||||||
def __compiletemplate(template, base=None, isString=False):
|
|
||||||
if isString:
|
|
||||||
text = template
|
|
||||||
else:
|
|
||||||
text = open('templates/'+template).read()
|
|
||||||
# implement #include at compile-time
|
|
||||||
def do_include(match):
|
|
||||||
text = open('templates/'+match.groups()[0]).read()
|
|
||||||
return text
|
|
||||||
while r_include.findall(text):
|
|
||||||
text = r_include.sub(do_include, text)
|
|
||||||
|
|
||||||
execspace = _compiletemplate.bases.copy()
|
|
||||||
tmpl_compiler = Compiler(source=text, mainClassName='GenTemplate')
|
|
||||||
tmpl_compiler.addImportedVarNames(execspace.keys())
|
|
||||||
exec str(tmpl_compiler) in execspace
|
|
||||||
if base:
|
|
||||||
_compiletemplate.bases[base] = execspace['GenTemplate']
|
|
||||||
|
|
||||||
return execspace['GenTemplate']
|
|
||||||
|
|
||||||
_compiletemplate = memoize(__compiletemplate)
|
|
||||||
_compiletemplate.bases = {}
|
|
||||||
|
|
||||||
def render(template, terms=None, asTemplate=False, base=None,
|
|
||||||
isString=False):
|
|
||||||
"""
|
|
||||||
Renders a template, caching where it can.
|
|
||||||
|
|
||||||
`template` is the name of a file containing the a template in
|
|
||||||
the `templates/` folder, unless `isString`, in which case it's the
|
|
||||||
template itself.
|
|
||||||
|
|
||||||
`terms` is a dictionary used to fill the template. If it's None, then
|
|
||||||
the caller's local variables are used instead, plus context, if it's not
|
|
||||||
already set, is set to `context`.
|
|
||||||
|
|
||||||
If asTemplate is False, it `output`s the template directly. Otherwise,
|
|
||||||
it returns the template object.
|
|
||||||
|
|
||||||
If the template is a potential base template (that is, something other templates)
|
|
||||||
can extend, then base should be a string with the name of the template. The
|
|
||||||
template will be cached and made available for future calls to `render`.
|
|
||||||
|
|
||||||
Requires [Cheetah](http://cheetahtemplate.org/).
|
|
||||||
"""
|
|
||||||
# terms=['var1', 'var2'] means grab those variables
|
|
||||||
if isinstance(terms, list):
|
|
||||||
new = {}
|
|
||||||
old = upvars()
|
|
||||||
for k in terms:
|
|
||||||
new[k] = old[k]
|
|
||||||
terms = new
|
|
||||||
# default: grab all locals
|
|
||||||
elif terms is None:
|
|
||||||
terms = {'context': ctx, 'ctx':ctx}
|
|
||||||
terms.update(sys._getframe(1).f_locals)
|
|
||||||
# terms=d means use d as the searchList
|
|
||||||
if not isinstance(terms, tuple):
|
|
||||||
terms = (terms,)
|
|
||||||
|
|
||||||
if 'headers' in ctx and not isString and template.endswith('.html'):
|
|
||||||
header('Content-Type','text/html; charset=utf-8', unique=True)
|
|
||||||
|
|
||||||
if loadhooks.has_key('reloader'):
|
|
||||||
compiled_tmpl = __compiletemplate(template, base=base, isString=isString)
|
|
||||||
else:
|
|
||||||
compiled_tmpl = _compiletemplate(template, base=base, isString=isString)
|
|
||||||
compiled_tmpl = compiled_tmpl(searchList=terms, filter=WebSafe)
|
|
||||||
if asTemplate:
|
|
||||||
return compiled_tmpl
|
|
||||||
else:
|
|
||||||
return output(str(compiled_tmpl))
|
|
||||||
|
|
||||||
class WebSafe(Filter):
|
|
||||||
def filter(self, val, **keywords):
|
|
||||||
return websafe(val)
|
|
||||||
@ -1,703 +0,0 @@
|
|||||||
"""
|
|
||||||
Database API
|
|
||||||
(part of web.py)
|
|
||||||
"""
|
|
||||||
|
|
||||||
# todo:
|
|
||||||
# - test with sqlite
|
|
||||||
# - a store function?
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"UnknownParamstyle", "UnknownDB",
|
|
||||||
"sqllist", "sqlors", "aparam", "reparam",
|
|
||||||
"SQLQuery", "sqlquote",
|
|
||||||
"SQLLiteral", "sqlliteral",
|
|
||||||
"connect",
|
|
||||||
"TransactionError", "transaction", "transact", "commit", "rollback",
|
|
||||||
"query",
|
|
||||||
"select", "insert", "update", "delete"
|
|
||||||
]
|
|
||||||
|
|
||||||
import time
|
|
||||||
try: import datetime
|
|
||||||
except ImportError: datetime = None
|
|
||||||
|
|
||||||
from utils import storage, iters, iterbetter
|
|
||||||
import webapi as web
|
|
||||||
|
|
||||||
try:
|
|
||||||
from DBUtils import PooledDB
|
|
||||||
web.config._hasPooling = True
|
|
||||||
except ImportError:
|
|
||||||
web.config._hasPooling = False
|
|
||||||
|
|
||||||
class _ItplError(ValueError):
|
|
||||||
def __init__(self, text, pos):
|
|
||||||
ValueError.__init__(self)
|
|
||||||
self.text = text
|
|
||||||
self.pos = pos
|
|
||||||
def __str__(self):
|
|
||||||
return "unfinished expression in %s at char %d" % (
|
|
||||||
repr(self.text), self.pos)
|
|
||||||
|
|
||||||
def _interpolate(format):
|
|
||||||
"""
|
|
||||||
Takes a format string and returns a list of 2-tuples of the form
|
|
||||||
(boolean, string) where boolean says whether string should be evaled
|
|
||||||
or not.
|
|
||||||
|
|
||||||
from <http://lfw.org/python/Itpl.py> (public domain, Ka-Ping Yee)
|
|
||||||
"""
|
|
||||||
from tokenize import tokenprog
|
|
||||||
|
|
||||||
def matchorfail(text, pos):
|
|
||||||
match = tokenprog.match(text, pos)
|
|
||||||
if match is None:
|
|
||||||
raise _ItplError(text, pos)
|
|
||||||
return match, match.end()
|
|
||||||
|
|
||||||
namechars = "abcdefghijklmnopqrstuvwxyz" \
|
|
||||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
|
|
||||||
chunks = []
|
|
||||||
pos = 0
|
|
||||||
|
|
||||||
while 1:
|
|
||||||
dollar = format.find("$", pos)
|
|
||||||
if dollar < 0:
|
|
||||||
break
|
|
||||||
nextchar = format[dollar + 1]
|
|
||||||
|
|
||||||
if nextchar == "{":
|
|
||||||
chunks.append((0, format[pos:dollar]))
|
|
||||||
pos, level = dollar + 2, 1
|
|
||||||
while level:
|
|
||||||
match, pos = matchorfail(format, pos)
|
|
||||||
tstart, tend = match.regs[3]
|
|
||||||
token = format[tstart:tend]
|
|
||||||
if token == "{":
|
|
||||||
level = level + 1
|
|
||||||
elif token == "}":
|
|
||||||
level = level - 1
|
|
||||||
chunks.append((1, format[dollar + 2:pos - 1]))
|
|
||||||
|
|
||||||
elif nextchar in namechars:
|
|
||||||
chunks.append((0, format[pos:dollar]))
|
|
||||||
match, pos = matchorfail(format, dollar + 1)
|
|
||||||
while pos < len(format):
|
|
||||||
if format[pos] == "." and \
|
|
||||||
pos + 1 < len(format) and format[pos + 1] in namechars:
|
|
||||||
match, pos = matchorfail(format, pos + 1)
|
|
||||||
elif format[pos] in "([":
|
|
||||||
pos, level = pos + 1, 1
|
|
||||||
while level:
|
|
||||||
match, pos = matchorfail(format, pos)
|
|
||||||
tstart, tend = match.regs[3]
|
|
||||||
token = format[tstart:tend]
|
|
||||||
if token[0] in "([":
|
|
||||||
level = level + 1
|
|
||||||
elif token[0] in ")]":
|
|
||||||
level = level - 1
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
chunks.append((1, format[dollar + 1:pos]))
|
|
||||||
|
|
||||||
else:
|
|
||||||
chunks.append((0, format[pos:dollar + 1]))
|
|
||||||
pos = dollar + 1 + (nextchar == "$")
|
|
||||||
|
|
||||||
if pos < len(format):
|
|
||||||
chunks.append((0, format[pos:]))
|
|
||||||
return chunks
|
|
||||||
|
|
||||||
class UnknownParamstyle(Exception):
|
|
||||||
"""
|
|
||||||
raised for unsupported db paramstyles
|
|
||||||
|
|
||||||
(currently supported: qmark, numeric, format, pyformat)
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def aparam():
|
|
||||||
"""
|
|
||||||
Returns the appropriate string to be used to interpolate
|
|
||||||
a value with the current `web.ctx.db_module` or simply %s
|
|
||||||
if there isn't one.
|
|
||||||
|
|
||||||
>>> aparam()
|
|
||||||
'%s'
|
|
||||||
"""
|
|
||||||
if hasattr(web.ctx, 'db_module'):
|
|
||||||
style = web.ctx.db_module.paramstyle
|
|
||||||
else:
|
|
||||||
style = 'pyformat'
|
|
||||||
|
|
||||||
if style == 'qmark':
|
|
||||||
return '?'
|
|
||||||
elif style == 'numeric':
|
|
||||||
return ':1'
|
|
||||||
elif style in ['format', 'pyformat']:
|
|
||||||
return '%s'
|
|
||||||
raise UnknownParamstyle, style
|
|
||||||
|
|
||||||
def reparam(string_, dictionary):
|
|
||||||
"""
|
|
||||||
Takes a string and a dictionary and interpolates the string
|
|
||||||
using values from the dictionary. Returns an `SQLQuery` for the result.
|
|
||||||
|
|
||||||
>>> reparam("s = $s", dict(s=True))
|
|
||||||
<sql: "s = 't'">
|
|
||||||
"""
|
|
||||||
vals = []
|
|
||||||
result = []
|
|
||||||
for live, chunk in _interpolate(string_):
|
|
||||||
if live:
|
|
||||||
result.append(aparam())
|
|
||||||
vals.append(eval(chunk, dictionary))
|
|
||||||
else: result.append(chunk)
|
|
||||||
return SQLQuery(''.join(result), vals)
|
|
||||||
|
|
||||||
def sqlify(obj):
|
|
||||||
"""
|
|
||||||
converts `obj` to its proper SQL version
|
|
||||||
|
|
||||||
>>> sqlify(None)
|
|
||||||
'NULL'
|
|
||||||
>>> sqlify(True)
|
|
||||||
"'t'"
|
|
||||||
>>> sqlify(3)
|
|
||||||
'3'
|
|
||||||
"""
|
|
||||||
|
|
||||||
# because `1 == True and hash(1) == hash(True)`
|
|
||||||
# we have to do this the hard way...
|
|
||||||
|
|
||||||
if obj is None:
|
|
||||||
return 'NULL'
|
|
||||||
elif obj is True:
|
|
||||||
return "'t'"
|
|
||||||
elif obj is False:
|
|
||||||
return "'f'"
|
|
||||||
elif datetime and isinstance(obj, datetime.datetime):
|
|
||||||
return repr(obj.isoformat())
|
|
||||||
else:
|
|
||||||
return repr(obj)
|
|
||||||
|
|
||||||
class SQLQuery:
|
|
||||||
"""
|
|
||||||
You can pass this sort of thing as a clause in any db function.
|
|
||||||
Otherwise, you can pass a dictionary to the keyword argument `vars`
|
|
||||||
and the function will call reparam for you.
|
|
||||||
"""
|
|
||||||
# tested in sqlquote's docstring
|
|
||||||
def __init__(self, s='', v=()):
|
|
||||||
self.s, self.v = str(s), tuple(v)
|
|
||||||
|
|
||||||
def __getitem__(self, key): # for backwards-compatibility
|
|
||||||
return [self.s, self.v][key]
|
|
||||||
|
|
||||||
def __add__(self, other):
|
|
||||||
if isinstance(other, str):
|
|
||||||
self.s += other
|
|
||||||
elif isinstance(other, SQLQuery):
|
|
||||||
self.s += other.s
|
|
||||||
self.v += other.v
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __radd__(self, other):
|
|
||||||
if isinstance(other, str):
|
|
||||||
self.s = other + self.s
|
|
||||||
return self
|
|
||||||
else:
|
|
||||||
return NotImplemented
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
try:
|
|
||||||
return self.s % tuple([sqlify(x) for x in self.v])
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
return self.s
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<sql: %s>' % repr(str(self))
|
|
||||||
|
|
||||||
class SQLLiteral:
|
|
||||||
"""
|
|
||||||
Protects a string from `sqlquote`.
|
|
||||||
|
|
||||||
>>> insert('foo', time=SQLLiteral('NOW()'), _test=True)
|
|
||||||
<sql: 'INSERT INTO foo (time) VALUES (NOW())'>
|
|
||||||
"""
|
|
||||||
def __init__(self, v):
|
|
||||||
self.v = v
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return self.v
|
|
||||||
|
|
||||||
sqlliteral = SQLLiteral
|
|
||||||
|
|
||||||
def sqlquote(a):
|
|
||||||
"""
|
|
||||||
Ensures `a` is quoted properly for use in a SQL query.
|
|
||||||
|
|
||||||
>>> 'WHERE x = ' + sqlquote(True) + ' AND y = ' + sqlquote(3)
|
|
||||||
<sql: "WHERE x = 't' AND y = 3">
|
|
||||||
"""
|
|
||||||
return SQLQuery(aparam(), (a,))
|
|
||||||
|
|
||||||
class UnknownDB(Exception):
|
|
||||||
"""raised for unsupported dbms"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def connect(dbn, **keywords):
|
|
||||||
"""
|
|
||||||
Connects to the specified database.
|
|
||||||
|
|
||||||
`dbn` currently must be "postgres", "mysql", or "sqlite".
|
|
||||||
|
|
||||||
If DBUtils is installed, connection pooling will be used.
|
|
||||||
"""
|
|
||||||
if dbn == "postgres":
|
|
||||||
try:
|
|
||||||
import psycopg2 as db
|
|
||||||
except ImportError:
|
|
||||||
try:
|
|
||||||
import psycopg as db
|
|
||||||
except ImportError:
|
|
||||||
import pgdb as db
|
|
||||||
if 'pw' in keywords:
|
|
||||||
keywords['password'] = keywords['pw']
|
|
||||||
del keywords['pw']
|
|
||||||
keywords['database'] = keywords['db']
|
|
||||||
del keywords['db']
|
|
||||||
|
|
||||||
elif dbn == "mysql":
|
|
||||||
import MySQLdb as db
|
|
||||||
if 'pw' in keywords:
|
|
||||||
keywords['passwd'] = keywords['pw']
|
|
||||||
del keywords['pw']
|
|
||||||
db.paramstyle = 'pyformat' # it's both, like psycopg
|
|
||||||
|
|
||||||
elif dbn == "sqlite":
|
|
||||||
try:
|
|
||||||
import sqlite3 as db
|
|
||||||
db.paramstyle = 'qmark'
|
|
||||||
except ImportError:
|
|
||||||
try:
|
|
||||||
from pysqlite2 import dbapi2 as db
|
|
||||||
db.paramstyle = 'qmark'
|
|
||||||
except ImportError:
|
|
||||||
import sqlite as db
|
|
||||||
web.config._hasPooling = False
|
|
||||||
keywords['database'] = keywords['db']
|
|
||||||
del keywords['db']
|
|
||||||
|
|
||||||
elif dbn == "firebird":
|
|
||||||
import kinterbasdb as db
|
|
||||||
if 'pw' in keywords:
|
|
||||||
keywords['passwd'] = keywords['pw']
|
|
||||||
del keywords['pw']
|
|
||||||
keywords['database'] = keywords['db']
|
|
||||||
del keywords['db']
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise UnknownDB, dbn
|
|
||||||
|
|
||||||
web.ctx.db_name = dbn
|
|
||||||
web.ctx.db_module = db
|
|
||||||
web.ctx.db_transaction = 0
|
|
||||||
web.ctx.db = keywords
|
|
||||||
|
|
||||||
def _PooledDB(db, keywords):
|
|
||||||
# In DBUtils 0.9.3, `dbapi` argument is renamed as `creator`
|
|
||||||
# see Bug#122112
|
|
||||||
if PooledDB.__version__.split('.') < '0.9.3'.split('.'):
|
|
||||||
return PooledDB.PooledDB(dbapi=db, **keywords)
|
|
||||||
else:
|
|
||||||
return PooledDB.PooledDB(creator=db, **keywords)
|
|
||||||
|
|
||||||
def db_cursor():
|
|
||||||
if isinstance(web.ctx.db, dict):
|
|
||||||
keywords = web.ctx.db
|
|
||||||
if web.config._hasPooling:
|
|
||||||
if 'db' not in globals():
|
|
||||||
globals()['db'] = _PooledDB(db, keywords)
|
|
||||||
web.ctx.db = globals()['db'].connection()
|
|
||||||
else:
|
|
||||||
web.ctx.db = db.connect(**keywords)
|
|
||||||
return web.ctx.db.cursor()
|
|
||||||
web.ctx.db_cursor = db_cursor
|
|
||||||
|
|
||||||
web.ctx.dbq_count = 0
|
|
||||||
|
|
||||||
def db_execute(cur, sql_query, dorollback=True):
|
|
||||||
"""executes an sql query"""
|
|
||||||
|
|
||||||
web.ctx.dbq_count += 1
|
|
||||||
|
|
||||||
try:
|
|
||||||
a = time.time()
|
|
||||||
out = cur.execute(sql_query.s, sql_query.v)
|
|
||||||
b = time.time()
|
|
||||||
except:
|
|
||||||
if web.config.get('db_printing'):
|
|
||||||
print >> web.debug, 'ERR:', str(sql_query)
|
|
||||||
if dorollback: rollback(care=False)
|
|
||||||
raise
|
|
||||||
|
|
||||||
if web.config.get('db_printing'):
|
|
||||||
print >> web.debug, '%s (%s): %s' % (round(b-a, 2), web.ctx.dbq_count, str(sql_query))
|
|
||||||
|
|
||||||
return out
|
|
||||||
web.ctx.db_execute = db_execute
|
|
||||||
return web.ctx.db
|
|
||||||
|
|
||||||
class TransactionError(Exception): pass
|
|
||||||
|
|
||||||
class transaction:
|
|
||||||
"""
|
|
||||||
A context that can be used in conjunction with "with" statements
|
|
||||||
to implement SQL transactions. Starts a transaction on enter,
|
|
||||||
rolls it back if there's an error; otherwise it commits it at the
|
|
||||||
end.
|
|
||||||
"""
|
|
||||||
def __enter__(self):
|
|
||||||
transact()
|
|
||||||
|
|
||||||
def __exit__(self, exctype, excvalue, traceback):
|
|
||||||
if exctype is not None:
|
|
||||||
rollback()
|
|
||||||
else:
|
|
||||||
commit()
|
|
||||||
|
|
||||||
def transact():
|
|
||||||
"""Start a transaction."""
|
|
||||||
if not web.ctx.db_transaction:
|
|
||||||
# commit everything up to now, so we don't rollback it later
|
|
||||||
if hasattr(web.ctx.db, 'commit'):
|
|
||||||
web.ctx.db.commit()
|
|
||||||
else:
|
|
||||||
db_cursor = web.ctx.db_cursor()
|
|
||||||
web.ctx.db_execute(db_cursor,
|
|
||||||
SQLQuery("SAVEPOINT webpy_sp_%s" % web.ctx.db_transaction))
|
|
||||||
web.ctx.db_transaction += 1
|
|
||||||
|
|
||||||
def commit():
|
|
||||||
"""Commits a transaction."""
|
|
||||||
web.ctx.db_transaction -= 1
|
|
||||||
if web.ctx.db_transaction < 0:
|
|
||||||
raise TransactionError, "not in a transaction"
|
|
||||||
|
|
||||||
if not web.ctx.db_transaction:
|
|
||||||
if hasattr(web.ctx.db, 'commit'):
|
|
||||||
web.ctx.db.commit()
|
|
||||||
else:
|
|
||||||
db_cursor = web.ctx.db_cursor()
|
|
||||||
web.ctx.db_execute(db_cursor,
|
|
||||||
SQLQuery("RELEASE SAVEPOINT webpy_sp_%s" % web.ctx.db_transaction))
|
|
||||||
|
|
||||||
def rollback(care=True):
|
|
||||||
"""Rolls back a transaction."""
|
|
||||||
web.ctx.db_transaction -= 1
|
|
||||||
if web.ctx.db_transaction < 0:
|
|
||||||
web.db_transaction = 0
|
|
||||||
if care:
|
|
||||||
raise TransactionError, "not in a transaction"
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
if not web.ctx.db_transaction:
|
|
||||||
if hasattr(web.ctx.db, 'rollback'):
|
|
||||||
web.ctx.db.rollback()
|
|
||||||
else:
|
|
||||||
db_cursor = web.ctx.db_cursor()
|
|
||||||
web.ctx.db_execute(db_cursor,
|
|
||||||
SQLQuery("ROLLBACK TO SAVEPOINT webpy_sp_%s" % web.ctx.db_transaction),
|
|
||||||
dorollback=False)
|
|
||||||
|
|
||||||
def query(sql_query, vars=None, processed=False, _test=False):
|
|
||||||
"""
|
|
||||||
Execute SQL query `sql_query` using dictionary `vars` to interpolate it.
|
|
||||||
If `processed=True`, `vars` is a `reparam`-style list to use
|
|
||||||
instead of interpolating.
|
|
||||||
|
|
||||||
>>> query("SELECT * FROM foo", _test=True)
|
|
||||||
<sql: 'SELECT * FROM foo'>
|
|
||||||
>>> query("SELECT * FROM foo WHERE x = $x", vars=dict(x='f'), _test=True)
|
|
||||||
<sql: "SELECT * FROM foo WHERE x = 'f'">
|
|
||||||
>>> query("SELECT * FROM foo WHERE x = " + sqlquote('f'), _test=True)
|
|
||||||
<sql: "SELECT * FROM foo WHERE x = 'f'">
|
|
||||||
"""
|
|
||||||
if vars is None: vars = {}
|
|
||||||
|
|
||||||
if not processed and not isinstance(sql_query, SQLQuery):
|
|
||||||
sql_query = reparam(sql_query, vars)
|
|
||||||
|
|
||||||
if _test: return sql_query
|
|
||||||
|
|
||||||
db_cursor = web.ctx.db_cursor()
|
|
||||||
web.ctx.db_execute(db_cursor, sql_query)
|
|
||||||
|
|
||||||
if db_cursor.description:
|
|
||||||
names = [x[0] for x in db_cursor.description]
|
|
||||||
def iterwrapper():
|
|
||||||
row = db_cursor.fetchone()
|
|
||||||
while row:
|
|
||||||
yield storage(dict(zip(names, row)))
|
|
||||||
row = db_cursor.fetchone()
|
|
||||||
out = iterbetter(iterwrapper())
|
|
||||||
if web.ctx.db_name != "sqlite":
|
|
||||||
out.__len__ = lambda: int(db_cursor.rowcount)
|
|
||||||
out.list = lambda: [storage(dict(zip(names, x))) \
|
|
||||||
for x in db_cursor.fetchall()]
|
|
||||||
else:
|
|
||||||
out = db_cursor.rowcount
|
|
||||||
|
|
||||||
if not web.ctx.db_transaction: web.ctx.db.commit()
|
|
||||||
return out
|
|
||||||
|
|
||||||
def sqllist(lst):
|
|
||||||
"""
|
|
||||||
Converts the arguments for use in something like a WHERE clause.
|
|
||||||
|
|
||||||
>>> sqllist(['a', 'b'])
|
|
||||||
'a, b'
|
|
||||||
>>> sqllist('a')
|
|
||||||
'a'
|
|
||||||
|
|
||||||
"""
|
|
||||||
if isinstance(lst, str):
|
|
||||||
return lst
|
|
||||||
else:
|
|
||||||
return ', '.join(lst)
|
|
||||||
|
|
||||||
def sqlors(left, lst):
|
|
||||||
"""
|
|
||||||
`left is a SQL clause like `tablename.arg = `
|
|
||||||
and `lst` is a list of values. Returns a reparam-style
|
|
||||||
pair featuring the SQL that ORs together the clause
|
|
||||||
for each item in the lst.
|
|
||||||
|
|
||||||
>>> sqlors('foo = ', [])
|
|
||||||
<sql: '2+2=5'>
|
|
||||||
>>> sqlors('foo = ', [1])
|
|
||||||
<sql: 'foo = 1'>
|
|
||||||
>>> sqlors('foo = ', 1)
|
|
||||||
<sql: 'foo = 1'>
|
|
||||||
>>> sqlors('foo = ', [1,2,3])
|
|
||||||
<sql: '(foo = 1 OR foo = 2 OR foo = 3)'>
|
|
||||||
"""
|
|
||||||
if isinstance(lst, iters):
|
|
||||||
lst = list(lst)
|
|
||||||
ln = len(lst)
|
|
||||||
if ln == 0:
|
|
||||||
return SQLQuery("2+2=5", [])
|
|
||||||
if ln == 1:
|
|
||||||
lst = lst[0]
|
|
||||||
|
|
||||||
if isinstance(lst, iters):
|
|
||||||
return SQLQuery('(' + left +
|
|
||||||
(' OR ' + left).join([aparam() for param in lst]) + ")", lst)
|
|
||||||
else:
|
|
||||||
return SQLQuery(left + aparam(), [lst])
|
|
||||||
|
|
||||||
def sqlwhere(dictionary, grouping=' AND '):
|
|
||||||
"""
|
|
||||||
Converts a `dictionary` to an SQL WHERE clause `SQLQuery`.
|
|
||||||
|
|
||||||
>>> sqlwhere({'cust_id': 2, 'order_id':3})
|
|
||||||
<sql: 'order_id = 3 AND cust_id = 2'>
|
|
||||||
>>> sqlwhere({'cust_id': 2, 'order_id':3}, grouping=', ')
|
|
||||||
<sql: 'order_id = 3, cust_id = 2'>
|
|
||||||
"""
|
|
||||||
|
|
||||||
return SQLQuery(grouping.join([
|
|
||||||
'%s = %s' % (k, aparam()) for k in dictionary.keys()
|
|
||||||
]), dictionary.values())
|
|
||||||
|
|
||||||
def select(tables, vars=None, what='*', where=None, order=None, group=None,
|
|
||||||
limit=None, offset=None, _test=False):
|
|
||||||
"""
|
|
||||||
Selects `what` from `tables` with clauses `where`, `order`,
|
|
||||||
`group`, `limit`, and `offset`. Uses vars to interpolate.
|
|
||||||
Otherwise, each clause can be a SQLQuery.
|
|
||||||
|
|
||||||
>>> select('foo', _test=True)
|
|
||||||
<sql: 'SELECT * FROM foo'>
|
|
||||||
>>> select(['foo', 'bar'], where="foo.bar_id = bar.id", limit=5, _test=True)
|
|
||||||
<sql: 'SELECT * FROM foo, bar WHERE foo.bar_id = bar.id LIMIT 5'>
|
|
||||||
"""
|
|
||||||
if vars is None: vars = {}
|
|
||||||
qout = ""
|
|
||||||
|
|
||||||
def gen_clause(sql, val):
|
|
||||||
if isinstance(val, (int, long)):
|
|
||||||
if sql == 'WHERE':
|
|
||||||
nout = 'id = ' + sqlquote(val)
|
|
||||||
else:
|
|
||||||
nout = SQLQuery(val)
|
|
||||||
elif isinstance(val, (list, tuple)) and len(val) == 2:
|
|
||||||
nout = SQLQuery(val[0], val[1]) # backwards-compatibility
|
|
||||||
elif isinstance(val, SQLQuery):
|
|
||||||
nout = val
|
|
||||||
elif val:
|
|
||||||
nout = reparam(val, vars)
|
|
||||||
else:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
out = ""
|
|
||||||
if qout: out += " "
|
|
||||||
out += sql + " " + nout
|
|
||||||
return out
|
|
||||||
|
|
||||||
if web.ctx.get('db_name') == "firebird":
|
|
||||||
for (sql, val) in (
|
|
||||||
('FIRST', limit),
|
|
||||||
('SKIP', offset)
|
|
||||||
):
|
|
||||||
qout += gen_clause(sql, val)
|
|
||||||
if qout:
|
|
||||||
SELECT = 'SELECT ' + qout
|
|
||||||
else:
|
|
||||||
SELECT = 'SELECT'
|
|
||||||
qout = ""
|
|
||||||
sql_clauses = (
|
|
||||||
(SELECT, what),
|
|
||||||
('FROM', sqllist(tables)),
|
|
||||||
('WHERE', where),
|
|
||||||
('GROUP BY', group),
|
|
||||||
('ORDER BY', order)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
sql_clauses = (
|
|
||||||
('SELECT', what),
|
|
||||||
('FROM', sqllist(tables)),
|
|
||||||
('WHERE', where),
|
|
||||||
('GROUP BY', group),
|
|
||||||
('ORDER BY', order),
|
|
||||||
('LIMIT', limit),
|
|
||||||
('OFFSET', offset)
|
|
||||||
)
|
|
||||||
|
|
||||||
for (sql, val) in sql_clauses:
|
|
||||||
qout += gen_clause(sql, val)
|
|
||||||
|
|
||||||
if _test: return qout
|
|
||||||
return query(qout, processed=True)
|
|
||||||
|
|
||||||
def insert(tablename, seqname=None, _test=False, **values):
|
|
||||||
"""
|
|
||||||
Inserts `values` into `tablename`. Returns current sequence ID.
|
|
||||||
Set `seqname` to the ID if it's not the default, or to `False`
|
|
||||||
if there isn't one.
|
|
||||||
|
|
||||||
>>> insert('foo', joe='bob', a=2, _test=True)
|
|
||||||
<sql: "INSERT INTO foo (a, joe) VALUES (2, 'bob')">
|
|
||||||
"""
|
|
||||||
|
|
||||||
if values:
|
|
||||||
sql_query = SQLQuery("INSERT INTO %s (%s) VALUES (%s)" % (
|
|
||||||
tablename,
|
|
||||||
", ".join(values.keys()),
|
|
||||||
', '.join([aparam() for x in values])
|
|
||||||
), values.values())
|
|
||||||
else:
|
|
||||||
sql_query = SQLQuery("INSERT INTO %s DEFAULT VALUES" % tablename)
|
|
||||||
|
|
||||||
if _test: return sql_query
|
|
||||||
|
|
||||||
db_cursor = web.ctx.db_cursor()
|
|
||||||
if seqname is False:
|
|
||||||
pass
|
|
||||||
elif web.ctx.db_name == "postgres":
|
|
||||||
if seqname is None:
|
|
||||||
seqname = tablename + "_id_seq"
|
|
||||||
sql_query += "; SELECT currval('%s')" % seqname
|
|
||||||
elif web.ctx.db_name == "mysql":
|
|
||||||
web.ctx.db_execute(db_cursor, sql_query)
|
|
||||||
sql_query = SQLQuery("SELECT last_insert_id()")
|
|
||||||
elif web.ctx.db_name == "sqlite":
|
|
||||||
web.ctx.db_execute(db_cursor, sql_query)
|
|
||||||
# not really the same...
|
|
||||||
sql_query = SQLQuery("SELECT last_insert_rowid()")
|
|
||||||
|
|
||||||
web.ctx.db_execute(db_cursor, sql_query)
|
|
||||||
try:
|
|
||||||
out = db_cursor.fetchone()[0]
|
|
||||||
except Exception:
|
|
||||||
out = None
|
|
||||||
|
|
||||||
if not web.ctx.db_transaction: web.ctx.db.commit()
|
|
||||||
|
|
||||||
return out
|
|
||||||
|
|
||||||
def update(tables, where, vars=None, _test=False, **values):
|
|
||||||
"""
|
|
||||||
Update `tables` with clause `where` (interpolated using `vars`)
|
|
||||||
and setting `values`.
|
|
||||||
|
|
||||||
>>> joe = 'Joseph'
|
|
||||||
>>> update('foo', where='name = $joe', name='bob', age=5,
|
|
||||||
... vars=locals(), _test=True)
|
|
||||||
<sql: "UPDATE foo SET age = 5, name = 'bob' WHERE name = 'Joseph'">
|
|
||||||
"""
|
|
||||||
if vars is None: vars = {}
|
|
||||||
|
|
||||||
if isinstance(where, (int, long)):
|
|
||||||
where = "id = " + sqlquote(where)
|
|
||||||
elif isinstance(where, (list, tuple)) and len(where) == 2:
|
|
||||||
where = SQLQuery(where[0], where[1])
|
|
||||||
elif isinstance(where, SQLQuery):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
where = reparam(where, vars)
|
|
||||||
|
|
||||||
query = (
|
|
||||||
"UPDATE " + sqllist(tables) +
|
|
||||||
" SET " + sqlwhere(values, ', ') +
|
|
||||||
" WHERE " + where)
|
|
||||||
|
|
||||||
if _test: return query
|
|
||||||
|
|
||||||
db_cursor = web.ctx.db_cursor()
|
|
||||||
web.ctx.db_execute(db_cursor, query)
|
|
||||||
|
|
||||||
if not web.ctx.db_transaction: web.ctx.db.commit()
|
|
||||||
return db_cursor.rowcount
|
|
||||||
|
|
||||||
def delete(table, where=None, using=None, vars=None, _test=False):
|
|
||||||
"""
|
|
||||||
Deletes from `table` with clauses `where` and `using`.
|
|
||||||
|
|
||||||
>>> name = 'Joe'
|
|
||||||
>>> delete('foo', where='name = $name', vars=locals(), _test=True)
|
|
||||||
<sql: "DELETE FROM foo WHERE name = 'Joe'">
|
|
||||||
"""
|
|
||||||
if vars is None: vars = {}
|
|
||||||
|
|
||||||
if isinstance(where, (int, long)):
|
|
||||||
where = "id = " + sqlquote(where)
|
|
||||||
elif isinstance(where, (list, tuple)) and len(where) == 2:
|
|
||||||
where = SQLQuery(where[0], where[1])
|
|
||||||
elif isinstance(where, SQLQuery):
|
|
||||||
pass
|
|
||||||
elif where is None:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
where = reparam(where, vars)
|
|
||||||
|
|
||||||
q = 'DELETE FROM ' + table
|
|
||||||
if where:
|
|
||||||
q += ' WHERE ' + where
|
|
||||||
if using and web.ctx.get('db_name') != "firebird":
|
|
||||||
q += ' USING ' + sqllist(using)
|
|
||||||
|
|
||||||
if _test: return q
|
|
||||||
|
|
||||||
db_cursor = web.ctx.db_cursor()
|
|
||||||
web.ctx.db_execute(db_cursor, q)
|
|
||||||
|
|
||||||
if not web.ctx.db_transaction: web.ctx.db.commit()
|
|
||||||
return db_cursor.rowcount
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
doctest.testmod()
|
|
||||||
@ -1,316 +0,0 @@
|
|||||||
"""
|
|
||||||
pretty debug errors
|
|
||||||
(part of web.py)
|
|
||||||
|
|
||||||
adapted from Django <djangoproject.com>
|
|
||||||
Copyright (c) 2005, the Lawrence Journal-World
|
|
||||||
Used under the modified BSD license:
|
|
||||||
http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
|
|
||||||
"""
|
|
||||||
|
|
||||||
__all__ = ["debugerror", "djangoerror"]
|
|
||||||
|
|
||||||
import sys, urlparse, pprint
|
|
||||||
from net import websafe
|
|
||||||
from template import Template
|
|
||||||
import webapi as web
|
|
||||||
|
|
||||||
import os, os.path
|
|
||||||
whereami = os.path.join(os.getcwd(), __file__)
|
|
||||||
whereami = os.path.sep.join(whereami.split(os.path.sep)[:-1])
|
|
||||||
djangoerror_t = """\
|
|
||||||
$def with (exception_type, exception_value, frames)
|
|
||||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
|
||||||
<meta name="robots" content="NONE,NOARCHIVE" />
|
|
||||||
<title>$exception_type at $ctx.path</title>
|
|
||||||
<style type="text/css">
|
|
||||||
html * { padding:0; margin:0; }
|
|
||||||
body * { padding:10px 20px; }
|
|
||||||
body * * { padding:0; }
|
|
||||||
body { font:small sans-serif; }
|
|
||||||
body>div { border-bottom:1px solid #ddd; }
|
|
||||||
h1 { font-weight:normal; }
|
|
||||||
h2 { margin-bottom:.8em; }
|
|
||||||
h2 span { font-size:80%; color:#666; font-weight:normal; }
|
|
||||||
h3 { margin:1em 0 .5em 0; }
|
|
||||||
h4 { margin:0 0 .5em 0; font-weight: normal; }
|
|
||||||
table {
|
|
||||||
border:1px solid #ccc; border-collapse: collapse; background:white; }
|
|
||||||
tbody td, tbody th { vertical-align:top; padding:2px 3px; }
|
|
||||||
thead th {
|
|
||||||
padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
|
|
||||||
font-weight:normal; font-size:11px; border:1px solid #ddd; }
|
|
||||||
tbody th { text-align:right; color:#666; padding-right:.5em; }
|
|
||||||
table.vars { margin:5px 0 2px 40px; }
|
|
||||||
table.vars td, table.req td { font-family:monospace; }
|
|
||||||
table td.code { width:100%;}
|
|
||||||
table td.code div { overflow:hidden; }
|
|
||||||
table.source th { color:#666; }
|
|
||||||
table.source td {
|
|
||||||
font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
|
|
||||||
ul.traceback { list-style-type:none; }
|
|
||||||
ul.traceback li.frame { margin-bottom:1em; }
|
|
||||||
div.context { margin: 10px 0; }
|
|
||||||
div.context ol {
|
|
||||||
padding-left:30px; margin:0 10px; list-style-position: inside; }
|
|
||||||
div.context ol li {
|
|
||||||
font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
|
|
||||||
div.context ol.context-line li { color:black; background-color:#ccc; }
|
|
||||||
div.context ol.context-line li span { float: right; }
|
|
||||||
div.commands { margin-left: 40px; }
|
|
||||||
div.commands a { color:black; text-decoration:none; }
|
|
||||||
#summary { background: #ffc; }
|
|
||||||
#summary h2 { font-weight: normal; color: #666; }
|
|
||||||
#explanation { background:#eee; }
|
|
||||||
#template, #template-not-exist { background:#f6f6f6; }
|
|
||||||
#template-not-exist ul { margin: 0 0 0 20px; }
|
|
||||||
#traceback { background:#eee; }
|
|
||||||
#requestinfo { background:#f6f6f6; padding-left:120px; }
|
|
||||||
#summary table { border:none; background:transparent; }
|
|
||||||
#requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
|
|
||||||
#requestinfo h3 { margin-bottom:-1em; }
|
|
||||||
.error { background: #ffc; }
|
|
||||||
.specific { color:#cc3300; font-weight:bold; }
|
|
||||||
</style>
|
|
||||||
<script type="text/javascript">
|
|
||||||
//<!--
|
|
||||||
function getElementsByClassName(oElm, strTagName, strClassName){
|
|
||||||
// Written by Jonathan Snook, http://www.snook.ca/jon;
|
|
||||||
// Add-ons by Robert Nyman, http://www.robertnyman.com
|
|
||||||
var arrElements = (strTagName == "*" && document.all)? document.all :
|
|
||||||
oElm.getElementsByTagName(strTagName);
|
|
||||||
var arrReturnElements = new Array();
|
|
||||||
strClassName = strClassName.replace(/\-/g, "\\-");
|
|
||||||
var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
|
|
||||||
var oElement;
|
|
||||||
for(var i=0; i<arrElements.length; i++){
|
|
||||||
oElement = arrElements[i];
|
|
||||||
if(oRegExp.test(oElement.className)){
|
|
||||||
arrReturnElements.push(oElement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (arrReturnElements)
|
|
||||||
}
|
|
||||||
function hideAll(elems) {
|
|
||||||
for (var e = 0; e < elems.length; e++) {
|
|
||||||
elems[e].style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
window.onload = function() {
|
|
||||||
hideAll(getElementsByClassName(document, 'table', 'vars'));
|
|
||||||
hideAll(getElementsByClassName(document, 'ol', 'pre-context'));
|
|
||||||
hideAll(getElementsByClassName(document, 'ol', 'post-context'));
|
|
||||||
}
|
|
||||||
function toggle() {
|
|
||||||
for (var i = 0; i < arguments.length; i++) {
|
|
||||||
var e = document.getElementById(arguments[i]);
|
|
||||||
if (e) {
|
|
||||||
e.style.display = e.style.display == 'none' ? 'block' : 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
function varToggle(link, id) {
|
|
||||||
toggle('v' + id);
|
|
||||||
var s = link.getElementsByTagName('span')[0];
|
|
||||||
var uarr = String.fromCharCode(0x25b6);
|
|
||||||
var darr = String.fromCharCode(0x25bc);
|
|
||||||
s.innerHTML = s.innerHTML == uarr ? darr : uarr;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
//-->
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div id="summary">
|
|
||||||
<h1>$exception_type at $ctx.path</h1>
|
|
||||||
<h2>$exception_value</h2>
|
|
||||||
<table><tr>
|
|
||||||
<th>Python</th>
|
|
||||||
<td>$frames[0].filename in $frames[0].function, line $frames[0].lineno</td>
|
|
||||||
</tr><tr>
|
|
||||||
<th>Web</th>
|
|
||||||
<td>$ctx.method $ctx.home$ctx.path</td>
|
|
||||||
</tr></table>
|
|
||||||
</div>
|
|
||||||
<div id="traceback">
|
|
||||||
<h2>Traceback <span>(innermost first)</span></h2>
|
|
||||||
<ul class="traceback">
|
|
||||||
$for frame in frames:
|
|
||||||
<li class="frame">
|
|
||||||
<code>$frame.filename</code> in <code>$frame.function</code>
|
|
||||||
$if frame.context_line:
|
|
||||||
<div class="context" id="c$frame.id">
|
|
||||||
$if frame.pre_context:
|
|
||||||
<ol start="$frame.pre_context_lineno" class="pre-context" id="pre$frame.id">
|
|
||||||
$for line in frame.pre_context:
|
|
||||||
<li onclick="toggle('pre$frame.id', 'post$frame.id')">$line</li>
|
|
||||||
</ol>
|
|
||||||
<ol start="$frame.lineno" class="context-line"><li onclick="toggle('pre$frame.id', 'post$frame.id')">$frame.context_line <span>...</span></li></ol>
|
|
||||||
$if frame.post_context:
|
|
||||||
<ol start='${frame.lineno + 1}' class="post-context" id="post$frame.id">
|
|
||||||
$for line in frame.post_context:
|
|
||||||
<li onclick="toggle('pre$frame.id', 'post$frame.id')">$line</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
$if frame.vars:
|
|
||||||
<div class="commands">
|
|
||||||
<a href='#' onclick="return varToggle(this, '$frame.id')"><span>▶</span> Local vars</a>
|
|
||||||
$# $inspect.formatargvalues(*inspect.getargvalues(frame['tb'].tb_frame))
|
|
||||||
</div>
|
|
||||||
$:dicttable(frame.vars, kls='vars', id=('v' + str(frame.id)))
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="requestinfo">
|
|
||||||
$if ctx.output or ctx.headers:
|
|
||||||
<h2>Response so far</h2>
|
|
||||||
<h3>HEADERS</h3>
|
|
||||||
<p class="req"><code>
|
|
||||||
$for kv in ctx.headers:
|
|
||||||
$kv[0]: $kv[1]<br />
|
|
||||||
$else:
|
|
||||||
[no headers]
|
|
||||||
</code></p>
|
|
||||||
|
|
||||||
<h3>BODY</h3>
|
|
||||||
<p class="req" style="padding-bottom: 2em"><code>
|
|
||||||
$ctx.output
|
|
||||||
</code></p>
|
|
||||||
|
|
||||||
<h2>Request information</h2>
|
|
||||||
|
|
||||||
<h3>INPUT</h3>
|
|
||||||
$:dicttable(web.input())
|
|
||||||
|
|
||||||
<h3 id="cookie-info">COOKIES</h3>
|
|
||||||
$:dicttable(web.cookies())
|
|
||||||
|
|
||||||
<h3 id="meta-info">META</h3>
|
|
||||||
$ newctx = []
|
|
||||||
$# ) and (k not in ['env', 'output', 'headers', 'environ', 'status', 'db_execute']):
|
|
||||||
$for k, v in ctx.iteritems():
|
|
||||||
$if not k.startswith('_') and (k in x):
|
|
||||||
$newctx.append(kv)
|
|
||||||
$:dicttable(dict(newctx))
|
|
||||||
|
|
||||||
<h3 id="meta-info">ENVIRONMENT</h3>
|
|
||||||
$:dicttable(ctx.env)
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="explanation">
|
|
||||||
<p>
|
|
||||||
You're seeing this error because you have <code>web.internalerror</code>
|
|
||||||
set to <code>web.debugerror</code>. Change that if you want a different one.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
dicttable_t = r"""$def with (d, kls='req', id=None)
|
|
||||||
$if d:
|
|
||||||
<table class="$kls"\
|
|
||||||
$if id: id="$id"\
|
|
||||||
><thead><tr><th>Variable</th><th>Value</th></tr></thead>
|
|
||||||
<tbody>
|
|
||||||
$ temp = d.items()
|
|
||||||
$temp.sort()
|
|
||||||
$for kv in temp:
|
|
||||||
<tr><td>$kv[0]</td><td class="code"><div>$prettify(kv[1])</div></td></tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
$else:
|
|
||||||
<p>No data.</p>
|
|
||||||
"""
|
|
||||||
|
|
||||||
dicttable_r = Template(dicttable_t, filter=websafe)
|
|
||||||
djangoerror_r = Template(djangoerror_t, filter=websafe)
|
|
||||||
|
|
||||||
def djangoerror():
|
|
||||||
def _get_lines_from_file(filename, lineno, context_lines):
|
|
||||||
"""
|
|
||||||
Returns context_lines before and after lineno from file.
|
|
||||||
Returns (pre_context_lineno, pre_context, context_line, post_context).
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
source = open(filename).readlines()
|
|
||||||
lower_bound = max(0, lineno - context_lines)
|
|
||||||
upper_bound = lineno + context_lines
|
|
||||||
|
|
||||||
pre_context = \
|
|
||||||
[line.strip('\n') for line in source[lower_bound:lineno]]
|
|
||||||
context_line = source[lineno].strip('\n')
|
|
||||||
post_context = \
|
|
||||||
[line.strip('\n') for line in source[lineno + 1:upper_bound]]
|
|
||||||
|
|
||||||
return lower_bound, pre_context, context_line, post_context
|
|
||||||
except (OSError, IOError):
|
|
||||||
return None, [], None, []
|
|
||||||
|
|
||||||
exception_type, exception_value, tback = sys.exc_info()
|
|
||||||
frames = []
|
|
||||||
while tback is not None:
|
|
||||||
filename = tback.tb_frame.f_code.co_filename
|
|
||||||
function = tback.tb_frame.f_code.co_name
|
|
||||||
lineno = tback.tb_lineno - 1
|
|
||||||
pre_context_lineno, pre_context, context_line, post_context = \
|
|
||||||
_get_lines_from_file(filename, lineno, 7)
|
|
||||||
frames.append(web.storage({
|
|
||||||
'tback': tback,
|
|
||||||
'filename': filename,
|
|
||||||
'function': function,
|
|
||||||
'lineno': lineno,
|
|
||||||
'vars': tback.tb_frame.f_locals,
|
|
||||||
'id': id(tback),
|
|
||||||
'pre_context': pre_context,
|
|
||||||
'context_line': context_line,
|
|
||||||
'post_context': post_context,
|
|
||||||
'pre_context_lineno': pre_context_lineno,
|
|
||||||
}))
|
|
||||||
tback = tback.tb_next
|
|
||||||
frames.reverse()
|
|
||||||
urljoin = urlparse.urljoin
|
|
||||||
def prettify(x):
|
|
||||||
try:
|
|
||||||
out = pprint.pformat(x)
|
|
||||||
except Exception, e:
|
|
||||||
out = '[could not display: <' + e.__class__.__name__ + \
|
|
||||||
': '+str(e)+'>]'
|
|
||||||
return out
|
|
||||||
dt = dicttable_r
|
|
||||||
dt.globals = {'prettify': prettify}
|
|
||||||
t = djangoerror_r
|
|
||||||
t.globals = {'ctx': web.ctx, 'web':web, 'dicttable':dt, 'dict':dict, 'str':str}
|
|
||||||
return t(exception_type, exception_value, frames)
|
|
||||||
|
|
||||||
def debugerror():
|
|
||||||
"""
|
|
||||||
A replacement for `internalerror` that presents a nice page with lots
|
|
||||||
of debug information for the programmer.
|
|
||||||
|
|
||||||
(Based on the beautiful 500 page from [Django](http://djangoproject.com/),
|
|
||||||
designed by [Wilson Miner](http://wilsonminer.com/).)
|
|
||||||
"""
|
|
||||||
|
|
||||||
web.ctx.headers = [('Content-Type', 'text/html')]
|
|
||||||
web.ctx.output = djangoerror()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
urls = (
|
|
||||||
'/', 'index'
|
|
||||||
)
|
|
||||||
|
|
||||||
class index:
|
|
||||||
def GET(self):
|
|
||||||
thisdoesnotexist
|
|
||||||
|
|
||||||
web.internalerror = web.debugerror
|
|
||||||
web.run(urls)
|
|
||||||
@ -1,215 +0,0 @@
|
|||||||
"""
|
|
||||||
HTML forms
|
|
||||||
(part of web.py)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import copy, re
|
|
||||||
import webapi as web
|
|
||||||
import utils, net
|
|
||||||
|
|
||||||
def attrget(obj, attr, value=None):
|
|
||||||
if hasattr(obj, 'has_key') and obj.has_key(attr): return obj[attr]
|
|
||||||
if hasattr(obj, attr): return getattr(obj, attr)
|
|
||||||
return value
|
|
||||||
|
|
||||||
class Form:
|
|
||||||
def __init__(self, *inputs, **kw):
|
|
||||||
self.inputs = inputs
|
|
||||||
self.valid = True
|
|
||||||
self.note = None
|
|
||||||
self.validators = kw.pop('validators', [])
|
|
||||||
|
|
||||||
def __call__(self, x=None):
|
|
||||||
o = copy.deepcopy(self)
|
|
||||||
if x: o.validates(x)
|
|
||||||
return o
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
out = ''
|
|
||||||
out += self.rendernote(self.note)
|
|
||||||
out += '<table>\n'
|
|
||||||
for i in self.inputs:
|
|
||||||
out += ' <tr><th><label for="%s">%s</label></th>' % (i.id, i.description)
|
|
||||||
out += "<td>"+i.pre+i.render()+i.post+"</td>"
|
|
||||||
out += '<td id="note_%s">%s</td></tr>\n' % (i.id, self.rendernote(i.note))
|
|
||||||
out += "</table>"
|
|
||||||
return out
|
|
||||||
|
|
||||||
def rendernote(self, note):
|
|
||||||
if note: return '<strong class="wrong">%s</strong>' % note
|
|
||||||
else: return ""
|
|
||||||
|
|
||||||
def validates(self, source=None, _validate=True, **kw):
|
|
||||||
source = source or kw or web.input()
|
|
||||||
out = True
|
|
||||||
for i in self.inputs:
|
|
||||||
v = attrget(source, i.name)
|
|
||||||
if _validate:
|
|
||||||
out = i.validate(v) and out
|
|
||||||
else:
|
|
||||||
i.value = v
|
|
||||||
if _validate:
|
|
||||||
out = out and self._validate(source)
|
|
||||||
self.valid = out
|
|
||||||
return out
|
|
||||||
|
|
||||||
def _validate(self, value):
|
|
||||||
self.value = value
|
|
||||||
for v in self.validators:
|
|
||||||
if not v.valid(value):
|
|
||||||
self.note = v.msg
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def fill(self, source=None, **kw):
|
|
||||||
return self.validates(source, _validate=False, **kw)
|
|
||||||
|
|
||||||
def __getitem__(self, i):
|
|
||||||
for x in self.inputs:
|
|
||||||
if x.name == i: return x
|
|
||||||
raise KeyError, i
|
|
||||||
|
|
||||||
def _get_d(self): #@@ should really be form.attr, no?
|
|
||||||
return utils.storage([(i.name, i.value) for i in self.inputs])
|
|
||||||
d = property(_get_d)
|
|
||||||
|
|
||||||
class Input(object):
|
|
||||||
def __init__(self, name, *validators, **attrs):
|
|
||||||
self.description = attrs.pop('description', name)
|
|
||||||
self.value = attrs.pop('value', None)
|
|
||||||
self.pre = attrs.pop('pre', "")
|
|
||||||
self.post = attrs.pop('post', "")
|
|
||||||
self.id = attrs.setdefault('id', name)
|
|
||||||
if 'class_' in attrs:
|
|
||||||
attrs['class'] = attrs['class_']
|
|
||||||
del attrs['class_']
|
|
||||||
self.name, self.validators, self.attrs, self.note = name, validators, attrs, None
|
|
||||||
|
|
||||||
def validate(self, value):
|
|
||||||
self.value = value
|
|
||||||
for v in self.validators:
|
|
||||||
if not v.valid(value):
|
|
||||||
self.note = v.msg
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def render(self): raise NotImplementedError
|
|
||||||
|
|
||||||
def addatts(self):
|
|
||||||
str = ""
|
|
||||||
for (n, v) in self.attrs.items():
|
|
||||||
str += ' %s="%s"' % (n, net.websafe(v))
|
|
||||||
return str
|
|
||||||
|
|
||||||
#@@ quoting
|
|
||||||
|
|
||||||
class Textbox(Input):
|
|
||||||
def render(self):
|
|
||||||
x = '<input type="text" name="%s"' % net.websafe(self.name)
|
|
||||||
if self.value: x += ' value="%s"' % net.websafe(self.value)
|
|
||||||
x += self.addatts()
|
|
||||||
x += ' />'
|
|
||||||
return x
|
|
||||||
|
|
||||||
class Password(Input):
|
|
||||||
def render(self):
|
|
||||||
x = '<input type="password" name="%s"' % net.websafe(self.name)
|
|
||||||
if self.value: x += ' value="%s"' % net.websafe(self.value)
|
|
||||||
x += self.addatts()
|
|
||||||
x += ' />'
|
|
||||||
return x
|
|
||||||
|
|
||||||
class Textarea(Input):
|
|
||||||
def render(self):
|
|
||||||
x = '<textarea name="%s"' % net.websafe(self.name)
|
|
||||||
x += self.addatts()
|
|
||||||
x += '>'
|
|
||||||
if self.value is not None: x += net.websafe(self.value)
|
|
||||||
x += '</textarea>'
|
|
||||||
return x
|
|
||||||
|
|
||||||
class Dropdown(Input):
|
|
||||||
def __init__(self, name, args, *validators, **attrs):
|
|
||||||
self.args = args
|
|
||||||
super(Dropdown, self).__init__(name, *validators, **attrs)
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
x = '<select name="%s"%s>\n' % (net.websafe(self.name), self.addatts())
|
|
||||||
for arg in self.args:
|
|
||||||
if type(arg) == tuple:
|
|
||||||
value, desc= arg
|
|
||||||
else:
|
|
||||||
value, desc = arg, arg
|
|
||||||
|
|
||||||
if self.value == value: select_p = ' selected="selected"'
|
|
||||||
else: select_p = ''
|
|
||||||
x += ' <option %s value="%s">%s</option>\n' % (select_p, net.websafe(value), net.websafe(desc))
|
|
||||||
x += '</select>\n'
|
|
||||||
return x
|
|
||||||
|
|
||||||
class Radio(Input):
|
|
||||||
def __init__(self, name, args, *validators, **attrs):
|
|
||||||
self.args = args
|
|
||||||
super(Radio, self).__init__(name, *validators, **attrs)
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
x = '<span>'
|
|
||||||
for arg in self.args:
|
|
||||||
if self.value == arg: select_p = ' checked="checked"'
|
|
||||||
else: select_p = ''
|
|
||||||
x += '<input type="radio" name="%s" value="%s"%s%s /> %s ' % (net.websafe(self.name), net.websafe(arg), select_p, self.addatts(), net.websafe(arg))
|
|
||||||
return x+'</span>'
|
|
||||||
|
|
||||||
class Checkbox(Input):
|
|
||||||
def render(self):
|
|
||||||
x = '<input name="%s" type="checkbox"' % net.websafe(self.name)
|
|
||||||
if self.value: x += ' checked="checked"'
|
|
||||||
x += self.addatts()
|
|
||||||
x += ' />'
|
|
||||||
return x
|
|
||||||
|
|
||||||
class Button(Input):
|
|
||||||
def __init__(self, name, *validators, **attrs):
|
|
||||||
super(Button, self).__init__(name, *validators, **attrs)
|
|
||||||
self.description = ""
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
safename = net.websafe(self.name)
|
|
||||||
x = '<button name="%s"%s>%s</button>' % (safename, self.addatts(), safename)
|
|
||||||
return x
|
|
||||||
|
|
||||||
class Hidden(Input):
|
|
||||||
def __init__(self, name, *validators, **attrs):
|
|
||||||
super(Hidden, self).__init__(name, *validators, **attrs)
|
|
||||||
# it doesnt make sence for a hidden field to have description
|
|
||||||
self.description = ""
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
x = '<input type="hidden" name="%s"' % net.websafe(self.name)
|
|
||||||
if self.value: x += ' value="%s"' % net.websafe(self.value)
|
|
||||||
x += ' />'
|
|
||||||
return x
|
|
||||||
|
|
||||||
class File(Input):
|
|
||||||
def render(self):
|
|
||||||
x = '<input type="file" name="%s"' % net.websafe(self.name)
|
|
||||||
x += self.addatts()
|
|
||||||
x += ' />'
|
|
||||||
return x
|
|
||||||
|
|
||||||
class Validator:
|
|
||||||
def __deepcopy__(self, memo): return copy.copy(self)
|
|
||||||
def __init__(self, msg, test, jstest=None): utils.autoassign(self, locals())
|
|
||||||
def valid(self, value):
|
|
||||||
try: return self.test(value)
|
|
||||||
except: return False
|
|
||||||
|
|
||||||
notnull = Validator("Required", bool)
|
|
||||||
|
|
||||||
class regexp(Validator):
|
|
||||||
def __init__(self, rexp, msg):
|
|
||||||
self.rexp = re.compile(rexp)
|
|
||||||
self.msg = msg
|
|
||||||
|
|
||||||
def valid(self, value):
|
|
||||||
return bool(self.rexp.match(value))
|
|
||||||
@ -1,271 +0,0 @@
|
|||||||
"""
|
|
||||||
HTTP Utilities
|
|
||||||
(from web.py)
|
|
||||||
"""
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"expires", "lastmodified",
|
|
||||||
"prefixurl", "modified",
|
|
||||||
"redirect", "found", "seeother", "tempredirect",
|
|
||||||
"write",
|
|
||||||
"changequery", "url",
|
|
||||||
"background", "backgrounder",
|
|
||||||
"Reloader", "reloader", "profiler",
|
|
||||||
]
|
|
||||||
|
|
||||||
import sys, os, threading, urllib, urlparse
|
|
||||||
try: import datetime
|
|
||||||
except ImportError: pass
|
|
||||||
import net, utils, webapi as web
|
|
||||||
|
|
||||||
def prefixurl(base=''):
|
|
||||||
"""
|
|
||||||
Sorry, this function is really difficult to explain.
|
|
||||||
Maybe some other time.
|
|
||||||
"""
|
|
||||||
url = web.ctx.path.lstrip('/')
|
|
||||||
for i in xrange(url.count('/')):
|
|
||||||
base += '../'
|
|
||||||
if not base:
|
|
||||||
base = './'
|
|
||||||
return base
|
|
||||||
|
|
||||||
def expires(delta):
|
|
||||||
"""
|
|
||||||
Outputs an `Expires` header for `delta` from now.
|
|
||||||
`delta` is a `timedelta` object or a number of seconds.
|
|
||||||
"""
|
|
||||||
if isinstance(delta, (int, long)):
|
|
||||||
delta = datetime.timedelta(seconds=delta)
|
|
||||||
date_obj = datetime.datetime.utcnow() + delta
|
|
||||||
web.header('Expires', net.httpdate(date_obj))
|
|
||||||
|
|
||||||
def lastmodified(date_obj):
|
|
||||||
"""Outputs a `Last-Modified` header for `datetime`."""
|
|
||||||
web.header('Last-Modified', net.httpdate(date_obj))
|
|
||||||
|
|
||||||
def modified(date=None, etag=None):
|
|
||||||
n = web.ctx.env.get('HTTP_IF_NONE_MATCH')
|
|
||||||
m = net.parsehttpdate(web.ctx.env.get('HTTP_IF_MODIFIED_SINCE', '').split(';')[0])
|
|
||||||
validate = False
|
|
||||||
if etag:
|
|
||||||
raise NotImplementedError, "no etag support yet"
|
|
||||||
# should really be a warning
|
|
||||||
if date and m:
|
|
||||||
# we subtract a second because
|
|
||||||
# HTTP dates don't have sub-second precision
|
|
||||||
if date-datetime.timedelta(seconds=1) <= m:
|
|
||||||
validate = True
|
|
||||||
|
|
||||||
if validate: web.ctx.status = '304 Not Modified'
|
|
||||||
return not validate
|
|
||||||
|
|
||||||
"""
|
|
||||||
By default, these all return simple error messages that send very short messages
|
|
||||||
(like "bad request") to the user. They can and should be overridden
|
|
||||||
to return nicer ones.
|
|
||||||
"""
|
|
||||||
def redirect(url, status='301 Moved Permanently'):
|
|
||||||
"""
|
|
||||||
Returns a `status` redirect to the new URL.
|
|
||||||
`url` is joined with the base URL so that things like
|
|
||||||
`redirect("about") will work properly.
|
|
||||||
"""
|
|
||||||
newloc = urlparse.urljoin(web.ctx.path, url)
|
|
||||||
|
|
||||||
# if newloc is relative then make it absolute
|
|
||||||
#mvoncken:Disabled because we don't want to redirect to localhost!
|
|
||||||
#mvoncken:back to http-spec, maybe better.
|
|
||||||
if newloc.startswith('/'):
|
|
||||||
newloc = web.ctx.home + newloc
|
|
||||||
|
|
||||||
web.ctx.status = status
|
|
||||||
web.ctx.output = ''
|
|
||||||
web.header('Content-Type', 'text/html')
|
|
||||||
web.header('Location', newloc)
|
|
||||||
# seems to add a three-second delay for some reason:
|
|
||||||
# web.output('<a href="'+ newloc + '">moved permanently</a>')
|
|
||||||
|
|
||||||
def found(url):
|
|
||||||
"""A `302 Found` redirect."""
|
|
||||||
return redirect(url, '302 Found')
|
|
||||||
|
|
||||||
def seeother(url):
|
|
||||||
"""A `303 See Other` redirect."""
|
|
||||||
return redirect(url, '303 See Other')
|
|
||||||
|
|
||||||
def tempredirect(url):
|
|
||||||
"""A `307 Temporary Redirect` redirect."""
|
|
||||||
return redirect(url, '307 Temporary Redirect')
|
|
||||||
|
|
||||||
def write(cgi_response):
|
|
||||||
"""
|
|
||||||
Converts a standard CGI-style string response into `header` and
|
|
||||||
`output` calls.
|
|
||||||
"""
|
|
||||||
cgi_response = str(cgi_response)
|
|
||||||
cgi_response.replace('\r\n', '\n')
|
|
||||||
head, body = cgi_response.split('\n\n', 1)
|
|
||||||
lines = head.split('\n')
|
|
||||||
|
|
||||||
for line in lines:
|
|
||||||
if line.isspace():
|
|
||||||
continue
|
|
||||||
hdr, value = line.split(":", 1)
|
|
||||||
value = value.strip()
|
|
||||||
if hdr.lower() == "status":
|
|
||||||
web.ctx.status = value
|
|
||||||
else:
|
|
||||||
web.header(hdr, value)
|
|
||||||
|
|
||||||
web.output(body)
|
|
||||||
|
|
||||||
def urlencode(query):
|
|
||||||
"""
|
|
||||||
Same as urllib.urlencode, but supports unicode strings.
|
|
||||||
|
|
||||||
>>> urlencode({'text':'foo bar'})
|
|
||||||
'text=foo+bar'
|
|
||||||
"""
|
|
||||||
query = dict([(k, utils.utf8(v)) for k, v in query.items()])
|
|
||||||
return urllib.urlencode(query)
|
|
||||||
|
|
||||||
def changequery(query=None, **kw):
|
|
||||||
"""
|
|
||||||
Imagine you're at `/foo?a=1&b=2`. Then `changequery(a=3)` will return
|
|
||||||
`/foo?a=3&b=2` -- the same URL but with the arguments you requested
|
|
||||||
changed.
|
|
||||||
"""
|
|
||||||
if query is None:
|
|
||||||
query = web.input(_method='get')
|
|
||||||
for k, v in kw.iteritems():
|
|
||||||
if v is None:
|
|
||||||
query.pop(k, None)
|
|
||||||
else:
|
|
||||||
query[k] = v
|
|
||||||
out = web.ctx.path
|
|
||||||
if query:
|
|
||||||
out += '?' + urlencode(query)
|
|
||||||
return out
|
|
||||||
|
|
||||||
def url(path=None, **kw):
|
|
||||||
"""
|
|
||||||
Makes url by concatinating web.ctx.homepath and path and the
|
|
||||||
query string created using the arguments.
|
|
||||||
"""
|
|
||||||
if path is None:
|
|
||||||
path = web.ctx.path
|
|
||||||
if path.startswith("/"):
|
|
||||||
out = web.ctx.homepath + path
|
|
||||||
else:
|
|
||||||
out = path
|
|
||||||
|
|
||||||
if kw:
|
|
||||||
out += '?' + urlencode(kw)
|
|
||||||
|
|
||||||
return out
|
|
||||||
|
|
||||||
def background(func):
|
|
||||||
"""A function decorator to run a long-running function as a background thread."""
|
|
||||||
def internal(*a, **kw):
|
|
||||||
web.data() # cache it
|
|
||||||
|
|
||||||
tmpctx = web._context[threading.currentThread()]
|
|
||||||
web._context[threading.currentThread()] = utils.storage(web.ctx.copy())
|
|
||||||
|
|
||||||
def newfunc():
|
|
||||||
web._context[threading.currentThread()] = tmpctx
|
|
||||||
func(*a, **kw)
|
|
||||||
myctx = web._context[threading.currentThread()]
|
|
||||||
for k in myctx.keys():
|
|
||||||
if k not in ['status', 'headers', 'output']:
|
|
||||||
try: del myctx[k]
|
|
||||||
except KeyError: pass
|
|
||||||
|
|
||||||
t = threading.Thread(target=newfunc)
|
|
||||||
background.threaddb[id(t)] = t
|
|
||||||
t.start()
|
|
||||||
web.ctx.headers = []
|
|
||||||
return seeother(changequery(_t=id(t)))
|
|
||||||
return internal
|
|
||||||
background.threaddb = {}
|
|
||||||
|
|
||||||
def backgrounder(func):
|
|
||||||
def internal(*a, **kw):
|
|
||||||
i = web.input(_method='get')
|
|
||||||
if '_t' in i:
|
|
||||||
try:
|
|
||||||
t = background.threaddb[int(i._t)]
|
|
||||||
except KeyError:
|
|
||||||
return web.notfound()
|
|
||||||
web._context[threading.currentThread()] = web._context[t]
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
return func(*a, **kw)
|
|
||||||
return internal
|
|
||||||
|
|
||||||
class Reloader:
|
|
||||||
"""
|
|
||||||
Before every request, checks to see if any loaded modules have changed on
|
|
||||||
disk and, if so, reloads them.
|
|
||||||
"""
|
|
||||||
def __init__(self, func):
|
|
||||||
self.func = func
|
|
||||||
self.mtimes = {}
|
|
||||||
# cheetah:
|
|
||||||
# b = _compiletemplate.bases
|
|
||||||
# _compiletemplate = globals()['__compiletemplate']
|
|
||||||
# _compiletemplate.bases = b
|
|
||||||
|
|
||||||
web.loadhooks['reloader'] = self.check
|
|
||||||
# todo:
|
|
||||||
# - replace relrcheck with a loadhook
|
|
||||||
#if reloader in middleware:
|
|
||||||
# relr = reloader(None)
|
|
||||||
# relrcheck = relr.check
|
|
||||||
# middleware.remove(reloader)
|
|
||||||
#else:
|
|
||||||
# relr = None
|
|
||||||
# relrcheck = lambda: None
|
|
||||||
# if relr:
|
|
||||||
# relr.func = wsgifunc
|
|
||||||
# return wsgifunc
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
def check(self):
|
|
||||||
for mod in sys.modules.values():
|
|
||||||
try:
|
|
||||||
mtime = os.stat(mod.__file__).st_mtime
|
|
||||||
except (AttributeError, OSError, IOError):
|
|
||||||
continue
|
|
||||||
if mod.__file__.endswith('.pyc') and \
|
|
||||||
os.path.exists(mod.__file__[:-1]):
|
|
||||||
mtime = max(os.stat(mod.__file__[:-1]).st_mtime, mtime)
|
|
||||||
if mod not in self.mtimes:
|
|
||||||
self.mtimes[mod] = mtime
|
|
||||||
elif self.mtimes[mod] < mtime:
|
|
||||||
try:
|
|
||||||
reload(mod)
|
|
||||||
self.mtimes[mod] = mtime
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
return True
|
|
||||||
|
|
||||||
def __call__(self, e, o):
|
|
||||||
self.check()
|
|
||||||
return self.func(e, o)
|
|
||||||
|
|
||||||
reloader = Reloader
|
|
||||||
|
|
||||||
def profiler(app):
|
|
||||||
"""Outputs basic profiling information at the bottom of each response."""
|
|
||||||
from utils import profile
|
|
||||||
def profile_internal(e, o):
|
|
||||||
out, result = profile(app)(e, o)
|
|
||||||
return out + ['<pre>' + net.websafe(result) + '</pre>']
|
|
||||||
return profile_internal
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
doctest.testmod()
|
|
||||||
@ -1,227 +0,0 @@
|
|||||||
__all__ = ["runsimple"]
|
|
||||||
|
|
||||||
import sys, os
|
|
||||||
import webapi as web
|
|
||||||
import net
|
|
||||||
|
|
||||||
def runbasic(func, server_address=("0.0.0.0", 8080)):
|
|
||||||
"""
|
|
||||||
Runs a simple HTTP server hosting WSGI app `func`. The directory `static/`
|
|
||||||
is hosted statically.
|
|
||||||
|
|
||||||
Based on [WsgiServer][ws] from [Colin Stewart][cs].
|
|
||||||
|
|
||||||
[ws]: http://www.owlfish.com/software/wsgiutils/documentation/wsgi-server-api.html
|
|
||||||
[cs]: http://www.owlfish.com/
|
|
||||||
"""
|
|
||||||
# Copyright (c) 2004 Colin Stewart (http://www.owlfish.com/)
|
|
||||||
# Modified somewhat for simplicity
|
|
||||||
# Used under the modified BSD license:
|
|
||||||
# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
|
|
||||||
|
|
||||||
import SimpleHTTPServer, SocketServer, BaseHTTPServer, urlparse
|
|
||||||
import socket, errno
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
class WSGIHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
|
||||||
def run_wsgi_app(self):
|
|
||||||
protocol, host, path, parameters, query, fragment = \
|
|
||||||
urlparse.urlparse('http://dummyhost%s' % self.path)
|
|
||||||
|
|
||||||
# we only use path, query
|
|
||||||
env = {'wsgi.version': (1, 0)
|
|
||||||
,'wsgi.url_scheme': 'http'
|
|
||||||
,'wsgi.input': self.rfile
|
|
||||||
,'wsgi.errors': sys.stderr
|
|
||||||
,'wsgi.multithread': 1
|
|
||||||
,'wsgi.multiprocess': 0
|
|
||||||
,'wsgi.run_once': 0
|
|
||||||
,'REQUEST_METHOD': self.command
|
|
||||||
,'REQUEST_URI': self.path
|
|
||||||
,'PATH_INFO': path
|
|
||||||
,'QUERY_STRING': query
|
|
||||||
,'CONTENT_TYPE': self.headers.get('Content-Type', '')
|
|
||||||
,'CONTENT_LENGTH': self.headers.get('Content-Length', '')
|
|
||||||
,'REMOTE_ADDR': self.client_address[0]
|
|
||||||
,'SERVER_NAME': self.server.server_address[0]
|
|
||||||
,'SERVER_PORT': str(self.server.server_address[1])
|
|
||||||
,'SERVER_PROTOCOL': self.request_version
|
|
||||||
}
|
|
||||||
|
|
||||||
for http_header, http_value in self.headers.items():
|
|
||||||
env ['HTTP_%s' % http_header.replace('-', '_').upper()] = \
|
|
||||||
http_value
|
|
||||||
|
|
||||||
# Setup the state
|
|
||||||
self.wsgi_sent_headers = 0
|
|
||||||
self.wsgi_headers = []
|
|
||||||
|
|
||||||
try:
|
|
||||||
# We have there environment, now invoke the application
|
|
||||||
result = self.server.app(env, self.wsgi_start_response)
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
for data in result:
|
|
||||||
if data:
|
|
||||||
self.wsgi_write_data(data)
|
|
||||||
finally:
|
|
||||||
if hasattr(result, 'close'):
|
|
||||||
result.close()
|
|
||||||
except socket.error, socket_err:
|
|
||||||
# Catch common network errors and suppress them
|
|
||||||
if (socket_err.args[0] in \
|
|
||||||
(errno.ECONNABORTED, errno.EPIPE)):
|
|
||||||
return
|
|
||||||
except socket.timeout, socket_timeout:
|
|
||||||
return
|
|
||||||
except:
|
|
||||||
print >> web.debug, traceback.format_exc(),
|
|
||||||
|
|
||||||
if (not self.wsgi_sent_headers):
|
|
||||||
# We must write out something!
|
|
||||||
self.wsgi_write_data(" ")
|
|
||||||
return
|
|
||||||
|
|
||||||
do_POST = run_wsgi_app
|
|
||||||
do_PUT = run_wsgi_app
|
|
||||||
do_DELETE = run_wsgi_app
|
|
||||||
|
|
||||||
def do_GET(self):
|
|
||||||
if self.path.startswith('/static/'):
|
|
||||||
SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
|
|
||||||
else:
|
|
||||||
self.run_wsgi_app()
|
|
||||||
|
|
||||||
def wsgi_start_response(self, response_status, response_headers,
|
|
||||||
exc_info=None):
|
|
||||||
if (self.wsgi_sent_headers):
|
|
||||||
raise Exception \
|
|
||||||
("Headers already sent and start_response called again!")
|
|
||||||
# Should really take a copy to avoid changes in the application....
|
|
||||||
self.wsgi_headers = (response_status, response_headers)
|
|
||||||
return self.wsgi_write_data
|
|
||||||
|
|
||||||
def wsgi_write_data(self, data):
|
|
||||||
if (not self.wsgi_sent_headers):
|
|
||||||
status, headers = self.wsgi_headers
|
|
||||||
# Need to send header prior to data
|
|
||||||
status_code = status[:status.find(' ')]
|
|
||||||
status_msg = status[status.find(' ') + 1:]
|
|
||||||
self.send_response(int(status_code), status_msg)
|
|
||||||
for header, value in headers:
|
|
||||||
self.send_header(header, value)
|
|
||||||
self.end_headers()
|
|
||||||
self.wsgi_sent_headers = 1
|
|
||||||
# Send the data
|
|
||||||
self.wfile.write(data)
|
|
||||||
|
|
||||||
class WSGIServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
|
|
||||||
def __init__(self, func, server_address):
|
|
||||||
BaseHTTPServer.HTTPServer.__init__(self,
|
|
||||||
server_address,
|
|
||||||
WSGIHandler)
|
|
||||||
self.app = func
|
|
||||||
self.serverShuttingDown = 0
|
|
||||||
|
|
||||||
print "http://%s:%d/" % server_address
|
|
||||||
WSGIServer(func, server_address).serve_forever()
|
|
||||||
|
|
||||||
def runsimple(func, server_address=("0.0.0.0", 8080)):
|
|
||||||
"""
|
|
||||||
Runs [CherryPy][cp] WSGI server hosting WSGI app `func`.
|
|
||||||
The directory `static/` is hosted statically.
|
|
||||||
|
|
||||||
[cp]: http://www.cherrypy.org
|
|
||||||
"""
|
|
||||||
from wsgiserver import CherryPyWSGIServer
|
|
||||||
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
|
||||||
from BaseHTTPServer import BaseHTTPRequestHandler
|
|
||||||
|
|
||||||
class StaticApp(SimpleHTTPRequestHandler):
|
|
||||||
"""WSGI application for serving static files."""
|
|
||||||
def __init__(self, environ, start_response):
|
|
||||||
self.headers = []
|
|
||||||
self.environ = environ
|
|
||||||
self.start_response = start_response
|
|
||||||
|
|
||||||
def send_response(self, status, msg=""):
|
|
||||||
self.status = str(status) + " " + msg
|
|
||||||
|
|
||||||
def send_header(self, name, value):
|
|
||||||
self.headers.append((name, value))
|
|
||||||
|
|
||||||
def end_headers(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def log_message(*a): pass
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
environ = self.environ
|
|
||||||
|
|
||||||
self.path = environ.get('PATH_INFO', '')
|
|
||||||
self.client_address = environ.get('REMOTE_ADDR','-'), \
|
|
||||||
environ.get('REMOTE_PORT','-')
|
|
||||||
self.command = environ.get('REQUEST_METHOD', '-')
|
|
||||||
|
|
||||||
from cStringIO import StringIO
|
|
||||||
self.wfile = StringIO() # for capturing error
|
|
||||||
|
|
||||||
f = self.send_head()
|
|
||||||
self.start_response(self.status, self.headers)
|
|
||||||
|
|
||||||
if f:
|
|
||||||
block_size = 16 * 1024
|
|
||||||
while True:
|
|
||||||
buf = f.read(block_size)
|
|
||||||
if not buf:
|
|
||||||
break
|
|
||||||
yield buf
|
|
||||||
f.close()
|
|
||||||
else:
|
|
||||||
value = self.wfile.getvalue()
|
|
||||||
yield value
|
|
||||||
|
|
||||||
class WSGIWrapper(BaseHTTPRequestHandler):
|
|
||||||
"""WSGI wrapper for logging the status and serving static files."""
|
|
||||||
def __init__(self, app):
|
|
||||||
self.app = app
|
|
||||||
self.format = '%s - - [%s] "%s %s %s" - %s'
|
|
||||||
|
|
||||||
def __call__(self, environ, start_response):
|
|
||||||
def xstart_response(status, response_headers, *args):
|
|
||||||
write = start_response(status, response_headers, *args)
|
|
||||||
self.log(status, environ)
|
|
||||||
return write
|
|
||||||
|
|
||||||
path = environ.get('PATH_INFO', '')
|
|
||||||
if path.startswith('/static/'):
|
|
||||||
return StaticApp(environ, xstart_response)
|
|
||||||
else:
|
|
||||||
return self.app(environ, xstart_response)
|
|
||||||
|
|
||||||
def log(self, status, environ):
|
|
||||||
#mvoncken,no logging..
|
|
||||||
return
|
|
||||||
|
|
||||||
outfile = environ.get('wsgi.errors', web.debug)
|
|
||||||
req = environ.get('PATH_INFO', '_')
|
|
||||||
protocol = environ.get('ACTUAL_SERVER_PROTOCOL', '-')
|
|
||||||
method = environ.get('REQUEST_METHOD', '-')
|
|
||||||
host = "%s:%s" % (environ.get('REMOTE_ADDR','-'),
|
|
||||||
environ.get('REMOTE_PORT','-'))
|
|
||||||
|
|
||||||
#@@ It is really bad to extend from
|
|
||||||
#@@ BaseHTTPRequestHandler just for this method
|
|
||||||
time = self.log_date_time_string()
|
|
||||||
|
|
||||||
print >> outfile, self.format % (host, time, protocol,
|
|
||||||
method, req, status)
|
|
||||||
|
|
||||||
func = WSGIWrapper(func)
|
|
||||||
server = CherryPyWSGIServer(server_address, func, server_name="localhost")
|
|
||||||
|
|
||||||
print "http://%s:%d/" % server_address
|
|
||||||
try:
|
|
||||||
server.start()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
server.stop()
|
|
||||||
@ -1,155 +0,0 @@
|
|||||||
"""
|
|
||||||
Network Utilities
|
|
||||||
(from web.py)
|
|
||||||
"""
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"validipaddr", "validipport", "validip", "validaddr",
|
|
||||||
"urlquote",
|
|
||||||
"httpdate", "parsehttpdate",
|
|
||||||
"htmlquote", "websafe",
|
|
||||||
]
|
|
||||||
|
|
||||||
import urllib, time
|
|
||||||
try: import datetime
|
|
||||||
except ImportError: pass
|
|
||||||
|
|
||||||
def validipaddr(address):
|
|
||||||
"""returns True if `address` is a valid IPv4 address"""
|
|
||||||
try:
|
|
||||||
octets = address.split('.')
|
|
||||||
assert len(octets) == 4
|
|
||||||
for x in octets:
|
|
||||||
assert 0 <= int(x) <= 255
|
|
||||||
except (AssertionError, ValueError):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def validipport(port):
|
|
||||||
"""returns True if `port` is a valid IPv4 port"""
|
|
||||||
try:
|
|
||||||
assert 0 <= int(port) <= 65535
|
|
||||||
except (AssertionError, ValueError):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def validip(ip, defaultaddr="0.0.0.0", defaultport=8080):
|
|
||||||
"""returns `(ip_address, port)` from string `ip_addr_port`"""
|
|
||||||
addr = defaultaddr
|
|
||||||
port = defaultport
|
|
||||||
|
|
||||||
ip = ip.split(":", 1)
|
|
||||||
if len(ip) == 1:
|
|
||||||
if not ip[0]:
|
|
||||||
pass
|
|
||||||
elif validipaddr(ip[0]):
|
|
||||||
addr = ip[0]
|
|
||||||
elif validipport(ip[0]):
|
|
||||||
port = int(ip[0])
|
|
||||||
else:
|
|
||||||
raise ValueError, ':'.join(ip) + ' is not a valid IP address/port'
|
|
||||||
elif len(ip) == 2:
|
|
||||||
addr, port = ip
|
|
||||||
if not validipaddr(addr) and validipport(port):
|
|
||||||
raise ValueError, ':'.join(ip) + ' is not a valid IP address/port'
|
|
||||||
port = int(port)
|
|
||||||
else:
|
|
||||||
raise ValueError, ':'.join(ip) + ' is not a valid IP address/port'
|
|
||||||
return (addr, port)
|
|
||||||
|
|
||||||
def validaddr(string_):
|
|
||||||
"""
|
|
||||||
returns either (ip_address, port) or "/path/to/socket" from string_
|
|
||||||
|
|
||||||
>>> validaddr('/path/to/socket')
|
|
||||||
'/path/to/socket'
|
|
||||||
>>> validaddr('8000')
|
|
||||||
('0.0.0.0', 8000)
|
|
||||||
>>> validaddr('127.0.0.1')
|
|
||||||
('127.0.0.1', 8080)
|
|
||||||
>>> validaddr('127.0.0.1:8000')
|
|
||||||
('127.0.0.1', 8000)
|
|
||||||
>>> validaddr('fff')
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: fff is not a valid IP address/port
|
|
||||||
"""
|
|
||||||
if '/' in string_:
|
|
||||||
return string_
|
|
||||||
else:
|
|
||||||
return validip(string_)
|
|
||||||
|
|
||||||
def urlquote(val):
|
|
||||||
"""
|
|
||||||
Quotes a string for use in a URL.
|
|
||||||
|
|
||||||
>>> urlquote('://?f=1&j=1')
|
|
||||||
'%3A//%3Ff%3D1%26j%3D1'
|
|
||||||
>>> urlquote(None)
|
|
||||||
''
|
|
||||||
>>> urlquote(u'\u203d')
|
|
||||||
'%E2%80%BD'
|
|
||||||
"""
|
|
||||||
if val is None: return ''
|
|
||||||
if not isinstance(val, unicode): val = str(val)
|
|
||||||
else: val = val.encode('utf-8')
|
|
||||||
return urllib.quote(val)
|
|
||||||
|
|
||||||
def httpdate(date_obj):
|
|
||||||
"""
|
|
||||||
Formats a datetime object for use in HTTP headers.
|
|
||||||
|
|
||||||
>>> import datetime
|
|
||||||
>>> httpdate(datetime.datetime(1970, 1, 1, 1, 1, 1))
|
|
||||||
'Thu, 01 Jan 1970 01:01:01 GMT'
|
|
||||||
"""
|
|
||||||
return date_obj.strftime("%a, %d %b %Y %H:%M:%S GMT")
|
|
||||||
|
|
||||||
def parsehttpdate(string_):
|
|
||||||
"""
|
|
||||||
Parses an HTTP date into a datetime object.
|
|
||||||
|
|
||||||
>>> parsehttpdate('Thu, 01 Jan 1970 01:01:01 GMT')
|
|
||||||
datetime.datetime(1970, 1, 1, 1, 1, 1)
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
t = time.strptime(string_, "%a, %d %b %Y %H:%M:%S %Z")
|
|
||||||
except ValueError:
|
|
||||||
return None
|
|
||||||
return datetime.datetime(*t[:6])
|
|
||||||
|
|
||||||
def htmlquote(text):
|
|
||||||
"""
|
|
||||||
Encodes `text` for raw use in HTML.
|
|
||||||
|
|
||||||
>>> htmlquote("<'&\\">")
|
|
||||||
'<'&">'
|
|
||||||
"""
|
|
||||||
text = text.replace("&", "&") # Must be done first!
|
|
||||||
text = text.replace("<", "<")
|
|
||||||
text = text.replace(">", ">")
|
|
||||||
text = text.replace("'", "'")
|
|
||||||
text = text.replace('"', """)
|
|
||||||
return text
|
|
||||||
|
|
||||||
def websafe(val):
|
|
||||||
"""
|
|
||||||
Converts `val` so that it's safe for use in UTF-8 HTML.
|
|
||||||
|
|
||||||
>>> websafe("<'&\\">")
|
|
||||||
'<'&">'
|
|
||||||
>>> websafe(None)
|
|
||||||
''
|
|
||||||
>>> websafe(u'\u203d')
|
|
||||||
'\\xe2\\x80\\xbd'
|
|
||||||
"""
|
|
||||||
if val is None:
|
|
||||||
return ''
|
|
||||||
if isinstance(val, unicode):
|
|
||||||
val = val.encode('utf-8')
|
|
||||||
val = str(val)
|
|
||||||
return htmlquote(val)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
doctest.testmod()
|
|
||||||
@ -1,153 +0,0 @@
|
|||||||
"""
|
|
||||||
Request Delegation
|
|
||||||
(from web.py)
|
|
||||||
"""
|
|
||||||
|
|
||||||
__all__ = ["handle", "nomethod", "autodelegate", "webpyfunc", "run"]
|
|
||||||
|
|
||||||
import sys, re, types, os.path, urllib
|
|
||||||
|
|
||||||
import http, wsgi, utils, webapi
|
|
||||||
import webapi as web
|
|
||||||
|
|
||||||
def handle(mapping, fvars=None):
|
|
||||||
"""
|
|
||||||
Call the appropriate function based on the url to function mapping in `mapping`.
|
|
||||||
If no module for the function is specified, look up the function in `fvars`. If
|
|
||||||
`fvars` is empty, using the caller's context.
|
|
||||||
|
|
||||||
`mapping` should be a tuple of paired regular expressions with function name
|
|
||||||
substitutions. `handle` will import modules as necessary.
|
|
||||||
"""
|
|
||||||
for url, ofno in utils.group(mapping, 2):
|
|
||||||
if isinstance(ofno, tuple):
|
|
||||||
ofn, fna = ofno[0], list(ofno[1:])
|
|
||||||
else:
|
|
||||||
ofn, fna = ofno, []
|
|
||||||
fn, result = utils.re_subm('^' + url + '$', ofn, web.ctx.path)
|
|
||||||
if result: # it's a match
|
|
||||||
if fn.split(' ', 1)[0] == "redirect":
|
|
||||||
url = fn.split(' ', 1)[1]
|
|
||||||
if web.ctx.method == "GET":
|
|
||||||
x = web.ctx.env.get('QUERY_STRING', '')
|
|
||||||
if x:
|
|
||||||
url += '?' + x
|
|
||||||
return http.redirect(url)
|
|
||||||
elif '.' in fn:
|
|
||||||
x = fn.split('.')
|
|
||||||
mod, cls = '.'.join(x[:-1]), x[-1]
|
|
||||||
mod = __import__(mod, globals(), locals(), [""])
|
|
||||||
cls = getattr(mod, cls)
|
|
||||||
else:
|
|
||||||
cls = fn
|
|
||||||
mod = fvars
|
|
||||||
if isinstance(mod, types.ModuleType):
|
|
||||||
mod = vars(mod)
|
|
||||||
try:
|
|
||||||
cls = mod[cls]
|
|
||||||
except KeyError:
|
|
||||||
return web.notfound()
|
|
||||||
|
|
||||||
meth = web.ctx.method
|
|
||||||
if meth == "HEAD":
|
|
||||||
if not hasattr(cls, meth):
|
|
||||||
meth = "GET"
|
|
||||||
if not hasattr(cls, meth):
|
|
||||||
return nomethod(cls)
|
|
||||||
tocall = getattr(cls(), meth)
|
|
||||||
args = list(result.groups())
|
|
||||||
for d in re.findall(r'\\(\d+)', ofn):
|
|
||||||
args.pop(int(d) - 1)
|
|
||||||
return tocall(*([x and urllib.unquote(x) for x in args] + fna))
|
|
||||||
|
|
||||||
return web.notfound()
|
|
||||||
|
|
||||||
def nomethod(cls):
|
|
||||||
"""Returns a `405 Method Not Allowed` error for `cls`."""
|
|
||||||
web.ctx.status = '405 Method Not Allowed'
|
|
||||||
web.header('Content-Type', 'text/html')
|
|
||||||
web.header('Allow', \
|
|
||||||
', '.join([method for method in \
|
|
||||||
['GET', 'HEAD', 'POST', 'PUT', 'DELETE'] \
|
|
||||||
if hasattr(cls, method)]))
|
|
||||||
|
|
||||||
# commented out for the same reason redirect is
|
|
||||||
# return output('method not allowed')
|
|
||||||
|
|
||||||
def autodelegate(prefix=''):
|
|
||||||
"""
|
|
||||||
Returns a method that takes one argument and calls the method named prefix+arg,
|
|
||||||
calling `notfound()` if there isn't one. Example:
|
|
||||||
|
|
||||||
urls = ('/prefs/(.*)', 'prefs')
|
|
||||||
|
|
||||||
class prefs:
|
|
||||||
GET = autodelegate('GET_')
|
|
||||||
def GET_password(self): pass
|
|
||||||
def GET_privacy(self): pass
|
|
||||||
|
|
||||||
`GET_password` would get called for `/prefs/password` while `GET_privacy` for
|
|
||||||
`GET_privacy` gets called for `/prefs/privacy`.
|
|
||||||
|
|
||||||
If a user visits `/prefs/password/change` then `GET_password(self, '/change')`
|
|
||||||
is called.
|
|
||||||
"""
|
|
||||||
def internal(self, arg):
|
|
||||||
if '/' in arg:
|
|
||||||
first, rest = arg.split('/', 1)
|
|
||||||
func = prefix + first
|
|
||||||
args = ['/' + rest]
|
|
||||||
else:
|
|
||||||
func = prefix + arg
|
|
||||||
args = []
|
|
||||||
|
|
||||||
if hasattr(self, func):
|
|
||||||
try:
|
|
||||||
return getattr(self, func)(*args)
|
|
||||||
except TypeError:
|
|
||||||
return web.notfound()
|
|
||||||
else:
|
|
||||||
return web.notfound()
|
|
||||||
return internal
|
|
||||||
|
|
||||||
def webpyfunc(inp, fvars, autoreload=False):
|
|
||||||
"""If `inp` is a url mapping, returns a function that calls handle."""
|
|
||||||
if not hasattr(inp, '__call__'):
|
|
||||||
if autoreload:
|
|
||||||
def modname():
|
|
||||||
"""find name of the module name from fvars."""
|
|
||||||
file, name = fvars['__file__'], fvars['__name__']
|
|
||||||
if name == '__main__':
|
|
||||||
# Since the __main__ module can't be reloaded, the module has
|
|
||||||
# to be imported using its file name.
|
|
||||||
name = os.path.splitext(os.path.basename(file))[0]
|
|
||||||
return name
|
|
||||||
|
|
||||||
mod = __import__(modname(), None, None, [""])
|
|
||||||
#@@probably should replace this with some inspect magic
|
|
||||||
name = utils.dictfind(fvars, inp)
|
|
||||||
func = lambda: handle(getattr(mod, name), mod)
|
|
||||||
else:
|
|
||||||
func = lambda: handle(inp, fvars)
|
|
||||||
else:
|
|
||||||
func = inp
|
|
||||||
return func
|
|
||||||
|
|
||||||
def run(inp, fvars, *middleware):
|
|
||||||
"""
|
|
||||||
Starts handling requests. If called in a CGI or FastCGI context, it will follow
|
|
||||||
that protocol. If called from the command line, it will start an HTTP
|
|
||||||
server on the port named in the first command line argument, or, if there
|
|
||||||
is no argument, on port 8080.
|
|
||||||
|
|
||||||
`input` is a callable, then it's called with no arguments.
|
|
||||||
Otherwise, it's a `mapping` object to be passed to `handle(...)`.
|
|
||||||
|
|
||||||
**Caveat:** So that `reloader` will work correctly, input has to be a variable,
|
|
||||||
it can't be a tuple passed in directly.
|
|
||||||
|
|
||||||
`middleware` is a list of WSGI middleware which is applied to the resulting WSGI
|
|
||||||
function.
|
|
||||||
"""
|
|
||||||
autoreload = http.reloader in middleware
|
|
||||||
return wsgi.runwsgi(webapi.wsgifunc(webpyfunc(inp, fvars, autoreload), *middleware))
|
|
||||||
@ -1,878 +0,0 @@
|
|||||||
"""
|
|
||||||
simple, elegant templating
|
|
||||||
(part of web.py)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re, glob, os, os.path
|
|
||||||
from types import FunctionType as function
|
|
||||||
from utils import storage, group, utf8
|
|
||||||
from net import websafe
|
|
||||||
|
|
||||||
# differences from python:
|
|
||||||
# - for: has an optional else: that gets called if the loop never runs
|
|
||||||
# differences to add:
|
|
||||||
# - you can use the expression inside if, while blocks
|
|
||||||
# - special for loop attributes, like django?
|
|
||||||
# - you can check to see if a variable is defined (perhaps w/ get func?)
|
|
||||||
# all these are probably good ideas for python...
|
|
||||||
|
|
||||||
# todo:
|
|
||||||
# inline tuple
|
|
||||||
# relax constraints on spacing
|
|
||||||
# continue, break, etc.
|
|
||||||
# tracebacks
|
|
||||||
|
|
||||||
global_globals = {'None':None, 'False':False, 'True': True}
|
|
||||||
MAX_ITERS = 100000
|
|
||||||
|
|
||||||
WHAT = 0
|
|
||||||
ARGS = 4
|
|
||||||
KWARGS = 6
|
|
||||||
NAME = 2
|
|
||||||
BODY = 4
|
|
||||||
CLAUSE = 2
|
|
||||||
ELIF = 6
|
|
||||||
ELSE = 8
|
|
||||||
IN = 6
|
|
||||||
NAME = 2
|
|
||||||
EXPR = 4
|
|
||||||
FILTER = 4
|
|
||||||
THING = 2
|
|
||||||
ATTR = 4
|
|
||||||
ITEM = 4
|
|
||||||
NEGATE = 4
|
|
||||||
X = 2
|
|
||||||
OP = 4
|
|
||||||
Y = 6
|
|
||||||
LINENO = -1
|
|
||||||
|
|
||||||
# http://docs.python.org/ref/identifiers.html
|
|
||||||
r_var = '[a-zA-Z_][a-zA-Z0-9_]*'
|
|
||||||
|
|
||||||
class ParseError(Exception): pass
|
|
||||||
class Parser:
|
|
||||||
def __init__(self, text, name=""):
|
|
||||||
self.t = text
|
|
||||||
self.p = 0
|
|
||||||
self._lock = [False]
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
def lock(self):
|
|
||||||
self._lock[-1] = True
|
|
||||||
|
|
||||||
def curline(self):
|
|
||||||
return self.t[:self.p].count('\n')+1
|
|
||||||
|
|
||||||
def csome(self):
|
|
||||||
return repr(self.t[self.p:self.p+5]+'...')
|
|
||||||
|
|
||||||
def Error(self, x, y=None):
|
|
||||||
if y is None: y = self.csome()
|
|
||||||
raise ParseError, "%s: expected %s, got %s (line %s)" % (self.name, x, y, self.curline())
|
|
||||||
|
|
||||||
def q(self, f):
|
|
||||||
def internal(*a, **kw):
|
|
||||||
checkp = self.p
|
|
||||||
self._lock.append(False)
|
|
||||||
try:
|
|
||||||
q = f(*a, **kw)
|
|
||||||
except ParseError:
|
|
||||||
if self._lock[-1]:
|
|
||||||
raise
|
|
||||||
self.p = checkp
|
|
||||||
self._lock.pop()
|
|
||||||
return False
|
|
||||||
self._lock.pop()
|
|
||||||
return q or True
|
|
||||||
return internal
|
|
||||||
|
|
||||||
def tokr(self, t):
|
|
||||||
text = self.c(len(t))
|
|
||||||
if text != t:
|
|
||||||
self.Error(repr(t), repr(text))
|
|
||||||
return t
|
|
||||||
|
|
||||||
def ltokr(self, *l):
|
|
||||||
for x in l:
|
|
||||||
o = self.tokq(x)
|
|
||||||
if o: return o
|
|
||||||
self.Error('one of '+repr(l))
|
|
||||||
|
|
||||||
def rer(self, r):
|
|
||||||
x = re.match(r, self.t[self.p:]) #@@re_compile
|
|
||||||
if not x:
|
|
||||||
self.Error('r'+repr(r))
|
|
||||||
return self.tokr(x.group())
|
|
||||||
|
|
||||||
def endr(self):
|
|
||||||
if self.p != len(self.t):
|
|
||||||
self.Error('EOF')
|
|
||||||
|
|
||||||
def c(self, n=1):
|
|
||||||
out = self.t[self.p:self.p+n]
|
|
||||||
if out == '' and n != 0:
|
|
||||||
self.Error('character', 'EOF')
|
|
||||||
self.p += n
|
|
||||||
return out
|
|
||||||
|
|
||||||
def lookbehind(self, t):
|
|
||||||
return self.t[self.p-len(t):self.p] == t
|
|
||||||
|
|
||||||
def __getattr__(self, a):
|
|
||||||
if a.endswith('q'):
|
|
||||||
return self.q(getattr(self, a[:-1]+'r'))
|
|
||||||
raise AttributeError, a
|
|
||||||
|
|
||||||
class TemplateParser(Parser):
|
|
||||||
def __init__(self, *a, **kw):
|
|
||||||
Parser.__init__(self, *a, **kw)
|
|
||||||
self.curws = ''
|
|
||||||
self.curind = ''
|
|
||||||
|
|
||||||
def o(self, *a):
|
|
||||||
return a+('lineno', self.curline())
|
|
||||||
|
|
||||||
def go(self):
|
|
||||||
# maybe try to do some traceback parsing/hacking
|
|
||||||
return self.gor()
|
|
||||||
|
|
||||||
def gor(self):
|
|
||||||
header = self.defwithq()
|
|
||||||
results = self.lines(start=True)
|
|
||||||
self.endr()
|
|
||||||
return header, results
|
|
||||||
|
|
||||||
def ws(self):
|
|
||||||
n = 0
|
|
||||||
while self.tokq(" "): n += 1
|
|
||||||
return " " * n
|
|
||||||
|
|
||||||
def defwithr(self):
|
|
||||||
self.tokr('$def with ')
|
|
||||||
self.lock()
|
|
||||||
self.tokr('(')
|
|
||||||
args = []
|
|
||||||
kw = []
|
|
||||||
x = self.req(r_var)
|
|
||||||
while x:
|
|
||||||
if self.tokq('='):
|
|
||||||
v = self.exprr()
|
|
||||||
kw.append((x, v))
|
|
||||||
else:
|
|
||||||
args.append(x)
|
|
||||||
x = self.tokq(', ') and self.req(r_var)
|
|
||||||
self.tokr(')\n')
|
|
||||||
return self.o('defwith', 'null', None, 'args', args, 'kwargs', kw)
|
|
||||||
|
|
||||||
def literalr(self):
|
|
||||||
o = (
|
|
||||||
self.req('"[^"]*"') or #@@ no support for escapes
|
|
||||||
self.req("'[^']*'")
|
|
||||||
)
|
|
||||||
if o is False:
|
|
||||||
o = self.req('\-?[0-9]+(\.[0-9]*)?')
|
|
||||||
if o is not False:
|
|
||||||
if '.' in o: o = float(o)
|
|
||||||
else: o = int(o)
|
|
||||||
|
|
||||||
if o is False: self.Error('literal')
|
|
||||||
return self.o('literal', 'thing', o)
|
|
||||||
|
|
||||||
def listr(self):
|
|
||||||
self.tokr('[')
|
|
||||||
self.lock()
|
|
||||||
x = []
|
|
||||||
if not self.tokq(']'):
|
|
||||||
while True:
|
|
||||||
t = self.exprr()
|
|
||||||
x.append(t)
|
|
||||||
if not self.tokq(', '): break
|
|
||||||
self.tokr(']')
|
|
||||||
return self.o('list', 'thing', x)
|
|
||||||
|
|
||||||
def dictr(self):
|
|
||||||
self.tokr('{')
|
|
||||||
self.lock()
|
|
||||||
x = {}
|
|
||||||
if not self.tokq('}'):
|
|
||||||
while True:
|
|
||||||
k = self.exprr()
|
|
||||||
self.tokr(': ')
|
|
||||||
v = self.exprr()
|
|
||||||
x[k] = v
|
|
||||||
if not self.tokq(', '): break
|
|
||||||
self.tokr('}')
|
|
||||||
return self.o('dict', 'thing', x)
|
|
||||||
|
|
||||||
def parenr(self):
|
|
||||||
self.tokr('(')
|
|
||||||
self.lock()
|
|
||||||
o = self.exprr() # todo: allow list
|
|
||||||
self.tokr(')')
|
|
||||||
return self.o('paren', 'thing', o)
|
|
||||||
|
|
||||||
def atomr(self):
|
|
||||||
"""returns var, literal, paren, dict, or list"""
|
|
||||||
o = (
|
|
||||||
self.varq() or
|
|
||||||
self.parenq() or
|
|
||||||
self.dictq() or
|
|
||||||
self.listq() or
|
|
||||||
self.literalq()
|
|
||||||
)
|
|
||||||
if o is False: self.Error('atom')
|
|
||||||
return o
|
|
||||||
|
|
||||||
def primaryr(self):
|
|
||||||
"""returns getattr, call, or getitem"""
|
|
||||||
n = self.atomr()
|
|
||||||
while 1:
|
|
||||||
if self.tokq('.'):
|
|
||||||
v = self.req(r_var)
|
|
||||||
if not v:
|
|
||||||
self.p -= 1 # get rid of the '.'
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
n = self.o('getattr', 'thing', n, 'attr', v)
|
|
||||||
elif self.tokq('('):
|
|
||||||
args = []
|
|
||||||
kw = []
|
|
||||||
|
|
||||||
while 1:
|
|
||||||
# need to see if we're doing a keyword argument
|
|
||||||
checkp = self.p
|
|
||||||
k = self.req(r_var)
|
|
||||||
if k and self.tokq('='): # yup
|
|
||||||
v = self.exprr()
|
|
||||||
kw.append((k, v))
|
|
||||||
else:
|
|
||||||
self.p = checkp
|
|
||||||
x = self.exprq()
|
|
||||||
if x: # at least it's something
|
|
||||||
args.append(x)
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
if not self.tokq(', '): break
|
|
||||||
self.tokr(')')
|
|
||||||
n = self.o('call', 'thing', n, 'args', args, 'kwargs', kw)
|
|
||||||
elif self.tokq('['):
|
|
||||||
v = self.exprr()
|
|
||||||
self.tokr(']')
|
|
||||||
n = self.o('getitem', 'thing', n, 'item', v)
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
return n
|
|
||||||
|
|
||||||
def exprr(self):
|
|
||||||
negate = self.tokq('not ')
|
|
||||||
x = self.primaryr()
|
|
||||||
if self.tokq(' '):
|
|
||||||
operator = self.ltokr('not in', 'in', 'is not', 'is', '==', '!=', '>=', '<=', '<', '>', 'and', 'or', '*', '+', '-', '/', '%')
|
|
||||||
self.tokr(' ')
|
|
||||||
y = self.exprr()
|
|
||||||
x = self.o('test', 'x', x, 'op', operator, 'y', y)
|
|
||||||
|
|
||||||
return self.o('expr', 'thing', x, 'negate', negate)
|
|
||||||
|
|
||||||
def varr(self):
|
|
||||||
return self.o('var', 'name', self.rer(r_var))
|
|
||||||
|
|
||||||
def liner(self):
|
|
||||||
out = []
|
|
||||||
o = self.curws
|
|
||||||
while 1:
|
|
||||||
c = self.c()
|
|
||||||
self.lock()
|
|
||||||
if c == '\n':
|
|
||||||
self.p -= 1
|
|
||||||
break
|
|
||||||
if c == '$':
|
|
||||||
if self.lookbehind('\\$'):
|
|
||||||
o = o[:-1] + c
|
|
||||||
else:
|
|
||||||
filter = not bool(self.tokq(':'))
|
|
||||||
|
|
||||||
if self.tokq('{'):
|
|
||||||
out.append(o)
|
|
||||||
out.append(self.o('itpl', 'name', self.exprr(), 'filter', filter))
|
|
||||||
self.tokr('}')
|
|
||||||
o = ''
|
|
||||||
else:
|
|
||||||
g = self.primaryq()
|
|
||||||
if g:
|
|
||||||
out.append(o)
|
|
||||||
out.append(self.o('itpl', 'name', g, 'filter', filter))
|
|
||||||
o = ''
|
|
||||||
else:
|
|
||||||
o += c
|
|
||||||
else:
|
|
||||||
o += c
|
|
||||||
self.tokr('\n')
|
|
||||||
if not self.lookbehind('\\\n'):
|
|
||||||
o += '\n'
|
|
||||||
else:
|
|
||||||
o = o[:-1]
|
|
||||||
out.append(o)
|
|
||||||
return self.o('line', 'thing', out)
|
|
||||||
|
|
||||||
def varsetr(self):
|
|
||||||
self.tokr('$var ')
|
|
||||||
self.lock()
|
|
||||||
what = self.rer(r_var)
|
|
||||||
self.tokr(':')
|
|
||||||
body = self.lines()
|
|
||||||
return self.o('varset', 'name', what, 'body', body)
|
|
||||||
|
|
||||||
def ifr(self):
|
|
||||||
self.tokr("$if ")
|
|
||||||
self.lock()
|
|
||||||
expr = self.exprr()
|
|
||||||
self.tokr(":")
|
|
||||||
ifc = self.lines()
|
|
||||||
|
|
||||||
elifs = []
|
|
||||||
while self.tokq(self.curws + self.curind + '$elif '):
|
|
||||||
v = self.exprr()
|
|
||||||
self.tokr(':')
|
|
||||||
c = self.lines()
|
|
||||||
elifs.append(self.o('elif', 'clause', v, 'body', c))
|
|
||||||
|
|
||||||
if self.tokq(self.curws + self.curind + "$else:"):
|
|
||||||
elsec = self.lines()
|
|
||||||
else:
|
|
||||||
elsec = None
|
|
||||||
|
|
||||||
return self.o('if', 'clause', expr, 'then', ifc, 'elif', elifs, 'else', elsec)
|
|
||||||
|
|
||||||
def forr(self):
|
|
||||||
self.tokr("$for ")
|
|
||||||
self.lock()
|
|
||||||
v = self.setabler()
|
|
||||||
self.tokr(" in ")
|
|
||||||
g = self.exprr()
|
|
||||||
self.tokr(":")
|
|
||||||
l = self.lines()
|
|
||||||
|
|
||||||
if self.tokq(self.curws + self.curind + '$else:'):
|
|
||||||
elsec = self.lines()
|
|
||||||
else:
|
|
||||||
elsec = None
|
|
||||||
|
|
||||||
return self.o('for', 'name', v, 'body', l, 'in', g, 'else', elsec)
|
|
||||||
|
|
||||||
def whiler(self):
|
|
||||||
self.tokr('$while ')
|
|
||||||
self.lock()
|
|
||||||
v = self.exprr()
|
|
||||||
self.tokr(":")
|
|
||||||
l = self.lines()
|
|
||||||
|
|
||||||
if self.tokq(self.curws + self.curind + '$else:'):
|
|
||||||
elsec = self.lines()
|
|
||||||
else:
|
|
||||||
elsec = None
|
|
||||||
|
|
||||||
return self.o('while', 'clause', v, 'body', l, 'null', None, 'else', elsec)
|
|
||||||
|
|
||||||
def assignr(self):
|
|
||||||
self.tokr('$ ')
|
|
||||||
assign = self.rer(r_var) # NOTE: setable
|
|
||||||
self.tokr(' = ')
|
|
||||||
expr = self.exprr()
|
|
||||||
self.tokr('\n')
|
|
||||||
|
|
||||||
return self.o('assign', 'name', assign, 'expr', expr)
|
|
||||||
|
|
||||||
def commentr(self):
|
|
||||||
self.tokr('$#')
|
|
||||||
self.lock()
|
|
||||||
while self.c() != '\n': pass
|
|
||||||
return self.o('comment')
|
|
||||||
|
|
||||||
def setabler(self):
|
|
||||||
out = [self.varr()] #@@ not quite right
|
|
||||||
while self.tokq(', '):
|
|
||||||
out.append(self.varr())
|
|
||||||
return out
|
|
||||||
|
|
||||||
def lines(self, start=False):
|
|
||||||
"""
|
|
||||||
This function gets called from two places:
|
|
||||||
1. at the start, where it's matching the document itself
|
|
||||||
2. after any command, where it matches one line or an indented block
|
|
||||||
"""
|
|
||||||
o = []
|
|
||||||
if not start: # try to match just one line
|
|
||||||
singleline = self.tokq(' ') and self.lineq()
|
|
||||||
if singleline:
|
|
||||||
return [singleline]
|
|
||||||
else:
|
|
||||||
self.rer(' *') #@@slurp space?
|
|
||||||
self.tokr('\n')
|
|
||||||
oldind = self.curind
|
|
||||||
self.curind += ' '
|
|
||||||
while 1:
|
|
||||||
oldws = self.curws
|
|
||||||
t = self.tokq(oldws + self.curind)
|
|
||||||
if not t: break
|
|
||||||
|
|
||||||
self.curws += self.ws()
|
|
||||||
x = t and (
|
|
||||||
self.varsetq() or
|
|
||||||
self.ifq() or
|
|
||||||
self.forq() or
|
|
||||||
self.whileq() or
|
|
||||||
self.assignq() or
|
|
||||||
self.commentq() or
|
|
||||||
self.lineq())
|
|
||||||
self.curws = oldws
|
|
||||||
if not x:
|
|
||||||
break
|
|
||||||
elif x[WHAT] == 'comment':
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
o.append(x)
|
|
||||||
|
|
||||||
if not start: self.curind = oldind
|
|
||||||
return o
|
|
||||||
|
|
||||||
class Stowage(storage):
|
|
||||||
def __str__(self): return self.get('_str')
|
|
||||||
#@@ edits in place
|
|
||||||
def __add__(self, other):
|
|
||||||
if isinstance(other, (unicode, str)):
|
|
||||||
self._str += other
|
|
||||||
return self
|
|
||||||
else:
|
|
||||||
raise TypeError, 'cannot add'
|
|
||||||
def __radd__(self, other):
|
|
||||||
if isinstance(other, (unicode, str)):
|
|
||||||
self._str = other + self._str
|
|
||||||
return self
|
|
||||||
else:
|
|
||||||
raise TypeError, 'cannot add'
|
|
||||||
|
|
||||||
class WTF(AssertionError): pass
|
|
||||||
class SecurityError(Exception):
|
|
||||||
"""The template seems to be trying to do something naughty."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Required = object()
|
|
||||||
class Template:
|
|
||||||
globals = {}
|
|
||||||
content_types = {
|
|
||||||
'.html' : 'text/html; charset=utf-8',
|
|
||||||
'.txt' : 'text/plain',
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, text, filter=None, filename=""):
|
|
||||||
self.filter = filter
|
|
||||||
self.filename = filename
|
|
||||||
# universal newlines:
|
|
||||||
text = text.replace('\r\n', '\n').replace('\r', '\n').expandtabs()
|
|
||||||
if not text.endswith('\n'): text += '\n'
|
|
||||||
header, tree = TemplateParser(text, filename).go()
|
|
||||||
self.tree = tree
|
|
||||||
if header:
|
|
||||||
self.h_defwith(header)
|
|
||||||
else:
|
|
||||||
self.args, self.kwargs = (), {}
|
|
||||||
|
|
||||||
def __call__(self, *a, **kw):
|
|
||||||
d = self.globals.copy()
|
|
||||||
d.update(self._parseargs(a, kw))
|
|
||||||
f = Fill(self.tree, d=d)
|
|
||||||
if self.filter: f.filter = self.filter
|
|
||||||
|
|
||||||
import webapi as web
|
|
||||||
if 'headers' in web.ctx and self.filename:
|
|
||||||
content_type = self.find_content_type()
|
|
||||||
if content_type:
|
|
||||||
web.header('Content-Type', content_type, unique=True)
|
|
||||||
|
|
||||||
return f.go()
|
|
||||||
|
|
||||||
def find_content_type(self):
|
|
||||||
for ext, content_type in self.content_types.iteritems():
|
|
||||||
if self.filename.endswith(ext):
|
|
||||||
return content_type
|
|
||||||
|
|
||||||
def _parseargs(self, inargs, inkwargs):
|
|
||||||
# difference from Python:
|
|
||||||
# no error on setting a keyword arg twice
|
|
||||||
d = {}
|
|
||||||
for arg in self.args:
|
|
||||||
d[arg] = Required
|
|
||||||
for kw, val in self.kwargs:
|
|
||||||
d[kw] = val
|
|
||||||
|
|
||||||
for n, val in enumerate(inargs):
|
|
||||||
if n < len(self.args):
|
|
||||||
d[self.args[n]] = val
|
|
||||||
elif n < len(self.args)+len(self.kwargs):
|
|
||||||
kw = self.kwargs[n - len(self.args)][0]
|
|
||||||
d[kw] = val
|
|
||||||
|
|
||||||
for kw, val in inkwargs.iteritems():
|
|
||||||
d[kw] = val
|
|
||||||
|
|
||||||
unset = []
|
|
||||||
for k, v in d.iteritems():
|
|
||||||
if v is Required:
|
|
||||||
unset.append(k)
|
|
||||||
if unset:
|
|
||||||
raise TypeError, 'values for %s are required' % unset
|
|
||||||
|
|
||||||
return d
|
|
||||||
|
|
||||||
def h_defwith(self, header):
|
|
||||||
assert header[WHAT] == 'defwith'
|
|
||||||
f = Fill(self.tree, d={})
|
|
||||||
|
|
||||||
self.args = header[ARGS]
|
|
||||||
self.kwargs = []
|
|
||||||
for var, valexpr in header[KWARGS]:
|
|
||||||
self.kwargs.append((var, f.h(valexpr)))
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<Template: %s>" % self.filename
|
|
||||||
|
|
||||||
class Handle:
|
|
||||||
def __init__(self, parsetree, **kw):
|
|
||||||
self._funccache = {}
|
|
||||||
self.parsetree = parsetree
|
|
||||||
for (k, v) in kw.iteritems(): setattr(self, k, v)
|
|
||||||
|
|
||||||
def h(self, item):
|
|
||||||
return getattr(self, 'h_' + item[WHAT])(item)
|
|
||||||
|
|
||||||
class Fill(Handle):
|
|
||||||
builtins = global_globals
|
|
||||||
def filter(self, text):
|
|
||||||
if text is None: return ''
|
|
||||||
else: return utf8(text)
|
|
||||||
# often replaced with stuff like net.websafe
|
|
||||||
|
|
||||||
def h_literal(self, i):
|
|
||||||
item = i[THING]
|
|
||||||
if isinstance(item, (unicode, str)) and item[0] in ['"', "'"]:
|
|
||||||
item = item[1:-1]
|
|
||||||
elif isinstance(item, (float, int)):
|
|
||||||
pass
|
|
||||||
return item
|
|
||||||
|
|
||||||
def h_list(self, i):
|
|
||||||
x = i[THING]
|
|
||||||
out = []
|
|
||||||
for item in x:
|
|
||||||
out.append(self.h(item))
|
|
||||||
return out
|
|
||||||
|
|
||||||
def h_dict(self, i):
|
|
||||||
x = i[THING]
|
|
||||||
out = {}
|
|
||||||
for k, v in x.iteritems():
|
|
||||||
out[self.h(k)] = self.h(v)
|
|
||||||
return out
|
|
||||||
|
|
||||||
def h_paren(self, i):
|
|
||||||
item = i[THING]
|
|
||||||
if isinstance(item, list):
|
|
||||||
raise NotImplementedError, 'tuples'
|
|
||||||
return self.h(item)
|
|
||||||
|
|
||||||
def h_getattr(self, i):
|
|
||||||
thing, attr = i[THING], i[ATTR]
|
|
||||||
thing = self.h(thing)
|
|
||||||
if attr.startswith('_') or attr.startswith('func_') or attr.startswith('im_'):
|
|
||||||
raise SecurityError, 'tried to get ' + attr
|
|
||||||
try:
|
|
||||||
if thing in self.builtins:
|
|
||||||
raise SecurityError, 'tried to getattr on ' + repr(thing)
|
|
||||||
except TypeError:
|
|
||||||
pass # raised when testing an unhashable object
|
|
||||||
try:
|
|
||||||
return getattr(thing, attr)
|
|
||||||
except AttributeError:
|
|
||||||
if isinstance(thing, list) and attr == 'join':
|
|
||||||
return lambda s: s.join(thing)
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
def h_call(self, i):
|
|
||||||
call = self.h(i[THING])
|
|
||||||
args = [self.h(x) for x in i[ARGS]]
|
|
||||||
kw = dict([(x, self.h(y)) for (x, y) in i[KWARGS]])
|
|
||||||
return call(*args, **kw)
|
|
||||||
|
|
||||||
def h_getitem(self, i):
|
|
||||||
thing, item = i[THING], i[ITEM]
|
|
||||||
thing = self.h(thing)
|
|
||||||
item = self.h(item)
|
|
||||||
return thing[item]
|
|
||||||
|
|
||||||
def h_expr(self, i):
|
|
||||||
item = self.h(i[THING])
|
|
||||||
if i[NEGATE]:
|
|
||||||
item = not item
|
|
||||||
return item
|
|
||||||
|
|
||||||
def h_test(self, item):
|
|
||||||
ox, op, oy = item[X], item[OP], item[Y]
|
|
||||||
# for short-circuiting to work, we can't eval these here
|
|
||||||
e = self.h
|
|
||||||
if op == 'is':
|
|
||||||
return e(ox) is e(oy)
|
|
||||||
elif op == 'is not':
|
|
||||||
return e(ox) is not e(oy)
|
|
||||||
elif op == 'in':
|
|
||||||
return e(ox) in e(oy)
|
|
||||||
elif op == 'not in':
|
|
||||||
return e(ox) not in e(oy)
|
|
||||||
elif op == '==':
|
|
||||||
return e(ox) == e(oy)
|
|
||||||
elif op == '!=':
|
|
||||||
return e(ox) != e(oy)
|
|
||||||
elif op == '>':
|
|
||||||
return e(ox) > e(oy)
|
|
||||||
elif op == '<':
|
|
||||||
return e(ox) < e(oy)
|
|
||||||
elif op == '<=':
|
|
||||||
return e(ox) <= e(oy)
|
|
||||||
elif op == '>=':
|
|
||||||
return e(ox) >= e(oy)
|
|
||||||
elif op == 'and':
|
|
||||||
return e(ox) and e(oy)
|
|
||||||
elif op == 'or':
|
|
||||||
return e(ox) or e(oy)
|
|
||||||
elif op == '+':
|
|
||||||
return e(ox) + e(oy)
|
|
||||||
elif op == '-':
|
|
||||||
return e(ox) - e(oy)
|
|
||||||
elif op == '*':
|
|
||||||
return e(ox) * e(oy)
|
|
||||||
elif op == '/':
|
|
||||||
return e(ox) / e(oy)
|
|
||||||
elif op == '%':
|
|
||||||
return e(ox) % e(oy)
|
|
||||||
else:
|
|
||||||
raise WTF, 'op ' + op
|
|
||||||
|
|
||||||
def h_var(self, i):
|
|
||||||
v = i[NAME]
|
|
||||||
if v in self.d:
|
|
||||||
return self.d[v]
|
|
||||||
elif v in self.builtins:
|
|
||||||
return self.builtins[v]
|
|
||||||
elif v == 'self':
|
|
||||||
return self.output
|
|
||||||
else:
|
|
||||||
raise NameError, 'could not find %s (line %s)' % (repr(i[NAME]), i[LINENO])
|
|
||||||
|
|
||||||
def h_line(self, i):
|
|
||||||
out = []
|
|
||||||
for x in i[THING]:
|
|
||||||
#@@ what if x is unicode
|
|
||||||
if isinstance(x, str):
|
|
||||||
out.append(x)
|
|
||||||
elif x[WHAT] == 'itpl':
|
|
||||||
o = self.h(x[NAME])
|
|
||||||
if x[FILTER]:
|
|
||||||
o = self.filter(o)
|
|
||||||
else:
|
|
||||||
o = (o is not None and utf8(o)) or ""
|
|
||||||
out.append(o)
|
|
||||||
else:
|
|
||||||
raise WTF, x
|
|
||||||
return ''.join(out)
|
|
||||||
|
|
||||||
def h_varset(self, i):
|
|
||||||
self.output[i[NAME]] = ''.join(self.h_lines(i[BODY]))
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def h_if(self, i):
|
|
||||||
expr = self.h(i[CLAUSE])
|
|
||||||
if expr:
|
|
||||||
do = i[BODY]
|
|
||||||
else:
|
|
||||||
for e in i[ELIF]:
|
|
||||||
expr = self.h(e[CLAUSE])
|
|
||||||
if expr:
|
|
||||||
do = e[BODY]
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
do = i[ELSE]
|
|
||||||
return ''.join(self.h_lines(do))
|
|
||||||
|
|
||||||
def h_for(self, i):
|
|
||||||
out = []
|
|
||||||
assert i[IN][WHAT] == 'expr'
|
|
||||||
invar = self.h(i[IN])
|
|
||||||
forvar = i[NAME]
|
|
||||||
if invar:
|
|
||||||
for nv in invar:
|
|
||||||
if len(forvar) == 1:
|
|
||||||
fv = forvar[0]
|
|
||||||
assert fv[WHAT] == 'var'
|
|
||||||
self.d[fv[NAME]] = nv # same (lack of) scoping as Python
|
|
||||||
else:
|
|
||||||
for x, y in zip(forvar, nv):
|
|
||||||
assert x[WHAT] == 'var'
|
|
||||||
self.d[x[NAME]] = y
|
|
||||||
|
|
||||||
out.extend(self.h_lines(i[BODY]))
|
|
||||||
else:
|
|
||||||
if i[ELSE]:
|
|
||||||
out.extend(self.h_lines(i[ELSE]))
|
|
||||||
return ''.join(out)
|
|
||||||
|
|
||||||
def h_while(self, i):
|
|
||||||
out = []
|
|
||||||
expr = self.h(i[CLAUSE])
|
|
||||||
if not expr:
|
|
||||||
return ''.join(self.h_lines(i[ELSE]))
|
|
||||||
c = 0
|
|
||||||
while expr:
|
|
||||||
c += 1
|
|
||||||
if c >= MAX_ITERS:
|
|
||||||
raise RuntimeError, 'too many while-loop iterations (line %s)' % i[LINENO]
|
|
||||||
out.extend(self.h_lines(i[BODY]))
|
|
||||||
expr = self.h(i[CLAUSE])
|
|
||||||
return ''.join(out)
|
|
||||||
|
|
||||||
def h_assign(self, i):
|
|
||||||
self.d[i[NAME]] = self.h(i[EXPR])
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def h_comment(self, i): pass
|
|
||||||
|
|
||||||
def h_lines(self, lines):
|
|
||||||
if lines is None: return []
|
|
||||||
return map(self.h, lines)
|
|
||||||
|
|
||||||
def go(self):
|
|
||||||
self.output = Stowage()
|
|
||||||
self.output._str = ''.join(map(self.h, self.parsetree))
|
|
||||||
if self.output.keys() == ['_str']:
|
|
||||||
self.output = self.output['_str']
|
|
||||||
return self.output
|
|
||||||
|
|
||||||
class render:
|
|
||||||
def __init__(self, loc='templates/', cache=True):
|
|
||||||
self.loc = loc
|
|
||||||
if cache:
|
|
||||||
self.cache = {}
|
|
||||||
else:
|
|
||||||
self.cache = False
|
|
||||||
|
|
||||||
def _do(self, name, filter=None):
|
|
||||||
if self.cache is False or name not in self.cache:
|
|
||||||
|
|
||||||
tmplpath = os.path.join(self.loc, name)
|
|
||||||
p = [f for f in glob.glob(tmplpath + '.*') if not f.endswith('~')] # skip backup files
|
|
||||||
if not p and os.path.isdir(tmplpath):
|
|
||||||
return render(tmplpath, cache=self.cache)
|
|
||||||
elif not p:
|
|
||||||
raise AttributeError, 'no template named ' + name
|
|
||||||
|
|
||||||
p = p[0]
|
|
||||||
c = Template(open(p).read(), filename=p)
|
|
||||||
if self.cache is not False: self.cache[name] = (p, c)
|
|
||||||
|
|
||||||
if self.cache is not False: p, c = self.cache[name]
|
|
||||||
|
|
||||||
if p.endswith('.html') or p.endswith('.xml'):
|
|
||||||
if not filter: c.filter = websafe
|
|
||||||
return c
|
|
||||||
|
|
||||||
def __getattr__(self, p):
|
|
||||||
return self._do(p)
|
|
||||||
|
|
||||||
def frender(fn, *a, **kw):
|
|
||||||
return Template(open(fn).read(), *a, **kw)
|
|
||||||
|
|
||||||
def test():
|
|
||||||
import sys
|
|
||||||
verbose = '-v' in sys.argv
|
|
||||||
def assertEqual(a, b):
|
|
||||||
if a == b:
|
|
||||||
if verbose:
|
|
||||||
sys.stderr.write('.')
|
|
||||||
sys.stderr.flush()
|
|
||||||
else:
|
|
||||||
assert a == b, "\nexpected: %s\ngot: %s" % (repr(b), repr(a))
|
|
||||||
|
|
||||||
from utils import storage, group
|
|
||||||
|
|
||||||
class t:
|
|
||||||
def __init__(self, text):
|
|
||||||
self.text = text
|
|
||||||
|
|
||||||
def __call__(self, *a, **kw):
|
|
||||||
return TestResult(self.text, Template(self.text)(*a, **kw))
|
|
||||||
|
|
||||||
class TestResult:
|
|
||||||
def __init__(self, source, value):
|
|
||||||
self.source = source
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if self.value == other:
|
|
||||||
if verbose:
|
|
||||||
sys.stderr.write('.')
|
|
||||||
else:
|
|
||||||
print >> sys.stderr, 'FAIL:', repr(self.source), 'expected', repr(other), ', got', repr(self.value)
|
|
||||||
sys.stderr.flush()
|
|
||||||
|
|
||||||
t('1')() == '1\n'
|
|
||||||
t('$def with ()\n1')() == '1\n'
|
|
||||||
t('$def with (a)\n$a')(1) == '1\n'
|
|
||||||
t('$def with (a=0)\n$a')(1) == '1\n'
|
|
||||||
t('$def with (a=0)\n$a')(a=1) == '1\n'
|
|
||||||
t('$if 1: 1')() == '1\n'
|
|
||||||
t('$if 1:\n 1')() == '1\n'
|
|
||||||
t('$if 0: 0\n$elif 1: 1')() == '1\n'
|
|
||||||
t('$if 0: 0\n$elif None: 0\n$else: 1')() == '1\n'
|
|
||||||
t('$if (0 < 1) and (1 < 2): 1')() == '1\n'
|
|
||||||
t('$for x in [1, 2, 3]: $x')() == '1\n2\n3\n'
|
|
||||||
t('$for x in []: 0\n$else: 1')() == '1\n'
|
|
||||||
t('$def with (a)\n$while a and a.pop(): 1')([1, 2, 3]) == '1\n1\n1\n'
|
|
||||||
t('$while 0: 0\n$else: 1')() == '1\n'
|
|
||||||
t('$ a = 1\n$a')() == '1\n'
|
|
||||||
t('$# 0')() == ''
|
|
||||||
t('$def with (d)\n$for k, v in d.iteritems(): $k')({1: 1}) == '1\n'
|
|
||||||
t('$def with (a)\n$(a)')(1) == '1\n'
|
|
||||||
t('$def with (a)\n$a')(1) == '1\n'
|
|
||||||
t('$def with (a)\n$a.b')(storage(b=1)) == '1\n'
|
|
||||||
t('$def with (a)\n$a[0]')([1]) == '1\n'
|
|
||||||
t('${0 or 1}')() == '1\n'
|
|
||||||
t('$ a = [1]\n$a[0]')() == '1\n'
|
|
||||||
t('$ a = {1: 1}\n$a.keys()[0]')() == '1\n'
|
|
||||||
t('$ a = []\n$if not a: 1')() == '1\n'
|
|
||||||
t('$ a = {}\n$if not a: 1')() == '1\n'
|
|
||||||
t('$ a = -1\n$a')() == '-1\n'
|
|
||||||
t('$ a = "1"\n$a')() == '1\n'
|
|
||||||
t('$if 1 is 1: 1')() == '1\n'
|
|
||||||
t('$if not 0: 1')() == '1\n'
|
|
||||||
t('$if 1:\n $if 1: 1')() == '1\n'
|
|
||||||
t('$ a = 1\n$a')() == '1\n'
|
|
||||||
t('$ a = 1.\n$a')() == '1.0\n'
|
|
||||||
t('$({1: 1}.keys()[0])')() == '1\n'
|
|
||||||
t('$for x in [1, 2, 3]:\n\t$x')() == ' 1\n 2\n 3\n'
|
|
||||||
t('$def with (a)\n$:a')(1) == '1\n'
|
|
||||||
t('$def with (a)\n$a')(u'\u203d') == u'\xe2\x80\xbd\n'
|
|
||||||
t(u'$def with (f)\n$:f("x")')(lambda x: x) == 'x\n'
|
|
||||||
|
|
||||||
j = Template("$var foo: bar")()
|
|
||||||
assertEqual(str(j), '')
|
|
||||||
assertEqual(j.foo, 'bar\n')
|
|
||||||
if verbose: sys.stderr.write('\n')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
test()
|
|
||||||
@ -1,787 +0,0 @@
|
|||||||
"""
|
|
||||||
General Utilities
|
|
||||||
(part of web.py)
|
|
||||||
"""
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"Storage", "storage", "storify",
|
|
||||||
"iters",
|
|
||||||
"rstrips", "lstrips", "strips", "utf8",
|
|
||||||
"TimeoutError", "timelimit",
|
|
||||||
"Memoize", "memoize",
|
|
||||||
"re_compile", "re_subm",
|
|
||||||
"group",
|
|
||||||
"IterBetter", "iterbetter",
|
|
||||||
"dictreverse", "dictfind", "dictfindall", "dictincr", "dictadd",
|
|
||||||
"listget", "intget", "datestr",
|
|
||||||
"numify", "denumify", "dateify",
|
|
||||||
"CaptureStdout", "capturestdout", "Profile", "profile",
|
|
||||||
"tryall",
|
|
||||||
"ThreadedDict",
|
|
||||||
"autoassign",
|
|
||||||
"to36",
|
|
||||||
"safemarkdown"
|
|
||||||
]
|
|
||||||
|
|
||||||
import re, sys, time, threading
|
|
||||||
try: import datetime
|
|
||||||
except ImportError: pass
|
|
||||||
|
|
||||||
class Storage(dict):
|
|
||||||
"""
|
|
||||||
A Storage object is like a dictionary except `obj.foo` can be used
|
|
||||||
in addition to `obj['foo']`.
|
|
||||||
|
|
||||||
>>> o = storage(a=1)
|
|
||||||
>>> o.a
|
|
||||||
1
|
|
||||||
>>> o['a']
|
|
||||||
1
|
|
||||||
>>> o.a = 2
|
|
||||||
>>> o['a']
|
|
||||||
2
|
|
||||||
>>> del o.a
|
|
||||||
>>> o.a
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
AttributeError: 'a'
|
|
||||||
|
|
||||||
"""
|
|
||||||
def __getattr__(self, key):
|
|
||||||
try:
|
|
||||||
return self[key]
|
|
||||||
except KeyError, k:
|
|
||||||
raise AttributeError, k
|
|
||||||
|
|
||||||
def __setattr__(self, key, value):
|
|
||||||
self[key] = value
|
|
||||||
|
|
||||||
def __delattr__(self, key):
|
|
||||||
try:
|
|
||||||
del self[key]
|
|
||||||
except KeyError, k:
|
|
||||||
raise AttributeError, k
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<Storage ' + dict.__repr__(self) + '>'
|
|
||||||
|
|
||||||
storage = Storage
|
|
||||||
|
|
||||||
def storify(mapping, *requireds, **defaults):
|
|
||||||
"""
|
|
||||||
Creates a `storage` object from dictionary `mapping`, raising `KeyError` if
|
|
||||||
d doesn't have all of the keys in `requireds` and using the default
|
|
||||||
values for keys found in `defaults`.
|
|
||||||
|
|
||||||
For example, `storify({'a':1, 'c':3}, b=2, c=0)` will return the equivalent of
|
|
||||||
`storage({'a':1, 'b':2, 'c':3})`.
|
|
||||||
|
|
||||||
If a `storify` value is a list (e.g. multiple values in a form submission),
|
|
||||||
`storify` returns the last element of the list, unless the key appears in
|
|
||||||
`defaults` as a list. Thus:
|
|
||||||
|
|
||||||
>>> storify({'a':[1, 2]}).a
|
|
||||||
2
|
|
||||||
>>> storify({'a':[1, 2]}, a=[]).a
|
|
||||||
[1, 2]
|
|
||||||
>>> storify({'a':1}, a=[]).a
|
|
||||||
[1]
|
|
||||||
>>> storify({}, a=[]).a
|
|
||||||
[]
|
|
||||||
|
|
||||||
Similarly, if the value has a `value` attribute, `storify will return _its_
|
|
||||||
value, unless the key appears in `defaults` as a dictionary.
|
|
||||||
|
|
||||||
>>> storify({'a':storage(value=1)}).a
|
|
||||||
1
|
|
||||||
>>> storify({'a':storage(value=1)}, a={}).a
|
|
||||||
<Storage {'value': 1}>
|
|
||||||
>>> storify({}, a={}).a
|
|
||||||
{}
|
|
||||||
|
|
||||||
"""
|
|
||||||
def getvalue(x):
|
|
||||||
if hasattr(x, 'value'):
|
|
||||||
return x.value
|
|
||||||
else:
|
|
||||||
return x
|
|
||||||
|
|
||||||
stor = Storage()
|
|
||||||
for key in requireds + tuple(mapping.keys()):
|
|
||||||
value = mapping[key]
|
|
||||||
if isinstance(value, list):
|
|
||||||
if isinstance(defaults.get(key), list):
|
|
||||||
value = [getvalue(x) for x in value]
|
|
||||||
else:
|
|
||||||
value = value[-1]
|
|
||||||
if not isinstance(defaults.get(key), dict):
|
|
||||||
value = getvalue(value)
|
|
||||||
if isinstance(defaults.get(key), list) and not isinstance(value, list):
|
|
||||||
value = [value]
|
|
||||||
setattr(stor, key, value)
|
|
||||||
|
|
||||||
for (key, value) in defaults.iteritems():
|
|
||||||
result = value
|
|
||||||
if hasattr(stor, key):
|
|
||||||
result = stor[key]
|
|
||||||
if value == () and not isinstance(result, tuple):
|
|
||||||
result = (result,)
|
|
||||||
setattr(stor, key, result)
|
|
||||||
|
|
||||||
return stor
|
|
||||||
|
|
||||||
iters = [list, tuple]
|
|
||||||
import __builtin__
|
|
||||||
if hasattr(__builtin__, 'set'):
|
|
||||||
iters.append(set)
|
|
||||||
try:
|
|
||||||
from sets import Set
|
|
||||||
iters.append(Set)
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
class _hack(tuple): pass
|
|
||||||
iters = _hack(iters)
|
|
||||||
iters.__doc__ = """
|
|
||||||
A list of iterable items (like lists, but not strings). Includes whichever
|
|
||||||
of lists, tuples, sets, and Sets are available in this version of Python.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _strips(direction, text, remove):
|
|
||||||
if direction == 'l':
|
|
||||||
if text.startswith(remove):
|
|
||||||
return text[len(remove):]
|
|
||||||
elif direction == 'r':
|
|
||||||
if text.endswith(remove):
|
|
||||||
return text[:-len(remove)]
|
|
||||||
else:
|
|
||||||
raise ValueError, "Direction needs to be r or l."
|
|
||||||
return text
|
|
||||||
|
|
||||||
def rstrips(text, remove):
|
|
||||||
"""
|
|
||||||
removes the string `remove` from the right of `text`
|
|
||||||
|
|
||||||
>>> rstrips("foobar", "bar")
|
|
||||||
'foo'
|
|
||||||
|
|
||||||
"""
|
|
||||||
return _strips('r', text, remove)
|
|
||||||
|
|
||||||
def lstrips(text, remove):
|
|
||||||
"""
|
|
||||||
removes the string `remove` from the left of `text`
|
|
||||||
|
|
||||||
>>> lstrips("foobar", "foo")
|
|
||||||
'bar'
|
|
||||||
|
|
||||||
"""
|
|
||||||
return _strips('l', text, remove)
|
|
||||||
|
|
||||||
def strips(text, remove):
|
|
||||||
"""removes the string `remove` from the both sides of `text`
|
|
||||||
|
|
||||||
>>> strips("foobarfoo", "foo")
|
|
||||||
'bar'
|
|
||||||
|
|
||||||
"""
|
|
||||||
return rstrips(lstrips(text, remove), remove)
|
|
||||||
|
|
||||||
def utf8(text):
|
|
||||||
"""Encodes text in utf-8."""
|
|
||||||
if isinstance(text, unicode):
|
|
||||||
return text.encode('utf-8')
|
|
||||||
elif isinstance(text, str):
|
|
||||||
return text
|
|
||||||
else:
|
|
||||||
return str(text)
|
|
||||||
|
|
||||||
class TimeoutError(Exception): pass
|
|
||||||
def timelimit(timeout):
|
|
||||||
"""
|
|
||||||
A decorator to limit a function to `timeout` seconds, raising `TimeoutError`
|
|
||||||
if it takes longer.
|
|
||||||
|
|
||||||
>>> import time
|
|
||||||
>>> def meaningoflife():
|
|
||||||
... time.sleep(.2)
|
|
||||||
... return 42
|
|
||||||
>>>
|
|
||||||
>>> timelimit(.1)(meaningoflife)()
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
TimeoutError: took too long
|
|
||||||
>>> timelimit(1)(meaningoflife)()
|
|
||||||
42
|
|
||||||
|
|
||||||
_Caveat:_ The function isn't stopped after `timeout` seconds but continues
|
|
||||||
executing in a separate thread. (There seems to be no way to kill a thread.)
|
|
||||||
|
|
||||||
inspired by <http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/473878>
|
|
||||||
"""
|
|
||||||
def _1(function):
|
|
||||||
def _2(*args, **kw):
|
|
||||||
class Dispatch(threading.Thread):
|
|
||||||
def __init__(self):
|
|
||||||
threading.Thread.__init__(self)
|
|
||||||
self.result = None
|
|
||||||
self.error = None
|
|
||||||
|
|
||||||
self.setDaemon(True)
|
|
||||||
self.start()
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
try:
|
|
||||||
self.result = function(*args, **kw)
|
|
||||||
except:
|
|
||||||
self.error = sys.exc_info()
|
|
||||||
|
|
||||||
c = Dispatch()
|
|
||||||
c.join(timeout)
|
|
||||||
if c.isAlive():
|
|
||||||
raise TimeoutError, 'took too long'
|
|
||||||
if c.error:
|
|
||||||
raise c.error[0], c.error[1]
|
|
||||||
return c.result
|
|
||||||
return _2
|
|
||||||
return _1
|
|
||||||
|
|
||||||
class Memoize:
|
|
||||||
"""
|
|
||||||
'Memoizes' a function, caching its return values for each input.
|
|
||||||
|
|
||||||
>>> import time
|
|
||||||
>>> def meaningoflife():
|
|
||||||
... time.sleep(.2)
|
|
||||||
... return 42
|
|
||||||
>>> fastlife = memoize(meaningoflife)
|
|
||||||
>>> meaningoflife()
|
|
||||||
42
|
|
||||||
>>> timelimit(.1)(meaningoflife)()
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
TimeoutError: took too long
|
|
||||||
>>> fastlife()
|
|
||||||
42
|
|
||||||
>>> timelimit(.1)(fastlife)()
|
|
||||||
42
|
|
||||||
|
|
||||||
"""
|
|
||||||
def __init__(self, func):
|
|
||||||
self.func = func
|
|
||||||
self.cache = {}
|
|
||||||
def __call__(self, *args, **keywords):
|
|
||||||
key = (args, tuple(keywords.items()))
|
|
||||||
if key not in self.cache:
|
|
||||||
self.cache[key] = self.func(*args, **keywords)
|
|
||||||
return self.cache[key]
|
|
||||||
|
|
||||||
memoize = Memoize
|
|
||||||
|
|
||||||
re_compile = memoize(re.compile) #@@ threadsafe?
|
|
||||||
re_compile.__doc__ = """
|
|
||||||
A memoized version of re.compile.
|
|
||||||
"""
|
|
||||||
|
|
||||||
class _re_subm_proxy:
|
|
||||||
def __init__(self):
|
|
||||||
self.match = None
|
|
||||||
def __call__(self, match):
|
|
||||||
self.match = match
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def re_subm(pat, repl, string):
|
|
||||||
"""
|
|
||||||
Like re.sub, but returns the replacement _and_ the match object.
|
|
||||||
|
|
||||||
>>> t, m = re_subm('g(oo+)fball', r'f\\1lish', 'goooooofball')
|
|
||||||
>>> t
|
|
||||||
'foooooolish'
|
|
||||||
>>> m.groups()
|
|
||||||
('oooooo',)
|
|
||||||
"""
|
|
||||||
compiled_pat = re_compile(pat)
|
|
||||||
proxy = _re_subm_proxy()
|
|
||||||
compiled_pat.sub(proxy.__call__, string)
|
|
||||||
return compiled_pat.sub(repl, string), proxy.match
|
|
||||||
|
|
||||||
def group(seq, size):
|
|
||||||
"""
|
|
||||||
Returns an iterator over a series of lists of length size from iterable.
|
|
||||||
|
|
||||||
>>> list(group([1,2,3,4], 2))
|
|
||||||
[[1, 2], [3, 4]]
|
|
||||||
"""
|
|
||||||
if not hasattr(seq, 'next'):
|
|
||||||
seq = iter(seq)
|
|
||||||
while True:
|
|
||||||
yield [seq.next() for i in xrange(size)]
|
|
||||||
|
|
||||||
class IterBetter:
|
|
||||||
"""
|
|
||||||
Returns an object that can be used as an iterator
|
|
||||||
but can also be used via __getitem__ (although it
|
|
||||||
cannot go backwards -- that is, you cannot request
|
|
||||||
`iterbetter[0]` after requesting `iterbetter[1]`).
|
|
||||||
|
|
||||||
>>> import itertools
|
|
||||||
>>> c = iterbetter(itertools.count())
|
|
||||||
>>> c[1]
|
|
||||||
1
|
|
||||||
>>> c[5]
|
|
||||||
5
|
|
||||||
>>> c[3]
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
IndexError: already passed 3
|
|
||||||
"""
|
|
||||||
def __init__(self, iterator):
|
|
||||||
self.i, self.c = iterator, 0
|
|
||||||
def __iter__(self):
|
|
||||||
while 1:
|
|
||||||
yield self.i.next()
|
|
||||||
self.c += 1
|
|
||||||
def __getitem__(self, i):
|
|
||||||
#todo: slices
|
|
||||||
if i < self.c:
|
|
||||||
raise IndexError, "already passed "+str(i)
|
|
||||||
try:
|
|
||||||
while i > self.c:
|
|
||||||
self.i.next()
|
|
||||||
self.c += 1
|
|
||||||
# now self.c == i
|
|
||||||
self.c += 1
|
|
||||||
return self.i.next()
|
|
||||||
except StopIteration:
|
|
||||||
raise IndexError, str(i)
|
|
||||||
iterbetter = IterBetter
|
|
||||||
|
|
||||||
def dictreverse(mapping):
|
|
||||||
"""
|
|
||||||
>>> dictreverse({1: 2, 3: 4})
|
|
||||||
{2: 1, 4: 3}
|
|
||||||
"""
|
|
||||||
return dict([(value, key) for (key, value) in mapping.iteritems()])
|
|
||||||
|
|
||||||
def dictfind(dictionary, element):
|
|
||||||
"""
|
|
||||||
Returns a key whose value in `dictionary` is `element`
|
|
||||||
or, if none exists, None.
|
|
||||||
|
|
||||||
>>> d = {1:2, 3:4}
|
|
||||||
>>> dictfind(d, 4)
|
|
||||||
3
|
|
||||||
>>> dictfind(d, 5)
|
|
||||||
"""
|
|
||||||
for (key, value) in dictionary.iteritems():
|
|
||||||
if element is value:
|
|
||||||
return key
|
|
||||||
|
|
||||||
def dictfindall(dictionary, element):
|
|
||||||
"""
|
|
||||||
Returns the keys whose values in `dictionary` are `element`
|
|
||||||
or, if none exists, [].
|
|
||||||
|
|
||||||
>>> d = {1:4, 3:4}
|
|
||||||
>>> dictfindall(d, 4)
|
|
||||||
[1, 3]
|
|
||||||
>>> dictfindall(d, 5)
|
|
||||||
[]
|
|
||||||
"""
|
|
||||||
res = []
|
|
||||||
for (key, value) in dictionary.iteritems():
|
|
||||||
if element is value:
|
|
||||||
res.append(key)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def dictincr(dictionary, element):
|
|
||||||
"""
|
|
||||||
Increments `element` in `dictionary`,
|
|
||||||
setting it to one if it doesn't exist.
|
|
||||||
|
|
||||||
>>> d = {1:2, 3:4}
|
|
||||||
>>> dictincr(d, 1)
|
|
||||||
3
|
|
||||||
>>> d[1]
|
|
||||||
3
|
|
||||||
>>> dictincr(d, 5)
|
|
||||||
1
|
|
||||||
>>> d[5]
|
|
||||||
1
|
|
||||||
"""
|
|
||||||
dictionary.setdefault(element, 0)
|
|
||||||
dictionary[element] += 1
|
|
||||||
return dictionary[element]
|
|
||||||
|
|
||||||
def dictadd(*dicts):
|
|
||||||
"""
|
|
||||||
Returns a dictionary consisting of the keys in the argument dictionaries.
|
|
||||||
If they share a key, the value from the last argument is used.
|
|
||||||
|
|
||||||
>>> dictadd({1: 0, 2: 0}, {2: 1, 3: 1})
|
|
||||||
{1: 0, 2: 1, 3: 1}
|
|
||||||
"""
|
|
||||||
result = {}
|
|
||||||
for dct in dicts:
|
|
||||||
result.update(dct)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def listget(lst, ind, default=None):
|
|
||||||
"""
|
|
||||||
Returns `lst[ind]` if it exists, `default` otherwise.
|
|
||||||
|
|
||||||
>>> listget(['a'], 0)
|
|
||||||
'a'
|
|
||||||
>>> listget(['a'], 1)
|
|
||||||
>>> listget(['a'], 1, 'b')
|
|
||||||
'b'
|
|
||||||
"""
|
|
||||||
if len(lst)-1 < ind:
|
|
||||||
return default
|
|
||||||
return lst[ind]
|
|
||||||
|
|
||||||
def intget(integer, default=None):
|
|
||||||
"""
|
|
||||||
Returns `integer` as an int or `default` if it can't.
|
|
||||||
|
|
||||||
>>> intget('3')
|
|
||||||
3
|
|
||||||
>>> intget('3a')
|
|
||||||
>>> intget('3a', 0)
|
|
||||||
0
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return int(integer)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
return default
|
|
||||||
|
|
||||||
def datestr(then, now=None):
|
|
||||||
"""
|
|
||||||
Converts a (UTC) datetime object to a nice string representation.
|
|
||||||
|
|
||||||
>>> from datetime import datetime, timedelta
|
|
||||||
>>> d = datetime(1970, 5, 1)
|
|
||||||
>>> datestr(d, now=d)
|
|
||||||
'0 microseconds ago'
|
|
||||||
>>> for t, v in {
|
|
||||||
... timedelta(microseconds=1): '1 microsecond ago',
|
|
||||||
... timedelta(microseconds=2): '2 microseconds ago',
|
|
||||||
... -timedelta(microseconds=1): '1 microsecond from now',
|
|
||||||
... -timedelta(microseconds=2): '2 microseconds from now',
|
|
||||||
... timedelta(microseconds=2000): '2 milliseconds ago',
|
|
||||||
... timedelta(seconds=2): '2 seconds ago',
|
|
||||||
... timedelta(seconds=2*60): '2 minutes ago',
|
|
||||||
... timedelta(seconds=2*60*60): '2 hours ago',
|
|
||||||
... timedelta(days=2): '2 days ago',
|
|
||||||
... }.iteritems():
|
|
||||||
... assert datestr(d, now=d+t) == v
|
|
||||||
>>> datestr(datetime(1970, 1, 1), now=d)
|
|
||||||
'January 1'
|
|
||||||
>>> datestr(datetime(1969, 1, 1), now=d)
|
|
||||||
'January 1, 1969'
|
|
||||||
>>> datestr(datetime(1970, 6, 1), now=d)
|
|
||||||
'June 1, 1970'
|
|
||||||
"""
|
|
||||||
def agohence(n, what, divisor=None):
|
|
||||||
if divisor: n = n // divisor
|
|
||||||
|
|
||||||
out = str(abs(n)) + ' ' + what # '2 day'
|
|
||||||
if abs(n) != 1: out += 's' # '2 days'
|
|
||||||
out += ' ' # '2 days '
|
|
||||||
if n < 0:
|
|
||||||
out += 'from now'
|
|
||||||
else:
|
|
||||||
out += 'ago'
|
|
||||||
return out # '2 days ago'
|
|
||||||
|
|
||||||
oneday = 24 * 60 * 60
|
|
||||||
|
|
||||||
if not now: now = datetime.datetime.utcnow()
|
|
||||||
if type(now).__name__ == "DateTime":
|
|
||||||
now = datetime.datetime.fromtimestamp(now)
|
|
||||||
if type(then).__name__ == "DateTime":
|
|
||||||
then = datetime.datetime.fromtimestamp(then)
|
|
||||||
delta = now - then
|
|
||||||
deltaseconds = int(delta.days * oneday + delta.seconds + delta.microseconds * 1e-06)
|
|
||||||
deltadays = abs(deltaseconds) // oneday
|
|
||||||
if deltaseconds < 0: deltadays *= -1 # fix for oddity of floor
|
|
||||||
|
|
||||||
if deltadays:
|
|
||||||
if abs(deltadays) < 4:
|
|
||||||
return agohence(deltadays, 'day')
|
|
||||||
|
|
||||||
out = then.strftime('%B %e') # e.g. 'June 13'
|
|
||||||
if then.year != now.year or deltadays < 0:
|
|
||||||
out += ', %s' % then.year
|
|
||||||
return out
|
|
||||||
|
|
||||||
if int(deltaseconds):
|
|
||||||
if abs(deltaseconds) > (60 * 60):
|
|
||||||
return agohence(deltaseconds, 'hour', 60 * 60)
|
|
||||||
elif abs(deltaseconds) > 60:
|
|
||||||
return agohence(deltaseconds, 'minute', 60)
|
|
||||||
else:
|
|
||||||
return agohence(deltaseconds, 'second')
|
|
||||||
|
|
||||||
deltamicroseconds = delta.microseconds
|
|
||||||
if delta.days: deltamicroseconds = int(delta.microseconds - 1e6) # datetime oddity
|
|
||||||
if abs(deltamicroseconds) > 1000:
|
|
||||||
return agohence(deltamicroseconds, 'millisecond', 1000)
|
|
||||||
|
|
||||||
return agohence(deltamicroseconds, 'microsecond')
|
|
||||||
|
|
||||||
def numify(string):
|
|
||||||
"""
|
|
||||||
Removes all non-digit characters from `string`.
|
|
||||||
|
|
||||||
>>> numify('800-555-1212')
|
|
||||||
'8005551212'
|
|
||||||
>>> numify('800.555.1212')
|
|
||||||
'8005551212'
|
|
||||||
|
|
||||||
"""
|
|
||||||
return ''.join([c for c in str(string) if c.isdigit()])
|
|
||||||
|
|
||||||
def denumify(string, pattern):
|
|
||||||
"""
|
|
||||||
Formats `string` according to `pattern`, where the letter X gets replaced
|
|
||||||
by characters from `string`.
|
|
||||||
|
|
||||||
>>> denumify("8005551212", "(XXX) XXX-XXXX")
|
|
||||||
'(800) 555-1212'
|
|
||||||
|
|
||||||
"""
|
|
||||||
out = []
|
|
||||||
for c in pattern:
|
|
||||||
if c == "X":
|
|
||||||
out.append(string[0])
|
|
||||||
string = string[1:]
|
|
||||||
else:
|
|
||||||
out.append(c)
|
|
||||||
return ''.join(out)
|
|
||||||
|
|
||||||
def dateify(datestring):
|
|
||||||
"""
|
|
||||||
Formats a numified `datestring` properly.
|
|
||||||
"""
|
|
||||||
return denumify(datestring, "XXXX-XX-XX XX:XX:XX")
|
|
||||||
|
|
||||||
class CaptureStdout:
|
|
||||||
"""
|
|
||||||
Captures everything `func` prints to stdout and returns it instead.
|
|
||||||
|
|
||||||
>>> def idiot():
|
|
||||||
... print "foo"
|
|
||||||
>>> capturestdout(idiot)()
|
|
||||||
'foo\\n'
|
|
||||||
|
|
||||||
**WARNING:** Not threadsafe!
|
|
||||||
"""
|
|
||||||
def __init__(self, func):
|
|
||||||
self.func = func
|
|
||||||
def __call__(self, *args, **keywords):
|
|
||||||
from cStringIO import StringIO
|
|
||||||
# Not threadsafe!
|
|
||||||
out = StringIO()
|
|
||||||
oldstdout = sys.stdout
|
|
||||||
sys.stdout = out
|
|
||||||
try:
|
|
||||||
self.func(*args, **keywords)
|
|
||||||
finally:
|
|
||||||
sys.stdout = oldstdout
|
|
||||||
return out.getvalue()
|
|
||||||
|
|
||||||
capturestdout = CaptureStdout
|
|
||||||
|
|
||||||
class Profile:
|
|
||||||
"""
|
|
||||||
Profiles `func` and returns a tuple containing its output
|
|
||||||
and a string with human-readable profiling information.
|
|
||||||
|
|
||||||
>>> import time
|
|
||||||
>>> out, inf = profile(time.sleep)(.001)
|
|
||||||
>>> out
|
|
||||||
>>> inf[:10].strip()
|
|
||||||
'took 0.0'
|
|
||||||
"""
|
|
||||||
def __init__(self, func):
|
|
||||||
self.func = func
|
|
||||||
def __call__(self, *args): ##, **kw): kw unused
|
|
||||||
import hotshot, hotshot.stats, tempfile ##, time already imported
|
|
||||||
temp = tempfile.NamedTemporaryFile()
|
|
||||||
prof = hotshot.Profile(temp.name)
|
|
||||||
|
|
||||||
stime = time.time()
|
|
||||||
result = prof.runcall(self.func, *args)
|
|
||||||
stime = time.time() - stime
|
|
||||||
|
|
||||||
prof.close()
|
|
||||||
stats = hotshot.stats.load(temp.name)
|
|
||||||
stats.strip_dirs()
|
|
||||||
stats.sort_stats('time', 'calls')
|
|
||||||
x = '\n\ntook '+ str(stime) + ' seconds\n'
|
|
||||||
x += capturestdout(stats.print_stats)(40)
|
|
||||||
x += capturestdout(stats.print_callers)()
|
|
||||||
return result, x
|
|
||||||
|
|
||||||
profile = Profile
|
|
||||||
|
|
||||||
|
|
||||||
import traceback
|
|
||||||
# hack for compatibility with Python 2.3:
|
|
||||||
if not hasattr(traceback, 'format_exc'):
|
|
||||||
from cStringIO import StringIO
|
|
||||||
def format_exc(limit=None):
|
|
||||||
strbuf = StringIO()
|
|
||||||
traceback.print_exc(limit, strbuf)
|
|
||||||
return strbuf.getvalue()
|
|
||||||
traceback.format_exc = format_exc
|
|
||||||
|
|
||||||
def tryall(context, prefix=None):
|
|
||||||
"""
|
|
||||||
Tries a series of functions and prints their results.
|
|
||||||
`context` is a dictionary mapping names to values;
|
|
||||||
the value will only be tried if it's callable.
|
|
||||||
|
|
||||||
>>> tryall(dict(j=lambda: True))
|
|
||||||
j: True
|
|
||||||
----------------------------------------
|
|
||||||
results:
|
|
||||||
True: 1
|
|
||||||
|
|
||||||
For example, you might have a file `test/stuff.py`
|
|
||||||
with a series of functions testing various things in it.
|
|
||||||
At the bottom, have a line:
|
|
||||||
|
|
||||||
if __name__ == "__main__": tryall(globals())
|
|
||||||
|
|
||||||
Then you can run `python test/stuff.py` and get the results of
|
|
||||||
all the tests.
|
|
||||||
"""
|
|
||||||
context = context.copy() # vars() would update
|
|
||||||
results = {}
|
|
||||||
for (key, value) in context.iteritems():
|
|
||||||
if not hasattr(value, '__call__'):
|
|
||||||
continue
|
|
||||||
if prefix and not key.startswith(prefix):
|
|
||||||
continue
|
|
||||||
print key + ':',
|
|
||||||
try:
|
|
||||||
r = value()
|
|
||||||
dictincr(results, r)
|
|
||||||
print r
|
|
||||||
except:
|
|
||||||
print 'ERROR'
|
|
||||||
dictincr(results, 'ERROR')
|
|
||||||
print ' ' + '\n '.join(traceback.format_exc().split('\n'))
|
|
||||||
|
|
||||||
print '-'*40
|
|
||||||
print 'results:'
|
|
||||||
for (key, value) in results.iteritems():
|
|
||||||
print ' '*2, str(key)+':', value
|
|
||||||
|
|
||||||
class ThreadedDict:
|
|
||||||
"""
|
|
||||||
Takes a dictionary that maps threads to objects.
|
|
||||||
When a thread tries to get or set an attribute or item
|
|
||||||
of the threadeddict, it passes it on to the object
|
|
||||||
for that thread in dictionary.
|
|
||||||
"""
|
|
||||||
def __init__(self, dictionary):
|
|
||||||
self.__dict__['_ThreadedDict__d'] = dictionary
|
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
return getattr(self.__d[threading.currentThread()], attr)
|
|
||||||
|
|
||||||
def __getitem__(self, item):
|
|
||||||
return self.__d[threading.currentThread()][item]
|
|
||||||
|
|
||||||
def __setattr__(self, attr, value):
|
|
||||||
if attr == '__doc__':
|
|
||||||
self.__dict__[attr] = value
|
|
||||||
else:
|
|
||||||
return setattr(self.__d[threading.currentThread()], attr, value)
|
|
||||||
|
|
||||||
def __delattr__(self, item):
|
|
||||||
try:
|
|
||||||
del self.__d[threading.currentThread()][item]
|
|
||||||
except KeyError, k:
|
|
||||||
raise AttributeError, k
|
|
||||||
|
|
||||||
def __delitem__(self, item):
|
|
||||||
del self.__d[threading.currentThread()][item]
|
|
||||||
|
|
||||||
def __setitem__(self, item, value):
|
|
||||||
self.__d[threading.currentThread()][item] = value
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash(self.__d[threading.currentThread()])
|
|
||||||
|
|
||||||
threadeddict = ThreadedDict
|
|
||||||
|
|
||||||
def autoassign(self, locals):
|
|
||||||
"""
|
|
||||||
Automatically assigns local variables to `self`.
|
|
||||||
|
|
||||||
>>> self = storage()
|
|
||||||
>>> autoassign(self, dict(a=1, b=2))
|
|
||||||
>>> self
|
|
||||||
<Storage {'a': 1, 'b': 2}>
|
|
||||||
|
|
||||||
Generally used in `__init__` methods, as in:
|
|
||||||
|
|
||||||
def __init__(self, foo, bar, baz=1): autoassign(self, locals())
|
|
||||||
"""
|
|
||||||
for (key, value) in locals.iteritems():
|
|
||||||
if key == 'self':
|
|
||||||
continue
|
|
||||||
setattr(self, key, value)
|
|
||||||
|
|
||||||
def to36(q):
|
|
||||||
"""
|
|
||||||
Converts an integer to base 36 (a useful scheme for human-sayable IDs).
|
|
||||||
|
|
||||||
>>> to36(35)
|
|
||||||
'z'
|
|
||||||
>>> to36(119292)
|
|
||||||
'2k1o'
|
|
||||||
>>> int(to36(939387374), 36)
|
|
||||||
939387374
|
|
||||||
>>> to36(0)
|
|
||||||
'0'
|
|
||||||
>>> to36(-393)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: must supply a positive integer
|
|
||||||
|
|
||||||
"""
|
|
||||||
if q < 0: raise ValueError, "must supply a positive integer"
|
|
||||||
letters = "0123456789abcdefghijklmnopqrstuvwxyz"
|
|
||||||
converted = []
|
|
||||||
while q != 0:
|
|
||||||
q, r = divmod(q, 36)
|
|
||||||
converted.insert(0, letters[r])
|
|
||||||
return "".join(converted) or '0'
|
|
||||||
|
|
||||||
|
|
||||||
r_url = re_compile('(?<!\()(http://(\S+))')
|
|
||||||
def safemarkdown(text):
|
|
||||||
"""
|
|
||||||
Converts text to HTML following the rules of Markdown, but blocking any
|
|
||||||
outside HTML input, so that only the things supported by Markdown
|
|
||||||
can be used. Also converts raw URLs to links.
|
|
||||||
|
|
||||||
(requires [markdown.py](http://webpy.org/markdown.py))
|
|
||||||
"""
|
|
||||||
from markdown import markdown
|
|
||||||
if text:
|
|
||||||
text = text.replace('<', '<')
|
|
||||||
# TODO: automatically get page title?
|
|
||||||
text = r_url.sub(r'<\1>', text)
|
|
||||||
text = markdown(text)
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
doctest.testmod()
|
|
||||||
@ -1,375 +0,0 @@
|
|||||||
"""
|
|
||||||
Web API (wrapper around WSGI)
|
|
||||||
(from web.py)
|
|
||||||
"""
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"config",
|
|
||||||
"badrequest", "notfound", "gone", "internalerror",
|
|
||||||
"header", "output", "flush", "debug",
|
|
||||||
"input", "data",
|
|
||||||
"setcookie", "cookies",
|
|
||||||
"ctx",
|
|
||||||
"loadhooks", "load", "unloadhooks", "unload", "_loadhooks",
|
|
||||||
"wsgifunc"
|
|
||||||
]
|
|
||||||
|
|
||||||
import sys, os, cgi, threading, Cookie, pprint, traceback
|
|
||||||
try: import itertools
|
|
||||||
except ImportError: pass
|
|
||||||
from utils import storage, storify, threadeddict, dictadd, intget, lstrips, utf8
|
|
||||||
|
|
||||||
config = storage()
|
|
||||||
config.__doc__ = """
|
|
||||||
A configuration object for various aspects of web.py.
|
|
||||||
|
|
||||||
`db_parameters`
|
|
||||||
: A dictionary containing the parameters to be passed to `connect`
|
|
||||||
when `load()` is called.
|
|
||||||
`db_printing`
|
|
||||||
: Set to `True` if you would like SQL queries and timings to be
|
|
||||||
printed to the debug output.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def badrequest():
|
|
||||||
"""Return a `400 Bad Request` error."""
|
|
||||||
ctx.status = '400 Bad Request'
|
|
||||||
header('Content-Type', 'text/html')
|
|
||||||
return output('bad request')
|
|
||||||
|
|
||||||
def notfound():
|
|
||||||
"""Returns a `404 Not Found` error."""
|
|
||||||
ctx.status = '404 Not Found'
|
|
||||||
header('Content-Type', 'text/html')
|
|
||||||
return output('not found')
|
|
||||||
|
|
||||||
def gone():
|
|
||||||
"""Returns a `410 Gone` error."""
|
|
||||||
ctx.status = '410 Gone'
|
|
||||||
header('Content-Type', 'text/html')
|
|
||||||
return output("gone")
|
|
||||||
|
|
||||||
def internalerror():
|
|
||||||
"""Returns a `500 Internal Server` error."""
|
|
||||||
ctx.status = "500 Internal Server Error"
|
|
||||||
ctx.headers = [('Content-Type', 'text/html')]
|
|
||||||
ctx.output = "internal server error"
|
|
||||||
|
|
||||||
def header(hdr, value, unique=False):
|
|
||||||
"""
|
|
||||||
Adds the header `hdr: value` with the response.
|
|
||||||
|
|
||||||
If `unique` is True and a header with that name already exists,
|
|
||||||
it doesn't add a new one.
|
|
||||||
"""
|
|
||||||
hdr, value = utf8(hdr), utf8(value)
|
|
||||||
# protection against HTTP response splitting attack
|
|
||||||
if '\n' in hdr or '\r' in hdr or '\n' in value or '\r' in value:
|
|
||||||
raise ValueError, 'invalid characters in header'
|
|
||||||
|
|
||||||
if unique is True:
|
|
||||||
for h, v in ctx.headers:
|
|
||||||
if h.lower() == hdr.lower(): return
|
|
||||||
|
|
||||||
ctx.headers.append((hdr, value))
|
|
||||||
|
|
||||||
def output(string_):
|
|
||||||
"""Appends `string_` to the response."""
|
|
||||||
if isinstance(string_, unicode): string_ = string_.encode('utf8')
|
|
||||||
if ctx.get('flush'):
|
|
||||||
ctx._write(string_)
|
|
||||||
else:
|
|
||||||
ctx.output += str(string_)
|
|
||||||
|
|
||||||
def flush():
|
|
||||||
ctx.flush = True
|
|
||||||
return flush
|
|
||||||
|
|
||||||
def input(*requireds, **defaults):
|
|
||||||
"""
|
|
||||||
Returns a `storage` object with the GET and POST arguments.
|
|
||||||
See `storify` for how `requireds` and `defaults` work.
|
|
||||||
"""
|
|
||||||
from cStringIO import StringIO
|
|
||||||
def dictify(fs): return dict([(k, fs[k]) for k in fs.keys()])
|
|
||||||
|
|
||||||
_method = defaults.pop('_method', 'both')
|
|
||||||
|
|
||||||
e = ctx.env.copy()
|
|
||||||
a = b = {}
|
|
||||||
|
|
||||||
if _method.lower() in ['both', 'post']:
|
|
||||||
if e['REQUEST_METHOD'] == 'POST':
|
|
||||||
a = cgi.FieldStorage(fp = StringIO(data()), environ=e,
|
|
||||||
keep_blank_values=1)
|
|
||||||
a = dictify(a)
|
|
||||||
|
|
||||||
if _method.lower() in ['both', 'get']:
|
|
||||||
e['REQUEST_METHOD'] = 'GET'
|
|
||||||
b = dictify(cgi.FieldStorage(environ=e, keep_blank_values=1))
|
|
||||||
|
|
||||||
out = dictadd(b, a)
|
|
||||||
try:
|
|
||||||
return storify(out, *requireds, **defaults)
|
|
||||||
except KeyError:
|
|
||||||
badrequest()
|
|
||||||
raise StopIteration
|
|
||||||
|
|
||||||
def data():
|
|
||||||
"""Returns the data sent with the request."""
|
|
||||||
if 'data' not in ctx:
|
|
||||||
cl = intget(ctx.env.get('CONTENT_LENGTH'), 0)
|
|
||||||
ctx.data = ctx.env['wsgi.input'].read(cl)
|
|
||||||
return ctx.data
|
|
||||||
|
|
||||||
def setcookie(name, value, expires="", domain=None):
|
|
||||||
"""Sets a cookie."""
|
|
||||||
if expires < 0:
|
|
||||||
expires = -1000000000
|
|
||||||
kargs = {'expires': expires, 'path':'/'}
|
|
||||||
if domain:
|
|
||||||
kargs['domain'] = domain
|
|
||||||
# @@ should we limit cookies to a different path?
|
|
||||||
cookie = Cookie.SimpleCookie()
|
|
||||||
cookie[name] = value
|
|
||||||
for key, val in kargs.iteritems():
|
|
||||||
cookie[name][key] = val
|
|
||||||
header('Set-Cookie', cookie.items()[0][1].OutputString())
|
|
||||||
|
|
||||||
def cookies(*requireds, **defaults):
|
|
||||||
"""
|
|
||||||
Returns a `storage` object with all the cookies in it.
|
|
||||||
See `storify` for how `requireds` and `defaults` work.
|
|
||||||
"""
|
|
||||||
cookie = Cookie.SimpleCookie()
|
|
||||||
cookie.load(ctx.env.get('HTTP_COOKIE', ''))
|
|
||||||
try:
|
|
||||||
return storify(cookie, *requireds, **defaults)
|
|
||||||
except KeyError:
|
|
||||||
badrequest()
|
|
||||||
raise StopIteration
|
|
||||||
|
|
||||||
def debug(*args):
|
|
||||||
"""
|
|
||||||
Prints a prettyprinted version of `args` to stderr.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
out = ctx.environ['wsgi.errors']
|
|
||||||
except:
|
|
||||||
out = sys.stderr
|
|
||||||
for arg in args:
|
|
||||||
print >> out, pprint.pformat(arg)
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def _debugwrite(x):
|
|
||||||
try:
|
|
||||||
out = ctx.environ['wsgi.errors']
|
|
||||||
except:
|
|
||||||
out = sys.stderr
|
|
||||||
out.write(x)
|
|
||||||
debug.write = _debugwrite
|
|
||||||
|
|
||||||
class _outputter:
|
|
||||||
"""Wraps `sys.stdout` so that print statements go into the response."""
|
|
||||||
def __init__(self, file): self.file = file
|
|
||||||
def write(self, string_):
|
|
||||||
if hasattr(ctx, 'output'):
|
|
||||||
return output(string_)
|
|
||||||
else:
|
|
||||||
self.file.write(string_)
|
|
||||||
def __getattr__(self, attr): return getattr(self.file, attr)
|
|
||||||
def __getitem__(self, item): return self.file[item]
|
|
||||||
|
|
||||||
def _capturedstdout():
|
|
||||||
sysstd = sys.stdout
|
|
||||||
while hasattr(sysstd, 'file'):
|
|
||||||
if isinstance(sys.stdout, _outputter): return True
|
|
||||||
sysstd = sysstd.file
|
|
||||||
if isinstance(sys.stdout, _outputter): return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
if not _capturedstdout():
|
|
||||||
sys.stdout = _outputter(sys.stdout)
|
|
||||||
|
|
||||||
_context = {threading.currentThread(): storage()}
|
|
||||||
ctx = context = threadeddict(_context)
|
|
||||||
|
|
||||||
ctx.__doc__ = """
|
|
||||||
A `storage` object containing various information about the request:
|
|
||||||
|
|
||||||
`environ` (aka `env`)
|
|
||||||
: A dictionary containing the standard WSGI environment variables.
|
|
||||||
|
|
||||||
`host`
|
|
||||||
: The domain (`Host` header) requested by the user.
|
|
||||||
|
|
||||||
`home`
|
|
||||||
: The base path for the application.
|
|
||||||
|
|
||||||
`ip`
|
|
||||||
: The IP address of the requester.
|
|
||||||
|
|
||||||
`method`
|
|
||||||
: The HTTP method used.
|
|
||||||
|
|
||||||
`path`
|
|
||||||
: The path request.
|
|
||||||
|
|
||||||
`query`
|
|
||||||
: If there are no query arguments, the empty string. Otherwise, a `?` followed
|
|
||||||
by the query string.
|
|
||||||
|
|
||||||
`fullpath`
|
|
||||||
: The full path requested, including query arguments (`== path + query`).
|
|
||||||
|
|
||||||
### Response Data
|
|
||||||
|
|
||||||
`status` (default: "200 OK")
|
|
||||||
: The status code to be used in the response.
|
|
||||||
|
|
||||||
`headers`
|
|
||||||
: A list of 2-tuples to be used in the response.
|
|
||||||
|
|
||||||
`output`
|
|
||||||
: A string to be used as the response.
|
|
||||||
"""
|
|
||||||
|
|
||||||
loadhooks = {}
|
|
||||||
_loadhooks = {}
|
|
||||||
|
|
||||||
def load():
|
|
||||||
"""
|
|
||||||
Loads a new context for the thread.
|
|
||||||
|
|
||||||
You can ask for a function to be run at loadtime by
|
|
||||||
adding it to the dictionary `loadhooks`.
|
|
||||||
"""
|
|
||||||
_context[threading.currentThread()] = storage()
|
|
||||||
ctx.status = '200 OK'
|
|
||||||
ctx.headers = []
|
|
||||||
if config.get('db_parameters'):
|
|
||||||
import db
|
|
||||||
db.connect(**config.db_parameters)
|
|
||||||
|
|
||||||
for x in loadhooks.values(): x()
|
|
||||||
|
|
||||||
def _load(env):
|
|
||||||
load()
|
|
||||||
ctx.output = ''
|
|
||||||
ctx.environ = ctx.env = env
|
|
||||||
ctx.host = env.get('HTTP_HOST')
|
|
||||||
|
|
||||||
#http://groups.google.com/group/webpy/browse_thread/thread/2a4665e54b07c991?pli=1
|
|
||||||
if env.get('HTTPS'):
|
|
||||||
ctx.protocol = "https"
|
|
||||||
else:
|
|
||||||
ctx.protocol = "http"
|
|
||||||
ctx.homedomain = ctx.protocol + "://" + env.get('HTTP_HOST','[unknown]')
|
|
||||||
ctx.homepath = os.environ.get('REAL_SCRIPT_NAME', env.get('SCRIPT_NAME', ''))
|
|
||||||
ctx.home = ctx.homedomain + ctx.homepath
|
|
||||||
ctx.ip = env.get('REMOTE_ADDR')
|
|
||||||
ctx.method = env.get('REQUEST_METHOD')
|
|
||||||
ctx.path = env.get('PATH_INFO')
|
|
||||||
# http://trac.lighttpd.net/trac/ticket/406 requires:
|
|
||||||
if env.get('SERVER_SOFTWARE', '').startswith('lighttpd/'):
|
|
||||||
ctx.path = lstrips(env.get('REQUEST_URI').split('?')[0],
|
|
||||||
os.environ.get('REAL_SCRIPT_NAME', env.get('SCRIPT_NAME', '')))
|
|
||||||
|
|
||||||
if env.get('QUERY_STRING'):
|
|
||||||
ctx.query = '?' + env.get('QUERY_STRING', '')
|
|
||||||
else:
|
|
||||||
ctx.query = ''
|
|
||||||
|
|
||||||
ctx.fullpath = ctx.path + ctx.query
|
|
||||||
for x in _loadhooks.values(): x()
|
|
||||||
|
|
||||||
unloadhooks = {}
|
|
||||||
|
|
||||||
def unload():
|
|
||||||
"""
|
|
||||||
Unloads the context for the thread.
|
|
||||||
|
|
||||||
You can ask for a function to be run at loadtime by
|
|
||||||
adding it ot the dictionary `unloadhooks`.
|
|
||||||
"""
|
|
||||||
for x in unloadhooks.values(): x()
|
|
||||||
# ensures db cursors and such are GCed promptly
|
|
||||||
del _context[threading.currentThread()]
|
|
||||||
|
|
||||||
def _unload():
|
|
||||||
unload()
|
|
||||||
|
|
||||||
def wsgifunc(func, *middleware):
|
|
||||||
"""Returns a WSGI-compatible function from a webpy-function."""
|
|
||||||
middleware = list(middleware)
|
|
||||||
|
|
||||||
def wsgifunc(env, start_resp):
|
|
||||||
_load(env)
|
|
||||||
try:
|
|
||||||
result = func()
|
|
||||||
except StopIteration:
|
|
||||||
result = None
|
|
||||||
except:
|
|
||||||
print >> debug, traceback.format_exc()
|
|
||||||
result = internalerror()
|
|
||||||
|
|
||||||
is_generator = result and hasattr(result, 'next')
|
|
||||||
if is_generator:
|
|
||||||
# wsgi requires the headers first
|
|
||||||
# so we need to do an iteration
|
|
||||||
# and save the result for later
|
|
||||||
try:
|
|
||||||
firstchunk = result.next()
|
|
||||||
except StopIteration:
|
|
||||||
firstchunk = ''
|
|
||||||
|
|
||||||
status, headers, output = ctx.status, ctx.headers, ctx.output
|
|
||||||
ctx._write = start_resp(status, headers)
|
|
||||||
|
|
||||||
# and now, the fun:
|
|
||||||
|
|
||||||
def cleanup():
|
|
||||||
# we insert this little generator
|
|
||||||
# at the end of our itertools.chain
|
|
||||||
# so that it unloads the request
|
|
||||||
# when everything else is done
|
|
||||||
|
|
||||||
yield '' # force it to be a generator
|
|
||||||
_unload()
|
|
||||||
|
|
||||||
# result is the output of calling the webpy function
|
|
||||||
# it could be a generator...
|
|
||||||
|
|
||||||
if is_generator:
|
|
||||||
if firstchunk is flush:
|
|
||||||
# oh, it's just our special flush mode
|
|
||||||
# ctx._write is set up, so just continue execution
|
|
||||||
try:
|
|
||||||
result.next()
|
|
||||||
except StopIteration:
|
|
||||||
pass
|
|
||||||
|
|
||||||
_unload()
|
|
||||||
return []
|
|
||||||
else:
|
|
||||||
return itertools.chain([firstchunk], result, cleanup())
|
|
||||||
|
|
||||||
# ... but it's usually just None
|
|
||||||
#
|
|
||||||
# output is the stuff in ctx.output
|
|
||||||
# it's usually a string...
|
|
||||||
if isinstance(output, str): #@@ other stringlikes?
|
|
||||||
_unload()
|
|
||||||
return [output]
|
|
||||||
# it could be a generator...
|
|
||||||
elif hasattr(output, 'next'):
|
|
||||||
return itertools.chain(output, cleanup())
|
|
||||||
else:
|
|
||||||
_unload()
|
|
||||||
raise Exception, "Invalid ctx.output"
|
|
||||||
|
|
||||||
for mw_func in middleware:
|
|
||||||
wsgifunc = mw_func(wsgifunc)
|
|
||||||
|
|
||||||
return wsgifunc
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
"""
|
|
||||||
WSGI Utilities
|
|
||||||
(from web.py)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os, sys
|
|
||||||
|
|
||||||
import http
|
|
||||||
import webapi as web
|
|
||||||
from utils import listget
|
|
||||||
from net import validaddr, validip
|
|
||||||
import httpserver
|
|
||||||
|
|
||||||
def runfcgi(func, addr=('localhost', 8000)):
|
|
||||||
"""Runs a WSGI function as a FastCGI server."""
|
|
||||||
import flup.server.fcgi as flups
|
|
||||||
return flups.WSGIServer(func, multiplexed=True, bindAddress=addr).run()
|
|
||||||
|
|
||||||
def runscgi(func, addr=('localhost', 4000)):
|
|
||||||
"""Runs a WSGI function as an SCGI server."""
|
|
||||||
import flup.server.scgi as flups
|
|
||||||
return flups.WSGIServer(func, bindAddress=addr).run()
|
|
||||||
|
|
||||||
def runwsgi(func):
|
|
||||||
"""
|
|
||||||
Runs a WSGI-compatible `func` using FCGI, SCGI, or a simple web server,
|
|
||||||
as appropriate based on context and `sys.argv`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if os.environ.has_key('SERVER_SOFTWARE'): # cgi
|
|
||||||
os.environ['FCGI_FORCE_CGI'] = 'Y'
|
|
||||||
|
|
||||||
if (os.environ.has_key('PHP_FCGI_CHILDREN') #lighttpd fastcgi
|
|
||||||
or os.environ.has_key('SERVER_SOFTWARE')):
|
|
||||||
return runfcgi(func, None)
|
|
||||||
|
|
||||||
if 'fcgi' in sys.argv or 'fastcgi' in sys.argv:
|
|
||||||
args = sys.argv[1:]
|
|
||||||
if 'fastcgi' in args: args.remove('fastcgi')
|
|
||||||
elif 'fcgi' in args: args.remove('fcgi')
|
|
||||||
if args:
|
|
||||||
return runfcgi(func, validaddr(args[0]))
|
|
||||||
else:
|
|
||||||
return runfcgi(func, None)
|
|
||||||
|
|
||||||
if 'scgi' in sys.argv:
|
|
||||||
args = sys.argv[1:]
|
|
||||||
args.remove('scgi')
|
|
||||||
if args:
|
|
||||||
return runscgi(func, validaddr(args[0]))
|
|
||||||
else:
|
|
||||||
return runscgi(func)
|
|
||||||
|
|
||||||
return httpserver.runsimple(func, validip(listget(sys.argv, 1, '')))
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
Copyright (c) 2004-2007, CherryPy Team (team@cherrypy.org)
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
|
||||||
are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
* 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.
|
|
||||||
* Neither the name of the CherryPy Team 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,159 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright (C) Martijn Voncken 2008 <mvoncken@gmail.com>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 3, or (at your option)
|
|
||||||
# any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, write to:
|
|
||||||
# The Free Software Foundation, Inc.,
|
|
||||||
# 51 Franklin Street, Fifth Floor
|
|
||||||
# Boston, MA 02110-1301, USA.
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
|
||||||
decorators for html-pages.
|
|
||||||
"""
|
|
||||||
#relative imports
|
|
||||||
from render import render
|
|
||||||
from utils import *
|
|
||||||
import utils
|
|
||||||
from deluge.ui.client import sclient as proxy
|
|
||||||
from deluge.log import LOG as log
|
|
||||||
#/relative
|
|
||||||
|
|
||||||
from web import cookies, setcookie as w_setcookie
|
|
||||||
from web import url, changequery
|
|
||||||
from utils import self_url
|
|
||||||
from render import error_page
|
|
||||||
|
|
||||||
#deco's:
|
|
||||||
def deluge_page_noauth(func):
|
|
||||||
"""
|
|
||||||
add http headers;print result of func
|
|
||||||
"""
|
|
||||||
def deco(self, name = None):
|
|
||||||
render.set_global("is_auto_refreshed", False);
|
|
||||||
web.header("Content-Type", "text/html; charset=utf-8")
|
|
||||||
web.header("Cache-Control", "no-cache, must-revalidate")
|
|
||||||
res = func(self, name) #deluge_page_noauth
|
|
||||||
print res
|
|
||||||
deco.__name__ = func.__name__
|
|
||||||
return deco
|
|
||||||
|
|
||||||
def check_session(func):
|
|
||||||
"""
|
|
||||||
1:check session
|
|
||||||
2:block urls in config.disallow
|
|
||||||
return func if session is valid, else redirect to login page.
|
|
||||||
mostly used for POST-pages.
|
|
||||||
"""
|
|
||||||
def deco(self, name = None):
|
|
||||||
log.debug('%s.%s(name=%s)' % (self.__class__.__name__, func.__name__,
|
|
||||||
name))
|
|
||||||
#check disallow config
|
|
||||||
current_url = changequery()
|
|
||||||
for blocked in utils.config["disallow"]:
|
|
||||||
if current_url.startswith(blocked):
|
|
||||||
return error_page("Not allowed to : '%s' , Reason: '%s'" %
|
|
||||||
(blocked , utils.config["disallow"][blocked]))
|
|
||||||
#/check disallow
|
|
||||||
|
|
||||||
#check session:
|
|
||||||
vars = web.input(redir_after_login = None)
|
|
||||||
ck = cookies()
|
|
||||||
if ck.has_key("session_id") and ck["session_id"] in utils.config["sessions"]:
|
|
||||||
return func(self, name) #check_session:ok
|
|
||||||
elif vars.redir_after_login:
|
|
||||||
utils.seeother(url("/login",redir=self_url()))
|
|
||||||
else:
|
|
||||||
utils.seeother("/login") #do not continue, and redirect to login page
|
|
||||||
#/check session
|
|
||||||
deco.__name__ = func.__name__
|
|
||||||
return deco
|
|
||||||
|
|
||||||
def check_connected(func):
|
|
||||||
def deco(self, name = None):
|
|
||||||
connected = False
|
|
||||||
try:
|
|
||||||
proxy.ping()
|
|
||||||
connected = True
|
|
||||||
except Exception, e:
|
|
||||||
log.debug("not_connected: %s" % e)
|
|
||||||
if connected:
|
|
||||||
return func(self, name) #check_connected:ok
|
|
||||||
else:
|
|
||||||
utils.seeother("/connect")
|
|
||||||
deco.__name__ = func.__name__
|
|
||||||
return deco
|
|
||||||
|
|
||||||
def deluge_page(func):
|
|
||||||
"deluge_page_noauth+check_session+check connected"
|
|
||||||
return check_session(check_connected(deluge_page_noauth(func)))
|
|
||||||
|
|
||||||
#combi-deco's:
|
|
||||||
#decorators to use in combination with the ones above.
|
|
||||||
def torrent_ids(func):
|
|
||||||
"""
|
|
||||||
change page(self, name) to page(self, torrent_ids)
|
|
||||||
for pages that allow a list of torrents.
|
|
||||||
"""
|
|
||||||
def deco(self, name):
|
|
||||||
return func (self, name.split(',')) #torrent_ids
|
|
||||||
deco.__name__ = func.__name__
|
|
||||||
return deco
|
|
||||||
|
|
||||||
def torrent_list(func):
|
|
||||||
"""
|
|
||||||
change page(self, name) to page(self, torrent_ids)
|
|
||||||
for pages that allow a list of torrents.
|
|
||||||
"""
|
|
||||||
def deco(self, name):
|
|
||||||
torrent_list = [get_torrent_status(id) for id in name.split(',')]
|
|
||||||
return func (self, torrent_list) #torrent_list
|
|
||||||
deco.__name__ = func.__name__
|
|
||||||
return deco
|
|
||||||
|
|
||||||
def torrent(func):
|
|
||||||
"""
|
|
||||||
change page(self, name) to page(self, get_torrent_status(torrent_id))
|
|
||||||
"""
|
|
||||||
def deco(self, name):
|
|
||||||
torrent_id = name.split(',')[0]
|
|
||||||
torrent =get_torrent_status(torrent_id)
|
|
||||||
return func (self, torrent) #torrent
|
|
||||||
deco.__name__ = func.__name__
|
|
||||||
return deco
|
|
||||||
|
|
||||||
def auto_refreshed(func):
|
|
||||||
""""
|
|
||||||
sets 'is_auto_refreshed' global for templates
|
|
||||||
note : decorate AFTER deluge_page_*
|
|
||||||
"""
|
|
||||||
def deco(self, name = None):
|
|
||||||
render.set_global("is_auto_refreshed", True);
|
|
||||||
return func(self, name) #auto_refreshed
|
|
||||||
deco.__name__ = func.__name__
|
|
||||||
return deco
|
|
||||||
|
|
||||||
def remote(func):
|
|
||||||
"decorator for remote (string) api's"
|
|
||||||
def deco(self, name = None):
|
|
||||||
try:
|
|
||||||
log.debug('%s.%s(%s)' ,self.__class__.__name__, func.__name__,name )
|
|
||||||
print func(self, name) #remote
|
|
||||||
except Exception, e:
|
|
||||||
print 'error:%s' % e.message
|
|
||||||
print '-'*20
|
|
||||||
print traceback.format_exc()
|
|
||||||
deco.__name__ = func.__name__
|
|
||||||
return deco
|
|
||||||
@ -1,481 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# Copyright (C) Martijn Voncken 2008 <mvoncken@gmail.com>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 3, or (at your option)
|
|
||||||
# any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, write to:
|
|
||||||
# The Free Software Foundation, Inc.,
|
|
||||||
# 51 Franklin Street, Fifth Floor
|
|
||||||
# Boston, MA 02110-1301, USA.
|
|
||||||
#
|
|
||||||
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
#todo: remove useless imports.
|
|
||||||
|
|
||||||
from utils import *
|
|
||||||
import utils #todo remove the line above.
|
|
||||||
from render import render, error_page
|
|
||||||
import page_decorators as deco
|
|
||||||
|
|
||||||
from deluge.common import get_pixmap
|
|
||||||
from deluge.log import LOG as log
|
|
||||||
|
|
||||||
import web
|
|
||||||
from web import url
|
|
||||||
from lib.static_handler import static_handler
|
|
||||||
|
|
||||||
from operator import attrgetter
|
|
||||||
import os
|
|
||||||
from deluge import component
|
|
||||||
from deluge.ui.client import sclient as proxy
|
|
||||||
from deluge.configmanager import ConfigManager
|
|
||||||
from deluge.ui.tracker_icons import TrackerIcons
|
|
||||||
|
|
||||||
page_manager = component.get("PageManager")
|
|
||||||
config = ConfigManager("webui06.conf")
|
|
||||||
|
|
||||||
def route(url, klass, use_module=True):
|
|
||||||
"""
|
|
||||||
url-mapping is using page_manager
|
|
||||||
not the default web.py way ; I want class decorators!
|
|
||||||
"""
|
|
||||||
page_manager.register_page(url, klass,use_module)
|
|
||||||
|
|
||||||
#pages:
|
|
||||||
class login:
|
|
||||||
@deco.deluge_page_noauth
|
|
||||||
def GET(self, name):
|
|
||||||
vars = web.input(error = None)
|
|
||||||
return render.login(vars.error)
|
|
||||||
|
|
||||||
def POST(self):
|
|
||||||
vars = web.input(pwd = None, redir = None)
|
|
||||||
|
|
||||||
if utils.check_pwd(vars.pwd):
|
|
||||||
#start new session
|
|
||||||
start_session()
|
|
||||||
utils.seeother('/index')
|
|
||||||
elif vars.redir:
|
|
||||||
utils.seeother(url('/login', error=1, redir=vars.redir))
|
|
||||||
else:
|
|
||||||
utils.seeother('/login?error=1')
|
|
||||||
|
|
||||||
route('/login',login)
|
|
||||||
|
|
||||||
class index:
|
|
||||||
"page containing the torrent list."
|
|
||||||
@deco.deluge_page
|
|
||||||
@deco.auto_refreshed
|
|
||||||
def GET(self, name):
|
|
||||||
vars = web.input(sort=None, order=None, filter_cat=None ,filter_value=None , tracker=None)
|
|
||||||
|
|
||||||
if not vars.sort: #no arguments, default to coockies.
|
|
||||||
newvars = cookies()
|
|
||||||
if vars.filter_cat:
|
|
||||||
newvars['filter_cat'] = vars.filter_cat
|
|
||||||
newvars['filter_value'] = vars.filter_value
|
|
||||||
vars.update(newvars)
|
|
||||||
|
|
||||||
else: #has arguments:set cookies from arguments.
|
|
||||||
for key in ["sort", "order", "filter_cat","filter_value"]:
|
|
||||||
value = getattr(vars, key) or ""
|
|
||||||
setcookie(key, value)
|
|
||||||
|
|
||||||
#Filters
|
|
||||||
filter_dict = {}
|
|
||||||
if vars.filter_cat and vars.filter_value <> "All":
|
|
||||||
filter_dict = {vars.filter_cat:vars.filter_value}
|
|
||||||
|
|
||||||
torrents = proxy.get_torrents_status(filter_dict, TORRENT_KEYS)
|
|
||||||
|
|
||||||
#sidebar-config:
|
|
||||||
hide_cat = []
|
|
||||||
if not config["sidebar_show_trackers"]:
|
|
||||||
hide_cat = ["tracker_host"]
|
|
||||||
|
|
||||||
if config["show_sidebar"]:
|
|
||||||
label_filters = proxy.get_filter_tree(config["sidebar_show_zero"], hide_cat)
|
|
||||||
else:
|
|
||||||
label_filters = {}
|
|
||||||
|
|
||||||
torrent_list = utils.get_enhanced_torrent_list(torrents)
|
|
||||||
|
|
||||||
#sorting:
|
|
||||||
if vars.sort:
|
|
||||||
try:
|
|
||||||
torrent_list.sort(key=attrgetter(vars.sort))
|
|
||||||
except:
|
|
||||||
log.error('Sorting Failed')
|
|
||||||
|
|
||||||
if vars.order == 'up':
|
|
||||||
torrent_list = list(reversed(torrent_list))
|
|
||||||
|
|
||||||
return render.index(torrent_list, label_filters)
|
|
||||||
route('/index',index)
|
|
||||||
|
|
||||||
#simple proxy's to deluge.ui.client
|
|
||||||
#execute a command on torrent(s) and redirect to index page.
|
|
||||||
def reg_torrents_POST(url_name, proxy_command):
|
|
||||||
class _page_class:
|
|
||||||
@deco.check_session
|
|
||||||
@deco.torrent_ids
|
|
||||||
def POST(self, torrent_ids):
|
|
||||||
getattr(proxy, proxy_command)(torrent_ids)
|
|
||||||
do_redirect()
|
|
||||||
_page_class.__name__ = "TORRENTS_POST:" + proxy_command
|
|
||||||
route("/torrent/%s/(.*)" % url_name, _page_class, use_module=False)
|
|
||||||
|
|
||||||
reg_torrents_POST("start", "resume_torrent")
|
|
||||||
reg_torrents_POST("stop", "pause_torrent")
|
|
||||||
reg_torrents_POST("recheck", "force_recheck")
|
|
||||||
reg_torrents_POST("reannounce", "force_reannounce")
|
|
||||||
reg_torrents_POST("queue/up", "queue_up")
|
|
||||||
reg_torrents_POST("queue/down", "queue_down")
|
|
||||||
reg_torrents_POST("queue/top", "queue_top")
|
|
||||||
reg_torrents_POST("queue/bottom", "queue_bottom")
|
|
||||||
|
|
||||||
|
|
||||||
class torrent_info:
|
|
||||||
@deco.deluge_page
|
|
||||||
@deco.auto_refreshed
|
|
||||||
@deco.torrent
|
|
||||||
def GET(self, torrent):
|
|
||||||
return render.torrent_info(torrent)
|
|
||||||
route("/torrent/info/(.*)", torrent_info)
|
|
||||||
|
|
||||||
class torrent_info_inner:
|
|
||||||
@deco.deluge_page
|
|
||||||
@deco.torrent
|
|
||||||
def GET(self, torrent):
|
|
||||||
vars = web.input(tab = None)
|
|
||||||
if vars.tab:
|
|
||||||
active_tab = vars.tab
|
|
||||||
else:
|
|
||||||
active_tab = getcookie("torrent_info_tab") or "details"
|
|
||||||
setcookie("torrent_info_tab", active_tab)
|
|
||||||
return render.torrent_info_inner(torrent, active_tab)
|
|
||||||
route("/torrent/info_inner/(.*)", torrent_info_inner)
|
|
||||||
|
|
||||||
class torrent_delete:
|
|
||||||
@deco.deluge_page
|
|
||||||
@deco.torrent_list
|
|
||||||
def GET(self, torrent_list):
|
|
||||||
torrent_str = ",".join([t.id for t in torrent_list])
|
|
||||||
#todo: remove the ",".join!
|
|
||||||
return render.torrent_delete(torrent_str, torrent_list)
|
|
||||||
|
|
||||||
@deco.check_session
|
|
||||||
@deco.torrent_ids
|
|
||||||
def POST(self, torrent_ids):
|
|
||||||
vars = web.input(data_also = None, torrent_also = None)
|
|
||||||
data_also = bool(vars.data_also)
|
|
||||||
torrent_also = bool(vars.torrent_also)
|
|
||||||
proxy.remove_torrent(torrent_ids, data_also)
|
|
||||||
do_redirect()
|
|
||||||
route("/torrent/delete/(.*)",torrent_delete)
|
|
||||||
|
|
||||||
class torrent_files:
|
|
||||||
@deco.deluge_page
|
|
||||||
@deco.torrent
|
|
||||||
def GET(self,torrent):
|
|
||||||
return render.torrent_files(torrent, None)
|
|
||||||
|
|
||||||
@deco.check_session
|
|
||||||
@deco.torrent
|
|
||||||
def POST(self, torrent):
|
|
||||||
values = web.input()
|
|
||||||
file_priorities = []
|
|
||||||
for i in xrange(len(torrent.files)):
|
|
||||||
file_priorities.append(int(getattr(values,"prio_%s" % i )))
|
|
||||||
|
|
||||||
proxy.set_torrent_file_priorities(torrent.id, file_priorities)
|
|
||||||
do_redirect()
|
|
||||||
route("/torrent/files/(.*)", torrent_files)
|
|
||||||
|
|
||||||
class torrent_trackers:
|
|
||||||
@deco.check_session
|
|
||||||
@deco.torrent
|
|
||||||
def POST(self, torrent):
|
|
||||||
vars = web.input(tier=[], url=[])
|
|
||||||
|
|
||||||
tiers_int = [int(t) for t in vars.tier]
|
|
||||||
sorted_urls = [url for num,url in sorted(zip(tiers_int, vars.url)) if url]
|
|
||||||
trackers = [{'tier':i , 'url':url} for i,url in enumerate(sorted_urls)]
|
|
||||||
|
|
||||||
proxy.set_torrent_trackers(torrent.id, trackers)
|
|
||||||
do_redirect()
|
|
||||||
route("/torrent/trackers/(.*)", torrent_trackers)
|
|
||||||
|
|
||||||
class pause_all:
|
|
||||||
@deco.check_session
|
|
||||||
def POST(self, name):
|
|
||||||
proxy.pause_torrent(proxy.get_session_state())
|
|
||||||
do_redirect()
|
|
||||||
route("/pause_all", pause_all)
|
|
||||||
|
|
||||||
class resume_all:
|
|
||||||
@deco.check_session
|
|
||||||
def POST(self, name):
|
|
||||||
proxy.resume_torrent(proxy.get_session_state())
|
|
||||||
do_redirect()
|
|
||||||
route("/resume_all", resume_all)
|
|
||||||
|
|
||||||
class refresh:
|
|
||||||
def GET(self, name):
|
|
||||||
return self.POST(name)
|
|
||||||
#WRONG, but makes it easyer to link with <a href> in the status-bar
|
|
||||||
|
|
||||||
@deco.check_session
|
|
||||||
def POST(self, name):
|
|
||||||
auto_refresh = {'off': '0', 'on': '1'}[name]
|
|
||||||
setcookie('auto_refresh', auto_refresh)
|
|
||||||
if not getcookie('auto_refresh_secs'):
|
|
||||||
setcookie('auto_refresh_secs', 10)
|
|
||||||
do_redirect()
|
|
||||||
route("/refresh/(.*)", refresh)
|
|
||||||
|
|
||||||
class refresh_set:
|
|
||||||
@deco.deluge_page
|
|
||||||
def GET(self, name):
|
|
||||||
return render.refresh_form()
|
|
||||||
|
|
||||||
@deco.check_session
|
|
||||||
def POST(self, name):
|
|
||||||
vars = web.input(refresh = 0)
|
|
||||||
refresh = int(vars.refresh)
|
|
||||||
if refresh > 0:
|
|
||||||
setcookie('auto_refresh', '1')
|
|
||||||
setcookie('auto_refresh_secs', str(refresh))
|
|
||||||
do_redirect()
|
|
||||||
else:
|
|
||||||
error_page(_('refresh must be > 0'))
|
|
||||||
route("/refresh/set", refresh_set)
|
|
||||||
|
|
||||||
class home:
|
|
||||||
@deco.check_session
|
|
||||||
def GET(self, name):
|
|
||||||
do_redirect()
|
|
||||||
route('/home', home)
|
|
||||||
route('/', home)
|
|
||||||
|
|
||||||
class about:
|
|
||||||
@deco.deluge_page_noauth
|
|
||||||
def GET(self, name):
|
|
||||||
return render.about()
|
|
||||||
route('/about', about)
|
|
||||||
|
|
||||||
class logout:
|
|
||||||
def GET(self):
|
|
||||||
return self.POST()
|
|
||||||
#WRONG, but makes it easyer to link with <a href> in the status-bar
|
|
||||||
@deco.check_session
|
|
||||||
def POST(self, name):
|
|
||||||
end_session()
|
|
||||||
utils.seeother('/login')
|
|
||||||
route('/logout', logout)
|
|
||||||
|
|
||||||
class connect:
|
|
||||||
@deco.check_session
|
|
||||||
@deco.deluge_page_noauth
|
|
||||||
def GET(self, name):
|
|
||||||
restart = False
|
|
||||||
try:
|
|
||||||
proxy.ping()
|
|
||||||
connected = proxy.get_core_uri()
|
|
||||||
if connected.startswith("http://localhost"):
|
|
||||||
restart = True
|
|
||||||
except:
|
|
||||||
connected = None
|
|
||||||
|
|
||||||
connect_list = ["http://localhost:58846"]
|
|
||||||
if config['daemon'] <> "http://localhost:58846":
|
|
||||||
connect_list = [config['daemon']] + connect_list
|
|
||||||
|
|
||||||
return render.connect(connect_list, connected ,restart)
|
|
||||||
|
|
||||||
def POST(self):
|
|
||||||
vars = web.input(uri = None, other_uri = None)
|
|
||||||
uri = ''
|
|
||||||
print "URI=",uri
|
|
||||||
if vars.uri == 'other':
|
|
||||||
if not vars.other_uri:
|
|
||||||
return error_page(_("no uri"))
|
|
||||||
uri = vars.other_uri
|
|
||||||
else:
|
|
||||||
uri = vars.uri
|
|
||||||
#TODO: more error-handling
|
|
||||||
utils.daemon_connect(uri)
|
|
||||||
do_redirect()
|
|
||||||
route('/connect', connect)
|
|
||||||
|
|
||||||
class daemon_control:
|
|
||||||
@deco.check_session
|
|
||||||
def POST(self, command):
|
|
||||||
if command == 'stop':
|
|
||||||
proxy.shutdown()
|
|
||||||
elif command == 'start':
|
|
||||||
self.start()
|
|
||||||
return do_redirect()
|
|
||||||
elif command == 'restart':
|
|
||||||
proxy.shutdown()
|
|
||||||
self.start()
|
|
||||||
return do_redirect()
|
|
||||||
else:
|
|
||||||
raise Exception('Unknown command:"%s"' % command)
|
|
||||||
|
|
||||||
utils.seeother('/connect')
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
import time
|
|
||||||
uri = web.input(uri = None).uri
|
|
||||||
if not uri:
|
|
||||||
uri = 'http://localhost:58846'
|
|
||||||
port = int(uri.split(':')[2])
|
|
||||||
utils.daemon_start_localhost(port)
|
|
||||||
time.sleep(1) #pause a while to let it start?
|
|
||||||
utils.daemon_connect( uri )
|
|
||||||
route("/daemon/control/(.*)", daemon_control)
|
|
||||||
|
|
||||||
#other stuff:
|
|
||||||
class remote_torrent_add:
|
|
||||||
"""
|
|
||||||
For use in remote scripts etc.
|
|
||||||
curl ->POST pwd and torrent as file
|
|
||||||
greasemonkey: POST pwd torrent_name and data_b64
|
|
||||||
"""
|
|
||||||
@deco.remote
|
|
||||||
def POST(self, name):
|
|
||||||
vars = web.input(pwd = None, torrent = {},
|
|
||||||
data_b64 = None , torrent_name= None)
|
|
||||||
|
|
||||||
if not utils.check_pwd(vars.pwd):
|
|
||||||
return 'error:wrong password'
|
|
||||||
|
|
||||||
if vars.data_b64: #b64 post (greasemonkey)
|
|
||||||
data_b64 = unicode(vars.data_b64)
|
|
||||||
torrent_name = vars.torrent_name
|
|
||||||
else: #file-post (curl)
|
|
||||||
data_b64 = base64.b64encode(vars.torrent.file.read())
|
|
||||||
torrent_name = vars.torrent.filename
|
|
||||||
proxy.add_torrent_filecontent(torrent_name, data_b64)
|
|
||||||
return 'ok'
|
|
||||||
route("/remote/torrent/add(.*)", remote_torrent_add)
|
|
||||||
|
|
||||||
class static(static_handler):
|
|
||||||
base_dir = os.path.join(os.path.dirname(__file__), 'static')
|
|
||||||
route("/static/(.*)", static)
|
|
||||||
|
|
||||||
class template_static(static_handler):
|
|
||||||
def get_base_dir(self):
|
|
||||||
return os.path.join(os.path.dirname(__file__),
|
|
||||||
'templates/%s/static' % config['template'])
|
|
||||||
route("/template/static/(.*)", template_static)
|
|
||||||
|
|
||||||
class template_render:
|
|
||||||
"render anything in /render/ dir"
|
|
||||||
|
|
||||||
def GET(self, name):
|
|
||||||
web.header("Content-type",utils.guess_mime_type(name))
|
|
||||||
#security : assumes config['template'] returns a safe subdir.
|
|
||||||
basepath = os.path.normpath(os.path.join(os.path.dirname(__file__),
|
|
||||||
'templates/%s/render' % config['template']))
|
|
||||||
filename = os.path.normpath(os.path.join(basepath,name))
|
|
||||||
if not filename.startswith(basepath):
|
|
||||||
#hack detected?
|
|
||||||
raise Exception("File to render is not located in %s" % basepath)
|
|
||||||
|
|
||||||
print web.template.Template(open(filename).read(), filename=filename)()
|
|
||||||
route("/template/render/(.*)", template_render)
|
|
||||||
|
|
||||||
class template_style:
|
|
||||||
def GET(self):
|
|
||||||
web.header("Content-Type", "text/css")
|
|
||||||
style = Storage()
|
|
||||||
print render.template_style(style)
|
|
||||||
route("/template_style.css", template_style)
|
|
||||||
|
|
||||||
class robots:
|
|
||||||
def GET(self):
|
|
||||||
"no robots/prevent searchengines from indexing"
|
|
||||||
web.header("Content-Type", "text/plain")
|
|
||||||
print "User-agent: *\nDisallow:/\n"
|
|
||||||
route("/robots.txt", robots)
|
|
||||||
|
|
||||||
class pixmaps:
|
|
||||||
"use the deluge-images. located in data/pixmaps"
|
|
||||||
def GET(self, name):
|
|
||||||
if name.startswith("flags") and not name.endswith('.png'):
|
|
||||||
name = name + ".png"
|
|
||||||
if not name.endswith('.png'):
|
|
||||||
if name == 'paused':
|
|
||||||
name = 'inactive'
|
|
||||||
if name == 'error':
|
|
||||||
name = 'alert'
|
|
||||||
name = name + '16.png'
|
|
||||||
|
|
||||||
if not os.path.exists(get_pixmap(name)):
|
|
||||||
name = 'dht16.png'
|
|
||||||
|
|
||||||
f = open(get_pixmap(name) ,'rb')
|
|
||||||
fs = os.fstat(f.fileno())
|
|
||||||
content = f.read()
|
|
||||||
f.close()
|
|
||||||
web.header("Content-Type", "image/png")
|
|
||||||
web.header("Content-Length", str(fs[6]))
|
|
||||||
web.header("Cache-Control" , "public, must-revalidate, max-age=86400")
|
|
||||||
print content
|
|
||||||
route("/pixmaps/(.*)", pixmaps)
|
|
||||||
|
|
||||||
|
|
||||||
class tracker_icon:
|
|
||||||
tracker_icons = TrackerIcons()
|
|
||||||
def GET(self, name):
|
|
||||||
filename = self.tracker_icons.get(name)
|
|
||||||
if filename:
|
|
||||||
log.debug("file-name=%s" % name)
|
|
||||||
web.header("Cache-Control" , "public, must-revalidate, max-age=86400")
|
|
||||||
if filename.endswith(".ico"):
|
|
||||||
web.header("Content-Type", "image/x-icon")
|
|
||||||
elif filename.endswith(".png"):
|
|
||||||
web.header("Content-Type", "image/png")
|
|
||||||
data = open(filename, "rb").read()
|
|
||||||
print data
|
|
||||||
else:
|
|
||||||
log.debug("not found:%s" % name)
|
|
||||||
web.header("Content-Type", "image/x-icon")
|
|
||||||
|
|
||||||
route("/tracker/icon/(.*)", tracker_icon)
|
|
||||||
|
|
||||||
class close:
|
|
||||||
"close open window"
|
|
||||||
@deco.deluge_page
|
|
||||||
def GET(self, name):
|
|
||||||
return """
|
|
||||||
<script language="javascript">
|
|
||||||
window.close();
|
|
||||||
</script>
|
|
||||||
"""
|
|
||||||
route("/close", close)
|
|
||||||
|
|
||||||
class gettext:
|
|
||||||
""""
|
|
||||||
gettext.js
|
|
||||||
"""
|
|
||||||
def GET(self):
|
|
||||||
web.header("Content-Type", "text/javascript")
|
|
||||||
print render.gettext()
|
|
||||||
route("/gettext.js", gettext)
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# Copyright (C) Martijn Voncken 2008 <mvoncken@gmail.com>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 3, or (at your option)
|
|
||||||
# any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, write to:
|
|
||||||
# The Free Software Foundation, Inc.,
|
|
||||||
# 51 Franklin Street, Fifth Floor
|
|
||||||
# Boston, MA 02110-1301, USA.
|
|
||||||
#
|
|
||||||
|
|
||||||
#
|
|
||||||
|
|
||||||
from deluge import component
|
|
||||||
|
|
||||||
menu = component.get("MenuManager")
|
|
||||||
TB = menu.TOOLBAR_FLAGS
|
|
||||||
|
|
||||||
menu.register_admin_page("config", _("Config"), "/config/")
|
|
||||||
menu.register_admin_page("connect", _("Connect"), "/connect")
|
|
||||||
menu.register_admin_page("about", _("About"), "/about")
|
|
||||||
menu.register_admin_page("logout", _("Logout"), "/logout")
|
|
||||||
|
|
||||||
menu.register_detail_tab("statistics", _("Statistics"), "tab_statistics")
|
|
||||||
menu.register_detail_tab("details", _("Details"), "tab_details")
|
|
||||||
menu.register_detail_tab("options", _("Options"), "tab_options")
|
|
||||||
menu.register_detail_tab("trackers", _("Trackers"), "tab_trackers")
|
|
||||||
menu.register_detail_tab("peers", _("Peers"), "tab_peers")
|
|
||||||
menu.register_detail_tab("files", _("Files"), "tab_files")
|
|
||||||
|
|
||||||
menu.register_toolbar_item("add", _("Add"), "list-add.png" , TB.generic, "GET","/torrent/add/", True)
|
|
||||||
menu.register_toolbar_item("delete",_("Delete"), "list-remove.png" ,TB.torrent_list, "GET","/torrent/delete/" , True)
|
|
||||||
menu.register_toolbar_item("stop",_("Stop"), "pause.png" ,TB.torrent_list, "POST","/torrent/stop/", True)
|
|
||||||
menu.register_toolbar_item("start",_("Start"), "start.png" ,TB.torrent_list, "POST","/torrent/start/", True)
|
|
||||||
menu.register_toolbar_item("queue_up",_("Queue Up"), "queue-up.png" ,TB.torrent_list, "POST","/torrent/queue/up/", True)
|
|
||||||
menu.register_toolbar_item("queue_down",_("Queue Down"), "queue-down.png" ,TB.torrent_list, "POST","/torrent/queue/down/", True)
|
|
||||||
menu.register_toolbar_item("queue_top",_("Queue Top"), "go-top.png" ,TB.torrent_list, "POST","/torrent/queue/top/", True)
|
|
||||||
menu.register_toolbar_item("queue_bottom",_("Queue Bottom"), "go-bottom.png" ,TB.torrent_list, "POST","/torrent/queue/bottom/", True)
|
|
||||||
menu.register_toolbar_item("details",_("Details"), "details.png" ,TB.torrent, "GET","/torrent/info/", True)
|
|
||||||
menu.register_toolbar_item("move",_("Move"), "move.png" ,TB.torrent_list,"POST","/torrent/move/", True)
|
|
||||||
menu.register_toolbar_item("reannounce",_("Reannounce"), "view-refresh.png" ,TB.torrent_list, "POST","/torrent/reannounce/", False)
|
|
||||||
menu.register_toolbar_item("recheck",_("Recheck"), "edit-redo.png" ,TB.torrent_list, "POST","/torrent/recheck/", False)
|
|
||||||
@ -1,217 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# Copyright (C) Martijn Voncken 2008 <mvoncken@gmail.com>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 3, or (at your option)
|
|
||||||
# any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, write to:
|
|
||||||
# The Free Software Foundation, Inc.,
|
|
||||||
# 51 Franklin Street, Fifth Floor
|
|
||||||
# Boston, MA 02110-1301, USA.
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
#relative:
|
|
||||||
|
|
||||||
from utils import *
|
|
||||||
import utils
|
|
||||||
#/relative
|
|
||||||
from deluge import common
|
|
||||||
from deluge import component
|
|
||||||
from web import template, Storage
|
|
||||||
import os
|
|
||||||
from deluge.configmanager import ConfigManager
|
|
||||||
|
|
||||||
config = ConfigManager("webui06.conf")
|
|
||||||
page_manager = component.get("PageManager")
|
|
||||||
|
|
||||||
|
|
||||||
class subclassed_render(object):
|
|
||||||
"""
|
|
||||||
adds limited subclassing for templates.
|
|
||||||
see: meta.cfg in the template-directory.
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
self.apply_cfg()
|
|
||||||
self.plugin_renderers = []
|
|
||||||
|
|
||||||
def apply_cfg(self):
|
|
||||||
self.cache = config['cache_templates']
|
|
||||||
self.renderers = []
|
|
||||||
self.template_cache = {}
|
|
||||||
self.webui_path = os.path.dirname(__file__)
|
|
||||||
|
|
||||||
#load template-meta-data
|
|
||||||
self.cfg_template = config['template']
|
|
||||||
template_path = os.path.join(self.webui_path, 'templates/%s/' % self.cfg_template)
|
|
||||||
if not os.path.exists(template_path):
|
|
||||||
template_path = os.path.join(self.webui_path, 'templates/white/')
|
|
||||||
self.meta = Storage(eval(open(os.path.join(template_path,'meta.cfg')).read()))
|
|
||||||
|
|
||||||
#load renerders
|
|
||||||
for template_name in [self.cfg_template] + list(reversed(self.meta.inherits)):
|
|
||||||
self.renderers.append(template.render(
|
|
||||||
os.path.join(self.webui_path, 'templates/%s/' % template_name),cache=False))
|
|
||||||
|
|
||||||
@logcall
|
|
||||||
def register_template_path(self, path):
|
|
||||||
self.plugin_renderers.append(template.render(path , cache=False))
|
|
||||||
|
|
||||||
@logcall
|
|
||||||
def deregister_template_path(self, path):
|
|
||||||
for i, renderer in list(ennumerate(self.plugin_renderers)):
|
|
||||||
if renderer.loc == path:
|
|
||||||
del self.plugin_renderers[i]
|
|
||||||
return
|
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
if self.cache and attr in self.template_cache:
|
|
||||||
return self.template_cache[attr]
|
|
||||||
|
|
||||||
for renderer in self.renderers + self.plugin_renderers:
|
|
||||||
if hasattr(renderer, attr):
|
|
||||||
self.template_cache[attr] = getattr(renderer, attr)
|
|
||||||
return getattr(renderer, attr)
|
|
||||||
raise AttributeError, 'no template named "%s" in template-dirs:%s' % (attr,
|
|
||||||
[r.loc for r in self.renderers + self.plugin_renderers] )
|
|
||||||
|
|
||||||
def __getitem__(self, item):
|
|
||||||
"for plugins/templates"
|
|
||||||
return getattr(self, item)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_templates():
|
|
||||||
"utility method."
|
|
||||||
template_path = os.path.join(os.path.dirname(__file__), 'templates')
|
|
||||||
return [dirname for dirname
|
|
||||||
in os.listdir(template_path)
|
|
||||||
if os.path.isdir(os.path.join(template_path, dirname))
|
|
||||||
and not dirname.startswith('.')]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def set_global(key, val):
|
|
||||||
template.Template.globals[key] = val
|
|
||||||
|
|
||||||
render = subclassed_render()
|
|
||||||
|
|
||||||
def error_page(error):
|
|
||||||
web.header("Content-Type", "text/html; charset=utf-8")
|
|
||||||
web.header("Cache-Control", "no-cache, must-revalidate")
|
|
||||||
print render.error(error)
|
|
||||||
|
|
||||||
|
|
||||||
def template_crop_middle(text, maxlen):
|
|
||||||
try:
|
|
||||||
if len(text) > maxlen:
|
|
||||||
half = (maxlen / 2) - 2
|
|
||||||
return text[0:half ] + '...' + text[-half:]
|
|
||||||
except:
|
|
||||||
return "[ERROR NOT A STRING:(%s)]" % text
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
def template_sort_head(id,name):
|
|
||||||
#got tired of doing these complex things inside templetor..
|
|
||||||
vars = web.input(sort = None, order = None)
|
|
||||||
active_up = False
|
|
||||||
active_down = False
|
|
||||||
order = 'down'
|
|
||||||
|
|
||||||
if not vars.sort: #no arguments, default to coockies.
|
|
||||||
vars.update(cookies())
|
|
||||||
|
|
||||||
|
|
||||||
if vars.sort == id:
|
|
||||||
if vars.order == 'down':
|
|
||||||
order = 'up'
|
|
||||||
active_down = True
|
|
||||||
else:
|
|
||||||
active_up = True
|
|
||||||
|
|
||||||
return render.sort_column_head(id, name, order, active_up, active_down)
|
|
||||||
|
|
||||||
def template_part_stats():
|
|
||||||
try:
|
|
||||||
return render.part_stats(Storage(sclient.get_stats()))
|
|
||||||
except Exception, e:
|
|
||||||
return str(e)
|
|
||||||
|
|
||||||
def get_config(var):
|
|
||||||
return config[var]
|
|
||||||
|
|
||||||
irow = 0
|
|
||||||
def altrow(reset = False):
|
|
||||||
global irow
|
|
||||||
if reset:
|
|
||||||
irow = 1
|
|
||||||
return
|
|
||||||
irow +=1
|
|
||||||
irow = irow % 2
|
|
||||||
return "altrow%s" % irow
|
|
||||||
|
|
||||||
def deluge_int(val):
|
|
||||||
if val == -1 :
|
|
||||||
return "∞"
|
|
||||||
return val
|
|
||||||
|
|
||||||
def ftime(val):
|
|
||||||
if val <= 0:
|
|
||||||
return _("∞")
|
|
||||||
return common.ftime(val)
|
|
||||||
|
|
||||||
def template_get(key):
|
|
||||||
val = getattr(web.input(**{key:None}), key)
|
|
||||||
if not val:
|
|
||||||
val = getcookie(key)
|
|
||||||
return val or ""
|
|
||||||
|
|
||||||
def id_to_label(text):
|
|
||||||
"translated capitalize"
|
|
||||||
text = text.replace("_"," ")
|
|
||||||
text = text.capitalize()
|
|
||||||
return _(text)
|
|
||||||
|
|
||||||
template.Template.globals.update({
|
|
||||||
'sort_head': template_sort_head,
|
|
||||||
'part_stats':template_part_stats,
|
|
||||||
'crop': template_crop_middle,
|
|
||||||
'crop_left': template_crop_middle,
|
|
||||||
'_': _ , #gettext/translations
|
|
||||||
'str': str, #because % in templetor is broken.
|
|
||||||
'int':int,
|
|
||||||
'len':len,
|
|
||||||
'deluge_int':deluge_int,
|
|
||||||
'sorted': sorted,
|
|
||||||
'altrow':altrow,
|
|
||||||
'get_config': get_config,
|
|
||||||
'self_url': utils.self_url,
|
|
||||||
'fspeed': common.fspeed,
|
|
||||||
'fsize': common.fsize,
|
|
||||||
'ftime':ftime,
|
|
||||||
'render': render, #for easy resuse of templates
|
|
||||||
'version':common.get_version() ,
|
|
||||||
'rev': common.get_revision(),
|
|
||||||
'getcookie':getcookie,
|
|
||||||
'get': template_get,
|
|
||||||
#'env':'0.6',
|
|
||||||
'forms':web.Storage(),
|
|
||||||
'enumerate':enumerate,
|
|
||||||
'base':'', #updated when running within apache.
|
|
||||||
'id_to_label':id_to_label,
|
|
||||||
'include_javascript':page_manager.include_javascript,
|
|
||||||
'ajax_javascript':page_manager.include_javascript,
|
|
||||||
'is_auto_refreshed':False
|
|
||||||
})
|
|
||||||
#/template-defs
|
|
||||||
|
|
||||||
__all__ = ['render']
|
|
||||||
@ -1 +0,0 @@
|
|||||||
185
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright (C) Martijn Voncken 2008 <mvoncken@gmail.com>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 3, or (at your option)
|
|
||||||
# any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, write to:
|
|
||||||
# The Free Software Foundation, Inc.,
|
|
||||||
# 51 Franklin Street, Fifth Floor
|
|
||||||
# Boston, MA 02110-1301, USA.
|
|
||||||
#
|
|
||||||
|
|
||||||
import deluge_webserver
|
|
||||||
|
|
||||||
deluge_webserver.run(debug = True)
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
cd ~/prj/WebUi
|
|
||||||
bzr revno > revno
|
|
||||||
bzr version-info > version
|
|
||||||
rm ~/prj/WebUi/WebUi.tgz
|
|
||||||
cd ~/prj
|
|
||||||
tar -zcvf ~/prj/WebUi/WebUi.tgz WebUi/ --exclude '.*' --exclude '*.pyc' --exclude '*.tgz' --exclude 'attic' --exclude 'xul' --exclude '*.sh' --exclude '*.*~'
|
|
||||||
@ -1,96 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright (C) Martijn Voncken 2008 <mvoncken@gmail.com>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 3, or (at your option)
|
|
||||||
# any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, write to:
|
|
||||||
# The Free Software Foundation, Inc.,
|
|
||||||
# 51 Franklin Street, Fifth Floor
|
|
||||||
# Boston, MA 02110-1301, USA.
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
|
||||||
copy icons from kde icon set.
|
|
||||||
Edit ICON_SET and TARGET_DIR before running this file.
|
|
||||||
#perl -pi -w -e 's/tango/16/g;' *.html
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
from os import path
|
|
||||||
from shutil import copyfile
|
|
||||||
|
|
||||||
ICON_SET = "/home/martijn/prj/oxygen/oxygen"
|
|
||||||
TARGET_DIR = "/home/martijn/src/deluge/deluge/ui/webui"
|
|
||||||
|
|
||||||
def copy_icons(source_dir , target_dir , mapping):
|
|
||||||
for target, source in mapping.iteritems():
|
|
||||||
source = path.join(source_dir, source) + ".png"
|
|
||||||
target = path.join(target_dir, target) + ".png"
|
|
||||||
print "%s -> %s" % (source, target)
|
|
||||||
copyfile(source, target)
|
|
||||||
|
|
||||||
map_static16 = {
|
|
||||||
"down":"actions/1downarrow",
|
|
||||||
"up":"actions/1downarrow",
|
|
||||||
"connections":"apps/preferences_system_network_sharing",
|
|
||||||
"details":"actions/object_edit",
|
|
||||||
"edit-clear":"actions/clear_left",
|
|
||||||
"edit-redo":"actions/edit_redo",
|
|
||||||
"go-bottom":"actions/2downarrow",
|
|
||||||
"go-top":"actions/2uparrow",
|
|
||||||
"label":"actions/rss_tag",
|
|
||||||
"list-add":"actions/add",
|
|
||||||
"list-remove":"actions/fileclose",
|
|
||||||
"move":"actions/filesaveas",
|
|
||||||
"pause":"actions/media_playback_pause",
|
|
||||||
"preferences-system":"apps/preferences_system",
|
|
||||||
"process-stop":"actions/process_stop",
|
|
||||||
"queue-down":"actions/1downarrow",
|
|
||||||
"queue-up":"actions/1uparrow",
|
|
||||||
"start":"actions/media_playback_start",
|
|
||||||
"stop":"actions/media_playback_stop",
|
|
||||||
"system-log-out":"actions/system_log_out",
|
|
||||||
"user-trash":"actions/edittrash",
|
|
||||||
"view-refresh":"actions/view_refresh",
|
|
||||||
"gtk-yes":"actions/flag_green",
|
|
||||||
"drive-harddisk":"devices/drive_harddisk",
|
|
||||||
"select-all":"actions/edit_select_all"
|
|
||||||
}
|
|
||||||
|
|
||||||
map_ajax16 = {
|
|
||||||
"gtk-edit":"actions/object_edit",
|
|
||||||
"gtk-yes":"actions/flag_green",
|
|
||||||
"network-idle":"apps/preferences_system_network_sharing",
|
|
||||||
"view-refresh":"actions/view_refresh",
|
|
||||||
"view-sort-ascending":"actions/view_sort_ascending",
|
|
||||||
"view-sort-descending":"actions/view_sort_descending"
|
|
||||||
}
|
|
||||||
|
|
||||||
map_ajax32 = {
|
|
||||||
"add":"actions/add",
|
|
||||||
"connections":"apps/preferences_system_network_sharing",
|
|
||||||
"down":"actions/1downarrow",
|
|
||||||
"up":"actions/1uparrow",
|
|
||||||
"new":"actions/document_new",
|
|
||||||
"options":"apps/preferences_system",
|
|
||||||
"pause":"actions/media_playback_pause",
|
|
||||||
"remove":"actions/fileclose",
|
|
||||||
"resume":"actions/media_playback_start"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
copy_icons( path.join(ICON_SET, "16x16"), path.join(TARGET_DIR, "static/images/16"), map_static16)
|
|
||||||
copy_icons( path.join(ICON_SET, "16x16"), path.join(TARGET_DIR, "templates/ajax/static/icons/16"), map_ajax16)
|
|
||||||
copy_icons( path.join(ICON_SET, "32x32"), path.join(TARGET_DIR, "templates/ajax/static/icons/32"), map_ajax32)
|
|
||||||
|
|
||||||
|
|
||||||
@ -1 +0,0 @@
|
|||||||
curl -F torrent=@./test1.torrent -F pwd=deluge http://localhost:8112/remote/torrent/add
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright (C) Martijn Voncken 2008 <mvoncken@gmail.com>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 3, or (at your option)
|
|
||||||
# any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, write to:
|
|
||||||
# The Free Software Foundation, Inc.,
|
|
||||||
# 51 Franklin Street, Fifth Floor
|
|
||||||
# Boston, MA 02110-1301, USA.
|
|
||||||
#
|
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
template_dirs = ['../templates/ajax/static/js']
|
|
||||||
|
|
||||||
template_dirs = [os.path.expanduser(template_dir ) for template_dir in template_dirs]
|
|
||||||
|
|
||||||
|
|
||||||
files = []
|
|
||||||
for template_dir in template_dirs:
|
|
||||||
files += [os.path.join(template_dir,fname)
|
|
||||||
for fname in os.listdir(template_dir)
|
|
||||||
if fname.endswith('.js')]
|
|
||||||
|
|
||||||
|
|
||||||
all_strings = []
|
|
||||||
for filename in files:
|
|
||||||
f = open(filename,'r')
|
|
||||||
content = f.read()
|
|
||||||
all_strings += re.findall("_\(\"(.*?)\"\)",content)
|
|
||||||
all_strings += re.findall("_\(\'(.*?)\'\)",content)
|
|
||||||
all_strings += re.findall("Deluge\.Strings\.get\(\'(.*?)\'\)",content)
|
|
||||||
all_strings += re.findall("Deluge\.Strings\.get\(\'(.*?)\'\)",content)
|
|
||||||
|
|
||||||
|
|
||||||
all_strings = sorted(set(all_strings))
|
|
||||||
|
|
||||||
f = open ('./ajax_strings.js','w')
|
|
||||||
f.write("/*generated code:*/\n")
|
|
||||||
for value in all_strings:
|
|
||||||
f.write('GetText.add("%s","$_("%s")");\n' % (value, value) )
|
|
||||||
f.write("/*end generated code.*/\n")
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright (C) Martijn Voncken 2008 <mvoncken@gmail.com>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 3, or (at your option)
|
|
||||||
# any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, write to:
|
|
||||||
# The Free Software Foundation, Inc.,
|
|
||||||
# 51 Franklin Street, Fifth Floor
|
|
||||||
# Boston, MA 02110-1301, USA.
|
|
||||||
#
|
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
template_dirs = ['../templates/classic','../templates/white','../templates/ajax/static/js']
|
|
||||||
|
|
||||||
template_dirs = [os.path.expanduser(template_dir ) for template_dir in template_dirs]
|
|
||||||
|
|
||||||
|
|
||||||
files = []
|
|
||||||
for template_dir in template_dirs:
|
|
||||||
files += [os.path.join(template_dir,fname)
|
|
||||||
for fname in os.listdir(template_dir)
|
|
||||||
if fname.endswith('.html') or fname.endswith('.js')]
|
|
||||||
|
|
||||||
|
|
||||||
all_strings = []
|
|
||||||
for filename in files:
|
|
||||||
f = open(filename,'r')
|
|
||||||
content = f.read()
|
|
||||||
all_strings += re.findall("_\(\"(.*?)\"\)",content)
|
|
||||||
all_strings += re.findall("_\(\'(.*?)\'\)",content)
|
|
||||||
all_strings += re.findall("Deluge\.Strings\.get\(\'(.*?)\'\)",content)
|
|
||||||
all_strings += re.findall("Deluge\.Strings\.get\(\'(.*?)\'\)",content)
|
|
||||||
|
|
||||||
all_strings = sorted(set(all_strings))
|
|
||||||
|
|
||||||
f = open ('./template_strings.py','w')
|
|
||||||
for value in all_strings:
|
|
||||||
f.write("_('%s')\n" % value )
|
|
||||||
|
|
||||||
@ -1,123 +0,0 @@
|
|||||||
_('# Of Files')
|
|
||||||
_('... and delete All files')
|
|
||||||
_('... and delete Downloaded files')
|
|
||||||
_('... and delete Torrent file')
|
|
||||||
_('About')
|
|
||||||
_('Active time')
|
|
||||||
_('Add Torrent')
|
|
||||||
_('Add Torrents')
|
|
||||||
_('Add torrent')
|
|
||||||
_('Address')
|
|
||||||
_('Admin')
|
|
||||||
_('Auto Managed')
|
|
||||||
_('Auto refresh:')
|
|
||||||
_('Ava')
|
|
||||||
_('Availability')
|
|
||||||
_('Bottom')
|
|
||||||
_('Cancel')
|
|
||||||
_('Clear')
|
|
||||||
_('Client')
|
|
||||||
_('Config')
|
|
||||||
_('Connect')
|
|
||||||
_('Connect to Daemon')
|
|
||||||
_('Connected to')
|
|
||||||
_('Connection Limit')
|
|
||||||
_('Connections')
|
|
||||||
_('Create Torrent')
|
|
||||||
_('D/L Speed Limit')
|
|
||||||
_('DHT Nodes')
|
|
||||||
_('Delete .torrent file')
|
|
||||||
_('Delete downloaded files.')
|
|
||||||
_('Deluge : Torrent List')
|
|
||||||
_('Deluge Login')
|
|
||||||
_('Details')
|
|
||||||
_('Disable')
|
|
||||||
_('Disk Space')
|
|
||||||
_('Do not download')
|
|
||||||
_('Down')
|
|
||||||
_('Down Speed')
|
|
||||||
_('Download')
|
|
||||||
_('Downloaded')
|
|
||||||
_('ETA')
|
|
||||||
_('Edit Trackers')
|
|
||||||
_('Enable')
|
|
||||||
_('Error')
|
|
||||||
_('Eta')
|
|
||||||
_('False')
|
|
||||||
_('File')
|
|
||||||
_('Files')
|
|
||||||
_('Filter on a keyword')
|
|
||||||
_('Force Recheck')
|
|
||||||
_('From Session')
|
|
||||||
_('From Url')
|
|
||||||
_('General')
|
|
||||||
_('High priority')
|
|
||||||
_('Highest priority')
|
|
||||||
_('Keyword')
|
|
||||||
_('Label')
|
|
||||||
_('Label torrent')
|
|
||||||
_('Login')
|
|
||||||
_('Logout')
|
|
||||||
_('Move')
|
|
||||||
_('Move Storage')
|
|
||||||
_('Move torrent')
|
|
||||||
_('Name')
|
|
||||||
_('Next Announce')
|
|
||||||
_('No Incoming Connections')
|
|
||||||
_('No Label')
|
|
||||||
_('None')
|
|
||||||
_('Normal priority')
|
|
||||||
_('Not Connected to a daemon')
|
|
||||||
_('Off')
|
|
||||||
_('Ok')
|
|
||||||
_('Options')
|
|
||||||
_('Other')
|
|
||||||
_('Password')
|
|
||||||
_('Password is invalid,try again')
|
|
||||||
_('Pause')
|
|
||||||
_('Pause all')
|
|
||||||
_('Peers')
|
|
||||||
_('Pieces')
|
|
||||||
_('Private')
|
|
||||||
_('Progress')
|
|
||||||
_('Queue')
|
|
||||||
_('Queue Position')
|
|
||||||
_('Ratio')
|
|
||||||
_('Refresh page every:')
|
|
||||||
_('Remove')
|
|
||||||
_('Remove Torrent')
|
|
||||||
_('Remove torrent')
|
|
||||||
_('Restart')
|
|
||||||
_('Resume')
|
|
||||||
_('Resume all')
|
|
||||||
_('Save')
|
|
||||||
_('Search')
|
|
||||||
_('Seed rank')
|
|
||||||
_('Seeders')
|
|
||||||
_('Seeding time')
|
|
||||||
_('Select All')
|
|
||||||
_('Set')
|
|
||||||
_('Set Timeout')
|
|
||||||
_('Share Ratio')
|
|
||||||
_('Size')
|
|
||||||
_('Speed')
|
|
||||||
_('Start')
|
|
||||||
_('Statistics')
|
|
||||||
_('Stop')
|
|
||||||
_('Submit')
|
|
||||||
_('Top')
|
|
||||||
_('Torrent list')
|
|
||||||
_('Total Size')
|
|
||||||
_('Tracker')
|
|
||||||
_('Tracker Status')
|
|
||||||
_('True')
|
|
||||||
_('U/L Speed Limit')
|
|
||||||
_('Unlimited')
|
|
||||||
_('Up')
|
|
||||||
_('Up Speed')
|
|
||||||
_('Update')
|
|
||||||
_('Update Tracker')
|
|
||||||
_('Upload')
|
|
||||||
_('Upload Slot Limit')
|
|
||||||
_('Uploaded')
|
|
||||||
_('seconds')
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
/*
|
|
||||||
# Copyright (C) Martijn Voncken 2008 <mvoncken@gmail.com>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 3, or (at your option)
|
|
||||||
# any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, write to:
|
|
||||||
# The Free Software Foundation, Inc.,
|
|
||||||
# 51 Franklin Street, Fifth Floor
|
|
||||||
# Boston, MA 02110-1301, USA.
|
|
||||||
#
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
= InputSensitivitySetter =
|
|
||||||
see : gtkui/sidebar_menu.py:
|
|
||||||
def apply_sensitivity(self, event=None):
|
|
||||||
for chk_id , sensitive_list in self.sensitive_groups:
|
|
||||||
chk = self.glade.get_widget(chk_id)
|
|
||||||
sens = chk.get_active() and chk.get_property("sensitive")
|
|
||||||
for widget_id in sensitive_list:
|
|
||||||
self.glade.get_widget(widget_id).set_sensitive(sens)
|
|
||||||
|
|
||||||
|
|
||||||
Sets disabled property of input fields according to value of checkboxes.
|
|
||||||
Example (see label plugin)
|
|
||||||
|
|
||||||
new InputSensitivitySetter({prefix:"id_",groups:[
|
|
||||||
["apply_max", ["max_download_speed", "max_upload_speed", "max_upload_slots", "max_connections"]],
|
|
||||||
["apply_queue", ["is_auto_managed", "stop_at_ratio"]],
|
|
||||||
["stop_at_ratio", ["remove_at_ratio", "stop_ratio"]], #nested
|
|
||||||
["apply_move_completed", ["move_completed"]],
|
|
||||||
["move_completed", ["move_completed_path"]], #nested
|
|
||||||
["auto_add", ["auto_add_trackers"]]
|
|
||||||
]});
|
|
||||||
|
|
||||||
*/
|
|
||||||
var InputSensitivitySetter = new Class({
|
|
||||||
Implements: [Options, Events],
|
|
||||||
options: {
|
|
||||||
groups : [],
|
|
||||||
prefix : "" /*django forms prefixes input elements with id_*/
|
|
||||||
},
|
|
||||||
initialize :function(options){
|
|
||||||
this.setOptions(options);
|
|
||||||
this.attachWidgets();
|
|
||||||
this.appySensitivity();
|
|
||||||
}
|
|
||||||
,attachWidgets: function() {
|
|
||||||
this.options.groups.each(function(group, i) {
|
|
||||||
el = $(this.options.prefix + group[0]);
|
|
||||||
el.addEvent('click', this.appySensitivity.bind(this));
|
|
||||||
}, this);
|
|
||||||
}
|
|
||||||
,appySensitivity: function () {
|
|
||||||
this.options.groups.each(function(group, i) {
|
|
||||||
var el = $(this.options.prefix + group[0]);
|
|
||||||
var widgets = group[1];
|
|
||||||
var sensitive = (el.checked && !el.get('disabled'));
|
|
||||||
widgets.each(function(widget, i) {
|
|
||||||
$(this.options.prefix + widget).set('disabled', !sensitive);
|
|
||||||
},this);
|
|
||||||
},this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -1,236 +0,0 @@
|
|||||||
/*
|
|
||||||
# Copyright (C) Martijn Voncken 2008 <mvoncken@gmail.com>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 3, or (at your option)
|
|
||||||
# any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, write to:
|
|
||||||
# The Free Software Foundation, Inc.,
|
|
||||||
# 51 Franklin Street, Fifth Floor
|
|
||||||
# Boston, MA 02110-1301, USA.
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
all javascript is optional, everything should work web 1.0
|
|
||||||
but javascript may/will enhance the experience.
|
|
||||||
i'm not a full time web-dev so don't expect beautifull patterns.
|
|
||||||
There's so much crap out there,i can't find good examples.
|
|
||||||
so i'd rather start from scratch,
|
|
||||||
Probably broken in an unexpected way , but worksforme.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*fix IE:*/
|
|
||||||
if(!Array.indexOf){
|
|
||||||
Array.prototype.indexOf = function(obj){
|
|
||||||
for(var i=0; i<this.length; i++){
|
|
||||||
if(this[i]==obj){
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*/fix IE*/
|
|
||||||
|
|
||||||
state = {
|
|
||||||
'row_js_continue':true,
|
|
||||||
'selected_rows': new Array(),
|
|
||||||
'base_url':''
|
|
||||||
};
|
|
||||||
|
|
||||||
function $(el_id){
|
|
||||||
return document.getElementById(el_id)
|
|
||||||
}
|
|
||||||
function el(el_id){
|
|
||||||
return document.getElementById(el_id)
|
|
||||||
}
|
|
||||||
function get_row(id){
|
|
||||||
return $('torrent_' + id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function on_click_row(e,id) {
|
|
||||||
/*filter out web 1.0 events for detail-link and pause*/
|
|
||||||
if (state.row_js_continue) {
|
|
||||||
on_click_action(e,id);
|
|
||||||
}
|
|
||||||
state.row_js_continue = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function on_click_row_js(e, id) {
|
|
||||||
/*real onClick event*/
|
|
||||||
if (!e.ctrlKey) {
|
|
||||||
deselect_all_rows();
|
|
||||||
select_row(id);
|
|
||||||
open_inner_details(id);
|
|
||||||
}
|
|
||||||
else if (state.selected_rows.indexOf(id) != -1) {
|
|
||||||
deselect_row(id);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
select_row(id);
|
|
||||||
open_inner_details(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function select_row(id){
|
|
||||||
var row = get_row(id);
|
|
||||||
if (row) {
|
|
||||||
if (!(row.default_class_name)) {
|
|
||||||
row.default_class_name = row.className;
|
|
||||||
}
|
|
||||||
row.className = 'torrent_table_selected';
|
|
||||||
state.selected_rows[state.selected_rows.length] = id;
|
|
||||||
setCookie('selected_rows',state.selected_rows);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function deselect_row(id){
|
|
||||||
var row = get_row(id);
|
|
||||||
if (row) {
|
|
||||||
row.className = row.default_class_name
|
|
||||||
/*remove from state.selected_rows*/
|
|
||||||
var idx = state.selected_rows.indexOf(id);
|
|
||||||
state.selected_rows.splice(idx,1);
|
|
||||||
setCookie('selected_rows',state.selected_rows);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function deselect_all_rows(){
|
|
||||||
/*unbind state.selected_rows from for..in:
|
|
||||||
there must be a better way to do this*/
|
|
||||||
var a = new Array()
|
|
||||||
for (i in state.selected_rows) {
|
|
||||||
a[a.length] = state.selected_rows[i];
|
|
||||||
}
|
|
||||||
for (i in a){
|
|
||||||
deselect_row(a[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function select_all_rows(){
|
|
||||||
torrents = torrent_table.torrents
|
|
||||||
for (i in torrents){
|
|
||||||
select_row(torrents[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function reselect_rows(){
|
|
||||||
var selected = false;
|
|
||||||
var selected_rows = getCookie('selected_rows').split(',');
|
|
||||||
for (i in getCookie('selected_rows')) {
|
|
||||||
if (select_row(selected_rows[i])) {
|
|
||||||
selected = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!selected) {
|
|
||||||
/*select 1st*/
|
|
||||||
select_row(torrent_table.torrents[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function open_details(e, id){
|
|
||||||
alert(id);
|
|
||||||
window.location.href = '/torrent/info/' + id;
|
|
||||||
}
|
|
||||||
|
|
||||||
function open_inner_details(id){
|
|
||||||
/*probably broken for IE, use FF!*/
|
|
||||||
$('torrent_info').src = state.base_url + '/torrent/info_inner/' + id;
|
|
||||||
}
|
|
||||||
|
|
||||||
function on_click_do_nothing(e, id){
|
|
||||||
}
|
|
||||||
|
|
||||||
on_click_action = on_click_do_nothing;
|
|
||||||
|
|
||||||
/*toobar buttons, */
|
|
||||||
function toolbar_post(url, selected) {
|
|
||||||
if ((!selected) || (state.selected_rows.length > 0)) {
|
|
||||||
var ids = state.selected_rows.join(',');
|
|
||||||
var ids = state.selected_rows.join(',');
|
|
||||||
var form = $('toolbar_form');
|
|
||||||
form.action = url +ids;
|
|
||||||
form.submit();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toolbar_get(url , selected) {
|
|
||||||
if (!selected) {
|
|
||||||
window.location.href = url
|
|
||||||
}
|
|
||||||
else if (state.selected_rows.length > 0) {
|
|
||||||
var ids = state.selected_rows.join(',');
|
|
||||||
window.location.href = url +ids;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*arrow-navigation*/
|
|
||||||
torrent_table = {}
|
|
||||||
torrent_table.select_prev = function () {
|
|
||||||
//torrent_tab
|
|
||||||
var prev_id = state.selected_rows[0];
|
|
||||||
var i = torrent_table.torrents.indexOf(prev_id);
|
|
||||||
var id = torrent_table.torrents[i - 1];
|
|
||||||
deselect_all_rows();
|
|
||||||
select_row(id);
|
|
||||||
open_inner_details(id);
|
|
||||||
}
|
|
||||||
torrent_table.select_next = function () {
|
|
||||||
var prev_id = state.selected_rows[0];
|
|
||||||
var i = torrent_table.torrents.indexOf(prev_id);
|
|
||||||
var id = torrent_table.torrents[i + 1];
|
|
||||||
deselect_all_rows();
|
|
||||||
select_row(id);
|
|
||||||
open_inner_details(id);
|
|
||||||
}
|
|
||||||
torrent_table.keydown = function (oEvent) {
|
|
||||||
switch(oEvent.keyCode) {
|
|
||||||
case 38: //up arrow
|
|
||||||
torrent_table.select_prev();
|
|
||||||
break;
|
|
||||||
case 40: //down arrow
|
|
||||||
torrent_table.select_next();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/*stuff copied from various places:*/
|
|
||||||
/*http://www.w3schools.com/js/js_cookies.asp*/
|
|
||||||
function setCookie(c_name,value,expiredays)
|
|
||||||
{
|
|
||||||
var exdate=new Date()
|
|
||||||
exdate.setDate(exdate.getDate()+expiredays)
|
|
||||||
document.cookie=c_name+ "=" +escape(value)+
|
|
||||||
((expiredays==null) ? "" : ";expires="+exdate.toGMTString())
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCookie(c_name)
|
|
||||||
{
|
|
||||||
if (document.cookie.length>0)
|
|
||||||
{
|
|
||||||
c_start=document.cookie.indexOf(c_name + "=")
|
|
||||||
if (c_start!=-1)
|
|
||||||
{
|
|
||||||
c_start=c_start + c_name.length+1
|
|
||||||
c_end=document.cookie.indexOf(";",c_start)
|
|
||||||
if (c_end==-1) c_end=document.cookie.length
|
|
||||||
return unescape(document.cookie.substring(c_start,c_end))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
icons in this folder are copied from the kde oxygen set
|
|
||||||
|
|
||||||
http://www.oxygen-icons.org/?page_id=4
|
|
||||||
|
|
||||||
LICENCE:
|
|
||||||
Oxygen icon theme is dual licensed.
|
|
||||||
You may copy it under the Creative Common Attribution-ShareAlike 3.0 License
|
|
||||||
or the GNU Library General Public License.
|
|
||||||
|
|
||||||
ICONS NOT UNDER THIS LICENCE:
|
|
||||||
*none yet
|
|
||||||
*add them here if needed.
|
|
||||||
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 868 B |
|
Before Width: | Height: | Size: 692 B |
|
Before Width: | Height: | Size: 525 B |
|
Before Width: | Height: | Size: 632 B |
|
Before Width: | Height: | Size: 625 B |
|
Before Width: | Height: | Size: 813 B |
|
Before Width: | Height: | Size: 788 B |
|
Before Width: | Height: | Size: 792 B |
|
Before Width: | Height: | Size: 562 B |
|
Before Width: | Height: | Size: 746 B |
|
Before Width: | Height: | Size: 761 B |
|
Before Width: | Height: | Size: 820 B |
|
Before Width: | Height: | Size: 860 B |
|
Before Width: | Height: | Size: 484 B |
|
Before Width: | Height: | Size: 874 B |
|
Before Width: | Height: | Size: 722 B |
|
Before Width: | Height: | Size: 525 B |
|
Before Width: | Height: | Size: 539 B |
@ -1,5 +0,0 @@
|
|||||||
images from the kde-oxygen set.
|
|
||||||
See webui/scripts/copy_icons.py for mapping.
|
|
||||||
See LICENSE for a list of icons not taken from oxygen.
|
|
||||||
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 541 B |
|
Before Width: | Height: | Size: 501 B |
|
Before Width: | Height: | Size: 499 B |
|
Before Width: | Height: | Size: 775 B |
|
Before Width: | Height: | Size: 525 B |
|
Before Width: | Height: | Size: 844 B |
|
Before Width: | Height: | Size: 931 B |
|
Before Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 722 B |
|
Before Width: | Height: | Size: 722 B |