remove old webui

This commit is contained in:
Damien Churchill 2009-03-06 15:01:05 +00:00
parent d4bc535027
commit fe6f045be1
241 changed files with 0 additions and 22899 deletions

View File

@ -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

View File

@ -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

View File

@ -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.
#

View File

@ -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()

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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>&#x25b6;</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)

View File

@ -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()

View File

@ -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"

View File

@ -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())

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -1,3 +0,0 @@
based on django rev.7350
,/django/ contains the parts of django required to run newforms.

View File

@ -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

View File

@ -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)

View File

@ -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='/#%[]=:;$&()+,!?*')

View File

@ -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)

View File

@ -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 = ['(', '<', '&lt;']
TRAILING_PUNCTUATION = ['.', ',', ')', '>', '\n', '&gt;']
# List of possible strings used for bullets in bulleted lists.
DOTS = ['&middot;', '*', u'\xe2\x80\xa2', '&#149;', '&bull;', '&#8226;']
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>(?:&nbsp;|\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('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;'))
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('&amp;', 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>&nbsp;&nbsp;</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>&nbsp;&nbsp;</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)

View File

@ -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]

View File

@ -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))

View File

@ -1,9 +0,0 @@
try:
_('translate something')
except:
import gettext
gettext.install('locale')
ugettext = _
ugettext_lazy = _

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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]

View File

@ -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.

View File

@ -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())

View File

@ -1,2 +0,0 @@
#compatibility: use the included version/release of web.py.
from webpy022 import *

View File

@ -1 +0,0 @@
http://webpy.org/

View File

@ -1,3 +0,0 @@
web.py is public domain.
see : http://webpy.org/

View File

@ -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()

View File

@ -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.

View File

@ -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)

View File

@ -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()

View File

@ -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>&#x25b6;</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)

View File

@ -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))

View File

@ -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()

View File

@ -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()

View File

@ -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("<'&\\">")
'&lt;&#39;&amp;&quot;&gt;'
"""
text = text.replace("&", "&amp;") # Must be done first!
text = text.replace("<", "&lt;")
text = text.replace(">", "&gt;")
text = text.replace("'", "&#39;")
text = text.replace('"', "&quot;")
return text
def websafe(val):
"""
Converts `val` so that it's safe for use in UTF-8 HTML.
>>> websafe("<'&\\">")
'&lt;&#39;&amp;&quot;&gt;'
>>> 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()

View File

@ -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))

View File

@ -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()

View File

@ -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('<', '&lt;')
# TODO: automatically get page title?
text = r_url.sub(r'<\1>', text)
text = markdown(text)
return text
if __name__ == "__main__":
import doctest
doctest.testmod()

View File

@ -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

View File

@ -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, '')))

View File

@ -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.

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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']

View File

@ -1 +0,0 @@
185

View File

@ -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)

View File

@ -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 '*.*~'

View File

@ -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)

View File

@ -1 +0,0 @@
curl -F torrent=@./test1.torrent -F pwd=deluge http://localhost:8112/remote/torrent/add

View File

@ -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")

View File

@ -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 )

View File

@ -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')

View File

@ -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);
}
});

View File

@ -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 ""
}

View File

@ -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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 868 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 692 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 632 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 625 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 813 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 788 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 562 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 746 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 761 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 860 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 484 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 874 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 722 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 539 B

View File

@ -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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 541 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 501 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 775 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 844 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 931 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 722 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 722 B

Some files were not shown because too many files have changed in this diff Show More