Add code
8
.hgignore
Normal file
@ -0,0 +1,8 @@
|
||||
^.venv/
|
||||
^openslides/.venv/
|
||||
\.pyc$
|
||||
^openslides/settings\.py$
|
||||
^database\.db$
|
||||
~$
|
||||
\.DS_Store$
|
||||
^docs/_build
|
7
AUTHORS
Normal file
@ -0,0 +1,7 @@
|
||||
Authors of OpenSlides in alphabetical order:
|
||||
|
||||
Emanuel Schütze <emanuel@intevation.de>
|
||||
Oskar Hahn <mail@oshahn.de>
|
||||
|
||||
Thanks:
|
||||
Norman Jäckel, Leipzig
|
124
INSTALL
Normal file
@ -0,0 +1,124 @@
|
||||
Installation Instructions for OpenSlides
|
||||
========================================
|
||||
2011-07-29
|
||||
|
||||
|
||||
Installation on Windows (32/64bit)
|
||||
----------------------------------
|
||||
|
||||
1. Install Python 2.7
|
||||
|
||||
- 32bit:
|
||||
a) Download and run 32bit MSI installer (python-2.7.2.msi)
|
||||
from http://www.python.org/download/
|
||||
b) Add python dirs to PATH (via Control Panel > System > Advanced):
|
||||
C:\Python27;C:\Python27\Scripts
|
||||
|
||||
- 64bit:
|
||||
a) Download and run 64bit MSI installer (python-2.7.2.amd64.msi)
|
||||
from http://www.python.org/download/
|
||||
b) Add python dirs to PATH (via Control Panel > System > Advanced):
|
||||
C:\Python27;C:\Python27\Scripts
|
||||
|
||||
2. Install Setuptools 0.6c11
|
||||
|
||||
- 32bit:
|
||||
Download and run 32bit binary installer
|
||||
(setuptools-0.6c11.win32-py2.7.exe)
|
||||
from http://pypi.python.org/pypi/setuptools
|
||||
|
||||
- 64bit:
|
||||
a) Download 64bit version (ez_setup.py)
|
||||
from http://pypi.python.org/pypi/setuptools
|
||||
b) Open cmd and run to install setuptools:
|
||||
python ez_setup.py
|
||||
|
||||
3. Install django packages
|
||||
Open cmd and run:
|
||||
|
||||
easy_install django django-model-utils
|
||||
|
||||
4. Install packages for building PDF (reportlab, PIL)
|
||||
|
||||
- 32bit:
|
||||
Run on cmd:
|
||||
|
||||
easy_install reportlab pil
|
||||
|
||||
- 64bit:
|
||||
a) Download and run Reportlab 64bit binary installer
|
||||
(reportlab-2.5-win-amd64-py2.7.exe) from
|
||||
http://www.reportlab.com/ftp/
|
||||
b) Download and run PIL 64bit binary installer
|
||||
(PIL-1.1.7.win-amd64-py2.7.exe) from
|
||||
http://www.lfd.uci.edu/~gohlke/pythonlibs/
|
||||
|
||||
5. Get OpenSlides source code (e.g. via mercurial checkout)
|
||||
|
||||
hg clone ssh://hg@openslides.org/openslides)
|
||||
|
||||
6. Copy default.settings.py to settings.py
|
||||
(in directory 'openslides')
|
||||
|
||||
7. Install OpenSlides database:
|
||||
|
||||
python manage.py syncdb
|
||||
|
||||
8. Run OpenSlides server:
|
||||
|
||||
python manage.py runserver
|
||||
|
||||
9. Open OpenSlides in your Browser:
|
||||
|
||||
http://127.0.0.1:8000/
|
||||
|
||||
|
||||
|
||||
Installation on Linux and MacOSX
|
||||
--------------------------------
|
||||
|
||||
Make sure that you have python and virtualenv installed on your
|
||||
system.
|
||||
|
||||
1. Setup virtualenv:
|
||||
|
||||
cd openslides
|
||||
virtualenv .venv
|
||||
. .venv/bin/activate
|
||||
|
||||
2. Install 'pip' if not available:
|
||||
|
||||
easy_install pip
|
||||
|
||||
3. Install django packages:
|
||||
|
||||
pip install django django-model-utils
|
||||
|
||||
4. Install packages for building PDF (reportlab, PIL):
|
||||
|
||||
pip install reportlab pil
|
||||
|
||||
5. Get OpenSlides source code (e.g. via mercurial checkout)
|
||||
|
||||
hg clone ssh://hg@openslides.org/openslides
|
||||
|
||||
6. Copy default.settings.py to settings.py
|
||||
(in directory 'openslides')
|
||||
|
||||
7. Install OpenSlides database (and follow on screen instruction):
|
||||
|
||||
python manage.py syncdb
|
||||
|
||||
8. Run OpenSlides server:
|
||||
|
||||
python manage.py runserver
|
||||
|
||||
9. Open OpenSlides in your Browser:
|
||||
|
||||
http://127.0.0.1:8000/
|
||||
|
||||
|
||||
--
|
||||
If you need help ask on OpenSlides users mailinglist.
|
||||
See www.openslides.org for more information.
|
||||
|
339
LICENSE
Normal file
@ -0,0 +1,339 @@
|
||||
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.
|
13
THANKS
Normal file
@ -0,0 +1,13 @@
|
||||
THANKS file for OpenSlides
|
||||
|
||||
|
||||
OpenSlides uses parts of the following projects:
|
||||
|
||||
* Oxygen-Icons
|
||||
<http://www.oxygen-icons.org/>
|
||||
|
||||
* jQuery
|
||||
<http://www.jquery.com>
|
||||
|
||||
* Drupal (tabledrag function)
|
||||
<http://www.drupal.org/>
|
143
docs/Makefile
Normal file
@ -0,0 +1,143 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
LANGUAGES = en de
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees/$$lang $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) -c . -A language=$$lang -A languages='$(LANGUAGES)'
|
||||
#ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees/$$lang $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) -c . -A language=$$lang -D language=$$lang -A target=$(TARGET) -A languages='$(LANGUAGES)'
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
@for lang in $(LANGUAGES);\
|
||||
do \
|
||||
mkdir -p $(BUILDDIR)/html/$$lang $(BUILDDIR)/doctrees/$$lang; \
|
||||
echo "$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $$lang $(BUILDDIR)/html/$$lang";\
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $$lang $(BUILDDIR)/html/$$lang;\
|
||||
done
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/OpenSlides.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/OpenSlides.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/OpenSlides"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/OpenSlides"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
make -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
13
docs/README
Normal file
@ -0,0 +1,13 @@
|
||||
The OpenSlides website based on Sphinx <http://sphinx.pocoo.org/>.
|
||||
You have to install Sphinx before you can build the website.
|
||||
|
||||
To build the website into the ./_build directory use:
|
||||
|
||||
make html
|
||||
|
||||
To clean up your build directory use:
|
||||
|
||||
make clean
|
||||
|
||||
--
|
||||
http://www.openslides.org
|
460
docs/_static/basic.css
vendored
Normal file
@ -0,0 +1,460 @@
|
||||
/*
|
||||
* basic.css
|
||||
* ~~~~~~~~~
|
||||
*
|
||||
* Sphinx stylesheet -- basic theme.
|
||||
*
|
||||
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
/* -- relbar ---------------------------------------------------------------- */
|
||||
|
||||
div.related {
|
||||
width: 100%;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
div.related h3 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.related ul {
|
||||
margin: 0;
|
||||
padding: 0 0 0 10px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
div.related li {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
div.related li.right {
|
||||
float: right;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
/* -- search page ----------------------------------------------------------- */
|
||||
|
||||
ul.search {
|
||||
margin: 10px 0 0 20px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul.search li {
|
||||
padding: 5px 0 5px 20px;
|
||||
background-image: url(file.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0 7px;
|
||||
}
|
||||
|
||||
ul.search li a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ul.search li div.context {
|
||||
color: #888;
|
||||
margin: 2px 0 0 30px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
ul.keywordmatches li.goodmatch a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* -- general index --------------------------------------------------------- */
|
||||
|
||||
table.indextable {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table.indextable td {
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
table.indextable dl, table.indextable dd {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
table.indextable tr.pcap {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
table.indextable tr.cap {
|
||||
margin-top: 10px;
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
img.toggler {
|
||||
margin-right: 3px;
|
||||
margin-top: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.modindex-jumpbox {
|
||||
border-top: 1px solid #ddd;
|
||||
border-bottom: 1px solid #ddd;
|
||||
margin: 1em 0 1em 0;
|
||||
padding: 0.4em;
|
||||
}
|
||||
|
||||
div.genindex-jumpbox {
|
||||
border-top: 1px solid #ddd;
|
||||
border-bottom: 1px solid #ddd;
|
||||
margin: 1em 0 1em 0;
|
||||
padding: 0.4em;
|
||||
}
|
||||
|
||||
/* -- general body styles --------------------------------------------------- */
|
||||
|
||||
a.headerlink {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
h1:hover > a.headerlink,
|
||||
h2:hover > a.headerlink,
|
||||
h3:hover > a.headerlink,
|
||||
h4:hover > a.headerlink,
|
||||
h5:hover > a.headerlink,
|
||||
h6:hover > a.headerlink,
|
||||
dt:hover > a.headerlink {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
div.body p.caption {
|
||||
text-align: inherit;
|
||||
}
|
||||
|
||||
div.body td {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.field-list ul {
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.first {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
p.rubric {
|
||||
margin-top: 30px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
img.align-left, .figure.align-left, object.align-left {
|
||||
clear: left;
|
||||
float: left;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
img.align-right, .figure.align-right, object.align-right {
|
||||
clear: right;
|
||||
float: right;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
img.align-center, .figure.align-center, object.align-center {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
clear: both;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* -- sidebars -------------------------------------------------------------- */
|
||||
|
||||
div.sidebar {
|
||||
margin: 0 0 0.5em 1em;
|
||||
border: 1px solid #ddb;
|
||||
padding: 7px 7px 0 7px;
|
||||
background-color: #ffe;
|
||||
width: 40%;
|
||||
float: right;
|
||||
}
|
||||
|
||||
p.sidebar-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* -- topics ---------------------------------------------------------------- */
|
||||
|
||||
div.topic {
|
||||
border: 1px solid #ccc;
|
||||
padding: 7px 7px 0 7px;
|
||||
margin: 10px 0 10px 0;
|
||||
}
|
||||
|
||||
p.topic-title {
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* -- admonitions ----------------------------------------------------------- */
|
||||
|
||||
div.admonition {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding: 7px;
|
||||
}
|
||||
|
||||
div.admonition dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.admonition dl {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
margin: 0px 10px 5px 0px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.body p.centered {
|
||||
text-align: center;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
/* -- tables ---------------------------------------------------------------- */
|
||||
|
||||
table.docutils {
|
||||
border: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table.docutils td, table.docutils th {
|
||||
padding: 1px 8px 1px 5px;
|
||||
border-top: 0;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid #aaa;
|
||||
}
|
||||
|
||||
table.field-list td, table.field-list th {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
table.footnote td, table.footnote th {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
table.citation {
|
||||
border-left: solid 1px gray;
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
||||
table.citation td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* -- other body styles ----------------------------------------------------- */
|
||||
|
||||
ol.arabic {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
ol.loweralpha {
|
||||
list-style: lower-alpha;
|
||||
}
|
||||
|
||||
ol.upperalpha {
|
||||
list-style: upper-alpha;
|
||||
}
|
||||
|
||||
ol.lowerroman {
|
||||
list-style: lower-roman;
|
||||
}
|
||||
|
||||
ol.upperroman {
|
||||
list-style: upper-roman;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
dd p {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
dd ul, dd table {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-top: 3px;
|
||||
margin-bottom: 10px;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
dt:target, .highlighted {
|
||||
background-color: #fbe54e;
|
||||
}
|
||||
|
||||
dl.glossary dt {
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.field-list ul {
|
||||
margin: 0;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.field-list p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.refcount {
|
||||
color: #060;
|
||||
}
|
||||
|
||||
.optional {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.versionmodified {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.system-message {
|
||||
background-color: #fda;
|
||||
padding: 5px;
|
||||
border: 3px solid red;
|
||||
}
|
||||
|
||||
.footnote:target {
|
||||
background-color: #ffa;
|
||||
}
|
||||
|
||||
.line-block {
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.line-block .line-block {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
margin-left: 1.5em;
|
||||
}
|
||||
|
||||
.guilabel, .menuselection {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.accelerator {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.classifier {
|
||||
font-style: oblique;
|
||||
}
|
||||
|
||||
/* -- code displays --------------------------------------------------------- */
|
||||
|
||||
pre {
|
||||
overflow: auto;
|
||||
overflow-y: hidden; /* fixes display issues on Chrome browsers */
|
||||
}
|
||||
|
||||
td.linenos pre {
|
||||
padding: 5px 0px;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
table.highlighttable {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
table.highlighttable td {
|
||||
padding: 0 0.5em 0 0.5em;
|
||||
}
|
||||
|
||||
tt.descname {
|
||||
background-color: transparent;
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
tt.descclassname {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
tt.xref, a tt {
|
||||
background-color: transparent;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.viewcode-link {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.viewcode-back {
|
||||
float: right;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
div.viewcode-block:target {
|
||||
margin: -1px -10px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
/* -- math display ---------------------------------------------------------- */
|
||||
|
||||
img.math {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
div.body div.math p {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
span.eqno {
|
||||
float: right;
|
||||
}
|
||||
|
||||
/* -- printout stylesheet --------------------------------------------------- */
|
||||
|
||||
@media print {
|
||||
div.document,
|
||||
div.documentwrapper,
|
||||
div.bodywrapper {
|
||||
margin: 0 !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.sphinxsidebar,
|
||||
div.related,
|
||||
div.footer,
|
||||
#top-link {
|
||||
display: none;
|
||||
}
|
||||
}
|
424
docs/_static/default.css
vendored
Normal file
@ -0,0 +1,424 @@
|
||||
/*
|
||||
OpenSlides CSS
|
||||
|
||||
@import url("basic.css");
|
||||
*/
|
||||
|
||||
|
||||
|
||||
@import url(http://fonts.googleapis.com/css?family=Ubuntu);
|
||||
|
||||
body { font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana',
|
||||
sans-serif; font-size: 13px; color: #000; }
|
||||
a { color: #185F6D; border-bottom: 1px dotted #2BABC4; text-decoration: none; }
|
||||
a:hover { color: #2794AA; border-bottom: 1px solid #2794AA; }
|
||||
.box { width: 540px; margin: 20px auto; }
|
||||
h1, h2, h3 { font-family: 'Ubuntu', 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
|
||||
'Verdana', sans-serif; font-weight: normal; }
|
||||
.header { height: 165px; }
|
||||
.header h1 { margin: 0 0 30px 0; background: url(logo.png) no-repeat right;
|
||||
font-size: 50px; font-weight: bold; padding-top: 50px; height: 120px; }
|
||||
.header p { font-size: 15px; margin: -90px 0 0 0; }
|
||||
h1 { font-size: 34px; margin: 25px 0 5px 0; }
|
||||
h2 { font-size: 18px; margin: 25px 0 5px 0; }
|
||||
h3 { font-size: 19px; margin: 25px 0 5px 0; }
|
||||
textarea, code,
|
||||
pre { font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono',
|
||||
monospace!important; font-size: 15px; background: #E8EFF0; }
|
||||
pre { padding: 7px 30px; margin: 15px -30px; line-height: 1.3; }
|
||||
.ig { color: #888; }
|
||||
p { line-height: 1.4; color:#444444;}
|
||||
ul { margin: 15px 0 15px 0; padding: 0; list-style: none; line-height: 1.4; }
|
||||
ul li:before { content: "\00BB \0020"; color: #888; position: absolute; margin-left: -19px; }
|
||||
ol { line-height: 1.4; margin: 15px 0 15px 30px; padding: 0; }
|
||||
blockquote { margin: 15px 0; font-style: italic; color: #444; }
|
||||
.footer { font-size: 10px; color: #888; text-align: right; margin-top: 25px; }
|
||||
.more { text-align: right; margin-top: 0; font-size: 0.9em; font-style: italic; }
|
||||
.nav { text-align: left; margin: 0 0 0 0; }
|
||||
table { border: 1px solid black; border-collapse: collapse;
|
||||
margin: 15px 0; }
|
||||
td, th { border: 1px solid black; padding: 4px 10px;
|
||||
text-align: left; }
|
||||
th { background: #eee; font-weight: normal; }
|
||||
|
||||
td input { border: none; padding: 0; }
|
||||
|
||||
/* latest version button */
|
||||
.latestver { margin: 20px 0 0 0; float: right; font-style: italic; }
|
||||
.latestver strong { font-weight: normal; }
|
||||
|
||||
|
||||
div.document {
|
||||
width: 940px;
|
||||
margin: 30px auto 0 auto;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0 0 0 220px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid #B1B4B6;
|
||||
}
|
||||
|
||||
div.body {
|
||||
background-color: #ffffff;
|
||||
color: #3E4349;
|
||||
padding: 0 30px 0 30px;
|
||||
}
|
||||
|
||||
img.floatingflask {
|
||||
padding: 0 0 10px 10px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
width: 940px;
|
||||
margin: 20px auto 30px auto;
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
div.footer a {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
div.related {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #444;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted #999;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a:hover {
|
||||
border-bottom: 1px solid #999;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper {
|
||||
padding: 18px 10px;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper p.logo {
|
||||
padding: 0 0 20px 0;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3,
|
||||
div.sphinxsidebar h4 {
|
||||
font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif;
|
||||
color: #444;
|
||||
font-size: 24px;
|
||||
font-weight: normal;
|
||||
margin: 0 0 5px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h4 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3 a {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p.logo a,
|
||||
div.sphinxsidebar h3 a,
|
||||
div.sphinxsidebar p.logo a:hover,
|
||||
div.sphinxsidebar h3 a:hover {
|
||||
border: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p {
|
||||
color: #555;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul {
|
||||
margin: 10px 0;
|
||||
padding: 0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input {
|
||||
border: 1px solid #ccc;
|
||||
font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar form.search input[name="q"] {
|
||||
width: 130px;
|
||||
}
|
||||
|
||||
/* -- body styles ----------------------------------------------------------- */
|
||||
|
||||
a {
|
||||
color: #185F6D;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #2794AA;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.body h1,
|
||||
div.body h2,
|
||||
div.body h3,
|
||||
div.body h4,
|
||||
div.body h5,
|
||||
div.body h6 {
|
||||
font-family: 'Ubuntu', 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif;
|
||||
font-weight: normal;
|
||||
margin: 30px 0px 10px 0px;
|
||||
padding: 0;
|
||||
color: black;
|
||||
}
|
||||
|
||||
div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
|
||||
div.body h2 { font-size: 180%; }
|
||||
div.body h3 { font-size: 150%; }
|
||||
div.body h4 { font-size: 130%; }
|
||||
div.body h5 { font-size: 100%; }
|
||||
div.body h6 { font-size: 100%; }
|
||||
|
||||
a.headerlink {
|
||||
color: #ddd;
|
||||
padding: 0 4px;
|
||||
text-decoration: none;
|
||||
}
|
||||
a.headerlink {
|
||||
visibility: hidden;
|
||||
}
|
||||
h1:hover > a.headerlink, h2:hover > a.headerlink, h3:hover > a.headerlink, h4:hover > a.headerlink, h5:hover > a.headerlink, h6:hover > a.headerlink, dt:hover > a.headerlink {
|
||||
visibility: visible;
|
||||
}
|
||||
a.headerlink:hover {
|
||||
color: #444;
|
||||
background: #eaeaea;
|
||||
|
||||
}
|
||||
|
||||
div.body p, div.body dd, div.body li {
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
div.admonition {
|
||||
background: #fafafa;
|
||||
margin: 20px -30px;
|
||||
padding: 10px 30px;
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.admonition tt.xref, div.admonition a tt {
|
||||
border-bottom: 1px solid #fafafa;
|
||||
}
|
||||
|
||||
dd div.admonition {
|
||||
margin-left: -60px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
div.admonition p.admonition-title {
|
||||
font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif;
|
||||
font-weight: normal;
|
||||
font-size: 24px;
|
||||
margin: 0 0 10px 0;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
div.admonition p.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.highlight {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
dt:target, .highlight {
|
||||
background: #FAF3E8;
|
||||
}
|
||||
|
||||
div.note {
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.seealso {
|
||||
background-color: #ffc;
|
||||
border: 1px solid #ff6;
|
||||
}
|
||||
|
||||
div.topic {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
p.admonition-title:after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
pre, tt {
|
||||
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
}
|
||||
|
||||
tt.descname, tt.descclassname {
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
tt.descname {
|
||||
padding-right: 0.08em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils {
|
||||
border: 1px solid #888;
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils td, table.docutils th {
|
||||
border: 1px solid #888;
|
||||
padding: 0.25em 0.7em;
|
||||
}
|
||||
|
||||
table.field-list, table.footnote {
|
||||
border: none;
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
table.footnote {
|
||||
margin: 15px 0;
|
||||
width: 100%;
|
||||
border: 1px solid #eee;
|
||||
background: #fdfdfd;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
table.footnote + table.footnote {
|
||||
margin-top: -15px;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
table.field-list th {
|
||||
padding: 0 0.8em 0 0;
|
||||
}
|
||||
|
||||
table.field-list td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table.footnote td.label {
|
||||
width: 0px;
|
||||
padding: 0.3em 0 0.3em 0.5em;
|
||||
}
|
||||
|
||||
table.footnote td {
|
||||
padding: 0.3em 0.5em;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dl dd {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 0 30px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin: 10px 0 10px 30px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #E8EFF0;
|
||||
padding: 7px 30px;
|
||||
margin: 15px -30px;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
dl pre, blockquote pre, li pre {
|
||||
margin-left: -60px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
dl dl pre {
|
||||
margin-left: -90px;
|
||||
padding-left: 90px;
|
||||
}
|
||||
|
||||
tt {
|
||||
background-color: #E8EFF0;
|
||||
color: #222;
|
||||
/* padding: 1px 2px; */
|
||||
}
|
||||
|
||||
tt.xref, a tt {
|
||||
background-color: #E8EFF0;
|
||||
border-bottom: 1px solid white;
|
||||
}
|
||||
|
||||
a.reference {
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted #2BABC4;
|
||||
}
|
||||
|
||||
a.reference:hover {
|
||||
border-bottom: 1px solid #2794AA;
|
||||
}
|
||||
|
||||
a.footnote-reference {
|
||||
text-decoration: none;
|
||||
font-size: 0.7em;
|
||||
vertical-align: top;
|
||||
border-bottom: 1px dotted #004B6B;
|
||||
}
|
||||
|
||||
a.footnote-reference:hover {
|
||||
border-bottom: 1px solid #6D4100;
|
||||
}
|
||||
|
||||
a:hover tt {
|
||||
background: #EEE;
|
||||
}
|
BIN
docs/_static/images/agenda-beamer_de.png
vendored
Normal file
After Width: | Height: | Size: 93 KiB |
BIN
docs/_static/images/agenda-new-item_de.png
vendored
Normal file
After Width: | Height: | Size: 94 KiB |
BIN
docs/_static/images/agenda-pdf_de.png
vendored
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
docs/_static/images/agenda_de.png
vendored
Normal file
After Width: | Height: | Size: 139 KiB |
BIN
docs/_static/images/application-history_de.png
vendored
Normal file
After Width: | Height: | Size: 97 KiB |
BIN
docs/_static/images/application-managebox_de.png
vendored
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
docs/_static/images/application-overview-beamer_de.png
vendored
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
docs/_static/images/application-overview_de.png
vendored
Normal file
After Width: | Height: | Size: 115 KiB |
BIN
docs/_static/images/application-view-beamer_de.png
vendored
Normal file
After Width: | Height: | Size: 208 KiB |
BIN
docs/_static/images/application-view_de.png
vendored
Normal file
After Width: | Height: | Size: 216 KiB |
BIN
docs/_static/images/application-viewbox_de.png
vendored
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
docs/_static/images/de.png
vendored
Executable file
After Width: | Height: | Size: 545 B |
BIN
docs/_static/images/en.png
vendored
Executable file
After Width: | Height: | Size: 609 B |
BIN
docs/_static/logo.png
vendored
Normal file
After Width: | Height: | Size: 10 KiB |
50
docs/_templates/layout.html
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
{% extends '!layout.html' %}
|
||||
|
||||
{%- block content %}
|
||||
|
||||
<div class="box">
|
||||
<div class="language">
|
||||
{%- for lng in languages.split(' ') %}
|
||||
{%- if lng != language %}
|
||||
<a href="{{ pathto('../'+lng+'/'+pagename) }}"><img src="{{ pathto('_static/images/'+lng+'.png', 1) }}" alt="{{ lng }}" title="{{ lng }}" border="0" width="18px" height="13px"/></a>
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
</div>
|
||||
|
||||
<div class="header">
|
||||
<h1><span>OpenSlides</span></h1>
|
||||
<p>
|
||||
{%- if language == 'de' %}
|
||||
Tagesordnungs-Präsentation<br> mit Antrags- und Wahlsystem
|
||||
{%- else %}
|
||||
Agenda presentation<br> with application and voting system
|
||||
{%- endif %}
|
||||
</div>
|
||||
|
||||
<div class="nav">
|
||||
{%- if language == 'de' %}
|
||||
<a href="{{ pathto('index') }}">Home</a> |
|
||||
<a href="about.html">Über</a> |
|
||||
<a href="download.html">Download</a> |
|
||||
<a href="http://trac.openslides.org">Entwicklung</a> |
|
||||
<a href="help.html">Hilfe</a>
|
||||
{%- else %}
|
||||
<a href="{{ pathto('index') }}">Home</a> |
|
||||
<a href="about.html">About</a> |
|
||||
<a href="download.html">Download</a> |
|
||||
<a href="http://trac.openslides.org">Development</a> |
|
||||
<a href="help.html">Help</a>
|
||||
{%- endif %}
|
||||
</div>
|
||||
|
||||
{% block body %} {% endblock %}
|
||||
|
||||
<p class="footer">
|
||||
© 2011 | OpenSlides is licensed under GPLv2+.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
{%- endblock %}
|
||||
|
||||
{% block relbar2 %}{% endblock %}
|
||||
{% block footer %} {% endblock %}
|
8
docs/_templates/sidebarintro.html
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
<h3>About</h3>
|
||||
<p>
|
||||
OpenSlides is a agenda tool with...
|
||||
</p>
|
||||
<h3>Demo</h3>
|
||||
<p>
|
||||
Try out the <a href="#">OpenSlides demo</a>!
|
||||
</p>
|
231
docs/conf.py
Normal file
@ -0,0 +1,231 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# OpenSlides documentation build configuration file, created by
|
||||
# sphinx-quickstart on Wed Jun 8 20:38:19 2011.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = []
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'OpenSlides'
|
||||
copyright = u'2011 OpenSlides '
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '1.0'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '1.0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#languages = 'en'
|
||||
#locale_dirs = 'locale/'
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'default'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
#html_sidebars = {
|
||||
# 'index': ['sidebarlogo.html', 'sidebarintro.html', 'searchbox.html'],
|
||||
# '**': ['sidebarlogo.html', 'localtoc.html', 'relations.html',
|
||||
# 'searchbox.html']
|
||||
#}
|
||||
html_sidebars = {
|
||||
'**': ['menu_en.html'],
|
||||
'de/about': ['menu_de.html'],
|
||||
}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
#html_additional_pages = {'de/about': 'about_de.html'}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'OpenSlidesdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
# The paper size ('letter' or 'a4').
|
||||
#latex_paper_size = 'letter'
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#latex_font_size = '10pt'
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'OpenSlides.tex', u'OpenSlides Documentation',
|
||||
u'OpenSlides Team', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#latex_preamble = ''
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'openslides', u'OpenSlides Documentation',
|
||||
[u'OpenSlides Team'], 1)
|
||||
]
|
||||
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {'http://docs.python.org/': None}
|
136
docs/de/about.rst
Normal file
@ -0,0 +1,136 @@
|
||||
Über OpenSlides
|
||||
===============
|
||||
|
||||
|
||||
Was ist OpenSlides?
|
||||
------------------
|
||||
|
||||
OpenSlides ist ein freies, webbasiertes Präsentationssystem für Tagesordnung, Anträge, Abtimmungen und Wahlen.
|
||||
|
||||
Über ein Webinterface lässt sich das Beamerbild mit der aktuellen Tagesordnung steuern. Änderungen an der Tagesordnung werden unmittelbar auf dem Beamer dargestellt.
|
||||
|
||||
Darüber hinaus können sich die Teilnehmer einer Veranstaltung mit ihrem Laptop/Smartphone an OpenSlides anmelden und so z.B. Anträge (elektronisch) einreichen, für eine Wahl kandidieren, die Tagesordnung verfolgen, Abstimmungsergebnisse nachlesen u.v.m.
|
||||
|
||||
OpenSlides ist konzipiert für den Einsatz auf Veranstaltungen wie
|
||||
Hauptversammlungen, Delegierten-/Mitgliederversammlungen und Parteitagen.
|
||||
|
||||
Der entscheidene Vorteil gegenüber herkömmlichen Office-Präsentations-Systemen, wie MS PowerPoint oder OpenOffice Impress, ist die Bearbeitung der Folien über ein Redaktionssystem-System. Der Präsentationsmodus auf dem Beamer muss nicht verlassen werden.
|
||||
|
||||
Systemanforderungen
|
||||
-------------------
|
||||
|
||||
- Django 1.3+
|
||||
- Python 2.5+
|
||||
- Webbrowser
|
||||
|
||||
Lizenz
|
||||
------
|
||||
OpenSlides ist Freie Software und steht unter der **GNU General Public License (GNU GPL)** Version 2+. Die Software darf ohne Restriktionen benutzt, verändert und (geändert) weitergegeben werden.
|
||||
Eine Kopie der Lizenz liegt jedem OpenSlides-Release bei und ist auch im Quellcode-Repository nachzulesen.
|
||||
|
||||
|
||||
Funktionen
|
||||
==========
|
||||
|
||||
Tagesordnung
|
||||
------------
|
||||
|
||||
- Tagesordnung verwalten
|
||||
- Eintrag auswählen zur Anzeige am Beamer
|
||||
- Beamer-Ansicht mit automatischer Aktualisierung bei Änderung (der Beamer läuft z.B. im Browser mit Vollbildmodus auf einem 2. Monitor)
|
||||
- Eintrag während der Anzeige am Beamer im Webinterface bearbeiten
|
||||
- Eintrag per Drag&Drop in der Tagesordnung sortieren (Unterpunkte möglich)
|
||||
- Eintrag als erledigt markieren
|
||||
- Eintrag auf der Beamer-Ansicht verstecken
|
||||
- Tagesordnung als pdf erzeugen
|
||||
- aktuelle Uhrzeit auf der Beameransicht
|
||||
|
||||
.. image:: _static/images/agenda_de.png
|
||||
:width: 45%
|
||||
:alt: Tagesordnungs-Ansicht
|
||||
|
||||
.. image:: _static/images/agenda-beamer_de.png
|
||||
:width: 45%
|
||||
:alt: Beamer-Ansicht
|
||||
|
||||
.. image:: _static/images/agenda-new-item_de.png
|
||||
:width: 45%
|
||||
:alt: Neuen Tagesordnungseintrag anlegen
|
||||
|
||||
.. image:: _static/images/agenda-pdf_de.png
|
||||
:width: 45%
|
||||
:alt: Tagesordnung als PDF
|
||||
|
||||
|
||||
Anträge
|
||||
-------
|
||||
|
||||
- Anträge anlegen, bearbeiten, löschen
|
||||
- Anträge von anderen Teilnehmern unterstützen lassen
|
||||
- Antragsstatus ändern und Abstimmungsergebnisse eingeben
|
||||
- Abstimmungsergebenisse aus mehreren Wahlgängen darstellen
|
||||
- Antrag als Tagesordnungseintrag anlegen und anzeigen
|
||||
- Änderungshistorie eines Antrags anzeigen
|
||||
- Antragsformular als pdf erzeugen
|
||||
- Übersicht aller Anträge als pdf
|
||||
|
||||
.. image:: _static/images/application-overview_de.png
|
||||
:width: 45%
|
||||
:alt: Antragsübersicht
|
||||
|
||||
.. image:: _static/images/application-overview-beamer_de.png
|
||||
:width: 45%
|
||||
:alt: Beamer-Ansicht Antragsübersicht
|
||||
|
||||
.. image:: _static/images/application-view_de.png
|
||||
:width: 45%
|
||||
:alt: Darstellung eines Antrags mit Verwaltugsfunktion
|
||||
|
||||
.. image:: _static/images/application-view-beamer_de.png
|
||||
:width: 45%
|
||||
:alt: Beamer-Ansicht eines einzelnen Antrags
|
||||
|
||||
Wahlen
|
||||
------
|
||||
|
||||
- Kandidaten aus Teilnehmerliste für eine Wahl vorschlagen (bzw. als angemeldeter Teilnehmer selbst kandidieren)
|
||||
- Wahlschein als pdf generieren (mit Ankreuzfeld)
|
||||
- Wahlergebenisse eingeben und darstellen
|
||||
- mehrere Wahlgänge und Stichwahlen werden unterstützt
|
||||
|
||||
|
||||
Abstimmungen
|
||||
------------
|
||||
|
||||
- Abstimmungen verwalten (als Ergänzung zu Anträgen und Wahlen)
|
||||
- Ergebnisse eingeben und darstellen (analog zu Anträgen und Wahlen)
|
||||
- Abstimmungsmodus: entweder nur Ja-Stimmen oder Ja/Nein/Enthaltungs-Stimmen wählbar
|
||||
- ungültige und abgegebene Stimmen können eingegeben werden
|
||||
|
||||
|
||||
Teilnehmer
|
||||
----------
|
||||
|
||||
- Teilnehmer anlegen und verwalten (vordefinierte Felder: *Name, Vorname, E-Mail, Geschlecht, Gruppe, Typ, Amt*)
|
||||
- importieren von Teilnehmerdaten (im CSV-Format)
|
||||
- Benutzergruppe frei konfigurierbar
|
||||
|
||||
Allgemein
|
||||
---------
|
||||
|
||||
- Template für Beamer und Webinterface leicht per HTML und CSS anpassbar
|
||||
- OpenSlides ist Freie Software (`GPL v2+ Lizenz <about.html#lizenz>`_)
|
||||
- Plattformunabhängig (läuft überall dort, wo Pyhton läuft)
|
||||
- vollständige deutsche und englische Übersetzung vorhanden, weitere Sprachen möglich
|
||||
|
||||
|
||||
Ausblick -- Weiterentwicklungsideen für OpenSlides:
|
||||
---------------------------------------------------
|
||||
|
||||
- schnelle und einfache Erstellung eines Ergebnisprotokolls (mit allen Tagesordnungseinträgen, Beschlüssen, Anträgen, Abstimmungen und Wahlergbnissen)
|
||||
- grafische Darstellung der Wahl- und Abstimmungsergebnisse in Diagrammen
|
||||
- Einbindung von Grafiken in Tagesordnungseinträge
|
||||
- Anbindung eines elektronischen Voting-Systems
|
||||
|
||||
Interesse an der Weiterentwicklung von OpenSlides? Wir freuen uns über jede Mithilfe!
|
||||
|
13
docs/de/download.rst
Normal file
@ -0,0 +1,13 @@
|
||||
Download
|
||||
========
|
||||
|
||||
Es ist noch kein Release von OpenSlides verfügbar.
|
||||
Das Erscheinen von Version 1.0 ist für Sommer 2011 geplant.
|
||||
|
||||
Der aktuelle Entwicklungsstand kann im öffentlichen
|
||||
Quellcode-Repository eingesehen werden.
|
||||
|
||||
|
||||
Quellcode von OpenSlides auschecken::
|
||||
|
||||
hg clone http://hg.openslides.org
|
40
docs/de/help.rst
Normal file
@ -0,0 +1,40 @@
|
||||
Hilfe
|
||||
=====
|
||||
|
||||
|
||||
Mailinglisten
|
||||
--------------
|
||||
|
||||
OpenSlides hat zwei öffentliche Mailinglisten (in deutscher und englischer Sprache)
|
||||
zur Koordination der Entwicklung und Diskussion von Tickets, Nutzerfragen
|
||||
oder speziellen Anwendungsfällen.
|
||||
Bitte abbonieren Sie eine Mailingliste bevor Sie an diese schreiben wollen.
|
||||
|
||||
Deutsch::
|
||||
|
||||
user-de@openslides.org
|
||||
|
||||
|
||||
English::
|
||||
|
||||
user@openslides.org
|
||||
|
||||
|
||||
|
||||
Probleme berichten
|
||||
------------------
|
||||
|
||||
Bitte nutzen Sie unser `Ticketsystem <http://trac.openslides.org/report/3>`_
|
||||
um Problem mit OpenSlides zu berichten.
|
||||
|
||||
|
||||
|
||||
Möchten Sie zu OpenSlides beitragen?
|
||||
------------------------------------
|
||||
|
||||
OpenSlides ist eine engagierte Freie Software-Initiative von Freiwilligen und
|
||||
freut sich über Mithilfe!
|
||||
|
||||
Arbeiten am Quellcode, Übersetzungen oder Grafikdesign - wir freuen uns über
|
||||
jede Form der Unterstützung!
|
||||
|
12
docs/de/index.rst
Normal file
@ -0,0 +1,12 @@
|
||||
Willkommen
|
||||
==========
|
||||
|
||||
OpenSlides ist ein freies, webbasiertes Präsentationssystem
|
||||
zur Darstellung und Steuerung von Tagesordnungen, Anträgen, Abstimmungen und Wahlen.
|
||||
|
||||
Erfahren Sie mehr über die :doc:`Funktionen <about>` von OpenSlides.
|
||||
|
||||
.. image:: _static/images/agenda_de.png
|
||||
:width: 90%
|
||||
:alt: Tagesordnungs-Ansicht von OpenSlides
|
||||
|
140
docs/en/about.rst
Normal file
@ -0,0 +1,140 @@
|
||||
About OpenSlides
|
||||
================
|
||||
|
||||
|
||||
|
||||
What is OpenSlides?
|
||||
------------------
|
||||
|
||||
OpenSlides ist ein freies, webbasiertes Präsentationssystem für Tagesordnung, Anträge, Abtimmungen und Wahlen.
|
||||
|
||||
Über ein Webinterface lässt sich das Beamerbild mit der aktuellen Tagesordnung steuern. Änderungen an der Tagesordnung werden unmittelbar auf dem Beamer dargestellt.
|
||||
|
||||
Darüber hinaus können sich die Teilnehmer einer Veranstaltung mit ihrem Laptop/Smartphone an OpenSlides anmelden und so z.B. Anträge (elektronisch) einreichen, für eine Wahl kandidieren, die Tagesordnung verfolgen, Abstimmungsergebnisse nachlesen u.v.m.
|
||||
|
||||
OpenSlides ist konzipiert für den Einsatz auf Veranstaltungen wie
|
||||
Hauptversammlungen, Delegierten-/Mitgliederversammlungen und Parteitagen.
|
||||
|
||||
Der entscheidene Vorteil gegenüber herkömmlichen Office-Präsentations-Systemen, wie MS PowerPoint oder OpenOffice Impress, ist die Bearbeitung der Folien über ein Redaktionssystem-System. Der Präsentationsmodus auf dem Beamer muss nicht verlassen werden.
|
||||
|
||||
Systemanforderungen
|
||||
-------------------
|
||||
|
||||
- Django 1.3+
|
||||
- Python 2.5+
|
||||
- Webbrowser
|
||||
|
||||
License
|
||||
-------
|
||||
OpenSlides is Free Software licensed under the GNU General Public License v2+ (GNU GPL).
|
||||
The software is free to use without restrictions, may be modified and that
|
||||
modifications may be distributed.
|
||||
A copy of the license is included with every release of OpenSlides, but you can
|
||||
also read the text of the license in the source code repository.
|
||||
|
||||
|
||||
Funktionen
|
||||
==========
|
||||
|
||||
Tagesordnung
|
||||
------------
|
||||
|
||||
- Tagesordnung verwalten
|
||||
- Eintrag auswählen zur Anzeige am Beamer
|
||||
- Beamer-Ansicht mit automatischer Aktualisierung bei Änderung (der Beamer läuft z.B. im Browser mit Vollbildmodus auf einem 2. Monitor)
|
||||
- Eintrag während der Anzeige am Beamer im Webinterface bearbeiten
|
||||
- Eintrag per Drag&Drop in der Tagesordnung sortieren (Unterpunkte möglich)
|
||||
- Eintrag als erledigt markieren
|
||||
- Eintrag auf der Beamer-Ansicht verstecken
|
||||
- Tagesordnung als pdf erzeugen
|
||||
- aktuelle Uhrzeit auf der Beameransicht
|
||||
|
||||
.. image:: _static/images/agenda_de.png
|
||||
:width: 45%
|
||||
:alt: Tagesordnungs-Ansicht
|
||||
|
||||
.. image:: _static/images/agenda-beamer_de.png
|
||||
:width: 45%
|
||||
:alt: Beamer-Ansicht
|
||||
|
||||
.. image:: _static/images/agenda-new-item_de.png
|
||||
:width: 45%
|
||||
:alt: Neuen Tagesordnungseintrag anlegen
|
||||
|
||||
.. image:: _static/images/agenda-pdf_de.png
|
||||
:width: 45%
|
||||
:alt: Tagesordnung als PDF
|
||||
|
||||
|
||||
Anträge
|
||||
-------
|
||||
|
||||
- Anträge anlegen, bearbeiten, löschen
|
||||
- Anträge von anderen Teilnehmern unterstützen lassen
|
||||
- Antragsstatus ändern und Abstimmungsergebnisse eingeben
|
||||
- Abstimmungsergebenisse aus mehreren Wahlgängen darstellen
|
||||
- Antrag als Tagesordnungseintrag anlegen und anzeigen
|
||||
- Änderungshistorie eines Antrags anzeigen
|
||||
- Antragsformular als pdf erzeugen
|
||||
- Übersicht aller Anträge als pdf
|
||||
|
||||
.. image:: _static/images/application-overview_de.png
|
||||
:width: 45%
|
||||
:alt: Antragsübersicht
|
||||
|
||||
.. image:: _static/images/application-overview-beamer_de.png
|
||||
:width: 45%
|
||||
:alt: Beamer-Ansicht Antragsübersicht
|
||||
|
||||
.. image:: _static/images/application-view_de.png
|
||||
:width: 45%
|
||||
:alt: Darstellung eines Antrags mit Verwaltugsfunktion
|
||||
|
||||
.. image:: _static/images/application-view-beamer_de.png
|
||||
:width: 45%
|
||||
:alt: Beamer-Ansicht eines einzelnen Antrags
|
||||
|
||||
Wahlen
|
||||
------
|
||||
|
||||
- Kandidaten aus Teilnehmerliste für eine Wahl vorschlagen (bzw. als angemeldeter Teilnehmer selbst kandidieren)
|
||||
- Wahlschein als pdf generieren (mit Ankreuzfeld)
|
||||
- Wahlergebenisse eingeben und darstellen
|
||||
- mehrere Wahlgänge und Stichwahlen werden unterstützt
|
||||
|
||||
|
||||
Abstimmungen
|
||||
------------
|
||||
|
||||
- Abstimmungen verwalten (als Ergänzung zu Anträgen und Wahlen)
|
||||
- Ergebnisse eingeben und darstellen (analog zu Anträgen und Wahlen)
|
||||
- Abstimmungsmodus: entweder nur Ja-Stimmen oder Ja/Nein/Enthaltungs-Stimmen wählbar
|
||||
- ungültige und abgegebene Stimmen können eingegeben werden
|
||||
|
||||
|
||||
Teilnehmer
|
||||
----------
|
||||
|
||||
- Teilnehmer anlegen und verwalten (vordefinierte Felder: *Name, Vorname, E-Mail, Geschlecht, Gruppe, Typ, Amt*)
|
||||
- importieren von Teilnehmerdaten (im CSV-Format)
|
||||
- Benutzergruppe frei konfigurierbar
|
||||
|
||||
Allgemein
|
||||
---------
|
||||
|
||||
- Template für Beamer und Webinterface leicht per HTML und CSS anpassbar
|
||||
- OpenSlides ist Freie Software (`GPL v2+ Lizenz <about.html#lizenz>`_)
|
||||
- Plattformunabhängig (läuft überall dort, wo Pyhton läuft)
|
||||
- vollständige deutsche und englische Übersetzung vorhanden, weitere Sprachen möglich
|
||||
|
||||
|
||||
Ausblick -- Weiterentwicklungsideen für OpenSlides:
|
||||
---------------------------------------------------
|
||||
|
||||
- schnelle und einfache Erstellung eines Ergebnisprotokolls (mit allen Tagesordnungseinträgen, Beschlüssen, Anträgen, Abstimmungen und Wahlergbnissen)
|
||||
- grafische Darstellung der Wahl- und Abstimmungsergebnisse in Diagrammen
|
||||
- Einbindung von Grafiken in Tagesordnungseinträge
|
||||
- Anbindung eines elektronischen Voting-Systems
|
||||
|
||||
Interesse an der Weiterentwicklung von OpenSlides? Wir freuen uns über jede Mithilfe!
|
||||
|
12
docs/en/download.rst
Normal file
@ -0,0 +1,12 @@
|
||||
Download
|
||||
========
|
||||
|
||||
There is not yet a release of OpenSlides.
|
||||
Version 1.0 is scheduled for summer 2011.
|
||||
|
||||
You can check the current development status in our
|
||||
public sourcecode repository.
|
||||
|
||||
Checkout OpenSlides sourcecode::
|
||||
|
||||
hg clone http://hg.openslides.org
|
34
docs/en/help.rst
Normal file
@ -0,0 +1,34 @@
|
||||
Help
|
||||
====
|
||||
|
||||
|
||||
Mailing lists
|
||||
-------------
|
||||
|
||||
OpenSlides has two public mailing lists in English and German language
|
||||
for coordinate the development and discussing tickets, user questions or
|
||||
special use cases. Please subscribe before you are sending an email to a list.
|
||||
|
||||
English language::
|
||||
|
||||
user@openslides.org
|
||||
|
||||
German language::
|
||||
|
||||
user-de@openslides.org
|
||||
|
||||
|
||||
|
||||
Bug reports
|
||||
-----------
|
||||
|
||||
Please use our `ticket system <http://trac.openslides.org/report/3>`_
|
||||
to report bugs for OpenSlides.
|
||||
|
||||
|
||||
|
||||
Want to contribute to OpenSlides?
|
||||
---------------------------------
|
||||
OpenSlides is an open-source project driven by volunteers.
|
||||
From code to localizations or artwork, any contribution is welcome!
|
||||
|
12
docs/en/index.rst
Normal file
@ -0,0 +1,12 @@
|
||||
Welcome
|
||||
=======
|
||||
|
||||
OpenSlides is a free, webbased presentation system
|
||||
for agenda, application, polls and elections.
|
||||
|
||||
Read more about the :doc:`features <about>` of OpenSlides.
|
||||
|
||||
.. image:: _static/images/agenda_de.png
|
||||
:width: 90%
|
||||
:alt: Agenda view of OpenSlides
|
||||
|
170
docs/make.bat
Normal file
@ -0,0 +1,170 @@
|
||||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set BUILDDIR=_build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. singlehtml to make a single large HTML file
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. devhelp to make HTML files and a Devhelp project
|
||||
echo. epub to make an epub
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. text to make text files
|
||||
echo. man to make manual pages
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "singlehtml" (
|
||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\OpenSlides.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\OpenSlides.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "devhelp" (
|
||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub" (
|
||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "text" (
|
||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "man" (
|
||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
1637
extras/logo/openlides-logo.svg
Normal file
After Width: | Height: | Size: 67 KiB |
BIN
extras/logo/openlides-logo_100.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
extras/logo/openlides-logo_150.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
extras/logo/openlides-logo_icon.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
1291
extras/logo/openlides-logo_icon.svg
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
extras/logo/openlides-logo_wide.png
Normal file
After Width: | Height: | Size: 13 KiB |
1675
extras/logo/openlides-logo_wide.svg
Normal file
After Width: | Height: | Size: 70 KiB |
294
initial_data.json
Normal file
@ -0,0 +1,294 @@
|
||||
[
|
||||
{
|
||||
"pk":1,
|
||||
"model":"agenda.item",
|
||||
"fields":{
|
||||
"weight":0,
|
||||
"parent":null,
|
||||
"title":"TOP 1 - Begr\u00fc\u00dfung",
|
||||
"hidden":false,
|
||||
"real_type":10,
|
||||
"closed":false
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk":2,
|
||||
"model":"agenda.item",
|
||||
"fields":{
|
||||
"weight":0,
|
||||
"parent":null,
|
||||
"title":"TOP 23 - Debatten \u00fcber IT",
|
||||
"hidden":false,
|
||||
"real_type":10,
|
||||
"closed":false
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk":1,
|
||||
"model":"agenda.itemtext",
|
||||
"fields":{
|
||||
"text":"Wir begr\u00fc\u00dfen euch herzlich."
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk":2,
|
||||
"model":"agenda.itemtext",
|
||||
"fields":{
|
||||
"text":"Hier debattieren wir \u00fcber IT-Sachen."
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk":1,
|
||||
"model":"participant.profile",
|
||||
"fields":{
|
||||
"gender":"none",
|
||||
"group":"",
|
||||
"user":2,
|
||||
"committee":"",
|
||||
"type":"observer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk":2,
|
||||
"model":"participant.profile",
|
||||
"fields":{
|
||||
"gender":"none",
|
||||
"group":"",
|
||||
"user":3,
|
||||
"committee":"",
|
||||
"type":"delegate"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk":3,
|
||||
"model":"participant.profile",
|
||||
"fields":{
|
||||
"gender":"none",
|
||||
"group":"",
|
||||
"user":4,
|
||||
"committee":"",
|
||||
"type":"staff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk":1,
|
||||
"model":"auth.group",
|
||||
"fields":{
|
||||
"name":"Teilnehmer (unangemeldet)",
|
||||
"permissions":[
|
||||
28,
|
||||
55,
|
||||
74,
|
||||
75,
|
||||
73,
|
||||
50
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk":2,
|
||||
"model":"auth.group",
|
||||
"fields":{
|
||||
"name":"Teilnehmer (angemeldet) = Beobachter, Berichterstatter, GS",
|
||||
"permissions":[
|
||||
28,
|
||||
56,
|
||||
55,
|
||||
74,
|
||||
75,
|
||||
73,
|
||||
50
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk":3,
|
||||
"model":"auth.group",
|
||||
"fields":{
|
||||
"name":"Delegierte",
|
||||
"permissions":[
|
||||
28,
|
||||
56,
|
||||
57,
|
||||
55,
|
||||
74,
|
||||
75,
|
||||
73,
|
||||
50
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk":4,
|
||||
"model":"auth.group",
|
||||
"fields":{
|
||||
"name":"Tagesleitung",
|
||||
"permissions":[
|
||||
29,
|
||||
30,
|
||||
28,
|
||||
56,
|
||||
58,
|
||||
55,
|
||||
76,
|
||||
74,
|
||||
75,
|
||||
73,
|
||||
50,
|
||||
66,
|
||||
65
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk":5,
|
||||
"model":"auth.group",
|
||||
"fields":{
|
||||
"name":"Infopoint (Teilnehmerverwaltung)",
|
||||
"permissions":[
|
||||
30,
|
||||
28,
|
||||
55,
|
||||
74,
|
||||
75,
|
||||
73,
|
||||
51,
|
||||
50
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk":6,
|
||||
"model":"auth.group",
|
||||
"fields":{
|
||||
"name":"Pr\u00e4sidium",
|
||||
"permissions":[
|
||||
29,
|
||||
30,
|
||||
28,
|
||||
56,
|
||||
58,
|
||||
55,
|
||||
76,
|
||||
74,
|
||||
75,
|
||||
73,
|
||||
50,
|
||||
51,
|
||||
66,
|
||||
65
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk":7,
|
||||
"model":"auth.group",
|
||||
"fields":{
|
||||
"name":"Techniker",
|
||||
"permissions":[
|
||||
29,
|
||||
30,
|
||||
28,
|
||||
58,
|
||||
55,
|
||||
76,
|
||||
74,
|
||||
75,
|
||||
73,
|
||||
51,
|
||||
50,
|
||||
66,
|
||||
65,
|
||||
46
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk":1,
|
||||
"model":"auth.user",
|
||||
"fields":{
|
||||
"username":"admin",
|
||||
"first_name":"",
|
||||
"last_name":"",
|
||||
"is_active":true,
|
||||
"is_superuser":true,
|
||||
"is_staff":true,
|
||||
"last_login":"2011-03-24 15:30:36",
|
||||
"groups":[
|
||||
|
||||
],
|
||||
"user_permissions":[
|
||||
|
||||
],
|
||||
"password":"sha1$40ca1$d91a0ff8e571d6577fe5de241717ac1e1da94596",
|
||||
"email":"admin@oshahn.de",
|
||||
"date_joined":"2011-03-13 08:56:04"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk":2,
|
||||
"model":"auth.user",
|
||||
"fields":{
|
||||
"username":"MusterBeobachter",
|
||||
"first_name":"Muster",
|
||||
"last_name":"Beobachter",
|
||||
"is_active":true,
|
||||
"is_superuser":false,
|
||||
"is_staff":false,
|
||||
"last_login":"2011-03-24 15:42:50",
|
||||
"groups":[
|
||||
2
|
||||
],
|
||||
"user_permissions":[
|
||||
|
||||
],
|
||||
"password":"sha1$3eccf$7b61f1f1a0fd19f003b80aa653d9ba55a22d2db7",
|
||||
"email":"",
|
||||
"date_joined":"2011-03-24 15:42:50"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk":3,
|
||||
"model":"auth.user",
|
||||
"fields":{
|
||||
"username":"MusterDelegierter",
|
||||
"first_name":"Muster",
|
||||
"last_name":"Delegierter",
|
||||
"is_active":true,
|
||||
"is_superuser":false,
|
||||
"is_staff":false,
|
||||
"last_login":"2011-03-24 15:43:57",
|
||||
"groups":[
|
||||
3
|
||||
],
|
||||
"user_permissions":[
|
||||
|
||||
],
|
||||
"password":"sha1$99b44$ca83292683582654fa55c3ce833ed9c914e9c127",
|
||||
"email":"",
|
||||
"date_joined":"2011-03-24 15:43:57"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk":4,
|
||||
"model":"auth.user",
|
||||
"fields":{
|
||||
"username":"MusterPr\u00e4sidium",
|
||||
"first_name":"Muster",
|
||||
"last_name":"Pr\u00e4sidium",
|
||||
"is_active":true,
|
||||
"is_superuser":false,
|
||||
"is_staff":false,
|
||||
"last_login":"2011-03-24 15:45:12",
|
||||
"groups":[
|
||||
6
|
||||
],
|
||||
"user_permissions":[
|
||||
|
||||
],
|
||||
"password":"sha1$d4a27$d8026e4e1169f7ae60dcb8372850f7218fb2b1dd",
|
||||
"email":"",
|
||||
"date_joined":"2011-03-24 15:45:12"
|
||||
}
|
||||
}
|
||||
]
|
29
manage.py
Normal file
@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.manage
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Django's execute manager.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.core.management import execute_manager
|
||||
|
||||
import os, site
|
||||
|
||||
SITE_ROOT = os.path.realpath(os.path.dirname(__file__))
|
||||
|
||||
site.addsitedir(os.path.join(SITE_ROOT, 'openslides'))
|
||||
|
||||
try:
|
||||
from openslides import settings
|
||||
except ImportError:
|
||||
import sys
|
||||
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
execute_manager(settings)
|
50
openslides/__init__.py
Normal file
@ -0,0 +1,50 @@
|
||||
import socket
|
||||
import os
|
||||
import sys
|
||||
from os.path import realpath, join, dirname
|
||||
try:
|
||||
from mercurial import ui as hgui
|
||||
from mercurial.localrepo import localrepository
|
||||
from mercurial.node import short as shorthex
|
||||
from mercurial.error import RepoError
|
||||
nomercurial = False
|
||||
except:
|
||||
nomercurial = True
|
||||
|
||||
from django.template import add_to_builtins
|
||||
|
||||
|
||||
add_to_builtins('django.templatetags.i18n')
|
||||
|
||||
OPENSLIDES_REVISION = 'unknown'
|
||||
|
||||
# Don't read ~/.hgrc, as extensions aren't available in the venvs
|
||||
os.environ['HGRCPATH'] = ''
|
||||
|
||||
|
||||
def _bootstrap():
|
||||
conts = realpath(join(dirname(__file__)))
|
||||
try:
|
||||
ui = hgui.ui()
|
||||
repository = localrepository(ui, join(conts, '..'))
|
||||
#repository = localrepository(ui, conts)
|
||||
ctx = repository['tip']
|
||||
revision = '%(num)s:%(id)s' % {
|
||||
'num': ctx.rev(), 'id': shorthex(ctx.node())
|
||||
}
|
||||
except TypeError:
|
||||
revision = 'unknown'
|
||||
except RepoError:
|
||||
return 0
|
||||
|
||||
# This value defines the timeout for sockets in seconds. Per default python
|
||||
# sockets do never timeout and as such we have blocking workers.
|
||||
# Socket timeouts are set globally within the whole application.
|
||||
# The value *must* be a floating point value.
|
||||
socket.setdefaulttimeout(10.0)
|
||||
|
||||
return revision
|
||||
|
||||
if not nomercurial:
|
||||
OPENSLIDES_REVISION = _bootstrap()
|
||||
del _bootstrap
|
0
openslides/agenda/__init__.py
Normal file
18
openslides/agenda/admin.py
Normal file
@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.agenda.admin
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Register app for admin site.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
from openslides.agenda.models import Item, ItemText
|
||||
|
||||
admin.site.register(Item)
|
||||
admin.site.register(ItemText)
|
55
openslides/agenda/api.py
Normal file
@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.agenda.api
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Useful functions for the agenda app.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from openslides.system.api import config_get
|
||||
|
||||
|
||||
def get_active_item(only_id=False):
|
||||
"""
|
||||
Returns the active Item. If no item is active, or it can not find an Item,
|
||||
it raise Item.DoesNotExist
|
||||
|
||||
if only_id is True, returns only the id of this item. Returns None if not Item
|
||||
is active. Does not Raise Item.DoesNotExist
|
||||
"""
|
||||
from agenda.models import Item
|
||||
id = config_get("presentation", None)
|
||||
if only_id:
|
||||
if id is None:
|
||||
return None
|
||||
return int(id)
|
||||
return Item.objects.get(pk=id)
|
||||
|
||||
|
||||
def is_summary():
|
||||
"""
|
||||
True, if a summery shall be displayed
|
||||
"""
|
||||
from agenda.models import Item
|
||||
try:
|
||||
get_active_item()
|
||||
except Item.DoesNotExist:
|
||||
return True
|
||||
if config_get('summary', False):
|
||||
return True
|
||||
return False
|
||||
|
||||
def children_list(items):
|
||||
"""
|
||||
Return a list for items with all childitems in the right order.
|
||||
"""
|
||||
l = []
|
||||
for item in items:
|
||||
l.append(item)
|
||||
if item.children:
|
||||
l += children_list(item.children)
|
||||
return l
|
82
openslides/agenda/forms.py
Normal file
@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.agenda.forms
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Forms for the agenda app.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.forms import Form, ModelForm, IntegerField, ChoiceField, \
|
||||
ModelChoiceField, HiddenInput, Select
|
||||
from django.utils.translation import ugettext as _
|
||||
from openslides.agenda.models import Item, ItemText, ItemApplication, ItemPoll, \
|
||||
ItemAssignment
|
||||
|
||||
class ItemFormText(ModelForm):
|
||||
error_css_class = 'error'
|
||||
required_css_class = 'required'
|
||||
items = Item.objects.all().filter(parent=None).order_by('weight')
|
||||
parent = ModelChoiceField(queryset=items, label=_("Parent item"))
|
||||
class Meta:
|
||||
model = ItemText
|
||||
exclude = ('closed')
|
||||
|
||||
|
||||
class ItemFormApplication(ModelForm):
|
||||
error_css_class = 'error'
|
||||
required_css_class = 'required'
|
||||
items = Item.objects.all().filter(parent=None).order_by('weight')
|
||||
parent = ModelChoiceField(queryset=items, label=_("Parent item"))
|
||||
|
||||
class Meta:
|
||||
model = ItemApplication
|
||||
exclude = ('closed')
|
||||
|
||||
|
||||
class ItemFormPoll(ModelForm):
|
||||
error_css_class = 'error'
|
||||
required_css_class = 'required'
|
||||
items = Item.objects.all().filter(parent=None).order_by('weight')
|
||||
parent = ModelChoiceField(queryset=items, label=_("Parent item"))
|
||||
|
||||
class Meta:
|
||||
model = ItemPoll
|
||||
exclude = ('closed')
|
||||
|
||||
|
||||
class ItemFormAssignment(ModelForm):
|
||||
error_css_class = 'error'
|
||||
required_css_class = 'required'
|
||||
items = Item.objects.all().filter(parent=None).order_by('weight')
|
||||
parent = ModelChoiceField(queryset=items, label=_("Parent item"))
|
||||
|
||||
class Meta:
|
||||
model = ItemAssignment
|
||||
exclude = ('closed')
|
||||
|
||||
|
||||
def genweightchoices():
|
||||
l = []
|
||||
for i in range(-50, 51):
|
||||
l.append(('%d' % i, i))
|
||||
return l
|
||||
|
||||
|
||||
class ElementOrderForm(Form):
|
||||
weight = ChoiceField(choices=genweightchoices(), \
|
||||
widget=Select(attrs={'class': 'menu-weight'}),
|
||||
label="")
|
||||
self = IntegerField(widget=HiddenInput(attrs={'class': 'menu-mlid'}))
|
||||
parent = IntegerField(widget=HiddenInput(attrs={'class': 'menu-plid'}))
|
||||
|
||||
|
||||
MODELFORM = {
|
||||
'ItemText': ItemFormText,
|
||||
'ItemApplication': ItemFormApplication,
|
||||
'ItemPoll': ItemFormPoll,
|
||||
'ItemAssignment': ItemFormAssignment,
|
||||
}
|
198
openslides/agenda/models.py
Normal file
@ -0,0 +1,198 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.agenda.models
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Models for the agenda app.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from model_utils.models import InheritanceCastModel
|
||||
|
||||
from openslides.agenda.api import get_active_item
|
||||
from openslides.system.api import config_set
|
||||
from openslides.application.models import Application
|
||||
from openslides.poll.models import Poll
|
||||
from openslides.assignment.models import Assignment
|
||||
|
||||
|
||||
class Item(InheritanceCastModel):
|
||||
"""
|
||||
The BasisItem.
|
||||
Has all the attributes all Items need.
|
||||
"""
|
||||
title = models.CharField(max_length=100, verbose_name=_("Title"))
|
||||
closed = models.BooleanField(default=False, verbose_name=_("Closed"))
|
||||
weight = models.IntegerField(default=0, verbose_name=_("Weight"))
|
||||
parent = models.ForeignKey('self', blank=True, null=True)
|
||||
hidden = models.BooleanField(default=False,
|
||||
verbose_name=_("Hidden (visible for agenda manager only)"))
|
||||
|
||||
@property
|
||||
def active(self):
|
||||
"""
|
||||
Return True, if the the item is the active one.
|
||||
"""
|
||||
return True if get_active_item(only_id=True) == self.id else False
|
||||
|
||||
@property
|
||||
def active_parent(self):
|
||||
"""
|
||||
Return True if the item has a activ parent
|
||||
"""
|
||||
if get_active_item(only_id=True) in \
|
||||
[parent.id for parent in self.parents]:
|
||||
return True
|
||||
return False
|
||||
|
||||
def set_active(self, summary=False):
|
||||
"""
|
||||
Appoint this item as the active one.
|
||||
"""
|
||||
config_set("presentation", self.id)
|
||||
if summary:
|
||||
config_set("summary", True)
|
||||
else:
|
||||
config_set("summary", '')
|
||||
|
||||
def set_closed(self, closed=True):
|
||||
"""
|
||||
Changes the closed-status of the item.
|
||||
"""
|
||||
self.closed = closed
|
||||
self.save()
|
||||
|
||||
@property
|
||||
def parents(self):
|
||||
"""
|
||||
Return the parent of this item, and the parent's partent and so
|
||||
furth a list.
|
||||
"""
|
||||
parents = []
|
||||
item = self
|
||||
while item.parent is not None:
|
||||
parents.append(item.parent)
|
||||
item = item.parent
|
||||
return parents
|
||||
|
||||
@property
|
||||
def children(self):
|
||||
"""
|
||||
Return a list of all childitems from the next generation. The list
|
||||
is ordert by weight. The childitems are not cast, so there are only
|
||||
Item-objects and not Item-type objects.
|
||||
"""
|
||||
return self.item_set.order_by("weight")
|
||||
|
||||
@property
|
||||
def weight_form(self):
|
||||
"""
|
||||
Return the WeightForm for this item.
|
||||
"""
|
||||
from agenda.forms import ElementOrderForm
|
||||
try:
|
||||
parent = self.parent.id
|
||||
except AttributeError:
|
||||
parent = 0
|
||||
initial = {
|
||||
'weight': self.weight,
|
||||
'self': self.id,
|
||||
'parent': parent,
|
||||
}
|
||||
return ElementOrderForm(initial=initial, prefix="i%d" % self.id)
|
||||
|
||||
def edit_form(self, post=None):
|
||||
"""
|
||||
Return the EditForm for this item.
|
||||
"""
|
||||
try:
|
||||
return self._edit_form
|
||||
except AttributeError:
|
||||
from agenda.forms import MODELFORM
|
||||
try:
|
||||
form = MODELFORM[self.type]
|
||||
except KeyError:
|
||||
raise NameError(_("No Form for itemtype %s") % self.type)
|
||||
|
||||
self._edit_form = form(post, instance=self)
|
||||
return self._edit_form
|
||||
|
||||
@models.permalink
|
||||
def get_absolute_url(self, link='view'):
|
||||
"""
|
||||
Return the URL to this item. By default it is the Link to its
|
||||
beamer-view.
|
||||
|
||||
link can be:
|
||||
* view
|
||||
* delete
|
||||
"""
|
||||
if link == 'view':
|
||||
return ('item_view', [str(self.id)])
|
||||
if link == 'delete':
|
||||
return ('item_delete', [str(self.id)])
|
||||
|
||||
def __unicode__(self):
|
||||
return self.title
|
||||
|
||||
def cast(self):
|
||||
try:
|
||||
return self.realobject
|
||||
except AttributeError:
|
||||
self.realobject = super(Item, self).cast()
|
||||
return self.realobject
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
"""
|
||||
Return the name of the class from this item
|
||||
"""
|
||||
try:
|
||||
return self._type
|
||||
except AttributeError:
|
||||
self._type = self.cast().__class__.__name__
|
||||
return self._type
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
('can_view_agenda', "Can see the agenda"),
|
||||
('can_manage_agenda', "Can manage the agenda and its items"),
|
||||
('can_see_beamer', "Can see the Beamer"),
|
||||
)
|
||||
|
||||
|
||||
class ItemText(Item):
|
||||
"""
|
||||
An Item with a TextField.
|
||||
"""
|
||||
text = models.TextField(null=True, blank=True, verbose_name=_("Text"))
|
||||
|
||||
class Meta:
|
||||
pass
|
||||
|
||||
|
||||
class ItemApplication(Item):
|
||||
"""
|
||||
An Item which is connected to an application.
|
||||
"""
|
||||
application = models.ForeignKey(Application, verbose_name=_("Application"))
|
||||
|
||||
|
||||
class ItemAssignment(Item):
|
||||
"""
|
||||
An Item which is connected to an assignment.
|
||||
"""
|
||||
assignment = models.ForeignKey(Assignment, verbose_name=_("Election"))
|
||||
|
||||
|
||||
class ItemPoll(Item):
|
||||
"""
|
||||
An Item which is connected to a poll
|
||||
"""
|
||||
poll = models.ForeignKey(Poll, verbose_name=_("Poll"))
|
17
openslides/agenda/templates/agenda/base_agenda.html
Normal file
@ -0,0 +1,17 @@
|
||||
{% extends "base.html" %}
|
||||
{% load tags %}
|
||||
|
||||
{% block submenu %}
|
||||
{% url item_overview as url_itemoverview %}
|
||||
{% url item_new as url_itemnew %}
|
||||
<h4 class="sectiontitle">{%trans "Agenda" %}</h4>
|
||||
<ul>
|
||||
<li class="{% if request.path == url_itemoverview %}selected{% endif %}"><a href="{% url item_overview %}">{%trans "All items" %}</a></li>
|
||||
{% if perms.agenda.can_manage_agenda %}
|
||||
<li class="{% active request '/item/new/' %}"><a href="{% url item_new 'ItemText' %}">{%trans "New item" %}</a></li>
|
||||
{% endif %}
|
||||
{% if perms.agenda.can_see_beamer %}
|
||||
<li><a href="{% url item_beamer %}"><img src="/static/images/icons/view-presentation.png"> {%trans 'Beamer view' %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
35
openslides/agenda/templates/agenda/edit.html
Normal file
@ -0,0 +1,35 @@
|
||||
{% extends "agenda/base_agenda.html" %}
|
||||
{% block title %}{{ block.super }} - {%trans "Item" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if item %}
|
||||
<h1>{%trans "Edit item" %}</h1>
|
||||
{% else %}
|
||||
<h1>{%trans "New item" %}</h1>
|
||||
|
||||
<p>{%trans "Choose item type:" %}</p>
|
||||
<p>
|
||||
<a href="{% url item_new 'ItemText' %}"
|
||||
{% ifequal request.path '/item/new/ItemText/' %}style='font-size:15px; font-weight:bold;'{% endifequal %}
|
||||
>{%trans "Item of Text" %}</a> |
|
||||
<a href="{% url item_new 'ItemApplication' %}"
|
||||
{% ifequal request.path '/item/new/ItemApplication/' %}style='font-size:15px; font-weight:bold;'{% endifequal %}
|
||||
>{%trans "Item of Application" %}</a> |
|
||||
<a href="{% url item_new 'ItemAssignment' %}"
|
||||
{% ifequal request.path '/item/new/ItemAssignment/' %}style='font-size:15px; font-weight:bold;'{% endifequal %}
|
||||
>{%trans "Item of Election" %}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit">
|
||||
<span class="icon ok">{%trans 'Save' %}</span>
|
||||
</button>
|
||||
<a href='{% url item_overview %}'>
|
||||
<button type="button">
|
||||
<span class="icon cancel">{%trans 'Cancel' %}</span>
|
||||
</button>
|
||||
</a>
|
||||
</form>
|
||||
{% endblock %}
|
158
openslides/agenda/templates/agenda/overview.html
Normal file
@ -0,0 +1,158 @@
|
||||
{% extends "agenda/base_agenda.html" %}
|
||||
{% load tags %}
|
||||
{% block title %}{{ block.super }} - {% trans "Agenda" %}{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
<link type="text/css" rel="stylesheet" media="all" href="/static/styles/tabledrag.css" />
|
||||
{% if perms.agenda.can_manage_agenda %}
|
||||
<script type="text/javascript" src="/static/javascript/jquery.js"></script>
|
||||
<script type="text/javascript" src="/static/javascript/jquery.once.js"></script>
|
||||
<script type="text/javascript" src="/static/javascript/jquery.cookie.js"></script>
|
||||
<script type="text/javascript" src="/static/javascript/tabledrag.js"></script>
|
||||
<script type="text/javascript">
|
||||
<!--//--><![CDATA[//><!--
|
||||
//This is Drupal Code
|
||||
jQuery.extend(
|
||||
Drupal.settings,
|
||||
{
|
||||
"tableDrag":
|
||||
{ "menu-overview":
|
||||
{ "menu-plid":
|
||||
[ { "target": "menu-plid", "source": "menu-mlid", "relationship": "parent", "action": "match", "hidden": true, "limit": 8 } ],
|
||||
"menu-weight":
|
||||
[ { "target": "menu-weight", "source": "menu-weight", "relationship": "sibling", "action": "order", "hidden": true, "limit": 0 } ]
|
||||
}
|
||||
}
|
||||
});
|
||||
//--><!]]>
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "Agenda" %}</h1>
|
||||
{% if items %}
|
||||
<form action="/item/" method="post">{% csrf_token %}
|
||||
{% if perms.agenda.can_manage_agenda %}
|
||||
<div id="changed-order-message" style="display:none" class="notification warning">
|
||||
<em>{% trans "Do you want to save the changed order of agenda items?" %}<br>
|
||||
<input type="submit" value="{% trans 'Yes' %}">
|
||||
<input type="button" onclick="window.location.href='{% url item_overview %}';" value="{%trans 'No' %}">
|
||||
</em>
|
||||
</div>
|
||||
{% endif %}
|
||||
<table id="menu-overview" class="agendatable">
|
||||
<tr>
|
||||
<th style="width: 1px;">{% trans "Beamer" %}</th>
|
||||
<th>{% trans "Item" %}</th>
|
||||
{% if perms.agenda.can_manage_agenda %}
|
||||
<th>{% trans "Item Type" %}</th>
|
||||
{% endif %}
|
||||
<th style="width: 1px;">{% if perms.agenda.can_manage_agenda %}{% trans "Actions" %}{% endif %}</th>
|
||||
{% if perms.agenda.can_manage_agenda %}
|
||||
<th class="tabledrag-hide">{% trans "Weight" %}</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr {% if overview %}class="activeline"{% endif %}>
|
||||
<td {% if perms.agenda.can_manage_agenda %}
|
||||
class="select" onclick="window.location.href='{% url item_activate 0 %}'"
|
||||
{% endif %}>
|
||||
{% if overview %}
|
||||
<img class="center" src="/static/images/icons/task-accepted.png" title="{% trans 'Agenda selected' %}">
|
||||
{% else %}
|
||||
{% if perms.agenda.can_manage_agenda %}<img class="center" src="/static/images/icons/task-accepted-grey.png" title="{% trans 'Select agenda' %}">{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><i>{% trans "Agenda" %} ({{ items|length }} {% trans "items" %})</i></td>
|
||||
{% if perms.agenda.can_manage_agenda %}
|
||||
<td></td>
|
||||
{% endif %}
|
||||
<td style="width: 1px;white-space: nowrap"><a href="{% url print_agenda %}" title="{%trans 'Print reduced agenda (only first parent items)' %}"><img src="/static/images/icons/application-pdf.png"></a>
|
||||
<a href="{% url print_agenda_full printAllItems=1 %}" title="{%trans 'Print full agenda (all items)' %}"><img src="/static/images/icons/pdf-annotations.png"></a></td>
|
||||
</tr>
|
||||
{% for item in items %}
|
||||
{% if not item.hidden or item.hidden and perms.agenda.can_manage_agenda %}
|
||||
<tr class="draggable {% cycle 'odd' '' %}
|
||||
{% if item.active %}activeline{% else %}
|
||||
{% if item.parent.active and summary %}activesummarychildline{% endif %}
|
||||
{% endif %}">
|
||||
<td {% if perms.agenda.can_manage_agenda %}
|
||||
class="select" onclick="window.location.href='{% url item_activate item.id %}'"
|
||||
{% endif %}>
|
||||
{% if item.active %}
|
||||
<img class="center" src="/static/images/icons/task-accepted.png" title="{% trans 'Item selected' %}"></span>
|
||||
{% else %}
|
||||
{% if perms.agenda.can_manage_agenda %}<img class="center" src="/static/images/icons/task-accepted-grey.png" title="{% trans 'Select item' %}"> {% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% for p in item.parents %}
|
||||
<div class="indentation"> </div>
|
||||
{% endfor %}
|
||||
|
||||
{% if perms.agenda.can_manage_agenda %}
|
||||
<div class="dragcell"></div>
|
||||
{% endif %}
|
||||
|
||||
{{ item }}
|
||||
|
||||
</td>
|
||||
{% if perms.agenda.can_manage_agenda %}
|
||||
<td>
|
||||
{% ifequal item.type 'ItemApplication' %}
|
||||
<a href="{% url application_view item.cast.application.id %}">{% trans "Application" %} {{ item.cast.application.number }}</a>
|
||||
{% endifequal %}
|
||||
|
||||
{% ifequal item.type 'ItemPoll' %}
|
||||
{% if item.cast.poll.application %}
|
||||
<a href="{% url poll_view item.cast.poll.id %}">{% trans "Poll of Application" %}</a>
|
||||
{% endif %}
|
||||
{% if item.cast.poll.assignment %}
|
||||
<a href="{% url poll_view item.cast.poll.id %}">{% trans "Poll of Election" %}</a>
|
||||
{% endif %}
|
||||
{% endifequal %}
|
||||
|
||||
{% ifequal item.type 'ItemAssignment' %}
|
||||
<a href="{% url assignment_view item.cast.assignment.id %}">{% trans "Election" %}</a>
|
||||
{% endifequal %}
|
||||
|
||||
{% ifequal item.type 'ItemText' %}
|
||||
Text
|
||||
{% endifequal %}
|
||||
</td>
|
||||
{% endif %}
|
||||
<td style="width: 1px;white-space: nowrap">
|
||||
<a href="{{ item.get_absolute_url }}"><img src="/static/images/icons/document-preview.png" title="{% trans 'Show beamer preview' %}"></a>
|
||||
|
||||
{% if perms.agenda.can_manage_agenda %}
|
||||
<a href="{% url item_edit item.id %}"><img src="/static/images/icons/document-edit.png" title="{% trans 'Edit item' %}"></a>
|
||||
<a href="{% url item_delete item.id %}"><img src="/static/images/icons/edit-delete.png" title="{% trans 'Delete item' %}"></a>
|
||||
{% if item.closed %}
|
||||
<a href="{% url item_open item.id %}"><img src="/static/images/icons/user-offline.png" title="{% trans 'Click to open item' %}"></a>
|
||||
{% else %}
|
||||
<a href="{% url item_close item.id %}"><img src="/static/images/icons/user-online.png" title="{% trans 'Click to close item' %}"></a>
|
||||
{% endif %}
|
||||
{% if item.children.exists %}
|
||||
<a href="{% url item_activate_summary item.id %}"><img src="/static/images/icons/view-list-tree.png" title="{% trans 'Select item overview' %}"></a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% if perms.agenda.can_manage_agenda %}
|
||||
<td class="tabledrag-hide">
|
||||
{% with form=item.weight_form %}
|
||||
{{ form.weight }}
|
||||
{{ form.self }}
|
||||
{{ form.parent }}
|
||||
{% endwith %}
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
</form>
|
||||
{% else %}
|
||||
<i>{% trans "No items available." %}</i>
|
||||
{% endif %}
|
||||
{% endblock %}
|
33
openslides/agenda/templates/beamer.html
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
||||
{% load tags %}
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="/static/styles/beamer.css">
|
||||
<script type="text/javascript" src="/static/javascript/jquery.js"></script>
|
||||
<script type="text/javascript" src="/static/javascript/beamer.js"></script>
|
||||
<title>{% block title %} {% get_config 'event_name' %} {% endblock %}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="config" style="display:none;">
|
||||
<div id="ajax">{{ ajax }}</div>
|
||||
</div>
|
||||
<div id="ajaxswitcher"></div>
|
||||
|
||||
<div id="header">
|
||||
<div id="logo"><img src="/static/images/logo-beamer.png"></div>
|
||||
<div class="event_name">{% get_config 'event_name' %}</div>
|
||||
<div class="event_description">{% get_config 'event_description' %}</div>
|
||||
</div>
|
||||
|
||||
<div id="currentTime">
|
||||
{% now "H:i" %}
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
47
openslides/agenda/templates/beamer/ItemApplication.html
Normal file
@ -0,0 +1,47 @@
|
||||
{% extends "beamer.html" %}
|
||||
{% load tags %}
|
||||
{% block title %}{{ block.super }} - {{ item.title }}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="number">{% trans "Application" %} #{{ item.application.number }}</div>
|
||||
<h1>{{ item.title }}</h1>
|
||||
|
||||
{% if item.application.status != "pub" and item.application.status != "per" %}
|
||||
<div id="sidebar">
|
||||
<div class="box">
|
||||
<h4>{%trans "Status" %}:</h4>
|
||||
{%trans item.application.get_status_display %}
|
||||
|
||||
{% with item.application.poll_set.all as poll %}
|
||||
{% if poll|length > 0 %}
|
||||
<h4>{% trans "Poll result" %}:</h4>
|
||||
{% for p in poll %}
|
||||
{% if p.has_vote %}
|
||||
{% if poll|length > 1 %}
|
||||
{{forloop.counter}}. {% trans "Poll" %}:<br>
|
||||
{% endif %}
|
||||
{% for option in p.get_options %}
|
||||
{% trans "Yes" %}: {{ option.voteyes }} <br>
|
||||
{% trans "No" %}: {{ option.voteno }} <br>
|
||||
{% trans "Abstention" %}: {{ option.voteundesided }}
|
||||
{% endfor %}
|
||||
<br>
|
||||
{% else %}
|
||||
{% if poll|length == 1 %}
|
||||
<i>{% trans "No poll results available." %}</i>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<p>
|
||||
<div class="text">{{ item.application.text|linebreaks }}</div>
|
||||
<br>
|
||||
<div class="reason"><p><b>{% trans "Reason" %}:</b></p>
|
||||
{{ item.application.reason|linebreaks }}</div>
|
||||
</p>
|
||||
{% endblock %}
|
69
openslides/agenda/templates/beamer/ItemAssignment.html
Normal file
@ -0,0 +1,69 @@
|
||||
{% extends "beamer.html" %}
|
||||
{% block title %}{{ block.super }} - {{ item.title }}{% endblock %}
|
||||
{% block content %}
|
||||
{% trans "Election" %}:
|
||||
<h1>{{ item.assignment }}</h1>
|
||||
|
||||
{% if item.assignment.status == "sea" or item.assignment.status == "vot" %}
|
||||
<div id="sidebar">
|
||||
<div class="box">
|
||||
<h4>{% trans "Status" %}:</h4>
|
||||
{% trans item.assignment.get_status_display %}
|
||||
<h4>{% trans "Number of available posts" %}:</h4>
|
||||
{{ item.assignment.assignment_number }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<p>
|
||||
<div class="text">{{ item.assignment.description|linebreaks }}</div>
|
||||
</p>
|
||||
|
||||
{% if item.assignment.status != "fin" %}
|
||||
<h3>{% trans "Candidates" %}</h3>
|
||||
<ol>
|
||||
{% for profile in item.assignment.profile.all|dictsort:"user.first_name" %}
|
||||
<li>{{ profile }} </li>
|
||||
{% empty %}
|
||||
<li style="list-style: none outside none;">
|
||||
<i>{% trans "No candidates available." %}</i>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
{% endif %}
|
||||
|
||||
{% if item.assignment.poll_set.all.count > 0 %}
|
||||
<p><br></p>
|
||||
<h3>{% trans "Election results" %}</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<th>{% trans "Candidates" %}</th>
|
||||
{% for poll in item.assignment.poll_set.all %}
|
||||
<th><nobr>{{forloop.counter}}. {% trans "ballot" %}</nobr></th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% for vote in votes %}
|
||||
<tr class="{% cycle 'odd' '' %}">
|
||||
{% for v in vote %}
|
||||
<td>{% if v %}
|
||||
{% if v|length == 3 %}
|
||||
<img src="/static/images/icons/voting-yes.png" title="{% trans 'Yes' %}"> {% if v.0 %}{{ v.0 }}{% else %}∅{% endif %}<br>
|
||||
<img src="/static/images/icons/voting-no.png" title="{% trans 'No' %}"> {% if v.1 %}{{ v.1 }}{% else %}∅{% endif %}<br>
|
||||
<img src="/static/images/icons/voting-abstention.png" title="{% trans 'Abstention' %}"> {% if v.2 %}{{ v.2 }}{% else %}∅{% endif %}<br>
|
||||
{% else %}
|
||||
{{ v }}
|
||||
{% endif %}
|
||||
{% else %}∅{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td {% if item.assignment.profile.exist %}colspan="2"{% endif %}><i>{% trans "No ballots available." %}</i></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
<br>
|
||||
{% endblock %}
|
19
openslides/agenda/templates/beamer/ItemPoll.html
Normal file
@ -0,0 +1,19 @@
|
||||
{% extends "beamer.html" %}
|
||||
{% block title %}{{ block.super }} - {{ item.title }}{% endblock %}
|
||||
{% block content %}
|
||||
{%trans "Poll about" %}:
|
||||
<h1>{{ item.title }}</h1>
|
||||
|
||||
<table>
|
||||
{% for option in item.poll.get_options %}
|
||||
<tr>
|
||||
<td>{{ option }}</td>
|
||||
<td>{{ option.voteyes }}</td>
|
||||
{% if item.poll.optiondecision %}
|
||||
<td>{{ option.voteno }}</td>
|
||||
<td>{{ option.voteundesided }}</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
11
openslides/agenda/templates/beamer/ItemText.html
Normal file
@ -0,0 +1,11 @@
|
||||
{% extends "beamer.html" %}
|
||||
{% block title %}{{ block.super }} - {{ item.title }}{% endblock %}
|
||||
{% block content %}
|
||||
{% if item.text %}
|
||||
<h1>{{ item.title }}</h1>
|
||||
{# item.parse|safe #}
|
||||
{{ item.text|linebreaks }}
|
||||
{% else %}
|
||||
<div class="item_fullscreen">{{ item.title }}</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
17
openslides/agenda/templates/beamer/overview.html
Normal file
@ -0,0 +1,17 @@
|
||||
{% extends "beamer.html" %}
|
||||
{% block title %}{{ block.super }} -
|
||||
{% if title %} {{ title }} {% else %} {%trans "Agenda" %} {% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if title %}
|
||||
<h1>{{ title }}</h1>
|
||||
{% else %}
|
||||
<h1>{%trans "Agenda" %}</h1>
|
||||
{% endif %}
|
||||
<ul class="itemlist">
|
||||
{% for item in items %}
|
||||
<li{% if item.closed %} class="closed"{% endif %}>{{ item }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
197
openslides/agenda/tests.py
Normal file
@ -0,0 +1,197 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.agenda.test
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Unit test for the agenda app.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test.client import Client
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from openslides.agenda.models import Item, ItemText
|
||||
from openslides.agenda.api import get_active_item, is_summary, children_list
|
||||
|
||||
class ItemTest(TestCase):
|
||||
def setUp(self):
|
||||
self.item1 = ItemText.objects.create(title='item1')
|
||||
self.item2 = ItemText.objects.create(title='item2')
|
||||
self.item3 = ItemText.objects.create(title='item1A', parent=self.item1)
|
||||
self.item4 = ItemText.objects.create(title='item1Aa', parent=self.item3)
|
||||
|
||||
def testActive(self):
|
||||
with self.assertRaises(Item.DoesNotExist):
|
||||
get_active_item()
|
||||
self.assertTrue(is_summary())
|
||||
self.assertFalse(self.item4.active_parent)
|
||||
|
||||
self.assertFalse(self.item1.active)
|
||||
|
||||
self.item1.set_active()
|
||||
self.assertTrue(self.item1.active)
|
||||
self.assertTrue(self.item4.active_parent)
|
||||
|
||||
self.assertEqual(get_active_item().cast(), self.item1)
|
||||
self.assertNotEqual(get_active_item().cast(), self.item2)
|
||||
|
||||
self.assertFalse(is_summary())
|
||||
|
||||
self.item2.set_active(summary=True)
|
||||
self.assertFalse(self.item1.active)
|
||||
self.assertTrue(is_summary())
|
||||
|
||||
def testClosed(self):
|
||||
self.assertFalse(self.item1.closed)
|
||||
|
||||
self.item1.set_closed()
|
||||
self.assertTrue(self.item1.closed)
|
||||
|
||||
self.item1.set_closed(closed=False)
|
||||
self.assertFalse(self.item1.closed)
|
||||
|
||||
def testParents(self):
|
||||
self.assertEqual(self.item1.parents, [])
|
||||
self.assertTrue(self.item1 in self.item3.parents)
|
||||
self.assertTrue(self.item1 in self.item4.parents)
|
||||
self.assertFalse(self.item2 in self.item4.parents)
|
||||
|
||||
def testChildren(self):
|
||||
self.assertEqual(list(self.item2.children), [])
|
||||
self.assertTrue(self.item3 in [item.cast() for item in self.item1.children])
|
||||
self.assertFalse(self.item4 in [item.cast() for item in self.item1.children])
|
||||
|
||||
l = children_list([self.item1, self.item2])
|
||||
self.assertEqual(str(l), "[<ItemText: item1>, <Item: item1A>, <Item: item1Aa>, <ItemText: item2>]")
|
||||
|
||||
def testForms(self):
|
||||
for item in Item.objects.all():
|
||||
initial = item.weight_form.initial
|
||||
self.assertEqual(initial['self'], item.id)
|
||||
if item.parent:
|
||||
self.assertEqual(initial['parent'], item.parent.id)
|
||||
else:
|
||||
self.assertEqual(initial['parent'], 0)
|
||||
self.assertEqual(initial['weight'], item.weight)
|
||||
|
||||
item.edit_form()
|
||||
|
||||
def testtype(self):
|
||||
self.assertEqual(self.item1.type, 'ItemText')
|
||||
|
||||
|
||||
class ViewTest(TestCase):
|
||||
def setUp(self):
|
||||
self.item1 = ItemText.objects.create(title='item1')
|
||||
self.item2 = ItemText.objects.create(title='item2')
|
||||
self.refreshItems()
|
||||
|
||||
self.admin = User.objects.create_user('testadmin', '', 'default')
|
||||
self.anonym = User.objects.create_user('testanoym', '', 'default')
|
||||
|
||||
self.admin.is_superuser = True
|
||||
self.admin.save()
|
||||
|
||||
def refreshItems(self):
|
||||
self.item1 = Item.objects.get(pk=self.item1.id)
|
||||
self.item2 = Item.objects.get(pk=self.item2.id)
|
||||
|
||||
@property
|
||||
def adminClient(self):
|
||||
c = Client()
|
||||
c.login(username='testadmin', password='default')
|
||||
return c
|
||||
|
||||
@property
|
||||
def anonymClient(self):
|
||||
return Client()
|
||||
|
||||
def testBeamer(self):
|
||||
c = self.anonymClient
|
||||
response = c.get('/beamer/')
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
c = self.adminClient
|
||||
response = c.get('/beamer/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = c.get('/item/%d/' % self.item1.id)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.context['item'], self.item1.cast())
|
||||
self.assertEqual(response.templates[0].name, 'beamer/ItemText.html')
|
||||
|
||||
def testActivate(self):
|
||||
c = self.adminClient
|
||||
|
||||
response = c.get('/item/%d/activate/' % self.item1.id)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertTrue(self.item1.active)
|
||||
self.assertFalse(self.item2.active)
|
||||
self.assertFalse(is_summary())
|
||||
|
||||
response = c.get('/item/%d/activate/summary/' % self.item2.id)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertTrue(self.item2.active)
|
||||
self.assertFalse(self.item1.active)
|
||||
self.assertTrue(is_summary())
|
||||
|
||||
response = c.get('/item/%d/activate/' % 0)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertFalse(self.item2.active)
|
||||
self.assertFalse(self.item1.active)
|
||||
with self.assertRaises(Item.DoesNotExist):
|
||||
get_active_item()
|
||||
|
||||
response = c.get('/item/%d/activate/' % 10000)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertFalse(self.item2.active)
|
||||
self.assertFalse(self.item1.active)
|
||||
|
||||
def testClose(self):
|
||||
c = self.adminClient
|
||||
|
||||
response = c.get('/item/%d/close/' % self.item1.id)
|
||||
self.refreshItems()
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertTrue(Item.objects.get(pk=self.item1.id).closed)
|
||||
|
||||
response = c.get('/item/%d/open/' % self.item1.id)
|
||||
self.refreshItems()
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertFalse(self.item1.closed)
|
||||
|
||||
response = c.get('/item/%d/open/' % 1000)
|
||||
self.refreshItems()
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def testEdit(self):
|
||||
c = self.adminClient
|
||||
|
||||
response = c.get('/item/%d/edit/' % self.item1.id)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.context['form'].instance, self.item1.cast())
|
||||
|
||||
response = c.get('/item/%d/edit/' % 1000)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
|
||||
data = {'title': 'newitem1', 'text': 'item1-text', 'weight':'0'}
|
||||
response = c.post('/item/%d/edit/' % self.item1.id, data)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.refreshItems()
|
||||
self.assertEqual(self.item1.cast().title, 'newitem1')
|
||||
self.assertEqual(self.item1.cast().text, 'item1-text')
|
||||
|
||||
data = {'title': '', 'text': 'item1-text', 'weight': '0'}
|
||||
response = c.post('/item/%d/edit/' % self.item1.id, data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.refreshItems()
|
||||
self.assertEqual(self.item1.cast().title, 'newitem1')
|
||||
|
||||
def testNew(self):
|
||||
pass
|
||||
|
62
openslides/agenda/urls.py
Normal file
@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.agenda.urls
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
URL list for the agenda app.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
urlpatterns = patterns('agenda.views',
|
||||
url(r'^beamer/$', 'beamer',
|
||||
name='item_beamer'),
|
||||
|
||||
url(r'^$', 'overview'),
|
||||
|
||||
url(r'^item/$', 'overview',
|
||||
name='item_overview'),
|
||||
|
||||
url(r'^item/(?P<item_id>\d+)/$', 'view',
|
||||
name='item_view'),
|
||||
|
||||
url(r'^item/(?P<item_id>\d+)/activate/$', 'set_active',
|
||||
name='item_activate'),
|
||||
|
||||
url(r'^item/(?P<item_id>\d+)/activate/summary/$', 'set_active',
|
||||
{'summary': True},\
|
||||
name='item_activate_summary'),
|
||||
|
||||
url(r'^item/(?P<item_id>\d+)/close/$', 'set_closed', {'closed': True},
|
||||
name='item_close'),
|
||||
|
||||
url(r'^item/(?P<item_id>\d+)/open/$', 'set_closed', {'closed': False},
|
||||
name='item_open'),
|
||||
|
||||
url(r'^item/(?P<item_id>\d+)/edit/$', 'edit',
|
||||
name='item_edit'),
|
||||
|
||||
url(r'^item/new/$', 'edit',
|
||||
name='item_new_default'),
|
||||
|
||||
url(r'^item/new/(?P<form>ItemText|ItemApplication|ItemPoll|'
|
||||
r'ItemAssignment)/$', 'edit',
|
||||
name='item_new'),
|
||||
|
||||
url(r'^item/new/(?P<form>ItemText|ItemApplication|ItemPoll|'
|
||||
r'ItemAssignment)/(?P<default>\d+)/$', 'edit',
|
||||
name='item_new_default'),
|
||||
|
||||
url(r'^item/(?P<item_id>\d+)/del/$', 'delete',
|
||||
name='item_delete'),
|
||||
|
||||
url(r'^item/print/(?P<printAllItems>\d+)/$', 'print_agenda',
|
||||
name='print_agenda_full'),
|
||||
|
||||
url(r'^item/print/$', 'print_agenda',
|
||||
name='print_agenda'),
|
||||
)
|
255
openslides/agenda/views.py
Normal file
@ -0,0 +1,255 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.agenda.views
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Views for the agenda app.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render_to_response, redirect
|
||||
from django.template import RequestContext
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from openslides.agenda.models import Item
|
||||
from openslides.agenda.api import get_active_item, is_summary, children_list
|
||||
|
||||
from openslides.agenda.forms import ElementOrderForm, MODELFORM
|
||||
from openslides.application.models import Application
|
||||
from openslides.assignment.models import Assignment
|
||||
from openslides.poll.models import Poll
|
||||
from openslides.system.api import config_set, config_get
|
||||
from openslides.utils.template import render_block_to_string
|
||||
from openslides.utils.utils import template, permission_required, \
|
||||
del_confirm_form
|
||||
from openslides.utils.pdf import print_agenda
|
||||
from poll.models import Poll, Option
|
||||
|
||||
def view(request, item_id):
|
||||
"""
|
||||
Shows the Slide.
|
||||
"""
|
||||
item = Item.objects.get(id=item_id)
|
||||
votes = assignment_votes(item)
|
||||
|
||||
return render_to_response('beamer/%s.html' % item.type,
|
||||
{
|
||||
'item': item.cast(),
|
||||
'ajax': 'off',
|
||||
'votes': votes,
|
||||
},
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
|
||||
@permission_required('agenda.can_see_beamer')
|
||||
def beamer(request):
|
||||
"""
|
||||
Shows a active Slide.
|
||||
"""
|
||||
data = {'ajax': 'on'}
|
||||
template = ''
|
||||
try:
|
||||
item = get_active_item()
|
||||
votes = assignment_votes(item)
|
||||
if is_summary():
|
||||
items = item.children.filter(hidden=False)
|
||||
data['items'] = items
|
||||
data['title'] = item.title
|
||||
template = 'beamer/overview.html'
|
||||
else:
|
||||
data['item'] = item.cast()
|
||||
data['title'] = item.title
|
||||
data['votes'] = votes
|
||||
template = 'beamer/%s.html' % (item.type)
|
||||
except Item.DoesNotExist:
|
||||
items = Item.objects.filter(parent=None).filter(hidden=False) \
|
||||
.order_by('weight')
|
||||
data['items'] = items
|
||||
data['title'] = _("Agenda")
|
||||
template = 'beamer/overview.html'
|
||||
|
||||
if request.is_ajax():
|
||||
content = render_block_to_string(template, 'content', data)
|
||||
jsondata = {'content': content,
|
||||
'title': data['title'],
|
||||
'time': datetime.now().strftime('%H:%M')}
|
||||
return HttpResponse(json.dumps(jsondata))
|
||||
else:
|
||||
return render_to_response(template,
|
||||
data,
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
|
||||
def assignment_votes(item):
|
||||
votes = []
|
||||
if item.type == "ItemAssignment":
|
||||
assignment = item.cast().assignment
|
||||
# list of candidates
|
||||
candidates = set()
|
||||
for option in Option.objects.filter(poll__assignment=assignment):
|
||||
candidates.add(option.value)
|
||||
# list of votes
|
||||
votes = []
|
||||
for candidate in candidates:
|
||||
tmplist = []
|
||||
tmplist.append(candidate)
|
||||
for poll in assignment.poll_set.all():
|
||||
if candidate in poll.options_values:
|
||||
option = Option.objects.filter(poll=poll).filter(user=candidate)[0]
|
||||
if poll.optiondecision:
|
||||
tmplist.append([option.yes, option.no, option.undesided])
|
||||
else:
|
||||
tmplist.append(option.yes)
|
||||
else:
|
||||
tmplist.append("-")
|
||||
votes.append(tmplist)
|
||||
|
||||
return votes
|
||||
|
||||
|
||||
@permission_required('agenda.can_view_agenda')
|
||||
@template('agenda/overview.html')
|
||||
def overview(request):
|
||||
"""
|
||||
Shows an overview of all items.
|
||||
"""
|
||||
if request.method == 'POST':
|
||||
for item in Item.objects.all():
|
||||
form = ElementOrderForm(request.POST, prefix="i%d" % item.id)
|
||||
if form.is_valid():
|
||||
try:
|
||||
item.parent = Item.objects.get( \
|
||||
id=form.cleaned_data['parent'])
|
||||
except Item.DoesNotExist:
|
||||
item.parent = None
|
||||
item.weight = form.cleaned_data['weight']
|
||||
item.save()
|
||||
|
||||
items = children_list(Item.objects.filter(parent=None).order_by('weight'))
|
||||
try:
|
||||
overview = is_summary() and not get_active_item()
|
||||
except Item.DoesNotExist:
|
||||
overview = True
|
||||
return {
|
||||
'items': items,
|
||||
'overview': overview,
|
||||
'summary': is_summary()
|
||||
}
|
||||
|
||||
|
||||
@permission_required('agenda.can_manage_agenda')
|
||||
def set_active(request, item_id, summary=False):
|
||||
"""
|
||||
Set an Item as the active one.
|
||||
"""
|
||||
if item_id == "0":
|
||||
config_set("presentation", "0")
|
||||
else:
|
||||
try:
|
||||
item = Item.objects.get(id=item_id)
|
||||
item.set_active(summary)
|
||||
except Item.DoesNotExist:
|
||||
messages.error(request, _('Item ID %d does not exist.') % int(item_id))
|
||||
return redirect(reverse('item_overview'))
|
||||
|
||||
|
||||
@permission_required('agenda.can_manage_agenda')
|
||||
def set_closed(request, item_id, closed=True):
|
||||
"""
|
||||
Close or open an Item.
|
||||
"""
|
||||
try:
|
||||
item = Item.objects.get(id=item_id)
|
||||
item.set_closed(closed)
|
||||
except Item.DoesNotExist:
|
||||
messages.error(request, _('Item ID %d does not exist.') % int(item_id))
|
||||
return redirect(reverse('item_overview'))
|
||||
|
||||
|
||||
@permission_required('agenda.can_manage_agenda')
|
||||
@template('agenda/edit.html')
|
||||
def edit(request, item_id=None, form='ItemText', default=None):
|
||||
"""
|
||||
Show a form to edit an existing Item, or create a new one.
|
||||
"""
|
||||
if item_id is not None:
|
||||
try:
|
||||
item = Item.objects.get(id=item_id).cast()
|
||||
except Item.DoesNotExist:
|
||||
messages.error(request, _('Item ID %d does not exist.') % int(item_id))
|
||||
return redirect(reverse('item_overview'))
|
||||
else:
|
||||
item = None
|
||||
|
||||
if request.method == 'POST':
|
||||
if item_id is None:
|
||||
form = MODELFORM[form](request.POST)
|
||||
else:
|
||||
form = item.edit_form(request.POST)
|
||||
|
||||
if form.is_valid():
|
||||
item = form.save()
|
||||
if item_id is None:
|
||||
messages.success(request, _('New item was successfully created.'))
|
||||
if "application" in request.POST:
|
||||
item.application.writelog(_('Agenda item created'), request.user)
|
||||
else:
|
||||
messages.success(request, _('Item was successfully modified.'))
|
||||
if "application" in request.POST:
|
||||
item.application.writelog(_('Agenda item modified'), request.user)
|
||||
return redirect(reverse('item_overview'))
|
||||
messages.error(request, _('Please check the form for errors.'))
|
||||
else:
|
||||
initial = {}
|
||||
if default:
|
||||
if form == "ItemAssignment":
|
||||
assignment = Assignment.objects.get(pk=default)
|
||||
initial = {
|
||||
'assignment': assignment,
|
||||
'title': assignment.name,
|
||||
}
|
||||
elif form == "ItemApplication":
|
||||
application = Application.objects.get(pk=default)
|
||||
initial = {
|
||||
'application': application,
|
||||
'title': application.title,
|
||||
}
|
||||
elif form == "ItemPoll":
|
||||
poll = Poll.objects.get(pk=default)
|
||||
initial = {
|
||||
'poll': poll,
|
||||
'title': poll.title,
|
||||
}
|
||||
|
||||
if item_id is None:
|
||||
form = MODELFORM[form](initial=initial)
|
||||
else:
|
||||
form = item.edit_form()
|
||||
return { 'form': form,
|
||||
'item': item }
|
||||
|
||||
|
||||
@permission_required('agenda.can_manage_agenda')
|
||||
def delete(request, item_id):
|
||||
"""
|
||||
Delete an Item.
|
||||
"""
|
||||
item = Item.objects.get(id=item_id).cast()
|
||||
if request.method == 'POST':
|
||||
item.delete()
|
||||
messages.success(request, _("Item <b>%s</b> was successfully deleted.") % item)
|
||||
else:
|
||||
del_confirm_form(request, item)
|
||||
return redirect(reverse('item_overview'))
|
0
openslides/application/__init__.py
Normal file
17
openslides/application/admin.py
Normal file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.application.admin
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Register app for admin site.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.contrib import admin
|
||||
from openslides.application.models import Application, AVersion
|
||||
|
||||
admin.site.register(Application)
|
||||
admin.site.register(AVersion)
|
34
openslides/application/forms.py
Normal file
@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.application.forms
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Forms for the application app.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.forms import ModelForm, Form, CharField, Textarea, TextInput
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from openslides.application.models import Application
|
||||
|
||||
|
||||
class ApplicationForm(Form):
|
||||
error_css_class = 'error'
|
||||
required_css_class = 'required'
|
||||
|
||||
title = CharField(widget=TextInput(), label=_("Title"))
|
||||
text = CharField(widget=Textarea(), label=_("Text"))
|
||||
reason = CharField(widget=Textarea(), required=False, label=_("Reason"))
|
||||
|
||||
|
||||
class ApplicationManagerForm(ModelForm):
|
||||
error_css_class = 'error'
|
||||
required_css_class = 'required'
|
||||
|
||||
class Meta:
|
||||
model = Application
|
||||
exclude = ('number', 'status', 'permitted', 'log')
|
408
openslides/application/models.py
Normal file
@ -0,0 +1,408 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.application.models
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Models for the application app.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Max
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from openslides.system.api import config_get
|
||||
|
||||
|
||||
class Application(models.Model):
|
||||
STATUS = (
|
||||
('pub', _('Published')),
|
||||
('per', _('Permitted')),
|
||||
('acc', _('Accepted')),
|
||||
('rej', _('Rejected')),
|
||||
('wit', _('Withdrawed')),
|
||||
('adj', _('Adjourned')),
|
||||
('noc', _('Not Concerned')),
|
||||
('com', _('Commited a bill')),
|
||||
('nop', _('Rejected (not permitted)')),
|
||||
#additional actions:
|
||||
# edit
|
||||
# delete
|
||||
# setnumber
|
||||
# support
|
||||
# unsupport
|
||||
# createitem
|
||||
# genpoll
|
||||
)
|
||||
|
||||
submitter = models.ForeignKey(User, verbose_name=_("Submitter"))
|
||||
supporter = models.ManyToManyField(User, related_name='supporter', \
|
||||
null=True, blank=True, verbose_name=_("Supporters"))
|
||||
number = models.PositiveSmallIntegerField(blank=True, null=True,
|
||||
unique=True)
|
||||
status = models.CharField(max_length=3, choices=STATUS, default='pub')
|
||||
permitted = models.ForeignKey('AVersion', related_name='permitted', \
|
||||
null=True, blank=True)
|
||||
log = models.TextField(blank=True, null=True)
|
||||
|
||||
@property
|
||||
def last_version(self):
|
||||
"""
|
||||
Return last version of the application.
|
||||
"""
|
||||
try:
|
||||
return AVersion.objects.filter(application=self).order_by('id') \
|
||||
.reverse()[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
@property
|
||||
def versions(self):
|
||||
"""
|
||||
Return a list of all versions of the application.
|
||||
"""
|
||||
return AVersion.objects.filter(application=self)
|
||||
|
||||
@property
|
||||
def creation_time(self):
|
||||
"""
|
||||
Return the time of the creation of the application.
|
||||
"""
|
||||
try:
|
||||
return self.versions[0].time
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
@property
|
||||
def notes(self):
|
||||
"""
|
||||
Return some information of the application.
|
||||
"""
|
||||
note = []
|
||||
if self.status == "pub" and not self.enough_supporters:
|
||||
note.append(_("Searching for supporters."))
|
||||
if self.status == "pub" and self.permitted is None:
|
||||
note.append(_("Not yet permitted."))
|
||||
elif self.unpermitted_changes and self.permitted:
|
||||
note.append(_("Not yet permitted changes."))
|
||||
return note
|
||||
|
||||
@property
|
||||
def unpermitted_changes(self):
|
||||
"""
|
||||
Return True if the application has unpermitted changes.
|
||||
|
||||
The application has unpermitted changes, if the permitted-version
|
||||
is not the lastone and the lastone is not abjected.
|
||||
TODO: rename the property in unchecked__changes
|
||||
"""
|
||||
if (self.last_version != self.permitted
|
||||
and not self.last_version.abjected):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
def enough_supporters(self):
|
||||
"""
|
||||
Return True, if the application has enough supporters
|
||||
"""
|
||||
min_supporters = int(config_get('application_min_supporters'))
|
||||
return self.supporter.count() >= min_supporters
|
||||
|
||||
def save(self, user=None):
|
||||
"""
|
||||
Save the Application, and create a new AVersion if necessary
|
||||
"""
|
||||
super(Application, self).save()
|
||||
last_version = self.last_version
|
||||
if last_version is not None:
|
||||
if (last_version.text == self.text
|
||||
and last_version.title == self.title
|
||||
and last_version.reason == self.reason):
|
||||
return # No changes
|
||||
try:
|
||||
if self.title != "":
|
||||
version = AVersion(title=getattr(self, 'title', ''),
|
||||
text=getattr(self, 'text', ''),
|
||||
reason=getattr(self, 'reason', ''),
|
||||
application=self)
|
||||
version.save()
|
||||
self.writelog(_("Version %s created") % version.aid, user)
|
||||
is_manager = user.has_perm('application.can_manage_application')
|
||||
except AttributeError:
|
||||
is_manager = False
|
||||
|
||||
if (self.status == "pub"
|
||||
and self.supporter.exists()
|
||||
and not is_manager):
|
||||
self.supporter.clear()
|
||||
self.writelog(_("Supporters removed"), user)
|
||||
|
||||
def reset(self, user):
|
||||
"""
|
||||
Reset the application.
|
||||
"""
|
||||
self.status = "pub"
|
||||
self.permitted = None
|
||||
self.save()
|
||||
self.writelog(_("Status reseted to: %s") % (self.get_status_display()), user)
|
||||
|
||||
def support(self, user):
|
||||
"""
|
||||
Add a Supporter to the list of supporters of the application.
|
||||
"""
|
||||
if user == self.submitter:
|
||||
raise NameError('Supporter can not be the submitter of a ' \
|
||||
'application.')
|
||||
if self.permitted is not None:
|
||||
raise NameError('This application is already permitted.')
|
||||
if user not in self.supporter.all():
|
||||
self.supporter.add(user)
|
||||
self.writelog(_("Supporter: +%s") % (user))
|
||||
|
||||
def unsupport(self, user):
|
||||
"""
|
||||
remove a supporter from the list of supporters of the application
|
||||
"""
|
||||
if self.permitted is not None:
|
||||
raise NameError('This application is already permitted.')
|
||||
if user in self.supporter.all():
|
||||
self.supporter.remove(user)
|
||||
self.writelog(_("Supporter: -%s") % (user))
|
||||
|
||||
def set_number(self, number=None, user=None):
|
||||
"""
|
||||
Set a number for ths application.
|
||||
"""
|
||||
if self.number is not None:
|
||||
raise NameError('This application has already a number.')
|
||||
if number is None:
|
||||
try:
|
||||
number = Application.objects.aggregate(Max('number')) \
|
||||
['number__max'] + 1
|
||||
except TypeError:
|
||||
number = 1
|
||||
self.number = number
|
||||
self.save()
|
||||
self.writelog(_("Number set: %s") % (self.number), user)
|
||||
return self.number
|
||||
|
||||
def permit(self, user=None):
|
||||
"""
|
||||
Change the status of this application to permit.
|
||||
"""
|
||||
self.set_status(user, "per")
|
||||
aversion = self.last_version
|
||||
if self.number is None:
|
||||
self.set_number()
|
||||
self.permitted = aversion
|
||||
self.save()
|
||||
self.writelog(_("Version %s permitted") % (aversion.aid), user)
|
||||
return self.permitted
|
||||
|
||||
def notpermit(self, user=None):
|
||||
"""
|
||||
Change the status of this application to 'not permitted (rejected)'.
|
||||
"""
|
||||
self.set_status(user, "nop")
|
||||
#TODO: reject last version
|
||||
aversion = self.last_version
|
||||
#self.permitted = aversion
|
||||
if self.number is None:
|
||||
self.set_number()
|
||||
self.save()
|
||||
self.writelog(_("Version %s not permitted") % (self.last_version.aid), user)
|
||||
|
||||
def set_status(self, user, status, force=False):
|
||||
"""
|
||||
Set the status of the application.
|
||||
"""
|
||||
error = True
|
||||
for a, b in Application.STATUS:
|
||||
if status == a:
|
||||
error = False
|
||||
break
|
||||
if error:
|
||||
raise NameError('%s is not a valid status.' % status)
|
||||
if self.status == status:
|
||||
raise NameError('The application status is already %s.' \
|
||||
% self.status)
|
||||
|
||||
actions = []
|
||||
actions = self.get_allowed_actions(user)
|
||||
if status not in actions and not force:
|
||||
raise NameError('The application status is: %s. You can not set' \
|
||||
' the status to %s.' % (self.status, status))
|
||||
|
||||
oldstatus = self.get_status_display()
|
||||
self.status = status
|
||||
self.save()
|
||||
self.writelog(_("Status modified")+": %s -> %s" \
|
||||
% (oldstatus, self.get_status_display()), user)
|
||||
|
||||
def get_allowed_actions(self, user=None):
|
||||
"""
|
||||
Return a list of all the allowed status.
|
||||
"""
|
||||
actions = []
|
||||
|
||||
# check if user allowed to withdraw an application
|
||||
if (self.status == "pub"
|
||||
and self.number \
|
||||
and user == self.submitter) \
|
||||
or (self.status == "pub" \
|
||||
and self.number \
|
||||
and user.has_perm("application.can_manage_application")) \
|
||||
or (self.status == "per" \
|
||||
and user == self.submitter) \
|
||||
or (self.status == "per" \
|
||||
and user.has_perm("application.can_manage_application")):
|
||||
actions.append("wit")
|
||||
|
||||
# Check if the user can support and unspoort the application
|
||||
if self.status == "pub" \
|
||||
and user != self.submitter \
|
||||
and user not in self.supporter.all():
|
||||
actions.append("support")
|
||||
|
||||
if self.status == "pub" and user in self.supporter.all():
|
||||
actions.append("unsupport")
|
||||
|
||||
#Check if the user can edit the application
|
||||
if user == self.submitter \
|
||||
or user.has_perm("application.can_manage_application"):
|
||||
actions.append("edit")
|
||||
|
||||
#Check if the user can delete the application
|
||||
if self.number is None \
|
||||
and self.status == "pub" \
|
||||
and (self.submitter == user \
|
||||
or user.has_perm("application.can_manage_application")):
|
||||
actions.append("delete")
|
||||
|
||||
#For the rest, all actions need the manage permission
|
||||
if not user.has_perm("application.can_manage_application"):
|
||||
return actions
|
||||
|
||||
if self.status == "pub":
|
||||
actions.append("nop")
|
||||
actions.append("per")
|
||||
if self.number == None:
|
||||
actions.append("setnumber")
|
||||
|
||||
if self.status == "per":
|
||||
actions.append("acc")
|
||||
actions.append("rej")
|
||||
actions.append("adj")
|
||||
actions.append("noc")
|
||||
actions.append("com")
|
||||
actions.append("genpoll")
|
||||
if self.unpermitted_changes:
|
||||
actions.append("permitversion")
|
||||
actions.append("rejectversion")
|
||||
|
||||
if self.number and not self.itemapplication_set.all():
|
||||
actions.append("createitem")
|
||||
|
||||
return actions
|
||||
|
||||
def delete(self, force=False):
|
||||
"""
|
||||
Delete the application. It is not possible, if the application has
|
||||
allready a number
|
||||
"""
|
||||
if self.number and not force:
|
||||
raise NameError('The application has already a number. ' \
|
||||
'You can not delete it.')
|
||||
super(Application, self).delete()
|
||||
|
||||
def writelog(self, text, user=None):
|
||||
if not self.log:
|
||||
self.log = ""
|
||||
self.log += u"%s | %s" % (datetime.now().strftime("%d.%m.%Y %H:%M:%S"), text)
|
||||
if user is not None:
|
||||
self.log += u" (by %s)" % (user.username)
|
||||
self.log += "\n"
|
||||
self.save()
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""
|
||||
if name is title, text, reason or time,
|
||||
Return this attribute from the newest version of the application
|
||||
"""
|
||||
if name in ('title', 'text', 'reason', 'time'):
|
||||
try:
|
||||
return self.last_version.__dict__[name]
|
||||
except TypeError:
|
||||
raise AttributeError(name)
|
||||
except AttributeError:
|
||||
raise AttributeError(name)
|
||||
raise AttributeError(name)
|
||||
|
||||
def gen_poll(self, user=None, pollcount=None):
|
||||
"""
|
||||
Generates a poll object for the application
|
||||
"""
|
||||
from poll.models import Poll
|
||||
if pollcount > 1:
|
||||
description = _("%s. poll") % pollcount
|
||||
else:
|
||||
description = _("Poll")
|
||||
poll = Poll(title=_("Vote on application #%s") % self.number, \
|
||||
optiondecision=True, \
|
||||
application=self,
|
||||
description=description)
|
||||
poll.save()
|
||||
poll.add_option(self)
|
||||
self.writelog(_("Poll created"), user)
|
||||
return poll
|
||||
|
||||
@models.permalink
|
||||
def get_absolute_url(self, link='view'):
|
||||
if link == 'view':
|
||||
return ('application_view', [str(self.id)])
|
||||
if link == 'delete':
|
||||
return ('application_delete', [str(self.id)])
|
||||
|
||||
def __unicode__(self):
|
||||
try:
|
||||
return self.last_version.title
|
||||
except AttributeError:
|
||||
return "no title jet"
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
('can_view_application', _("Can see applications")),
|
||||
('can_insert_application', _("Can insert new applications")),
|
||||
('can_support_application', _("Can support applications")),
|
||||
('can_manage_application', _("Can manage applications")),
|
||||
)
|
||||
|
||||
|
||||
class AVersion(models.Model):
|
||||
title = models.CharField(max_length=100)
|
||||
text = models.TextField()
|
||||
reason = models.TextField(null=True, blank=True)
|
||||
abjected = models.BooleanField()
|
||||
time = models.DateTimeField(auto_now=True)
|
||||
application = models.ForeignKey(Application)
|
||||
|
||||
def __unicode__(self):
|
||||
return "%s %s" % (self.id, self.title)
|
||||
|
||||
@property
|
||||
def aid(self):
|
||||
try:
|
||||
return self._aid
|
||||
except AttributeError:
|
||||
self._aid = AVersion.objects \
|
||||
.filter(application=self.application) \
|
||||
.filter(id__lte=self.id).count()
|
||||
return self._aid
|
@ -0,0 +1,14 @@
|
||||
{% extends "base.html" %}
|
||||
{% load tags %}
|
||||
|
||||
{% block submenu %}
|
||||
{% url application_overview as url_applicationoverview %}
|
||||
<h4>{%trans "Applications" %}</h4>
|
||||
<ul>
|
||||
<li class="{% if request.path == url_applicationoverview %}selected{% endif %}"><a href="{% url application_overview %}">{%trans "All applications" %}</a></li>
|
||||
{% if perms.application.can_insert_application or perms.application.can_manage_application %}
|
||||
<li class="{% active request '/application/new' %}"><a href="{% url application_new %}">{%trans "New application" %}</a></li>
|
||||
{% endif %}
|
||||
<li><a href="{% url print_applications %}"><img src="/static/images/icons/application-pdf.png"> {%trans 'Print all applications' %}</a></li>
|
||||
</ul>
|
||||
{% endblock %}
|
27
openslides/application/templates/application/edit.html
Normal file
@ -0,0 +1,27 @@
|
||||
{% extends "application/base_application.html" %}
|
||||
{% block title %}{{ block.super }} - {%trans "Application" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if application %}
|
||||
<h1>{%trans "Edit application" %}</h1>
|
||||
{% else %}
|
||||
<h1>{%trans "New application" %}</h1>
|
||||
{% endif %}
|
||||
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
{{ managerform.as_p }}
|
||||
<small>* {%trans "required" %}</small>
|
||||
<p>
|
||||
<button type="submit">
|
||||
<span class="icon ok">{%trans 'Save' %}</span>
|
||||
</button>
|
||||
<a href='{% url application_overview %}'>
|
||||
<button type="button">
|
||||
<span class="icon cancel">{%trans 'Cancel' %}</span>
|
||||
</button>
|
||||
</a>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
62
openslides/application/templates/application/overview.html
Normal file
@ -0,0 +1,62 @@
|
||||
{% extends "application/base_application.html" %}
|
||||
{% block title %}{{ block.super }} - {%trans "Applications" %}{% endblock %}
|
||||
{% load tags %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{%trans "Applications" %}</h1>
|
||||
<p><form action="{{request.url}}" name="filter" method="get">
|
||||
{%trans "Filter" %}:
|
||||
{% if min_supporters > 0 %}
|
||||
<input type="checkbox" name="needsup" onchange="document.forms['filter'].submit()"
|
||||
{% if 'on' in request.GET.needsup %}checked{% endif %}> {%trans "Need supporters" %}
|
||||
{% endif %}
|
||||
<input type="checkbox" name="number" onchange="document.forms['filter'].submit()"
|
||||
{% if 'on' in request.GET.number %}checked{% endif %}> {%trans "Without number" %}
|
||||
<input type="checkbox" name="status" onchange="document.forms['filter'].submit()"
|
||||
{% if 'on' in request.GET.status %}checked{% endif %}> {%trans "Status" %}:
|
||||
<select class="default-input" name="statusvalue" onchange="{% if 'on' in request.GET.status %}document.forms['filter'].submit(){% endif %}">
|
||||
<option value="pub" {% if 'pub' in request.GET.statusvalue %}selected{% endif %}>{%trans "Not yet permitted" %}</option>
|
||||
<option value="per" {% if 'on' in request.GET.status and 'per' in request.GET.statusvalue %}selected{% endif %}>{%trans "Permitted" %}</option>
|
||||
<option value="acc" {% if 'on' in request.GET.status and 'acc' in request.GET.statusvalue %}selected{% endif %}>{%trans "Accepted" %}</option>
|
||||
<option value="rej" {% if 'on' in request.GET.status and 'rej' in request.GET.statusvalue %}selected{% endif %}>{%trans "Rejected" %}</option>
|
||||
<option value="wit" {% if 'on' in request.GET.status and 'wit' in request.GET.statusvalue %}selected{% endif %}>{%trans "Withdrawed (by submitter)" %}</option>
|
||||
</select>
|
||||
</form>
|
||||
</p>
|
||||
<br>
|
||||
<table>
|
||||
<tr>
|
||||
<th><a href="?sort=number{% if 'number' in request.GET.sort and 'reverse' not in request.GET %}&reverse{%endif%}">{%trans "Number" %}</a></th>
|
||||
<th><a href="?sort=aversion__title{% if 'aversion__title' in request.GET.sort and 'reverse' not in request.GET %}&reverse{%endif%}">{%trans "Application title" %}</a></th>
|
||||
{% if min_supporters > 0 %}
|
||||
<th><a href="?sort=supporter{% if 'supporter' in request.GET.sort and 'reverse' not in request.GET %}&reverse{%endif%}">{%trans "Number of supporters" %}*</a></th>
|
||||
{% endif %}
|
||||
<th><a href="?sort=status{% if 'status' in request.GET.sort and 'reverse' not in request.GET %}&reverse{%endif%}">{%trans "Status" %}</a></th>
|
||||
<th><a href="?sort=submitter{% if 'submitter' in request.GET.sort and 'reverse' not in request.GET %}&reverse{%endif%}">{%trans "Submitter" %}</a></th>
|
||||
<th><a href="?sort=aversion__time{% if 'aversion__time' in request.GET.sort and 'reverse' not in request.GET %}&reverse{%endif%}">{%trans "Creation Time" %}<a></th>
|
||||
</tr>
|
||||
{% for application in applications %}
|
||||
<tr class="{% cycle '' 'odd' %}">
|
||||
<td>{% if application.number %}{{ application.number }}{% else %}-{% endif %}</td>
|
||||
<td><a href="{% url application_view application.id %}">{{ application }}</a></td>
|
||||
{% if min_supporters > 0 %}
|
||||
<td>{{ application.supporter.count }}</td>
|
||||
{% endif %}
|
||||
<td>{% if application.status != "pub" %}
|
||||
{{ application.get_status_display }}<br>
|
||||
{% endif %}
|
||||
{% for note in application.notes %}
|
||||
{{ note }}
|
||||
{% if not forloop.last %}<br>{%endif%}
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>{{ application.submitter }}</td>
|
||||
<td>{{ application.creation_time }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="6"><i>{%trans "No applications available." %}</i></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
50
openslides/application/templates/application/poll_view.html
Normal file
@ -0,0 +1,50 @@
|
||||
{% extends 'application/base_application.html' %}
|
||||
{% block title %}{{ block.super }} - {{ poll.title }}{% endblock %}
|
||||
|
||||
{% if perms.poll.can_manage_poll %}
|
||||
{% block submenu %}
|
||||
{{ block.super }}
|
||||
<br>
|
||||
<h3>{%trans "Application" %} #{{poll.application.number}}</h3>
|
||||
<ul>
|
||||
<li><a href="{% url print_application_poll poll.id %}"><img src="/static/images/icons/application-pdf.png"> {%trans 'Print Poll' %}</a></li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ poll.title }}</h1>
|
||||
<h4>Title: "{{ poll.application.title }}"</h4>
|
||||
|
||||
{% if perms.poll.can_manage_poll %}
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
<fieldset>
|
||||
<legend>{{ poll.description }}</legend>
|
||||
<p><nobr><label>{%trans "Votes in favour" %}:</label></nobr>
|
||||
{{ options.0.form.yes.errors }}{{ options.0.form.yes }}
|
||||
</p>
|
||||
<p><nobr><label>{%trans "Votes against" %}:</label></nobr>
|
||||
{{ options.0.form.no.errors }}{{ options.0.form.no }}
|
||||
</p>
|
||||
<p><nobr><label>{%trans "Abstentions" %}:</label></nobr>
|
||||
{{ options.0.form.undesided.errors }}{{ options.0.form.undesided }}
|
||||
</p>
|
||||
<p><label>{%trans "Invalid" %}:</label>
|
||||
{{ form.invalid.errors }}{{ form.invalid }}
|
||||
</p>
|
||||
<p>
|
||||
<button type="submit">
|
||||
<span class="icon ok">{%trans 'Apply' %}</span>
|
||||
</button>
|
||||
</fieldset>
|
||||
|
||||
<p></p>
|
||||
<a href='{% url application_view poll.application.id %}'>
|
||||
<button type="button">
|
||||
<span class="icon previous">{%trans 'Back to application' %}</span>
|
||||
</button>
|
||||
</a>
|
||||
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endblock %}
|
276
openslides/application/templates/application/view.html
Normal file
@ -0,0 +1,276 @@
|
||||
{% extends "application/base_application.html" %}
|
||||
{% block title %}{{ block.super }} - {% trans "Application" %} "{{ application.title }}"{% endblock %}
|
||||
{% load tags %}
|
||||
|
||||
{% block content %}
|
||||
<div id="sidebar">
|
||||
|
||||
<div class="box">
|
||||
<h4 style="float:left;">{% trans "Submitter" %}:</h4>
|
||||
<h4 style="text-align: right;">
|
||||
{% if "delete" in actions %}
|
||||
<a href="{% url application_delete application.id %}"><img src="/static/images/icons/edit-delete.png" title="{% trans 'Delete Application' %}"></a>
|
||||
{% endif %}
|
||||
<a href="{% url print_application application.id %}"><img src="/static/images/icons/application-pdf.png" title="{% trans 'Print Application' %}"></a>
|
||||
</h4>
|
||||
<span clear="all"> </span>
|
||||
{{ application.submitter }}
|
||||
{% if user == application.submitter %}
|
||||
<img src="/static/images/icons/user-information.png" title="{% trans 'You!' %}">
|
||||
{% endif %}
|
||||
|
||||
{% if min_supporters > 0 %}
|
||||
<h4>{% trans "Supporters" %}: *</h4>
|
||||
{% if application.supporter.count == 0 %}
|
||||
-
|
||||
{% else %}
|
||||
{% for supporter in application.supporter.all %}
|
||||
{{ forloop.counter }}. {{supporter }}
|
||||
{% if not forloop.last %}<br>{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<h4>{% trans "Status" %}:</h4>
|
||||
{% if application.status != "pub" %}
|
||||
{% trans application.get_status_display %}
|
||||
<br>
|
||||
{% endif %}
|
||||
{% for note in application.notes %}
|
||||
{{ note }}
|
||||
{% if not forloop.last %}<br>{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% with application.poll_set.all as polls %}
|
||||
{% if poll|length > 0 %}
|
||||
<h4>{% trans "Poll result" %}:</h4>
|
||||
{% for poll in polls %}
|
||||
{% if poll.has_vote %}
|
||||
{% if polls|length > 1 %}
|
||||
{{forloop.counter}}. {% trans "Poll" %}:<br>
|
||||
{% endif %}
|
||||
{% for option in poll.options %}
|
||||
{% trans "Yes" %}: {{ option.yes }} |
|
||||
{% trans "No" %}: {{ option.no }} |
|
||||
{% trans "Abstention" %}: {{ option.undesided }}
|
||||
{% endfor %}
|
||||
<br>
|
||||
{% else %}
|
||||
{% if polls|length == 1 %}
|
||||
<i>{% trans "Not (yet) available." %}</i>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<h4>{% trans "Creation Time" %}:</h4>
|
||||
{{ application.creation_time }}
|
||||
|
||||
<p></p>
|
||||
{% if "edit" in actions %}
|
||||
<a href="{% url application_edit application.id %}">
|
||||
<button><span class="icon edit">{%trans 'Edit' %}</span></button>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if "wit" in actions and user == application.submitter %}
|
||||
<p></p>
|
||||
<a href='{% url application_set_status application.id 'wit' %}'>
|
||||
<button><span class="icon revert">{%trans 'Withdraw' %}</span></button>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if perms.application.can_support_application and min_supporters > 0 %}
|
||||
{% if "unsupport" in actions %}
|
||||
<p></p>
|
||||
<a href='{% url application_unsupport application.id %}'>
|
||||
<button><span class="icon remove">{% trans 'Unsupport' %}</span></button>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if "support" in actions %}
|
||||
<p></p>
|
||||
<a href='{% url application_support application.id %}'>
|
||||
<button><span class="icon add">{% trans 'Support' %}</span></button>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if min_supporters > 0 %}
|
||||
<small>* {% trans "minimum required supporters" %}: {{ min_supporters }}</small>
|
||||
{% endif %}
|
||||
|
||||
<br><br>
|
||||
|
||||
{% if perms.application.can_manage_application %}
|
||||
<div class="box">
|
||||
<h4><b>{% trans "Manage application" %}</b></h4>
|
||||
|
||||
{% if "per" in actions or "nop" in actions or "setnumber" in actions %}
|
||||
<h4>{% trans "Formal validation" %}:</h4>
|
||||
{% if "per" in actions %}
|
||||
<a href='{% url application_permit application.id %}'><button><span class="icon ok-blue">{% trans 'Permit' %}</span></button></a>
|
||||
{% endif %}
|
||||
{% if "nop" in actions %}
|
||||
<a href='{% url application_notpermit application.id %}'><button><span class="icon stop">{% trans 'Not permit (reject)' %}</span></button></a>
|
||||
{% endif %}
|
||||
{% if "setnumber" in actions %}
|
||||
<a href='{% url application_set_number application.id %}'><button><span class="icon number">{% trans 'Set Number' %}</span></button></a>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if "createitem" in actions %}
|
||||
<h4></h4>
|
||||
<a href='{% url item_new_default 'ItemApplication' application.id %}'>
|
||||
<button><span class="icon item">{%trans 'New agenda item' %}</span></button>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if application.status != "pub" %}
|
||||
<h4></h4>
|
||||
{% for poll in application.poll_set.all %}
|
||||
{% if poll.has_vote %}<a href="{% url application_poll_view poll.id %}">{% endif %}
|
||||
{{ forloop.counter }}. {% trans "Poll" %}:
|
||||
{% if poll.has_vote %}</a>{% endif %}
|
||||
<a href="{% url application_poll_delete poll.id %}"><img src="/static/images/icons/edit-delete.png" title="{% trans 'Delete Poll' %}"></a>
|
||||
<br>
|
||||
{% if poll.has_vote %}
|
||||
{% for option in poll.options %}
|
||||
{% trans "Yes" %}: {{ option.yes }} |
|
||||
{% trans "No" %}: {{ option.no }} |
|
||||
{% trans "Abstention" %}: {{ option.undesided }}
|
||||
{% endfor %}
|
||||
{% if forloop.last %}
|
||||
{% if "genpoll" in actions %}
|
||||
<br>
|
||||
<a href='{% url application_gen_poll application.id %}'>
|
||||
<button><span class="icon poll">{%trans 'New poll' %}</span></button>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<a href="{% url application_poll_view poll.id %}">{% trans "Enter vote result!" %}</a>
|
||||
{% endif %}
|
||||
{% if not forloop.last %}<br>{% endif %}
|
||||
{% empty %}
|
||||
{% if "genpoll" in actions %}
|
||||
<a href='{% url application_gen_poll application.id %}'>
|
||||
<button><span class="icon poll">{%trans 'New poll' %}</span></button>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if "acc" in actions or "rej" in actions %}
|
||||
<h4>{% trans "Result after vote" %}:</h4>
|
||||
{% if "acc" in actions %}
|
||||
<a href='{% url application_set_status application.id 'acc' %}'>
|
||||
<button><span class="icon accept">{%trans 'Accepted' %}</span></button>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if "rej" in actions %}
|
||||
<a href='{% url application_set_status application.id 'rej' %}'>
|
||||
<button><span class="icon reject">{%trans 'Rejected' %}</span></button>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if "adj" in actions or "noc" in actions or "com" in actions or "wit" in actions %}
|
||||
<h4>{% trans 'Result after debate' %}:</h4>
|
||||
{% if "adj" in actions %}
|
||||
<a href='{% url application_set_status application.id 'adj' %}'><button>{% trans 'Adjourned' %}</button></a><br>
|
||||
{% endif %}
|
||||
{% if "noc" in actions %}
|
||||
<a href='{% url application_set_status application.id 'noc' %}'><button>{% trans 'Not Concerned' %}</button></a><br>
|
||||
{% endif %}
|
||||
{% if "com" in actions %}
|
||||
<a href='{% url application_set_status application.id 'com' %}'><button>{% trans 'Commited a bill' %}</button></a><br>
|
||||
{% endif %}
|
||||
{% if "wit" in actions %}
|
||||
<a href='{% url application_set_status application.id 'wit' %}'><button>{% trans 'Withdrawed by Submitter' %}</button></a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<p></p>
|
||||
<hr>
|
||||
<h4>{%trans "For Administration only:" %}</h4>
|
||||
<a href='{% url application_reset application.id %}'>
|
||||
<button><span class="icon undo">{%trans 'Reset' %}</span></button>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %} {# end perms.application.can_support_application #}
|
||||
|
||||
|
||||
</div> <!-- end sidebar -->
|
||||
|
||||
|
||||
|
||||
<div>
|
||||
<p>{% trans "Application" %}
|
||||
{% if application.number != None %}
|
||||
#{{ application.number }}
|
||||
{% else %}
|
||||
<i>[no number]</i>
|
||||
{% endif %}</p>
|
||||
<h1>{{ application.title }}</h1>
|
||||
|
||||
|
||||
{{ application.text|linebreaks }}
|
||||
|
||||
<h2>{% trans "Reason" %}:</h2>
|
||||
|
||||
{% if application.reason %}
|
||||
{{ application.reason|linebreaks }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if application.versions|length > 1 %}
|
||||
<h2>{% trans "Revisions" %}:</h2>
|
||||
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>{% trans "Time" %}</th>
|
||||
<th>{% trans "Title" %}</th>
|
||||
<th>{% trans "Text" %}</th>
|
||||
|
||||
<th>{% trans "Reason" %}</th>
|
||||
</tr>
|
||||
{% for revision in application.versions %}
|
||||
<tr>
|
||||
<td>{{ revision.aid }} </td>
|
||||
<td><i>{{ revision.time }}</i></td>
|
||||
<td>
|
||||
{% ifchanged %}
|
||||
<b>{{ revision.title }}</b>
|
||||
{% else %}
|
||||
<i>[{% trans "unchanged" %}]</i>
|
||||
{% endifchanged %}
|
||||
</td>
|
||||
<td>
|
||||
{% ifchanged %}
|
||||
{{ revision.text|linebreaks }}
|
||||
{% else %}
|
||||
<i>[{% trans "unchanged" %}]</i>
|
||||
{% endifchanged %}
|
||||
</td>
|
||||
<td>
|
||||
{% ifchanged %}
|
||||
{{ revision.reason|linebreaks }}
|
||||
{% else %}
|
||||
<i>[{% trans "unchanged" %}]</i>
|
||||
{% endifchanged %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% if perms.application.can_manage_application %}
|
||||
<h2>{% trans "Log" %}:</h2>
|
||||
{{ application.log|linebreaks }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
40
openslides/application/tests.py
Normal file
@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.application.test
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Unit tests for the application app.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test.client import Client
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from openslides.application.models import Application, AVersion
|
||||
|
||||
class ApplicationTest(TestCase):
|
||||
def setUp(self):
|
||||
self.admin = User.objects.create_user('testadmin', '', 'default')
|
||||
self.anonym = User.objects.create_user('testanoym', '', 'default')
|
||||
self.app1 = Application(submitter=self.admin)
|
||||
self.app1.save()
|
||||
|
||||
def refresh(self):
|
||||
self.app1 = Application.objects.get(pk=self.app1.id)
|
||||
|
||||
def testVersion(self):
|
||||
self.assertTrue(self.app1.versions.exists())
|
||||
self.assertEqual(self.app1.last_version, self.app1.versions[0])
|
||||
self.assertEqual(self.app1.creation_time, self.app1.last_version.time)
|
||||
|
||||
self.app1.title = "app1"
|
||||
self.app1.save()
|
||||
self.refresh()
|
||||
|
||||
self.assertEqual(self.app1.versions.count(), 2)
|
||||
self.assertEqual(self.app1.last_version, self.app1.versions[1])
|
||||
|
69
openslides/application/urls.py
Normal file
@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.application.urls
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
URL list for the application app.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
urlpatterns = patterns('application.views',
|
||||
url(r'^application/$', 'overview', \
|
||||
name='application_overview'),
|
||||
|
||||
url(r'^application/(?P<application_id>\d+)$', 'view', \
|
||||
name='application_view'),
|
||||
|
||||
url(r'^application/new$', 'edit', \
|
||||
name='application_new'),
|
||||
|
||||
url(r'^application/(?P<application_id>\d+)/edit$', 'edit', \
|
||||
name='application_edit'),
|
||||
|
||||
url(r'^application/(?P<application_id>\d+)/del$', 'delete', \
|
||||
name='application_delete'),
|
||||
|
||||
url(r'^application/(?P<application_id>\d+)/setnumber$', 'set_number', \
|
||||
name='application_set_number'),
|
||||
|
||||
url(r'^application/(?P<application_id>\d+)/setstatus/' \
|
||||
'(?P<status>[a-z]{3})$', 'set_status', \
|
||||
name='application_set_status'),
|
||||
|
||||
url(r'^application/(?P<application_id>\d+)/permit$', 'permit', \
|
||||
name='application_permit'),
|
||||
|
||||
url(r'^application/(?P<application_id>\d+)/notpermit$', 'notpermit', \
|
||||
name='application_notpermit'),
|
||||
|
||||
url(r'^application/(?P<application_id>\d+)/reset$', 'reset', \
|
||||
name='application_reset'),
|
||||
|
||||
url(r'^application/(?P<application_id>\d+)/support$', 'support', \
|
||||
name='application_support'),
|
||||
|
||||
url(r'^application/(?P<application_id>\d+)/unsupport$', 'unsupport', \
|
||||
name='application_unsupport'),
|
||||
|
||||
url(r'^application/(?P<application_id>\d+)/gen_poll$', 'gen_poll', \
|
||||
name='application_gen_poll'),
|
||||
|
||||
url(r'^application/print$', 'print_application', \
|
||||
name='print_applications'),
|
||||
|
||||
url(r'^application/(?P<application_id>\d+)/print$', 'print_application', \
|
||||
name='print_application'),
|
||||
|
||||
url(r'^application/poll/(?P<poll_id>\d+)/print$', 'print_application_poll', name='print_application_poll'),
|
||||
|
||||
url(r'^application/poll/(?P<poll_id>\d+)$', 'view_poll', \
|
||||
name='application_poll_view'),
|
||||
|
||||
url(r'^application/poll/(?P<poll_id>\d+)/del$', 'delete_poll', \
|
||||
name='application_poll_delete'),
|
||||
)
|
350
openslides/application/views.py
Normal file
@ -0,0 +1,350 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.application.views
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Views for the application app.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.shortcuts import redirect
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from openslides.application.models import Application
|
||||
from openslides.application.forms import ApplicationForm, \
|
||||
ApplicationManagerForm
|
||||
from openslides.poll.models import Poll
|
||||
from openslides.poll.forms import OptionResultForm, PollInvalidForm
|
||||
from openslides.utils.utils import template, permission_required, \
|
||||
render_to_forbitten, del_confirm_form
|
||||
from openslides.utils.pdf import print_application, print_application_poll
|
||||
from openslides.system.api import config_get
|
||||
|
||||
|
||||
@permission_required('application.can_view_application')
|
||||
@template('application/overview.html')
|
||||
def overview(request):
|
||||
"""
|
||||
View all applications
|
||||
"""
|
||||
query = Application.objects
|
||||
if 'number' in request.GET:
|
||||
query = query.filter(number=None)
|
||||
if 'status' in request.GET:
|
||||
if 'statusvalue' in request.GET and 'on' in request.GET['status']:
|
||||
query = query.filter(status__iexact=request.GET['statusvalue'])
|
||||
try:
|
||||
sort = request.GET['sort']
|
||||
if sort in ['number', 'supporter', 'status', 'submitter', \
|
||||
'aversion__time', 'aversion__title']:
|
||||
query = query.order_by(sort)
|
||||
except KeyError:
|
||||
query = query.order_by("number")
|
||||
if 'reverse' in request.GET:
|
||||
query = query.reverse()
|
||||
if 'needsup' in request.GET:
|
||||
applications = []
|
||||
for application in query.all():
|
||||
if not application.enough_supporters:
|
||||
applications.append(application)
|
||||
else:
|
||||
applications = query.all()
|
||||
return {
|
||||
'applications': applications,
|
||||
'min_supporters': int(config_get('application_min_supporters')),
|
||||
}
|
||||
|
||||
|
||||
@permission_required('application.can_view_application')
|
||||
@template('application/view.html')
|
||||
def view(request, application_id):
|
||||
"""
|
||||
View one application.
|
||||
"""
|
||||
application = Application.objects.get(pk=application_id)
|
||||
revisions = application.versions
|
||||
actions = application.get_allowed_actions(user=request.user)
|
||||
|
||||
return {
|
||||
'application': application,
|
||||
'revisions': revisions,
|
||||
'actions': actions,
|
||||
'min_supporters': int(config_get('application_min_supporters')),
|
||||
}
|
||||
|
||||
|
||||
@login_required
|
||||
@template('application/edit.html')
|
||||
def edit(request, application_id=None):
|
||||
"""
|
||||
View a form to edit or create a application.
|
||||
"""
|
||||
if request.user.has_perm('application.can_manage_application'):
|
||||
is_manager = True
|
||||
else:
|
||||
is_manager = False
|
||||
|
||||
if not is_manager \
|
||||
and not request.user.has_perm('application.can_insert_application'):
|
||||
messages.error(request, _("You have not the necessary rights to edit or insert applications."))
|
||||
return redirect(reverse('application_overview'))
|
||||
if application_id is not None:
|
||||
application = Application.objects.get(id=application_id)
|
||||
if not request.user == application.submitter and not is_manager:
|
||||
messages.error(request, _("You can not edit this application. You are not the submitter."))
|
||||
return redirect(reverse('application_view', args=[application.id]))
|
||||
else:
|
||||
application = None
|
||||
|
||||
if request.method == 'POST':
|
||||
dataform = ApplicationForm(request.POST, prefix="data")
|
||||
valid = dataform.is_valid()
|
||||
|
||||
if is_manager:
|
||||
managerform = ApplicationManagerForm(request.POST, \
|
||||
instance=application, \
|
||||
prefix="manager")
|
||||
valid = valid and managerform.is_valid()
|
||||
else:
|
||||
managerform = None
|
||||
|
||||
if valid:
|
||||
del_supporters = True
|
||||
if is_manager:
|
||||
application = managerform.save()
|
||||
elif application_id is None:
|
||||
application = Application(submitter=request.user)
|
||||
application.title = dataform.cleaned_data['title']
|
||||
application.text = dataform.cleaned_data['text']
|
||||
application.reason = dataform.cleaned_data['reason']
|
||||
application.save(request.user)
|
||||
if application_id is None:
|
||||
messages.success(request, _('New application was successfully created.'))
|
||||
else:
|
||||
messages.success(request, _('Application was successfully modified.'))
|
||||
return redirect(reverse('application_view', args=[application.id]))
|
||||
else:
|
||||
if application_id is None:
|
||||
initial = {'text': config_get('application_preamble')}
|
||||
else:
|
||||
if application.status == "pub" and application.supporter.count() > 0:
|
||||
if request.user.has_perm('application.can_manage_applications'):
|
||||
messages.warning(request, _("Attention: Do you really want to edit this application? The supporters will not be removed automatically. Please check if the supports are valid after your changing."))
|
||||
else:
|
||||
messages.warning(request, _("Attention: Do you really want to edit this application? All <b>%s</b> supporters will be removed! Try to convince the supporters again.") % application.supporter.count() )
|
||||
initial = {'title': application.title,
|
||||
'text': application.text,
|
||||
'reason': application.reason}
|
||||
|
||||
dataform = ApplicationForm(initial=initial, prefix="data")
|
||||
if is_manager:
|
||||
if application_id is None:
|
||||
initial = {'submitter': str(request.user.id)}
|
||||
else:
|
||||
initial = {}
|
||||
managerform = ApplicationManagerForm(initial=initial, \
|
||||
instance=application, \
|
||||
prefix="manager")
|
||||
else:
|
||||
managerform = None
|
||||
return {
|
||||
'form': dataform,
|
||||
'managerform': managerform,
|
||||
'application': application,
|
||||
}
|
||||
|
||||
|
||||
@template('application/view.html')
|
||||
def delete(request, application_id):
|
||||
"""
|
||||
delete a application.
|
||||
"""
|
||||
application = Application.objects.get(id=application_id)
|
||||
if not 'delete' in application.get_allowed_actions(user=request.user):
|
||||
messages.error(request, _("You can not delete application <b>%s</b>.") % application)
|
||||
else:
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
title = str(application)
|
||||
application.delete()
|
||||
messages.success(request, _("Application <b>%s</b> was successfully deleted.") % title)
|
||||
except NameError, name:
|
||||
messages.error(request, name)
|
||||
else:
|
||||
del_confirm_form(request, application)
|
||||
return redirect(reverse('application_overview'))
|
||||
|
||||
|
||||
@permission_required('application.can_manage_application')
|
||||
@template('application/view.html')
|
||||
def set_number(request, application_id):
|
||||
"""
|
||||
set a number for an application.
|
||||
"""
|
||||
try:
|
||||
Application.objects.get(pk=application_id).set_number(user=request.user)
|
||||
messages.success(request, _("Application number was successfully set."))
|
||||
except Application.DoesNotExist:
|
||||
pass
|
||||
except NameError:
|
||||
pass
|
||||
return redirect(reverse('application_view', args=[application_id]))
|
||||
|
||||
|
||||
@permission_required('application.can_manage_application')
|
||||
@template('application/view.html')
|
||||
def permit(request, application_id):
|
||||
"""
|
||||
permit an application.
|
||||
"""
|
||||
try:
|
||||
Application.objects.get(pk=application_id).permit(user=request.user)
|
||||
messages.success(request, _("Application was successfully permitted."))
|
||||
except Application.DoesNotExist:
|
||||
pass
|
||||
return redirect(reverse('application_view', args=[application_id]))
|
||||
|
||||
@permission_required('application.can_manage_application')
|
||||
@template('application/view.html')
|
||||
def notpermit(request, application_id):
|
||||
"""
|
||||
reject (not permit) an application.
|
||||
"""
|
||||
try:
|
||||
Application.objects.get(pk=application_id).notpermit(user=request.user)
|
||||
messages.success(request, _("Application was successfully rejected."))
|
||||
except Application.DoesNotExist:
|
||||
pass
|
||||
return redirect(reverse('application_view', args=[application_id]))
|
||||
|
||||
@template('application/view.html')
|
||||
def set_status(request, application_id=None, status=None):
|
||||
"""
|
||||
set a status of an application.
|
||||
"""
|
||||
try:
|
||||
if status is not None:
|
||||
application = Application.objects.get(pk=application_id)
|
||||
application.set_status(user=request.user, status=status)
|
||||
messages.success(request, _("Application status was set to: <b>%s</b>.") % application.get_status_display())
|
||||
except Application.DoesNotExist:
|
||||
pass
|
||||
return redirect(reverse('application_view', args=[application_id]))
|
||||
|
||||
|
||||
@permission_required('application.can_manage_application')
|
||||
@template('application/view.html')
|
||||
def reset(request, application_id):
|
||||
"""
|
||||
reset an application.
|
||||
"""
|
||||
try:
|
||||
Application.objects.get(pk=application_id).reset(user=request.user)
|
||||
messages.success(request, _("Application status was reset.") )
|
||||
except Application.DoesNotExist:
|
||||
pass
|
||||
return redirect(reverse('application_view', args=[application_id]))
|
||||
|
||||
|
||||
@permission_required('application.can_support_application')
|
||||
@template('application/view.html')
|
||||
def support(request, application_id):
|
||||
"""
|
||||
support an application.
|
||||
"""
|
||||
try:
|
||||
Application.objects.get(pk=application_id).support(user=request.user)
|
||||
messages.success(request, _("You have support the application successfully.") )
|
||||
except Application.DoesNotExist:
|
||||
pass
|
||||
return redirect(reverse('application_view', args=[application_id]))
|
||||
|
||||
|
||||
@permission_required('application.can_support_application')
|
||||
@template('application/view.html')
|
||||
def unsupport(request, application_id):
|
||||
"""
|
||||
unsupport an application.
|
||||
"""
|
||||
try:
|
||||
Application.objects.get(pk=application_id).unsupport(user=request.user)
|
||||
messages.success(request, _("You have unsupport the application successfully.") )
|
||||
except Application.DoesNotExist:
|
||||
pass
|
||||
return redirect(reverse('application_view', args=[application_id]))
|
||||
|
||||
|
||||
@permission_required('application.can_manage_application')
|
||||
@template('application/view.html')
|
||||
def gen_poll(request, application_id):
|
||||
"""
|
||||
gen a poll for this application.
|
||||
"""
|
||||
try:
|
||||
count = Poll.objects.filter(application=application_id).count()
|
||||
Application.objects.get(pk=application_id).gen_poll(user=request.user, pollcount=count+1)
|
||||
messages.success(request, _("New poll was successfully created.") )
|
||||
except Application.DoesNotExist:
|
||||
pass
|
||||
return redirect(reverse('application_view', args=[application_id]))
|
||||
|
||||
|
||||
@permission_required('application.can_manage_application')
|
||||
def delete_poll(request, poll_id):
|
||||
"""
|
||||
delete a poll from this application
|
||||
"""
|
||||
poll = Poll.objects.get(pk=poll_id)
|
||||
application = poll.application
|
||||
count = application.poll_set.filter(id__lte=poll_id).count()
|
||||
if request.method == 'POST':
|
||||
poll.delete()
|
||||
messages.success(request, _('Poll was successfully deleted.'))
|
||||
else:
|
||||
del_confirm_form(request, poll, name=_("the %s. poll") % count)
|
||||
return redirect(reverse('application_view', args=[application.id]))
|
||||
|
||||
|
||||
@permission_required('application.can_view_poll')
|
||||
@template('application/poll_view.html')
|
||||
def view_poll(request, poll_id):
|
||||
"""
|
||||
view a poll for this application.
|
||||
"""
|
||||
poll = Poll.objects.get(pk=poll_id)
|
||||
options = poll.options
|
||||
if request.user.has_perm('application.can_manage_applications'):
|
||||
if request.method == 'POST':
|
||||
form = PollInvalidForm(request.POST, prefix="poll")
|
||||
if form.is_valid():
|
||||
poll.voteinvalid = form.cleaned_data['invalid'] or 0
|
||||
poll.save()
|
||||
|
||||
for option in options:
|
||||
option.form = OptionResultForm(request.POST,
|
||||
prefix="o%d" % option.id)
|
||||
if option.form.is_valid():
|
||||
option.voteyes = option.form.cleaned_data['yes']
|
||||
option.voteno = option.form.cleaned_data['no'] or 0
|
||||
option.voteundesided = option.form. \
|
||||
cleaned_data['undesided'] or 0
|
||||
option.save()
|
||||
else:
|
||||
form = PollInvalidForm(initial={'invalid': poll.voteinvalid}, prefix="poll")
|
||||
for option in options:
|
||||
option.form = OptionResultForm(initial={
|
||||
'yes': option.voteyes,
|
||||
'no': option.voteno,
|
||||
'undesided': option.voteundesided,
|
||||
}, prefix="o%d" % option.id)
|
||||
return {
|
||||
'poll': poll,
|
||||
'form': form,
|
||||
'options': options,
|
||||
}
|
0
openslides/assignment/__init__.py
Normal file
16
openslides/assignment/admin.py
Normal file
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.assignment.admin
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Register app for admin site.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.contrib import admin
|
||||
from assignment.models import Assignment
|
||||
|
||||
admin.site.register(Assignment)
|
35
openslides/assignment/forms.py
Normal file
@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.assignment.forms
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Forms for the assignment app.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.forms import ModelForm, Form, ModelChoiceField, Select
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from participant.models import Profile
|
||||
from assignment.models import Assignment
|
||||
|
||||
|
||||
class AssigmentForm(ModelForm):
|
||||
error_css_class = 'error'
|
||||
required_css_class = 'required'
|
||||
|
||||
class Meta:
|
||||
model = Assignment
|
||||
exclude = ('status', 'profile')
|
||||
|
||||
|
||||
class AssigmentRunForm(Form):
|
||||
error_css_class = 'error'
|
||||
|
||||
candidate = ModelChoiceField(
|
||||
widget=Select(attrs={'class': 'medium-input'}), \
|
||||
queryset=Profile.objects.all().order_by('user__first_name'), \
|
||||
label=_("Nominate a participant"))
|
110
openslides/assignment/models.py
Normal file
@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.assignment.models
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Models for the assignment app.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from participant.models import Profile
|
||||
|
||||
|
||||
class Assignment(models.Model):
|
||||
STATUS = (
|
||||
('sea', _('Searching for candidates')),
|
||||
('vot', _('Voting')),
|
||||
('fin', _('Finished')),
|
||||
)
|
||||
|
||||
name = models.CharField(max_length=100, verbose_name = _("Name"))
|
||||
description = models.TextField(null=True, blank=True, verbose_name = _("Description"))
|
||||
assignment_number = models.PositiveSmallIntegerField(verbose_name = _("Number of available posts"))
|
||||
polldescription = models.CharField(max_length=50, null=True, blank=True, verbose_name = _("Short description (for ballot paper)"))
|
||||
profile = models.ManyToManyField(Profile, null=True, blank=True)
|
||||
status = models.CharField(max_length=1, choices=STATUS, default='sea')
|
||||
|
||||
def set_status(self, status):
|
||||
error = True
|
||||
for a, b in Assignment.STATUS:
|
||||
if status == a:
|
||||
error = False
|
||||
break
|
||||
if error:
|
||||
raise NameError(_('%s is not a valid status.') % status)
|
||||
if self.status == status:
|
||||
raise NameError(_('The assignment status is already %s.') % self.status)
|
||||
self.status = status
|
||||
self.save()
|
||||
|
||||
def run(self, profile):
|
||||
"""
|
||||
run for a vote
|
||||
"""
|
||||
if self.is_candidate(profile):
|
||||
raise NameError(_('<b>%s</b> is already a candidate.') % profile)
|
||||
self.profile.add(profile)
|
||||
|
||||
def delrun(self, profile):
|
||||
"""
|
||||
stop running for a vote
|
||||
"""
|
||||
if self.is_candidate(profile):
|
||||
self.profile.remove(profile)
|
||||
else:
|
||||
raise NameError(_('%s is no candidate') % profile)
|
||||
|
||||
def is_candidate(self, profile):
|
||||
if profile in self.profile.get_query_set():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def gen_poll(self):
|
||||
from poll.models import Poll
|
||||
poll = Poll()
|
||||
poll.title = _("Election for %s") % self.name
|
||||
|
||||
# Option A: candidates <= available posts -> yes/no/abstention
|
||||
if self.profile.count() <= self.assignment_number:
|
||||
poll.optiondecision = True
|
||||
else:
|
||||
poll.optiondecision = False
|
||||
|
||||
# Option B: candidates == 1 -> yes/no/abstention
|
||||
#if self.profile.count() == 1:
|
||||
# poll.optiondecision = True
|
||||
#else:
|
||||
# poll.optiondecision = False
|
||||
|
||||
poll.assignment = self
|
||||
poll.description = self.polldescription
|
||||
poll.save()
|
||||
for profile in self.profile.get_query_set():
|
||||
poll.add_option(profile)
|
||||
return poll
|
||||
|
||||
@models.permalink
|
||||
def get_absolute_url(self, link='view'):
|
||||
if link == 'view':
|
||||
return ('assignment_view', [str(self.id)])
|
||||
if link == 'delete':
|
||||
return ('assignment_delete', [str(self.id)])
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
('can_view_assignment', "Can see the assignments"),
|
||||
('can_nominate_other', "Can nominate another person"
|
||||
" for a election"),
|
||||
('can_nominate_self', "Can nominate hisself for a election"),
|
||||
('can_manage_assignment', "Can manage assignments"),
|
||||
)
|
@ -0,0 +1,13 @@
|
||||
{% extends "base.html" %}
|
||||
{% load tags %}
|
||||
|
||||
{% block submenu %}
|
||||
{% url election_overview as url_electionoverview %}
|
||||
<h4 class="sectiontitle">{%trans "Elections" %}</h4>
|
||||
<ul>
|
||||
<li class="{% if request.path == url_electionoverview %}selected{% endif %}"><a href="{% url assignment_overview %}">{%trans "All elections" %}</a></li>
|
||||
{% if perms.assignment.can_manage_assignment %}
|
||||
<li class="{% active request '/assignment/new' %}"><a href="{% url assignment_new %}">{%trans "New election" %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
23
openslides/assignment/templates/assignment/edit.html
Normal file
@ -0,0 +1,23 @@
|
||||
{% extends "assignment/base_assignment.html" %}
|
||||
{% block title %}{{ block.super }} - {%trans "Election" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if assignment %}
|
||||
<h1>{%trans "Edit election" %}</h1>
|
||||
{% else %}
|
||||
<h1>{%trans "New election" %}</h1>
|
||||
{% endif %}
|
||||
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
|
||||
<button type="submit">
|
||||
<span class="icon ok">{%trans 'Save' %}</span>
|
||||
</button>
|
||||
<a href='{% url assignment_overview %}'>
|
||||
<button type="button">
|
||||
<span class="icon cancel">{%trans 'Cancel' %}</span>
|
||||
</button>
|
||||
</a>
|
||||
</form>
|
||||
{% endblock %}
|
44
openslides/assignment/templates/assignment/overview.html
Normal file
@ -0,0 +1,44 @@
|
||||
{% extends "assignment/base_assignment.html" %}
|
||||
{% block title %}{{ block.super }} - {%trans "Elections" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{%trans "Elections" %}</h1>
|
||||
|
||||
<p><form action="{{request.url}}" name="filter" method="get">
|
||||
{%trans "Filter" %}:
|
||||
<select class="default-input" name="status" onchange="document.forms['filter'].submit()">
|
||||
<option value="---">-- {%trans "Status" %}--</option>
|
||||
<option value="sea" {% if 'sea' in request.GET.status %}selected{% endif %}>{%trans "Searching for candidates" %}</option>
|
||||
<option value="vot" {% if 'vot' in request.GET.status %}selected{% endif %}>{%trans "Voting" %}</option>
|
||||
<option value="fin" {% if 'fin' in request.GET.status %}selected{% endif %}>{%trans "Finished" %}</option>
|
||||
</select>
|
||||
</form>
|
||||
</p>
|
||||
<br>
|
||||
<table>
|
||||
<tr>
|
||||
<th><a href="?sort=name{% if 'name' in request.GET.sort and 'reverse' not in request.GET %}&reverse{%endif%}">{%trans "Elections" %}</a></th>
|
||||
<th>{%trans "Candidates" %}</th>
|
||||
<th><a href="?sort=status{% if 'status' in request.GET.sort and 'reverse' not in request.GET %}&reverse{%endif%}">{%trans "Status" %}</a></th>
|
||||
{% if perms.assignment.can_manage_assignment %}
|
||||
<th>{%trans "Actions" %}</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% for assignment in assignments %}
|
||||
<tr class="{% cycle '' 'odd' %}">
|
||||
<td><a href="{% url assignment_view assignment.id %}">{{ assignment }}</a></td>
|
||||
<td>{{ assignment.profile.count }} / {{ assignment.assignment_number }}</td>
|
||||
<td>{{ assignment.get_status_display }}</td>
|
||||
{% if perms.assignment.can_manage_assignment %}
|
||||
<td><a href="{% url assignment_edit assignment.id %}"><img src="/static/images/icons/document-edit.png" title="{%trans 'Edit assignment' %}"></a>
|
||||
<a href="{% url assignment_delete assignment.id %}"><img src="/static/images/icons/edit-delete.png" title="{%trans 'Delete assignment' %}"></a>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="4"><i>{%trans "No assignments available." %}</i></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
58
openslides/assignment/templates/assignment/poll_view.html
Normal file
@ -0,0 +1,58 @@
|
||||
{% extends 'assignment/base_assignment.html' %}
|
||||
{% block title %}{{ block.super }} - {%trans "Poll" %} "{{ poll.title }}"{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ poll.title }}</h1>
|
||||
<p>{{ ballotnumber }}. {%trans "ballot" %}: {{options.count}}
|
||||
{% blocktrans count counter=options|length %}candidate{% plural %}candidates{% endblocktrans %}
|
||||
</p>
|
||||
<p><b>{% trans "Short description" %}:</b> {{ poll.description }}</p>
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>{%trans "Option" %}</th>
|
||||
{% if poll.optiondecision %}
|
||||
<th>{%trans "Yes" %}</th>
|
||||
<th>{%trans "No" %}</th>
|
||||
<th>{%trans "Abstention" %}</th>
|
||||
{% else %}
|
||||
<th>{%trans "Votes" %}</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% for option in options %}
|
||||
<tr>
|
||||
<td>{{ option }}</td>
|
||||
{% if poll.optiondecision %}
|
||||
<td>{{ option.form.yes.errors }}{{ option.form.yes }}</td>
|
||||
<td>{{ option.form.no.errors }}{{ option.form.no }}</td>
|
||||
<td>{{ option.form.undesided.errors }}{{ option.form.undesided }}</td>
|
||||
{% else %}
|
||||
<td>{{ option.form.yes.errors }}{{ option.form.yes }}</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<label for="id_voteinvalid">{% trans "Invalid" %}:</label>
|
||||
{{ form.invalid.errors }}{{ form.invalid }}
|
||||
|
||||
{% if perms.poll.can_manage_poll %}
|
||||
<p>
|
||||
<button type="submit">
|
||||
<span class="icon ok">{%trans 'Apply' %}</span>
|
||||
</button>
|
||||
<a href='{% url assignment_view poll.assignment.id %}'>
|
||||
<button type="button">
|
||||
<span class="icon previous">{%trans 'Back to election' %}</span>
|
||||
</button>
|
||||
</a>
|
||||
<a href='{% url print_assignment_poll poll.id ballotnumber poll.assignment.assignment_number %}'>
|
||||
<button type="button">
|
||||
<span class="icon pdf">{%trans 'Print ballot' %}</span>
|
||||
</button>
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
185
openslides/assignment/templates/assignment/view.html
Normal file
@ -0,0 +1,185 @@
|
||||
{% extends "assignment/base_assignment.html" %}
|
||||
{% block title %}{{ block.super }} - {% trans "Assignment" %} "{{ assignment }}"{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="sidebar">
|
||||
|
||||
<div class="box">
|
||||
<h4>{% trans "Status" %}:</h4>
|
||||
{% trans assignment.get_status_display %}
|
||||
|
||||
<h4>{% trans "Number of available posts" %}:</h4>
|
||||
{{ assignment.assignment_number }}
|
||||
</div>
|
||||
|
||||
<br><br>
|
||||
|
||||
{% if perms.assignment.can_manage_assignment %}
|
||||
<div class="box">
|
||||
<h4><b>{% trans "Manage election" %}</b></h4>
|
||||
|
||||
<h4>{% trans "Change status" %}:</h4>
|
||||
<input type="radio" name="status" onclick="window.location.href='{% url assignment_set_status assignment.id 'sea' %}';"
|
||||
{% if 'sea' in assignment.status %}checked{% endif %}>{% trans 'Searching for candidates' %}<br>
|
||||
<input type="radio" name="status" onclick="window.location.href='{% url assignment_set_status assignment.id 'vot' %}';"
|
||||
{% if 'vot' in assignment.status %}checked{% endif %}>{% trans 'Voting' %}<br>
|
||||
<input type="radio" name="status" onclick="window.location.href='{% url assignment_set_status assignment.id 'fin' %}';"
|
||||
{% if 'fin' in assignment.status %}checked{% endif %}>{% trans 'Finish' %}
|
||||
|
||||
<h4></h4>
|
||||
<a href="{% url assignment_edit assignment.id %}">
|
||||
<button><span class="icon edit">{%trans 'Edit' %}</span></button>
|
||||
</a>
|
||||
|
||||
{% if not assignment.itemassignment_set.all %}
|
||||
<h4></h4>
|
||||
<a href='{% url item_new_default 'ItemAssignment' assignment.id %}'>
|
||||
<button type="button">
|
||||
<span class="icon item">{%trans 'New agenda item' %}</span>
|
||||
</button>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div style="margin-right: 250px; min-width: 400px;">
|
||||
<h1>{{ assignment }}</h1>
|
||||
<p>{{ assignment.description }}</p>
|
||||
|
||||
<h3>{% trans "Candidates" %}</h3>
|
||||
<ol>
|
||||
{% for profile in assignment.profile.all|dictsort:"user.first_name" %}
|
||||
<li>{{ profile }}
|
||||
{% if perms.assignment.can_manage_assignment %}
|
||||
{% if assignment.status == "sea" or assignment.status == "vot" %}
|
||||
<a href="{% url assignment_delother assignment.id profile.id %}"><img src="/static/images/icons/edit-delete.png" title="{% trans 'Remove candidate' %}"></a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% empty %}
|
||||
<li style="list-style: none outside none;"><i>{% trans "No candidates available." %}</i></li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
|
||||
{% if assignment.status == "sea" or perms.assignment.can_manage_assignment and assignment.status == "vot" %}
|
||||
{% if perms.assignment.can_nominate_self or perms.assignment.can_nominate_other %}
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
{% if perms.assignment.can_nominate_self %}
|
||||
<p>
|
||||
{% if user.profile in assignment.profile.all %}
|
||||
|
||||
<a href='{% url assignment_delrun assignment.id %}'>
|
||||
<button type="button">
|
||||
<span class="icon removeuser">{%trans 'Withdraw self candidature' %}</span>
|
||||
</button>
|
||||
</a>
|
||||
{% else %}
|
||||
{% if user.profile %}
|
||||
<a href='{% url assignment_run assignment.id %}'>
|
||||
<button type="button">
|
||||
<span class="icon adduser">{%trans 'Self candidature' %}</span>
|
||||
</button>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if perms.assignment.can_nominate_other %}
|
||||
{% for field in form %}
|
||||
<label>{{ field.label }}:</label>
|
||||
<nobr>{{ field }}
|
||||
{% if perms.participant.can_view_participants and perms.participant.can_manage_participants %}
|
||||
<a href="{% url user_new %}"><img src="/static/images/icons/list-add-user.png" title="{% trans 'Add new participant' %}"></a>
|
||||
{% endif %}
|
||||
</nobr>
|
||||
{% endfor %}
|
||||
<p>
|
||||
<button type="submit">
|
||||
<span class="icon ok">{%trans 'Apply' %}</span>
|
||||
</button>
|
||||
</p>
|
||||
{% endif %}
|
||||
</form>
|
||||
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<p><br></p>
|
||||
<h3>{% trans "Election results" %}</h3>
|
||||
|
||||
{% if assignment.poll_set.all.count > 0 %}
|
||||
<table style="width: auto;">
|
||||
<tr>
|
||||
<th></th>
|
||||
{% with ballotnumber=assignment.poll_set.all.count %}
|
||||
<th colspan="{{ ballotnumber|add:'1' }}" style="text-align: center;">
|
||||
{% trans "ballot" %}</th>
|
||||
{% endwith %}
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{% trans "Candidates" %}</th>
|
||||
{% for poll in assignment.poll_set.all %}
|
||||
<th style="vertical-align: top; white-space:nowrap;">{% if perms.assignment.can_manage_assignment %}<a href="{% url assignment_poll_view poll.id forloop.counter %}">{% endif %}
|
||||
{{forloop.counter}}.
|
||||
{% if perms.assignment.can_manage_assignment %}
|
||||
</a>
|
||||
<a href="{% url assignment_poll_delete poll.id %}"><img src="/static/images/icons/edit-delete.png" title="{% trans 'Delete Poll' %}"></a>
|
||||
{% endif %}
|
||||
</th>
|
||||
{% endfor %}
|
||||
{% if assignment.profile.count > 0 and perms.assignment.can_manage_assignment and assignment.status == "vot" %}
|
||||
<th>
|
||||
{% with ballotnumber=assignment.poll_set.all.count %}
|
||||
<a href='{% url assignment_gen_poll assignment.id ballotnumber|add:'1' %}'>
|
||||
<button type="button">
|
||||
<span class="icon poll">{%trans 'New ballot' %}</span>
|
||||
</button>
|
||||
</a>
|
||||
{% endwith %}
|
||||
</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
|
||||
{% for vote in votes %}
|
||||
<tr class="{% cycle 'odd' '' %}">
|
||||
{% for v in vote %}
|
||||
<td style="white-space:nowrap;">{% if v %}
|
||||
{% if v|length == 3 %}
|
||||
<img src="/static/images/icons/voting-yes.png" title="{% trans 'Yes' %}"> {% if v.0 %}{{ v.0 }}{% else %}∅{% endif %}<br>
|
||||
<img src="/static/images/icons/voting-no.png" title="{% trans 'No' %}"> {% if v.1 %}{{ v.1 }}{% else %}∅{% endif %}<br>
|
||||
<img src="/static/images/icons/voting-abstention.png" title="{% trans 'Abstention' %}"> {% if v.2 %}{{ v.2 }}{% else %}∅{% endif %}<br>
|
||||
{% else %}
|
||||
{{ v }}
|
||||
{% endif %}
|
||||
{% else %}∅{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
{% if assignment.profile.count > 0 and perms.assignment.can_manage_assignment and assignment.status == "vot" %}
|
||||
<td></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% else %}
|
||||
|
||||
<i>{% trans "No ballots available." %}</i>
|
||||
|
||||
{% if assignment.profile.count > 0 and perms.assignment.can_manage_assignment and assignment.status == "vot" %}
|
||||
{% with ballotnumber=assignment.poll_set.all.count %}
|
||||
<p><a href='{% url assignment_gen_poll assignment.id ballotnumber|add:'1' %}'>
|
||||
<button type="button">
|
||||
<span class="icon poll">{%trans 'New ballot' %}</span>
|
||||
</button>
|
||||
</a></p>
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
30
openslides/assignment/tests.py
Normal file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
openslides.assignment.test
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Unit tests for the assignment app.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
#TODO: Replace these tests!
|
||||
class SimpleTest(TestCase):
|
||||
def test_basic_addition(self):
|
||||
"""
|
||||
Tests that 1 + 1 always equals 2.
|
||||
"""
|
||||
self.failUnlessEqual(1 + 1, 2)
|
||||
|
||||
__test__ = {"doctest": """
|
||||
Another way to test that 1 + 1 is equal to 2.
|
||||
|
||||
>>> 1 + 1 == 2
|
||||
True
|
||||
"""}
|
||||
|
54
openslides/assignment/urls.py
Normal file
@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.assignments.urls
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
URL list for the assignment app.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
urlpatterns = patterns('assignment.views',
|
||||
url(r'^assignment/$', 'get_overview', \
|
||||
name='assignment_overview'),
|
||||
|
||||
url(r'^assignment/(?P<assignment_id>\d+)$', 'view', \
|
||||
name='assignment_view'),
|
||||
|
||||
url(r'^assignment/new$', 'edit', \
|
||||
name='assignment_new'),
|
||||
|
||||
url(r'^assignment/(?P<assignment_id>\d+)/edit$', 'edit', \
|
||||
name='assignment_edit'),
|
||||
|
||||
url(r'^assignment/(?P<assignment_id>\d+)/del$', 'delete', \
|
||||
name='assignment_delete'),
|
||||
|
||||
url(r'^assignment/(?P<assignment_id>\d+)/setstatus/(?P<status>[a-z]{3})$', 'set_status', \
|
||||
name='assignment_set_status'),
|
||||
|
||||
url(r'^assignment/(?P<assignment_id>\d+)/run$', 'run', \
|
||||
name='assignment_run'),
|
||||
|
||||
url(r'^assignment/(?P<assignment_id>\d+)/delrun$', 'delrun', \
|
||||
name='assignment_delrun'),
|
||||
|
||||
url(r'^assignment/(?P<assignment_id>\d+)/delother/(?P<profile_id>\d+)$', 'delother', \
|
||||
name='assignment_delother'),
|
||||
|
||||
url(r'^assignment/poll/(?P<poll_id>\d+)/print/(?P<ballotnumber>\d+)/(?P<posts>\d+)$', 'print_assignment_poll', \
|
||||
name='print_assignment_poll'),
|
||||
|
||||
url(r'^assignment/(?P<assignment_id>\d+)/gen_poll/(?P<ballotnumber>\d+)$', 'gen_poll', \
|
||||
name='assignment_gen_poll'),
|
||||
|
||||
url(r'^assignment/poll/(?P<poll_id>\d+)/(?P<ballotnumber>\d+)$', 'poll_view', \
|
||||
name='assignment_poll_view'),
|
||||
|
||||
url(r'^assignment/poll/(?P<poll_id>\d+)/del$', 'delete_poll', \
|
||||
name='assignment_poll_delete'),
|
||||
)
|
242
openslides/assignment/views.py
Normal file
@ -0,0 +1,242 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.assignment.views
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Views for the assignment app.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.shortcuts import redirect
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from poll.models import Poll, Option
|
||||
from poll.forms import OptionResultForm, PollInvalidForm
|
||||
from assignment.models import Assignment
|
||||
from assignment.forms import AssigmentForm, AssigmentRunForm
|
||||
from utils.utils import template, permission_required, gen_confirm_form, del_confirm_form
|
||||
from utils.pdf import print_assignment_poll
|
||||
from participant.models import Profile
|
||||
|
||||
|
||||
@permission_required('assignment.can_view_assignment')
|
||||
@template('assignment/overview.html')
|
||||
def get_overview(request):
|
||||
query = Assignment.objects
|
||||
if 'status' in request.GET and '---' not in request.GET['status']:
|
||||
query = query.filter(status__iexact=request.GET['status'])
|
||||
try:
|
||||
sort = request.GET['sort']
|
||||
if sort in ['name','status']:
|
||||
query = query.order_by(sort)
|
||||
except KeyError:
|
||||
pass
|
||||
if 'reverse' in request.GET:
|
||||
query = query.reverse()
|
||||
|
||||
assignments = query.all()
|
||||
return {
|
||||
'assignments': assignments,
|
||||
}
|
||||
|
||||
|
||||
@permission_required('assignment.can_view_assignment')
|
||||
@template('assignment/view.html')
|
||||
def view(request, assignment_id=None):
|
||||
form = None
|
||||
assignment = Assignment.objects.get(pk=assignment_id)
|
||||
if request.method == 'POST':
|
||||
if request.user.has_perm('assignment.can_nominate_other'):
|
||||
form = AssigmentRunForm(request.POST)
|
||||
if form.is_valid():
|
||||
user = form.cleaned_data['candidate']
|
||||
try:
|
||||
assignment.run(user)
|
||||
messages.success(request, _("Candidate <b>%s</b> was nominated successfully.") % (user))
|
||||
except NameError, e:
|
||||
messages.error(request, e)
|
||||
else:
|
||||
if request.user.has_perm('assignment.can_nominate_other'):
|
||||
form = AssigmentRunForm()
|
||||
|
||||
# list of candidates
|
||||
candidates = set()
|
||||
for option in Option.objects.filter(poll__assignment=assignment):
|
||||
candidates.add(option.value)
|
||||
|
||||
votes = []
|
||||
for candidate in candidates:
|
||||
tmplist = []
|
||||
tmplist.append(candidate)
|
||||
for poll in assignment.poll_set.all():
|
||||
if candidate in poll.options_values:
|
||||
option = Option.objects.filter(poll=poll).filter(user=candidate)[0]
|
||||
if poll.optiondecision:
|
||||
tmplist.append([option.yes, option.no, option.undesided])
|
||||
else:
|
||||
tmplist.append(option.yes)
|
||||
else:
|
||||
tmplist.append("-")
|
||||
votes.append(tmplist)
|
||||
|
||||
return {'assignment': assignment,
|
||||
'form': form,
|
||||
'votes': votes}
|
||||
|
||||
|
||||
@permission_required('assignment.can_manage_assignment')
|
||||
@template('assignment/edit.html')
|
||||
def edit(request, assignment_id=None):
|
||||
"""
|
||||
View zum editieren und neuanlegen von Wahlen
|
||||
"""
|
||||
if assignment_id is not None:
|
||||
assignment = Assignment.objects.get(id=assignment_id)
|
||||
else:
|
||||
assignment = None
|
||||
|
||||
if request.method == 'POST':
|
||||
form = AssigmentForm(request.POST, instance=assignment)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
if assignment_id is None:
|
||||
messages.success(request, _('New election was successfully created.'))
|
||||
else:
|
||||
messages.success(request, _('Election was successfully modified.'))
|
||||
return redirect(reverse("assignment_overview"))
|
||||
else:
|
||||
form = AssigmentForm(instance=assignment)
|
||||
return {
|
||||
'form': form,
|
||||
'assignment': assignment,
|
||||
}
|
||||
|
||||
|
||||
@permission_required('assignment.can_manage_assignment')
|
||||
def delete(request, assignment_id):
|
||||
assignment = Assignment.objects.get(pk=assignment_id)
|
||||
if request.method == 'POST':
|
||||
assignment.delete()
|
||||
messages.success(request, _('Election <b>%s</b> was successfully deleted.') % assignment)
|
||||
else:
|
||||
del_confirm_form(request, assignment)
|
||||
return redirect(reverse('assignment_overview'))
|
||||
|
||||
|
||||
@permission_required('assignment.can_manage_assignment')
|
||||
@template('assignment/view.html')
|
||||
def set_status(request, assignment_id=None, status=None):
|
||||
try:
|
||||
if status is not None:
|
||||
assignment = Assignment.objects.get(pk=assignment_id)
|
||||
assignment.set_status(status)
|
||||
messages.success(request, _('Election status was set to: <b>%s</b>.') % assignment.get_status_display())
|
||||
except Assignment.DoesNotExist:
|
||||
pass
|
||||
return redirect(reverse('assignment_view', args=[assignment_id]))
|
||||
|
||||
|
||||
@permission_required('assignment.can_nominate_self')
|
||||
def run(request, assignment_id):
|
||||
assignment = Assignment.objects.get(pk=assignment_id)
|
||||
try:
|
||||
assignment.run(request.user.profile)
|
||||
messages.success(request, _('You have set your candidature successfully.') )
|
||||
except NameError, e:
|
||||
messages.error(request, e)
|
||||
except Profile.DoesNotExist:
|
||||
messages.error(request,
|
||||
_("You can't candidate. Your user account is only for administration."))
|
||||
return redirect(reverse('assignment_view', args=assignment_id))
|
||||
|
||||
|
||||
@login_required
|
||||
def delrun(request, assignment_id):
|
||||
assignment = Assignment.objects.get(pk=assignment_id)
|
||||
assignment.delrun(request.user.profile)
|
||||
messages.success(request, _("You have withdrawn your candidature successfully.") )
|
||||
return redirect(reverse('assignment_view', args=assignment_id))
|
||||
|
||||
|
||||
@permission_required('assignment.can_manage_assignment')
|
||||
def delother(request, assignment_id, profile_id):
|
||||
assignment = Assignment.objects.get(pk=assignment_id)
|
||||
profile = Profile.objects.get(pk=profile_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
assignment.delrun(profile)
|
||||
messages.success(request, _("Candidate <b>%s</b> was withdrawn successfully.") % (profile))
|
||||
else:
|
||||
gen_confirm_form(request,
|
||||
_("Do you really want to withdraw <b>%s</b> from the election?") \
|
||||
% profile, reverse('assignment_delother', args=[assignment_id, profile_id]))
|
||||
return redirect(reverse('assignment_view', args=assignment_id))
|
||||
|
||||
|
||||
@permission_required('assignment.can_manage_assignment')
|
||||
def gen_poll(request, assignment_id, ballotnumber):
|
||||
try:
|
||||
poll = Assignment.objects.get(pk=assignment_id).gen_poll()
|
||||
messages.success(request, _("New ballot was successfully created.") )
|
||||
except Assignment.DoesNotExist:
|
||||
pass
|
||||
return redirect(reverse('assignment_poll_view', args=[poll.id, ballotnumber]))
|
||||
|
||||
|
||||
@permission_required('assignment.can_view_assignment')
|
||||
@template('assignment/poll_view.html')
|
||||
def poll_view(request, poll_id, ballotnumber=1):
|
||||
poll = Poll.objects.get(pk=poll_id)
|
||||
options = poll.options.order_by('user__user__first_name')
|
||||
assignment = poll.assignment
|
||||
if request.user.has_perm('assignment.can_manage_assignment'):
|
||||
if request.method == 'POST':
|
||||
form = PollInvalidForm(request.POST, prefix="poll")
|
||||
if form.is_valid():
|
||||
poll.voteinvalid = form.cleaned_data['invalid'] or 0
|
||||
poll.save()
|
||||
|
||||
success = 0
|
||||
for option in options:
|
||||
option.form = OptionResultForm(request.POST, prefix="o%d" % option.id)
|
||||
if option.form.is_valid():
|
||||
option.voteyes = option.form.cleaned_data['yes']
|
||||
option.voteno = option.form.cleaned_data['no'] or 0
|
||||
option.voteundesided = option.form.cleaned_data['undesided'] or 0
|
||||
option.save()
|
||||
success = success + 1
|
||||
if success == options.count():
|
||||
messages.success(request, _("Votes are successfully saved.") )
|
||||
else:
|
||||
form = PollInvalidForm(initial={'invalid': poll.voteinvalid}, prefix="poll")
|
||||
for option in options:
|
||||
option.form = OptionResultForm(initial={
|
||||
'yes': option.voteyes,
|
||||
'no': option.voteno,
|
||||
'undesided': option.voteundesided,
|
||||
}, prefix="o%d" % option.id)
|
||||
return {
|
||||
'poll': poll,
|
||||
'form': form,
|
||||
'options': options,
|
||||
'ballotnumber': ballotnumber,
|
||||
}
|
||||
|
||||
|
||||
@permission_required('assignment.can_manage_assignment')
|
||||
def delete_poll(request, poll_id):
|
||||
poll = Poll.objects.get(pk=poll_id)
|
||||
assignment = poll.assignment
|
||||
ballot = assignment.poll_set.filter(id__lte=poll_id).count()
|
||||
if request.method == 'POST':
|
||||
poll.delete()
|
||||
messages.success(request, _('The %s. ballot was successfully deleted.') % ballot)
|
||||
else:
|
||||
del_confirm_form(request, poll, name=_("the %s. ballot") % ballot)
|
||||
return redirect(reverse('assignment_view', args=[assignment.id]))
|
141
openslides/default.settings.py
Normal file
@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.default.settings
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Global settings file.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
# Django settings for openslides project.
|
||||
import os
|
||||
|
||||
SITE_ROOT = os.path.realpath(os.path.dirname(__file__))
|
||||
DEBUG = True
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
|
||||
AUTH_PROFILE_MODULE = 'participant.Profile'
|
||||
LOGIN_URL = '/login/'
|
||||
LOGIN_REDIRECT_URL = '/item/'
|
||||
|
||||
ADMINS = (
|
||||
# ('Your Name', 'your_email@domain.com'),
|
||||
)
|
||||
|
||||
MANAGERS = ADMINS
|
||||
|
||||
DBPATH = os.path.join(os.path.join(SITE_ROOT, '..'), 'database.db')
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
|
||||
'NAME': DBPATH, # Or path to database file if using sqlite3.
|
||||
'USER': '', # Not used with sqlite3.
|
||||
'PASSWORD': '', # Not used with sqlite3.
|
||||
'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
|
||||
'PORT': '', # Set to empty string for default. Not used with sqlite3.
|
||||
}
|
||||
}
|
||||
|
||||
# Local time zone for this installation. Choices can be found here:
|
||||
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
|
||||
# although not all choices may be available on all operating systems.
|
||||
# On Unix systems, a value of None will cause Django to use the same
|
||||
# timezone as the operating system.
|
||||
# If running in a Windows environment this must be set to the same as your
|
||||
# system time zone.
|
||||
TIME_ZONE = 'Europe/Berlin'
|
||||
|
||||
# Language code for this installation. All choices can be found here:
|
||||
# http://www.i18nguy.com/unicode/language-identifiers.html
|
||||
LANGUAGE_CODE = 'de'
|
||||
|
||||
ugettext = lambda s: s
|
||||
|
||||
LANGUAGES = (
|
||||
('de', ugettext('German')),
|
||||
('en', ugettext('English')),
|
||||
)
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
# If you set this to False, Django will make some optimizations so as not
|
||||
# to load the internationalization machinery.
|
||||
USE_I18N = True
|
||||
|
||||
# If you set this to False, Django will not format dates, numbers and
|
||||
# calendars according to the current locale
|
||||
USE_L10N = True
|
||||
|
||||
# Absolute path to the directory that holds media.
|
||||
# Example: "/home/media/media.lawrence.com/"
|
||||
MEDIA_ROOT = ''
|
||||
|
||||
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
|
||||
# trailing slash if there is a path component (optional in other cases).
|
||||
# Examples: "http://media.lawrence.com", "http://example.com/media/"
|
||||
MEDIA_URL = ''
|
||||
|
||||
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
|
||||
# trailing slash.
|
||||
# Examples: "http://foo.com/media/", "/media/".
|
||||
ADMIN_MEDIA_PREFIX = '/media/'
|
||||
|
||||
STATIC_DOC_ROOT = os.path.join(SITE_ROOT, 'static')
|
||||
|
||||
# Make this unique, and don't share it with anybody.
|
||||
SECRET_KEY = '=(v@$58k$fcl4y8t2#q15y-9p=^45y&!$!ap$7xo6ub$akg-!5'
|
||||
|
||||
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
|
||||
|
||||
# List of callables that know how to import templates from various sources.
|
||||
TEMPLATE_LOADERS = (
|
||||
'django.template.loaders.filesystem.Loader',
|
||||
'django.template.loaders.app_directories.Loader',
|
||||
)
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
'participant.api.ChangePasswordMiddleware',
|
||||
)
|
||||
|
||||
ROOT_URLCONF = 'openslides.urls'
|
||||
|
||||
TEMPLATE_DIRS = (
|
||||
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
|
||||
# Always use forward slashes, even on Windows.
|
||||
# Don't forget to use absolute paths, not relative paths.
|
||||
os.path.join(SITE_ROOT, 'templates'),
|
||||
)
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.admin',
|
||||
'agenda',
|
||||
'system',
|
||||
'participant',
|
||||
'application',
|
||||
'poll',
|
||||
'assignment',
|
||||
'utils',
|
||||
)
|
||||
|
||||
TEMPLATE_CONTEXT_PROCESSORS = (
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'django.core.context_processors.request',
|
||||
'django.core.context_processors.i18n',
|
||||
'utils.utils.revision',
|
||||
)
|
23
openslides/locale/README.txt
Normal file
@ -0,0 +1,23 @@
|
||||
Steps to update translation for OpenSlides:
|
||||
------------------------------------------
|
||||
|
||||
1. Go to project root directory:
|
||||
$ cd openslides
|
||||
|
||||
2. Update the German po file (locale/de/LC_MESSAGES/django.po):
|
||||
$ django-admin.py makemessages -l de
|
||||
|
||||
3. Edit the German po file: locale/de/LC_MESSAGES/django.po
|
||||
(Search for "fuzzy" and empty msgstr entries.)
|
||||
|
||||
4. Update the German mo file (locale/de/LC_MESSAGES/django.mo):
|
||||
$ django-admin.py compilemessages
|
||||
|
||||
5. Restart server:
|
||||
$ python manage.py runserver
|
||||
|
||||
|
||||
|
||||
Additional hints for internationalization (i18n) in Django:
|
||||
- http://docs.djangoproject.com/en/dev/topics/i18n/internationalization/
|
||||
- http://docs.djangoproject.com/en/dev/topics/i18n/localization/
|
BIN
openslides/locale/de/LC_MESSAGES/django.mo
Normal file
1559
openslides/locale/de/LC_MESSAGES/django.po
Normal file
0
openslides/participant/__init__.py
Normal file
16
openslides/participant/admin.py
Normal file
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.participant.admin
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Register app for admin site.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.contrib import admin
|
||||
from participant.models import Profile
|
||||
|
||||
admin.site.register(Profile)
|
45
openslides/participant/api.py
Normal file
@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.participant.api
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Useful functions for the participant app.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.contrib.auth.models import User, get_hexdigest
|
||||
from django.shortcuts import redirect
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
class ChangePasswordMiddleware(object):
|
||||
def process_request(self, request):
|
||||
if request.user.is_authenticated() and "password_checked" not in request.session:
|
||||
algo, salt, hsh = request.user.password.split('$')
|
||||
bad_password = get_hexdigest(algo, salt, "%s%s" % (request.user.first_name, request.user.last_name))
|
||||
if hsh == bad_password:
|
||||
messages.info(request, _('You have to change your Password.'))
|
||||
if request.path_info != '/user/settings' and 'static' not in request.path_info:
|
||||
return redirect(reverse('user_settings'))
|
||||
else:
|
||||
request.session["password_checked"] = True
|
||||
|
||||
|
||||
def gen_username(first_name, last_name):
|
||||
testname = "%s%s" % (first_name, last_name)
|
||||
try:
|
||||
User.objects.get(username=testname)
|
||||
except User.DoesNotExist:
|
||||
return testname
|
||||
i = 0
|
||||
while True:
|
||||
i += 1
|
||||
testname = "%s%s%s" % (first_name, last_name, i)
|
||||
try:
|
||||
User.objects.get(username=testname)
|
||||
except User.DoesNotExist:
|
||||
return testname
|
63
openslides/participant/forms.py
Normal file
@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.participant.forms
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Forms for the participant app.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.forms import Form, ModelForm, CharField, EmailField, FileField, FileInput, MultipleChoiceField
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.utils.translation import ugettext as _
|
||||
from participant.models import Profile
|
||||
|
||||
class UserForm(ModelForm):
|
||||
error_css_class = 'error'
|
||||
required_css_class = 'required'
|
||||
|
||||
first_name = CharField(label=_("First name"))
|
||||
last_name = CharField(label=_("Last name"))
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
exclude = ('username', 'password', 'is_staff', 'last_login', 'date_joined', 'user_permissions')
|
||||
|
||||
class UsernameForm(ModelForm):
|
||||
error_css_class = 'error'
|
||||
required_css_class = 'required'
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
exclude = ('first_name', 'last_name', 'email', 'is_active','is_superuser', 'groups', 'password', 'is_staff', 'last_login', 'date_joined', 'user_permissions')
|
||||
|
||||
class ProfileForm(ModelForm):
|
||||
error_css_class = 'error'
|
||||
required_css_class = 'required'
|
||||
|
||||
class Meta:
|
||||
model = Profile
|
||||
|
||||
class GroupForm(ModelForm):
|
||||
error_css_class = 'error'
|
||||
required_css_class = 'required'
|
||||
|
||||
class Meta:
|
||||
model = Group
|
||||
|
||||
class UsersettingsForm(UserForm):
|
||||
error_css_class = 'error'
|
||||
required_css_class = 'required'
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('username', 'first_name', 'last_name', 'email')
|
||||
|
||||
class UserImportForm(Form):
|
||||
error_css_class = 'error'
|
||||
required_css_class = 'required'
|
||||
|
||||
csvfile = FileField(widget=FileInput(attrs={'size':'50'}), label=_("CSV File"))
|
46
openslides/participant/models.py
Normal file
@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.participant.models
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Models for the participant app.
|
||||
|
||||
:copyright: 2011 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
class Profile(models.Model):
|
||||
GENDER_CHOICES = (
|
||||
('none', _('Not specified')),
|
||||
('male', _('Male')),
|
||||
('female', _('Female')),
|
||||
)
|
||||
TYPE_CHOICE = (
|
||||
('delegate', _('Delegate')),
|
||||
('guest', _('Guest')),
|
||||
('observer', _('Observer')),
|
||||
('staff', _('Staff')),
|
||||
)
|
||||
|
||||
user = models.OneToOneField(User, unique=True, editable=False)
|
||||
gender = models.CharField(max_length=50, choices=GENDER_CHOICES, default='none', verbose_name = _("Gender"))
|
||||
group = models.CharField(max_length=100, null=True, blank=True, verbose_name = _("Group"))
|
||||
type = models.CharField(max_length=100, choices=TYPE_CHOICE, default='delegate', verbose_name = _("Typ"))
|
||||
committee = models.CharField(max_length=100, null=True, blank=True, verbose_name = _("Committee"))
|
||||
|
||||
def __unicode__(self):
|
||||
if self.group:
|
||||
return "%s (%s)" % (self.user.get_full_name(), self.group)
|
||||
return "%s" % self.user.get_full_name()
|
||||
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
('can_view_participants', "Can see the list of participants"),
|
||||
('can_manage_participants', "Can manage the participant list"),
|
||||
)
|
@ -0,0 +1,19 @@
|
||||
{% extends "base.html" %}
|
||||
{% load tags %}
|
||||
|
||||
{% block submenu %}
|
||||
{% url user_overview as url_useroverview %}
|
||||
<h4 class="sectiontitle">{%trans "Participants" %}</h4>
|
||||
<ul>
|
||||
{% if perms.participant.can_view_participants %}
|
||||
<li class="{% if request.path == url_useroverview %}selected{% endif %}"><a href="{% url user_overview %}">{%trans "All participants" %}</a></li>
|
||||
{% endif %}
|
||||
{% if perms.participant.can_manage_participants %}
|
||||
<li class="{% active request '/user/new' %}"><a href="{% url user_new %}">{%trans "New participant" %}</a></li>
|
||||
<li><a href="{% url user_group_overview %}">{%trans "All user groups" %}</a></li>
|
||||
<li><a href="{% url user_group_new %}">{%trans "New user group" %}</a></li>
|
||||
<li><a href="{% url user_print %}"><img src="/static/images/icons/application-pdf.png"> {%trans 'Print participant list' %}</a></li>
|
||||
<li><a href="{% url user_import %}"> {%trans 'Import' %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
26
openslides/participant/templates/participant/edit.html
Normal file
@ -0,0 +1,26 @@
|
||||
{% extends "participant/base_participant.html" %}
|
||||
{% block title %}{{ block.super }} - {%trans "Participant" %} {% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if edituser %}
|
||||
<h1>{%trans "Edit participant" %}</h1>
|
||||
{% else %}
|
||||
<h1>{%trans "New participant" %}</h1>
|
||||
{% endif %}
|
||||
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
{% if edituser %}
|
||||
{{ usernameform.as_p }}
|
||||
{% endif %}
|
||||
{{ userform.as_p }}
|
||||
{{ profileform.as_p }}
|
||||
<button type="submit">
|
||||
<span class="icon ok">{%trans 'Save' %}</span>
|
||||
</button>
|
||||
<a href='{% url user_overview %}'>
|
||||
<button type="button">
|
||||
<span class="icon cancel">{%trans 'Cancel' %}</span>
|
||||
</button>
|
||||
</a>
|
||||
</form>
|
||||
{% endblock %}
|
22
openslides/participant/templates/participant/group_edit.html
Normal file
@ -0,0 +1,22 @@
|
||||
{% extends "participant/base_participant.html" %}
|
||||
{% block title %}{{ block.super }} - {%trans "User Group" %} {% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if group %}
|
||||
<h1>{%trans "Edit user group" %}</h1>
|
||||
{% else %}
|
||||
<h1>{%trans "New user group" %}</h1>
|
||||
{% endif %}
|
||||
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit">
|
||||
<span class="icon ok">{%trans 'Save' %}</span>
|
||||
</button>
|
||||
<a href='{% url user_overview %}'>
|
||||
<button type="button">
|
||||
<span class="icon cancel">{%trans 'Cancel' %}</span>
|
||||
</button>
|
||||
</a>
|
||||
</form>
|
||||
{% endblock %}
|
@ -0,0 +1,24 @@
|
||||
{% extends "participant/base_participant.html" %}
|
||||
{% block title %}{{ block.super }} - {%trans "Groups" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{%trans "User groups" %}</h1>
|
||||
<table>
|
||||
<tr>
|
||||
<th>{%trans "User Group" %}</th>
|
||||
<th>{%trans "Action" %}</th>
|
||||
</tr>
|
||||
{% for group in groups %}
|
||||
<tr class="{% cycle '' 'odd' %}">
|
||||
<td>{{ group.name }}</td>
|
||||
<td><a href="{% url user_group_edit group.id %}"><img src="/static/images/icons/document-edit.png" title="{%trans 'Edit group' %}"></a>
|
||||
<a href="{% url user_group_delete group.id %}"><img src="/static/images/icons/edit-delete.png" title="{%trans 'Delete group' %}"></a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="5"><i>{%trans "No participants available." %}</i></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
21
openslides/participant/templates/participant/import.html
Normal file
@ -0,0 +1,21 @@
|
||||
{% extends "participant/base_participant.html" %}
|
||||
{% block title %}{{ block.super }} - {%trans "Participant Import" %} {% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Import</h1>
|
||||
<p>{% trans 'Select a CSV file to import participants!' %}</p>
|
||||
|
||||
<p>{% trans '(Required comma separated values: <code>last_name, first_name, email, gender, group, type, committee</code>)' %} </p>
|
||||
<form enctype="multipart/form-data" action="" method="post">{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit">
|
||||
<span class="icon import">{%trans 'Import' %}</span>
|
||||
</button>
|
||||
<a href="{% url user_overview%}">
|
||||
<button type="button">
|
||||
<span class="icon cancel">{%trans 'Cancel' %}</span>
|
||||
</button>
|
||||
</a>
|
||||
</form>
|
||||
</p>
|
||||
{% endblock %}
|