summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael DeHaan <mdehaan@redhat.com>2008-07-08 12:21:57 -0400
committerMichael DeHaan <mdehaan@redhat.com>2008-07-08 12:21:57 -0400
commit692807b31f32d79c90e202a8edca3508dbd03e6b (patch)
tree4e6dc47e8a7f61d2477b9837a527407e51ac6162
parentcd7320d59ec7f6acc087507686a05711a59654bc (diff)
parent2db8f86f027509846d2bf0c0af801c902f124e78 (diff)
downloadfunc-692807b31f32d79c90e202a8edca3508dbd03e6b.tar.gz
func-692807b31f32d79c90e202a8edca3508dbd03e6b.tar.xz
func-692807b31f32d79c90e202a8edca3508dbd03e6b.zip
Merge branch 'makkalot_extreme'
-rw-r--r--func/minion/modules/echo.py131
-rw-r--r--funcweb/AUTHORS15
-rw-r--r--funcweb/LICENSE340
-rw-r--r--funcweb/MANIFEST.in8
-rwxr-xr-xfuncweb/Makefile87
-rw-r--r--funcweb/etc/funcweb.conf18
-rw-r--r--funcweb/etc/prod.cfg66
-rw-r--r--funcweb/funcweb.spec138
-rwxr-xr-xfuncweb/funcweb/Makefile6
-rw-r--r--funcweb/funcweb/commands.py22
-rw-r--r--funcweb/funcweb/controllers.py166
-rwxr-xr-xfuncweb/funcweb/identity/Makefile5
-rwxr-xr-xfuncweb/funcweb/static/css/Makefile6
-rw-r--r--funcweb/funcweb/static/css/expanding_form.css6
-rwxr-xr-xfuncweb/funcweb/static/images/Makefile6
-rw-r--r--funcweb/funcweb/static/images/loading.gifbin46766 -> 36115 bytes
-rwxr-xr-xfuncweb/funcweb/static/javascript/Makefile6
-rw-r--r--funcweb/funcweb/static/javascript/expanding_form.js135
-rwxr-xr-xfuncweb/funcweb/templates/Makefile6
-rw-r--r--funcweb/funcweb/templates/master.html5
-rw-r--r--funcweb/funcweb/templates/method_args.html1
-rw-r--r--funcweb/funcweb/templates/minions.html2
-rw-r--r--funcweb/funcweb/templates/repeater_form.kid30
-rwxr-xr-xfuncweb/funcweb/tests/Makefile5
-rw-r--r--funcweb/funcweb/tests/test_client_rendering.py45
-rw-r--r--funcweb/funcweb/tests/test_widget_automation.py88
-rw-r--r--funcweb/funcweb/tests/test_widget_validation.py86
-rw-r--r--funcweb/funcweb/widget_automation.py96
-rw-r--r--funcweb/funcweb/widget_validation.py29
-rwxr-xr-xfuncweb/init-scripts/funcwebd115
-rw-r--r--funcweb/setup.py20
-rw-r--r--funcweb/version1
-rw-r--r--test/unittest/test_client.py32
33 files changed, 1616 insertions, 106 deletions
diff --git a/func/minion/modules/echo.py b/func/minion/modules/echo.py
new file mode 100644
index 0000000..31ecf5f
--- /dev/null
+++ b/func/minion/modules/echo.py
@@ -0,0 +1,131 @@
+"""
+Test module for rendering funcweb
+"""
+
+import func_module
+
+class EchoTest(func_module.FuncModule):
+
+ version = "0.0.1"
+ api_version = "0.0.1"
+ description = "Module that all of its methods returns back the same thing it recieves!"
+
+ def run_string(self, command):
+ """
+ Run String
+ """
+ return str(command)
+
+ def run_int(self,command):
+ """
+ Run Integer
+ """
+ return int(command)
+
+ def run_float(self,command):
+ """
+ Run float
+ """
+ return float(command)
+
+ def run_options(self,command):
+ """
+ Run options
+ """
+ return str(command)
+
+ def run_list(self,command):
+ """
+ Run a list
+ """
+ return command
+
+ def run_hash(self,command):
+ """
+ Run hash
+ """
+
+ return command
+
+ def run_boolean(self,command):
+ """
+ Run boolean
+ """
+ return command
+
+ def register_method_args(self):
+ """
+ Implementing the argument getter
+ """
+ return {
+ 'run_string':{
+ 'args':
+ {
+ 'command':{
+ 'type':'string',
+ 'optional':False
+ }
+ },
+ 'description':'Returns back a string'
+ },
+ 'run_int':{
+ 'args':
+ {
+ 'command':{
+ 'type':'int',
+ 'optional':False
+ }
+ },
+ 'description':'Returns back an integer'
+ },
+ 'run_float':{
+ 'args':
+ {
+ 'command':{
+ 'type':'float',
+ 'optional':False
+ },
+ },
+ 'description':'Returns back a float'
+ },
+ 'run_options':{
+ 'args':{
+ 'command':{
+ 'type':'string',
+ 'optional':False,
+ 'options':['first_option','second_option','third_option']
+ },
+ },
+ 'description':'Getting the status of the service_name'
+ },
+ 'run_list':{
+ 'args':
+ {
+ 'command':{
+ 'type':'list',
+ 'optional':False
+ }
+ },
+ 'description':'Returns back a list'
+ },
+ 'run_hash':{
+ 'args':
+ {
+ 'command':{
+ 'type':'hash',
+ 'optional':False
+ }
+ },
+ 'description':'Returns back a hash'
+ },
+ 'run_boolean':{
+ 'args':
+ {
+ 'command':{
+ 'type':'boolean',
+ 'optional':False
+ }
+ },
+ 'description':'Returns back a boolean'
+ }
+ }
diff --git a/funcweb/AUTHORS b/funcweb/AUTHORS
new file mode 100644
index 0000000..375480b
--- /dev/null
+++ b/funcweb/AUTHORS
@@ -0,0 +1,15 @@
+
+funcweb is written and maintained by (alphabetically) ...
+
+ Michael DeHaan <mdehaan@redhat.com>
+ Adrian Likins <alikins@redhat.com>
+ Seth Vidal <skvidal@redhat.com>
+ ...
+
+Additional patches and contributions by ...
+
+ Denis Kurov <makkalot@gmail.com>
+ Luke Macken <lmacken@redhat.com>
+ [ send in patches to get your name here ]
+
+
diff --git a/funcweb/LICENSE b/funcweb/LICENSE
new file mode 100644
index 0000000..08ddefd
--- /dev/null
+++ b/funcweb/LICENSE
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) 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
+this service 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 make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. 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.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+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 Program or any portion
+of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+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 Program, 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 Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) 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; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, 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 executable. However, as a
+special exception, the source code 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.
+
+If distribution of executable or 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 counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program 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.
+
+ 5. 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 Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program 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 to
+this License.
+
+ 7. 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 Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program 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 Program.
+
+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.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program 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.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the 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 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 Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, 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
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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.
+
+ 12. 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 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.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. 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 program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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 2 of the License, 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.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
+
diff --git a/funcweb/MANIFEST.in b/funcweb/MANIFEST.in
new file mode 100644
index 0000000..388846a
--- /dev/null
+++ b/funcweb/MANIFEST.in
@@ -0,0 +1,8 @@
+include version
+recursive-include etc *
+recursive-include init-scripts *
+recursive-include funcweb *
+include AUTHORS
+include LICENSE
+include README
+
diff --git a/funcweb/Makefile b/funcweb/Makefile
new file mode 100755
index 0000000..d9a865a
--- /dev/null
+++ b/funcweb/Makefile
@@ -0,0 +1,87 @@
+VERSION = $(shell echo `awk '{ print $$1 }' version`)
+RELEASE = $(shell echo `awk '{ print $$2 }' version`)
+NEWRELEASE = $(shell echo $$(($(RELEASE) + 1)))
+
+
+TOPDIR = $(shell pwd)
+DIRS = funcweb funcweb/config funcweb/identity funcweb/templates funcweb/tests funcweb/static \
+ funcweb/static/javascript funcweb/static/css funcweb/static/images
+PYDIRS = funcweb funcweb/identity funcweb/tests
+INITDIR = init-scripts
+
+all: rpms
+
+bumprelease:
+ -echo "$(VERSION) $(NEWRELEASE)" > version
+
+setversion:
+ -echo "$(VERSION) $(RELEASE)" > version
+
+build: clean
+ python setup.py build -f
+
+clean:
+ -rm -f MANIFEST
+ -rm -rf build/
+ -rm -rf dist/
+ -rm -rf *~
+ -rm -rf rpm-build/
+ -for d in $(DIRS); do ($(MAKE) -C $$d clean ); done
+
+clean_hard:
+ -rm -rf $(shell python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")/funcweb
+
+clean_harder:
+ -rm -rf /etc/httpd/conf.d/funcweb.conf
+ -rm -rf /etc/funcweb
+
+clean_hardest: clean_rpms
+
+sdist:
+ python setup.py sdist
+
+install: build
+ python setup.py install -f
+
+install_hard: clean_hard install
+
+install_harder: clean_harder install
+
+install_hardest: clean_harder clean_rpms rpms install_rpm restart
+
+install_rpm:
+ -rpm -Uvh rpm-build/funcweb-$(VERSION)-$(RELEASE)$(shell rpm -E "%{?dist}").noarch.rpm
+
+restart:
+ -/etc/init.d/funcwebd restart
+
+recombuild: install_harder restart
+
+clean_rpms:
+ -rpm -e funcweb
+
+new-rpms: bumprelease rpms
+
+pychecker:
+ -for d in $(PYDIRS); do ($(MAKE) -C $$d pychecker ); done
+pyflakes:
+ -for d in $(PYDIRS); do ($(MAKE) -C $$d pyflakes ); done
+
+money: clean
+ -sloccount --addlang "makefile" $(TOPDIR) $(PYDIRS) $(EXAMPLEDIR) $(INITDIR)
+
+unittest:
+ -nosetests -v -w funcweb/tests
+
+rpms: build sdist
+ mkdir -p rpm-build
+ cp dist/*.gz rpm-build/
+ cp version rpm-build/
+ rpmbuild --define "_topdir %(pwd)/rpm-build" \
+ --define "_builddir %{_topdir}" \
+ --define "_rpmdir %{_topdir}" \
+ --define "_srcrpmdir %{_topdir}" \
+ --define '_rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' \
+ --define "_specdir %{_topdir}" \
+ --define "_sourcedir %{_topdir}" \
+ -ba funcweb.spec
diff --git a/funcweb/etc/funcweb.conf b/funcweb/etc/funcweb.conf
new file mode 100644
index 0000000..2b73eb7
--- /dev/null
+++ b/funcweb/etc/funcweb.conf
@@ -0,0 +1,18 @@
+NameVirtualHost 127.0.0.1:443
+
+<VirtualHost 127.0.0.1:443>
+ ServerName 127.0.0.1
+ SSLEngine on
+ SSLCertificateFile /etc/pki/tls/certs/localhost.crt
+ SSLCertificateKeyFile /etc/pki/tls/private/localhost.key
+ Errorlog /var/log/httpd/funcweb-error
+ Customlog /var/log/httpd/funcweb-access common
+ UseCanonicalName Off
+ ServerSignature Off
+ AddDefaultCharset utf-8
+ ProxyPreserveHost On
+ ProxyRequests Off
+ #ProxyPass /static/ !
+ ProxyPass / http://127.0.0.1:51236/
+ ProxyPassReverse / http://127.0.0.1:51236/
+</VirtualHost>
diff --git a/funcweb/etc/prod.cfg b/funcweb/etc/prod.cfg
new file mode 100644
index 0000000..91d8e3f
--- /dev/null
+++ b/funcweb/etc/prod.cfg
@@ -0,0 +1,66 @@
+[global]
+# This is where all of your settings go for your development environment
+# Settings that are the same for both development and production
+# (such as template engine, encodings, etc.) all go in
+# funcweb/config/app.cfg
+
+# DATABASE (We're trying our best to avoid this)
+
+# driver://username:password@host:port/database
+
+# pick the form for your database
+# sqlalchemy.dburi="postgres://username@hostname/databasename"
+# sqlalchemy.dburi="mysql://username:password@hostname:port/databasename"
+# sqlalchemy.dburi="sqlite://%(current_dir_uri)s/devdata.sqlite"
+
+# If you have sqlite, here's a simple default to get you started
+# in development
+# sqlalchemy.dburi="sqlite:///devdata.sqlite"
+base_url_filter.on = True
+base_url_filter.use_x_forwarded_host = True
+
+# SERVER
+
+# Some server parameters that you may want to tweak
+server.socket_port=51236
+
+# Enable the debug output at the end on pages.
+# log_debug_info_filter.on = False
+
+server.socket_host="127.0.0.1"
+server.environment="production"
+autoreload.package="funcweb"
+
+# Auto-Reload after code modification
+# autoreload.on = True
+
+# Set to True if you'd like to abort execution if a controller gets an
+# unexpected parameter. False by default
+tg.strict_parameters = True
+
+# LOGGING
+# Logging configuration generally follows the style of the standard
+# Python logging module configuration. Note that when specifying
+# log format messages, you need to use *() for formatting variables.
+# Deployment independent log configuration is in funcweb/config/log.cfg
+[logging]
+
+[[loggers]]
+[[[funcweb]]]
+level='DEBUG'
+qualname='funcweb'
+handlers=['error_out']
+
+
+[[[access]]]
+level='INFO'
+qualname='turbogears.access'
+handlers=['error_out']
+propagate=0
+
+[[[identity]]]
+level='INFO'
+qualname='turbogears.identity'
+handlers=['error_out']
+propagate=0
+
diff --git a/funcweb/funcweb.spec b/funcweb/funcweb.spec
new file mode 100644
index 0000000..54c41a1
--- /dev/null
+++ b/funcweb/funcweb.spec
@@ -0,0 +1,138 @@
+
+%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
+
+%define is_suse %(test -e /etc/SuSE-release && echo 1 || echo 0)
+
+%define version 0.1
+Summary: Web GUI for FUNC API
+Name: funcweb
+Source1: version
+Version: %(echo `awk '{ print $1 }' %{SOURCE1}`)
+Release: %(echo `awk '{ print $2 }' %{SOURCE1}`)%{?dist}
+License: GPLv2+
+Group: Applications/System
+Source0: %{name}-%{version}.tar.gz
+
+#packages that are required
+Requires: python >= 2.3
+Requires: func >= 0.20
+Requires: certmaster >= 0.1
+Requires: mod_ssl >= 2.0
+Requires: httpd >= 2.0
+Requires: TurboGears >= 1.0.4.2
+
+#the build requires
+BuildRequires: python-devel
+BuildRequires: TurboGears >= 1.0.4.2
+%if %is_suse
+BuildRequires: gettext-devel
+%else
+%if 0%{?fedora} >= 8
+BuildRequires: python-setuptools-devel
+%else
+BuildRequires: python-setuptools
+%endif
+%endif
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
+BuildArch: noarch
+Url: https://hosted.fedoraproject.org/projects/func/
+%description
+
+FuncWeb is the Web GUI management tool for commandline based tool func.
+
+%prep
+%setup -q
+
+%build
+%{__python} setup.py build
+
+%install
+test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT
+%{__python} setup.py install --prefix=/usr --root=$RPM_BUILD_ROOT
+
+%clean
+rm -fr $RPM_BUILD_ROOT
+
+%files
+%defattr(-, root, root, -)
+%if 0%{?fedora} >= 8
+%dir %{python_sitelib}/funcweb*egg-info
+%{python_sitelib}/funcweb*egg-info/*
+%endif
+
+#creating the directory structure
+%dir %{python_sitelib}/funcweb/
+%dir %{python_sitelib}/funcweb/config
+%dir %{python_sitelib}/funcweb/templates
+%dir %{python_sitelib}/funcweb/tests
+%dir %{python_sitelib}/funcweb/static
+%dir %{python_sitelib}/funcweb/static/css
+%dir %{python_sitelib}/funcweb/static/images
+%dir %{python_sitelib}/funcweb/static/javascript
+%dir %{python_sitelib}/funcweb/identity
+%dir %{_sysconfdir}/%{name}
+%config(noreplace) %{_sysconfdir}/httpd/conf.d/funcweb.conf
+%config(noreplace) %{_sysconfdir}/%{name}/prod.cfg
+
+#adding the server startup shutdown thing
+/etc/init.d/funcwebd
+
+#the python files for funcweb
+%{python_sitelib}/funcweb/*.py*
+%{python_sitelib}/funcweb/Makefile
+%{python_sitelib}/funcweb/config/*.py*
+%{python_sitelib}/funcweb/config/*.cfg
+%{python_sitelib}/funcweb/templates/*.py*
+%{python_sitelib}/funcweb/templates/*.kid
+%{python_sitelib}/funcweb/templates/*.html
+%{python_sitelib}/funcweb/templates/Makefile
+%{python_sitelib}/funcweb/tests/*.py*
+%{python_sitelib}/funcweb/tests/Makefile
+%{python_sitelib}/funcweb/static/css/*.css
+%{python_sitelib}/funcweb/static/css/Makefile
+%{python_sitelib}/funcweb/static/images/*.png
+%{python_sitelib}/funcweb/static/images/*.ico
+%{python_sitelib}/funcweb/static/images/*.gif
+%{python_sitelib}/funcweb/static/images/Makefile
+%{python_sitelib}/funcweb/static/javascript/*.js
+%{python_sitelib}/funcweb/static/javascript/Makefile
+%{python_sitelib}/funcweb/identity/*.py*
+%{python_sitelib}/funcweb/identity/Makefile
+/usr/bin/funcwebd
+%doc README
+
+%post
+# for suse
+if [ -x /usr/lib/lsb/install_initd ]; then
+ /usr/lib/lsb/install_initd /etc/init.d/funcwebd
+# for red hat distros
+elif [ -x /sbin/chkconfig ]; then
+ /sbin/chkconfig --add funcwebd
+# or, the old fashioned way
+else
+ for i in 2 3 4 5; do
+ ln -sf /etc/init.d/funcwebd /etc/rc.d/rc${i}.d/S99funcwebd
+ done
+ for i in 1 6; do
+ ln -sf /etc/init.d/funcwebd /etc/rc.d/rc${i}.d/S99funcwebd
+ done
+fi
+
+#before uninstall the things
+%preun
+if [ "$1" = 0 ] ; then
+ /etc/init.d/funcwebd stop > /dev/null 2>&1
+ if [ -x /usr/lib/lsb/remove_initd ]; then
+ /usr/lib/lsb/remove_initd /etc/init.d/funcwebd
+ elif [ -x /sbin/chkconfig ]; then
+ /sbin/chkconfig --del funcwebd
+ else
+ rm -f /etc/rc.d/rc?.d/???funcwebd
+ fi
+fi
+
+
+%changelog
+* Sat Jul 05 2008 Denis Kurov <makkalot@gmail.com> - 0.1
+- The first RPM for funcweb with new dynamic widget stuff
+
diff --git a/funcweb/funcweb/Makefile b/funcweb/funcweb/Makefile
new file mode 100755
index 0000000..d2a3a2b
--- /dev/null
+++ b/funcweb/funcweb/Makefile
@@ -0,0 +1,6 @@
+clean::
+ @rm -fv *.pyc *~ .*~ *.pyo
+ @find . -name .\#\* -exec rm -fv {} \;
+ @rm -fv *.rpm
+ -for d in $(DIRS); do ($(MAKE) -C $$d clean ); done
+
diff --git a/funcweb/funcweb/commands.py b/funcweb/funcweb/commands.py
index 043ce1f..154309a 100644
--- a/funcweb/funcweb/commands.py
+++ b/funcweb/funcweb/commands.py
@@ -11,6 +11,7 @@ pkg_resources.require("TurboGears")
import turbogears
import cherrypy
+import func.utils as futils
cherrypy.lowercase_api = True
@@ -30,17 +31,25 @@ def start():
# is probably installed and we'll look first for a file called
# 'prod.cfg' in the current directory and then for a default
# config file called 'default.cfg' packaged in the egg.
- if len(sys.argv) > 1:
+ if exists("/etc/funcweb/prod.cfg"):
+ print "I use the production ito the etc !"
+ configfile = "/etc/funcweb/prod.cfg"
+
+ elif len(sys.argv) > 1:
+ print "We got something from the sys"
configfile = sys.argv[1]
elif exists(join(setupdir, "setup.py")):
+ print "I use the dev one into the dev dir"
configfile = join(setupdir, "dev.cfg")
elif exists(join(curdir, "prod.cfg")):
+ print "I use the prod one into the cur dir"
configfile = join(curdir, "prod.cfg")
else:
try:
configfile = pkg_resources.resource_filename(
pkg_resources.Requirement.parse("funcweb"),
"config/default.cfg")
+ print "That is another default conf"
except pkg_resources.DistributionNotFound:
raise ConfigurationError("Could not find default configuration.")
@@ -48,5 +57,12 @@ def start():
modulename="funcweb.config")
from funcweb.controllers import Root
-
- turbogears.start_server(Root())
+
+ if exists("/etc/funcweb/prod.cfg"):
+ futils.daemonize("/var/run/funcwebd.pid")
+ #then start the server
+ try:
+ turbogears.start_server(Root())
+ except Exception,e:
+ print "Exception occured :",e
+ sys.exit(1)
diff --git a/funcweb/funcweb/controllers.py b/funcweb/funcweb/controllers.py
index f2c030f..b2748a6 100644
--- a/funcweb/funcweb/controllers.py
+++ b/funcweb/funcweb/controllers.py
@@ -28,14 +28,33 @@ def validate_decorator_updater(validator_value=None):
class Root(controllers.RootController):
-
+ #preventing the everytime polling and getting
+ #func = Overlord("name") thing
+ func_cache={
+ 'fc_object':None,#the fc = Overlord() thing,
+ 'glob':None,
+ 'minion_name':None,
+ 'module_name':None,
+ 'modules':None,
+ 'minions':None,
+ 'methods':None
+ }
#will be reused for widget validation
@expose(template="funcweb.templates.minions")
@identity.require(identity.not_anonymous())
def minions(self, glob='*'):
""" Return a list of our minions that match a given glob """
- return dict(minions=Minions(glob).get_all_hosts())
+ #make the cache thing
+ if self.func_cache['glob'] == glob:
+ minions = self.func_cache['minions']
+ else:
+ #we dont have it it is for first time so lets pull it
+ minions=Minions(glob).get_all_hosts()
+ self.func_cache['glob']=glob
+ self.func_cache['minions']=minions
+
+ return dict(minions=minions)
index = minions # start with our minion view, for now
@@ -46,47 +65,106 @@ class Root(controllers.RootController):
If only the minion name is given, it will display a list of modules
for that minion. If a module is supplied, it will display a list of
- methods. If a method is supplied, it will display a method execution
- form.
+ methods.
"""
- fc = Overlord(name)
- if not module: # list all modules
- #just list those who have get_method_args
- modules = fc.system.list_modules()
- display_modules = []
- #FIXME slow really i know !
- for module in modules.itervalues():
- for mod in module:
- #if it is not empty
- if getattr(fc,mod).get_method_args()[name]:
- display_modules.append(mod)
+ #if we have it in the cache
+ if self.func_cache['minion_name'] == name:
+ fc = self.func_cache['fc_object']
+ else:
+ fc = Overlord(name)
+ self.func_cache['fc_object']=fc
+ self.func_cache['minion_name']=name
+ #reset the children :)
+ self.func_cache['module_name']=None
+ self.func_cache['modules']=None
+ self.func_cache['methods']=None
+
+ #should also reset the other fields or not ?
+
+
+ if not module:
+ if not self.func_cache['modules']:
+ modules = fc.system.list_modules()
+ display_modules = []
+
+ for module in modules.itervalues():
+ for mod in module:
+ #if it is not empty
+ if getattr(fc,mod).get_method_args()[name]:
+ display_modules.append(mod)
+
+ #put it into the cache to make that slow thing faster
+ self.func_cache['modules']=display_modules
+
+ else:
+ #print "Im in the cache"
+ #just list those who have get_method_args
+ display_modules = self.func_cache['modules']
+
modules = {}
modules[name]=display_modules
return dict(modules=modules)
else: # a module is specified
- if method: # minion.module.method specified; bring up execution form
- return dict(minion=name, module=module, method=method,
- tg_template="funcweb.templates.method")
- else: # return a list of methods for specified module
- modules = getattr(fc, module).list_methods()
+ if not method: # return a list of methods for specified module
+ #first check if we have it into the cache
+ if self.func_cache['module_name'] == module and self.func_cache['methods']:
+ modules = self.func_cache['methods']
+ #print "Im in the cache"
+
+ else:
+ self.func_cache['module_name']= module
+ #display the list only that is registered with register_method template !
+ registered_methods=getattr(fc,module).get_method_args()[name].keys()
+ modules = getattr(fc, module).list_methods()
+ for mods in modules.itervalues():
+ from copy import copy
+ cp_mods = copy(mods)
+ for m in cp_mods:
+ if not m in registered_methods:
+ mods.remove(m)
+
+ #store into cache if we get it again
+ self.func_cache['methods'] = modules
+ #display em
return dict(modules=modules, module=module,
tg_template="funcweb.templates.module")
+ else:
+ return "Wrong place :)"
@expose(template="funcweb.templates.method_args")
@identity.require(identity.not_anonymous())
def method_display(self,minion=None,module=None,method=None):
+ """
+ That method generates the input widget for givent method.
+ """
global global_form
- fc = Overlord(minion)
+ if self.func_cache['minion_name'] == minion:
+ fc = self.func_cache['fc_object']
+ else:
+ fc = Overlord(minion)
+ self.func_cache['fc_object']=fc
+ self.func_cache['minion_name']=minion
+ #reset the children :)
+ self.func_cache['module_name']=module
+ self.func_cache['modules']=None
+ self.func_cache['methods']=None
+
+ #get the method args
method_args = getattr(fc,module).get_method_args()
if not method_args.values():
- print "Not registered method here"
+ #print "Not registered method here"
return dict(minion_form = None,minion=minion,module=module,method=method)
minion_arguments = method_args[minion][method]['args']
+ #the description of the method we are going to display
+ if method_args[minion][method].has_key('description'):
+ description = method_args[minion][method]['description']
+ else:
+ description = None
if minion_arguments:
wlist_object = WidgetListFactory(minion_arguments,minion=minion,module=module,method=method)
wlist_object = wlist_object.get_widgetlist_object()
@@ -104,14 +182,17 @@ class Root(controllers.RootController):
del wlist_object
del minion_arguments
- return dict(minion_form =minion_form,minion=minion,module=module,method=method)
+ return dict(minion_form =minion_form,minion=minion,module=module,method=method,description=description)
else:
- return dict(minion_form = None,minion=minion,module=module,method=method)
+ return dict(minion_form = None,minion=minion,module=module,method=method,description = description)
@expose(template="funcweb.templates.login")
def login(self, forward_url=None, previous_url=None, *args, **kw):
+ """
+ The login form for not registered users
+ """
from cherrypy import request, response
if not identity.current.anonymous \
and identity.was_login_attempted() \
@@ -157,20 +238,34 @@ class Root(controllers.RootController):
@identity.require(identity.not_anonymous())
def post_form(self,**kw):
"""
- Data processing part
+ Data processing part for methods that accept some inputs.
+ Method recieves the method arguments for minion method then
+ orders them into their original order and sends the xmlrpc
+ request to the minion !
"""
if kw.has_key('minion') and kw.has_key('module') and kw.has_key('method'):
#assign them because we need the rest so dont control everytime
#and dont make lookup everytime ...
+ #the del statements above are important dont remove them :)
minion = kw['minion']
del kw['minion']
module = kw['module']
del kw['module']
method = kw['method']
del kw['method']
-
- #everytime we do that should be a clever way for that ???
- fc = Overlord(minion)
+
+ if self.func_cache['minion_name'] == minion:
+ fc = self.func_cache['fc_object']
+ else:
+ fc = Overlord(minion)
+ self.func_cache['fc_object']=fc
+ self.func_cache['minion_name']=minion
+ #reset the children :)
+ self.func_cache['module_name']=module
+ self.func_cache['modules']=None
+ self.func_cache['methods']=None
+
+
#get again the method args to get their order :
arguments=getattr(fc,module).get_method_args()
#so we know the order just allocate and put them there
@@ -196,7 +291,17 @@ class Root(controllers.RootController):
arguments so they provide only some information,executed
by pressing only the link !
"""
- fc = Overlord(minion)
+ if self.func_cache['minion_name'] == minion:
+ fc = self.func_cache['fc_object']
+ else:
+ fc = Overlord(minion)
+ self.func_cache['fc_object']=fc
+ self.func_cache['minion_name']=minion
+ #reset the children :)
+ self.func_cache['module_name']=module
+ self.func_cache['modules']=None
+ self.func_cache['methods']=None
+
result = getattr(getattr(fc,module),method)()
return str(result)
@@ -204,5 +309,8 @@ class Root(controllers.RootController):
@expose()
def logout(self):
+ """
+ The logoout part
+ """
identity.current.logout()
raise redirect("/")
diff --git a/funcweb/funcweb/identity/Makefile b/funcweb/funcweb/identity/Makefile
new file mode 100755
index 0000000..ac39036
--- /dev/null
+++ b/funcweb/funcweb/identity/Makefile
@@ -0,0 +1,5 @@
+clean::
+ @rm -fv *.pyc *~ .*~ *.pyo
+ @find . -name .\#\* -exec rm -fv {} \;
+ @rm -fv *.rpm
+ -for d in $(DIRS); do ($(MAKE) -C $$d clean ); done
diff --git a/funcweb/funcweb/static/css/Makefile b/funcweb/funcweb/static/css/Makefile
new file mode 100755
index 0000000..d2a3a2b
--- /dev/null
+++ b/funcweb/funcweb/static/css/Makefile
@@ -0,0 +1,6 @@
+clean::
+ @rm -fv *.pyc *~ .*~ *.pyo
+ @find . -name .\#\* -exec rm -fv {} \;
+ @rm -fv *.rpm
+ -for d in $(DIRS); do ($(MAKE) -C $$d clean ); done
+
diff --git a/funcweb/funcweb/static/css/expanding_form.css b/funcweb/funcweb/static/css/expanding_form.css
new file mode 100644
index 0000000..d7924c7
--- /dev/null
+++ b/funcweb/funcweb/static/css/expanding_form.css
@@ -0,0 +1,6 @@
+li.appendableformfieldlist ul {
+ list-style-type: none;
+}
+li.appendableformfieldlist ul li {
+ display: inline;
+}
diff --git a/funcweb/funcweb/static/images/Makefile b/funcweb/funcweb/static/images/Makefile
new file mode 100755
index 0000000..d2a3a2b
--- /dev/null
+++ b/funcweb/funcweb/static/images/Makefile
@@ -0,0 +1,6 @@
+clean::
+ @rm -fv *.pyc *~ .*~ *.pyo
+ @find . -name .\#\* -exec rm -fv {} \;
+ @rm -fv *.rpm
+ -for d in $(DIRS); do ($(MAKE) -C $$d clean ); done
+
diff --git a/funcweb/funcweb/static/images/loading.gif b/funcweb/funcweb/static/images/loading.gif
index 83729ec..794a549 100644
--- a/funcweb/funcweb/static/images/loading.gif
+++ b/funcweb/funcweb/static/images/loading.gif
Binary files differ
diff --git a/funcweb/funcweb/static/javascript/Makefile b/funcweb/funcweb/static/javascript/Makefile
new file mode 100755
index 0000000..d2a3a2b
--- /dev/null
+++ b/funcweb/funcweb/static/javascript/Makefile
@@ -0,0 +1,6 @@
+clean::
+ @rm -fv *.pyc *~ .*~ *.pyo
+ @find . -name .\#\* -exec rm -fv {} \;
+ @rm -fv *.rpm
+ -for d in $(DIRS); do ($(MAKE) -C $$d clean ); done
+
diff --git a/funcweb/funcweb/static/javascript/expanding_form.js b/funcweb/funcweb/static/javascript/expanding_form.js
new file mode 100644
index 0000000..2c1b67f
--- /dev/null
+++ b/funcweb/funcweb/static/javascript/expanding_form.js
@@ -0,0 +1,135 @@
+
+/* Based on dynwidget by Randall Smith */
+
+var ExpandingForm = {
+
+ li_count: null,
+ li_template: null,
+
+ removeItem: function(node_id) {
+ this_node = document.getElementById(node_id);
+ parent_node = this_node.parentNode;
+ list_items = ExpandingForm.getChildNodesByAttribute(parent_node, 'tagname', 'TR')
+ ExpandingForm.updateVars(parent_node.parentNode);
+ if (list_items.length == 1) {
+ alert('This item cannot be removed.')
+ }
+ else {
+ parent_node.removeChild(this_node);
+ }
+ },
+
+ getChildNodesByAttribute: function(pnode, identifier, value) {
+ new_nodes = new Array;
+ nodes = pnode.childNodes;
+ for (node_id=0, len=nodes.length; node_id<len; node_id++) {
+ node = nodes[node_id];
+ if (identifier == 'tagname') {
+ if (node && node.tagName == value) {
+ new_nodes.push(node);
+ }
+ }
+ else if (node && node.getAttribute(identifier) == value) {
+ new_nodes.push(node);
+ }
+ }
+ return new_nodes;
+ },
+
+ updateVars: function(parent_node){
+ tbody = ExpandingForm.getChildNodesByAttribute(parent_node, 'tagname', 'TBODY')[0];
+ list_items = ExpandingForm.getChildNodesByAttribute(tbody, 'tagname', 'TR');
+ ExpandingForm['li_template'] = list_items[0];
+ ExpandingForm['li_count'] = list_items.length
+ },
+
+ addItem: function(parent_id) {
+ var parent_node = document.getElementById(parent_id);
+ ExpandingForm.updateVars(parent_node);
+ tbody = ExpandingForm.getChildNodesByAttribute(parent_node, 'tagname', 'TBODY')[0];
+ list_items = ExpandingForm.getChildNodesByAttribute(tbody, 'tagname', 'TR');
+ li_clone = ExpandingForm.li_template.cloneNode(true);
+ // Fix the labels.
+/* labels = li_clone.getElementsByTagName('LABEL')
+ for (node_id=0, len=labels.length; node_id<len; node_id++) {
+ label = labels[node_id];
+ // Why am I having to check for the node type?
+ if (label.nodeType == 1) {
+ label.setAttribute('for', label.getAttribute('for').replace(
+ '_0_', '_' + ExpandingForm.li_count + '_'));
+ }
+ }
+*/ // Fix the input values.
+ inputs = li_clone.getElementsByTagName('INPUT')
+ for (node_id=0, len=inputs.length; node_id<len; node_id++) {
+ input = inputs[node_id];
+ if (input.nodeType == 1) {
+ input.setAttribute('id', input.getAttribute('id').replace(
+ '_0_', '_' + ExpandingForm.li_count + '_'));
+ if (input.getAttribute('name'))
+ {
+ input.setAttribute('name', input.getAttribute('name').replace(
+ '-0', '-' + ExpandingForm.li_count));
+ }
+ if (input.getAttribute('type') == 'button')
+ {
+ input.setAttribute('value', input.getAttribute('value'))
+ }
+ else
+ {
+ input.value = '';
+ }
+ }
+ }
+ inputs = li_clone.getElementsByTagName('SELECT')
+ for (node_id=0, len=inputs.length; node_id<len; node_id++) {
+ input = inputs[node_id];
+ if (input.nodeType == 1) {
+ input.setAttribute('id', input.getAttribute('id').replace(
+ '_0_', '_' + ExpandingForm.li_count + '_'));
+ input.setAttribute('name', input.getAttribute('name').replace(
+ '-0', '-' + ExpandingForm.li_count));
+ input.value = '';
+ }
+ }
+ li_clone.setAttribute('id', li_clone.getAttribute('id').replace(
+ '_0', '_' + ExpandingForm.li_count))
+ // Add a remove link.
+ child_tds = li_clone.getElementsByTagName('td');
+ last_td = child_tds[child_tds.length-1];
+ a_clone = last_td.getElementsByTagName('A')[0];
+ href_text = "javascript:ExpandingForm.removeItem('" + li_clone.getAttribute('ID') + "')";
+ a_clone.setAttribute('href', href_text);
+ /*
+ link_li = document.createElement('LI');
+ link_a = document.createElement('A');
+ href_text = "javascript:ExpandingForm.removeItem('" + li_clone.getAttribute('ID') + "')";
+ link_a.setAttribute('href', href_text);
+ /*
+ text = 'Remove (-)';
+ link_text = document.createTextNode(text);
+ link_a.appendChild(link_text);
+ link_li.appendChild(link_a);
+ ul.appendChild(link_li);
+ */
+ // Finally.
+ tbody = ExpandingForm.getChildNodesByAttribute(parent_node, 'tagname', 'TBODY')[0];
+ tbody.appendChild(li_clone);
+ // Focus
+ li_clone.getElementsByTagName('INPUT')[0].focus()
+ ExpandingForm['li_count'] = ExpandingForm.li_count + 1;
+ }
+}
+
+// Not specifically part of appendable_form_field, but used by the customised version of this form
+function typeChanged(obj) {
+ var parent = obj.parentNode.parentNode.parentNode;
+ var preference = parent.getElementsByTagName("input")[1];
+ var type = obj.value;
+ if (type == 'MX') {
+ preference.style.visibility = "visible";
+ }
+ else {
+ preference.style.visibility = "hidden";
+ }
+}
diff --git a/funcweb/funcweb/templates/Makefile b/funcweb/funcweb/templates/Makefile
new file mode 100755
index 0000000..d2a3a2b
--- /dev/null
+++ b/funcweb/funcweb/templates/Makefile
@@ -0,0 +1,6 @@
+clean::
+ @rm -fv *.pyc *~ .*~ *.pyo
+ @find . -name .\#\* -exec rm -fv {} \;
+ @rm -fv *.rpm
+ -for d in $(DIRS); do ($(MAKE) -C $$d clean ); done
+
diff --git a/funcweb/funcweb/templates/master.html b/funcweb/funcweb/templates/master.html
index 073adc1..0ac8ebb 100644
--- a/funcweb/funcweb/templates/master.html
+++ b/funcweb/funcweb/templates/master.html
@@ -21,7 +21,10 @@
<style type="text/css" media="screen">
@import url("/static/css/style.css");
</style>
-
+
+ <link media="screen" href="/static/css/expanding_form.css" type="text/css" rel="stylesheet"/>
+ <script src="../static/javascript/expanding_form.js" type="text/javascript"></script>
+
<script type="text/javascript">
jQuery._$ = MochiKit.DOM.getElement;
var myj = jQuery.noConflict();
diff --git a/funcweb/funcweb/templates/method_args.html b/funcweb/funcweb/templates/method_args.html
index 2e7d4bd..870d562 100644
--- a/funcweb/funcweb/templates/method_args.html
+++ b/funcweb/funcweb/templates/method_args.html
@@ -5,6 +5,7 @@
<body>
<div py:if="minion_form" id="men">
+ <p py:if="description">Description : ${description}</p>
${ET(minion_form.display(displays_on='genshi'))}
</div>
<div py:if="not minion_form" id="men">
diff --git a/funcweb/funcweb/templates/minions.html b/funcweb/funcweb/templates/minions.html
index 6a39f54..1db0f38 100644
--- a/funcweb/funcweb/templates/minions.html
+++ b/funcweb/funcweb/templates/minions.html
@@ -10,7 +10,7 @@
<ul>
<li><h2>minions</h2></li>
<li py:for="minion in minions">
- <a onclick="jQuery('#col3').hide();myj('#col4').hide();myj('#col5').hide();getElement('col2').innerHTML=toHTML(IMG({src:'../static/images/loading.gif',width:'80',height:'80'}));myj('#col2').hide().load('/minion/${minion}').show('slow');" href="#">${minion}</a>
+ <a onclick="jQuery('#col3').hide();myj('#col4').hide();myj('#col5').hide();getElement('col2').innerHTML=toHTML(IMG({src:'../static/images/loading.gif',width:'100',height:'100'}));myj('#col2').hide().load('/minion/${minion}').show('slow');" href="#">${minion}</a>
</li>
</ul>
</div>
diff --git a/funcweb/funcweb/templates/repeater_form.kid b/funcweb/funcweb/templates/repeater_form.kid
new file mode 100644
index 0000000..606b30a
--- /dev/null
+++ b/funcweb/funcweb/templates/repeater_form.kid
@@ -0,0 +1,30 @@
+<div class="expanding_form" xmlns:py="http://purl.org/kid/ns#">
+<table id="${field_id}">
+<thead><tr>
+ <th py:for="field in fields">
+ <span class="fieldlabel" py:content="field.label" />
+ </th>
+ </tr></thead>
+ <tbody>
+ <tr py:for="repetition in repetitions"
+ class="${field_class}"
+ id="${field_id}_${repetition}">
+
+ <td py:for="field in fields">
+ <span py:content="field.display(value_for(field),
+ **params_for(field))" />
+ <span py:if="error_for(field)" class="fielderror"
+ py:content="error_for(field)" />
+ <span py:if="field.help_text" class="fieldhelp"
+ py:content="field_help_text" />
+ </td>
+ <td>
+ <a
+ href="javascript:ExpandingForm.removeItem('${field_id}_${repetition}')">Remove (-)</a>
+ </td>
+
+ </tr>
+ </tbody>
+</table>
+<a id="doclink" href="javascript:ExpandingForm.addItem('${field_id}');">Add ( + )</a>
+</div>
diff --git a/funcweb/funcweb/tests/Makefile b/funcweb/funcweb/tests/Makefile
new file mode 100755
index 0000000..ac39036
--- /dev/null
+++ b/funcweb/funcweb/tests/Makefile
@@ -0,0 +1,5 @@
+clean::
+ @rm -fv *.pyc *~ .*~ *.pyo
+ @find . -name .\#\* -exec rm -fv {} \;
+ @rm -fv *.rpm
+ -for d in $(DIRS); do ($(MAKE) -C $$d clean ); done
diff --git a/funcweb/funcweb/tests/test_client_rendering.py b/funcweb/funcweb/tests/test_client_rendering.py
new file mode 100644
index 0000000..f457da3
--- /dev/null
+++ b/funcweb/funcweb/tests/test_client_rendering.py
@@ -0,0 +1,45 @@
+from funcweb.widget_validation import WidgetSchemaFactory
+from funcweb.widget_automation import WidgetListFactory,RemoteFormAutomation,RemoteFormFactory
+from func.overlord.client import Overlord, Minions
+import socket
+import func.utils
+
+class TestClientWidgetRender(object):
+ minion = None
+
+ def test_all_minions(self):
+ minions =Minions("*").get_all_hosts()
+ for m in minions:
+ self.minion = m
+ self.remote_widget_render()
+
+ def remote_widget_render(self):
+ print "\n******testing minion : %s**********"%(self.minion)
+ fc = Overlord(self.minion)
+ modules = fc.system.list_modules()
+ display_modules={}
+
+ print "Getting the modules that has exported arguments"
+ for module in modules.itervalues():
+ for mod in module:
+ #if it is not empty
+ exported_methods = getattr(fc,mod).get_method_args()[self.minion]
+ if exported_methods:
+ print "%s will be rendered"%(mod)
+ display_modules[mod]=exported_methods
+
+ #do the rendering work here
+ for module,exp_meths in display_modules.iteritems():
+ for method_name,other_options in exp_meths.iteritems():
+ minion_arguments = other_options['args']
+ if minion_arguments:
+ wlist_object = WidgetListFactory(minion_arguments,minion=self.minion,module=module,method=method_name)
+ wlist_object = wlist_object.get_widgetlist_object()
+ #print wlist_object
+ wf = WidgetSchemaFactory(minion_arguments)
+ schema_man=wf.get_ready_schema()
+ minion_form = RemoteFormAutomation(wlist_object,schema_man)
+ print "%s.%s.%s rendered"%(self.minion,module,method_name)
+
+
+
diff --git a/funcweb/funcweb/tests/test_widget_automation.py b/funcweb/funcweb/tests/test_widget_automation.py
index d02de93..36fc387 100644
--- a/funcweb/funcweb/tests/test_widget_automation.py
+++ b/funcweb/funcweb/tests/test_widget_automation.py
@@ -2,19 +2,18 @@ import unittest
import turbogears
from turbogears import testutil
-from funcweb.widget_automation import WidgetListFactory,RemoteFormAutomation,RemoteFormFactory
-from funcweb.widget_validation import WidgetSchemaFactory
+from funcweb.widget_automation import *
+from funcweb.widget_validation import *
class TestWidgetListFactory(unittest.TestCase):
def setUp(self):
self.widget_factory = WidgetListFactory(self.get_test_default_args(),minion="myminion",module="mymodule",method="my_method")
-
-
- def tearDown(self):
- pass
def test_default_args(self):
+ """
+ Test to check the default args if they were assigned
+ """
compare_with = self.get_test_default_args()
widget_list=self.widget_factory.get_widgetlist()
@@ -22,6 +21,10 @@ class TestWidgetListFactory(unittest.TestCase):
for argument_name,argument_options in compare_with.iteritems():
assert widget_list.has_key(argument_name) == True
+ #test the label
+ assert pretty_label(argument_name) == getattr(widget_list[argument_name],'label')
+ #print getattr(widget_list[argument_name],'label')
+
#print "The argument name is :",argument_name
#because some of them dont have it like boolean
if argument_options.has_key('default'):
@@ -32,9 +35,47 @@ class TestWidgetListFactory(unittest.TestCase):
if argument_options.has_key("options"):
assert argument_options['options'] == getattr(widget_list[argument_name],"options")
-
- #that should be enough
+
+ def test_add_specialized_list(self):
+ """
+ Testing the internals of the special list widget
+ """
+ test_list_data = self.get_test_default_args()['list_default']
+ widget_list_object = self.widget_factory.get_widgetlist_object()
+ #not very efficient but works
+ #hash_widget_object should be a widgets.RepeatingFieldSet
+ list_widget_object = [h_obj for h_obj in widget_list_object if getattr(h_obj,'name')=='list_default'][0]
+
+ assert isinstance(list_widget_object.fields[0],widgets.TextField) == True
+ assert getattr(list_widget_object.fields[0],'name') == 'listfield'
+ assert getattr(list_widget_object.fields[0],'label') == 'List Field'
+
+
+ def test_add_specialized_hash(self):
+ """
+ Testing the internals of the special hash widget
+ """
+ test_hash_data = self.get_test_default_args()['hash_default']
+ widget_list_object = self.widget_factory.get_widgetlist_object()
+ #not very efficient but works
+ #hash_widget_object should be a widgets.RepeatingFieldSet
+ hash_widget_object = [h_obj for h_obj in widget_list_object if getattr(h_obj,'name')=='hash_default'][0]
+
+ #print hash_widget_object.fields
+ #check the key data
+ assert isinstance(hash_widget_object.fields[0],widgets.TextField) == True
+ assert getattr(hash_widget_object.fields[0],'name') == 'keyfield'
+ assert getattr(hash_widget_object.fields[0],'label') == 'Key Field'
+ #check the value data
+ assert isinstance(hash_widget_object.fields[1],widgets.TextField) == True
+ assert getattr(hash_widget_object.fields[1],'name') == 'valuefield'
+ assert getattr(hash_widget_object.fields[1],'label') == 'Value Field'
+
+
def test_get_widgetlist_object(self):
+ """
+ Test the final widgetlist object
+ """
compare_with = self.get_test_default_args()
widget_list_object = self.widget_factory.get_widgetlist_object()
@@ -43,7 +84,7 @@ class TestWidgetListFactory(unittest.TestCase):
all_fields = [getattr(field,"name") for field in widget_list_object]
#print all_fields
for argument_name in compare_with.keys():
- print argument_name
+ #print argument_name
assert argument_name in all_fields
#print getattr(widget_list_object,argument_name)
@@ -58,11 +99,14 @@ class TestWidgetListFactory(unittest.TestCase):
def test_remote_form_factory(self):
from turbogears.view import load_engines
load_engines()
-
+
+ schema_factory = WidgetSchemaFactory(self.get_test_default_args())
+ schema_validator=schema_factory.get_ready_schema()
+
# WidgetsList object
widget_list_object = self.widget_factory.get_widgetlist_object()
#print widget_list_object
- remote_form = RemoteFormFactory(widget_list_object).get_remote_form()
+ remote_form = RemoteFormFactory(widget_list_object,schema_validator).get_remote_form()
#it is a key,value dict
widget_list=self.widget_factory.get_widgetlist()
@@ -75,6 +119,22 @@ class TestWidgetListFactory(unittest.TestCase):
#print remote_form.render()
+
+ def test_pretty_label(self):
+ """
+ Testing the label converter util method
+ """
+ test_strings = ('service_name','some__other','cool-arg','somenormal','someweir*1*2*3*3')
+ #print pretty_label(test_strings[0])
+ assert pretty_label(test_strings[0]) == 'Service Name'
+ #print pretty_label(test_strings[1])
+ assert pretty_label(test_strings[1]) == 'Some Other'
+ #print pretty_label(test_strings[2])
+ assert pretty_label(test_strings[2]) == 'Cool Arg'
+ #print pretty_label(test_strings[3])
+ assert pretty_label(test_strings[3]) == 'Somenormal'
+ #print pretty_label(test_strings[4])
+ assert pretty_label(test_strings[4]) == 'Someweir*1*2*3*3'
def get_test_default_args(self):
return {
@@ -107,14 +167,16 @@ class TestWidgetListFactory(unittest.TestCase):
'type':'hash',
'default':'default hash',
'optional':False,
- 'description':'default description'
+ 'description':'default description',
+ 'validator':'^[0-9]*$'
},
'list_default':{
'type':'list',
'default':'default list',
'optional':False,
- 'description':'default description'
+ 'description':'default description',
+ 'validator':'^[0-9]*$'
},
#will be converted to dropdown
diff --git a/funcweb/funcweb/tests/test_widget_validation.py b/funcweb/funcweb/tests/test_widget_validation.py
index c800d41..373ce0b 100644
--- a/funcweb/funcweb/tests/test_widget_validation.py
+++ b/funcweb/funcweb/tests/test_widget_validation.py
@@ -18,23 +18,22 @@ class TestWidgetValidator(unittest.TestCase):
#do better test here
for argument_name,arg_options in self.get_string_params().iteritems():
- #print argument_name
- assert hasattr(schema_man,argument_name)==True
+ #print getattr(schema_man,'fields')
+ assert getattr(schema_man,'fields').has_key(argument_name) == True
+ current_schema_object = getattr(schema_man,'fields')[argument_name]
#not very efficient but it si just a test :)
- if argument_name != 'string_mix':
- for arg,value in arg_options.iteritems():
- #print getattr(schema_man,argument_name)
- if conversion_schema.has_key(arg):
- if hasattr(getattr(schema_man,argument_name),conversion_schema[arg]):
- #print arg,value
- #couldnt find a way to test it !??
- if arg != 'validator':
- assert getattr(getattr(schema_man,argument_name),conversion_schema[arg])==value
- #print getattr(getattr(schema_man,argument_name),conversion_schema[arg])
- else:
- #just print it to see what is inside because the test will be very hardcoded otherwise
- #print getattr(schema_man,argument_name)
+ if argument_name == 'string_mix':
continue
+ if arg_options.has_key('max_length'):
+ assert getattr(current_schema_object,'max') == arg_options['max_length']
+ if arg_options.has_key('min_length'):
+ assert getattr(current_schema_object,'min') == arg_options['min_length']
+ if arg_options.has_key('validator'):
+ assert getattr(current_schema_object,'regex')
+ if arg_options.has_key('optional'):
+ #print " ",argument_name," : ",getattr(schema_man,argument_name)
+ assert not getattr(current_schema_object,'not_empty') == arg_options['optional']
+
print "Happy tests !"
def test_int_validator(self):
@@ -43,25 +42,26 @@ class TestWidgetValidator(unittest.TestCase):
for argument_name,arg_options in self.get_int_params().iteritems():
#print argument_name
- assert hasattr(schema_man,argument_name)==True
+ assert getattr(schema_man,'fields').has_key(argument_name)==True
+ current_schema_object = getattr(schema_man,'fields')[argument_name]
#print " ",argument_name," : ",getattr(schema_man,argument_name)
#if the argument includes some range
if arg_options.has_key('range'):
#print " ",argument_name," : ",getattr(schema_man,argument_name)
- assert getattr(getattr(schema_man,argument_name),'max') == arg_options['range'][1]
- assert getattr(getattr(schema_man,argument_name),'min') == arg_options['range'][0]
+ assert getattr(current_schema_object,'max') == arg_options['range'][1]
+ assert getattr(current_schema_object,'min') == arg_options['range'][0]
if arg_options.has_key('min'):
#print " ",argument_name," : ",getattr(schema_man,argument_name)
- assert getattr(getattr(schema_man,argument_name),'min') == arg_options['min']
+ assert getattr(current_schema_object,'min') == arg_options['min']
if arg_options.has_key('max'):
#print " ",argument_name," : ",getattr(schema_man,argument_name)
- assert getattr(getattr(schema_man,argument_name),'max') == arg_options['max']
+ assert getattr(current_schema_object,'max') == arg_options['max']
if arg_options.has_key('optional'):
#print " ",argument_name," : ",getattr(schema_man,argument_name)
- assert not getattr(getattr(schema_man,argument_name),'not_empty') == arg_options['optional']
+ assert not getattr(current_schema_object,'not_empty') == arg_options['optional']
print "Happy test!"
@@ -72,20 +72,21 @@ class TestWidgetValidator(unittest.TestCase):
for argument_name,arg_options in self.get_float_params().iteritems():
#print argument_name
- assert hasattr(schema_man,argument_name)==True
+ assert getattr(schema_man,'fields').has_key(argument_name)==True
+ current_schema_object = getattr(schema_man,'fields')[argument_name]
#print " ",argument_name," : ",getattr(schema_man,argument_name)
if arg_options.has_key('min'):
#print " ",argument_name," : ",getattr(schema_man,argument_name)
- assert getattr(getattr(schema_man,argument_name),'min') == arg_options['min']
+ assert getattr(current_schema_object,'min') == arg_options['min']
if arg_options.has_key('max'):
#print " ",argument_name," : ",getattr(schema_man,argument_name)
- assert getattr(getattr(schema_man,argument_name),'max') == arg_options['max']
+ assert getattr(current_schema_object,'max') == arg_options['max']
if arg_options.has_key('optional'):
#print " ",argument_name," : ",getattr(schema_man,argument_name)
- assert not getattr(getattr(schema_man,argument_name),'not_empty') == arg_options['optional']
+ assert not getattr(current_schema_object,'not_empty') == arg_options['optional']
print "Happy test!"
@@ -98,12 +99,14 @@ class TestWidgetValidator(unittest.TestCase):
for argument_name,arg_options in testing_data.iteritems():
#print argument_name
#should all the argument names really
- assert hasattr(schema_man,argument_name)==True
+ assert getattr(schema_man,'fields').has_key(argument_name)==True
+ current_schema_object = getattr(schema_man,'fields')[argument_name]
+
#print " ",argument_name," : ",getattr(schema_man,argument_name)
if arg_options.has_key('optional'):
#print " ",argument_name," : ",getattr(schema_man,argument_name)
- assert not getattr(getattr(schema_man,argument_name),'not_empty') == arg_options['optional']
+ assert not getattr(current_schema_object,'not_empty') == arg_options['optional']
print "Happy test!"
@@ -121,16 +124,18 @@ class TestWidgetValidator(unittest.TestCase):
for argument_name,arg_options in testing_data.iteritems():
#print argument_name
#should all the argument names really
- assert hasattr(schema_man,argument_name)==True
#print " ",argument_name," : ",getattr(schema_man,argument_name)
-
+ assert getattr(schema_man,'fields').has_key(argument_name)==True
+ current_schema_object = getattr(schema_man,'fields')[argument_name]
+
+
if arg_options.has_key('validator'):
#print " ",argument_name," : ",getattr(schema_man,argument_name)
- assert getattr(getattr(schema_man,argument_name),'regex_string') == arg_options['validator']
+ assert getattr(current_schema_object,'regex_string') == arg_options['validator']
if arg_options.has_key('optional'):
#print " ",argument_name," : ",getattr(schema_man,argument_name)
- assert not getattr(getattr(schema_man,argument_name),'not_empty') == arg_options['optional']
+ assert not getattr(current_schema_object,'not_empty') == arg_options['optional']
print "Happy test!"
@@ -191,14 +196,15 @@ class TestWidgetValidator(unittest.TestCase):
#test default
mv = MinionListValidator()
- assert mv.to_python(['fedora','debian','others']) == ['fedora','debian','others']
+ [{'listfield': u'listone'}, {'listfield': u'listtwo'}]
+ assert mv.to_python([{'listfield': u'listone'}, {'listfield': u'listtwo'}]) == ['listone','listtwo']
del mv
#test with regex
mv = MinionListValidator(regex_string='^[A-Z]+$',not_empty=True)
- assert mv.to_python(['FEDORA','MACOSX']) == ['FEDORA','MACOSX']
+ assert mv.to_python([{'listfield': u'LISTONE'}, {'listfield': u'LISTTWO'}]) == ['LISTONE','LISTTWO']
self.assertRaises(validators.Invalid,mv.to_python,[])
- self.assertRaises(validators.Invalid,mv.to_python,['hey_error'])
+ self.assertRaises(validators.Invalid,mv.to_python,[{'listfield':'hey_error'}])
self.assertRaises(validators.Invalid,mv.to_python,{})
del mv
@@ -210,15 +216,15 @@ class TestWidgetValidator(unittest.TestCase):
#test default
mv = MinionHashValidator()
- assert mv.to_python({'fedora':1,'debian':2,'others':3}) == {'fedora':1,'debian':2,'others':3}
+ assert mv.to_python([{'keyfield':'keyvalue','valuefield':'valuehere'}]) == {'keyvalue':'valuehere'}
del mv
#test with regex
mv = MinionHashValidator(regex_string='^[A-Z]+$',not_empty=True)
- assert mv.to_python({'FEDORA':'FEDORA','MACOSX':'MACOSX'}) == {'FEDORA':'FEDORA','MACOSX':'MACOSX'}
+ assert mv.to_python([{'keyfield':'FEDORA','valuefield':'MACOSX'}]) == {'FEDORA':'MACOSX'}
self.assertRaises(validators.Invalid,mv.to_python,{})
- self.assertRaises(validators.Invalid,mv.to_python,{'hey_error':12})
- self.assertRaises(validators.Invalid,mv.to_python,"hwy")
+ self.assertRaises(validators.Invalid,mv.to_python,[{'keyfield':12,'valuefield':123}])
+ self.assertRaises(TypeError,mv.to_python,"hwy")
del mv
print "Happy testing !"
@@ -305,7 +311,7 @@ class TestWidgetValidator(unittest.TestCase):
'default':'hey',
'optional':False,
'description':'default regex list',
- 'validator':'^[A-Z]$'
+ 'validator':'^[A-Z]+$'
},
}
@@ -321,7 +327,7 @@ class TestWidgetValidator(unittest.TestCase):
'default':{'hey':12},
'optional':False,
'description':'default regex hash',
- 'validator':'^[A-Z]$'
+ 'validator':'^[A-Z]+$'
},
}
diff --git a/funcweb/funcweb/widget_automation.py b/funcweb/funcweb/widget_automation.py
index 5d299bd..351603b 100644
--- a/funcweb/funcweb/widget_automation.py
+++ b/funcweb/funcweb/widget_automation.py
@@ -26,9 +26,9 @@ class WidgetListFactory(object):
'default_value':"TextField",
},
'hash':{
- 'default_value':"TextArea"},
+ 'type':"RepeatingFieldSet"},
'list':{
- 'default_value':"TextArea"}
+ 'type':"RepeatingFieldSet"}
}
#will contain the input widget created in that class
@@ -83,11 +83,11 @@ class WidgetListFactory(object):
#adding the hidden fields (that part wass adde later can be made more generic)
if self.minion:
- self.__widget_list['minion']= getattr(widgets,'HiddenField')(name="minion",value=self.minion,default=self.minion)
+ self.__widget_list['minion']= getattr(widgets,'HiddenField')(name="minion",default=self.minion)
if self.module:
- self.__widget_list['module']= getattr(widgets,'HiddenField')(name="module",value=self.module,default=self.module)
+ self.__widget_list['module']= getattr(widgets,'HiddenField')(name="module",default=self.module)
if self.method:
- self.__widget_list['method']= getattr(widgets,'HiddenField')(name="method",value=self.method,default=self.method)
+ self.__widget_list['method']= getattr(widgets,'HiddenField')(name="method",default=self.method)
@@ -112,6 +112,68 @@ class WidgetListFactory(object):
self.__widget_list[argument_name]=temp_object
del temp_object
+ def __add_specialized_hash(self,argument,argument_name):
+ """
+ Specialized option adder for hash, we need it to be diffferent
+ because the hash and list objects uses an advanced type of widgets
+ which make them to be able to add, remove fields during using the
+ web UI. It uses the RepeatingFieldSet which is able to contain the
+ other normal input widgets. It will have two fields (TextFields)
+ one for key : keyfield and other for value : valuefield
+ Also the validator addition is a little bit different and should
+ be done in that method also ...
+
+ @param : argument : the argument options,
+ @param : argument_name : the name of the argument also the name of the widget
+ @return : Nothing
+ """
+ hash_repeat_data = {
+ 'template':"funcweb.templates.repeater_form",#may change that if someone doesnt like my design :)
+ 'fields': [
+ widgets.TextField(name="keyfield",label="Key Field"),
+ widgets.TextField(name="valuefield",label="Value Field")
+ ],
+ }
+
+ #create the RepeatingFieldSet object and add it to global list like you do for others
+ temp_object = getattr(widgets,self.__convert_table[argument['type']]['type'])(**hash_repeat_data)
+ #print temp_object.fields
+ #add the common options
+ self.__add_commons_to_object(temp_object,argument,argument_name)
+ #add a new entry to final list
+ self.__widget_list[argument_name]=temp_object
+ del temp_object
+
+
+
+
+ def __add_specialized_list(self,argument,argument_name):
+ """
+ Very similar to __add_specialized_hash except it has one field
+ that is repeated so that provides a dynamic numbers of fields into
+ the web UI.
+
+ TODO : combine the 2 methods into a one generic they are very similar
+ @param : argument : the argument options,
+ @param : argument_name : the name of the argument also the name of the widget
+ @return : Nothing
+ """
+ list_repeat_data = {
+ 'template':"funcweb.templates.repeater_form",#may change that if someone doesnt like my design :)
+ 'fields' : [
+ widgets.TextField(name="listfield",label="List Field")
+ ],
+ }
+
+ #create the RepeatingFieldSet object and add it to global list like you do for others
+ temp_object = getattr(widgets,self.__convert_table[argument['type']]['type'])(**list_repeat_data)
+ #add the commno options
+ self.__add_commons_to_object(temp_object,argument,argument_name)
+ #add a new entry to final list
+ self.__widget_list[argument_name]=temp_object
+ del temp_object
+
+
def __add_commons_to_object(self,object,argument,argument_name):
"""
As it was thought all input widgets have the same
@@ -125,6 +187,7 @@ class WidgetListFactory(object):
"""
#firstly set the name of the argument
setattr(object,"name",argument_name)
+ setattr(object,"label",pretty_label(argument_name))
#print "The argument name is :",argument_name
#print "The argument options are :",argument
@@ -198,7 +261,7 @@ class RemoteFormAutomation(CoreWD):
validator = validator_schema,
name = "minion_form",
update = "col5",
- before='getElement(\'loading\').innerHTML=toHTML(IMG({src:\'../static/images/loading.gif\',width:\'80\',height:\'80\'}));',
+ before='getElement(\'loading\').innerHTML=toHTML(IMG({src:\'../static/images/loading.gif\',width:\'100\',height:\'100\'}));',
on_complete='getElement(\'loading\' ).innerHTML=\'Done!\';',
)
@@ -212,7 +275,7 @@ class RemoteFormFactory(object):
#some values that may want to change later
name = "minion_form",
update = "col5",
- before='getElement(\'loading\').innerHTML=toHTML(IMG({src:\'../static/images/loading.gif\',width:\'80\',height:\'80\'}));',
+ before='getElement(\'loading\').innerHTML=toHTML(IMG({src:\'../static/images/loading.gif\',width:\'100\',height:\'100\'}));',
on_complete='getElement(\'loading\' ).innerHTML=\'Done!\';',
submit_text = "Send Minion Form"
action = "/post_form"
@@ -261,3 +324,22 @@ class RemoteLinkFactory(CoreWD):
)
+#############################################################################################
+def pretty_label(name_to_label):
+ """
+ Simple util method to show the labels better
+ without __ things and other ugly looking stuff
+ """
+ tmp = None
+ split_tokens = ('__','_','-')
+ for st in split_tokens:
+ tmp = name_to_label.split(st)
+ if len(tmp)>1:
+ break
+
+ if tmp :
+ name_to_label = " ".join([s.capitalize() for s in tmp])
+ else:
+ name_to_label = name_to_label.capitalize()
+
+ return name_to_label
diff --git a/funcweb/funcweb/widget_validation.py b/funcweb/funcweb/widget_validation.py
index 0347fa6..9b67e7f 100644
--- a/funcweb/funcweb/widget_validation.py
+++ b/funcweb/funcweb/widget_validation.py
@@ -266,6 +266,8 @@ class MinionListValidator(validators.FancyValidator):
"""
#will add more beautiful validation here after
#integrate the complex widgets for lists and dicts
+ #print "Im in the list validator the value i recieved is : ",value
+
if self.not_empty:
if len(value)==0:
raise validators.Invalid('Empty list passed when not_empty is set',value,state)
@@ -274,9 +276,13 @@ class MinionListValidator(validators.FancyValidator):
tmp = []
if type(tmp) != type(value):
value = list(value)
- value = [list_value.strip() for list_value in value]
-
- return value
+
+ #concert the data to proper format
+ final_list = []
+ for hash_data in value:
+ final_list.extend(hash_data.values())
+
+ return final_list
def validate_python(self,value,state):
import re
@@ -303,17 +309,26 @@ class MinionHashValidator(validators.FancyValidator):
"""
#will add more beautiful validation here after
#integrate the complex widgets for lists and dicts
+ #print "Im in hash validator the value i recieved is ",value
+
if self.not_empty:
if len(value)==0:
raise validators.Invalid('Empty hash passed when not_empty is set',value,state)
+
+ #concert the data to proper format
+ final_hash = {}
+ for hash_data in value:
+ final_hash[hash_data['keyfield']] = hash_data['valuefield']
+
+
- #check the type firstly
+ #check the type firstly
tmp = {}
- if type(tmp) != type(value):
- raise validators.Invalid('The value passed to MinionHashValidator should be a dict object',value,state)
+ if type(tmp) != type(final_hash):
+ raise validators.Invalid('The value passed to MinionHashValidator should be a dict object',final_hash,state)
#print value
- return value
+ return final_hash
def validate_python(self,value,state):
#print value
diff --git a/funcweb/init-scripts/funcwebd b/funcweb/init-scripts/funcwebd
new file mode 100755
index 0000000..01ee280
--- /dev/null
+++ b/funcweb/init-scripts/funcwebd
@@ -0,0 +1,115 @@
+#!/bin/sh
+#
+# funcwebd Fedora Unified Network Control
+###################################
+
+# LSB header
+
+### BEGIN INIT INFO
+# Provides: funcwebd
+# Required-Start: network
+# Required-Stop:
+# Default-Start: 3 4 5
+# Default-Stop: 0 1 2 6
+# Short-Description: Fedora Unified Network Control
+# Description: Crazy simple, secure remote management.
+### END INIT INFO
+
+# chkconfig header
+
+# chkconfig: - 99 99
+# description: Crazy simple, secure remote management.
+#
+# processname: /usr/bin/funcwebd
+
+# Sanity checks.
+[ -x /usr/bin/funcwebd ] || exit 0
+
+SERVICE=funcwebd
+PROCESS=funcwebd
+DAEMON=/usr/bin/funcwebd
+CONFIG_ARGS="--daemon"
+
+
+FuncStatus()
+{
+ ps wt? | grep "$DAEMON" 2>&1 > /dev/null
+ if [ "x$?" = "x0" ]; then
+ RVAL=0
+ echo "$DAEMON is running"
+ else
+ RVAL=3
+ echo "$DAEMON is not running"
+ fi
+}
+
+if [ -f /lib/lsb/init-functions ]; then
+ . /lib/lsb/init-functions
+ alias START_DAEMON=start_daemon
+ alias STATUS=FuncStatus
+ alias LOG_SUCCESS=log_success_msg
+ alias LOG_FAILURE=log_failure_msg
+ alias LOG_WARNING=log_warning_msg
+elif [ -f /etc/init.d/functions ]; then
+ . /etc/init.d/functions
+ alias START_DAEMON=daemon
+ alias STATUS=status
+ alias LOG_SUCCESS=success
+ alias LOG_FAILURE=failure
+ alias LOG_WARNING=passed
+else
+ echo "Error: your platform is not supported by $0" > /dev/stderr
+ exit 1
+fi
+
+
+RETVAL=0
+
+start() {
+ echo -n $"Starting funcweb server"
+ START_DAEMON $PROCESS $CONFIG_ARGS
+ RETVAL=$?
+ echo
+ [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$SERVICE
+ return $RETVAL
+}
+
+stop() {
+ echo -n $"Stopping funcweb server: "
+ killproc $PROCESS
+ RETVAL=$?
+ echo
+ if [ $RETVAL -eq 0 ]; then
+ rm -f /var/lock/subsys/$SERVICE
+ rm -f /var/run/$SERVICE.pid
+ fi
+}
+
+restart() {
+ stop
+ start
+}
+
+# See how we were called.
+case "$1" in
+ start|stop|restart)
+ $1
+ ;;
+ status)
+ STATUS $PROCESS
+ RETVAL=$?
+ ;;
+ condrestart)
+ [ -f /var/lock/subsys/$SERVICE ] && restart || :
+ ;;
+ reload)
+ echo "can't reload configuration, you have to restart it"
+ RETVAL=$?
+ ;;
+ *)
+ echo $"Usage: $0 {start|stop|status|restart|condrestart|reload}"
+ exit 1
+ ;;
+esac
+exit $RETVAL
+
diff --git a/funcweb/setup.py b/funcweb/setup.py
index c634983..40c2e00 100644
--- a/funcweb/setup.py
+++ b/funcweb/setup.py
@@ -14,6 +14,14 @@ if os.path.isdir('locales'):
package_data.update(find_package_data(where='locales',
exclude=('*.po',), only_in_packages=False))
+#adding to the virtual part of the apache
+etcpath = "/etc/httpd/conf.d"
+#having a manual part for funcweb may add more things there in the future
+self_etcpath = "/etc/funcweb"
+#the init path for starting and stoping the server !
+initpath = "/etc/init.d"
+
+#the setup part
setup(
name="funcweb",
version=version,
@@ -22,10 +30,8 @@ setup(
author_email=email,
url=url,
license=license,
-
install_requires=[
"TurboGears >= 1.0.4.2",
- "SQLAlchemy>=0.3.10",
],
zip_safe=False,
packages=packages,
@@ -66,8 +72,8 @@ setup(
test_suite='nose.collector',
entry_points = {
'console_scripts': [
- 'start-funcweb = funcweb.commands:start',
- ],
+ 'funcwebd = funcweb.commands:start',
+ ],
'turbogears.identity.provider' : [
'pam = funcweb.identity.pamprovider:PAMIdentityProvider'
@@ -79,5 +85,9 @@ setup(
},
# Uncomment next line and create a default.cfg file in your project dir
# if you want to package a default configuration in your egg.
- #data_files = [('config', ['default.cfg'])],
+ data_files = [
+ (etcpath,['etc/funcweb.conf']),
+ (self_etcpath,['etc/prod.cfg']),
+ (initpath,['init-scripts/funcwebd'])
+ ],
)
diff --git a/funcweb/version b/funcweb/version
new file mode 100644
index 0000000..084a74c
--- /dev/null
+++ b/funcweb/version
@@ -0,0 +1 @@
+0.1 1
diff --git a/test/unittest/test_client.py b/test/unittest/test_client.py
index eae7746..8d65176 100644
--- a/test/unittest/test_client.py
+++ b/test/unittest/test_client.py
@@ -388,8 +388,40 @@ class TestIptablesPort(BaseTest):
# doesnt have an inventory, so er... -akl
+class TestEchoTest(BaseTest):
+ module = "echo"
+
+ def test_run_string(self):
+ result=self.overlord.echo.run_string("heyman")
+ self.assert_on_fault(result)
+
+ def test_run_int(self):
+ result=self.overlord.echo.run_int(12)
+ self.assert_on_fault(result)
+ def test_run_float(self):
+ result=self.overlord.echo.run_float(12.0)
+ self.assert_on_fault(result)
+ def test_run_options(self):
+ result=self.overlord.echo.run_options("hehehh")
+ self.assert_on_fault(result)
+
+ def test_run_list(self):
+ result=self.overlord.echo.run_list(['one','two','three'])
+ self.assert_on_fault(result)
+
+ def test_run_hash(self):
+ result=self.overlord.echo.run_hash({'one':1,'two':2})
+ self.assert_on_fault(result)
+
+ def test_run_boolean(self):
+ result=self.overlord.echo.run_hash(True)
+ self.assert_on_fault(result)
+
+
+
+
class TestSystem(BaseTest):
module = "system"