Merge remote branch 'upstream/master' into newtemplate
Conflicts: openslides/application/views.py openslides/assignment/templates/assignment/overview.html openslides/assignment/templates/assignment/view.html openslides/assignment/views.py openslides/locale/de/LC_MESSAGES/django.mo openslides/locale/de/LC_MESSAGES/django.po openslides/motion/templates/motion/config.html openslides/motion/templates/motion/edit.html openslides/motion/templates/motion/import.html openslides/motion/templates/motion/overview.html openslides/motion/templates/motion/poll_view.html openslides/motion/templates/motion/view.html openslides/participant/templates/participant/base_participant.html openslides/participant/templates/participant/config.html openslides/participant/templates/participant/group_edit.html openslides/participant/templates/participant/group_overview.html openslides/participant/templates/participant/overview.html openslides/projector/templates/projector/base_projector.html openslides/projector/templates/projector/dashboard.html openslides/projector/templates/projector/live_view_widget.html openslides/static/styles/base.css openslides/templates/base.html openslides/templates/front_page.html openslides/utils/utils.py
This commit is contained in:
commit
4ae7f3e243
4
.coveragerc
Normal file
4
.coveragerc
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[run]
|
||||||
|
source=openslides
|
||||||
|
[report]
|
||||||
|
exclude_lines = def __(unicode|repr)__
|
4
.gitattributes
vendored
Normal file
4
.gitattributes
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.gitattributes export-ignore
|
||||||
|
.gitignore export-ignore
|
||||||
|
/docs export-ignore
|
||||||
|
/extras export-ignore
|
7
.gitignore
vendored
7
.gitignore
vendored
@ -11,3 +11,10 @@ docs/_build/*
|
|||||||
*.egg-info
|
*.egg-info
|
||||||
build/*
|
build/*
|
||||||
dist/*
|
dist/*
|
||||||
|
.DS_Store
|
||||||
|
settings.py
|
||||||
|
versiontools*
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
.coverage
|
||||||
|
htmlcov
|
||||||
|
11
.travis.yml
Normal file
11
.travis.yml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
language: python
|
||||||
|
python:
|
||||||
|
- "2.5"
|
||||||
|
- "2.6"
|
||||||
|
- "2.7"
|
||||||
|
install:
|
||||||
|
- pip install -r requirements.txt --use-mirrors
|
||||||
|
- python extras/scripts/create_local_settings.py
|
||||||
|
script:
|
||||||
|
- coverage run ./manage.py test tests && coverage report -m
|
||||||
|
- pep8 --max-line-length=150 --exclude="urls.py,motion/" --statistics openslides
|
2
AUTHORS
2
AUTHORS
@ -5,3 +5,5 @@ Authors of OpenSlides in chronological order of first contribution:
|
|||||||
Norman Jäckel <mail@normanjaeckel.de>
|
Norman Jäckel <mail@normanjaeckel.de>
|
||||||
René Köcher <shirk@bitspin.org>
|
René Köcher <shirk@bitspin.org>
|
||||||
Andy Kittner <andkit@gmx.net>
|
Andy Kittner <andkit@gmx.net>
|
||||||
|
Moira Brülisauer <moira.bruelisauer@piratenpartei.ch> (French translation)
|
||||||
|
Alexis Roussel <alexis.roussel@partipirate.ch> (French translation)
|
||||||
|
74
CHANGELOG
74
CHANGELOG
@ -2,6 +2,80 @@ CHANGELOG of OpenSlides
|
|||||||
http://openslides.org
|
http://openslides.org
|
||||||
|
|
||||||
|
|
||||||
|
Version 1.3 (2012-12-10)
|
||||||
|
========================
|
||||||
|
[http://dev.openslides.org/milestone/1.3]
|
||||||
|
|
||||||
|
Projector:
|
||||||
|
- New public dashboard which allows access for all users per default. (#361)
|
||||||
|
(changed from the old, limited projector control page)
|
||||||
|
- New dashboard widgets:
|
||||||
|
* welcome widget (shows static welcome title and text)
|
||||||
|
* participant widget
|
||||||
|
* group widget
|
||||||
|
* personal widget (shows my motions and my elections)
|
||||||
|
- Hide scrollbar in projector view.
|
||||||
|
- Added cache for AJAX version of the projector view.
|
||||||
|
- Moved projector control icons into projector live widget. (#403)
|
||||||
|
- New weight field for custom slides (to order custom slides in widget).
|
||||||
|
- Fixed drag'n'drop behaviour of widgets into empty dashboard column.
|
||||||
|
- Fixed permissions for agenda, motion and assignment widgets (set to projector.can_manage_projector).
|
||||||
|
Agenda:
|
||||||
|
- Fixed slide error if agenda item deleted. (#330)
|
||||||
|
Motions:
|
||||||
|
- Translation: Changed 'application' to 'motion'.
|
||||||
|
- Fixed: Manager could not edit supporters. (#336)
|
||||||
|
- Fixed attribute error for anonymous users in motion view. (#329)
|
||||||
|
- Set default sorting of motions by number (in widget).
|
||||||
|
- CSV import allows to import group as submitter. (#419)
|
||||||
|
- Updated motion code for new user API.
|
||||||
|
- Rewrote motion views as class based views.
|
||||||
|
Elections:
|
||||||
|
- User can block himself/herself from candidate list after delete his/her candidature.
|
||||||
|
- Show blocked candidates in separate list.
|
||||||
|
- Mark elected candidates in candidate list. (#374)
|
||||||
|
- Show linebreaks in description. (#392)
|
||||||
|
- Set default sorting of elections by name (in widget).
|
||||||
|
- Fixed redirect from a poll which does not exists anymore.
|
||||||
|
- Changed default permissions of anonymous user to see elections. (#334)
|
||||||
|
- Updated assignment code for new user API.
|
||||||
|
Participants:
|
||||||
|
- New user and group API.
|
||||||
|
- New group option to handle a group as participant (and thus e.g. as submitter of motion).
|
||||||
|
- CSV import does not delete existing users anymore and append users as new users.
|
||||||
|
- New user field 'about me'. (#390)
|
||||||
|
- New config option for sorting users by first or last name (in participant lists, elections and motions). (#303)
|
||||||
|
- Allowed whitespaces in username, default: <firstname lastname>. (#326)
|
||||||
|
- New user and group slides. (#176)
|
||||||
|
- Don't allow to deactivate the administrator or themself.
|
||||||
|
- Don't allow to delete themself.
|
||||||
|
- Renamed participant field 'groups' to 'structure level' (German: Gliederungsebene).
|
||||||
|
- Rewrote participant views as class based views.
|
||||||
|
- Made OpenSlides user a child model of Django user model.
|
||||||
|
- Appended tests.
|
||||||
|
- Fixed error to allow admins to delete anonymous group
|
||||||
|
|
||||||
|
Other:
|
||||||
|
- Added French translation (Thanks to Moira).
|
||||||
|
- Updated setup.py to make an openslides python package.
|
||||||
|
- Removed frontpage (welcome widget contains it's content) and redirect '/' to dashboard url.
|
||||||
|
|
||||||
|
- Added LOCALE_PATHS to openslides_settings to avoid deprication in Django 1.5.
|
||||||
|
- Redesigned the DeleteView (append QuestionMixin to send question via the django message API).
|
||||||
|
- Fixed encoding error in settings.py. (#349)
|
||||||
|
- Renamed openslides_settings.py to openslides_global_settings.py.
|
||||||
|
- New default path to database file (XDG_DATA_HOME, e.g. ~/.local/share/openslides/).
|
||||||
|
- New default path to settings file (XDG_CONFIG_HOME, e.g. ~/.config/openslides/).
|
||||||
|
- Added special handling to determine location of database and settings file in portable version.
|
||||||
|
- Don't use similar characters in generated passwords (no 'Il10oO').
|
||||||
|
- Localised the datetime in PDF header. (#296)
|
||||||
|
- Used specific session cookie name. (#332)
|
||||||
|
- Moved code repository from hg to git (incl. some required updates, e.g. version string function).
|
||||||
|
- Updated German translations.
|
||||||
|
- Several code optimizations.
|
||||||
|
- Several minor and medium issues and errors were fixed.
|
||||||
|
|
||||||
|
|
||||||
Version 1.2 (2012-07-25)
|
Version 1.2 (2012-07-25)
|
||||||
========================
|
========================
|
||||||
[http://dev.openslides.org/milestone/1.2]
|
[http://dev.openslides.org/milestone/1.2]
|
||||||
|
237
INSTALL.txt
237
INSTALL.txt
@ -1,29 +1,148 @@
|
|||||||
Installation Instructions for OpenSlides 1.2
|
Installation Instructions for OpenSlides 1.3
|
||||||
============================================
|
============================================
|
||||||
|
|
||||||
Content
|
Content
|
||||||
-------
|
-------
|
||||||
I. Installation on Windows (32/64bit)
|
|
||||||
II. Installation on GNU/Linux and MacOSX
|
I. Installation on GNU/Linux and MacOSX using the Python Package Index (PyPI)
|
||||||
|
II. Installation on GNU/Linux and MacOSX using the sources
|
||||||
|
III. Installation on Windows (32bit) using the Python Package Index (PyPI)
|
||||||
|
|
||||||
If you need help ask on OpenSlides users mailing list.
|
If you need help ask on OpenSlides users mailing list.
|
||||||
See http://openslides.org for more information.
|
See http://openslides.org for more information.
|
||||||
|
|
||||||
|
|
||||||
I. Installation on Windows (32/64bit)
|
I. Installation on GNU/Linux and MacOSX using the Python Package Index (PyPI)
|
||||||
-------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
1. Check requirements:
|
||||||
|
|
||||||
|
Make sure that you have installed Python Programming Language 2
|
||||||
|
(>= 2.5) on your system.
|
||||||
|
|
||||||
|
2. Setup a virtual environment with virtualenv (optional):
|
||||||
|
|
||||||
|
You can setup a virtual environment to install OpenSlides as
|
||||||
|
non-root user.
|
||||||
|
|
||||||
|
E. g. for Ubuntu run:
|
||||||
|
$ sudo apt-get install python-virtualenv
|
||||||
|
|
||||||
|
To setup and activate the virtual environment, create your
|
||||||
|
OpenSlides directory, change to it and run:
|
||||||
|
|
||||||
|
$ virtualenv .venv
|
||||||
|
$ source .venv/bin/activate
|
||||||
|
|
||||||
|
3. Install OpenSlides:
|
||||||
|
|
||||||
|
$ pip install openslides
|
||||||
|
|
||||||
|
OpenSlides will install the following required python packages:
|
||||||
|
+ Django
|
||||||
|
+ django-mptt
|
||||||
|
+ ReportLab Toolkit
|
||||||
|
+ Python Imaging Library (PIL)
|
||||||
|
|
||||||
|
4. Start OpenSlides server and open URL in your default browser:
|
||||||
|
|
||||||
|
$ openslides
|
||||||
|
|
||||||
|
If you run this command the first time a new database and the
|
||||||
|
admin account are created. Please change the password after
|
||||||
|
first login!
|
||||||
|
|
||||||
|
Username: admin
|
||||||
|
Password: admin
|
||||||
|
|
||||||
|
Use 'openslides --help' to show all start options.
|
||||||
|
|
||||||
|
|
||||||
|
II. Installation on GNU/Linux and MacOSX using the sources
|
||||||
|
----------------------------------------------------------
|
||||||
|
|
||||||
1. Install requirements:
|
1. Install requirements:
|
||||||
|
|
||||||
OpenSlides requires following programs, which should be
|
OpenSlides requires following programs, which should be
|
||||||
installed first:
|
installed first:
|
||||||
+ Python Programming Language 2 (>= 2.5),
|
+ Python Programming Language 2 (>= 2.5)
|
||||||
+ Setuptools
|
+ virtualenv (>= 1.4.1)
|
||||||
+ ReportLab Toolkit
|
+ ReportLab Toolkit
|
||||||
+ Python Imaging Library (PIL)
|
+ Python Imaging Library (PIL)
|
||||||
+ Django
|
|
||||||
+ django-mptt
|
|
||||||
|
|
||||||
|
E. g. for Ubuntu run:
|
||||||
|
$ sudo apt-get install python python-virtualenv python-reportlab python-imaging
|
||||||
|
|
||||||
|
2. Get OpenSlides:
|
||||||
|
|
||||||
|
a) Download latest OpenSlides release from http://openslides.org.
|
||||||
|
|
||||||
|
OR
|
||||||
|
|
||||||
|
b) Clone development version from OpenSlides' github repository
|
||||||
|
https://github.com/OpenSlides/OpenSlides. This requires Git,
|
||||||
|
see http://git-scm.com/.
|
||||||
|
|
||||||
|
E. g. for Ubuntu run:
|
||||||
|
$ sudo apt-get install git
|
||||||
|
$ git clone git://github.com/OpenSlides/OpenSlides.git OpenSlides
|
||||||
|
|
||||||
|
3. Setup a virtual environment with virtualenv (optional):
|
||||||
|
|
||||||
|
You can setup a virtual environment to install OpenSlides as
|
||||||
|
non-root user.
|
||||||
|
|
||||||
|
E. g. for Ubuntu run:
|
||||||
|
$ sudo apt-get install python-virtualenv
|
||||||
|
|
||||||
|
To setup and activate the virtual environment go to the
|
||||||
|
(extracted/cloned) root directory of OpenSlides and run:
|
||||||
|
|
||||||
|
$ virtualenv .venv
|
||||||
|
$ source .venv/bin/activate
|
||||||
|
|
||||||
|
4. Install the required python-packages:
|
||||||
|
|
||||||
|
$ pip install django django-mptt
|
||||||
|
|
||||||
|
If you use python < 2.6 you also have to install simplejson:
|
||||||
|
$ pip install simplejson
|
||||||
|
|
||||||
|
If requirements reportlab or PIL still missing (see 1.):
|
||||||
|
$ pip install reportlab pil
|
||||||
|
|
||||||
|
5. Start OpenSlides server and open URL in your default browser:
|
||||||
|
|
||||||
|
$ python start.py
|
||||||
|
|
||||||
|
If you run this script the first time a new database and the
|
||||||
|
admin account are created. Please change the password after
|
||||||
|
first login!
|
||||||
|
|
||||||
|
Username: admin
|
||||||
|
Password: admin
|
||||||
|
|
||||||
|
Use 'python start.py --help' to show all start options.
|
||||||
|
|
||||||
|
6. Restart OpenSlides:
|
||||||
|
|
||||||
|
To restart OpenSlides after closing the terminal activate the
|
||||||
|
virtual environment (see 4.) before starting the server (see 6.).
|
||||||
|
|
||||||
|
|
||||||
|
III. Installation on Windows (32bit) using the Python Package Index (PyPI)
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
NOTE: There is a portable version of OpenSlides for Windows which does not
|
||||||
|
required any install steps! If there is a reason that you can not use the
|
||||||
|
portable version you should run the following install steps.
|
||||||
|
|
||||||
|
1. Install requirements:
|
||||||
|
|
||||||
|
The OpenSlides install requires following programs, which should be
|
||||||
|
installed first:
|
||||||
|
+ Python Programming Language 2 (>= 2.5)
|
||||||
|
+ Setuptools
|
||||||
|
|
||||||
a) Download and run 32bit MSI installer from http://www.python.org/:
|
a) Download and run 32bit MSI installer from http://www.python.org/:
|
||||||
|
|
||||||
@ -40,33 +159,17 @@ I. Installation on Windows (32/64bit)
|
|||||||
|
|
||||||
http://pypi.python.org/packages/2.7/s/setuptools/setuptools-0.6c11.win32-py2.7.exe
|
http://pypi.python.org/packages/2.7/s/setuptools/setuptools-0.6c11.win32-py2.7.exe
|
||||||
|
|
||||||
d) Install ReportLab Toolkit, Python Imaging Library (PIL), Django
|
2. Install OpenSlides:
|
||||||
and django-mptt:
|
|
||||||
|
|
||||||
Open command line (cmd) and run:
|
Open command line (cmd) and run:
|
||||||
|
|
||||||
easy_install django django-mptt reportlab pil
|
easy_install openslides
|
||||||
|
|
||||||
If you use a 64bit version of Python, you have to install reportlab
|
|
||||||
and pil not by using easy_install but manually.
|
|
||||||
|
|
||||||
2. Get OpenSlides:
|
|
||||||
|
|
||||||
a) Download latest OpenSlides release from http://openslides.org.
|
|
||||||
|
|
||||||
OR
|
|
||||||
|
|
||||||
b) Clone development version from mercurial repository
|
|
||||||
http://hg.openslides.org. This requires Mercurial source control
|
|
||||||
management (hg), see http://mercurial.selenic.com.
|
|
||||||
|
|
||||||
Open command line (cmd) and run:
|
|
||||||
|
|
||||||
hg clone http://hg.openslides.org OpenSlides
|
|
||||||
|
|
||||||
3. Start OpenSlides server and open URL in your default browser:
|
3. Start OpenSlides server and open URL in your default browser:
|
||||||
|
|
||||||
python start.py
|
Open command line (cmd) and run:
|
||||||
|
|
||||||
|
openslides
|
||||||
|
|
||||||
If you run this script the first time a new database and the
|
If you run this script the first time a new database and the
|
||||||
admin account are created. Please change the password after
|
admin account are created. Please change the password after
|
||||||
@ -75,76 +178,4 @@ I. Installation on Windows (32/64bit)
|
|||||||
Username: admin
|
Username: admin
|
||||||
Password: admin
|
Password: admin
|
||||||
|
|
||||||
Use 'python start.py --help' to show all start options.
|
Use 'openslides --help' to show all start options.
|
||||||
|
|
||||||
|
|
||||||
II. Installation on GNU/Linux and MacOSX
|
|
||||||
----------------------------------------
|
|
||||||
|
|
||||||
1. Install requirements:
|
|
||||||
|
|
||||||
OpenSlides requires following programs, which should be
|
|
||||||
installed first:
|
|
||||||
+ Python Programming Language 2 (>= 2.5)
|
|
||||||
+ virtualenv (>= 1.4.1)
|
|
||||||
+ ReportLab Toolkit
|
|
||||||
+ Python Imaging Library (PIL)
|
|
||||||
|
|
||||||
E.g. for ubuntu run:
|
|
||||||
$ sudo apt-get install python python-virtualenv python-reportlab python-imaging
|
|
||||||
|
|
||||||
2. Get OpenSlides:
|
|
||||||
|
|
||||||
a) Download latest OpenSlides release from http://openslides.org.
|
|
||||||
|
|
||||||
OR
|
|
||||||
|
|
||||||
b) Clone development version from mercurial repository
|
|
||||||
http://hg.openslides.org. This requires Mercurial source control
|
|
||||||
management (hg):
|
|
||||||
|
|
||||||
E.g. for Ubuntu run:
|
|
||||||
$ sudo apt-get install mercurial
|
|
||||||
$ hg clone http://hg.openslides.org OpenSlides
|
|
||||||
|
|
||||||
3. Setup your virtual environment with virtualenv:
|
|
||||||
|
|
||||||
Go to the (extracted/cloned) root directory of OpenSlides
|
|
||||||
and create virtualenv environment:
|
|
||||||
|
|
||||||
$ virtualenv .venv
|
|
||||||
|
|
||||||
For virtualenv >= 1.7 use instead:
|
|
||||||
$ virtualenv --system-site-packages .venv
|
|
||||||
|
|
||||||
4. Activate the virtual environment:
|
|
||||||
|
|
||||||
$ source .venv/bin/activate
|
|
||||||
|
|
||||||
5. Install the required python-packages:
|
|
||||||
|
|
||||||
$ pip install django django-mptt
|
|
||||||
|
|
||||||
If you use python < 2.6 you also have to install simplejson:
|
|
||||||
$ pip install simplejson
|
|
||||||
|
|
||||||
If requirements reportlab or PIL still missing (see 1.):
|
|
||||||
$ pip install reportlab pil
|
|
||||||
|
|
||||||
6. Start OpenSlides server and open URL in your default browser:
|
|
||||||
|
|
||||||
$ python start.py
|
|
||||||
|
|
||||||
If you run this script the first time a new database and the
|
|
||||||
admin account are created. Please change the password after
|
|
||||||
first login!
|
|
||||||
|
|
||||||
Username: admin
|
|
||||||
Password: admin
|
|
||||||
|
|
||||||
Use 'python start.py --help' to show all start options.
|
|
||||||
|
|
||||||
7. Restart OpenSlides:
|
|
||||||
|
|
||||||
To restart OpenSlides after closing the terminal activate the
|
|
||||||
virtual environment (see 4.) before starting the server (see 6.).
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
include AUTHORS
|
include AUTHORS
|
||||||
include CHANGELOG
|
include CHANGELOG
|
||||||
include initial_data.json
|
|
||||||
include INSTALL.txt
|
include INSTALL.txt
|
||||||
include LICENSE
|
include LICENSE
|
||||||
include manage.py
|
include manage.py
|
||||||
@ -12,16 +11,14 @@ recursive-include openslides/templates *
|
|||||||
|
|
||||||
recursive-include openslides/agenda/templates *
|
recursive-include openslides/agenda/templates *
|
||||||
recursive-include openslides/agenda/static *
|
recursive-include openslides/agenda/static *
|
||||||
recursive-include openslides/application/templates *
|
recursive-include openslides/motion/templates *
|
||||||
recursive-include openslides/application/static *
|
|
||||||
recursive-include openslides/assignment/templates *
|
recursive-include openslides/assignment/templates *
|
||||||
recursive-include openslides/assignment/static *
|
recursive-include openslides/assignment/static *
|
||||||
recursive-include openslides/config/templates *
|
recursive-include openslides/config/templates *
|
||||||
recursive-include openslides/config/static *
|
|
||||||
recursive-include openslides/participant/templates *
|
recursive-include openslides/participant/templates *
|
||||||
recursive-include openslides/participant/static *
|
recursive-include openslides/participant/static *
|
||||||
|
include openslides/participant/fixtures/groups_de.json
|
||||||
recursive-include openslides/poll/templates *
|
recursive-include openslides/poll/templates *
|
||||||
recursive-include openslides/poll/static *
|
|
||||||
recursive-include openslides/projector/templates *
|
recursive-include openslides/projector/templates *
|
||||||
recursive-include openslides/projector/static *
|
recursive-include openslides/projector/static *
|
||||||
|
|
||||||
|
49
README.txt
49
README.txt
@ -1,16 +1,16 @@
|
|||||||
==================================
|
==================================
|
||||||
English README file for OpenSlides
|
English README file for OpenSlides
|
||||||
==================================
|
==================================
|
||||||
|
|
||||||
This is OpenSlides, version 1.2 (2012-07-25).
|
This is OpenSlides, version 1.3.1 (unreleased).
|
||||||
|
|
||||||
|
|
||||||
What is OpenSlides?
|
What is OpenSlides?
|
||||||
===================
|
===================
|
||||||
OpenSlides is a free, web-based presentation system for displaying and
|
OpenSlides is a free web-based presentation and assembly system for
|
||||||
controlling of agenda, applications and elections of an assembly.
|
displaying and controlling of agenda, motions and elections of an assembly.
|
||||||
|
|
||||||
See http://www.openslides.org for more information.
|
See http://openslides.org for more information.
|
||||||
|
|
||||||
|
|
||||||
Getting started
|
Getting started
|
||||||
@ -18,14 +18,16 @@ Getting started
|
|||||||
Install and start OpenSlides as described in the INSTALL.txt.
|
Install and start OpenSlides as described in the INSTALL.txt.
|
||||||
|
|
||||||
If you need help please contact the OpenSlides team on public mailing
|
If you need help please contact the OpenSlides team on public mailing
|
||||||
list or read the OpenSlides manual. See http://www.openslides.org.
|
list or read the OpenSlides manual. See http://openslides.org.
|
||||||
|
|
||||||
|
|
||||||
The start script of OpenSlides
|
The start script of OpenSlides
|
||||||
==============================
|
==============================
|
||||||
Simply running
|
Simply running
|
||||||
openslides.exe (on Windows) or
|
openslides.exe (on Windows)
|
||||||
python start.py (on Linux/MacOS)
|
or
|
||||||
|
openslides (on Linux/MacOS)
|
||||||
|
|
||||||
will start OpenSlides using djangos development server. It will also
|
will start OpenSlides using djangos development server. It will also
|
||||||
try to open OpenSlides in your default webbrowser.
|
try to open OpenSlides in your default webbrowser.
|
||||||
|
|
||||||
@ -47,27 +49,35 @@ Command line options
|
|||||||
--------------------
|
--------------------
|
||||||
The following command line options are available:
|
The following command line options are available:
|
||||||
|
|
||||||
|
-h, --help
|
||||||
|
Shows all options
|
||||||
|
|
||||||
-a, --address=ADDRESS
|
-a, --address=ADDRESS
|
||||||
Changes the address on which the server will listen for connections
|
Changes the address on which the server will listen for connections
|
||||||
|
|
||||||
-p, --port
|
-p PORT, --port=PORT
|
||||||
Changes the port on which the server will listen for connections
|
Changes the port on which the server will listen for connections
|
||||||
|
|
||||||
--syncdb
|
--syncdb
|
||||||
Create/ update the database
|
Creates/updates database before starting the server
|
||||||
|
|
||||||
--reset-admin
|
--reset-admin
|
||||||
This will reset the password of the user
|
Resets the password to 'admin' for user 'admin'
|
||||||
|
|
||||||
|
-s SETTINGS, --settings=SETTINGS
|
||||||
|
Sets the path to the settings file.
|
||||||
|
|
||||||
|
--no-reload
|
||||||
|
Does not reload the development server
|
||||||
|
|
||||||
|
--version
|
||||||
|
Show version and exit.
|
||||||
|
|
||||||
Example 1: Openslides should only be accessible on this computer:
|
Example 1: Openslides should only be accessible on this computer:
|
||||||
openslides.exe -a 127.0.0.1
|
openslides -a 127.0.0.1
|
||||||
or
|
|
||||||
python start.py -a 127.0.0.1
|
|
||||||
|
|
||||||
Example 2: Like above, but also specify the port as 8080
|
Example 2: Like above, but also specify the port as 8080
|
||||||
openslides.exe -a 127.0.0.01 -p 8080
|
openslides -a 127.0.0.01 -p 8080
|
||||||
or
|
|
||||||
python start.py -a 127.0.0.1 -p 8080
|
|
||||||
|
|
||||||
|
|
||||||
Supported operating systems and browsers
|
Supported operating systems and browsers
|
||||||
@ -82,4 +92,3 @@ Browsers:
|
|||||||
IE 7+
|
IE 7+
|
||||||
Chrome
|
Chrome
|
||||||
Safari
|
Safari
|
||||||
|
|
||||||
|
15
extras/scripts/create_local_settings.py
Normal file
15
extras/scripts/create_local_settings.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
script_path = os.path.realpath(os.path.dirname(__file__))
|
||||||
|
sys.path.append(os.path.join(script_path, '..', '..'))
|
||||||
|
|
||||||
|
from openslides.main import create_settings
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
cwd = os.getcwd()
|
||||||
|
create_settings(os.path.join(cwd, 'settings.py'),
|
||||||
|
os.path.join(cwd, 'database.sqlite'))
|
@ -6,16 +6,13 @@ How to create a new portable Windows distribution of OpenSlides:
|
|||||||
|
|
||||||
easy_install -Z django django-mptt reportlab pil
|
easy_install -Z django django-mptt reportlab pil
|
||||||
|
|
||||||
2.) Install OpenSlides by running python setup.py install in the top directory
|
2.) Run in the main directory of the OpenSlides checkout:
|
||||||
of OpenSlides.
|
|
||||||
NOTE: This step must be repeated whenever you make changes to OpenSlides
|
|
||||||
|
|
||||||
3.) In the main directory of the OpenSlides checkout execute:
|
python extras\win32-portable\prepare_portable.py
|
||||||
|
|
||||||
python extras/win32-portable/prepare_portable.py
|
3.) The portable OpenSlides distribution is now ready as a zip archive
|
||||||
|
in the 'dist' directory
|
||||||
|
|
||||||
4.) The portable OpenSlides distribution is now ready as a zip in the
|
|
||||||
'dist/' directory
|
|
||||||
|
|
||||||
Note: Creating the portable Windows distribution of OpenSlides is not possible,
|
NOTE: Creating the portable Windows distribution of OpenSlides is not possible,
|
||||||
if Python is installed in 64-bit version.
|
if Python is installed in 64-bit version.
|
||||||
|
@ -18,8 +18,6 @@ import zipfile
|
|||||||
import distutils.ccompiler
|
import distutils.ccompiler
|
||||||
import distutils.sysconfig
|
import distutils.sysconfig
|
||||||
|
|
||||||
from contextlib import nested
|
|
||||||
|
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
|
||||||
sys.path.insert(0, os.getcwd())
|
sys.path.insert(0, os.getcwd())
|
||||||
@ -80,10 +78,7 @@ SITE_PACKAGES = {
|
|||||||
"pil": {
|
"pil": {
|
||||||
# NOTE: PIL is a special case, see copy_pil
|
# NOTE: PIL is a special case, see copy_pil
|
||||||
"copy": [],
|
"copy": [],
|
||||||
},
|
}
|
||||||
"openslides": {
|
|
||||||
"copy" : ["openslides"],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PY_DLLS = [
|
PY_DLLS = [
|
||||||
@ -92,6 +87,7 @@ PY_DLLS = [
|
|||||||
"_sqlite3.pyd",
|
"_sqlite3.pyd",
|
||||||
"_socket.pyd",
|
"_socket.pyd",
|
||||||
"select.pyd",
|
"select.pyd",
|
||||||
|
"_ctypes.pyd",
|
||||||
]
|
]
|
||||||
|
|
||||||
MSVCR_PUBLIC_KEY = "1fc8b3b9a1e18e3b"
|
MSVCR_PUBLIC_KEY = "1fc8b3b9a1e18e3b"
|
||||||
@ -297,9 +293,13 @@ def main():
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
os.makedirs(odir)
|
os.makedirs(odir)
|
||||||
|
out_site_packages = os.path.join(odir, "site-packages")
|
||||||
|
|
||||||
collect_lib(libdir, odir)
|
collect_lib(libdir, odir)
|
||||||
collect_site_packages(sitedir, os.path.join(odir, "site-packages"))
|
collect_site_packages(sitedir, out_site_packages)
|
||||||
|
|
||||||
|
exclude = get_pkg_exclude("openslides")
|
||||||
|
copy_dir_exclude(exclude, ".", "openslides", out_site_packages)
|
||||||
|
|
||||||
if not compile_openslides_launcher():
|
if not compile_openslides_launcher():
|
||||||
sys.stdout.write("Using prebuild openslides.exe\n")
|
sys.stdout.write("Using prebuild openslides.exe\n")
|
||||||
@ -307,8 +307,8 @@ def main():
|
|||||||
shutil.copyfile("extras/win32-portable/openslides.exe",
|
shutil.copyfile("extras/win32-portable/openslides.exe",
|
||||||
os.path.join(odir, "openslides.exe"))
|
os.path.join(odir, "openslides.exe"))
|
||||||
|
|
||||||
shutil.copyfile("initial_data.json",
|
shutil.copyfile("openslides/participant/fixtures/groups_de.json",
|
||||||
os.path.join(odir, "initial_data.json"))
|
os.path.join(odir, "groups_de.json"))
|
||||||
|
|
||||||
copy_dlls(odir)
|
copy_dlls(odir)
|
||||||
copy_msvcr(odir)
|
copy_msvcr(odir)
|
||||||
|
15
manage.py
15
manage.py
@ -7,14 +7,11 @@
|
|||||||
:license: GNU GPL, see LICENSE for more details.
|
:license: GNU GPL, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.core.management import execute_manager
|
import os, sys
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
try:
|
from openslides.main import get_user_config_path, setup_django_environment
|
||||||
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__":
|
if __name__ == "__main__":
|
||||||
execute_manager(settings)
|
setup_django_environment(
|
||||||
|
get_user_config_path('openslides', 'settings.py'))
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
@ -5,79 +5,47 @@
|
|||||||
:license: GNU GPL, see LICENSE for more details.
|
:license: GNU GPL, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
VERSION = (1, 2, 0, 'final', 1)
|
VERSION = (1, 4, 0, 'alpha', 0) # During development it is the next release
|
||||||
|
RELEASE = False
|
||||||
|
|
||||||
def get_version(version=None):
|
|
||||||
"""Derives a PEP386-compliant version number from VERSION."""
|
def get_version(version=None, release=None):
|
||||||
# TODO: Get the Version Hash from GIT.
|
"""
|
||||||
|
Derives a PEP386-compliant version number from VERSION. Adds id of
|
||||||
|
the current git commit.
|
||||||
|
"""
|
||||||
if version is None:
|
if version is None:
|
||||||
version = VERSION
|
version = VERSION
|
||||||
|
if release is None:
|
||||||
|
release = RELEASE
|
||||||
assert len(version) == 5
|
assert len(version) == 5
|
||||||
assert version[3] in ('alpha', 'beta', 'rc', 'final')
|
assert version[3] in ('alpha', 'beta', 'rc', 'final')
|
||||||
|
|
||||||
# Now build the two parts of the version number:
|
# Now build the two parts of the version number:
|
||||||
# main = X.Y[.Z]
|
# main = X.Y[.Z]
|
||||||
# sub = .devN - for pre-alpha releases
|
# sub = {a|b|c}N for alpha, beta and rc releases
|
||||||
# | {a|b|c}N - for alpha, beta and rc releases
|
# Add '-dev', if it is not a release commit
|
||||||
|
main_parts = 2 if version[2] == 0 else 3
|
||||||
parts = 2 if version[2] == 0 else 3
|
main = '.'.join(str(x) for x in version[:main_parts])
|
||||||
main = '.'.join(str(x) for x in version[:parts])
|
if version[3] != 'final':
|
||||||
|
mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'c'}
|
||||||
sub = ''
|
sub = mapping[version[3]] + str(version[4])
|
||||||
if version[3] == 'alpha' and version[4] == 0:
|
|
||||||
mercurial_version = hg_version()
|
|
||||||
if mercurial_version != 'unknown':
|
|
||||||
sub = '.dev%s' % mercurial_version
|
|
||||||
else:
|
else:
|
||||||
sub = '.dev'
|
sub = ''
|
||||||
|
if not release:
|
||||||
elif version[3] != 'final':
|
sub += '-dev'
|
||||||
sub = "-" + version[3] + str(version[4])
|
|
||||||
|
|
||||||
return main + sub
|
return main + sub
|
||||||
|
|
||||||
|
|
||||||
def hg_version():
|
def get_git_commit_id():
|
||||||
import socket
|
"""
|
||||||
import os
|
Catches the commit id of the git head.
|
||||||
import sys
|
"""
|
||||||
from os.path import realpath, join, dirname
|
|
||||||
try:
|
try:
|
||||||
from mercurial import ui as hgui
|
git_head = open('.git/HEAD', 'r').read().rstrip()
|
||||||
from mercurial.localrepo import localrepository
|
if git_head[:5] == 'ref: ':
|
||||||
from mercurial.node import short as shorthex
|
git_commit_id = open('.git/%s' % git_head[5:], 'r').read().rstrip()
|
||||||
from mercurial.error import RepoError
|
|
||||||
nomercurial = False
|
|
||||||
except ImportError:
|
|
||||||
return 'unknown'
|
|
||||||
|
|
||||||
os.environ['HGRCPATH'] = ''
|
|
||||||
conts = realpath(join(dirname(__file__)))
|
|
||||||
try:
|
|
||||||
ui = hgui.ui()
|
|
||||||
repository = localrepository(ui, join(conts, '..'))
|
|
||||||
ctx = repository['.']
|
|
||||||
if ctx.tags() and ctx.tags() != ['tip']:
|
|
||||||
version = ' '.join(ctx.tags())
|
|
||||||
else:
|
else:
|
||||||
version = '%(num)s:%(id)s' % {
|
git_commit_id = git_head
|
||||||
'num': ctx.rev(), 'id': shorthex(ctx.node())
|
except IOError:
|
||||||
}
|
git_commit_id = 'unknown'
|
||||||
except TypeError:
|
return str(git_commit_id)
|
||||||
version = '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 version
|
|
||||||
|
|
||||||
|
|
||||||
## import os, site
|
|
||||||
##
|
|
||||||
## SITE_ROOT = os.path.realpath(os.path.dirname(__file__))
|
|
||||||
## site.addsitedir(SITE_ROOT)
|
|
||||||
|
@ -24,8 +24,8 @@ class ItemForm(forms.ModelForm, CssClassMixin):
|
|||||||
"""
|
"""
|
||||||
Form to create of update an item.
|
Form to create of update an item.
|
||||||
"""
|
"""
|
||||||
parent = TreeNodeChoiceField(queryset=Item.objects.all(),
|
parent = TreeNodeChoiceField(
|
||||||
label=_("Parent item"), required=False)
|
queryset=Item.objects.all(), label=_("Parent item"), required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Item
|
model = Item
|
||||||
|
@ -10,12 +10,6 @@
|
|||||||
:license: GNU GPL, see LICENSE for more details.
|
:license: GNU GPL, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
|
||||||
import json
|
|
||||||
except ImportError:
|
|
||||||
# for python 2.5 support
|
|
||||||
import simplejson as json
|
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.utils.translation import ugettext_lazy as _, ugettext_noop, ugettext
|
from django.utils.translation import ugettext_lazy as _, ugettext_noop, ugettext
|
||||||
@ -23,11 +17,9 @@ from django.utils.translation import ugettext_lazy as _, ugettext_noop, ugettext
|
|||||||
from mptt.models import MPTTModel, TreeForeignKey
|
from mptt.models import MPTTModel, TreeForeignKey
|
||||||
|
|
||||||
from openslides.config.models import config
|
from openslides.config.models import config
|
||||||
|
|
||||||
from openslides.projector.projector import SlideMixin
|
from openslides.projector.projector import SlideMixin
|
||||||
from openslides.projector.api import (register_slidemodel, get_slide_from_sid,
|
from openslides.projector.api import (
|
||||||
register_slidefunc, split_sid)
|
register_slidemodel, get_slide_from_sid, register_slidefunc)
|
||||||
|
|
||||||
from openslides.agenda.slides import agenda_show
|
from openslides.agenda.slides import agenda_show
|
||||||
|
|
||||||
|
|
||||||
@ -84,7 +76,6 @@ class Item(MPTTModel, SlideMixin):
|
|||||||
return self.title
|
return self.title
|
||||||
return self.get_related_slide().get_agenda_title()
|
return self.get_related_slide().get_agenda_title()
|
||||||
|
|
||||||
|
|
||||||
def get_title_supplement(self):
|
def get_title_supplement(self):
|
||||||
"""
|
"""
|
||||||
return a supplement for the title.
|
return a supplement for the title.
|
||||||
|
@ -24,7 +24,7 @@ function hideClosedSlides(hide) {
|
|||||||
hideLine($(this));
|
hideLine($(this));
|
||||||
});
|
});
|
||||||
hidden = $('#menu-overview tr:hidden').size();
|
hidden = $('#menu-overview tr:hidden').size();
|
||||||
$('#hiddencount').text(', davon ' + hidden + ' verborgen.');
|
$('#hiddencount').text(interpolate(gettext(', of which %s are hidden.'), [hidden]));
|
||||||
} else {
|
} else {
|
||||||
$('#menu-overview tr').show();
|
$('#menu-overview tr').show();
|
||||||
$('#hidelink').attr('title','hide');
|
$('#hidelink').attr('title','hide');
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
"""
|
"""
|
||||||
from reportlab.platypus import Paragraph
|
from reportlab.platypus import Paragraph
|
||||||
|
|
||||||
from django.core.context_processors import csrf
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
@ -20,18 +19,15 @@ from django.utils.translation import ugettext as _, ugettext_lazy
|
|||||||
from django.views.generic.detail import SingleObjectMixin
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
|
|
||||||
from openslides.utils.pdf import stylesheet
|
from openslides.utils.pdf import stylesheet
|
||||||
from openslides.utils.views import (TemplateView, RedirectView, UpdateView,
|
from openslides.utils.views import (
|
||||||
CreateView, DeleteView, PDFView, DetailView)
|
TemplateView, RedirectView, UpdateView, CreateView, DeleteView, PDFView,
|
||||||
|
DetailView)
|
||||||
from openslides.utils.template import Tab
|
from openslides.utils.template import Tab
|
||||||
from openslides.utils.utils import html_strong
|
from openslides.utils.utils import html_strong
|
||||||
|
|
||||||
from openslides.config.models import config
|
|
||||||
|
|
||||||
from openslides.projector.api import get_active_slide
|
from openslides.projector.api import get_active_slide
|
||||||
from openslides.projector.projector import Widget, SLIDE
|
from openslides.projector.projector import Widget, SLIDE
|
||||||
|
from .models import Item
|
||||||
from openslides.agenda.models import Item
|
from .forms import ItemOrderForm, ItemForm
|
||||||
from openslides.agenda.forms import ItemOrderForm, ItemForm
|
|
||||||
|
|
||||||
|
|
||||||
class Overview(TemplateView):
|
class Overview(TemplateView):
|
||||||
@ -53,7 +49,8 @@ class Overview(TemplateView):
|
|||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
context = self.get_context_data(**kwargs)
|
context = self.get_context_data(**kwargs)
|
||||||
if not request.user.has_perm('agenda.can_manage_agenda'):
|
if not request.user.has_perm('agenda.can_manage_agenda'):
|
||||||
messages.error(request,
|
messages.error(
|
||||||
|
request,
|
||||||
_('You are not authorized to manage the agenda.'))
|
_('You are not authorized to manage the agenda.'))
|
||||||
return self.render_to_response(context)
|
return self.render_to_response(context)
|
||||||
transaction.commit()
|
transaction.commit()
|
||||||
@ -69,8 +66,8 @@ class Overview(TemplateView):
|
|||||||
Model.save(item)
|
Model.save(item)
|
||||||
else:
|
else:
|
||||||
transaction.rollback()
|
transaction.rollback()
|
||||||
messages.error(request,
|
messages.error(
|
||||||
_('Errors when reordering of the agenda'))
|
request, _('Errors when reordering of the agenda'))
|
||||||
return self.render_to_response(context)
|
return self.render_to_response(context)
|
||||||
Item.objects.rebuild()
|
Item.objects.rebuild()
|
||||||
# TODO: assure, that it is a valid tree
|
# TODO: assure, that it is a valid tree
|
||||||
@ -130,8 +127,8 @@ class ItemUpdate(UpdateView):
|
|||||||
apply_url = 'item_edit'
|
apply_url = 'item_edit'
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
messages.success(self.request,
|
messages.success(
|
||||||
_("Item %s was successfully modified.") \
|
self.request, _("Item %s was successfully modified.")
|
||||||
% html_strong(self.request.POST['title']))
|
% html_strong(self.request.POST['title']))
|
||||||
if 'apply' in self.request.POST:
|
if 'apply' in self.request.POST:
|
||||||
return ''
|
return ''
|
||||||
@ -151,8 +148,8 @@ class ItemCreate(CreateView):
|
|||||||
apply_url = 'item_edit'
|
apply_url = 'item_edit'
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
messages.success(self.request,
|
messages.success(
|
||||||
_("Item %s was successfully created.") \
|
self.request, _("Item %s was successfully created.")
|
||||||
% html_strong(self.request.POST['title']))
|
% html_strong(self.request.POST['title']))
|
||||||
if 'apply' in self.request.POST:
|
if 'apply' in self.request.POST:
|
||||||
return reverse(self.get_apply_url(), args=[self.object.id])
|
return reverse(self.get_apply_url(), args=[self.object.id])
|
||||||
@ -176,13 +173,13 @@ class ItemDelete(DeleteView):
|
|||||||
def pre_post_redirect(self, request, *args, **kwargs):
|
def pre_post_redirect(self, request, *args, **kwargs):
|
||||||
if self.get_answer() == 'all':
|
if self.get_answer() == 'all':
|
||||||
self.object.delete(with_children=True)
|
self.object.delete(with_children=True)
|
||||||
messages.success(request,
|
messages.success(
|
||||||
_("Item %s and his children were successfully deleted.") \
|
request, _("Item %s and his children were successfully deleted.")
|
||||||
% html_strong(self.object))
|
% html_strong(self.object))
|
||||||
elif self.get_answer() == 'yes':
|
elif self.get_answer() == 'yes':
|
||||||
self.object.delete(with_children=False)
|
self.object.delete(with_children=False)
|
||||||
messages.success(request,
|
messages.success(
|
||||||
_("Item %s was successfully deleted.") \
|
request, _("Item %s was successfully deleted.")
|
||||||
% html_strong(self.object))
|
% html_strong(self.object))
|
||||||
|
|
||||||
|
|
||||||
@ -199,7 +196,8 @@ class AgendaPDF(PDFView):
|
|||||||
ancestors = item.get_ancestors()
|
ancestors = item.get_ancestors()
|
||||||
if ancestors:
|
if ancestors:
|
||||||
space = " " * 6 * ancestors.count()
|
space = " " * 6 * ancestors.count()
|
||||||
story.append(Paragraph("%s%s" % (space, item.get_title()),
|
story.append(Paragraph(
|
||||||
|
"%s%s" % (space, item.get_title()),
|
||||||
stylesheet['Subitem']))
|
stylesheet['Subitem']))
|
||||||
else:
|
else:
|
||||||
story.append(Paragraph(item.get_title(), stylesheet['Item']))
|
story.append(Paragraph(item.get_title(), stylesheet['Item']))
|
||||||
@ -214,10 +212,9 @@ def register_tab(request):
|
|||||||
title=_('Agenda'),
|
title=_('Agenda'),
|
||||||
app='agenda',
|
app='agenda',
|
||||||
url=reverse('item_overview'),
|
url=reverse('item_overview'),
|
||||||
permission=request.user.has_perm('agenda.can_see_agenda')
|
permission=(request.user.has_perm('agenda.can_see_agenda') or
|
||||||
or request.user.has_perm('agenda.can_manage_agenda'),
|
request.user.has_perm('agenda.can_manage_agenda')),
|
||||||
selected=selected,
|
selected=selected)
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_widgets(request):
|
def get_widgets(request):
|
||||||
@ -227,8 +224,9 @@ def get_widgets(request):
|
|||||||
return [
|
return [
|
||||||
Widget(
|
Widget(
|
||||||
name='agenda',
|
name='agenda',
|
||||||
|
display_name=_('Agenda'),
|
||||||
template='agenda/widget.html',
|
template='agenda/widget.html',
|
||||||
context={
|
context={
|
||||||
'agenda': SLIDE['agenda'],
|
'agenda': SLIDE['agenda'],
|
||||||
'items': Item.objects.all()},
|
'items': Item.objects.all()},
|
||||||
permission_required='agenda.can_manage_agenda')]
|
permission_required='projector.can_manage_projector')]
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
{% load staticfiles %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load tags %}
|
|
||||||
|
|
||||||
<ul style="line-height: 180%">
|
|
||||||
{% for application in applications %}
|
|
||||||
<li class="{% if application.active %}activeline{% endif %}">
|
|
||||||
<a href="{% url projector_activate_slide application.sid %}" class="activate_link {% if application.active %}active{% endif %}">
|
|
||||||
<div></div>
|
|
||||||
</a>
|
|
||||||
<a href="{% model_url application 'delete' %}" title="{% trans 'Delete' %}" class="icon delete right">
|
|
||||||
<span></span>
|
|
||||||
</a>
|
|
||||||
<a href="{% model_url application 'edit' %}" title="{% trans 'Edit' %}" class="icon edit right">
|
|
||||||
<span></span>
|
|
||||||
</a>
|
|
||||||
<a href="{% url projctor_preview_slide application.sid %}" title="{% trans 'Preview' %}" class="icon preview right">
|
|
||||||
<span></span>
|
|
||||||
</a>
|
|
||||||
<a href="{% model_url application 'view' %}">
|
|
||||||
{{ application.public_version.title }}
|
|
||||||
</a>
|
|
||||||
({% trans "motion" %}
|
|
||||||
{% if application.number %}
|
|
||||||
{{ application.number }})
|
|
||||||
{% else %}
|
|
||||||
<i>[{% trans "no number" %}]</i>)
|
|
||||||
{% endif %}
|
|
||||||
</li>
|
|
||||||
{% empty %}
|
|
||||||
<li>{% trans 'No motion available.' %}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
|
|
@ -1,141 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
openslides.application.urls
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
URL list for the application app.
|
|
||||||
|
|
||||||
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
|
|
||||||
:license: GNU GPL, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from django.conf.urls.defaults import url, patterns
|
|
||||||
|
|
||||||
from openslides.application.views import (ApplicationDelete, ViewPoll,
|
|
||||||
ApplicationPDF, ApplicationPollPDF, CreateAgendaItem)
|
|
||||||
|
|
||||||
urlpatterns = patterns('openslides.application.views',
|
|
||||||
url(r'^$',
|
|
||||||
'overview',
|
|
||||||
name='application_overview',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^(?P<application_id>\d+)/$',
|
|
||||||
'view',
|
|
||||||
name='application_view',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^(?P<application_id>\d+)/agenda/$',
|
|
||||||
CreateAgendaItem.as_view(),
|
|
||||||
name='application_create_agenda',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^(?P<application_id>\d+)/newest/$',
|
|
||||||
'view',
|
|
||||||
{'newest': True},
|
|
||||||
name='application_view_newest',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^new/$',
|
|
||||||
'edit',
|
|
||||||
name='application_new',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^import/$',
|
|
||||||
'application_import',
|
|
||||||
name='application_import',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^(?P<application_id>\d+)/edit/$',
|
|
||||||
'edit',
|
|
||||||
name='application_edit',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^(?P<application_id>\d+)/del/$',
|
|
||||||
ApplicationDelete.as_view(),
|
|
||||||
name='application_delete',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^del/$',
|
|
||||||
ApplicationDelete.as_view(),
|
|
||||||
{ 'application_id' : None , 'application_ids' : None },
|
|
||||||
name='application_delete',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^(?P<application_id>\d+)/setnumber/$',
|
|
||||||
'set_number',
|
|
||||||
name='application_set_number',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^(?P<application_id>\d+)/setstatus/(?P<status>[a-z]{3})/$',
|
|
||||||
'set_status',
|
|
||||||
name='application_set_status',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^(?P<application_id>\d+)/permit/$',
|
|
||||||
'permit',
|
|
||||||
name='application_permit',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^version/(?P<aversion_id>\d+)/permit/$',
|
|
||||||
'permit_version',
|
|
||||||
name='application_version_permit',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^version/(?P<aversion_id>\d+)/reject/$',
|
|
||||||
'reject_version',
|
|
||||||
name='application_version_reject',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^(?P<application_id>\d+)/notpermit/$',
|
|
||||||
'notpermit',
|
|
||||||
name='application_notpermit',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^(?P<application_id>\d+)/reset/$',
|
|
||||||
'reset',
|
|
||||||
name='application_reset',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^(?P<application_id>\d+)/support/$',
|
|
||||||
'support',
|
|
||||||
name='application_support',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^(?P<application_id>\d+)/unsupport/$',
|
|
||||||
'unsupport',
|
|
||||||
name='application_unsupport',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^(?P<application_id>\d+)/gen_poll/$',
|
|
||||||
'gen_poll',
|
|
||||||
name='application_gen_poll',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^print/$',
|
|
||||||
ApplicationPDF.as_view(),
|
|
||||||
{'application_id': None},
|
|
||||||
name='print_applications',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^(?P<application_id>\d+)/print/$',
|
|
||||||
ApplicationPDF.as_view(),
|
|
||||||
name='print_application',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^poll/(?P<poll_id>\d+)/print/$',
|
|
||||||
ApplicationPollPDF.as_view(),
|
|
||||||
name='print_application_poll',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^poll/(?P<poll_id>\d+)/$',
|
|
||||||
ViewPoll.as_view(),
|
|
||||||
name='application_poll_view',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^poll/(?P<poll_id>\d+)/del/$',
|
|
||||||
'delete_poll',
|
|
||||||
name='application_poll_delete',
|
|
||||||
),
|
|
||||||
)
|
|
@ -1,925 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
openslides.application.views
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Views for the application app.
|
|
||||||
|
|
||||||
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
|
|
||||||
:license: GNU GPL, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# for python 2.5 support
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
import csv
|
|
||||||
import os
|
|
||||||
|
|
||||||
try:
|
|
||||||
from urlparse import parse_qs
|
|
||||||
except ImportError: # python <= 2.5
|
|
||||||
from cgi import parse_qs
|
|
||||||
|
|
||||||
from reportlab.lib import colors
|
|
||||||
from reportlab.lib.units import cm
|
|
||||||
from reportlab.platypus import (SimpleDocTemplate, PageBreak, Paragraph, Spacer,
|
|
||||||
Table, TableStyle)
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.core.context_processors import csrf
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.db import transaction
|
|
||||||
from django.shortcuts import redirect
|
|
||||||
from django.utils.translation import ugettext as _, ungettext
|
|
||||||
|
|
||||||
from openslides.utils import csv_ext
|
|
||||||
from openslides.utils.pdf import stylesheet
|
|
||||||
from openslides.utils.template import Tab
|
|
||||||
from openslides.utils.utils import (template, permission_required,
|
|
||||||
del_confirm_form, gen_confirm_form)
|
|
||||||
from openslides.utils.views import PDFView, RedirectView, DeleteView, FormView
|
|
||||||
from openslides.utils.person import get_person
|
|
||||||
|
|
||||||
from openslides.config.models import config
|
|
||||||
|
|
||||||
from openslides.projector.projector import Widget
|
|
||||||
|
|
||||||
from openslides.poll.views import PollFormView
|
|
||||||
|
|
||||||
from openslides.participant.api import gen_username, gen_password
|
|
||||||
from openslides.participant.models import User
|
|
||||||
|
|
||||||
from openslides.agenda.models import Item
|
|
||||||
|
|
||||||
from openslides.application.models import Application, AVersion, ApplicationPoll
|
|
||||||
from openslides.application.forms import (ApplicationForm,
|
|
||||||
ApplicationFormTrivialChanges, ApplicationManagerForm,
|
|
||||||
ApplicationManagerFormSupporter, ApplicationImportForm, ConfigForm)
|
|
||||||
|
|
||||||
|
|
||||||
@permission_required('application.can_see_application')
|
|
||||||
@template('application/overview.html')
|
|
||||||
def overview(request):
|
|
||||||
"""
|
|
||||||
View all applications
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
sortfilter = parse_qs(request.COOKIES['votecollector_sortfilter'])
|
|
||||||
for value in sortfilter:
|
|
||||||
sortfilter[value] = sortfilter[value][0]
|
|
||||||
except KeyError:
|
|
||||||
sortfilter = {}
|
|
||||||
|
|
||||||
for value in [u'sort', u'reverse', u'number', u'status', u'needsup', u'statusvalue']:
|
|
||||||
if value in request.REQUEST:
|
|
||||||
if request.REQUEST[value] == '0':
|
|
||||||
try:
|
|
||||||
del sortfilter[value]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
sortfilter[value] = request.REQUEST[value]
|
|
||||||
|
|
||||||
query = Application.objects.all()
|
|
||||||
if 'number' in sortfilter:
|
|
||||||
query = query.filter(number=None)
|
|
||||||
if 'status' in sortfilter:
|
|
||||||
if 'statusvalue' in sortfilter and 'on' in sortfilter['status']:
|
|
||||||
query = query.filter(status__iexact=sortfilter['statusvalue'])
|
|
||||||
|
|
||||||
if 'sort' in sortfilter:
|
|
||||||
if sortfilter['sort'] == 'title':
|
|
||||||
sort = 'aversion__title'
|
|
||||||
elif sortfilter['sort'] == 'time':
|
|
||||||
sort = 'aversion__time'
|
|
||||||
else:
|
|
||||||
sort = sortfilter['sort']
|
|
||||||
query = query.order_by(sort)
|
|
||||||
|
|
||||||
if sort.startswith('aversion_'):
|
|
||||||
# limit result to last version of an application
|
|
||||||
query = query.filter(aversion__id__in=[x.last_version.id for x in Application.objects.all()])
|
|
||||||
|
|
||||||
if 'reverse' in sortfilter:
|
|
||||||
query = query.reverse()
|
|
||||||
|
|
||||||
# todo: rewrite this with a .filter()
|
|
||||||
if 'needsup' in sortfilter:
|
|
||||||
applications = []
|
|
||||||
for application in query.all():
|
|
||||||
if not application.enough_supporters:
|
|
||||||
applications.append(application)
|
|
||||||
else:
|
|
||||||
applications = query
|
|
||||||
|
|
||||||
if type(applications) is not list:
|
|
||||||
applications = list(query.all())
|
|
||||||
|
|
||||||
# not the most efficient way to do this but 'get_allowed_actions'
|
|
||||||
# is not callable from within djangos templates..
|
|
||||||
for (i, application) in enumerate(applications):
|
|
||||||
try:
|
|
||||||
applications[i] = {
|
|
||||||
'actions' : application.get_allowed_actions(request.user),
|
|
||||||
'application' : application
|
|
||||||
}
|
|
||||||
except:
|
|
||||||
# todo: except what?
|
|
||||||
applications[i] = {
|
|
||||||
'actions' : [],
|
|
||||||
'application' : application
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
'applications': applications,
|
|
||||||
'min_supporters': int(config['application_min_supporters']),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@permission_required('application.can_see_application')
|
|
||||||
@template('application/view.html')
|
|
||||||
def view(request, application_id, newest=False):
|
|
||||||
"""
|
|
||||||
View one application.
|
|
||||||
"""
|
|
||||||
application = Application.objects.get(pk=application_id)
|
|
||||||
if newest:
|
|
||||||
version = application.last_version
|
|
||||||
else:
|
|
||||||
version = application.public_version
|
|
||||||
revisions = application.versions
|
|
||||||
actions = application.get_allowed_actions(user=request.user)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'application': application,
|
|
||||||
'revisions': revisions,
|
|
||||||
'actions': actions,
|
|
||||||
'min_supporters': int(config['application_min_supporters']),
|
|
||||||
'version': version,
|
|
||||||
#'results': application.results
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@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_create_application'):
|
|
||||||
messages.error(request, _("You have not the necessary rights to create or edit motions."))
|
|
||||||
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 motion. You are not the submitter."))
|
|
||||||
return redirect(reverse('application_view', args=[application.id]))
|
|
||||||
actions = application.get_allowed_actions(user=request.user)
|
|
||||||
else:
|
|
||||||
application = None
|
|
||||||
actions = None
|
|
||||||
|
|
||||||
formclass = ApplicationFormTrivialChanges \
|
|
||||||
if config['application_allow_trivial_change'] and application_id \
|
|
||||||
else ApplicationForm
|
|
||||||
|
|
||||||
managerformclass = ApplicationManagerFormSupporter \
|
|
||||||
if config['application_min_supporters'] \
|
|
||||||
else ApplicationManagerForm
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
|
||||||
dataform = formclass(request.POST, prefix="data")
|
|
||||||
valid = dataform.is_valid()
|
|
||||||
|
|
||||||
if is_manager:
|
|
||||||
managerform = managerformclass(request.POST,
|
|
||||||
instance=application,
|
|
||||||
prefix="manager")
|
|
||||||
valid = valid and managerform.is_valid()
|
|
||||||
else:
|
|
||||||
managerform = None
|
|
||||||
|
|
||||||
if valid:
|
|
||||||
del_supporters = True
|
|
||||||
if is_manager:
|
|
||||||
if application: # Edit application
|
|
||||||
original_supporters = list(application.supporters)
|
|
||||||
else:
|
|
||||||
original_supporters = []
|
|
||||||
application = managerform.save(commit=False)
|
|
||||||
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']
|
|
||||||
|
|
||||||
try:
|
|
||||||
trivial_change = config['application_allow_trivial_change'] \
|
|
||||||
and dataform.cleaned_data['trivial_change']
|
|
||||||
except KeyError:
|
|
||||||
trivial_change = False
|
|
||||||
application.save(request.user, trivial_change=trivial_change)
|
|
||||||
if is_manager:
|
|
||||||
try:
|
|
||||||
new_supporters = set(managerform.cleaned_data['supporter'])
|
|
||||||
except KeyError:
|
|
||||||
# The managerform has no field for the supporters
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
old_supporters = set(application.supporters)
|
|
||||||
# add new supporters
|
|
||||||
for supporter in new_supporters.difference(old_supporters):
|
|
||||||
application.support(supporter)
|
|
||||||
# remove old supporters
|
|
||||||
for supporter in old_supporters.difference(new_supporters):
|
|
||||||
application.unsupport(supporter)
|
|
||||||
|
|
||||||
if application_id is None:
|
|
||||||
messages.success(request, _('New motion was successfully created.'))
|
|
||||||
else:
|
|
||||||
messages.success(request, _('Motion was successfully modified.'))
|
|
||||||
|
|
||||||
if not 'apply' in request.POST:
|
|
||||||
return redirect(reverse('application_view', args=[application.id]))
|
|
||||||
if application_id is None:
|
|
||||||
return redirect(reverse('application_edit', args=[application.id]))
|
|
||||||
else:
|
|
||||||
messages.error(request, _('Please check the form for errors.'))
|
|
||||||
else:
|
|
||||||
if application_id is None:
|
|
||||||
initial = {'text': config['application_preamble']}
|
|
||||||
else:
|
|
||||||
if application.status == "pub" and application.supporters:
|
|
||||||
if request.user.has_perm('application.can_manage_application'):
|
|
||||||
messages.warning(request, _("Attention: Do you really want to edit this motion? The supporters will <b>not</b> be removed automatically because you can manage motions. Please check if the supports are valid after your changing!"))
|
|
||||||
else:
|
|
||||||
messages.warning(request, _("Attention: Do you really want to edit this motion? 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 = formclass(initial=initial, prefix="data")
|
|
||||||
if is_manager:
|
|
||||||
if application_id is None:
|
|
||||||
initial = {'submitter': request.user.person_id}
|
|
||||||
else:
|
|
||||||
initial = {'submitter': application.submitter.person_id,
|
|
||||||
'supporter': [supporter.person_id for supporter in application.supporters]}
|
|
||||||
managerform = managerformclass(initial=initial,
|
|
||||||
instance=application, prefix="manager")
|
|
||||||
else:
|
|
||||||
managerform = None
|
|
||||||
return {
|
|
||||||
'form': dataform,
|
|
||||||
'managerform': managerform,
|
|
||||||
'application': application,
|
|
||||||
'actions': actions,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@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, _("Motion 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, _("Motion was successfully authorized."))
|
|
||||||
except Application.DoesNotExist:
|
|
||||||
pass
|
|
||||||
except NameError, e:
|
|
||||||
messages.error(request, e)
|
|
||||||
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, _("Motion was successfully rejected."))
|
|
||||||
except Application.DoesNotExist:
|
|
||||||
pass
|
|
||||||
except NameError, e:
|
|
||||||
messages.error(request, e)
|
|
||||||
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, _("Motion status was set to: <b>%s</b>.") % application.get_status_display())
|
|
||||||
except Application.DoesNotExist:
|
|
||||||
pass
|
|
||||||
except NameError, e:
|
|
||||||
messages.error(request, e)
|
|
||||||
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, _("Motion 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 motion 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 motion 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:
|
|
||||||
poll = Application.objects.get(pk=application_id).gen_poll(user=request.user)
|
|
||||||
messages.success(request, _("New vote was successfully created.") )
|
|
||||||
except Application.DoesNotExist:
|
|
||||||
pass # TODO: do not call poll after this excaption
|
|
||||||
return redirect(reverse('application_poll_view', args=[poll.id]))
|
|
||||||
|
|
||||||
|
|
||||||
@permission_required('application.can_manage_application')
|
|
||||||
def delete_poll(request, poll_id):
|
|
||||||
"""
|
|
||||||
delete a poll from this application
|
|
||||||
"""
|
|
||||||
poll = ApplicationPoll.objects.get(pk=poll_id)
|
|
||||||
application = poll.application
|
|
||||||
count = application.polls.filter(id__lte=poll_id).count()
|
|
||||||
if request.method == 'POST':
|
|
||||||
poll.delete()
|
|
||||||
application.writelog(_("Poll deleted"), request.user)
|
|
||||||
messages.success(request, _('Poll was successfully deleted.'))
|
|
||||||
else:
|
|
||||||
del_confirm_form(request, poll, name=_("the %s. poll") % count, delete_link=reverse('application_poll_delete', args=[poll_id]))
|
|
||||||
return redirect(reverse('application_view', args=[application.id]))
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationDelete(DeleteView):
|
|
||||||
"""
|
|
||||||
Delete one or more Applications.
|
|
||||||
"""
|
|
||||||
permission_required = 'application.can_manage_application'
|
|
||||||
model = Application
|
|
||||||
url = 'application_overview'
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
self.applications = []
|
|
||||||
|
|
||||||
if self.kwargs.get('application_id', None):
|
|
||||||
try:
|
|
||||||
return Application.objects.get(id=int(self.kwargs['application_id']))
|
|
||||||
except Application.DoesNotExist:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if self.kwargs.get('application_ids', []):
|
|
||||||
for appid in self.kwargs['application_ids']:
|
|
||||||
try:
|
|
||||||
self.applications.append(Application.objects.get(id=int(appid)))
|
|
||||||
except Application.DoesNotExist:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if self.applications:
|
|
||||||
return self.applications[0]
|
|
||||||
return None
|
|
||||||
|
|
||||||
def pre_post_redirect(self, request, *args, **kwargs):
|
|
||||||
self.object = self.get_object()
|
|
||||||
|
|
||||||
if len(self.applications):
|
|
||||||
for application in self.applications:
|
|
||||||
if not 'delete' in application.get_allowed_actions(user=request.user):
|
|
||||||
messages.error(request, _("You can not delete motion <b>%s</b>.") % application)
|
|
||||||
continue
|
|
||||||
|
|
||||||
title = application.title
|
|
||||||
application.delete(force=True)
|
|
||||||
messages.success(request, _("Motion <b>%s</b> was successfully deleted.") % title)
|
|
||||||
|
|
||||||
elif self.object:
|
|
||||||
if not 'delete' in self.object.get_allowed_actions(user=request.user):
|
|
||||||
messages.error(request, _("You can not delete motion <b>%s</b>.") % self.object)
|
|
||||||
else:
|
|
||||||
title = self.object.title
|
|
||||||
self.object.delete(force=True)
|
|
||||||
messages.success(request, _("Motion <b>%s</b> was successfully deleted.") % title)
|
|
||||||
else:
|
|
||||||
messages.error(request, _("Invalid request"))
|
|
||||||
|
|
||||||
def gen_confirm_form(self, request, message, url):
|
|
||||||
formbase = '%s<form action="%s" method="post"><input type="hidden" value="%s" name="csrfmiddlewaretoken">' % (message, url, csrf(request)['csrf_token'])
|
|
||||||
|
|
||||||
if len(self.applications):
|
|
||||||
for application in self.applications:
|
|
||||||
formbase += '<input type="hidden" name="application_ids" value="%s">' % application.id
|
|
||||||
elif self.object:
|
|
||||||
formbase += '<input type="hidden" name="application_id" value="%s">' % self.object.id
|
|
||||||
|
|
||||||
formbase +='<input type="submit" value="%s" /> <input type="button" value="%s"></form>' % (_("Yes"), _("No"))
|
|
||||||
messages.warning(request, formbase)
|
|
||||||
|
|
||||||
|
|
||||||
def confirm_form(self, request, object, item=None):
|
|
||||||
self.object = self.get_object()
|
|
||||||
|
|
||||||
if len(self.applications):
|
|
||||||
self.gen_confirm_form(request, _('Do you really want to delete multiple motions?') % self.object.get_absolute_url('delete'))
|
|
||||||
else:
|
|
||||||
self.gen_confirm_form(request, _('Do you really want to delete <b>%s</b>?') % self.object, self.object.get_absolute_url('delete'))
|
|
||||||
|
|
||||||
|
|
||||||
class ViewPoll(PollFormView):
|
|
||||||
permission_required = 'application.can_manage_application'
|
|
||||||
poll_class = ApplicationPoll
|
|
||||||
template_name = 'application/poll_view.html'
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(ViewPoll, self).get_context_data(**kwargs)
|
|
||||||
self.application = self.poll.get_application()
|
|
||||||
context['application'] = self.application
|
|
||||||
context['ballot'] = self.poll.get_ballot()
|
|
||||||
context['actions'] = self.application.get_allowed_actions(user=self.request.user)
|
|
||||||
return context
|
|
||||||
|
|
||||||
def get_modelform_class(self):
|
|
||||||
cls = super(ViewPoll, self).get_modelform_class()
|
|
||||||
user = self.request.user
|
|
||||||
|
|
||||||
class ViewPollFormClass(cls):
|
|
||||||
def save(self, commit = True):
|
|
||||||
instance = super(ViewPollFormClass, self).save(commit)
|
|
||||||
application = instance.application
|
|
||||||
application.writelog(_("Poll was updated"), user)
|
|
||||||
return instance
|
|
||||||
|
|
||||||
return ViewPollFormClass
|
|
||||||
|
|
||||||
def get_success_url(self):
|
|
||||||
if not 'apply' in self.request.POST:
|
|
||||||
return reverse('application_view', args=[self.poll.application.id])
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
@permission_required('application.can_manage_application')
|
|
||||||
def permit_version(request, aversion_id):
|
|
||||||
aversion = AVersion.objects.get(pk=aversion_id)
|
|
||||||
application = aversion.application
|
|
||||||
if request.method == 'POST':
|
|
||||||
application.accept_version(aversion, user=request.user)
|
|
||||||
messages.success(request, _("Version <b>%s</b> accepted.") % (aversion.aid))
|
|
||||||
else:
|
|
||||||
gen_confirm_form(request, _('Do you really want to authorize version <b>%s</b>?') % aversion.aid, reverse('application_version_permit', args=[aversion.id]))
|
|
||||||
return redirect(reverse('application_view', args=[application.id]))
|
|
||||||
|
|
||||||
|
|
||||||
@permission_required('application.can_manage_application')
|
|
||||||
def reject_version(request, aversion_id):
|
|
||||||
aversion = AVersion.objects.get(pk=aversion_id)
|
|
||||||
application = aversion.application
|
|
||||||
if request.method == 'POST':
|
|
||||||
if application.reject_version(aversion, user=request.user):
|
|
||||||
messages.success(request, _("Version <b>%s</b> rejected.") % (aversion.aid))
|
|
||||||
else:
|
|
||||||
messages.error(request, _("ERROR by rejecting the version.") )
|
|
||||||
else:
|
|
||||||
gen_confirm_form(request, _('Do you really want to reject version <b>%s</b>?') % aversion.aid, reverse('application_version_reject', args=[aversion.id]))
|
|
||||||
return redirect(reverse('application_view', args=[application.id]))
|
|
||||||
|
|
||||||
|
|
||||||
@permission_required('application.can_manage_application')
|
|
||||||
@template('application/import.html')
|
|
||||||
def application_import(request):
|
|
||||||
if request.method == 'POST':
|
|
||||||
form = ApplicationImportForm(request.POST, request.FILES)
|
|
||||||
if form.is_valid():
|
|
||||||
import_permitted = form.cleaned_data['import_permitted']
|
|
||||||
try:
|
|
||||||
# check for valid encoding (will raise UnicodeDecodeError if not)
|
|
||||||
request.FILES['csvfile'].read().decode('utf-8')
|
|
||||||
request.FILES['csvfile'].seek(0)
|
|
||||||
|
|
||||||
users_generated = 0
|
|
||||||
applications_generated = 0
|
|
||||||
applications_modified = 0
|
|
||||||
with transaction.commit_on_success():
|
|
||||||
dialect = csv.Sniffer().sniff(request.FILES['csvfile'].readline())
|
|
||||||
dialect = csv_ext.patchup(dialect)
|
|
||||||
request.FILES['csvfile'].seek(0)
|
|
||||||
for (lno, line) in enumerate(csv.reader(request.FILES['csvfile'], dialect=dialect)):
|
|
||||||
# basic input verification
|
|
||||||
if lno < 1:
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
(number, title, text, reason, first_name, last_name) = line[:6]
|
|
||||||
except ValueError:
|
|
||||||
messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1))
|
|
||||||
continue
|
|
||||||
form = ApplicationForm({'title': title, 'text': text, 'reason': reason})
|
|
||||||
if not form.is_valid():
|
|
||||||
messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1))
|
|
||||||
continue
|
|
||||||
if number:
|
|
||||||
try:
|
|
||||||
number = abs(long(number))
|
|
||||||
if number < 1:
|
|
||||||
messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1))
|
|
||||||
continue
|
|
||||||
except ValueError:
|
|
||||||
messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1))
|
|
||||||
continue
|
|
||||||
# fetch existing users or create new users as needed
|
|
||||||
try:
|
|
||||||
user = User.objects.get(first_name=first_name, last_name=last_name)
|
|
||||||
except User.DoesNotExist:
|
|
||||||
user = None
|
|
||||||
if user is None:
|
|
||||||
user = User()
|
|
||||||
user.last_name = last_name
|
|
||||||
user.first_name = first_name
|
|
||||||
user.username = gen_username(first_name, last_name)
|
|
||||||
user.save()
|
|
||||||
profile = Profile()
|
|
||||||
profile.user = user
|
|
||||||
profile.group = ''
|
|
||||||
profile.committee = ''
|
|
||||||
profile.gender = 'none'
|
|
||||||
profile.type = 'guest'
|
|
||||||
profile.firstpassword = gen_password()
|
|
||||||
profile.user.set_password(profile.firstpassword)
|
|
||||||
profile.save()
|
|
||||||
users_generated += 1
|
|
||||||
# create / modify the application
|
|
||||||
application = None
|
|
||||||
if number:
|
|
||||||
try:
|
|
||||||
application = Application.objects.get(number=number)
|
|
||||||
applications_modified += 1
|
|
||||||
except Application.DoesNotExist:
|
|
||||||
application = None
|
|
||||||
if application is None:
|
|
||||||
application = Application(submitter=user)
|
|
||||||
if number:
|
|
||||||
application.number = number
|
|
||||||
applications_generated += 1
|
|
||||||
|
|
||||||
application.title = form.cleaned_data['title']
|
|
||||||
application.text = form.cleaned_data['text']
|
|
||||||
application.reason = form.cleaned_data['reason']
|
|
||||||
if import_permitted:
|
|
||||||
application.status = 'per'
|
|
||||||
|
|
||||||
application.save(user, trivial_change=True)
|
|
||||||
|
|
||||||
if applications_generated:
|
|
||||||
messages.success(request, ungettext('%d motion was successfully imported.',
|
|
||||||
'%d motions were successfully imported.', applications_generated) % applications_generated)
|
|
||||||
if applications_modified:
|
|
||||||
messages.success(request, ungettext('%d motion was successfully modified.',
|
|
||||||
'%d motions were successfully modified.', applications_modified) % applications_modified)
|
|
||||||
if users_generated:
|
|
||||||
messages.success(request, ungettext('%d new user was added.', '%d new users were added.', users_generated) % users_generated)
|
|
||||||
return redirect(reverse('application_overview'))
|
|
||||||
|
|
||||||
except csv.Error:
|
|
||||||
message.error(request, _('Import aborted because of severe errors in the input file.'))
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
messages.error(request, _('Import file has wrong character encoding, only UTF-8 is supported!'))
|
|
||||||
else:
|
|
||||||
messages.error(request, _('Please check the form for errors.'))
|
|
||||||
else:
|
|
||||||
messages.warning(request, _("Attention: Existing motions will be modified if you import new motions with the same number."))
|
|
||||||
messages.warning(request, _("Attention: Importing an motions without a number multiple times will create duplicates."))
|
|
||||||
form = ApplicationImportForm()
|
|
||||||
return {
|
|
||||||
'form': form,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class CreateAgendaItem(RedirectView):
|
|
||||||
permission_required = 'agenda.can_manage_agenda'
|
|
||||||
|
|
||||||
def pre_redirect(self, request, *args, **kwargs):
|
|
||||||
self.application = Application.objects.get(pk=kwargs['application_id'])
|
|
||||||
self.item = Item(related_sid=self.application.sid)
|
|
||||||
self.item.save()
|
|
||||||
|
|
||||||
def get_redirect_url(self, **kwargs):
|
|
||||||
return reverse('item_overview')
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationPDF(PDFView):
|
|
||||||
permission_required = 'application.can_see_application'
|
|
||||||
top_space = 0
|
|
||||||
|
|
||||||
def get_filename(self):
|
|
||||||
application_id = self.kwargs['application_id']
|
|
||||||
if application_id is None:
|
|
||||||
filename = _("Applications")
|
|
||||||
else:
|
|
||||||
application = Application.objects.get(id=application_id)
|
|
||||||
if application.number:
|
|
||||||
number = application.number
|
|
||||||
else:
|
|
||||||
number = ""
|
|
||||||
filename = u'%s%s' % (_("Application"), str(number))
|
|
||||||
return filename
|
|
||||||
|
|
||||||
def append_to_pdf(self, story):
|
|
||||||
application_id = self.kwargs['application_id']
|
|
||||||
if application_id is None: #print all applications
|
|
||||||
title = config["application_pdf_title"]
|
|
||||||
story.append(Paragraph(title, stylesheet['Heading1']))
|
|
||||||
preamble = config["application_pdf_preamble"]
|
|
||||||
if preamble:
|
|
||||||
story.append(Paragraph("%s" % preamble.replace('\r\n','<br/>'), stylesheet['Paragraph']))
|
|
||||||
story.append(Spacer(0,0.75*cm))
|
|
||||||
applications = Application.objects.order_by('number')
|
|
||||||
if not applications: # No applications existing
|
|
||||||
story.append(Paragraph(_("No motions available."), stylesheet['Heading3']))
|
|
||||||
else: # Print all Applications
|
|
||||||
# List of applications
|
|
||||||
for application in applications:
|
|
||||||
if application.number:
|
|
||||||
story.append(Paragraph(_("Motion No.")+" %s: %s" % (application.number, application.title), stylesheet['Heading3']))
|
|
||||||
else:
|
|
||||||
story.append(Paragraph(_("Motion No.")+" : %s" % (application.title), stylesheet['Heading3']))
|
|
||||||
# Applications details (each application on single page)
|
|
||||||
for application in applications:
|
|
||||||
story.append(PageBreak())
|
|
||||||
story = self.get_application(application, story)
|
|
||||||
else: # print selected application
|
|
||||||
application = Application.objects.get(id=application_id)
|
|
||||||
story = self.get_application(application, story)
|
|
||||||
|
|
||||||
def get_application(self, application, story):
|
|
||||||
# application number
|
|
||||||
if application.number:
|
|
||||||
story.append(Paragraph(_("Motion No.")+" %s" % application.number, stylesheet['Heading1']))
|
|
||||||
else:
|
|
||||||
story.append(Paragraph(_("Motion No."), stylesheet['Heading1']))
|
|
||||||
|
|
||||||
# submitter
|
|
||||||
cell1a = []
|
|
||||||
cell1a.append(Spacer(0,0.2*cm))
|
|
||||||
cell1a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Submitter"), stylesheet['Heading4']))
|
|
||||||
cell1b = []
|
|
||||||
cell1b.append(Spacer(0,0.2*cm))
|
|
||||||
if application.status == "pub":
|
|
||||||
cell1b.append(Paragraph("__________________________________________",stylesheet['Signaturefield']))
|
|
||||||
cell1b.append(Spacer(0,0.1*cm))
|
|
||||||
cell1b.append(Paragraph(" "+unicode(application.submitter), stylesheet['Small']))
|
|
||||||
cell1b.append(Spacer(0,0.2*cm))
|
|
||||||
else:
|
|
||||||
cell1b.append(Paragraph(unicode(application.submitter), stylesheet['Normal']))
|
|
||||||
|
|
||||||
# supporters
|
|
||||||
cell2a = []
|
|
||||||
cell2b = []
|
|
||||||
if config['application_min_supporters']:
|
|
||||||
|
|
||||||
cell2a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font><seqreset id='counter'>" % _("Supporters"), stylesheet['Heading4']))
|
|
||||||
|
|
||||||
for supporter in application.supporters:
|
|
||||||
cell2b.append(Paragraph("<seq id='counter'/>. %s" % unicode(supporter), stylesheet['Signaturefield']))
|
|
||||||
if application.status == "pub":
|
|
||||||
for x in range(0,application.missing_supporters):
|
|
||||||
cell2b.append(Paragraph("<seq id='counter'/>. __________________________________________",stylesheet['Signaturefield']))
|
|
||||||
cell2b.append(Spacer(0,0.2*cm))
|
|
||||||
|
|
||||||
# status
|
|
||||||
note = ""
|
|
||||||
for n in application.notes:
|
|
||||||
note += "%s " % unicode(n)
|
|
||||||
cell3a = []
|
|
||||||
cell3a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Status"), stylesheet['Heading4']))
|
|
||||||
cell3b = []
|
|
||||||
if note != "":
|
|
||||||
if application.status == "pub":
|
|
||||||
cell3b.append(Paragraph(note, stylesheet['Normal']))
|
|
||||||
else:
|
|
||||||
cell3b.append(Paragraph("%s | %s" % (application.get_status_display(), note), stylesheet['Normal']))
|
|
||||||
else:
|
|
||||||
cell3b.append(Paragraph("%s" % application.get_status_display(), stylesheet['Normal']))
|
|
||||||
|
|
||||||
# table
|
|
||||||
data = []
|
|
||||||
data.append([cell1a,cell1b])
|
|
||||||
data.append([cell2a,cell2b])
|
|
||||||
data.append([cell3a,cell3b])
|
|
||||||
|
|
||||||
# Version number (aid)
|
|
||||||
if application.public_version.aid > 1:
|
|
||||||
cell4a = []
|
|
||||||
cell4a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Version"), stylesheet['Heading4']))
|
|
||||||
cell4b = []
|
|
||||||
cell4b.append(Paragraph("%s" % application.public_version.aid, stylesheet['Normal']))
|
|
||||||
data.append([cell4a,cell4b])
|
|
||||||
|
|
||||||
poll_results = application.get_poll_results()
|
|
||||||
|
|
||||||
# voting results
|
|
||||||
if poll_results:
|
|
||||||
cell5a = []
|
|
||||||
cell5a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Vote results"), stylesheet['Heading4']))
|
|
||||||
cell5b = []
|
|
||||||
ballotcounter = 0
|
|
||||||
for result in poll_results:
|
|
||||||
ballotcounter += 1
|
|
||||||
if len(poll_results) > 1:
|
|
||||||
cell5b.append(Paragraph("%s. %s" % (ballotcounter, _("Vote")), stylesheet['Bold']))
|
|
||||||
cell5b.append(Paragraph("%s: %s <br/> %s: %s <br/> %s: %s <br/> %s: %s <br/> %s: %s" % (_("Yes"), result[0], _("No"), result[1], _("Abstention"), result[2], _("Invalid"), result[3], _("Votes cast"), result[4]), stylesheet['Normal']))
|
|
||||||
cell5b.append(Spacer(0,0.2*cm))
|
|
||||||
data.append([cell5a,cell5b])
|
|
||||||
|
|
||||||
t=Table(data)
|
|
||||||
t._argW[0]=4.5*cm
|
|
||||||
t._argW[1]=11*cm
|
|
||||||
t.setStyle(TableStyle([ ('BOX', (0,0), (-1,-1), 1, colors.black),
|
|
||||||
('VALIGN', (0,0), (-1,-1), 'TOP'),
|
|
||||||
]))
|
|
||||||
story.append(t)
|
|
||||||
story.append(Spacer(0,1*cm))
|
|
||||||
|
|
||||||
# title
|
|
||||||
story.append(Paragraph(application.public_version.title, stylesheet['Heading3']))
|
|
||||||
# text
|
|
||||||
story.append(Paragraph("%s" % application.public_version.text.replace('\r\n','<br/>'), stylesheet['Paragraph']))
|
|
||||||
# reason
|
|
||||||
if application.public_version.reason:
|
|
||||||
story.append(Paragraph(_("Reason")+":", stylesheet['Heading3']))
|
|
||||||
story.append(Paragraph("%s" % application.public_version.reason.replace('\r\n','<br/>'), stylesheet['Paragraph']))
|
|
||||||
return story
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationPollPDF(PDFView):
|
|
||||||
permission_required = 'application.can_manage_application'
|
|
||||||
top_space = 0
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
self.poll = ApplicationPoll.objects.get(id=self.kwargs['poll_id'])
|
|
||||||
return super(ApplicationPollPDF, self).get(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def get_filename(self):
|
|
||||||
filename = u'%s%s_%s' % (_("Application"), str(self.poll.application.number), _("Poll"))
|
|
||||||
return filename
|
|
||||||
|
|
||||||
def get_template(self, buffer):
|
|
||||||
return SimpleDocTemplate(buffer, topMargin=-6, bottomMargin=-6, leftMargin=0, rightMargin=0, showBoundary=False)
|
|
||||||
|
|
||||||
def build_document(self, pdf_document, story):
|
|
||||||
pdf_document.build(story)
|
|
||||||
|
|
||||||
def append_to_pdf(self, story):
|
|
||||||
imgpath = os.path.join(settings.SITE_ROOT, 'static/images/circle.png')
|
|
||||||
circle = "<img src='%s' width='15' height='15'/> " % imgpath
|
|
||||||
cell = []
|
|
||||||
cell.append(Spacer(0,0.8*cm))
|
|
||||||
cell.append(Paragraph(_("Application No. %s") % self.poll.application.number, stylesheet['Ballot_title']))
|
|
||||||
cell.append(Paragraph(self.poll.application.title, stylesheet['Ballot_subtitle']))
|
|
||||||
cell.append(Paragraph(_("%d. Vote") % self.poll.get_ballot(), stylesheet['Ballot_description']))
|
|
||||||
cell.append(Spacer(0,0.5*cm))
|
|
||||||
cell.append(Paragraph(circle + unicode(_("Yes")), stylesheet['Ballot_option']))
|
|
||||||
cell.append(Paragraph(circle + unicode(_("No")), stylesheet['Ballot_option']))
|
|
||||||
cell.append(Paragraph(circle + unicode(_("Abstention")), stylesheet['Ballot_option']))
|
|
||||||
data= []
|
|
||||||
# get ballot papers config values
|
|
||||||
ballot_papers_selection = config["application_pdf_ballot_papers_selection"]
|
|
||||||
ballot_papers_number = config["application_pdf_ballot_papers_number"]
|
|
||||||
|
|
||||||
# set number of ballot papers
|
|
||||||
if ballot_papers_selection == "NUMBER_OF_DELEGATES":
|
|
||||||
number = User.objects.filter(profile__type__iexact="delegate").count()
|
|
||||||
elif ballot_papers_selection == "NUMBER_OF_ALL_PARTICIPANTS":
|
|
||||||
number = int(Profile.objects.count())
|
|
||||||
else: # ballot_papers_selection == "CUSTOM_NUMBER"
|
|
||||||
number = int(ballot_papers_number)
|
|
||||||
number = max(1, number)
|
|
||||||
|
|
||||||
# print ballot papers
|
|
||||||
if number > 0:
|
|
||||||
for user in xrange(number / 2):
|
|
||||||
data.append([cell, cell])
|
|
||||||
rest = number % 2
|
|
||||||
if rest:
|
|
||||||
data.append([cell, ''])
|
|
||||||
t=Table(data, 10.5 * cm, 7.42 * cm)
|
|
||||||
t.setStyle(TableStyle([('GRID', (0, 0), (-1, -1), 0.25, colors.grey),
|
|
||||||
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
|
||||||
]))
|
|
||||||
story.append(t)
|
|
||||||
|
|
||||||
|
|
||||||
class Config(FormView):
|
|
||||||
permission_required = 'config.can_manage_config'
|
|
||||||
form_class = ConfigForm
|
|
||||||
template_name = 'application/config.html'
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
return {
|
|
||||||
'application_min_supporters': config['application_min_supporters'],
|
|
||||||
'application_preamble': config['application_preamble'],
|
|
||||||
'application_pdf_ballot_papers_selection': config['application_pdf_ballot_papers_selection'],
|
|
||||||
'application_pdf_ballot_papers_number': config['application_pdf_ballot_papers_number'],
|
|
||||||
'application_pdf_title': config['application_pdf_title'],
|
|
||||||
'application_pdf_preamble': config['application_pdf_preamble'],
|
|
||||||
'application_allow_trivial_change': config['application_allow_trivial_change'],
|
|
||||||
}
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
|
||||||
config['application_min_supporters'] = form.cleaned_data['application_min_supporters']
|
|
||||||
config['application_preamble'] = form.cleaned_data['application_preamble']
|
|
||||||
config['application_pdf_ballot_papers_selection'] = form.cleaned_data['application_pdf_ballot_papers_selection']
|
|
||||||
config['application_pdf_ballot_papers_number'] = form.cleaned_data['application_pdf_ballot_papers_number']
|
|
||||||
config['application_pdf_title'] = form.cleaned_data['application_pdf_title']
|
|
||||||
config['application_pdf_preamble'] = form.cleaned_data['application_pdf_preamble']
|
|
||||||
config['application_allow_trivial_change'] = form.cleaned_data['application_allow_trivial_change']
|
|
||||||
messages.success(self.request, _('Motion settings successfully saved.'))
|
|
||||||
return super(Config, self).form_valid(form)
|
|
||||||
|
|
||||||
|
|
||||||
def register_tab(request):
|
|
||||||
selected = True if request.path.startswith('/application/') else False
|
|
||||||
return Tab(
|
|
||||||
title=_('Applications'),
|
|
||||||
app='application',
|
|
||||||
url=reverse('application_overview'),
|
|
||||||
permission=request.user.has_perm('application.can_see_application') or request.user.has_perm('application.can_support_application') or request.user.has_perm('application.can_support_application') or request.user.has_perm('application.can_manage_application'),
|
|
||||||
selected=selected,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_widgets(request):
|
|
||||||
return [
|
|
||||||
Widget(
|
|
||||||
name='applications',
|
|
||||||
template='application/widget.html',
|
|
||||||
context={'applications': Application.objects.all()},
|
|
||||||
permission_required='application.can_manage_application')]
|
|
@ -11,7 +11,7 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _, ugettext_noop
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from openslides.utils.forms import CssClassMixin
|
from openslides.utils.forms import CssClassMixin
|
||||||
from openslides.utils.person import PersonFormField
|
from openslides.utils.person import PersonFormField
|
||||||
@ -20,8 +20,8 @@ from openslides.assignment.models import Assignment
|
|||||||
|
|
||||||
|
|
||||||
class AssignmentForm(forms.ModelForm, CssClassMixin):
|
class AssignmentForm(forms.ModelForm, CssClassMixin):
|
||||||
posts = forms.IntegerField(min_value=1, initial=1,
|
posts = forms.IntegerField(
|
||||||
label=_("Number of available posts"))
|
min_value=1, initial=1, label=_("Number of available posts"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Assignment
|
model = Assignment
|
||||||
@ -39,8 +39,7 @@ class ConfigForm(forms.Form, CssClassMixin):
|
|||||||
assignment_publish_winner_results_only = forms.BooleanField(
|
assignment_publish_winner_results_only = forms.BooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_("Only publish voting results for selected winners "
|
label=_("Only publish voting results for selected winners "
|
||||||
"(Projector view only)")
|
"(Projector view only)"))
|
||||||
)
|
|
||||||
assignment_pdf_ballot_papers_selection = forms.ChoiceField(
|
assignment_pdf_ballot_papers_selection = forms.ChoiceField(
|
||||||
widget=forms.Select(),
|
widget=forms.Select(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -48,31 +47,25 @@ class ConfigForm(forms.Form, CssClassMixin):
|
|||||||
choices=(
|
choices=(
|
||||||
("NUMBER_OF_DELEGATES", _("Number of all delegates")),
|
("NUMBER_OF_DELEGATES", _("Number of all delegates")),
|
||||||
("NUMBER_OF_ALL_PARTICIPANTS", _("Number of all participants")),
|
("NUMBER_OF_ALL_PARTICIPANTS", _("Number of all participants")),
|
||||||
("CUSTOM_NUMBER", _("Use the following custom number"))
|
("CUSTOM_NUMBER", _("Use the following custom number"))))
|
||||||
)
|
|
||||||
)
|
|
||||||
assignment_pdf_ballot_papers_number = forms.IntegerField(
|
assignment_pdf_ballot_papers_number = forms.IntegerField(
|
||||||
widget=forms.TextInput(attrs={'class':'small-input'}),
|
widget=forms.TextInput(attrs={'class': 'small-input'}),
|
||||||
required=False,
|
required=False,
|
||||||
min_value=1,
|
min_value=1,
|
||||||
label=_("Custom number of ballot papers")
|
label=_("Custom number of ballot papers"))
|
||||||
)
|
|
||||||
assignment_pdf_title = forms.CharField(
|
assignment_pdf_title = forms.CharField(
|
||||||
widget=forms.TextInput(),
|
widget=forms.TextInput(),
|
||||||
required=False,
|
required=False,
|
||||||
label=_("Title for PDF document (all elections)")
|
label=_("Title for PDF document (all elections)"))
|
||||||
)
|
|
||||||
assignment_pdf_preamble = forms.CharField(
|
assignment_pdf_preamble = forms.CharField(
|
||||||
widget=forms.Textarea(),
|
widget=forms.Textarea(),
|
||||||
required=False,
|
required=False,
|
||||||
label=_("Preamble text for PDF document (all elections)")
|
label=_("Preamble text for PDF document (all elections)"))
|
||||||
)
|
assignment_poll_vote_values = forms.ChoiceField(
|
||||||
assignment_poll_vote_values = forms.ChoiceField(widget=forms.Select(),
|
widget=forms.Select(),
|
||||||
required=False,
|
required=False,
|
||||||
label=_("Election method"),
|
label=_("Election method"),
|
||||||
choices=(
|
choices=(
|
||||||
("auto", _("Automatic assign of method.")),
|
("auto", _("Automatic assign of method.")),
|
||||||
("votes", _("Always one option per candidate.")),
|
("votes", _("Always one option per candidate.")),
|
||||||
("yesnoabstain", _("Always Yes-No-Abstain per candidate.")),
|
("yesnoabstain", _("Always Yes-No-Abstain per candidate."))))
|
||||||
)
|
|
||||||
)
|
|
||||||
|
@ -16,16 +16,12 @@ from django.dispatch import receiver
|
|||||||
from django.utils.translation import ugettext_lazy as _, ugettext_noop
|
from django.utils.translation import ugettext_lazy as _, ugettext_noop
|
||||||
|
|
||||||
from openslides.utils.person import PersonField
|
from openslides.utils.person import PersonField
|
||||||
|
|
||||||
from openslides.config.models import config
|
from openslides.config.models import config
|
||||||
from openslides.config.signals import default_config_value
|
from openslides.config.signals import default_config_value
|
||||||
|
|
||||||
from openslides.projector.api import register_slidemodel
|
from openslides.projector.api import register_slidemodel
|
||||||
from openslides.projector.projector import SlideMixin
|
from openslides.projector.projector import SlideMixin
|
||||||
|
from openslides.poll.models import (
|
||||||
from openslides.poll.models import (BasePoll, CountInvalid, CountVotesCast,
|
BasePoll, CountInvalid, CountVotesCast, BaseOption, PublishPollMixin, BaseVote)
|
||||||
BaseOption, PublishPollMixin, BaseVote)
|
|
||||||
|
|
||||||
from openslides.agenda.models import Item
|
from openslides.agenda.models import Item
|
||||||
|
|
||||||
|
|
||||||
@ -33,10 +29,14 @@ class AssignmentCandidate(models.Model):
|
|||||||
assignment = models.ForeignKey("Assignment")
|
assignment = models.ForeignKey("Assignment")
|
||||||
person = PersonField(db_index=True)
|
person = PersonField(db_index=True)
|
||||||
elected = models.BooleanField(default=False)
|
elected = models.BooleanField(default=False)
|
||||||
|
blocked = models.BooleanField(default=False)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return unicode(self.person)
|
return unicode(self.person)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ("assignment", "person")
|
||||||
|
|
||||||
|
|
||||||
class Assignment(models.Model, SlideMixin):
|
class Assignment(models.Model, SlideMixin):
|
||||||
prefix = 'assignment'
|
prefix = 'assignment'
|
||||||
@ -47,11 +47,10 @@ class Assignment(models.Model, SlideMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
name = models.CharField(max_length=100, verbose_name=_("Name"))
|
name = models.CharField(max_length=100, verbose_name=_("Name"))
|
||||||
description = models.TextField(null=True, blank=True,
|
description = models.TextField(null=True, blank=True, verbose_name=_("Description"))
|
||||||
verbose_name=_("Description"))
|
posts = models.PositiveSmallIntegerField(verbose_name=_("Number of available posts"))
|
||||||
posts = models.PositiveSmallIntegerField(
|
polldescription = models.CharField(
|
||||||
verbose_name=_("Number of available posts"))
|
max_length=100, null=True, blank=True,
|
||||||
polldescription = models.CharField(max_length=100, null=True, blank=True,
|
|
||||||
verbose_name=_("Comment on the ballot paper"))
|
verbose_name=_("Comment on the ballot paper"))
|
||||||
status = models.CharField(max_length=3, choices=STATUS, default='sea')
|
status = models.CharField(max_length=3, choices=STATUS, default='sea')
|
||||||
|
|
||||||
@ -64,14 +63,16 @@ class Assignment(models.Model, SlideMixin):
|
|||||||
if error:
|
if error:
|
||||||
raise NameError(_('%s is not a valid status.') % status)
|
raise NameError(_('%s is not a valid status.') % status)
|
||||||
if self.status == status:
|
if self.status == status:
|
||||||
raise NameError(_('The assignment status is already %s.')
|
raise NameError(
|
||||||
% self.status)
|
_('The assignment status is already %s.') % self.status)
|
||||||
self.status = status
|
self.status = status
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def run(self, candidate, person=None):
|
def run(self, candidate, person=None):
|
||||||
"""
|
"""
|
||||||
run for a vote
|
run for a vote
|
||||||
|
candidate: The user who will be a candidate
|
||||||
|
person: The user who chooses the candidate
|
||||||
"""
|
"""
|
||||||
# TODO: don't make any permission checks here.
|
# TODO: don't make any permission checks here.
|
||||||
# Use other Exceptions
|
# Use other Exceptions
|
||||||
@ -79,26 +80,54 @@ class Assignment(models.Model, SlideMixin):
|
|||||||
raise NameError(_('<b>%s</b> is already a candidate.') % candidate)
|
raise NameError(_('<b>%s</b> is already a candidate.') % candidate)
|
||||||
if not person.has_perm("assignment.can_manage_assignment") and self.status != 'sea':
|
if not person.has_perm("assignment.can_manage_assignment") and self.status != 'sea':
|
||||||
raise NameError(_('The candidate list is already closed.'))
|
raise NameError(_('The candidate list is already closed.'))
|
||||||
AssignmentCandidate(assignment=self, person=candidate, elected=False).save()
|
candidature = self.assignment_candidates.filter(person=candidate)
|
||||||
|
if candidature and candidate != person and \
|
||||||
|
not person.has_perm("assignment.can_manage_assignment"):
|
||||||
|
# if the candidature is blocked and anotherone tries to run the
|
||||||
|
# candidate
|
||||||
|
raise NameError(
|
||||||
|
_('%s does not want to be a candidate.') % candidate)
|
||||||
|
elif candidature:
|
||||||
|
candidature[0].blocked = False
|
||||||
|
candidature[0].save()
|
||||||
|
else:
|
||||||
|
AssignmentCandidate(assignment=self, person=candidate).save()
|
||||||
|
|
||||||
def delrun(self, candidate):
|
def delrun(self, candidate, blocked=True):
|
||||||
"""
|
"""
|
||||||
stop running for a vote
|
stop running for a vote
|
||||||
"""
|
"""
|
||||||
if self.is_candidate(candidate):
|
try:
|
||||||
self.assignment_candidats.get(person=candidate).delete()
|
candidature = self.assignment_candidates.get(person=candidate)
|
||||||
else:
|
except AssignmentCandidate.DoesNotExist:
|
||||||
# TODO: Use an OpenSlides Error
|
|
||||||
raise Exception(_('%s is no candidate') % candidate)
|
raise Exception(_('%s is no candidate') % candidate)
|
||||||
|
|
||||||
def is_candidate(self, person):
|
if not candidature.blocked:
|
||||||
if self.assignment_candidats.filter(person=person).exists():
|
if blocked:
|
||||||
return True
|
candidature.blocked = True
|
||||||
|
candidature.save()
|
||||||
else:
|
else:
|
||||||
|
candidature.delete()
|
||||||
|
else:
|
||||||
|
candidature.delete()
|
||||||
|
|
||||||
|
def is_candidate(self, person):
|
||||||
|
"""
|
||||||
|
return True, if person is a candidate.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self.assignment_candidates.filter(person=person).exclude(blocked=True).exists()
|
||||||
|
except AttributeError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def is_blocked(self, person):
|
||||||
|
"""
|
||||||
|
return True, if the person is blockt for candidature.
|
||||||
|
"""
|
||||||
|
return self.assignment_candidates.filter(person=person).filter(blocked=True).exists()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def assignment_candidats(self):
|
def assignment_candidates(self):
|
||||||
return AssignmentCandidate.objects.filter(assignment=self)
|
return AssignmentCandidate.objects.filter(assignment=self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -110,11 +139,9 @@ class Assignment(models.Model, SlideMixin):
|
|||||||
return self.get_participants(only_elected=True)
|
return self.get_participants(only_elected=True)
|
||||||
|
|
||||||
def get_participants(self, only_elected=False, only_candidate=False):
|
def get_participants(self, only_elected=False, only_candidate=False):
|
||||||
candidates = self.assignment_candidats
|
candidates = self.assignment_candidates.exclude(blocked=True)
|
||||||
|
|
||||||
if only_elected and only_candidate:
|
assert not (only_elected and only_candidate)
|
||||||
# TODO: Use right Exception
|
|
||||||
raise Exception("only_elected and only_candidate can not both be Treu")
|
|
||||||
|
|
||||||
if only_elected:
|
if only_elected:
|
||||||
candidates = candidates.filter(elected=True)
|
candidates = candidates.filter(elected=True)
|
||||||
@ -122,12 +149,15 @@ class Assignment(models.Model, SlideMixin):
|
|||||||
if only_candidate:
|
if only_candidate:
|
||||||
candidates = candidates.filter(elected=False)
|
candidates = candidates.filter(elected=False)
|
||||||
|
|
||||||
|
participants = []
|
||||||
for candidate in candidates.all():
|
for candidate in candidates.all():
|
||||||
yield candidate.person
|
participants.append(candidate.person)
|
||||||
|
participants.sort(key=lambda person: person.sort_name)
|
||||||
|
return participants
|
||||||
|
#return candidates.values_list('person', flat=True)
|
||||||
|
|
||||||
def set_elected(self, person, value=True):
|
def set_elected(self, person, value=True):
|
||||||
candidate = self.assignment_candidats.get(person=person)
|
candidate = self.assignment_candidates.get(person=person)
|
||||||
candidate.elected = value
|
candidate.elected = value
|
||||||
candidate.save()
|
candidate.save()
|
||||||
|
|
||||||
@ -140,7 +170,6 @@ class Assignment(models.Model, SlideMixin):
|
|||||||
poll.set_options([{'candidate': person} for person in self.candidates])
|
poll.set_options([{'candidate': person} for person in self.candidates])
|
||||||
return poll
|
return poll
|
||||||
|
|
||||||
|
|
||||||
def vote_results(self, only_published):
|
def vote_results(self, only_published):
|
||||||
"""
|
"""
|
||||||
returns a table represented as a list with all candidates from all
|
returns a table represented as a list with all candidates from all
|
||||||
@ -174,12 +203,11 @@ class Assignment(models.Model, SlideMixin):
|
|||||||
vote_results_dict[candidate].append(votes)
|
vote_results_dict[candidate].append(votes)
|
||||||
return vote_results_dict
|
return vote_results_dict
|
||||||
|
|
||||||
|
|
||||||
def get_agenda_title(self):
|
def get_agenda_title(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
# Remove any Agenda-Item, which is related to this application.
|
# Remove any Agenda-Item, which is related to this assignment.
|
||||||
for item in Item.objects.filter(related_sid=self.sid):
|
for item in Item.objects.filter(related_sid=self.sid):
|
||||||
item.delete()
|
item.delete()
|
||||||
super(Assignment, self).delete()
|
super(Assignment, self).delete()
|
||||||
@ -188,10 +216,12 @@ class Assignment(models.Model, SlideMixin):
|
|||||||
"""
|
"""
|
||||||
return the slide dict
|
return the slide dict
|
||||||
"""
|
"""
|
||||||
|
polls = self.poll_set
|
||||||
data = super(Assignment, self).slide()
|
data = super(Assignment, self).slide()
|
||||||
data['assignment'] = self
|
data['assignment'] = self
|
||||||
data['title'] = self.name
|
data['title'] = self.name
|
||||||
data['polls'] = self.poll_set.filter(published=True)
|
data['some_polls_available'] = polls.exists()
|
||||||
|
data['polls'] = polls.filter(published=True)
|
||||||
data['vote_results'] = self.vote_results(only_published=True)
|
data['vote_results'] = self.vote_results(only_published=True)
|
||||||
data['assignment_publish_winner_results_only'] = \
|
data['assignment_publish_winner_results_only'] = \
|
||||||
config['assignment_publish_winner_results_only']
|
config['assignment_publish_winner_results_only']
|
||||||
@ -217,6 +247,7 @@ class Assignment(models.Model, SlideMixin):
|
|||||||
('can_nominate_self', ugettext_noop("Can nominate themselves")),
|
('can_nominate_self', ugettext_noop("Can nominate themselves")),
|
||||||
('can_manage_assignment', ugettext_noop("Can manage assignment")),
|
('can_manage_assignment', ugettext_noop("Can manage assignment")),
|
||||||
)
|
)
|
||||||
|
ordering = ('name',)
|
||||||
|
|
||||||
register_slidemodel(Assignment)
|
register_slidemodel(Assignment)
|
||||||
|
|
||||||
@ -251,14 +282,13 @@ class AssignmentPoll(BasePoll, CountInvalid, CountVotesCast, PublishPollMixin):
|
|||||||
self.yesnoabstain = True
|
self.yesnoabstain = True
|
||||||
else:
|
else:
|
||||||
# candidates <= available posts -> yes/no/abstain
|
# candidates <= available posts -> yes/no/abstain
|
||||||
if self.assignment.assignment_candidats.filter(elected=False).count() <= (self.assignment.posts):
|
if len(self.assignment.candidates) <= (self.assignment.posts - len(self.assignment.elected)):
|
||||||
self.yesnoabstain = True
|
self.yesnoabstain = True
|
||||||
else:
|
else:
|
||||||
self.yesnoabstain = False
|
self.yesnoabstain = False
|
||||||
self.save()
|
self.save()
|
||||||
if self.yesnoabstain:
|
if self.yesnoabstain:
|
||||||
return [ugettext_noop('Yes'), ugettext_noop('No'),
|
return [ugettext_noop('Yes'), ugettext_noop('No'), ugettext_noop('Abstain')]
|
||||||
ugettext_noop('Abstain')]
|
|
||||||
else:
|
else:
|
||||||
return [ugettext_noop('Votes')]
|
return [ugettext_noop('Votes')]
|
||||||
|
|
||||||
|
@ -42,7 +42,13 @@
|
|||||||
<tr class="{% cycle '' 'odd' %}
|
<tr class="{% cycle '' 'odd' %}
|
||||||
{% if assignment.active %}activeline{% endif %}">
|
{% if assignment.active %}activeline{% endif %}">
|
||||||
<td><a href="{% url assignment_view assignment.id %}">{{ assignment }}</a></td>
|
<td><a href="{% url assignment_view assignment.id %}">{{ assignment }}</a></td>
|
||||||
<td>{{ assignment.candidates|length }} / {{ assignment.posts }}</td>
|
<td>
|
||||||
|
{% blocktrans with posts=assignment.posts context "Number of searched candidates for an election"%}posts: {{ posts }}{% endblocktrans %}
|
||||||
|
{% if assignment.status != 'fin' %}
|
||||||
|
| {% blocktrans with candidates=assignment.get_participants|length %}candidates: {{ candidates }}{% endblocktrans %}
|
||||||
|
{% endif %}
|
||||||
|
| {% blocktrans with elected=assignment.elected|length %}elected: {{ elected }}{% endblocktrans %}
|
||||||
|
</td>
|
||||||
<td class="optional">{{ assignment.get_status_display }}</td>
|
<td class="optional">{{ assignment.get_status_display }}</td>
|
||||||
<td>
|
<td>
|
||||||
<span style="width: 1px; white-space: nowrap;">
|
<span style="width: 1px; white-space: nowrap;">
|
||||||
|
@ -35,9 +35,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<br>
|
<br>
|
||||||
<!-- Candidates -->
|
<!-- Candidates -->
|
||||||
|
{% if assignment.status != "fin" %}
|
||||||
<h4>{% trans "Candidates" %}</h4>
|
<h4>{% trans "Candidates" %}</h4>
|
||||||
<ol>
|
<ol>
|
||||||
{% for person in assignment.candidates %}
|
{% for person in assignment.get_participants %}
|
||||||
<li>
|
<li>
|
||||||
{{ person }}
|
{{ person }}
|
||||||
{% if perms.assignment.can_manage_assignment %}
|
{% if perms.assignment.can_manage_assignment %}
|
||||||
@ -45,6 +46,14 @@
|
|||||||
<a href="{% url assignment_delother assignment.id person.person_id %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Remove candidate' %}"><i class="icon-remove"></i></a>
|
<a href="{% url assignment_delother assignment.id person.person_id %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Remove candidate' %}"><i class="icon-remove"></i></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if person in assignment.elected %}
|
||||||
|
| <b>{% trans "elected" %}</b>
|
||||||
|
{% if perms.assignment.can_manage_assignment %}
|
||||||
|
{% if assignment.status == "sea" or assignment.status == "vot" %}
|
||||||
|
<a href="{% url assignment_user_not_elected assignment.id person.person_id %}"><img src="{% static 'images/icons/dialog-cancel.png' %}" title="{% trans 'Mark candidate as not elected' %}"></a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<li style="list-style: none outside none; margin-left: -25px;"><i>{% trans "No candidates available." %}</i></li>
|
<li style="list-style: none outside none; margin-left: -25px;"><i>{% trans "No candidates available." %}</i></li>
|
||||||
@ -84,7 +93,24 @@
|
|||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!-- Results -->
|
{% endif %}
|
||||||
|
|
||||||
|
{% if perms.assignment.can_manage_assignments and blocked_candidates and assignment.status != "fin" %}
|
||||||
|
<h3>{% trans "Blocked Candidates" %}</h3>
|
||||||
|
<ul>
|
||||||
|
{% for person in blocked_candidates %}
|
||||||
|
<li>
|
||||||
|
{{ person }}<a href="{% url assignment_delother assignment.id person.person_id %}"><img src="{% static 'images/icons/delete.png' %}" title="{% trans 'Remove candidate' %}"></a>
|
||||||
|
</li>
|
||||||
|
{% empty %}
|
||||||
|
<li>{% trans "No blocked candidates available." %}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Results -->
|
||||||
|
{% if assignment.status != "sea" or polls.exists %}
|
||||||
<h4>{% trans "Election results" %}</h4>
|
<h4>{% trans "Election results" %}</h4>
|
||||||
{% if polls.exists %}
|
{% if polls.exists %}
|
||||||
<table class="table table-striped table-bordered">
|
<table class="table table-striped table-bordered">
|
||||||
@ -175,7 +201,6 @@
|
|||||||
<td></td>
|
<td></td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr class="info total">
|
<tr class="info total">
|
||||||
<td><strong>{% trans 'Votes cast' %}</strong></td>
|
<td><strong>{% trans 'Votes cast' %}</strong></td>
|
||||||
{% for poll in polls %}
|
{% for poll in polls %}
|
||||||
|
@ -62,9 +62,9 @@
|
|||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% for candidate, poll_list in vote_results.items %}
|
{% for candidate, poll_list in vote_results.items %}
|
||||||
<tr class="{% cycle 'odd' '' %}">
|
<tr class="{% cycle 'odd' '' as rowcolors %}">
|
||||||
<td class="candidate{% if candidate in assignment.elected.all %} elected{% endif %}">
|
<td class="candidate{% if candidate in assignment.elected %} elected{% endif %}">
|
||||||
{% if candidate in assignment.elected.all %}
|
{% if candidate in assignment.elected %}
|
||||||
<a class="elected">
|
<a class="elected">
|
||||||
<img src="{% static 'images/icons/voting-yes.png' %}" title="{% trans 'Candidate is elected' %}">
|
<img src="{% static 'images/icons/voting-yes.png' %}" title="{% trans 'Candidate is elected' %}">
|
||||||
</a>
|
</a>
|
||||||
@ -72,8 +72,8 @@
|
|||||||
{{ candidate }}
|
{{ candidate }}
|
||||||
</td>
|
</td>
|
||||||
{% for vote in poll_list %}
|
{% for vote in poll_list %}
|
||||||
<td style="white-space:nowrap;"{% if candidate in assignment.elected.all %} class="elected"{% endif %}>
|
<td style="white-space:nowrap;"{% if candidate in assignment.elected %} class="elected"{% endif %}>
|
||||||
{% if not assignment_publish_winner_results_only or candidate in assignment.elected.all %}
|
{% if not assignment_publish_winner_results_only or candidate in assignment.elected %}
|
||||||
{% if 'Yes' in vote and 'No' in vote and 'Abstain' in vote %}
|
{% if 'Yes' in vote and 'No' in vote and 'Abstain' in vote %}
|
||||||
<img src="{% static 'images/icons/voting-yes.png' %}" title="{% trans 'Yes' %}"> {{ vote.Yes }}<br>
|
<img src="{% static 'images/icons/voting-yes.png' %}" title="{% trans 'Yes' %}"> {{ vote.Yes }}<br>
|
||||||
<img src="{% static 'images/icons/voting-no.png' %}" title="{% trans 'No' %}"> {{ vote.No }}<br>
|
<img src="{% static 'images/icons/voting-no.png' %}" title="{% trans 'No' %}"> {{ vote.No }}<br>
|
||||||
@ -92,8 +92,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
<tr class="{% cycle rowcolors %}">
|
||||||
<tr>
|
|
||||||
<td>{% trans 'Invalid votes' %}</td>
|
<td>{% trans 'Invalid votes' %}</td>
|
||||||
{% for poll in polls %}
|
{% for poll in polls %}
|
||||||
<td style="white-space:nowrap;">
|
<td style="white-space:nowrap;">
|
||||||
@ -105,7 +104,6 @@
|
|||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr class="total">
|
<tr class="total">
|
||||||
<td>
|
<td>
|
||||||
<strong>{% trans 'Votes cast' %}</strong>
|
<strong>{% trans 'Votes cast' %}</strong>
|
||||||
@ -120,10 +118,6 @@
|
|||||||
</td>
|
</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
{% elif assignment.candidates %}
|
|
||||||
<i>{% trans "No ballots available." %}</i>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<br>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -13,39 +13,31 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from reportlab.lib import colors
|
from reportlab.lib import colors
|
||||||
from reportlab.platypus import (SimpleDocTemplate, PageBreak, Paragraph,
|
from reportlab.platypus import (
|
||||||
Spacer, Table, TableStyle)
|
SimpleDocTemplate, PageBreak, Paragraph, Spacer, Table, TableStyle)
|
||||||
from reportlab.lib.units import cm
|
from reportlab.lib.units import cm
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.utils.translation import ungettext, ugettext as _
|
from django.utils.translation import ungettext, ugettext as _
|
||||||
|
|
||||||
from openslides.utils.pdf import stylesheet
|
from openslides.utils.pdf import stylesheet
|
||||||
from openslides.utils.template import Tab
|
from openslides.utils.template import Tab
|
||||||
from openslides.utils.utils import (template, permission_required,
|
from openslides.utils.utils import (
|
||||||
gen_confirm_form, del_confirm_form, ajax_request)
|
template, permission_required, gen_confirm_form, del_confirm_form, ajax_request)
|
||||||
from openslides.utils.views import FormView, DeleteView, PDFView, RedirectView
|
from openslides.utils.views import FormView, DeleteView, PDFView, RedirectView
|
||||||
from openslides.utils.person import get_person
|
from openslides.utils.person import get_person
|
||||||
|
|
||||||
from openslides.config.models import config
|
from openslides.config.models import config
|
||||||
|
|
||||||
from openslides.participant.models import User
|
from openslides.participant.models import User
|
||||||
|
|
||||||
from openslides.projector.projector import Widget
|
from openslides.projector.projector import Widget
|
||||||
|
|
||||||
from openslides.poll.views import PollFormView
|
from openslides.poll.views import PollFormView
|
||||||
|
|
||||||
from openslides.agenda.models import Item
|
from openslides.agenda.models import Item
|
||||||
|
from openslides.assignment.models import Assignment, AssignmentPoll
|
||||||
from openslides.assignment.models import (Assignment, AssignmentPoll,
|
from openslides.assignment.forms import (
|
||||||
AssignmentOption)
|
AssignmentForm, AssignmentRunForm, ConfigForm)
|
||||||
from openslides.assignment.forms import (AssignmentForm, AssignmentRunForm,
|
|
||||||
ConfigForm)
|
|
||||||
|
|
||||||
|
|
||||||
@permission_required('assignment.can_see_assignment')
|
@permission_required('assignment.can_see_assignment')
|
||||||
@ -56,10 +48,10 @@ def get_overview(request):
|
|||||||
query = query.filter(status__iexact=request.GET['status'])
|
query = query.filter(status__iexact=request.GET['status'])
|
||||||
try:
|
try:
|
||||||
sort = request.GET['sort']
|
sort = request.GET['sort']
|
||||||
if sort in ['name','status']:
|
if sort in ['name', 'status']:
|
||||||
query = query.order_by(sort)
|
query = query.order_by(sort)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
query = query.order_by('name')
|
pass
|
||||||
if 'reverse' in request.GET:
|
if 'reverse' in request.GET:
|
||||||
query = query.reverse()
|
query = query.reverse()
|
||||||
|
|
||||||
@ -81,14 +73,16 @@ def view(request, assignment_id=None):
|
|||||||
user = form.cleaned_data['candidate']
|
user = form.cleaned_data['candidate']
|
||||||
try:
|
try:
|
||||||
assignment.run(user, request.user)
|
assignment.run(user, request.user)
|
||||||
messages.success(request, _("Candidate <b>%s</b> was nominated successfully.") % (user))
|
|
||||||
except NameError, e:
|
except NameError, e:
|
||||||
messages.error(request, e)
|
messages.error(request, e)
|
||||||
|
else:
|
||||||
|
messages.success(request, _(
|
||||||
|
"Candidate <b>%s</b> was nominated successfully.")
|
||||||
|
% user)
|
||||||
else:
|
else:
|
||||||
if request.user.has_perm('assignment.can_nominate_other'):
|
if request.user.has_perm('assignment.can_nominate_other'):
|
||||||
form = AssignmentRunForm()
|
form = AssignmentRunForm()
|
||||||
|
|
||||||
|
|
||||||
polls = assignment.poll_set.all()
|
polls = assignment.poll_set.all()
|
||||||
if not request.user.has_perm('assignment.can_manage_assignment'):
|
if not request.user.has_perm('assignment.can_manage_assignment'):
|
||||||
polls = assignment.poll_set.filter(published=True)
|
polls = assignment.poll_set.filter(published=True)
|
||||||
@ -97,13 +91,16 @@ def view(request, assignment_id=None):
|
|||||||
polls = assignment.poll_set.all()
|
polls = assignment.poll_set.all()
|
||||||
vote_results = assignment.vote_results(only_published=False)
|
vote_results = assignment.vote_results(only_published=False)
|
||||||
|
|
||||||
|
blocked_candidates = [
|
||||||
|
candidate.person for candidate in
|
||||||
|
assignment.assignment_candidates.filter(blocked=True)]
|
||||||
return {
|
return {
|
||||||
'assignment': assignment,
|
'assignment': assignment,
|
||||||
|
'blocked_candidates': blocked_candidates,
|
||||||
'form': form,
|
'form': form,
|
||||||
'vote_results': vote_results,
|
'vote_results': vote_results,
|
||||||
'polls': polls,
|
'polls': polls,
|
||||||
'user_is_candidate': assignment.is_candidate(request.user)
|
'user_is_candidate': assignment.is_candidate(request.user)}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@permission_required('assignment.can_manage_assignment')
|
@permission_required('assignment.can_manage_assignment')
|
||||||
@ -165,6 +162,8 @@ def set_status(request, assignment_id=None, status=None):
|
|||||||
messages.success(request, _('Election status was set to: <b>%s</b>.') % assignment.get_status_display())
|
messages.success(request, _('Election status was set to: <b>%s</b>.') % assignment.get_status_display())
|
||||||
except Assignment.DoesNotExist:
|
except Assignment.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
except NameError, e:
|
||||||
|
messages.error(request, e)
|
||||||
return redirect(reverse('assignment_view', args=[assignment_id]))
|
return redirect(reverse('assignment_view', args=[assignment_id]))
|
||||||
|
|
||||||
|
|
||||||
@ -173,7 +172,7 @@ def run(request, assignment_id):
|
|||||||
assignment = Assignment.objects.get(pk=assignment_id)
|
assignment = Assignment.objects.get(pk=assignment_id)
|
||||||
try:
|
try:
|
||||||
assignment.run(request.user, request.user)
|
assignment.run(request.user, request.user)
|
||||||
messages.success(request, _('You have set your candidature successfully.') )
|
messages.success(request, _('You have set your candidature successfully.'))
|
||||||
except NameError, e:
|
except NameError, e:
|
||||||
messages.error(request, e)
|
messages.error(request, e)
|
||||||
return redirect(reverse('assignment_view', args=[assignment_id]))
|
return redirect(reverse('assignment_view', args=[assignment_id]))
|
||||||
@ -182,15 +181,19 @@ def run(request, assignment_id):
|
|||||||
@login_required
|
@login_required
|
||||||
def delrun(request, assignment_id):
|
def delrun(request, assignment_id):
|
||||||
assignment = Assignment.objects.get(pk=assignment_id)
|
assignment = Assignment.objects.get(pk=assignment_id)
|
||||||
|
if assignment.status == 'sea' or request.user.has_perm("assignment.can_manage_assignment"):
|
||||||
try:
|
try:
|
||||||
if assignment.status == 'sea' or user.has_perm("assignment.can_manage_assignment"):
|
assignment.delrun(request.user, blocked=True)
|
||||||
assignment.delrun(request.user)
|
|
||||||
else:
|
|
||||||
messages.error(request, _('The candidate list is already closed.'))
|
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
messages.error(request, e)
|
messages.error(request, e)
|
||||||
else:
|
else:
|
||||||
messages.success(request, _("You have withdrawn your candidature successfully.") )
|
messages.success(
|
||||||
|
request,
|
||||||
|
_("You have withdrawn your candidature successfully. "
|
||||||
|
"You can not be nominated by other participants anymore."))
|
||||||
|
else:
|
||||||
|
messages.error(request, _('The candidate list is already closed.'))
|
||||||
|
|
||||||
return redirect(reverse('assignment_view', args=[assignment_id]))
|
return redirect(reverse('assignment_view', args=[assignment_id]))
|
||||||
|
|
||||||
|
|
||||||
@ -198,18 +201,25 @@ def delrun(request, assignment_id):
|
|||||||
def delother(request, assignment_id, user_id):
|
def delother(request, assignment_id, user_id):
|
||||||
assignment = Assignment.objects.get(pk=assignment_id)
|
assignment = Assignment.objects.get(pk=assignment_id)
|
||||||
person = get_person(user_id)
|
person = get_person(user_id)
|
||||||
|
is_blocked = assignment.is_blocked(person)
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
try:
|
try:
|
||||||
assignment.delrun(person)
|
assignment.delrun(person, blocked=False)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
messages.error(request, e)
|
messages.error(request, e)
|
||||||
else:
|
else:
|
||||||
messages.success(request, _("Candidate <b>%s</b> was withdrawn successfully.") % (person))
|
if not is_blocked:
|
||||||
|
message = _("Candidate <b>%s</b> was withdrawn successfully.") % person
|
||||||
else:
|
else:
|
||||||
gen_confirm_form(request,
|
message = _("<b>%s</b> was unblocked successfully.") % person
|
||||||
_("Do you really want to withdraw <b>%s</b> from the election?") \
|
messages.success(request, message)
|
||||||
% person, reverse('assignment_delother', args=[assignment_id, user_id]))
|
else:
|
||||||
|
if not is_blocked:
|
||||||
|
message = _("Do you really want to withdraw <b>%s</b> from the election?") % person
|
||||||
|
else:
|
||||||
|
message = _("Do you really want to unblock <b>%s</b> for the election?") % person
|
||||||
|
gen_confirm_form(request, message, reverse('assignment_delother', args=[assignment_id, user_id]))
|
||||||
return redirect(reverse('assignment_view', args=[assignment_id]))
|
return redirect(reverse('assignment_view', args=[assignment_id]))
|
||||||
|
|
||||||
|
|
||||||
@ -223,7 +233,7 @@ def set_active(request, assignment_id):
|
|||||||
@permission_required('assignment.can_manage_assignment')
|
@permission_required('assignment.can_manage_assignment')
|
||||||
def gen_poll(request, assignment_id):
|
def gen_poll(request, assignment_id):
|
||||||
poll = Assignment.objects.get(pk=assignment_id).gen_poll()
|
poll = Assignment.objects.get(pk=assignment_id).gen_poll()
|
||||||
messages.success(request, _("New ballot was successfully created.") )
|
messages.success(request, _("New ballot was successfully created."))
|
||||||
return redirect(reverse('assignment_poll_view', args=[poll.id]))
|
return redirect(reverse('assignment_poll_view', args=[poll.id]))
|
||||||
|
|
||||||
|
|
||||||
@ -262,9 +272,9 @@ def set_publish_status(request, poll_id):
|
|||||||
return ajax_request({'published': poll.published})
|
return ajax_request({'published': poll.published})
|
||||||
|
|
||||||
if poll.published:
|
if poll.published:
|
||||||
messages.success(request, _("Ballot successfully published.") )
|
messages.success(request, _("Ballot successfully published."))
|
||||||
else:
|
else:
|
||||||
messages.success(request, _("Ballot successfully unpublished.") )
|
messages.success(request, _("Ballot successfully unpublished."))
|
||||||
return redirect(reverse('assignment_view', args=[poll.assignment.id]))
|
return redirect(reverse('assignment_view', args=[poll.assignment.id]))
|
||||||
|
|
||||||
|
|
||||||
@ -319,8 +329,9 @@ class AssignmentPDF(PDFView):
|
|||||||
try:
|
try:
|
||||||
assignment_id = self.kwargs['assignment_id']
|
assignment_id = self.kwargs['assignment_id']
|
||||||
assignment = Assignment.objects.get(id=assignment_id)
|
assignment = Assignment.objects.get(id=assignment_id)
|
||||||
filename = u'%s-%s' % (_("Assignment"),
|
filename = u'%s-%s' % (
|
||||||
assignment.name.replace(' ','_'))
|
_("Assignment"),
|
||||||
|
assignment.name.replace(' ', '_'))
|
||||||
except:
|
except:
|
||||||
filename = _("Elections")
|
filename = _("Elections")
|
||||||
return filename
|
return filename
|
||||||
@ -330,23 +341,24 @@ class AssignmentPDF(PDFView):
|
|||||||
assignment_id = self.kwargs['assignment_id']
|
assignment_id = self.kwargs['assignment_id']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
assignment_id = None
|
assignment_id = None
|
||||||
if assignment_id is None: #print all assignments
|
if assignment_id is None: # print all assignments
|
||||||
title = config["assignment_pdf_title"]
|
title = config["assignment_pdf_title"]
|
||||||
story.append(Paragraph(title, stylesheet['Heading1']))
|
story.append(Paragraph(title, stylesheet['Heading1']))
|
||||||
preamble = config["assignment_pdf_preamble"]
|
preamble = config["assignment_pdf_preamble"]
|
||||||
if preamble:
|
if preamble:
|
||||||
story.append(Paragraph("%s" % preamble.replace('\r\n', '<br/>'),
|
story.append(Paragraph(
|
||||||
|
"%s" % preamble.replace('\r\n', '<br/>'),
|
||||||
stylesheet['Paragraph']))
|
stylesheet['Paragraph']))
|
||||||
story.append(Spacer(0, 0.75 * cm))
|
story.append(Spacer(0, 0.75 * cm))
|
||||||
assignments = Assignment.objects.order_by('name')
|
assignments = Assignment.objects.all()
|
||||||
if not assignments: # No assignments existing
|
if not assignments: # No assignments existing
|
||||||
story.append(Paragraph(_("No assignments available."),
|
story.append(Paragraph(
|
||||||
stylesheet['Heading3']))
|
_("No assignments available."), stylesheet['Heading3']))
|
||||||
else: # Print all assignments
|
else: # Print all assignments
|
||||||
# List of assignments
|
# List of assignments
|
||||||
for assignment in assignments:
|
for assignment in assignments:
|
||||||
story.append(Paragraph(assignment.name,
|
story.append(Paragraph(
|
||||||
stylesheet['Heading3']))
|
assignment.name, stylesheet['Heading3']))
|
||||||
# Assignment details (each assignment on single page)
|
# Assignment details (each assignment on single page)
|
||||||
for assignment in assignments:
|
for assignment in assignments:
|
||||||
story.append(PageBreak())
|
story.append(PageBreak())
|
||||||
@ -359,26 +371,31 @@ class AssignmentPDF(PDFView):
|
|||||||
|
|
||||||
def get_assignment(self, assignment, story):
|
def get_assignment(self, assignment, story):
|
||||||
# title
|
# title
|
||||||
story.append(Paragraph(_("Election: %s") % assignment.name,
|
story.append(Paragraph(
|
||||||
stylesheet['Heading1']))
|
_("Election: %s") % assignment.name, stylesheet['Heading1']))
|
||||||
story.append(Spacer(0, 0.5 * cm))
|
story.append(Spacer(0, 0.5 * cm))
|
||||||
# posts
|
# posts
|
||||||
cell1a = []
|
cell1a = []
|
||||||
cell1a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" %
|
cell1a.append(Paragraph(
|
||||||
|
"<font name='Ubuntu-Bold'>%s:</font>" %
|
||||||
_("Number of available posts"), stylesheet['Bold']))
|
_("Number of available posts"), stylesheet['Bold']))
|
||||||
cell1b = []
|
cell1b = []
|
||||||
cell1b.append(Paragraph(str(assignment.posts), stylesheet['Paragraph']))
|
cell1b.append(Paragraph(str(assignment.posts), stylesheet['Paragraph']))
|
||||||
# candidates
|
# candidates
|
||||||
cell2a = []
|
cell2a = []
|
||||||
cell2a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font><seqreset" \
|
cell2a.append(Paragraph(
|
||||||
|
"<font name='Ubuntu-Bold'>%s:</font><seqreset"
|
||||||
" id='counter'>" % _("Candidates"), stylesheet['Heading4']))
|
" id='counter'>" % _("Candidates"), stylesheet['Heading4']))
|
||||||
cell2b = []
|
cell2b = []
|
||||||
for candidate in assignment.candidates:
|
for candidate in assignment.candidates:
|
||||||
cell2b.append(Paragraph("<seq id='counter'/>. %s" % candidate,
|
cell2b.append(Paragraph(
|
||||||
|
"<seq id='counter'/>. %s" % candidate,
|
||||||
stylesheet['Signaturefield']))
|
stylesheet['Signaturefield']))
|
||||||
if assignment.status == "sea":
|
if assignment.status == "sea":
|
||||||
for x in range(0, 2 * assignment.posts):
|
for x in range(0, 2 * assignment.posts):
|
||||||
cell2b.append(Paragraph("<seq id='counter'/>. "
|
cell2b.append(
|
||||||
|
Paragraph(
|
||||||
|
"<seq id='counter'/>. "
|
||||||
"__________________________________________",
|
"__________________________________________",
|
||||||
stylesheet['Signaturefield']))
|
stylesheet['Signaturefield']))
|
||||||
cell2b.append(Spacer(0, 0.2 * cm))
|
cell2b.append(Spacer(0, 0.2 * cm))
|
||||||
@ -392,15 +409,15 @@ class AssignmentPDF(PDFView):
|
|||||||
|
|
||||||
# Left side
|
# Left side
|
||||||
cell3a = []
|
cell3a = []
|
||||||
cell3a.append(Paragraph("%s:" % (_("Vote results")),
|
cell3a.append(Paragraph(
|
||||||
stylesheet['Heading4']))
|
"%s:" % (_("Vote results")), stylesheet['Heading4']))
|
||||||
|
|
||||||
if polls.count() == 1:
|
if polls.count() == 1:
|
||||||
cell3a.append(Paragraph("%s %s" % (polls.count(), _("ballot")),
|
cell3a.append(Paragraph(
|
||||||
stylesheet['Normal']))
|
"%s %s" % (polls.count(), _("ballot")), stylesheet['Normal']))
|
||||||
elif polls.count() > 1:
|
elif polls.count() > 1:
|
||||||
cell3a.append(Paragraph("%s %s" % (polls.count(), _("ballots")),
|
cell3a.append(Paragraph(
|
||||||
stylesheet['Normal']))
|
"%s %s" % (polls.count(), _("ballots")), stylesheet['Normal']))
|
||||||
|
|
||||||
# Add table head row
|
# Add table head row
|
||||||
headrow = []
|
headrow = []
|
||||||
@ -409,24 +426,24 @@ class AssignmentPDF(PDFView):
|
|||||||
headrow.append("%s." % poll.get_ballot())
|
headrow.append("%s." % poll.get_ballot())
|
||||||
data_votes.append(headrow)
|
data_votes.append(headrow)
|
||||||
|
|
||||||
|
|
||||||
# Add result rows
|
# Add result rows
|
||||||
elected_candidates = list(assignment.elected)
|
elected_candidates = list(assignment.elected)
|
||||||
for candidate, poll_list in vote_results.iteritems():
|
for candidate, poll_list in vote_results.iteritems():
|
||||||
row = []
|
row = []
|
||||||
|
|
||||||
candidate_string = candidate.user.get_full_name()
|
candidate_string = candidate.clean_name
|
||||||
if candidate in elected_candidates:
|
if candidate in elected_candidates:
|
||||||
candidate_string = "* " + candidate_string
|
candidate_string = "* " + candidate_string
|
||||||
if candidate.group:
|
if candidate.name_suffix:
|
||||||
candidate_string += "\n(%s)" % candidate.group
|
candidate_string += "\n(%s)" % candidate.name_suffix
|
||||||
row.append(candidate_string)
|
row.append(candidate_string)
|
||||||
for vote in poll_list:
|
for vote in poll_list:
|
||||||
if vote == None:
|
if vote is None:
|
||||||
row.append('–')
|
row.append('–')
|
||||||
elif 'Yes' in vote and 'No' in vote and 'Abstain' in vote:
|
elif 'Yes' in vote and 'No' in vote and 'Abstain' in vote:
|
||||||
row.append(_("Y: %(YES)s\nN: %(NO)s\nA: %(ABSTAIN)s")
|
row.append(
|
||||||
% {'YES':vote['Yes'], 'NO': vote['No'],
|
_("Y: %(YES)s\nN: %(NO)s\nA: %(ABSTAIN)s")
|
||||||
|
% {'YES': vote['Yes'], 'NO': vote['No'],
|
||||||
'ABSTAIN': vote['Abstain']})
|
'ABSTAIN': vote['Abstain']})
|
||||||
elif 'Votes' in vote:
|
elif 'Votes' in vote:
|
||||||
row.append(vote['Votes'])
|
row.append(vote['Votes'])
|
||||||
@ -448,15 +465,14 @@ class AssignmentPDF(PDFView):
|
|||||||
footrow_two.append(poll.print_votescast())
|
footrow_two.append(poll.print_votescast())
|
||||||
data_votes.append(footrow_two)
|
data_votes.append(footrow_two)
|
||||||
|
|
||||||
table_votes=Table(data_votes)
|
table_votes = Table(data_votes)
|
||||||
table_votes.setStyle( TableStyle([
|
table_votes.setStyle(TableStyle([
|
||||||
('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
|
('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
|
||||||
('VALIGN',(0, 0),(-1, -1), 'TOP'),
|
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||||
('LINEABOVE',(0, 0),(-1, 0), 2, colors.black),
|
('LINEABOVE', (0, 0), (-1, 0), 2, colors.black),
|
||||||
('LINEABOVE',(0, 1),(-1, 1), 1, colors.black),
|
('LINEABOVE', (0, 1), (-1, 1), 1, colors.black),
|
||||||
('LINEBELOW',(0, -1),(-1, -1), 2, colors.black),
|
('LINEBELOW', (0, -1), (-1, -1), 2, colors.black),
|
||||||
('ROWBACKGROUNDS', (0, 1), (-1, -1), (colors.white, (.9, .9, .9))),
|
('ROWBACKGROUNDS', (0, 1), (-1, -1), (colors.white, (.9, .9, .9)))]))
|
||||||
]))
|
|
||||||
|
|
||||||
# table
|
# table
|
||||||
data = []
|
data = []
|
||||||
@ -467,17 +483,18 @@ class AssignmentPDF(PDFView):
|
|||||||
else:
|
else:
|
||||||
data.append([cell2a, cell2b])
|
data.append([cell2a, cell2b])
|
||||||
data.append([Spacer(0, 0.2 * cm), ''])
|
data.append([Spacer(0, 0.2 * cm), ''])
|
||||||
t=Table(data)
|
t = Table(data)
|
||||||
t._argW[0] = 4.5 * cm
|
t._argW[0] = 4.5 * cm
|
||||||
t._argW[1] = 11 * cm
|
t._argW[1] = 11 * cm
|
||||||
t.setStyle(TableStyle([ ('BOX', (0,0), (-1, -1), 1, colors.black),
|
t.setStyle(TableStyle([
|
||||||
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
('BOX', (0, 0), (-1, -1), 1, colors.black),
|
||||||
]))
|
('VALIGN', (0, 0), (-1, -1), 'TOP')]))
|
||||||
story.append(t)
|
story.append(t)
|
||||||
story.append(Spacer(0, 1 * cm))
|
story.append(Spacer(0, 1 * cm))
|
||||||
|
|
||||||
# text
|
# text
|
||||||
story.append(Paragraph("%s" % assignment.description.replace('\r\n',
|
story.append(Paragraph(
|
||||||
|
"%s" % assignment.description.replace('\r\n',
|
||||||
'<br/>'), stylesheet['Paragraph']))
|
'<br/>'), stylesheet['Paragraph']))
|
||||||
|
|
||||||
|
|
||||||
@ -502,13 +519,15 @@ class AssignmentPollPDF(PDFView):
|
|||||||
return super(AssignmentPollPDF, self).get(request, *args, **kwargs)
|
return super(AssignmentPollPDF, self).get(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_filename(self):
|
def get_filename(self):
|
||||||
filename = u'%s-%s_%s' % (_("Election"), self.poll.assignment.name.replace(' ', '_'),
|
filename = u'%s-%s_%s' % (
|
||||||
|
_("Election"), self.poll.assignment.name.replace(' ', '_'),
|
||||||
self.poll.get_ballot())
|
self.poll.get_ballot())
|
||||||
return filename
|
return filename
|
||||||
|
|
||||||
def get_template(self, buffer):
|
def get_template(self, buffer):
|
||||||
return SimpleDocTemplate(buffer, topMargin=-6, bottomMargin=-6,
|
return SimpleDocTemplate(
|
||||||
leftMargin=0, rightMargin=0, showBoundary=False)
|
buffer, topMargin=-6, bottomMargin=-6, leftMargin=0, rightMargin=0,
|
||||||
|
showBoundary=False)
|
||||||
|
|
||||||
def build_document(self, pdf_document, story):
|
def build_document(self, pdf_document, story):
|
||||||
pdf_document.build(story)
|
pdf_document.build(story)
|
||||||
@ -517,31 +536,36 @@ class AssignmentPollPDF(PDFView):
|
|||||||
imgpath = os.path.join(settings.SITE_ROOT, 'static/images/circle.png')
|
imgpath = os.path.join(settings.SITE_ROOT, 'static/images/circle.png')
|
||||||
circle = "<img src='%s' width='15' height='15'/> " % imgpath
|
circle = "<img src='%s' width='15' height='15'/> " % imgpath
|
||||||
cell = []
|
cell = []
|
||||||
cell.append(Spacer(0,0.8*cm))
|
cell.append(Spacer(0, 0.8 * cm))
|
||||||
cell.append(Paragraph(_("Election") + ": " + self.poll.assignment.name,
|
cell.append(Paragraph(
|
||||||
|
_("Election") + ": " + self.poll.assignment.name,
|
||||||
stylesheet['Ballot_title']))
|
stylesheet['Ballot_title']))
|
||||||
cell.append(Paragraph(self.poll.assignment.polldescription,
|
cell.append(Paragraph(
|
||||||
|
self.poll.assignment.polldescription,
|
||||||
stylesheet['Ballot_subtitle']))
|
stylesheet['Ballot_subtitle']))
|
||||||
options = self.poll.get_options().order_by('candidate')
|
options = self.poll.get_options()
|
||||||
|
|
||||||
ballot_string = _("%d. ballot") % self.poll.get_ballot()
|
ballot_string = _("%d. ballot") % self.poll.get_ballot()
|
||||||
candidate_string = ungettext("%d candidate", "%d candidates",
|
candidate_string = ungettext(
|
||||||
len(options)) % len(options)
|
"%d candidate", "%d candidates", len(options)) % len(options)
|
||||||
available_posts_string = _("%d available posts") % self.poll.assignment.posts
|
available_posts_string = ungettext(
|
||||||
cell.append(Paragraph("%s, %s, %s" % (ballot_string, candidate_string,
|
"%d available post", "%d available posts",
|
||||||
|
self.poll.assignment.posts) % self.poll.assignment.posts
|
||||||
|
cell.append(Paragraph(
|
||||||
|
"%s, %s, %s" % (ballot_string, candidate_string,
|
||||||
available_posts_string), stylesheet['Ballot_description']))
|
available_posts_string), stylesheet['Ballot_description']))
|
||||||
cell.append(Spacer(0, 0.4 * cm))
|
cell.append(Spacer(0, 0.4 * cm))
|
||||||
|
|
||||||
data= []
|
data = []
|
||||||
# get ballot papers config values
|
# get ballot papers config values
|
||||||
ballot_papers_selection = config["assignment_pdf_ballot_papers_selection"]
|
ballot_papers_selection = config["assignment_pdf_ballot_papers_selection"]
|
||||||
ballot_papers_number = config["assignment_pdf_ballot_papers_number"]
|
ballot_papers_number = config["assignment_pdf_ballot_papers_number"]
|
||||||
|
|
||||||
# set number of ballot papers
|
# set number of ballot papers
|
||||||
if ballot_papers_selection == "NUMBER_OF_DELEGATES":
|
if ballot_papers_selection == "NUMBER_OF_DELEGATES":
|
||||||
number = User.objects.filter(profile__type__iexact="delegate").count()
|
number = User.objects.filter(type__iexact="delegate").count()
|
||||||
elif ballot_papers_selection == "NUMBER_OF_ALL_PARTICIPANTS":
|
elif ballot_papers_selection == "NUMBER_OF_ALL_PARTICIPANTS":
|
||||||
number = int(Profile.objects.count())
|
number = int(User.objects.count())
|
||||||
else: # ballot_papers_selection == "CUSTOM_NUMBER"
|
else: # ballot_papers_selection == "CUSTOM_NUMBER"
|
||||||
number = int(ballot_papers_number)
|
number = int(ballot_papers_number)
|
||||||
number = max(1, number)
|
number = max(1, number)
|
||||||
@ -550,16 +574,18 @@ class AssignmentPollPDF(PDFView):
|
|||||||
if self.poll.yesnoabstain:
|
if self.poll.yesnoabstain:
|
||||||
for option in options:
|
for option in options:
|
||||||
candidate = option.candidate
|
candidate = option.candidate
|
||||||
cell.append(Paragraph(candidate.user.get_full_name(),
|
cell.append(Paragraph(
|
||||||
stylesheet['Ballot_option_name']))
|
candidate.clean_name, stylesheet['Ballot_option_name']))
|
||||||
if candidate.name_surfix:
|
if candidate.name_suffix:
|
||||||
cell.append(Paragraph("(%s)" % candidate.name_surfix,
|
cell.append(Paragraph(
|
||||||
|
"(%s)" % candidate.name_suffix,
|
||||||
stylesheet['Ballot_option_group']))
|
stylesheet['Ballot_option_group']))
|
||||||
else:
|
else:
|
||||||
cell.append(Paragraph(" ",
|
cell.append(Paragraph(
|
||||||
stylesheet['Ballot_option_group']))
|
" ", stylesheet['Ballot_option_group']))
|
||||||
cell.append(Paragraph(circle + _("Yes") + " " * 3 + circle
|
cell.append(Paragraph(
|
||||||
+ _("No") + " " * 3 + circle+ _("Abstention"),
|
circle + _("Yes") + " " * 3 + circle
|
||||||
|
+ _("No") + " " * 3 + circle + _("Abstention"),
|
||||||
stylesheet['Ballot_option_YNA']))
|
stylesheet['Ballot_option_YNA']))
|
||||||
# print ballot papers
|
# print ballot papers
|
||||||
for user in xrange(number / 2):
|
for user in xrange(number / 2):
|
||||||
@ -576,14 +602,16 @@ class AssignmentPollPDF(PDFView):
|
|||||||
else:
|
else:
|
||||||
for option in options:
|
for option in options:
|
||||||
candidate = option.candidate
|
candidate = option.candidate
|
||||||
cell.append(Paragraph(circle + candidate.user.get_full_name(),
|
cell.append(Paragraph(
|
||||||
|
circle + candidate.clean_name,
|
||||||
stylesheet['Ballot_option_name']))
|
stylesheet['Ballot_option_name']))
|
||||||
if candidate.group:
|
if candidate.name_suffix:
|
||||||
cell.append(Paragraph("(%s)" % candidate.group,
|
cell.append(Paragraph(
|
||||||
|
"(%s)" % candidate.name_suffix,
|
||||||
stylesheet['Ballot_option_group_right']))
|
stylesheet['Ballot_option_group_right']))
|
||||||
else:
|
else:
|
||||||
cell.append(Paragraph(" ",
|
cell.append(Paragraph(
|
||||||
stylesheet['Ballot_option_group_right']))
|
" ", stylesheet['Ballot_option_group_right']))
|
||||||
# print ballot papers
|
# print ballot papers
|
||||||
for user in xrange(number / 2):
|
for user in xrange(number / 2):
|
||||||
data.append([cell, cell])
|
data.append([cell, cell])
|
||||||
@ -597,9 +625,9 @@ class AssignmentPollPDF(PDFView):
|
|||||||
else:
|
else:
|
||||||
t = Table(data, 10.5 * cm, 29.7 * cm)
|
t = Table(data, 10.5 * cm, 29.7 * cm)
|
||||||
|
|
||||||
t.setStyle(TableStyle([('GRID', (0, 0), (-1, -1), 0.25, colors.grey),
|
t.setStyle(TableStyle([
|
||||||
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
('GRID', (0, 0), (-1, -1), 0.25, colors.grey),
|
||||||
]))
|
('VALIGN', (0, 0), (-1, -1), 'TOP')]))
|
||||||
story.append(t)
|
story.append(t)
|
||||||
|
|
||||||
|
|
||||||
@ -619,8 +647,7 @@ class Config(FormView):
|
|||||||
'assignment_pdf_title': config['assignment_pdf_title'],
|
'assignment_pdf_title': config['assignment_pdf_title'],
|
||||||
'assignment_pdf_preamble': config['assignment_pdf_preamble'],
|
'assignment_pdf_preamble': config['assignment_pdf_preamble'],
|
||||||
'assignment_poll_vote_values':
|
'assignment_poll_vote_values':
|
||||||
config['assignment_poll_vote_values'],
|
config['assignment_poll_vote_values']}
|
||||||
}
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
if form.cleaned_data['assignment_publish_winner_results_only']:
|
if form.cleaned_data['assignment_publish_winner_results_only']:
|
||||||
@ -637,8 +664,8 @@ class Config(FormView):
|
|||||||
form.cleaned_data['assignment_pdf_preamble']
|
form.cleaned_data['assignment_pdf_preamble']
|
||||||
config['assignment_poll_vote_values'] = \
|
config['assignment_poll_vote_values'] = \
|
||||||
form.cleaned_data['assignment_poll_vote_values']
|
form.cleaned_data['assignment_poll_vote_values']
|
||||||
messages.success(self.request,
|
messages.success(
|
||||||
_('Election settings successfully saved.'))
|
self.request, _('Election settings successfully saved.'))
|
||||||
return super(Config, self).form_valid(form)
|
return super(Config, self).form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
@ -648,10 +675,11 @@ def register_tab(request):
|
|||||||
title=_('Elections'),
|
title=_('Elections'),
|
||||||
app='assignment',
|
app='assignment',
|
||||||
url=reverse('assignment_overview'),
|
url=reverse('assignment_overview'),
|
||||||
permission=request.user.has_perm('assignment.can_see_assignment')
|
permission=(
|
||||||
or request.user.has_perm('assignment.can_nominate_other')
|
request.user.has_perm('assignment.can_see_assignment') or
|
||||||
or request.user.has_perm('assignment.can_nominate_self')
|
request.user.has_perm('assignment.can_nominate_other') or
|
||||||
or request.user.has_perm('assignment.can_manage_assignment'),
|
request.user.has_perm('assignment.can_nominate_self') or
|
||||||
|
request.user.has_perm('assignment.can_manage_assignment')),
|
||||||
selected=selected,
|
selected=selected,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -660,6 +688,7 @@ def get_widgets(request):
|
|||||||
return [
|
return [
|
||||||
Widget(
|
Widget(
|
||||||
name='assignments',
|
name='assignments',
|
||||||
|
display_name=_('Elections'),
|
||||||
template='assignment/widget.html',
|
template='assignment/widget.html',
|
||||||
context={'assignments': Assignment.objects.all()},
|
context={'assignments': Assignment.objects.all()},
|
||||||
permission_required='assignment.can_manage_assignment')]
|
permission_required='projector.can_manage_projector')]
|
||||||
|
@ -15,8 +15,6 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
|
|
||||||
from openslides.utils.forms import CssClassMixin
|
from openslides.utils.forms import CssClassMixin
|
||||||
|
|
||||||
from openslides.config.models import config
|
|
||||||
|
|
||||||
|
|
||||||
class GeneralConfigForm(forms.Form, CssClassMixin):
|
class GeneralConfigForm(forms.Form, CssClassMixin):
|
||||||
event_name = forms.CharField(
|
event_name = forms.CharField(
|
||||||
@ -56,13 +54,13 @@ class GeneralConfigForm(forms.Form, CssClassMixin):
|
|||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
frontpage_title = forms.CharField(
|
welcome_title = forms.CharField(
|
||||||
widget=forms.TextInput(),
|
widget=forms.TextInput(),
|
||||||
label=_("Title"),
|
label=_("Title"),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
frontpage_welcometext = forms.CharField(
|
welcome_text = forms.CharField(
|
||||||
widget=forms.Textarea(),
|
widget=forms.Textarea(),
|
||||||
label=_("Welcome text"),
|
label=_("Welcome text"),
|
||||||
required=False,
|
required=False,
|
||||||
|
@ -69,7 +69,6 @@ class Config(object):
|
|||||||
def __contains__(self, item):
|
def __contains__(self, item):
|
||||||
return ConfigStore.objects.filter(key=item).exists()
|
return ConfigStore.objects.filter(key=item).exists()
|
||||||
|
|
||||||
|
|
||||||
config = Config()
|
config = Config()
|
||||||
|
|
||||||
|
|
||||||
@ -81,17 +80,13 @@ def default_config(sender, key, **kwargs):
|
|||||||
return {
|
return {
|
||||||
'event_name': 'OpenSlides',
|
'event_name': 'OpenSlides',
|
||||||
'event_description':
|
'event_description':
|
||||||
_('Presentation system for agenda, motions and elections'),
|
_('Presentation and assembly system'),
|
||||||
'event_date': '',
|
'event_date': '',
|
||||||
'event_location': '',
|
'event_location': '',
|
||||||
'event_organizer': '',
|
'event_organizer': '',
|
||||||
'presentation': '',
|
'presentation': '',
|
||||||
'frontpage_title': _('Welcome'),
|
'welcome_title': _('Welcome to OpenSlides'),
|
||||||
'frontpage_welcometext': _('Welcome to OpenSlides!'),
|
'welcome_text': _('[Place for your welcome text.]'),
|
||||||
'show_help_text': True,
|
|
||||||
'help_text': _("Get professional support for OpenSlides on %s.") %
|
|
||||||
"<a href='http://openslides.org/' target='_blank'> \
|
|
||||||
www.openslides.org</a>",
|
|
||||||
'system_enable_anonymous': False,
|
'system_enable_anonymous': False,
|
||||||
}.get(key)
|
}.get(key)
|
||||||
|
|
||||||
@ -127,11 +122,9 @@ def set_submenu(sender, request, context, **kwargs):
|
|||||||
(reverse('config_%s' % appname), _(title), selected)
|
(reverse('config_%s' % appname), _(title), selected)
|
||||||
)
|
)
|
||||||
|
|
||||||
menu_links.append (
|
menu_links.append((
|
||||||
(reverse('config_version'), _('Version'),
|
reverse('config_version'), _('Version'),
|
||||||
request.path == reverse('config_version'))
|
request.path == reverse('config_version')))
|
||||||
)
|
|
||||||
|
|
||||||
context.update({
|
context.update({
|
||||||
'menu_links': menu_links,
|
'menu_links': menu_links})
|
||||||
})
|
|
||||||
|
@ -25,9 +25,9 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
<p></p>
|
<p></p>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{% trans "Frontpage" %}</legend>
|
<legend>{% trans "Welcome Widget" %}</legend>
|
||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
{% if "id_frontpage" in field.label_tag %}
|
{% if "id_welcome" in field.label_tag %}
|
||||||
<p>
|
<p>
|
||||||
{{ field.errors }}
|
{{ field.errors }}
|
||||||
{{ field.required }}
|
{{ field.required }}
|
||||||
|
@ -12,18 +12,18 @@
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.models import Group, Permission
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.utils.importlib import import_module
|
from django.utils.importlib import import_module
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from openslides import get_version
|
from openslides import get_version, get_git_commit_id, RELEASE
|
||||||
|
|
||||||
from openslides.utils.template import Tab
|
from openslides.utils.template import Tab
|
||||||
from openslides.utils.views import FormView, TemplateView
|
from openslides.utils.views import FormView, TemplateView
|
||||||
|
from .forms import GeneralConfigForm
|
||||||
|
from .models import config
|
||||||
|
|
||||||
from openslides.config.forms import GeneralConfigForm
|
# TODO: Do not import the participant module in config
|
||||||
from openslides.config.models import config
|
from openslides.participant.api import get_or_create_anonymous_group
|
||||||
|
|
||||||
|
|
||||||
class GeneralConfig(FormView):
|
class GeneralConfig(FormView):
|
||||||
@ -41,8 +41,8 @@ class GeneralConfig(FormView):
|
|||||||
'event_date': config['event_date'],
|
'event_date': config['event_date'],
|
||||||
'event_location': config['event_location'],
|
'event_location': config['event_location'],
|
||||||
'event_organizer': config['event_organizer'],
|
'event_organizer': config['event_organizer'],
|
||||||
'frontpage_title': config['frontpage_title'],
|
'welcome_title': config['welcome_title'],
|
||||||
'frontpage_welcometext': config['frontpage_welcometext'],
|
'welcome_text': config['welcome_text'],
|
||||||
'system_enable_anonymous': config['system_enable_anonymous'],
|
'system_enable_anonymous': config['system_enable_anonymous'],
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,34 +54,19 @@ class GeneralConfig(FormView):
|
|||||||
config['event_location'] = form.cleaned_data['event_location']
|
config['event_location'] = form.cleaned_data['event_location']
|
||||||
config['event_organizer'] = form.cleaned_data['event_organizer']
|
config['event_organizer'] = form.cleaned_data['event_organizer']
|
||||||
|
|
||||||
# frontpage
|
# welcome widget
|
||||||
config['frontpage_title'] = form.cleaned_data['frontpage_title']
|
config['welcome_title'] = form.cleaned_data['welcome_title']
|
||||||
config['frontpage_welcometext'] = \
|
config['welcome_text'] = form.cleaned_data['welcome_text']
|
||||||
form.cleaned_data['frontpage_welcometext']
|
|
||||||
|
|
||||||
# system
|
# system
|
||||||
if form.cleaned_data['system_enable_anonymous']:
|
if form.cleaned_data['system_enable_anonymous']:
|
||||||
config['system_enable_anonymous'] = True
|
config['system_enable_anonymous'] = True
|
||||||
# check for Anonymous group and (re)create it as needed
|
get_or_create_anonymous_group()
|
||||||
try:
|
|
||||||
anonymous = Group.objects.get(name='Anonymous')
|
|
||||||
except Group.DoesNotExist:
|
|
||||||
default_perms = [u'can_see_agenda', u'can_see_projector',
|
|
||||||
u'can_see_application', u'can_see_assignment']
|
|
||||||
anonymous = Group()
|
|
||||||
anonymous.name = 'Anonymous'
|
|
||||||
anonymous.save()
|
|
||||||
anonymous.permissions = Permission.objects.filter(
|
|
||||||
codename__in=default_perms)
|
|
||||||
anonymous.save()
|
|
||||||
messages.success(self.request,
|
|
||||||
_('Anonymous access enabled. Please modify the "Anonymous" ' \
|
|
||||||
'group to fit your required permissions.'))
|
|
||||||
else:
|
else:
|
||||||
config['system_enable_anonymous'] = False
|
config['system_enable_anonymous'] = False
|
||||||
|
|
||||||
messages.success(self.request,
|
messages.success(
|
||||||
_('General settings successfully saved.'))
|
self.request, _('General settings successfully saved.'))
|
||||||
return super(GeneralConfig, self).form_valid(form)
|
return super(GeneralConfig, self).form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
@ -94,7 +79,14 @@ class VersionConfig(TemplateView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(VersionConfig, self).get_context_data(**kwargs)
|
context = super(VersionConfig, self).get_context_data(**kwargs)
|
||||||
context['versions'] = [('OpenSlides', get_version())]
|
|
||||||
|
# OpenSlides version. During development the git commit id is added.
|
||||||
|
openslides_version_string = get_version()
|
||||||
|
if not RELEASE:
|
||||||
|
openslides_version_string += ' Commit: %s' % get_git_commit_id()
|
||||||
|
context['versions'] = [('OpenSlides', openslides_version_string)]
|
||||||
|
|
||||||
|
# Version of plugins.
|
||||||
for plugin in settings.INSTALLED_PLUGINS:
|
for plugin in settings.INSTALLED_PLUGINS:
|
||||||
try:
|
try:
|
||||||
mod = import_module(plugin)
|
mod = import_module(plugin)
|
||||||
@ -105,7 +97,6 @@ class VersionConfig(TemplateView):
|
|||||||
plugin_name = mod.NAME
|
plugin_name = mod.NAME
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
plugin_name = mod.__name__.split('.')[0]
|
plugin_name = mod.__name__.split('.')[0]
|
||||||
|
|
||||||
context['versions'].append((plugin_name, plugin_version))
|
context['versions'].append((plugin_name, plugin_version))
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
@ -13,15 +13,12 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
_fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
|
from openslides.main import fs2unicode
|
||||||
def _fs2unicode(s):
|
|
||||||
if isinstance(s, unicode):
|
|
||||||
return s
|
|
||||||
return s.decode(_fs_encoding)
|
|
||||||
|
|
||||||
SITE_ROOT = os.path.realpath(os.path.dirname(__file__))
|
SITE_ROOT = os.path.realpath(os.path.dirname(__file__))
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',
|
AUTHENTICATION_BACKENDS = (
|
||||||
|
'django.contrib.auth.backends.ModelBackend',
|
||||||
'openslides.utils.auth.AnonymousAuth',)
|
'openslides.utils.auth.AnonymousAuth',)
|
||||||
|
|
||||||
LOGIN_URL = '/login/'
|
LOGIN_URL = '/login/'
|
||||||
@ -35,6 +32,7 @@ ugettext = lambda s: s
|
|||||||
LANGUAGES = (
|
LANGUAGES = (
|
||||||
('de', ugettext('German')),
|
('de', ugettext('German')),
|
||||||
('en', ugettext('English')),
|
('en', ugettext('English')),
|
||||||
|
('fr', ugettext('French')),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -47,12 +45,12 @@ USE_I18N = True
|
|||||||
USE_L10N = True
|
USE_L10N = True
|
||||||
|
|
||||||
LOCALE_PATHS = (
|
LOCALE_PATHS = (
|
||||||
_fs2unicode(os.path.join(SITE_ROOT, 'locale')),
|
fs2unicode(os.path.join(SITE_ROOT, 'locale')),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Absolute path to the directory that holds media.
|
# Absolute path to the directory that holds media.
|
||||||
# Example: "/home/media/media.lawrence.com/"
|
# Example: "/home/media/media.lawrence.com/"
|
||||||
MEDIA_ROOT = _fs2unicode(os.path.join(SITE_ROOT, './static/'))
|
MEDIA_ROOT = fs2unicode(os.path.join(SITE_ROOT, './static/'))
|
||||||
|
|
||||||
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
|
# 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).
|
# trailing slash if there is a path component (optional in other cases).
|
||||||
@ -61,7 +59,7 @@ MEDIA_URL = ''
|
|||||||
|
|
||||||
# Absolute path to the directory that holds static media from ``collectstatic``
|
# Absolute path to the directory that holds static media from ``collectstatic``
|
||||||
# Example: "/home/media/static.lawrence.com/"
|
# Example: "/home/media/static.lawrence.com/"
|
||||||
STATIC_ROOT = _fs2unicode(os.path.join(SITE_ROOT, '../site-static'))
|
STATIC_ROOT = fs2unicode(os.path.join(SITE_ROOT, '../site-static'))
|
||||||
|
|
||||||
# URL that handles the media served from STATIC_ROOT. Make sure to use a
|
# URL that handles the media served from STATIC_ROOT. Make sure to use a
|
||||||
# trailing slash if there is a path component (optional in other cases).
|
# trailing slash if there is a path component (optional in other cases).
|
||||||
@ -71,7 +69,7 @@ STATIC_URL = '/static/'
|
|||||||
# Additional directories containing static files (not application specific)
|
# Additional directories containing static files (not application specific)
|
||||||
# Examples: "/home/media/lawrence.com/extra-static/"
|
# Examples: "/home/media/lawrence.com/extra-static/"
|
||||||
STATICFILES_DIRS = (
|
STATICFILES_DIRS = (
|
||||||
_fs2unicode(os.path.join(SITE_ROOT, 'static')),
|
fs2unicode(os.path.join(SITE_ROOT, 'static')),
|
||||||
)
|
)
|
||||||
|
|
||||||
#XXX: Note this setting (as well as our workaround finder)
|
#XXX: Note this setting (as well as our workaround finder)
|
||||||
@ -105,7 +103,7 @@ TEMPLATE_DIRS = (
|
|||||||
# "C:/www/django/templates".
|
# "C:/www/django/templates".
|
||||||
# Always use forward slashes, even on Windows.
|
# Always use forward slashes, even on Windows.
|
||||||
# Don't forget to use absolute paths, not relative paths.
|
# Don't forget to use absolute paths, not relative paths.
|
||||||
_fs2unicode(os.path.join(SITE_ROOT, 'templates')),
|
fs2unicode(os.path.join(SITE_ROOT, 'templates')),
|
||||||
)
|
)
|
||||||
|
|
||||||
INSTALLED_APPS = (
|
INSTALLED_APPS = (
|
||||||
@ -119,7 +117,7 @@ INSTALLED_APPS = (
|
|||||||
'openslides.poll',
|
'openslides.poll',
|
||||||
'openslides.projector',
|
'openslides.projector',
|
||||||
'openslides.agenda',
|
'openslides.agenda',
|
||||||
'openslides.application',
|
'openslides.motion',
|
||||||
'openslides.assignment',
|
'openslides.assignment',
|
||||||
'openslides.participant',
|
'openslides.participant',
|
||||||
'openslides.config',
|
'openslides.config',
|
||||||
@ -131,6 +129,15 @@ TEMPLATE_CONTEXT_PROCESSORS = (
|
|||||||
'django.core.context_processors.request',
|
'django.core.context_processors.request',
|
||||||
'django.core.context_processors.i18n',
|
'django.core.context_processors.i18n',
|
||||||
'django.core.context_processors.static',
|
'django.core.context_processors.static',
|
||||||
'openslides.utils.utils.revision',
|
|
||||||
'openslides.utils.auth.anonymous_context_additions',
|
'openslides.utils.auth.anonymous_context_additions',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CACHES = {
|
||||||
|
'default': {
|
||||||
|
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||||
|
'LOCATION': 'openslidecache'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_RUNNER = 'discover_runner.DiscoverRunner'
|
||||||
|
TEST_DISCOVER_TOP_LEVEL = os.path.dirname(os.path.dirname(__file__))
|
@ -6,8 +6,10 @@ Instruction to update translation for OpenSlides:
|
|||||||
|
|
||||||
2. Update the German po file (locale/de/LC_MESSAGES/django.po):
|
2. Update the German po file (locale/de/LC_MESSAGES/django.po):
|
||||||
$ django-admin.py makemessages -l de
|
$ django-admin.py makemessages -l de
|
||||||
|
$ django-admin.py makemessages -l de -d djangojs
|
||||||
|
|
||||||
3. Edit the German po file: locale/de/LC_MESSAGES/django.po
|
3. Edit the German po file: locale/de/LC_MESSAGES/django.po
|
||||||
|
Don't forget the js-file: locale/de/LC_MESSAGES/djangojs.po
|
||||||
(Search for "fuzzy" and empty msgstr entries.)
|
(Search for "fuzzy" and empty msgstr entries.)
|
||||||
|
|
||||||
4. Update the German mo file (locale/de/LC_MESSAGES/django.mo):
|
4. Update the German mo file (locale/de/LC_MESSAGES/django.mo):
|
||||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
openslides/locale/de/LC_MESSAGES/djangojs.mo
Normal file
BIN
openslides/locale/de/LC_MESSAGES/djangojs.mo
Normal file
Binary file not shown.
22
openslides/locale/de/LC_MESSAGES/djangojs.po
Normal file
22
openslides/locale/de/LC_MESSAGES/djangojs.po
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: OpenSlides 1.3\n"
|
||||||
|
"Report-Msgid-Bugs-To: support@openslides.org\n"
|
||||||
|
"POT-Creation-Date: 2012-12-09 11:12+0100\n"
|
||||||
|
"PO-Revision-Date: 2012-07-28 11:07+0200\n"
|
||||||
|
"Last-Translator: Oskar Hahn <mail@oshahn.de>\n"
|
||||||
|
"Language: de\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
#: agenda/static/javascript/agenda.js:27
|
||||||
|
#, c-format
|
||||||
|
msgid ", of which %s are hidden."
|
||||||
|
msgstr ", davon %s verborgen."
|
BIN
openslides/locale/fr/LC_MESSAGES/django.mo
Normal file
BIN
openslides/locale/fr/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
2394
openslides/locale/fr/LC_MESSAGES/django.po
Normal file
2394
openslides/locale/fr/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
BIN
openslides/locale/fr/LC_MESSAGES/djangojs.mo
Normal file
BIN
openslides/locale/fr/LC_MESSAGES/djangojs.mo
Normal file
Binary file not shown.
25
openslides/locale/fr/LC_MESSAGES/djangojs.po
Normal file
25
openslides/locale/fr/LC_MESSAGES/djangojs.po
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Translators:
|
||||||
|
# <moosline@savvy.ch>, 2012.
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: OpenSlides\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2012-12-09 14:17+0100\n"
|
||||||
|
"PO-Revision-Date: 2012-12-09 11:46+0000\n"
|
||||||
|
"Last-Translator: moosline <moosline@savvy.ch>\n"
|
||||||
|
"Language-Team: French (http://www.transifex.com/projects/p/openslides/"
|
||||||
|
"language/fr/)\n"
|
||||||
|
"Language: fr\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
|
||||||
|
#: agenda/static/javascript/agenda.js:27
|
||||||
|
#, c-format
|
||||||
|
msgid ", of which %s are hidden."
|
||||||
|
msgstr ", dans %s sont cachés"
|
216
openslides/main.py
Normal file → Executable file
216
openslides/main.py
Normal file → Executable file
@ -13,29 +13,34 @@
|
|||||||
# for python 2.5 support
|
# for python 2.5 support
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import optparse
|
|
||||||
import socket
|
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
import base64
|
import base64
|
||||||
|
import ctypes
|
||||||
|
import optparse
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
|
||||||
import django.conf
|
import django.conf
|
||||||
from django.core.management import execute_from_command_line
|
from django.core.management import execute_from_command_line
|
||||||
|
|
||||||
|
from openslides import get_version
|
||||||
|
|
||||||
CONFIG_TEMPLATE = """#!/usr/bin/env python
|
CONFIG_TEMPLATE = """#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from openslides.openslides_global_settings import *
|
import openslides.main
|
||||||
|
from openslides.global_settings import *
|
||||||
|
|
||||||
# Use 'DEBUG = True' to get more details for server errors
|
# Use 'DEBUG = True' to get more details for server errors
|
||||||
# (Default for relaeses: 'False')
|
# (Default for releases: 'False')
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
TEMPLATE_DEBUG = DEBUG
|
TEMPLATE_DEBUG = DEBUG
|
||||||
|
|
||||||
DBPATH = %(dbpath)r
|
DBPATH = %(dbpath)s
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
@ -64,51 +69,89 @@ INSTALLED_APPS += INSTALLED_PLUGINS
|
|||||||
|
|
||||||
KEY_LENGTH = 30
|
KEY_LENGTH = 30
|
||||||
|
|
||||||
|
# sentinel used to signal that the database ought to be stored
|
||||||
_fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
|
# relative to the portable's directory
|
||||||
def _fs2unicode(s):
|
_portable_db_path = object()
|
||||||
if isinstance(s, unicode):
|
|
||||||
return s
|
|
||||||
return s.decode(_fs_encoding)
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv=None, opt_defaults=None):
|
def process_options(argv=None):
|
||||||
if argv is None:
|
if argv is None:
|
||||||
argv = sys.argv[1:]
|
argv = sys.argv[1:]
|
||||||
|
|
||||||
parser = optparse.OptionParser(
|
parser = optparse.OptionParser(
|
||||||
description="Run openslides using django's builtin webserver")
|
description="Run openslides using django's builtin webserver")
|
||||||
parser.add_option("-a", "--address", help="IP Address to listen on")
|
parser.add_option("-a", "--address", help="IP Address to listen on.")
|
||||||
parser.add_option("-p", "--port", type="int", help="Port to listen on")
|
parser.add_option("-p", "--port", type="int", help="Port to listen on.")
|
||||||
parser.add_option(
|
parser.add_option(
|
||||||
"--syncdb", action="store_true",
|
"--syncdb", action="store_true",
|
||||||
help="Update/create database before starting the server")
|
help="Update/create database before starting the server.")
|
||||||
parser.add_option(
|
parser.add_option(
|
||||||
"--reset-admin", action="store_true",
|
"--reset-admin", action="store_true",
|
||||||
help="Make sure the user 'admin' exists and uses 'admin' as password")
|
help="Make sure the user 'admin' exists and uses 'admin' as password.")
|
||||||
parser.add_option("-s", "--settings", help="Path to the setting.")
|
|
||||||
parser.add_option(
|
parser.add_option(
|
||||||
"--no-reload", action="store_true", help="Do not reload the development server")
|
"-s", "--settings", help="Path to the openslides configuration.")
|
||||||
|
parser.add_option(
|
||||||
if not opt_defaults is None:
|
"--no-reload", action="store_true",
|
||||||
parser.set_defaults(**opt_defaults)
|
help="Do not reload the development server.")
|
||||||
|
parser.add_option(
|
||||||
|
"--version", action="store_true",
|
||||||
|
help="Show version and exit.")
|
||||||
|
|
||||||
opts, args = parser.parse_args(argv)
|
opts, args = parser.parse_args(argv)
|
||||||
|
if opts.version:
|
||||||
|
print get_version()
|
||||||
|
exit(0)
|
||||||
if args:
|
if args:
|
||||||
sys.stderr.write("This command does not take arguments!\n\n")
|
sys.stderr.write("This command does not take arguments!\n\n")
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
return opts
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None):
|
||||||
|
opts = process_options(argv)
|
||||||
|
_main(opts)
|
||||||
|
|
||||||
|
|
||||||
|
def win32_portable_main(argv=None):
|
||||||
|
"""special entry point for the win32 portable version"""
|
||||||
|
|
||||||
|
opts = process_options(argv)
|
||||||
|
|
||||||
|
database_path = None
|
||||||
|
|
||||||
|
if opts.settings is None:
|
||||||
|
portable_dir = get_portable_path()
|
||||||
|
try:
|
||||||
|
fd, test_file = tempfile.mkstemp(dir=portable_dir)
|
||||||
|
except OSError:
|
||||||
|
portable_dir_writeable = False
|
||||||
|
else:
|
||||||
|
portable_dir_writeable = True
|
||||||
|
os.close(fd)
|
||||||
|
os.unlink(test_file)
|
||||||
|
|
||||||
|
if portable_dir_writeable:
|
||||||
|
opts.settings = os.path.join(
|
||||||
|
portable_dir, "openslides", "settings.py")
|
||||||
|
database_path = _portable_db_path
|
||||||
|
|
||||||
|
_main(opts, database_path=database_path)
|
||||||
|
|
||||||
|
|
||||||
|
def _main(opts, database_path=None):
|
||||||
# Find the path to the settings
|
# Find the path to the settings
|
||||||
settings = opts.settings or \
|
settings_path = opts.settings
|
||||||
os.path.join(os.path.expanduser('~'),'.openslides','openslides_personal_settings.py')
|
if settings_path is None:
|
||||||
|
settings_path = get_user_config_path('openslides', 'settings.py')
|
||||||
|
|
||||||
# Create settings if necessary
|
# Create settings if necessary
|
||||||
if not os.path.exists(settings):
|
if not os.path.exists(settings_path):
|
||||||
create_settings(settings)
|
create_settings(settings_path, database_path)
|
||||||
|
|
||||||
# Set the django environment to the settings
|
# Set the django environment to the settings
|
||||||
setup_django_environment(settings)
|
setup_django_environment(settings_path)
|
||||||
|
|
||||||
# Find url to openslides
|
# Find url to openslides
|
||||||
addr, port = detect_listen_opts(opts.address, opts.port)
|
addr, port = detect_listen_opts(opts.address, opts.port)
|
||||||
@ -135,24 +178,40 @@ def main(argv=None, opt_defaults=None):
|
|||||||
start_openslides(addr, port, start_browser_url=url, extra_args=extra_args)
|
start_openslides(addr, port, start_browser_url=url, extra_args=extra_args)
|
||||||
|
|
||||||
|
|
||||||
def create_settings(settings):
|
def create_settings(settings_path, database_path=None):
|
||||||
path_to_dir = os.path.dirname(settings)
|
settings_module = os.path.dirname(settings_path)
|
||||||
|
|
||||||
setting_content = CONFIG_TEMPLATE % dict(
|
if database_path is _portable_db_path:
|
||||||
|
database_path = get_portable_db_path()
|
||||||
|
dbpath_value = 'openslides.main.get_portable_db_path()'
|
||||||
|
else:
|
||||||
|
if database_path is None:
|
||||||
|
database_path = get_user_data_path('openslides', 'database.sqlite')
|
||||||
|
dbpath_value = repr(fs2unicode(database_path))
|
||||||
|
|
||||||
|
settings_content = CONFIG_TEMPLATE % dict(
|
||||||
default_key=base64.b64encode(os.urandom(KEY_LENGTH)),
|
default_key=base64.b64encode(os.urandom(KEY_LENGTH)),
|
||||||
dbpath=_fs2unicode((os.path.join(path_to_dir, 'database.db'))))
|
dbpath=dbpath_value)
|
||||||
|
|
||||||
if not os.path.exists(path_to_dir):
|
if not os.path.exists(settings_module):
|
||||||
os.makedirs(path_to_dir)
|
os.makedirs(settings_module)
|
||||||
|
|
||||||
with open(settings, 'w') as settings_file:
|
if not os.path.exists(os.path.dirname(database_path)):
|
||||||
settings_file.write(setting_content)
|
os.makedirs(os.path.dirname(database_path))
|
||||||
|
|
||||||
|
with open(settings_path, 'w') as file:
|
||||||
|
file.write(settings_content)
|
||||||
|
|
||||||
|
|
||||||
def setup_django_environment(settings):
|
def setup_django_environment(settings_path):
|
||||||
sys.path.append(os.path.dirname(settings))
|
settings_file = os.path.basename(settings_path)
|
||||||
setting_module = os.path.basename(settings)[:-3]
|
settings_module_name = "".join(settings_file.split('.')[:-1])
|
||||||
os.environ[django.conf.ENVIRONMENT_VARIABLE] = setting_module
|
if '.' in settings_module_name:
|
||||||
|
print "'.' is not an allowed character in the settings-file"
|
||||||
|
sys.exit(1)
|
||||||
|
settings_module_dir = os.path.dirname(settings_path)
|
||||||
|
sys.path.append(settings_module_dir)
|
||||||
|
os.environ[django.conf.ENVIRONMENT_VARIABLE] = '%s' % settings_module_name
|
||||||
|
|
||||||
|
|
||||||
def detect_listen_opts(address, port):
|
def detect_listen_opts(address, port):
|
||||||
@ -186,6 +245,7 @@ def database_exists():
|
|||||||
from openslides.participant.models import User
|
from openslides.participant.models import User
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# TODO: Use another model, the User could be deactivated
|
||||||
User.objects.count()
|
User.objects.count()
|
||||||
except DatabaseError:
|
except DatabaseError:
|
||||||
return False
|
return False
|
||||||
@ -200,6 +260,7 @@ def run_syncdb():
|
|||||||
# now initialize the database
|
# now initialize the database
|
||||||
argv = ["", "syncdb", "--noinput"]
|
argv = ["", "syncdb", "--noinput"]
|
||||||
execute_from_command_line(argv)
|
execute_from_command_line(argv)
|
||||||
|
execute_from_command_line(["", "loaddata", "groups_de"])
|
||||||
|
|
||||||
|
|
||||||
def set_system_url(url):
|
def set_system_url(url):
|
||||||
@ -223,7 +284,7 @@ def create_or_reset_admin_user():
|
|||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
admin = User()
|
admin = User()
|
||||||
admin.username = 'admin'
|
admin.username = 'admin'
|
||||||
admin.last_name = 'Admin User'
|
admin.last_name = 'Administrator'
|
||||||
print("Created default admin user")
|
print("Created default admin user")
|
||||||
|
|
||||||
admin.is_superuser = True
|
admin.is_superuser = True
|
||||||
@ -252,33 +313,60 @@ def start_browser(url):
|
|||||||
t = threading.Thread(target=f)
|
t = threading.Thread(target=f)
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
def win32_portable_main(argv=None):
|
|
||||||
"""special entry point for the win32 portable version"""
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
|
def fs2unicode(s):
|
||||||
|
if isinstance(s, unicode):
|
||||||
|
return s
|
||||||
|
fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
|
||||||
|
return s.decode(fs_encoding)
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_config_path(*args):
|
||||||
|
if sys.platform == "win32":
|
||||||
|
return win32_get_app_data_path(*args)
|
||||||
|
|
||||||
|
config_home = os.environ.get(
|
||||||
|
'XDG_CONFIG_HOME', os.path.join(os.path.expanduser('~'), '.config'))
|
||||||
|
|
||||||
|
return os.path.join(fs2unicode(config_home), *args)
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_data_path(*args):
|
||||||
|
if sys.platform == "win32":
|
||||||
|
return win32_get_app_data_path(*args)
|
||||||
|
|
||||||
|
data_home = os.environ.get(
|
||||||
|
'XDG_DATA_HOME', os.path.join(
|
||||||
|
os.path.expanduser('~'), '.local', 'share'))
|
||||||
|
|
||||||
|
return os.path.join(fs2unicode(data_home), *args)
|
||||||
|
|
||||||
|
|
||||||
|
def get_portable_path(*args):
|
||||||
# NOTE: sys.executable will be the path to openslides.exe
|
# NOTE: sys.executable will be the path to openslides.exe
|
||||||
# since it is essentially a small wrapper that embeds the
|
# since it is essentially a small wrapper that embeds the
|
||||||
# python interpreter
|
# python interpreter
|
||||||
portable_dir = os.path.dirname(os.path.abspath(sys.executable))
|
|
||||||
try:
|
|
||||||
fd, test_file = tempfile.mkstemp(dir=portable_dir)
|
|
||||||
except OSError:
|
|
||||||
portable_dir_writeable = False
|
|
||||||
else:
|
|
||||||
portable_dir_writeable = True
|
|
||||||
os.close(fd)
|
|
||||||
os.unlink(test_file)
|
|
||||||
|
|
||||||
if portable_dir_writeable:
|
exename = os.path.basename(sys.executable).lower()
|
||||||
default_settings = os.path.join(portable_dir, "openslides",
|
if exename != "openslides.exe":
|
||||||
"openslides_personal_settings.py")
|
raise Exception(
|
||||||
else:
|
"Cannot determine portable path when "
|
||||||
import ctypes
|
"not running as portable")
|
||||||
|
|
||||||
|
portable_dir = fs2unicode(os.path.dirname(os.path.abspath(sys.executable)))
|
||||||
|
return os.path.join(portable_dir, *args)
|
||||||
|
|
||||||
|
|
||||||
|
def get_portable_db_path():
|
||||||
|
return get_portable_path('openslides', 'database.sqlite')
|
||||||
|
|
||||||
|
|
||||||
|
def win32_get_app_data_path(*args):
|
||||||
shell32 = ctypes.WinDLL("shell32.dll")
|
shell32 = ctypes.WinDLL("shell32.dll")
|
||||||
SHGetFolderPath = shell32.SHGetFolderPathW
|
SHGetFolderPath = shell32.SHGetFolderPathW
|
||||||
SHGetFolderPath.argtypes = (ctypes.c_void_p, ctypes.c_int,
|
SHGetFolderPath.argtypes = (
|
||||||
ctypes.c_void_p, ctypes.c_uint32, ctypes.c_wchar_p)
|
ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_uint32,
|
||||||
|
ctypes.c_wchar_p)
|
||||||
SHGetFolderPath.restype = ctypes.c_uint32
|
SHGetFolderPath.restype = ctypes.c_uint32
|
||||||
|
|
||||||
CSIDL_LOCAL_APPDATA = 0x001c
|
CSIDL_LOCAL_APPDATA = 0x001c
|
||||||
@ -288,10 +376,8 @@ def win32_portable_main(argv=None):
|
|||||||
res = SHGetFolderPath(0, CSIDL_LOCAL_APPDATA, 0, 0, buf)
|
res = SHGetFolderPath(0, CSIDL_LOCAL_APPDATA, 0, 0, buf)
|
||||||
if res != 0:
|
if res != 0:
|
||||||
raise Exception("Could not deterime APPDATA path")
|
raise Exception("Could not deterime APPDATA path")
|
||||||
default_settings = os.path.join(buf.value, "openslides",
|
|
||||||
"openslides_personal_settings.py")
|
|
||||||
|
|
||||||
main(argv, opt_defaults={ "settings": default_settings })
|
return os.path.join(buf.value, *args)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -1,52 +1,52 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
openslides.application.forms
|
openslides.motion.forms
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Forms for the application app.
|
Forms for the motion app.
|
||||||
|
|
||||||
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
|
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
|
||||||
:license: GNU GPL, see LICENSE for more details.
|
:license: GNU GPL, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _, ugettext_noop
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from openslides.utils.forms import CssClassMixin
|
from openslides.utils.forms import CssClassMixin
|
||||||
from openslides.utils.person import PersonFormField, MultiplePersonFormField
|
from openslides.utils.person import PersonFormField, MultiplePersonFormField
|
||||||
from openslides.application.models import Application
|
from openslides.motion.models import Motion
|
||||||
|
|
||||||
|
|
||||||
class ApplicationForm(forms.Form, CssClassMixin):
|
class MotionForm(forms.Form, CssClassMixin):
|
||||||
title = forms.CharField(widget=forms.TextInput(), label=_("Title"))
|
title = forms.CharField(widget=forms.TextInput(), label=_("Title"))
|
||||||
text = forms.CharField(widget=forms.Textarea(), label=_("Text"))
|
text = forms.CharField(widget=forms.Textarea(), label=_("Text"))
|
||||||
reason = forms.CharField(widget=forms.Textarea(), required=False,
|
reason = forms.CharField(
|
||||||
label=_("Reason"))
|
widget=forms.Textarea(), required=False, label=_("Reason"))
|
||||||
|
|
||||||
|
|
||||||
class ApplicationFormTrivialChanges(ApplicationForm):
|
class MotionFormTrivialChanges(MotionForm):
|
||||||
trivial_change = forms.BooleanField(required=False,
|
trivial_change = forms.BooleanField(
|
||||||
label=_("Trivial change"),
|
required=False, label=_("Trivial change"),
|
||||||
help_text=_("Trivial changes don't create a new version."))
|
help_text=_("Trivial changes don't create a new version."))
|
||||||
|
|
||||||
|
|
||||||
class ApplicationManagerForm(forms.ModelForm, CssClassMixin):
|
class MotionManagerForm(forms.ModelForm, CssClassMixin):
|
||||||
submitter = PersonFormField()
|
submitter = PersonFormField(label=_("Submitter"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Application
|
model = Motion
|
||||||
exclude = ('number', 'status', 'permitted', 'log', 'supporter')
|
exclude = ('number', 'status', 'permitted', 'log', 'supporter')
|
||||||
|
|
||||||
|
|
||||||
class ApplicationManagerFormSupporter(ApplicationManagerForm):
|
class MotionManagerFormSupporter(MotionManagerForm):
|
||||||
# TODO: Do not show the submitter in the user-list
|
# TODO: Do not show the submitter in the user-list
|
||||||
supporter = MultiplePersonFormField(required=False, label=_("Supporters"))
|
supporter = MultiplePersonFormField(required=False, label=_("Supporters"))
|
||||||
|
|
||||||
|
|
||||||
class ApplicationImportForm(forms.Form, CssClassMixin):
|
class MotionImportForm(forms.Form, CssClassMixin):
|
||||||
csvfile = forms.FileField(
|
csvfile = forms.FileField(
|
||||||
widget=forms.FileInput(attrs={'size':'50'}),
|
widget=forms.FileInput(attrs={'size': '50'}),
|
||||||
label=_("CSV File"),
|
label=_("CSV File"),
|
||||||
)
|
)
|
||||||
import_permitted = forms.BooleanField(
|
import_permitted = forms.BooleanField(
|
||||||
@ -58,20 +58,20 @@ class ApplicationImportForm(forms.Form, CssClassMixin):
|
|||||||
|
|
||||||
|
|
||||||
class ConfigForm(forms.Form, CssClassMixin):
|
class ConfigForm(forms.Form, CssClassMixin):
|
||||||
application_min_supporters = forms.IntegerField(
|
motion_min_supporters = forms.IntegerField(
|
||||||
widget=forms.TextInput(attrs={'class':'small-input'}),
|
widget=forms.TextInput(attrs={'class': 'small-input'}),
|
||||||
label=_("Number of (minimum) required supporters for a motion"),
|
label=_("Number of (minimum) required supporters for a motion"),
|
||||||
initial=4,
|
initial=4,
|
||||||
min_value=0,
|
min_value=0,
|
||||||
max_value=8,
|
max_value=8,
|
||||||
help_text=_("Choose 0 to disable the supporting system"),
|
help_text=_("Choose 0 to disable the supporting system"),
|
||||||
)
|
)
|
||||||
application_preamble = forms.CharField(
|
motion_preamble = forms.CharField(
|
||||||
widget=forms.TextInput(),
|
widget=forms.TextInput(),
|
||||||
required=False,
|
required=False,
|
||||||
label=_("Motion preamble")
|
label=_("Motion preamble")
|
||||||
)
|
)
|
||||||
application_pdf_ballot_papers_selection = forms.ChoiceField(
|
motion_pdf_ballot_papers_selection = forms.ChoiceField(
|
||||||
widget=forms.Select(),
|
widget=forms.Select(),
|
||||||
required=False,
|
required=False,
|
||||||
label=_("Number of ballot papers (selection)"),
|
label=_("Number of ballot papers (selection)"),
|
||||||
@ -81,24 +81,24 @@ class ConfigForm(forms.Form, CssClassMixin):
|
|||||||
("CUSTOM_NUMBER", _("Use the following custom number")),
|
("CUSTOM_NUMBER", _("Use the following custom number")),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
application_pdf_ballot_papers_number = forms.IntegerField(
|
motion_pdf_ballot_papers_number = forms.IntegerField(
|
||||||
widget=forms.TextInput(attrs={'class':'small-input'}),
|
widget=forms.TextInput(attrs={'class': 'small-input'}),
|
||||||
required=False,
|
required=False,
|
||||||
min_value=1,
|
min_value=1,
|
||||||
label=_("Custom number of ballot papers")
|
label=_("Custom number of ballot papers")
|
||||||
)
|
)
|
||||||
application_pdf_title = forms.CharField(
|
motion_pdf_title = forms.CharField(
|
||||||
widget=forms.TextInput(),
|
widget=forms.TextInput(),
|
||||||
required=False,
|
required=False,
|
||||||
label=_("Title for PDF document (all motions)")
|
label=_("Title for PDF document (all motions)")
|
||||||
)
|
)
|
||||||
application_pdf_preamble = forms.CharField(
|
motion_pdf_preamble = forms.CharField(
|
||||||
widget=forms.Textarea(),
|
widget=forms.Textarea(),
|
||||||
required=False,
|
required=False,
|
||||||
label=_("Preamble text for PDF document (all motions)")
|
label=_("Preamble text for PDF document (all motions)")
|
||||||
)
|
)
|
||||||
|
|
||||||
application_allow_trivial_change = forms.BooleanField(
|
motion_allow_trivial_change = forms.BooleanField(
|
||||||
label=_("Allow trivial changes"),
|
label=_("Allow trivial changes"),
|
||||||
help_text=_('Warning: Trivial changes undermine the motions '
|
help_text=_('Warning: Trivial changes undermine the motions '
|
||||||
'autorisation system.'),
|
'autorisation system.'),
|
@ -1,10 +1,10 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
openslides.application.models
|
openslides.motion.models
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Models for the application app.
|
Models for the motion app.
|
||||||
|
|
||||||
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
|
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
|
||||||
:license: GNU GPL, see LICENSE for more details.
|
:license: GNU GPL, see LICENSE for more details.
|
||||||
@ -21,26 +21,23 @@ from django.utils.translation import ugettext_lazy as _, ugettext_noop, ugettext
|
|||||||
|
|
||||||
from openslides.utils.utils import _propper_unicode
|
from openslides.utils.utils import _propper_unicode
|
||||||
from openslides.utils.person import PersonField
|
from openslides.utils.person import PersonField
|
||||||
|
|
||||||
from openslides.config.models import config
|
from openslides.config.models import config
|
||||||
from openslides.config.signals import default_config_value
|
from openslides.config.signals import default_config_value
|
||||||
|
from openslides.poll.models import (
|
||||||
from openslides.poll.models import (BaseOption, BasePoll, CountVotesCast,
|
BaseOption, BasePoll, CountVotesCast, CountInvalid, BaseVote)
|
||||||
CountInvalid, BaseVote)
|
from openslides.participant.models import User
|
||||||
|
|
||||||
from openslides.projector.api import register_slidemodel
|
from openslides.projector.api import register_slidemodel
|
||||||
from openslides.projector.models import SlideMixin
|
from openslides.projector.models import SlideMixin
|
||||||
|
|
||||||
from openslides.agenda.models import Item
|
from openslides.agenda.models import Item
|
||||||
|
|
||||||
|
|
||||||
class ApplicationSupporter(models.Model):
|
class MotionSupporter(models.Model):
|
||||||
application = models.ForeignKey("Application")
|
motion = models.ForeignKey("Motion")
|
||||||
person = PersonField()
|
person = PersonField()
|
||||||
|
|
||||||
|
|
||||||
class Application(models.Model, SlideMixin):
|
class Motion(models.Model, SlideMixin):
|
||||||
prefix = "application"
|
prefix = "motion"
|
||||||
STATUS = (
|
STATUS = (
|
||||||
('pub', _('Published')),
|
('pub', _('Published')),
|
||||||
('per', _('Permitted')),
|
('per', _('Permitted')),
|
||||||
@ -67,17 +64,17 @@ class Application(models.Model, SlideMixin):
|
|||||||
number = models.PositiveSmallIntegerField(blank=True, null=True,
|
number = models.PositiveSmallIntegerField(blank=True, null=True,
|
||||||
unique=True)
|
unique=True)
|
||||||
status = models.CharField(max_length=3, choices=STATUS, default='pub')
|
status = models.CharField(max_length=3, choices=STATUS, default='pub')
|
||||||
permitted = models.ForeignKey('AVersion', related_name='permitted', \
|
permitted = models.ForeignKey('AVersion', related_name='permitted',
|
||||||
null=True, blank=True)
|
null=True, blank=True)
|
||||||
log = models.TextField(blank=True, null=True)
|
log = models.TextField(blank=True, null=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def last_version(self):
|
def last_version(self):
|
||||||
"""
|
"""
|
||||||
Return last version of the application.
|
Return last version of the motion.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return AVersion.objects.filter(application=self).order_by('id') \
|
return AVersion.objects.filter(motion=self).order_by('id') \
|
||||||
.reverse()[0]
|
.reverse()[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return None
|
return None
|
||||||
@ -85,14 +82,14 @@ class Application(models.Model, SlideMixin):
|
|||||||
@property
|
@property
|
||||||
def public_version(self):
|
def public_version(self):
|
||||||
"""
|
"""
|
||||||
Return permitted, if the application was permitted, else last_version
|
Return permitted, if the motion was permitted, else last_version
|
||||||
"""
|
"""
|
||||||
if self.permitted is not None:
|
if self.permitted is not None:
|
||||||
return self.permitted
|
return self.permitted
|
||||||
else:
|
else:
|
||||||
return self.last_version
|
return self.last_version
|
||||||
|
|
||||||
def accept_version(self, version, user = None):
|
def accept_version(self, version, user=None):
|
||||||
"""
|
"""
|
||||||
accept a Version
|
accept a Version
|
||||||
"""
|
"""
|
||||||
@ -100,29 +97,29 @@ class Application(models.Model, SlideMixin):
|
|||||||
self.save(nonewversion=True)
|
self.save(nonewversion=True)
|
||||||
version.rejected = False
|
version.rejected = False
|
||||||
version.save()
|
version.save()
|
||||||
self.writelog(_("Version %d authorized") % (version.aid, ),
|
self.writelog(_("Version %d authorized") % version.aid, user)
|
||||||
user)
|
|
||||||
|
|
||||||
def reject_version(self, version, user = None):
|
def reject_version(self, version, user=None):
|
||||||
if version.id > self.permitted.id:
|
if version.id > self.permitted.id:
|
||||||
version.rejected = True
|
version.rejected = True
|
||||||
version.save()
|
version.save()
|
||||||
self.writelog(pgettext("Rejected means not authorized", "Version %d rejected")
|
self.writelog(pgettext(
|
||||||
% (version.aid, ), user)
|
"Rejected means not authorized", "Version %d rejected")
|
||||||
|
% version.aid, user)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def versions(self):
|
def versions(self):
|
||||||
"""
|
"""
|
||||||
Return a list of all versions of the application.
|
Return a list of all versions of the motion.
|
||||||
"""
|
"""
|
||||||
return AVersion.objects.filter(application=self)
|
return AVersion.objects.filter(motion=self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def creation_time(self):
|
def creation_time(self):
|
||||||
"""
|
"""
|
||||||
Return the time of the creation of the application.
|
Return the time of the creation of the motion.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return self.versions[0].time
|
return self.versions[0].time
|
||||||
@ -132,60 +129,63 @@ class Application(models.Model, SlideMixin):
|
|||||||
@property
|
@property
|
||||||
def notes(self):
|
def notes(self):
|
||||||
"""
|
"""
|
||||||
Return some information of the application.
|
Return some information of the motion.
|
||||||
"""
|
"""
|
||||||
note = []
|
note = []
|
||||||
if self.status == "pub" and not self.enough_supporters:
|
if self.status == "pub" and not self.enough_supporters:
|
||||||
note.append(_("Searching for supporters."))
|
note.append(ugettext("Searching for supporters."))
|
||||||
if self.status == "pub" and self.permitted is None:
|
if self.status == "pub" and self.permitted is None:
|
||||||
note.append(_("Not yet authorized."))
|
note.append(ugettext("Not yet authorized."))
|
||||||
elif self.unpermitted_changes and self.permitted:
|
elif self.unpermitted_changes and self.permitted:
|
||||||
note.append(_("Not yet authorized changes."))
|
note.append(ugettext("Not yet authorized changes."))
|
||||||
return note
|
return note
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unpermitted_changes(self):
|
def unpermitted_changes(self):
|
||||||
"""
|
"""
|
||||||
Return True if the application has unpermitted changes.
|
Return True if the motion has unpermitted changes.
|
||||||
|
|
||||||
The application has unpermitted changes, if the permitted-version
|
The motion has unpermitted changes, if the permitted-version
|
||||||
is not the lastone and the lastone is not rejected.
|
is not the lastone and the lastone is not rejected.
|
||||||
TODO: rename the property in unchecked__changes
|
TODO: rename the property in unchecked__changes
|
||||||
"""
|
"""
|
||||||
if (self.last_version != self.permitted
|
if (self.last_version != self.permitted and
|
||||||
and not self.last_version.rejected):
|
not self.last_version.rejected):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supporters(self):
|
def supporters(self):
|
||||||
for object in self.applicationsupporter_set.all():
|
return sorted([object.person for object in self.motionsupporter_set.all()],
|
||||||
yield object.person
|
key=lambda person: person.sort_name)
|
||||||
|
|
||||||
def is_supporter(self, person):
|
def is_supporter(self, person):
|
||||||
return self.applicationsupporter_set.filter(person=person).exists()
|
try:
|
||||||
|
return self.motionsupporter_set.filter(person=person).exists()
|
||||||
|
except AttributeError:
|
||||||
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def enough_supporters(self):
|
def enough_supporters(self):
|
||||||
"""
|
"""
|
||||||
Return True, if the application has enough supporters
|
Return True, if the motion has enough supporters
|
||||||
"""
|
"""
|
||||||
min_supporters = int(config['application_min_supporters'])
|
min_supporters = int(config['motion_min_supporters'])
|
||||||
if self.status == "pub":
|
if self.status == "pub":
|
||||||
return self.count_supporters() >= min_supporters
|
return self.count_supporters() >= min_supporters
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def count_supporters(self):
|
def count_supporters(self):
|
||||||
return self.applicationsupporter_set.count()
|
return self.motionsupporter_set.count()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def missing_supporters(self):
|
def missing_supporters(self):
|
||||||
"""
|
"""
|
||||||
Return number of missing supporters
|
Return number of missing supporters
|
||||||
"""
|
"""
|
||||||
min_supporters = int(config['application_min_supporters'])
|
min_supporters = int(config['motion_min_supporters'])
|
||||||
delta = min_supporters - self.count_supporters()
|
delta = min_supporters - self.count_supporters()
|
||||||
if delta > 0:
|
if delta > 0:
|
||||||
return delta
|
return delta
|
||||||
@ -194,15 +194,16 @@ class Application(models.Model, SlideMixin):
|
|||||||
|
|
||||||
def save(self, user=None, nonewversion=False, trivial_change=False):
|
def save(self, user=None, nonewversion=False, trivial_change=False):
|
||||||
"""
|
"""
|
||||||
Save the Application, and create a new AVersion if necessary
|
Save the Motion, and create a new AVersion if necessary
|
||||||
"""
|
"""
|
||||||
super(Application, self).save()
|
super(Motion, self).save()
|
||||||
if nonewversion:
|
if nonewversion:
|
||||||
return
|
return
|
||||||
last_version = self.last_version
|
last_version = self.last_version
|
||||||
fields = ["text", "title", "reason"]
|
fields = ["text", "title", "reason"]
|
||||||
if last_version is not None:
|
if last_version is not None:
|
||||||
changed_fields = [f for f in fields
|
changed_fields = [
|
||||||
|
f for f in fields
|
||||||
if getattr(last_version, f) != getattr(self, f)]
|
if getattr(last_version, f) != getattr(self, f)]
|
||||||
if not changed_fields:
|
if not changed_fields:
|
||||||
return # No changes
|
return # No changes
|
||||||
@ -214,35 +215,37 @@ class Application(models.Model, SlideMixin):
|
|||||||
last_version.save()
|
last_version.save()
|
||||||
|
|
||||||
meta = AVersion._meta
|
meta = AVersion._meta
|
||||||
field_names = [unicode(meta.get_field(f).verbose_name)
|
field_names = [
|
||||||
|
unicode(meta.get_field(f).verbose_name)
|
||||||
for f in changed_fields]
|
for f in changed_fields]
|
||||||
|
|
||||||
self.writelog(_("Trivial changes to version %(version)d; "
|
self.writelog(
|
||||||
|
_("Trivial changes to version %(version)d; "
|
||||||
"changed fields: %(changed_fields)s")
|
"changed fields: %(changed_fields)s")
|
||||||
% dict(version = last_version.aid,
|
% dict(version=last_version.aid,
|
||||||
changed_fields = ", ".join(field_names)))
|
changed_fields=", ".join(field_names)))
|
||||||
return # Done
|
return # Done
|
||||||
|
|
||||||
version = AVersion(title=getattr(self, 'title', ''),
|
version = AVersion(
|
||||||
|
title=getattr(self, 'title', ''),
|
||||||
text=getattr(self, 'text', ''),
|
text=getattr(self, 'text', ''),
|
||||||
reason=getattr(self, 'reason', ''),
|
reason=getattr(self, 'reason', ''),
|
||||||
application=self)
|
motion=self)
|
||||||
version.save()
|
version.save()
|
||||||
self.writelog(_("Version %s created") % version.aid, user)
|
self.writelog(_("Version %s created") % version.aid, user)
|
||||||
is_manager = user.has_perm('application.can_manage_application')
|
is_manager = user.has_perm('motion.can_manage_motion')
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
is_manager = False
|
is_manager = False
|
||||||
|
|
||||||
supporters = self.applicationsupporter_set.all()
|
supporters = self.motionsupporter_set.all()
|
||||||
if (self.status == "pub"
|
if (self.status == "pub" and
|
||||||
and supporters
|
supporters and not is_manager):
|
||||||
and not is_manager):
|
|
||||||
supporters.delete()
|
supporters.delete()
|
||||||
self.writelog(_("Supporters removed"), user)
|
self.writelog(_("Supporters removed"), user)
|
||||||
|
|
||||||
def reset(self, user):
|
def reset(self, user):
|
||||||
"""
|
"""
|
||||||
Reset the application.
|
Reset the motion.
|
||||||
"""
|
"""
|
||||||
self.status = "pub"
|
self.status = "pub"
|
||||||
self.permitted = None
|
self.permitted = None
|
||||||
@ -251,44 +254,39 @@ class Application(models.Model, SlideMixin):
|
|||||||
|
|
||||||
def support(self, person):
|
def support(self, person):
|
||||||
"""
|
"""
|
||||||
Add a Supporter to the list of supporters of the application.
|
Add a Supporter to the list of supporters of the motion.
|
||||||
"""
|
"""
|
||||||
if person == self.submitter:
|
if person == self.submitter:
|
||||||
# TODO: Use own Exception
|
# TODO: Use own Exception
|
||||||
raise NameError('Supporter can not be the submitter of a ' \
|
raise NameError('Supporter can not be the submitter of a '
|
||||||
'application.')
|
'motion.')
|
||||||
if self.permitted is not None:
|
|
||||||
# TODO: Use own Exception
|
|
||||||
raise NameError('This application is already permitted.')
|
|
||||||
if not self.is_supporter(person):
|
if not self.is_supporter(person):
|
||||||
ApplicationSupporter(application=self, person=person).save()
|
MotionSupporter(motion=self, person=person).save()
|
||||||
self.writelog(_("Supporter: +%s") % (person))
|
self.writelog(_("Supporter: +%s") % (person))
|
||||||
|
# TODO: Raise a precise exception for the view in else-clause
|
||||||
|
|
||||||
def unsupport(self, user):
|
def unsupport(self, person):
|
||||||
"""
|
"""
|
||||||
remove a supporter from the list of supporters of the application
|
remove a supporter from the list of supporters of the motion
|
||||||
"""
|
"""
|
||||||
if self.permitted is not None:
|
|
||||||
# TODO: Use own Exception
|
|
||||||
raise NameError('This application is already permitted.')
|
|
||||||
try:
|
try:
|
||||||
object = self.applicationsupporter_set.get(user=user).delete()
|
self.motionsupporter_set.get(person=person).delete()
|
||||||
except ApplicationSupporter.DoesNotExist:
|
except MotionSupporter.DoesNotExist:
|
||||||
|
# TODO: Don't do nothing but raise a precise exception for the view
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
self.writelog(_("Supporter: -%s") % (user))
|
self.writelog(_("Supporter: -%s") % (person))
|
||||||
|
|
||||||
def set_number(self, number=None, user=None):
|
def set_number(self, number=None, user=None):
|
||||||
"""
|
"""
|
||||||
Set a number for ths application.
|
Set a number for ths motion.
|
||||||
"""
|
"""
|
||||||
if self.number is not None:
|
if self.number is not None:
|
||||||
# TODO: Use own Exception
|
# TODO: Use own Exception
|
||||||
raise NameError('This application has already a number.')
|
raise NameError('This motion has already a number.')
|
||||||
if number is None:
|
if number is None:
|
||||||
try:
|
try:
|
||||||
number = Application.objects.aggregate(Max('number')) \
|
number = Motion.objects.aggregate(Max('number'))['number__max'] + 1
|
||||||
['number__max'] + 1
|
|
||||||
except TypeError:
|
except TypeError:
|
||||||
number = 1
|
number = 1
|
||||||
self.number = number
|
self.number = number
|
||||||
@ -298,7 +296,7 @@ class Application(models.Model, SlideMixin):
|
|||||||
|
|
||||||
def permit(self, user=None):
|
def permit(self, user=None):
|
||||||
"""
|
"""
|
||||||
Change the status of this application to permit.
|
Change the status of this motion to permit.
|
||||||
"""
|
"""
|
||||||
self.set_status(user, "per")
|
self.set_status(user, "per")
|
||||||
aversion = self.last_version
|
aversion = self.last_version
|
||||||
@ -311,12 +309,10 @@ class Application(models.Model, SlideMixin):
|
|||||||
|
|
||||||
def notpermit(self, user=None):
|
def notpermit(self, user=None):
|
||||||
"""
|
"""
|
||||||
Change the status of this application to 'not permitted (rejected)'.
|
Change the status of this motion to 'not permitted (rejected)'.
|
||||||
"""
|
"""
|
||||||
self.set_status(user, "nop")
|
self.set_status(user, "nop")
|
||||||
#TODO: reject last version
|
#TODO: reject last version
|
||||||
aversion = self.last_version
|
|
||||||
#self.permitted = aversion
|
|
||||||
if self.number is None:
|
if self.number is None:
|
||||||
self.set_number()
|
self.set_number()
|
||||||
self.save()
|
self.save()
|
||||||
@ -324,19 +320,19 @@ class Application(models.Model, SlideMixin):
|
|||||||
|
|
||||||
def set_status(self, user, status, force=False):
|
def set_status(self, user, status, force=False):
|
||||||
"""
|
"""
|
||||||
Set the status of the application.
|
Set the status of the motion.
|
||||||
"""
|
"""
|
||||||
error = True
|
error = True
|
||||||
for a, b in Application.STATUS:
|
for a, b in Motion.STATUS:
|
||||||
if status == a:
|
if status == a:
|
||||||
error = False
|
error = False
|
||||||
break
|
break
|
||||||
if error:
|
if error:
|
||||||
#TODO: Use the Right Error
|
# TODO: Use the Right Error
|
||||||
raise NameError(_('%s is not a valid status.') % status)
|
raise NameError(_('%s is not a valid status.') % status)
|
||||||
if self.status == status:
|
if self.status == status:
|
||||||
#TODO: Use the Right Error
|
# TODO: Use the Right Error
|
||||||
raise NameError(_('The motion status is already \'%s.\'') \
|
raise NameError(_('The motion status is already \'%s.\'')
|
||||||
% self.status)
|
% self.status)
|
||||||
|
|
||||||
actions = []
|
actions = []
|
||||||
@ -352,7 +348,7 @@ class Application(models.Model, SlideMixin):
|
|||||||
oldstatus = self.get_status_display()
|
oldstatus = self.get_status_display()
|
||||||
self.status = status
|
self.status = status
|
||||||
self.save()
|
self.save()
|
||||||
self.writelog(_("Status modified")+": %s -> %s" \
|
self.writelog(_("Status modified") + ": %s -> %s"
|
||||||
% (oldstatus, self.get_status_display()), user)
|
% (oldstatus, self.get_status_display()), user)
|
||||||
|
|
||||||
def get_allowed_actions(self, user):
|
def get_allowed_actions(self, user):
|
||||||
@ -361,25 +357,25 @@ class Application(models.Model, SlideMixin):
|
|||||||
"""
|
"""
|
||||||
actions = []
|
actions = []
|
||||||
|
|
||||||
# check if user allowed to withdraw an application
|
# check if user allowed to withdraw an motion
|
||||||
if ((self.status == "pub"
|
if ((self.status == "pub"
|
||||||
and self.number
|
and self.number
|
||||||
and user == self.submitter)
|
and user == self.submitter)
|
||||||
or (self.status == "pub"
|
or (self.status == "pub"
|
||||||
and self.number
|
and self.number
|
||||||
and user.has_perm("application.can_manage_application"))
|
and user.has_perm("motion.can_manage_motion"))
|
||||||
or (self.status == "per"
|
or (self.status == "per"
|
||||||
and user == self.submitter)
|
and user == self.submitter)
|
||||||
or (self.status == "per"
|
or (self.status == "per"
|
||||||
and user.has_perm("application.can_manage_application"))):
|
and user.has_perm("motion.can_manage_motion"))):
|
||||||
actions.append("wit")
|
actions.append("wit")
|
||||||
#Check if the user can review the application
|
#Check if the user can review the motion
|
||||||
if (self.status == "rev"
|
if (self.status == "rev"
|
||||||
and (self.submitter == user
|
and (self.submitter == user
|
||||||
or user.has_perm("application.can_manage_application"))):
|
or user.has_perm("motion.can_manage_motion"))):
|
||||||
actions.append("pub")
|
actions.append("pub")
|
||||||
|
|
||||||
# Check if the user can support and unspoort the application
|
# Check if the user can support and unspoort the motion
|
||||||
if (self.status == "pub"
|
if (self.status == "pub"
|
||||||
and user != self.submitter
|
and user != self.submitter
|
||||||
and not self.is_supporter(user)):
|
and not self.is_supporter(user)):
|
||||||
@ -388,22 +384,22 @@ class Application(models.Model, SlideMixin):
|
|||||||
if self.status == "pub" and self.is_supporter(user):
|
if self.status == "pub" and self.is_supporter(user):
|
||||||
actions.append("unsupport")
|
actions.append("unsupport")
|
||||||
|
|
||||||
#Check if the user can edit the application
|
#Check if the user can edit the motion
|
||||||
if (user == self.submitter \
|
if (user == self.submitter \
|
||||||
and (self.status in ('pub', 'per'))) \
|
and (self.status in ('pub', 'per'))) \
|
||||||
or user.has_perm("application.can_manage_application"):
|
or user.has_perm("motion.can_manage_motion"):
|
||||||
actions.append("edit")
|
actions.append("edit")
|
||||||
|
|
||||||
# Check if the user can delete the application (admin, manager, owner)
|
# Check if the user can delete the motion (admin, manager, owner)
|
||||||
# reworked as requiered in #100
|
# reworked as requiered in #100
|
||||||
if (user.has_perm("applicatoin.can_delete_all_applications") or
|
if (user.has_perm("motion.can_delete_all_motions") or
|
||||||
(user.has_perm("application.can_manage_application") and
|
(user.has_perm("motion.can_manage_motion") and
|
||||||
self.number is None) or
|
self.number is None) or
|
||||||
(self.submitter == user and self.number is None)):
|
(self.submitter == user and self.number is None)):
|
||||||
actions.append("delete")
|
actions.append("delete")
|
||||||
|
|
||||||
#For the rest, all actions need the manage permission
|
#For the rest, all actions need the manage permission
|
||||||
if not user.has_perm("application.can_manage_application"):
|
if not user.has_perm("motion.can_manage_motion"):
|
||||||
return actions
|
return actions
|
||||||
|
|
||||||
if self.status == "pub":
|
if self.status == "pub":
|
||||||
@ -427,24 +423,26 @@ class Application(models.Model, SlideMixin):
|
|||||||
|
|
||||||
def delete(self, force=False):
|
def delete(self, force=False):
|
||||||
"""
|
"""
|
||||||
Delete the application. It is not possible, if the application has
|
Delete the motion. It is not possible, if the motion has
|
||||||
allready a number
|
allready a number
|
||||||
"""
|
"""
|
||||||
if self.number and not force:
|
if self.number and not force:
|
||||||
raise NameError('The application has already a number. ' \
|
raise NameError('The motion has already a number. '
|
||||||
'You can not delete it.')
|
'You can not delete it.')
|
||||||
|
|
||||||
|
|
||||||
for item in Item.objects.filter(related_sid=self.sid):
|
for item in Item.objects.filter(related_sid=self.sid):
|
||||||
item.delete()
|
item.delete()
|
||||||
super(Application, self).delete()
|
super(Motion, self).delete()
|
||||||
|
|
||||||
def writelog(self, text, user=None):
|
def writelog(self, text, user=None):
|
||||||
if not self.log:
|
if not self.log:
|
||||||
self.log = ""
|
self.log = ""
|
||||||
self.log += u"%s | %s" % (datetime.now().strftime("%d.%m.%Y %H:%M:%S"), _propper_unicode(text))
|
self.log += u"%s | %s" % (datetime.now().strftime("%d.%m.%Y %H:%M:%S"), _propper_unicode(text))
|
||||||
if user is not None:
|
if user is not None:
|
||||||
|
if isinstance(user, User):
|
||||||
self.log += u" (%s %s)" % (_("by"), _propper_unicode(user.username))
|
self.log += u" (%s %s)" % (_("by"), _propper_unicode(user.username))
|
||||||
|
else:
|
||||||
|
self.log += u" (%s %s)" % (_("by"), _propper_unicode(str(user)))
|
||||||
self.log += "\n"
|
self.log += "\n"
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
@ -458,7 +456,7 @@ class Application(models.Model, SlideMixin):
|
|||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
"""
|
"""
|
||||||
if name is title, text, reason or time,
|
if name is title, text, reason or time,
|
||||||
Return this attribute from the newest version of the application
|
Return this attribute from the newest version of the motion
|
||||||
"""
|
"""
|
||||||
if name in ('title', 'text', 'reason', 'time', 'aid'):
|
if name in ('title', 'text', 'reason', 'time', 'aid'):
|
||||||
try:
|
try:
|
||||||
@ -473,9 +471,9 @@ class Application(models.Model, SlideMixin):
|
|||||||
|
|
||||||
def gen_poll(self, user=None):
|
def gen_poll(self, user=None):
|
||||||
"""
|
"""
|
||||||
Generates a poll object for the application
|
Generates a poll object for the motion
|
||||||
"""
|
"""
|
||||||
poll = ApplicationPoll(application=self)
|
poll = MotionPoll(motion=self)
|
||||||
poll.save()
|
poll.save()
|
||||||
poll.set_options()
|
poll.set_options()
|
||||||
self.writelog(_("Poll created"), user)
|
self.writelog(_("Poll created"), user)
|
||||||
@ -483,7 +481,7 @@ class Application(models.Model, SlideMixin):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def polls(self):
|
def polls(self):
|
||||||
return self.applicationpoll_set.all()
|
return self.motionpoll_set.all()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def results(self):
|
def results(self):
|
||||||
@ -497,29 +495,29 @@ class Application(models.Model, SlideMixin):
|
|||||||
for poll in self.polls:
|
for poll in self.polls:
|
||||||
for option in poll.get_options():
|
for option in poll.get_options():
|
||||||
if option.get_votes().exists():
|
if option.get_votes().exists():
|
||||||
results.append((option['Yes'], option['No'],
|
results.append((
|
||||||
|
option['Yes'], option['No'],
|
||||||
option['Abstain'], poll.print_votesinvalid(),
|
option['Abstain'], poll.print_votesinvalid(),
|
||||||
poll.print_votescast()))
|
poll.print_votescast()))
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
def slide(self):
|
def slide(self):
|
||||||
"""
|
"""
|
||||||
return the slide dict
|
return the slide dict
|
||||||
"""
|
"""
|
||||||
data = super(Application, self).slide()
|
data = super(Motion, self).slide()
|
||||||
data['application'] = self
|
data['motion'] = self
|
||||||
data['title'] = self.title
|
data['title'] = self.title
|
||||||
data['template'] = 'projector/Application.html'
|
data['template'] = 'projector/Motion.html'
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_absolute_url(self, link='view'):
|
def get_absolute_url(self, link='view'):
|
||||||
if link == 'view':
|
if link == 'view':
|
||||||
return reverse('application_view', args=[str(self.id)])
|
return reverse('motion_view', args=[str(self.id)])
|
||||||
if link == 'edit':
|
if link == 'edit':
|
||||||
return reverse('application_edit', args=[str(self.id)])
|
return reverse('motion_edit', args=[str(self.id)])
|
||||||
if link == 'delete':
|
if link == 'delete':
|
||||||
return reverse('application_delete', args=[str(self.id)])
|
return reverse('motion_delete', args=[str(self.id)])
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
try:
|
try:
|
||||||
@ -529,20 +527,21 @@ class Application(models.Model, SlideMixin):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
permissions = (
|
permissions = (
|
||||||
('can_see_application', ugettext_noop("Can see motions")),
|
('can_see_motion', ugettext_noop("Can see motions")),
|
||||||
('can_create_application', ugettext_noop("Can create motions")),
|
('can_create_motion', ugettext_noop("Can create motions")),
|
||||||
('can_support_application', ugettext_noop("Can support motions")),
|
('can_support_motion', ugettext_noop("Can support motions")),
|
||||||
('can_manage_application', ugettext_noop("Can manage motions")),
|
('can_manage_motion', ugettext_noop("Can manage motions")),
|
||||||
)
|
)
|
||||||
|
ordering = ('number',)
|
||||||
|
|
||||||
|
|
||||||
class AVersion(models.Model):
|
class AVersion(models.Model):
|
||||||
title = models.CharField(max_length=100, verbose_name = _("Title"))
|
title = models.CharField(max_length=100, verbose_name=_("Title"))
|
||||||
text = models.TextField(verbose_name = _("Text"))
|
text = models.TextField(verbose_name=_("Text"))
|
||||||
reason = models.TextField(null=True, blank=True, verbose_name = _("Reason"))
|
reason = models.TextField(null=True, blank=True, verbose_name=_("Reason"))
|
||||||
rejected = models.BooleanField() # = Not Permitted
|
rejected = models.BooleanField() # = Not Permitted
|
||||||
time = models.DateTimeField(auto_now=True)
|
time = models.DateTimeField(auto_now=True)
|
||||||
application = models.ForeignKey(Application)
|
motion = models.ForeignKey(Motion)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return "%s %s" % (self.id, self.title)
|
return "%s %s" % (self.id, self.title)
|
||||||
@ -553,31 +552,31 @@ class AVersion(models.Model):
|
|||||||
return self._aid
|
return self._aid
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self._aid = AVersion.objects \
|
self._aid = AVersion.objects \
|
||||||
.filter(application=self.application) \
|
.filter(motion=self.motion) \
|
||||||
.filter(id__lte=self.id).count()
|
.filter(id__lte=self.id).count()
|
||||||
return self._aid
|
return self._aid
|
||||||
|
|
||||||
register_slidemodel(Application)
|
register_slidemodel(Motion)
|
||||||
|
|
||||||
|
|
||||||
class ApplicationVote(BaseVote):
|
class MotionVote(BaseVote):
|
||||||
option = models.ForeignKey('ApplicationOption')
|
option = models.ForeignKey('MotionOption')
|
||||||
|
|
||||||
|
|
||||||
class ApplicationOption(BaseOption):
|
class MotionOption(BaseOption):
|
||||||
poll = models.ForeignKey('ApplicationPoll')
|
poll = models.ForeignKey('MotionPoll')
|
||||||
vote_class = ApplicationVote
|
vote_class = MotionVote
|
||||||
|
|
||||||
|
|
||||||
class ApplicationPoll(BasePoll, CountInvalid, CountVotesCast):
|
class MotionPoll(BasePoll, CountInvalid, CountVotesCast):
|
||||||
option_class = ApplicationOption
|
option_class = MotionOption
|
||||||
vote_values = [ugettext_noop('Yes'), ugettext_noop('No'),
|
vote_values = [
|
||||||
ugettext_noop('Abstain')]
|
ugettext_noop('Yes'), ugettext_noop('No'), ugettext_noop('Abstain')]
|
||||||
|
|
||||||
application = models.ForeignKey(Application)
|
motion = models.ForeignKey(Motion)
|
||||||
|
|
||||||
def get_application(self):
|
def get_motion(self):
|
||||||
return self.application
|
return self.motion
|
||||||
|
|
||||||
def set_options(self):
|
def set_options(self):
|
||||||
#TODO: maybe it is possible with .create() to call this without poll=self
|
#TODO: maybe it is possible with .create() to call this without poll=self
|
||||||
@ -588,20 +587,20 @@ class ApplicationPoll(BasePoll, CountInvalid, CountVotesCast):
|
|||||||
CountVotesCast.append_pollform_fields(self, fields)
|
CountVotesCast.append_pollform_fields(self, fields)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('application_poll_view', args=[self.id])
|
return reverse('motion_poll_view', args=[self.id])
|
||||||
|
|
||||||
def get_ballot(self):
|
def get_ballot(self):
|
||||||
return self.application.applicationpoll_set.filter(id__lte=self.id).count()
|
return self.motion.motionpoll_set.filter(id__lte=self.id).count()
|
||||||
|
|
||||||
|
|
||||||
@receiver(default_config_value, dispatch_uid="application_default_config")
|
@receiver(default_config_value, dispatch_uid="motion_default_config")
|
||||||
def default_config(sender, key, **kwargs):
|
def default_config(sender, key, **kwargs):
|
||||||
return {
|
return {
|
||||||
'application_min_supporters': 0,
|
'motion_min_supporters': 0,
|
||||||
'application_preamble': _('The Assembly may decide,'),
|
'motion_preamble': _('The assembly may decide,'),
|
||||||
'application_pdf_ballot_papers_selection': 'CUSTOM_NUMBER',
|
'motion_pdf_ballot_papers_selection': 'CUSTOM_NUMBER',
|
||||||
'application_pdf_ballot_papers_number': '8',
|
'motion_pdf_ballot_papers_number': '8',
|
||||||
'application_pdf_title': _('Motions'),
|
'motion_pdf_title': _('Motions'),
|
||||||
'application_pdf_preamble': '',
|
'motion_pdf_preamble': '',
|
||||||
'application_allow_trivial_change': False,
|
'motion_allow_trivial_change': False,
|
||||||
}.get(key)
|
}.get(key)
|
66
openslides/motion/templates/motion/base_motion.html
Normal file
66
openslides/motion/templates/motion/base_motion.html
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% load tags %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
|
{% block submenu %}
|
||||||
|
{% url motion_overview as url_motionoverview %}
|
||||||
|
<h4>{% trans "Motions" %}</h4>
|
||||||
|
<ul>
|
||||||
|
<li class="{% if request.path == url_motionoverview %}selected{% endif %}"><a href="{% url motion_overview %}">{% trans "All motions" %}</a></li>
|
||||||
|
{% if perms.motion.can_create_motion or perms.motion.can_manage_motion %}
|
||||||
|
<li class="{% active request '/motion/new' %}"><a href="{% url motion_new %}">{% trans "New motion" %}</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.motion.can_manage_motion %}
|
||||||
|
<li class="{% active request '/motion/import' %}"><a href="{% url motion_import %}">{% trans 'Import motions' %}</a></li>
|
||||||
|
{% endif %}
|
||||||
|
<li><a href="{% url print_motions %}"><img src="{% static 'images/icons/pdf.png' %}"> {% trans 'All motions as PDF' %}</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{# second submenu #}
|
||||||
|
{% if motion %}
|
||||||
|
<br>
|
||||||
|
<h3>{% trans "Motion No." %}
|
||||||
|
{% if motion.number != None %}
|
||||||
|
{{ motion.number }}
|
||||||
|
{% else %}
|
||||||
|
<i>[-]</i>
|
||||||
|
{% endif %}
|
||||||
|
</h3>
|
||||||
|
<ul>
|
||||||
|
{# view motion #}
|
||||||
|
{% url motion_view motion.id as url_motionview %}
|
||||||
|
<li class="{% if request.path == url_motionview %}selected{% endif %}"><a href="{% url motion_view motion.id %}">{% trans 'View motion' %}</a></li>
|
||||||
|
{# edit motion #}
|
||||||
|
{% if "edit" in actions %}
|
||||||
|
{% url motion_edit motion.id as url_motionedit %}
|
||||||
|
<li class="{% if request.path == url_motionedit %}selected{% endif %}"><a href="{% url motion_edit motion.id %}"><img src="{% static 'images/icons/edit.png' %}"> {% trans 'Edit motion' %}</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{# delete motion #}
|
||||||
|
{% if "delete" in actions %}
|
||||||
|
<li><a href="{% url motion_delete motion.id %}"><img src="{% static 'images/icons/delete.png' %}"> {% trans 'Delete motion' %}</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{# PDF #}
|
||||||
|
<li><a href="{% url print_motion motion.id %}"><img src="{% static 'images/icons/pdf.png' %}"> {% trans 'Motion as PDF' %}</a></li>
|
||||||
|
{# activate and polls #}
|
||||||
|
{% if perms.projector.can_manage_projector %}
|
||||||
|
<li>
|
||||||
|
<a class="activate_link {% if item.active %}active{% endif %}" href="{% url projector_activate_slide motion.sid %}"><img src="{% static 'images/icons/projector.png' %}"> {% trans 'Show Motion' %}</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.motion.can_manage_motion %}
|
||||||
|
{% for poll in motion.polls %}
|
||||||
|
{% url motion_poll_view poll.id as url_motionpollview %}
|
||||||
|
<li class="{% if request.path == url_motionpollview %}selected{% endif %}"><a href="{% url motion_poll_view poll.id %}"><img src="{% static 'images/icons/edit.png' %}"> {{ forloop.counter }}. {% trans "Vote" %}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{# Agenda Item #}
|
||||||
|
{% if perms.agenda.can_manage_agenda %}
|
||||||
|
<li>
|
||||||
|
<a href="{% url motion_create_agenda motion.id %}">{% trans 'New agenda item' %}</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block title %}{{ block.super }} – {% trans "Application settings" %}{% endblock %}
|
{% block title %}{{ block.super }} – {% trans "Motion settings" %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{% trans "Configuration" %}: {% trans "Applications" %}
|
<h1>{% trans "Configuration" %}: {% trans "Motions" %}
|
||||||
{% block config_submenu %}{{ block.super }}{% endblock %}
|
{% block config_submenu %}{{ block.super }}{% endblock %}
|
||||||
</h1>
|
</h1>
|
||||||
<form action="" method="post">{% csrf_token %}
|
<form action="" method="post">{% csrf_token %}
|
||||||
@ -14,8 +14,8 @@
|
|||||||
<button class="button" type="submit">
|
<button class="button" type="submit">
|
||||||
<span class="icon ok">{% trans 'Save' %}</span>
|
<span class="icon ok">{% trans 'Save' %}</span>
|
||||||
</button>
|
</button>
|
||||||
<a href='{% url config_application %}'>
|
<a href='{% url config_motion %}'>
|
||||||
<button class="button" type="button" onclick="window.location='{% url config_application %}'">
|
<button class="button" type="button" onclick="window.location='{% url config_motion %}'">
|
||||||
<span class="icon cancel">{% trans 'Cancel' %}</span>
|
<span class="icon cancel">{% trans 'Cancel' %}</span>
|
||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{{ block.super }} –
|
{{ block.super }} –
|
||||||
{% if application %}
|
{% if motion %}
|
||||||
{% trans "Edit motion" %}
|
{% trans "Edit motion" %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans "New motion" %}
|
{% trans "New motion" %}
|
||||||
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>
|
<h1>
|
||||||
{% if application %}
|
{% if motion %}
|
||||||
{% trans "Edit motion" %}
|
{% trans "Edit motion" %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans "New motion" %}
|
{% trans "New motion" %}
|
||||||
@ -43,13 +43,12 @@
|
|||||||
<button class="btn" type="submit" name="apply">
|
<button class="btn" type="submit" name="apply">
|
||||||
{% trans 'Apply' %}
|
{% trans 'Apply' %}
|
||||||
</button>
|
</button>
|
||||||
<a href='{% url application_overview %}'>
|
<a href='{% url motion_overview %}'>
|
||||||
<button class="btn" type="button" onclick="window.location='{% url application_overview %}'">
|
<button class="btn" type="button" onclick="window.location='{% url motion_overview %}'">
|
||||||
{% trans 'Cancel' %}</span>
|
{% trans 'Cancel' %}
|
||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<small>* {% trans "required" %}</small>
|
<small>* {% trans "required" %}</small>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -12,7 +12,10 @@
|
|||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p>{% trans 'Select a CSV file to import motions!' %}</p>
|
<p>{% trans 'Select a CSV file to import motions!' %}</p>
|
||||||
<p>{% trans 'Required comma separated values: <code>{number, title, text, reason, first_name, last_name}</code> (<code>number</code> and <code>reason</code> are optional and may be empty)' %}
|
<p>{% trans 'Required comma separated values' %}:
|
||||||
|
<code>({% trans 'number, title, text, reason, first_name, last_name, is_group' %})</code>
|
||||||
|
<br>
|
||||||
|
{% trans '<code>number</code>, <code>reason</code> and <code>is_group</code> are optional and may be empty' %}.
|
||||||
<br>
|
<br>
|
||||||
{% trans 'Required CSV file encoding: UTF-8 (Unicode).' %}
|
{% trans 'Required CSV file encoding: UTF-8 (Unicode).' %}
|
||||||
</p>
|
</p>
|
||||||
@ -37,7 +40,7 @@
|
|||||||
<button class="btn btn-primary" type="submit">
|
<button class="btn btn-primary" type="submit">
|
||||||
{% trans 'Import' %}
|
{% trans 'Import' %}
|
||||||
</button>
|
</button>
|
||||||
<a href='{% url application_overview %}' class="btn">
|
<a href='{% url motionn_overview %}' class="btn">
|
||||||
{% trans 'Cancel' %}
|
{% trans 'Cancel' %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
@ -43,10 +43,9 @@
|
|||||||
<option value="wit" {% if 'on' in request.GET.status and 'wit' in request.GET.statusvalue %}selected{% endif %}>{% trans "Withdrawen (by submitter)" %}</option>
|
<option value="wit" {% if 'on' in request.GET.status and 'wit' in request.GET.statusvalue %}selected{% endif %}>{% trans "Withdrawen (by submitter)" %}</option>
|
||||||
<option value="rev" {% if 'rev' in request.GET.statusvalue %}selected{% endif %}>{% trans "Needs Review" %}</option>
|
<option value="rev" {% if 'rev' in request.GET.statusvalue %}selected{% endif %}>{% trans "Needs Review" %}</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
<small><i>{{ applications|length }}
|
<small><i>{{ motions|length }}
|
||||||
{% blocktrans count counter=applications|length %}motion{% plural %}motions{% endblocktrans %}
|
{% blocktrans count counter=motions|length context "number of motions"%}motion{% plural %}motions{% endblocktrans %}
|
||||||
</i></small>
|
</i></small>
|
||||||
<table class="table table-striped table-bordered">
|
<table class="table table-striped table-bordered">
|
||||||
<tr>
|
<tr>
|
||||||
@ -60,39 +59,39 @@
|
|||||||
<th class="optional"><a href="?sort=time{% if 'time' in request.GET.sort and 'reverse' not in request.GET %}&reverse{%endif%}">{% trans "Creation Time" %}<a></th>
|
<th class="optional"><a href="?sort=time{% if 'time' in request.GET.sort and 'reverse' not in request.GET %}&reverse{%endif%}">{% trans "Creation Time" %}<a></th>
|
||||||
<th style="width: 1px;">{% trans "Actions" %}</th>
|
<th style="width: 1px;">{% trans "Actions" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for app_info in applications %}
|
{% for app_info in motions %}
|
||||||
{% with application=app_info.application useractions=app_info.actions %}
|
{% with motion=app_info.motion useractions=app_info.actions %}
|
||||||
<tr class="{% cycle '' 'odd' %}
|
<tr class="{% cycle '' 'odd' %}
|
||||||
{% if application.active %}activeline{% endif %}">
|
{% if motion.active %}activeline{% endif %}">
|
||||||
<td>{% if application.number %}{{ application.number }}{% else %}-{% endif %}</td>
|
<td>{% if motion.number %}{{ motion.number }}{% else %}-{% endif %}</td>
|
||||||
<td><a href="{% url application_view application.id %}">{{ application.public_version.title }}</a></td>
|
<td><a href="{% url motion_view motion.id %}">{{ motion.public_version.title }}</a></td>
|
||||||
{% if min_supporters > 0 %}
|
{% if min_supporters > 0 %}
|
||||||
<td>{{ application.supporter.count }}</td>
|
<td>{{ motion.count_supporters }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td>{% if application.status != "pub" %}
|
<td>{% if motion.status != "pub" %}
|
||||||
{{ application.get_status_display }}<br>
|
{{ motion.get_status_display }}<br>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for note in application.notes %}
|
{% for note in motion.notes %}
|
||||||
{{ note }}
|
{{ note }}
|
||||||
{% if not forloop.last %}<br>{%endif%}
|
{% if not forloop.last %}<br>{%endif%}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ application.submitter }}</td>
|
<td>{{ motion.submitter }}</td>
|
||||||
<td class="optional">{{ application.creation_time }}</td>
|
<td class="optional">{{ motion.creation_time }}</td>
|
||||||
<td>
|
<td>
|
||||||
<span style="width: 1px; white-space: nowrap;">
|
<span style="width: 1px; white-space: nowrap;">
|
||||||
{% if perms.projector.can_manage_projector %}
|
{% if perms.projector.can_manage_projector %}
|
||||||
<a class="activate_link {% if application.active %}active{% endif %}" href="{% url projector_activate_slide application.sid %}" title="{% trans 'Activate motion' %}">
|
<a class="activate_link {% if motion.active %}active{% endif %}" href="{% url projector_activate_slide motion.sid %}" title="{% trans 'Activate motion' %}">
|
||||||
<span></span>
|
<span></span>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.application.can_manage_application %}
|
{% if perms.motion.can_manage_motion %}
|
||||||
<a href="{% url application_edit application.id %}"><img src="{% static 'images/icons/edit.png' %}" title="{% trans 'Edit motion' %}"></a>
|
<a href="{% url motion_edit motion.id %}"><img src="{% static 'images/icons/edit.png' %}" title="{% trans 'Edit motion' %}"></a>
|
||||||
{% if "delete" in useractions %}
|
{% if "delete" in useractions %}
|
||||||
<a href="{% url application_delete application.id %}"><img src="{% static 'images/icons/delete.png' %}" title="{% trans 'Delete motion' %}"></a>
|
<a href="{% url motion_delete motion.id %}"><img src="{% static 'images/icons/delete.png' %}" title="{% trans 'Delete motion' %}"></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url print_application application.id %}" title="{% trans 'Motion as PDF' %}"><img src="{% static 'images/icons/pdf.png' %}"></a>
|
<a href="{% url print_motion motion.id %}" title="{% trans 'Motion as PDF' %}"><img src="{% static 'images/icons/pdf.png' %}"></a>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
@ -4,14 +4,14 @@
|
|||||||
{% load staticfiles %}
|
{% load staticfiles %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{{ block.super }} - {% trans "Motion" %} {{ application.number }}, {{ ballot }}. {% trans "Vote" %}
|
{{ block.super }} - {% trans "Motion" %} {{ motion.number }}, {{ ballot }}. {% trans "Vote" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>
|
<h1>
|
||||||
{{ application.title }}
|
{{ motion.public_version.title }}
|
||||||
<small>
|
<small>
|
||||||
{% trans "Motion" %} {{ application.number }}, {{ ballot }}. {% trans "Vote" %}
|
{% trans "Motion" %} {{ motion.number }}, {{ ballot }}. {% trans "Vote" %}
|
||||||
</small>
|
</small>
|
||||||
<small class="pull-right">
|
<small class="pull-right">
|
||||||
<div class="btn-toolbar">
|
<div class="btn-toolbar">
|
||||||
@ -39,6 +39,7 @@
|
|||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<i class="helptext">{% trans "Special values" %}: -1 = {% trans 'majority' %}; -2 = {% trans 'undocumented' %}</i>
|
<i class="helptext">{% trans "Special values" %}: -1 = {% trans 'majority' %}; -2 = {% trans 'undocumented' %}</i>
|
||||||
|
|
||||||
<form action="" method="post" class="small-form">{% csrf_token %}
|
<form action="" method="post" class="small-form">{% csrf_token %}
|
||||||
{{ pre_form }}
|
{{ pre_form }}
|
||||||
<span id="poll_id" style="display:none">{{ poll.id }}</span>
|
<span id="poll_id" style="display:none">{{ poll.id }}</span>
|
||||||
@ -65,7 +66,7 @@
|
|||||||
{{ post_form }}
|
{{ post_form }}
|
||||||
<!-- ballot paper button -->
|
<!-- ballot paper button -->
|
||||||
<p>
|
<p>
|
||||||
<a href='{% url print_application_poll poll.id %}' class="btn">
|
<a href='{% url print_motion_poll poll.id %}' class="btn">
|
||||||
<i class="icon-print"></i> {% trans 'Ballot paper as PDF' %}
|
<i class="icon-print"></i> {% trans 'Ballot paper as PDF' %}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
@ -77,7 +78,7 @@
|
|||||||
<button type="submit" name="apply" class="btn">
|
<button type="submit" name="apply" class="btn">
|
||||||
{% trans 'Apply' %}
|
{% trans 'Apply' %}
|
||||||
</button>
|
</button>
|
||||||
<a href='{% url application_view application.id %}' class="btn">
|
<a href='{% url motion_view motion.id %}' class="btn">
|
||||||
{% trans 'Cancel' %}
|
{% trans 'Cancel' %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{{ block.super }} - {% trans "Motion" %}
|
{{ block.super }} - {% trans "Motion" %}
|
||||||
{% if application.number != None %}
|
{% if motion.number != None %}
|
||||||
{{ application.number }}
|
{{ motion.number }}
|
||||||
{% else %}
|
{% else %}
|
||||||
[{% trans "no number" %}]
|
[{% trans "no number" %}]
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -17,8 +17,8 @@
|
|||||||
<h1>
|
<h1>
|
||||||
{{ version.title }}
|
{{ version.title }}
|
||||||
<small>
|
<small>
|
||||||
{% if application.number != None %}
|
{% if motion.number != None %}
|
||||||
{% trans "Motion" %} {{ application.number }},
|
{% trans "Motion" %} {{ motion.number }},
|
||||||
{% else %}
|
{% else %}
|
||||||
<i>[{% trans "no number" %}]</i>,
|
<i>[{% trans "no number" %}]</i>,
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -26,8 +26,8 @@
|
|||||||
</small>
|
</small>
|
||||||
<small class="pull-right">
|
<small class="pull-right">
|
||||||
<div class="btn-toolbar">
|
<div class="btn-toolbar">
|
||||||
<a href="{% url application_overview %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Back to overview" %}</a>
|
<a href="{% url motion_overview %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Back to overview" %}</a>
|
||||||
<a href="{% url print_application application.id %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Print this motion as PDF' %}"><i class="icon-print"></i> PDF</a>
|
<a href="{% url print_motion motion.id %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Print this motion as PDF' %}"><i class="icon-print"></i> PDF</a>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a data-toggle="dropdown" href="#" class="btn btn-mini dropdown-toggle">
|
<a data-toggle="dropdown" href="#" class="btn btn-mini dropdown-toggle">
|
||||||
{% trans 'More actions' %}
|
{% trans 'More actions' %}
|
||||||
@ -36,28 +36,28 @@
|
|||||||
<ul class="dropdown-menu pull-right">
|
<ul class="dropdown-menu pull-right">
|
||||||
<!-- edit -->
|
<!-- edit -->
|
||||||
{% if "edit" in actions %}
|
{% if "edit" in actions %}
|
||||||
<li><a href="{% url application_edit application.id %}"><i class="icon-edit"></i> {% trans 'Edit motion' %}</a></li>
|
<li><a href="{% url motion_edit motion.id %}"><i class="icon-edit"></i> {% trans 'Edit motion' %}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!-- delete -->
|
<!-- delete -->
|
||||||
{% if "delete" in actions %}
|
{% if "delete" in actions %}
|
||||||
<li><a href="{% url application_delete application.id %}"><i class="icon-remove"></i> {% trans 'Delete motion' %}</a></li>
|
<li><a href="{% url motion_delete motion.id %}"><i class="icon-remove"></i> {% trans 'Delete motion' %}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!-- activate projector -->
|
<!-- activate projector -->
|
||||||
{% if perms.projector.can_manage_projector %}
|
{% if perms.projector.can_manage_projector %}
|
||||||
<li>
|
<li>
|
||||||
<a class="activate_link {% if item.active %}active{% endif %}" href="{% url projector_activate_slide application.sid %}"><i class="icon-facetime-video"></i> {% trans 'Show Application' %}</a>
|
<a class="activate_link {% if item.active %}active{% endif %}" href="{% url projector_activate_slide motion.sid %}"><i class="icon-facetime-video"></i> {% trans 'Show motion' %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!-- edit poll -->
|
<!-- edit poll -->
|
||||||
{% if perms.application.can_manage_application %}
|
{% if perms.motion.can_manage_motion %}
|
||||||
{% for poll in application.polls %}
|
{% for poll in motion.polls %}
|
||||||
<li><a href="{% url application_poll_view poll.id %}"><i class="icon-edit"></i> {{ forloop.counter }}. {% trans "Vote" %}</a></li>
|
<li><a href="{% url motion_poll_view poll.id %}"><i class="icon-edit"></i> {{ forloop.counter }}. {% trans "Vote" %}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!-- create agenda item -->
|
<!-- create agenda item -->
|
||||||
{% if perms.agenda.can_manage_agenda %}
|
{% if perms.agenda.can_manage_agenda %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url application_create_agenda application.id %}"><i class="icon-plus"></i> {% trans 'New agenda item' %}</a>
|
<a href="{% url motion_create_agenda motion.id %}"><i class="icon-plus"></i> {% trans 'New agenda item' %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
@ -68,12 +68,12 @@
|
|||||||
|
|
||||||
<div class="row-fluid">
|
<div class="row-fluid">
|
||||||
<div class="span9">
|
<div class="span9">
|
||||||
{% if application.public_version != application.last_version %}
|
{% if motion.public_version != motion.last_version %}
|
||||||
⋅
|
⋅
|
||||||
{% if version == application.public_version %}
|
{% if version == motion.public_version %}
|
||||||
{% trans "This is not the newest version." %} <a href="{% url application_view_newest application.id %}">{% trans "Go to version" %} {{ application.last_version.aid }}.</a>
|
{% trans "This is not the newest version." %} <a href="{% url motion_view_newest motion.id %}">{% trans "Go to version" %} {{ motion.last_version.aid }}.</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans "This is not the authorized version." %} <a href="{% url application_view application.id %}">{% trans "Go to version" %} {{ application.public_version.aid }}.</a>
|
{% trans "This is not the authorized version." %} <a href="{% url motion_view motion.id %}">{% trans "Go to version" %} {{ motion.public_version.aid }}.</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@ -90,7 +90,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<br>
|
<br>
|
||||||
<!-- Version history -->
|
<!-- Version history -->
|
||||||
{% if application.versions|length > 1 %}
|
{% if motion.versions|length > 1 %}
|
||||||
<h4>{% trans "Version History" %}:</h4>
|
<h4>{% trans "Version History" %}:</h4>
|
||||||
<table class="table table-striped table-bordered">
|
<table class="table table-striped table-bordered">
|
||||||
<tr>
|
<tr>
|
||||||
@ -101,18 +101,18 @@
|
|||||||
<th>{% trans "Text" %}</th>
|
<th>{% trans "Text" %}</th>
|
||||||
<th>{% trans "Reason" %}</th>
|
<th>{% trans "Reason" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for revision in application.versions %}
|
{% for revision in motion.versions %}
|
||||||
<tr>
|
<tr>
|
||||||
<td style="white-space:nowrap;">
|
<td style="white-space:nowrap;">
|
||||||
{% if application.status != "pub" %}
|
{% if motion.status != "pub" %}
|
||||||
{% if revision == application.permitted %}
|
{% if revision == motion.permitted %}
|
||||||
<img title="{% trans 'Version authorized' %}" src="{% static 'images/icons/accept.png' %}">
|
<img title="{% trans 'Version authorized' %}" src="{% static 'images/icons/accept.png' %}">
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if perms.application.can_manage_application %}
|
{% if perms.motion.can_manage_motion %}
|
||||||
<a href="{% url application_version_permit revision.id %}"><img title="{% trans 'Permit Version' %}" src="{% static 'images/icons/accept-grey.png' %}"></a>
|
<a href="{% url motion_version_permit revision.id %}"><img title="{% trans 'Permit Version' %}" src="{% static 'images/icons/accept-grey.png' %}"></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if not revision.rejected and revision.id > application.permitted.id and perms.application.can_manage_application %}
|
{% if not revision.rejected and revision.id > motion.permitted.id and perms.motion.can_manage_motion %}
|
||||||
<a href="{% url application_version_reject revision.id %}"><img title="{% trans 'Reject Version' %}" src="{% static 'images/icons/reject-grey.png' %}"></a>
|
<a href="{% url motion_version_reject revision.id %}"><img title="{% trans 'Reject Version' %}" src="{% static 'images/icons/reject-grey.png' %}"></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if revision.rejected %}
|
{% if revision.rejected %}
|
||||||
@ -148,51 +148,49 @@
|
|||||||
</table>
|
</table>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!-- Log -->
|
<!-- Log -->
|
||||||
{% if perms.application.can_manage_application %}
|
{% if perms.motion.can_manage_motion %}
|
||||||
<h4>{% trans "Log" %}:</h4>
|
<h4>{% trans "Log" %}:</h4>
|
||||||
<small>{{ application.log|linebreaks }}</small>
|
<small>{{ motion.log|linebreaks }}</small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</div> <!--/span-->
|
</div> <!--/span-->
|
||||||
|
|
||||||
|
|
||||||
<div class="span3">
|
<div class="span3">
|
||||||
<div class="well">
|
<div class="well">
|
||||||
<!-- Submitter -->
|
<!-- Submitter -->
|
||||||
<h5>{% trans "Submitter" %}:</h5>
|
<h5>{% trans "Submitter" %}:</h5>
|
||||||
{{ application.submitter }}
|
{{ motion.submitter }}
|
||||||
{% if user == application.submitter.user %}
|
|
||||||
<img src="{% static 'images/icons/user-information.png' %}" title="{% trans 'Me' %}">
|
|
||||||
{% endif %}
|
|
||||||
<!-- Supporters -->
|
<!-- Supporters -->
|
||||||
{% if min_supporters > 0 %}
|
{% if min_supporters > 0 %}
|
||||||
<h5>{% trans "Supporters" %}: *</h5>
|
<h5>{% trans "Supporters" %}: *</h5>
|
||||||
{% if not application.supporters %}
|
{% if not motion.supporters %}
|
||||||
-
|
-
|
||||||
{% else %}
|
{% else %}
|
||||||
<ol>
|
<ol>
|
||||||
{% for supporter in application.supporters %}
|
{% for supporter in motion.supporters %}
|
||||||
<li> {{ supporter }}</li>
|
<li>{{ supporter }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ol>
|
</ol>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!-- Status -->
|
<!-- Status -->
|
||||||
<h5>{% trans "Status" %}:</h5>
|
<h5>{% trans "Status" %}:</h5>
|
||||||
{% if application.status != "pub" %}
|
{% if motion.status != "pub" %}
|
||||||
{% trans application.get_status_display %}
|
{% trans motion.get_status_display %}
|
||||||
<br>
|
<br>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for note in application.notes %}
|
{% for note in motion.notes %}
|
||||||
{{ note }}
|
{{ note }}
|
||||||
{% if not forloop.last %}<br>{% endif %}
|
{% if not forloop.last %}<br>{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<!-- Vote results -->
|
<!-- Vote results -->
|
||||||
<h5>{% trans "Vote results" %}:</h5>
|
<h5>{% trans "Vote results" %}:</h5>
|
||||||
{% with application.polls as polls %}
|
{% with motion.polls as polls %}
|
||||||
{% if not polls.exists %}
|
{% if not polls.exists %}
|
||||||
{% if perms.application.can_manage_application %}
|
{% if perms.motion.can_manage_motion %}
|
||||||
{% if "genpoll" in actions %}
|
{% if "genpoll" in actions %}
|
||||||
<a href='{% url application_gen_poll application.id %}' class="btn btn-mini">
|
<a href='{% url motion_gen_poll motion.id %}' class="btn btn-mini">
|
||||||
<i class="icon-signal"></i> {% trans 'New vote' %}
|
<i class="icon-signal"></i> {% trans 'New vote' %}
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -204,11 +202,11 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<ol>
|
<ol>
|
||||||
{% for poll in polls %}
|
{% for poll in polls %}
|
||||||
{% if perms.application.can_manage_application or poll.has_votes %}
|
{% if perms.motion.can_manage_motion or poll.has_votes %}
|
||||||
<li>{% trans "Vote" %}
|
<li>{% trans "Vote" %}
|
||||||
{% if perms.application.can_manage_application %}
|
{% if perms.motion.can_manage_motion %}
|
||||||
<a class="btn btn-mini" href="{% url application_poll_view poll.id %}" title="{% trans 'Edit Vote' %}"><i class="icon-edit"></i></a>
|
<a class="btn btn-mini" href="{% url motion_poll_view poll.id %}" title="{% trans 'Edit Vote' %}"><i class="icon-edit"></i></a>
|
||||||
<a class="btn btn-mini" href="{% url application_poll_delete poll.id %}" title="{% trans 'Delete Vote' %}"><i class="icon-remove"></i></a>
|
<a class="btn btn-mini" href="{% url motion_poll_delete poll.id %}" title="{% trans 'Delete Vote' %}"><i class="icon-remove"></i></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<br>
|
<br>
|
||||||
{% if poll.has_votes %}
|
{% if poll.has_votes %}
|
||||||
@ -221,17 +219,17 @@
|
|||||||
<img src="{% static 'images/icons/voting-total.png' %}" title="{% trans 'Votes cast' %}"> {{ poll.print_votescast }}
|
<img src="{% static 'images/icons/voting-total.png' %}" title="{% trans 'Votes cast' %}"> {{ poll.print_votescast }}
|
||||||
</div>
|
</div>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% if perms.application.can_manage_application %}
|
{% if perms.motion.can_manage_motion %}
|
||||||
{% if forloop.last %}
|
{% if forloop.last %}
|
||||||
{% if "genpoll" in actions %}
|
{% if "genpoll" in actions %}
|
||||||
<a href='{% url application_gen_poll application.id %}' class="btn btn-mini">
|
<a href='{% url motion_gen_poll motion.id %}' class="btn btn-mini">
|
||||||
<i class="icon-signal"></i> {% trans 'New vote' %}
|
<i class="icon-signal"></i> {% trans 'New vote' %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if perms.application.can_manage_application %}
|
{% if perms.motion.can_manage_motion %}
|
||||||
<span class="label label-info">{% trans 'No results' %}</span>
|
<span class="label label-info">{% trans 'No results' %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -242,25 +240,25 @@
|
|||||||
{% endwith %}
|
{% endwith %}
|
||||||
<!-- Creation Time -->
|
<!-- Creation Time -->
|
||||||
<h5>{% trans "Creation Time" %}:</h5>
|
<h5>{% trans "Creation Time" %}:</h5>
|
||||||
{{ application.creation_time }}
|
{{ motion.creation_time }}
|
||||||
<!-- Widthdraw button -->
|
<!-- Widthdraw button -->
|
||||||
{% if "wit" in actions and user == application.submitter.user %}
|
{% if "wit" in actions and user == motion.submitter.user %}
|
||||||
<br><br>
|
<br><br>
|
||||||
<a href='{% url application_set_status application.id 'wit' %}' class="btn">
|
<a href='{% url motion_set_status motion.id 'wit' %}' class="btn">
|
||||||
<span class="icon revert">{% trans 'Withdraw motion' %}</span>
|
<span class="icon revert">{% trans 'Withdraw motion' %}</span>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!-- Support/Unsupport button -->
|
<!-- Support/Unsupport button -->
|
||||||
{% if perms.application.can_support_application and min_supporters > 0 %}
|
{% if perms.motion.can_support_motion and min_supporters > 0 %}
|
||||||
{% if "unsupport" in actions %}
|
{% if "unsupport" in actions %}
|
||||||
<br><br>
|
<br><br>
|
||||||
<a href='{% url application_unsupport application.id %}' class="btn">
|
<a href='{% url motion_unsupport motion.id %}' class="btn">
|
||||||
{% trans 'Unsupport motion' %}
|
{% trans 'Unsupport motion' %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if "support" in actions %}
|
{% if "support" in actions %}
|
||||||
<br><br>
|
<br><br>
|
||||||
<a href='{% url application_support application.id %}'>
|
<a href='{% url motion_support motion.id %}'>
|
||||||
{% trans 'Support' %}
|
{% trans 'Support' %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -272,8 +270,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div> <!--/well-->
|
</div> <!--/well-->
|
||||||
|
|
||||||
{% if perms.application.can_manage_application %}
|
{% if perms.motion.can_manage_motion %}
|
||||||
<!-- Manage application box -->
|
<!-- Manage motion box -->
|
||||||
<div class="well">
|
<div class="well">
|
||||||
<h4>{% trans "Manage motion" %}</h4>
|
<h4>{% trans "Manage motion" %}</h4>
|
||||||
<!-- Formal validation -->
|
<!-- Formal validation -->
|
||||||
@ -281,16 +279,16 @@
|
|||||||
<h5>{% trans "Formal validation" %}:</h5>
|
<h5>{% trans "Formal validation" %}:</h5>
|
||||||
<div class="btn-group btn-group-vertical">
|
<div class="btn-group btn-group-vertical">
|
||||||
{% if "pub" in actions %}
|
{% if "pub" in actions %}
|
||||||
<a href='{% url application_set_status application.id 'pub' %}' class="btn">{% trans 'Publish' %}</a>
|
<a href='{% url motion_set_status motion.id 'pub' %}' class="btn">{% trans 'Publish' %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if "per" in actions %}
|
{% if "per" in actions %}
|
||||||
<a href='{% url application_permit application.id %}' class="btn btn-info">{% trans 'Permit' %}</a>
|
<a href='{% url motion_permit motion.id %}' class="btn btn-info">{% trans 'Permit' %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if "nop" in actions %}
|
{% if "nop" in actions %}
|
||||||
<a href='{% url application_notpermit application.id %}' class="btn">{% trans 'Not permit' %}</a>
|
<a href='{% url motion_notpermit motion.id %}' class="btn">{% trans 'Not permit' %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if "setnumber" in actions %}
|
{% if "setnumber" in actions %}
|
||||||
<a href='{% url application_set_number application.id %}' class="btn">{% trans 'Set number' %}</a>
|
<a href='{% url motion_set_number motion.id %}' class="btn">{% trans 'Set number' %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -299,12 +297,12 @@
|
|||||||
<h5>{% trans "Result after vote" %}:</h5>
|
<h5>{% trans "Result after vote" %}:</h5>
|
||||||
<div class="btn-group btn-group-vertical">
|
<div class="btn-group btn-group-vertical">
|
||||||
{% if "acc" in actions %}
|
{% if "acc" in actions %}
|
||||||
<a href='{% url application_set_status application.id 'acc' %}' class="btn btn-success">
|
<a href='{% url motion_set_status motion.id 'acc' %}' class="btn btn-success">
|
||||||
<i class="icon-ok icon-white"></i> {% trans 'Accepted' %}
|
<i class="icon-ok icon-white"></i> {% trans 'Accepted' %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if "rej" in actions %}
|
{% if "rej" in actions %}
|
||||||
<a href='{% url application_set_status application.id 'rej' %}' class="btn btn-danger">
|
<a href='{% url motion_set_status motion.id 'rej' %}' class="btn btn-danger">
|
||||||
<i class="icon-ban-circle icon-white"></i> {% trans 'Rejected' %}
|
<i class="icon-ban-circle icon-white"></i> {% trans 'Rejected' %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -320,16 +318,16 @@
|
|||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu pull-right">
|
<ul class="dropdown-menu pull-right">
|
||||||
{% if "adj" in actions %}
|
{% if "adj" in actions %}
|
||||||
<li><a href='{% url application_set_status application.id 'adj' %}'>{% trans 'Adjourned' %}</a></li>
|
<li><a href='{% url motion_set_status motion.id 'adj' %}'>{% trans 'Adjourned' %}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if "noc" in actions %}
|
{% if "noc" in actions %}
|
||||||
<li><a href='{% url application_set_status application.id 'noc' %}'>{% trans 'Not Concerned' %}</a></li>
|
<li><a href='{% url motion_set_status motion.id 'noc' %}'>{% trans 'Not Concerned' %}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if "com" in actions %}
|
{% if "com" in actions %}
|
||||||
<li><a href='{% url application_set_status application.id 'com' %}'>{% trans 'Commited a bill' %}</a></li>
|
<li><a href='{% url motion_set_status motion.id 'com' %}'>{% trans 'Commited a bill' %}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if "wit" in actions %}
|
{% if "wit" in actions %}
|
||||||
<li><a href='{% url application_set_status application.id 'wit' %}'>{% trans 'Withdrawed by Submitter' %}</a></li>
|
<li><a href='{% url motion_set_status motion.id 'wit' %}'>{% trans 'Withdrawed by Submitter' %}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -337,11 +335,11 @@
|
|||||||
<p></p>
|
<p></p>
|
||||||
<hr>
|
<hr>
|
||||||
<h5>{% trans "For Administration only:" %}</h5>
|
<h5>{% trans "For Administration only:" %}</h5>
|
||||||
<a href='{% url application_reset application.id %}' class="btn btn-danger">
|
<a href='{% url motion_reset motion.id %}' class="btn btn-danger">
|
||||||
{% trans 'Reset' %}
|
{% trans 'Reset' %}
|
||||||
</a>
|
</a>
|
||||||
</div> <!--/well-->
|
</div> <!--/well-->
|
||||||
{% endif %} {# end perms.application.can_support_application #}
|
{% endif %} {# end perms.motion.can_support_motion #}
|
||||||
</div> <!--/span-->
|
</div> <!--/span-->
|
||||||
</div> <!--/row-->
|
</div> <!--/row-->
|
||||||
{% endblock %}
|
{% endblock %}
|
34
openslides/motion/templates/motion/widget.html
Normal file
34
openslides/motion/templates/motion/widget.html
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{% load staticfiles %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load tags %}
|
||||||
|
|
||||||
|
<ul style="line-height: 180%">
|
||||||
|
{% for motion in motions %}
|
||||||
|
<li class="{% if motion.active %}activeline{% endif %}">
|
||||||
|
<a href="{% url projector_activate_slide motion.sid %}" class="activate_link {% if motion.active %}active{% endif %}">
|
||||||
|
<div></div>
|
||||||
|
</a>
|
||||||
|
<a href="{% model_url motion 'delete' %}" title="{% trans 'Delete' %}" class="icon delete right">
|
||||||
|
<span></span>
|
||||||
|
</a>
|
||||||
|
<a href="{% model_url motion 'edit' %}" title="{% trans 'Edit' %}" class="icon edit right">
|
||||||
|
<span></span>
|
||||||
|
</a>
|
||||||
|
<a href="{% url projctor_preview_slide motion.sid %}" title="{% trans 'Preview' %}" class="icon preview right">
|
||||||
|
<span></span>
|
||||||
|
</a>
|
||||||
|
<a href="{% model_url motion 'view' %}">
|
||||||
|
{{ motion.public_version.title }}
|
||||||
|
</a>
|
||||||
|
({% trans "motion" %}
|
||||||
|
{% if motion.number %}
|
||||||
|
{{ motion.number }})
|
||||||
|
{% else %}
|
||||||
|
<i>[{% trans "no number" %}]</i>)
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% empty %}
|
||||||
|
<li>{% trans 'No motion available.' %}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
@ -4,27 +4,27 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load staticfiles %}
|
{% load staticfiles %}
|
||||||
|
|
||||||
{% block title %}{{ block.super }} - {% trans "Motion" %} {{ application.number }}{% endblock %}
|
{% block title %}{{ block.super }} - {% trans "Motion" %} {{ motion.number }}{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="sidebar">
|
<div id="sidebar">
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<p><b>{% trans "Status" %}:</b><br>
|
<p><b>{% trans "Status" %}:</b><br>
|
||||||
{% if application.status != "pub" %}
|
{% if motion.status != "pub" %}
|
||||||
{% if application.status == "acc" %}
|
{% if motion.status == "acc" %}
|
||||||
<img src="{% static 'images/icons/voting-yes.png' %}">
|
<img src="{% static 'images/icons/voting-yes.png' %}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if application.status == "rej" %}
|
{% if motion.status == "rej" %}
|
||||||
<img src="{% static 'images/icons/voting-no.png' %}">
|
<img src="{% static 'images/icons/voting-no.png' %}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% trans application.get_status_display %}
|
{% trans motion.get_status_display %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% for note in application.notes %}
|
{% for note in motion.notes %}
|
||||||
{{ note }}
|
{{ note }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% with application.polls as polls %}
|
{% with motion.polls as polls %}
|
||||||
{% if polls.exists and polls.0.has_votes %}
|
{% if polls.exists and polls.0.has_votes %}
|
||||||
<p><b>{% trans "Poll result" %}:</b>
|
<p><b>{% trans "Poll result" %}:</b>
|
||||||
{% for poll in polls %}
|
{% for poll in polls %}
|
||||||
@ -53,29 +53,29 @@
|
|||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
<p><b>{% trans "Submitter" %}:</b><br>
|
<p><b>{% trans "Submitter" %}:</b><br>
|
||||||
{{ application.submitter }}
|
{{ motion.submitter }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1>
|
<h1>
|
||||||
{% if application.number != None %}
|
{% if motion.number != None %}
|
||||||
{% trans "Motion No." %} {{ application.number }}
|
{% trans "Motion No." %} {{ motion.number }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans "Motion" %} <i>[{% trans "no number" %}]</i>
|
{% trans "Motion" %} <i>[{% trans "no number" %}]</i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h1>
|
</h1>
|
||||||
<b>{{ application.public_version.title }}</b>
|
<b>{{ motion.public_version.title }}</b>
|
||||||
<hr>
|
<hr>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scrollcontent %}
|
{% block scrollcontent %}
|
||||||
<p>
|
<p>
|
||||||
<div class="text">{{ application.public_version.text|linebreaks }}</div>
|
<div class="text">{{ motion.public_version.text|linebreaks }}</div>
|
||||||
{% if application.public_version.reason %}
|
{% if motion.public_version.reason %}
|
||||||
<br>
|
<br>
|
||||||
<div class="reason"><p><b>{% trans "Reason" %}:</b></p>
|
<div class="reason"><p><b>{% trans "Reason" %}:</b></p>
|
||||||
{{ application.public_version.reason|linebreaks }}</div>
|
{{ motion.public_version.reason|linebreaks }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
{% endblock %}
|
{% endblock %}
|
141
openslides/motion/urls.py
Normal file
141
openslides/motion/urls.py
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
openslides.motion.urls
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
URL list for the motion app.
|
||||||
|
|
||||||
|
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
|
||||||
|
:license: GNU GPL, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.conf.urls.defaults import url, patterns
|
||||||
|
|
||||||
|
from openslides.motion.views import (MotionDelete, ViewPoll,
|
||||||
|
MotionPDF, MotionPollPDF, CreateAgendaItem, SupportView)
|
||||||
|
|
||||||
|
urlpatterns = patterns('openslides.motion.views',
|
||||||
|
url(r'^$',
|
||||||
|
'overview',
|
||||||
|
name='motion_overview',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^(?P<motion_id>\d+)/$',
|
||||||
|
'view',
|
||||||
|
name='motion_view',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^(?P<motion_id>\d+)/agenda/$',
|
||||||
|
CreateAgendaItem.as_view(),
|
||||||
|
name='motion_create_agenda',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^(?P<motion_id>\d+)/newest/$',
|
||||||
|
'view',
|
||||||
|
{'newest': True},
|
||||||
|
name='motion_view_newest',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^new/$',
|
||||||
|
'edit',
|
||||||
|
name='motion_new',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^import/$',
|
||||||
|
'motion_import',
|
||||||
|
name='motion_import',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^(?P<motion_id>\d+)/edit/$',
|
||||||
|
'edit',
|
||||||
|
name='motion_edit',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^(?P<motion_id>\d+)/del/$',
|
||||||
|
MotionDelete.as_view(),
|
||||||
|
name='motion_delete',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^del/$',
|
||||||
|
MotionDelete.as_view(),
|
||||||
|
{ 'motion_id' : None , 'motion_ids' : None },
|
||||||
|
name='motion_delete',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^(?P<motion_id>\d+)/setnumber/$',
|
||||||
|
'set_number',
|
||||||
|
name='motion_set_number',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^(?P<motion_id>\d+)/setstatus/(?P<status>[a-z]{3})/$',
|
||||||
|
'set_status',
|
||||||
|
name='motion_set_status',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^(?P<motion_id>\d+)/permit/$',
|
||||||
|
'permit',
|
||||||
|
name='motion_permit',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^version/(?P<aversion_id>\d+)/permit/$',
|
||||||
|
'permit_version',
|
||||||
|
name='motion_version_permit',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^version/(?P<aversion_id>\d+)/reject/$',
|
||||||
|
'reject_version',
|
||||||
|
name='motion_version_reject',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^(?P<motion_id>\d+)/notpermit/$',
|
||||||
|
'notpermit',
|
||||||
|
name='motion_notpermit',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^(?P<motion_id>\d+)/reset/$',
|
||||||
|
'reset',
|
||||||
|
name='motion_reset',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^(?P<motion_id>\d+)/support/$',
|
||||||
|
SupportView.as_view(support=True),
|
||||||
|
name='motion_support',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^(?P<motion_id>\d+)/unsupport/$',
|
||||||
|
SupportView.as_view(support=False),
|
||||||
|
name='motion_unsupport',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^(?P<motion_id>\d+)/gen_poll/$',
|
||||||
|
'gen_poll',
|
||||||
|
name='motion_gen_poll',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^print/$',
|
||||||
|
MotionPDF.as_view(),
|
||||||
|
{'motion_id': None},
|
||||||
|
name='print_motions',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^(?P<motion_id>\d+)/print/$',
|
||||||
|
MotionPDF.as_view(),
|
||||||
|
name='print_motion',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^poll/(?P<poll_id>\d+)/print/$',
|
||||||
|
MotionPollPDF.as_view(),
|
||||||
|
name='print_motion_poll',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^poll/(?P<poll_id>\d+)/$',
|
||||||
|
ViewPoll.as_view(),
|
||||||
|
name='motion_poll_view',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^poll/(?P<poll_id>\d+)/del/$',
|
||||||
|
'delete_poll',
|
||||||
|
name='motion_poll_delete',
|
||||||
|
),
|
||||||
|
)
|
957
openslides/motion/views.py
Normal file
957
openslides/motion/views.py
Normal file
@ -0,0 +1,957 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
openslides.motion.views
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Views for the motion app.
|
||||||
|
|
||||||
|
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
|
||||||
|
:license: GNU GPL, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# for python 2.5 support
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
|
||||||
|
try:
|
||||||
|
from urlparse import parse_qs
|
||||||
|
except ImportError: # python <= 2.5
|
||||||
|
from cgi import parse_qs
|
||||||
|
|
||||||
|
from reportlab.lib import colors
|
||||||
|
from reportlab.lib.units import cm
|
||||||
|
from reportlab.platypus import (
|
||||||
|
SimpleDocTemplate, PageBreak, Paragraph, Spacer, Table, TableStyle)
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.db import transaction
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.utils.translation import ugettext as _, ungettext
|
||||||
|
|
||||||
|
from openslides.utils import csv_ext
|
||||||
|
from openslides.utils.pdf import stylesheet
|
||||||
|
from openslides.utils.template import Tab
|
||||||
|
from openslides.utils.utils import (
|
||||||
|
template, permission_required, del_confirm_form, gen_confirm_form)
|
||||||
|
from openslides.utils.views import (
|
||||||
|
PDFView, RedirectView, DeleteView, FormView, SingleObjectMixin,
|
||||||
|
QuestionMixin)
|
||||||
|
from openslides.utils.person import get_person
|
||||||
|
from openslides.config.models import config
|
||||||
|
from openslides.projector.projector import Widget
|
||||||
|
from openslides.poll.views import PollFormView
|
||||||
|
from openslides.participant.api import gen_username, gen_password
|
||||||
|
from openslides.participant.models import User, Group
|
||||||
|
from openslides.agenda.models import Item
|
||||||
|
from openslides.motion.models import Motion, AVersion, MotionPoll
|
||||||
|
from openslides.motion.forms import (
|
||||||
|
MotionForm, MotionFormTrivialChanges, MotionManagerForm,
|
||||||
|
MotionManagerFormSupporter, MotionImportForm, ConfigForm)
|
||||||
|
|
||||||
|
|
||||||
|
@permission_required('motion.can_see_motion')
|
||||||
|
@template('motion/overview.html')
|
||||||
|
def overview(request):
|
||||||
|
"""
|
||||||
|
View all motions
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
sortfilter = parse_qs(request.COOKIES['votecollector_sortfilter'])
|
||||||
|
for value in sortfilter:
|
||||||
|
sortfilter[value] = sortfilter[value][0]
|
||||||
|
except KeyError:
|
||||||
|
sortfilter = {}
|
||||||
|
|
||||||
|
for value in [u'sort', u'reverse', u'number', u'status', u'needsup', u'statusvalue']:
|
||||||
|
if value in request.REQUEST:
|
||||||
|
if request.REQUEST[value] == '0':
|
||||||
|
try:
|
||||||
|
del sortfilter[value]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
sortfilter[value] = request.REQUEST[value]
|
||||||
|
|
||||||
|
query = Motion.objects.all()
|
||||||
|
if 'number' in sortfilter:
|
||||||
|
query = query.filter(number=None)
|
||||||
|
if 'status' in sortfilter:
|
||||||
|
if 'statusvalue' in sortfilter and 'on' in sortfilter['status']:
|
||||||
|
query = query.filter(status__iexact=sortfilter['statusvalue'])
|
||||||
|
|
||||||
|
if 'sort' in sortfilter:
|
||||||
|
if sortfilter['sort'] == 'title':
|
||||||
|
sort = 'aversion__title'
|
||||||
|
elif sortfilter['sort'] == 'time':
|
||||||
|
sort = 'aversion__time'
|
||||||
|
else:
|
||||||
|
sort = sortfilter['sort']
|
||||||
|
query = query.order_by(sort)
|
||||||
|
if sort.startswith('aversion_'):
|
||||||
|
# limit result to last version of an motion
|
||||||
|
query = query.filter(aversion__id__in=[x.last_version.id for x in Motion.objects.all()])
|
||||||
|
|
||||||
|
if 'reverse' in sortfilter:
|
||||||
|
query = query.reverse()
|
||||||
|
|
||||||
|
# todo: rewrite this with a .filter()
|
||||||
|
if 'needsup' in sortfilter:
|
||||||
|
motions = []
|
||||||
|
for motion in query.all():
|
||||||
|
if not motion.enough_supporters:
|
||||||
|
motions.append(motion)
|
||||||
|
else:
|
||||||
|
motions = query
|
||||||
|
|
||||||
|
if type(motions) is not list:
|
||||||
|
motions = list(query.all())
|
||||||
|
|
||||||
|
# not the most efficient way to do this but 'get_allowed_actions'
|
||||||
|
# is not callable from within djangos templates..
|
||||||
|
for (i, motion) in enumerate(motions):
|
||||||
|
try:
|
||||||
|
motions[i] = {
|
||||||
|
'actions': motion.get_allowed_actions(request.user),
|
||||||
|
'motion': motion
|
||||||
|
}
|
||||||
|
except:
|
||||||
|
# todo: except what?
|
||||||
|
motions[i] = {
|
||||||
|
'actions': [],
|
||||||
|
'motion': motion
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'motions': motions,
|
||||||
|
'min_supporters': int(config['motion_min_supporters']),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@permission_required('motion.can_see_motion')
|
||||||
|
@template('motion/view.html')
|
||||||
|
def view(request, motion_id, newest=False):
|
||||||
|
"""
|
||||||
|
View one motion.
|
||||||
|
"""
|
||||||
|
motion = Motion.objects.get(pk=motion_id)
|
||||||
|
if newest:
|
||||||
|
version = motion.last_version
|
||||||
|
else:
|
||||||
|
version = motion.public_version
|
||||||
|
revisions = motion.versions
|
||||||
|
actions = motion.get_allowed_actions(user=request.user)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'motion': motion,
|
||||||
|
'revisions': revisions,
|
||||||
|
'actions': actions,
|
||||||
|
'min_supporters': int(config['motion_min_supporters']),
|
||||||
|
'version': version,
|
||||||
|
#'results': motion.results
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@template('motion/edit.html')
|
||||||
|
def edit(request, motion_id=None):
|
||||||
|
"""
|
||||||
|
View a form to edit or create a motion.
|
||||||
|
"""
|
||||||
|
if request.user.has_perm('motion.can_manage_motion'):
|
||||||
|
is_manager = True
|
||||||
|
else:
|
||||||
|
is_manager = False
|
||||||
|
|
||||||
|
if not is_manager \
|
||||||
|
and not request.user.has_perm('motion.can_create_motion'):
|
||||||
|
messages.error(request, _("You have not the necessary rights to create or edit motions."))
|
||||||
|
return redirect(reverse('motion_overview'))
|
||||||
|
if motion_id is not None:
|
||||||
|
motion = Motion.objects.get(id=motion_id)
|
||||||
|
if not 'edit' in motion.get_allowed_actions(request.user):
|
||||||
|
messages.error(request, _("You can not edit this motion."))
|
||||||
|
return redirect(reverse('motion_view', args=[motion.id]))
|
||||||
|
actions = motion.get_allowed_actions(user=request.user)
|
||||||
|
else:
|
||||||
|
motion = None
|
||||||
|
actions = None
|
||||||
|
|
||||||
|
formclass = MotionFormTrivialChanges \
|
||||||
|
if config['motion_allow_trivial_change'] and motion_id \
|
||||||
|
else MotionForm
|
||||||
|
|
||||||
|
managerformclass = MotionManagerFormSupporter \
|
||||||
|
if config['motion_min_supporters'] \
|
||||||
|
else MotionManagerForm
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
dataform = formclass(request.POST, prefix="data")
|
||||||
|
valid = dataform.is_valid()
|
||||||
|
|
||||||
|
if is_manager:
|
||||||
|
managerform = managerformclass(request.POST,
|
||||||
|
instance=motion,
|
||||||
|
prefix="manager")
|
||||||
|
valid = valid and managerform.is_valid()
|
||||||
|
else:
|
||||||
|
managerform = None
|
||||||
|
|
||||||
|
if valid:
|
||||||
|
if is_manager:
|
||||||
|
motion = managerform.save(commit=False)
|
||||||
|
elif motion_id is None:
|
||||||
|
motion = Motion(submitter=request.user)
|
||||||
|
motion.title = dataform.cleaned_data['title']
|
||||||
|
motion.text = dataform.cleaned_data['text']
|
||||||
|
motion.reason = dataform.cleaned_data['reason']
|
||||||
|
|
||||||
|
try:
|
||||||
|
trivial_change = config['motion_allow_trivial_change'] \
|
||||||
|
and dataform.cleaned_data['trivial_change']
|
||||||
|
except KeyError:
|
||||||
|
trivial_change = False
|
||||||
|
motion.save(request.user, trivial_change=trivial_change)
|
||||||
|
if is_manager:
|
||||||
|
try:
|
||||||
|
new_supporters = set(managerform.cleaned_data['supporter'])
|
||||||
|
except KeyError:
|
||||||
|
# The managerform has no field for the supporters
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
old_supporters = set(motion.supporters)
|
||||||
|
# add new supporters
|
||||||
|
for supporter in new_supporters.difference(old_supporters):
|
||||||
|
motion.support(supporter)
|
||||||
|
# remove old supporters
|
||||||
|
for supporter in old_supporters.difference(new_supporters):
|
||||||
|
motion.unsupport(supporter)
|
||||||
|
|
||||||
|
if motion_id is None:
|
||||||
|
messages.success(request, _('New motion was successfully created.'))
|
||||||
|
else:
|
||||||
|
messages.success(request, _('Motion was successfully modified.'))
|
||||||
|
|
||||||
|
if not 'apply' in request.POST:
|
||||||
|
return redirect(reverse('motion_view', args=[motion.id]))
|
||||||
|
if motion_id is None:
|
||||||
|
return redirect(reverse('motion_edit', args=[motion.id]))
|
||||||
|
else:
|
||||||
|
messages.error(request, _('Please check the form for errors.'))
|
||||||
|
else:
|
||||||
|
if motion_id is None:
|
||||||
|
initial = {'text': config['motion_preamble']}
|
||||||
|
else:
|
||||||
|
if motion.status == "pub" and motion.supporters:
|
||||||
|
if request.user.has_perm('motion.can_manage_motion'):
|
||||||
|
messages.warning(request, _("Attention: Do you really want to edit this motion? The supporters will <b>not</b> be removed automatically because you can manage motions. Please check if the supports are valid after your changing!"))
|
||||||
|
else:
|
||||||
|
messages.warning(request, _("Attention: Do you really want to edit this motion? All <b>%s</b> supporters will be removed! Try to convince the supporters again.") % motion.count_supporters() )
|
||||||
|
initial = {'title': motion.title,
|
||||||
|
'text': motion.text,
|
||||||
|
'reason': motion.reason}
|
||||||
|
|
||||||
|
dataform = formclass(initial=initial, prefix="data")
|
||||||
|
if is_manager:
|
||||||
|
if motion_id is None:
|
||||||
|
initial = {'submitter': request.user.person_id}
|
||||||
|
else:
|
||||||
|
initial = {'submitter': motion.submitter.person_id,
|
||||||
|
'supporter': [supporter.person_id for supporter in motion.supporters]}
|
||||||
|
managerform = managerformclass(initial=initial,
|
||||||
|
instance=motion, prefix="manager")
|
||||||
|
else:
|
||||||
|
managerform = None
|
||||||
|
return {
|
||||||
|
'form': dataform,
|
||||||
|
'managerform': managerform,
|
||||||
|
'motion': motion,
|
||||||
|
'actions': actions,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@permission_required('motion.can_manage_motion')
|
||||||
|
@template('motion/view.html')
|
||||||
|
def set_number(request, motion_id):
|
||||||
|
"""
|
||||||
|
set a number for an motion.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
Motion.objects.get(pk=motion_id).set_number(user=request.user)
|
||||||
|
messages.success(request, _("Motion number was successfully set."))
|
||||||
|
except Motion.DoesNotExist:
|
||||||
|
pass
|
||||||
|
except NameError:
|
||||||
|
pass
|
||||||
|
return redirect(reverse('motion_view', args=[motion_id]))
|
||||||
|
|
||||||
|
|
||||||
|
@permission_required('motion.can_manage_motion')
|
||||||
|
@template('motion/view.html')
|
||||||
|
def permit(request, motion_id):
|
||||||
|
"""
|
||||||
|
permit an motion.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
Motion.objects.get(pk=motion_id).permit(user=request.user)
|
||||||
|
messages.success(request, _("Motion was successfully authorized."))
|
||||||
|
except Motion.DoesNotExist:
|
||||||
|
pass
|
||||||
|
except NameError, e:
|
||||||
|
messages.error(request, e)
|
||||||
|
return redirect(reverse('motion_view', args=[motion_id]))
|
||||||
|
|
||||||
|
@permission_required('motion.can_manage_motion')
|
||||||
|
@template('motion/view.html')
|
||||||
|
def notpermit(request, motion_id):
|
||||||
|
"""
|
||||||
|
reject (not permit) an motion.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
Motion.objects.get(pk=motion_id).notpermit(user=request.user)
|
||||||
|
messages.success(request, _("Motion was successfully rejected."))
|
||||||
|
except Motion.DoesNotExist:
|
||||||
|
pass
|
||||||
|
except NameError, e:
|
||||||
|
messages.error(request, e)
|
||||||
|
return redirect(reverse('motion_view', args=[motion_id]))
|
||||||
|
|
||||||
|
@template('motion/view.html')
|
||||||
|
def set_status(request, motion_id=None, status=None):
|
||||||
|
"""
|
||||||
|
set a status of an motion.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if status is not None:
|
||||||
|
motion = Motion.objects.get(pk=motion_id)
|
||||||
|
motion.set_status(user=request.user, status=status)
|
||||||
|
messages.success(request, _("Motion status was set to: <b>%s</b>.") % motion.get_status_display())
|
||||||
|
except Motion.DoesNotExist:
|
||||||
|
pass
|
||||||
|
except NameError, e:
|
||||||
|
messages.error(request, e)
|
||||||
|
return redirect(reverse('motion_view', args=[motion_id]))
|
||||||
|
|
||||||
|
|
||||||
|
@permission_required('motion.can_manage_motion')
|
||||||
|
@template('motion/view.html')
|
||||||
|
def reset(request, motion_id):
|
||||||
|
"""
|
||||||
|
reset an motion.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
Motion.objects.get(pk=motion_id).reset(user=request.user)
|
||||||
|
messages.success(request, _("Motion status was reset.") )
|
||||||
|
except Motion.DoesNotExist:
|
||||||
|
pass
|
||||||
|
return redirect(reverse('motion_view', args=[motion_id]))
|
||||||
|
|
||||||
|
|
||||||
|
class SupportView(SingleObjectMixin, QuestionMixin, RedirectView):
|
||||||
|
"""
|
||||||
|
Classed based view to support or unsupport a motion. Use
|
||||||
|
support=True or support=False in urls.py
|
||||||
|
"""
|
||||||
|
permission_required = 'motion.can_support_motion'
|
||||||
|
model = Motion
|
||||||
|
pk_url_kwarg = 'motion_id'
|
||||||
|
support = True
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
self.object = self.get_object()
|
||||||
|
return super(SupportView, self).get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def check_allowed_actions(self, request):
|
||||||
|
"""
|
||||||
|
Checks whether request.user can support or unsupport the motion.
|
||||||
|
Returns True or False.
|
||||||
|
"""
|
||||||
|
allowed_actions = self.object.get_allowed_actions(request.user)
|
||||||
|
if self.support and not 'support' in allowed_actions:
|
||||||
|
messages.error(request, _('You can not support this motion.'))
|
||||||
|
return False
|
||||||
|
elif not self.support and not 'unsupport' in allowed_actions:
|
||||||
|
messages.error(request, _('You can not unsupport this motion.'))
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def pre_redirect(self, request, *args, **kwargs):
|
||||||
|
if self.check_allowed_actions(request):
|
||||||
|
super(SupportView, self).pre_redirect(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get_question(self):
|
||||||
|
if self.support:
|
||||||
|
return _('Do you really want to support this motion?')
|
||||||
|
else:
|
||||||
|
return _('Do you really want to unsupport this motion?')
|
||||||
|
|
||||||
|
def case_yes(self):
|
||||||
|
if self.check_allowed_actions(self.request):
|
||||||
|
if self.support:
|
||||||
|
self.object.support(person=self.request.user)
|
||||||
|
else:
|
||||||
|
self.object.unsupport(person=self.request.user)
|
||||||
|
|
||||||
|
def get_success_message(self):
|
||||||
|
if self.support:
|
||||||
|
return _("You have supported this motion successfully.")
|
||||||
|
else:
|
||||||
|
return _("You have unsupported this motion successfully.")
|
||||||
|
|
||||||
|
def get_redirect_url(self, **kwargs):
|
||||||
|
return reverse('motion_view', args=[kwargs[self.pk_url_kwarg]])
|
||||||
|
|
||||||
|
|
||||||
|
@permission_required('motion.can_manage_motion')
|
||||||
|
@template('motion/view.html')
|
||||||
|
def gen_poll(request, motion_id):
|
||||||
|
"""
|
||||||
|
gen a poll for this motion.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
poll = Motion.objects.get(pk=motion_id).gen_poll(user=request.user)
|
||||||
|
messages.success(request, _("New vote was successfully created.") )
|
||||||
|
except Motion.DoesNotExist:
|
||||||
|
pass # TODO: do not call poll after this excaption
|
||||||
|
return redirect(reverse('motion_poll_view', args=[poll.id]))
|
||||||
|
|
||||||
|
|
||||||
|
@permission_required('motion.can_manage_motion')
|
||||||
|
def delete_poll(request, poll_id):
|
||||||
|
"""
|
||||||
|
delete a poll from this motion
|
||||||
|
"""
|
||||||
|
poll = MotionPoll.objects.get(pk=poll_id)
|
||||||
|
motion = poll.motion
|
||||||
|
count = motion.polls.filter(id__lte=poll_id).count()
|
||||||
|
if request.method == 'POST':
|
||||||
|
poll.delete()
|
||||||
|
motion.writelog(_("Poll deleted"), request.user)
|
||||||
|
messages.success(request, _('Poll was successfully deleted.'))
|
||||||
|
else:
|
||||||
|
del_confirm_form(request, poll, name=_("the %s. poll") % count, delete_link=reverse('motion_poll_delete', args=[poll_id]))
|
||||||
|
return redirect(reverse('motion_view', args=[motion.id]))
|
||||||
|
|
||||||
|
|
||||||
|
class MotionDelete(DeleteView):
|
||||||
|
"""
|
||||||
|
Delete one or more Motions.
|
||||||
|
"""
|
||||||
|
model = Motion
|
||||||
|
url = 'motion_overview'
|
||||||
|
|
||||||
|
def has_permission(self, request, *args, **kwargs):
|
||||||
|
self.kwargs = kwargs
|
||||||
|
return self.get_object().get_allowed_actions(request.user)
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
self.motions = []
|
||||||
|
|
||||||
|
if self.kwargs.get('motion_id', None):
|
||||||
|
try:
|
||||||
|
return Motion.objects.get(id=int(self.kwargs['motion_id']))
|
||||||
|
except Motion.DoesNotExist:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if self.kwargs.get('motion_ids', []):
|
||||||
|
for appid in self.kwargs['motion_ids']:
|
||||||
|
try:
|
||||||
|
self.motions.append(Motion.objects.get(id=int(appid)))
|
||||||
|
except Motion.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if self.motions:
|
||||||
|
return self.motions[0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def pre_post_redirect(self, request, *args, **kwargs):
|
||||||
|
self.object = self.get_object()
|
||||||
|
|
||||||
|
if len(self.motions):
|
||||||
|
for motion in self.motions:
|
||||||
|
if not 'delete' in motion.get_allowed_actions(user=request.user):
|
||||||
|
messages.error(request, _("You can not delete motion <b>%s</b>.") % motion)
|
||||||
|
continue
|
||||||
|
|
||||||
|
title = motion.title
|
||||||
|
motion.delete(force=True)
|
||||||
|
messages.success(request, _("Motion <b>%s</b> was successfully deleted.") % title)
|
||||||
|
|
||||||
|
elif self.object:
|
||||||
|
if not 'delete' in self.object.get_allowed_actions(user=request.user):
|
||||||
|
messages.error(request, _("You can not delete motion <b>%s</b>.") % self.object)
|
||||||
|
elif self.get_answer() == 'yes':
|
||||||
|
title = self.object.title
|
||||||
|
self.object.delete(force=True)
|
||||||
|
messages.success(request, _("Motion <b>%s</b> was successfully deleted.") % title)
|
||||||
|
else:
|
||||||
|
messages.error(request, _("Invalid request"))
|
||||||
|
|
||||||
|
|
||||||
|
class ViewPoll(PollFormView):
|
||||||
|
permission_required = 'motion.can_manage_motion'
|
||||||
|
poll_class = MotionPoll
|
||||||
|
template_name = 'motion/poll_view.html'
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(ViewPoll, self).get_context_data(**kwargs)
|
||||||
|
self.motion = self.poll.get_motion()
|
||||||
|
context['motion'] = self.motion
|
||||||
|
context['ballot'] = self.poll.get_ballot()
|
||||||
|
context['actions'] = self.motion.get_allowed_actions(user=self.request.user)
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_modelform_class(self):
|
||||||
|
cls = super(ViewPoll, self).get_modelform_class()
|
||||||
|
user = self.request.user
|
||||||
|
|
||||||
|
class ViewPollFormClass(cls):
|
||||||
|
def save(self, commit = True):
|
||||||
|
instance = super(ViewPollFormClass, self).save(commit)
|
||||||
|
motion = instance.motion
|
||||||
|
motion.writelog(_("Poll was updated"), user)
|
||||||
|
return instance
|
||||||
|
|
||||||
|
return ViewPollFormClass
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
if not 'apply' in self.request.POST:
|
||||||
|
return reverse('motion_view', args=[self.poll.motion.id])
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
@permission_required('motion.can_manage_motion')
|
||||||
|
def permit_version(request, aversion_id):
|
||||||
|
aversion = AVersion.objects.get(pk=aversion_id)
|
||||||
|
motion = aversion.motion
|
||||||
|
if request.method == 'POST':
|
||||||
|
motion.accept_version(aversion, user=request.user)
|
||||||
|
messages.success(request, _("Version <b>%s</b> accepted.") % (aversion.aid))
|
||||||
|
else:
|
||||||
|
gen_confirm_form(request, _('Do you really want to authorize version <b>%s</b>?') % aversion.aid, reverse('motion_version_permit', args=[aversion.id]))
|
||||||
|
return redirect(reverse('motion_view', args=[motion.id]))
|
||||||
|
|
||||||
|
|
||||||
|
@permission_required('motion.can_manage_motion')
|
||||||
|
def reject_version(request, aversion_id):
|
||||||
|
aversion = AVersion.objects.get(pk=aversion_id)
|
||||||
|
motion = aversion.motion
|
||||||
|
if request.method == 'POST':
|
||||||
|
if motion.reject_version(aversion, user=request.user):
|
||||||
|
messages.success(request, _("Version <b>%s</b> rejected.") % (aversion.aid))
|
||||||
|
else:
|
||||||
|
messages.error(request, _("ERROR by rejecting the version.") )
|
||||||
|
else:
|
||||||
|
gen_confirm_form(request, _('Do you really want to reject version <b>%s</b>?') % aversion.aid, reverse('motion_version_reject', args=[aversion.id]))
|
||||||
|
return redirect(reverse('motion_view', args=[motion.id]))
|
||||||
|
|
||||||
|
|
||||||
|
@permission_required('motion.can_manage_motion')
|
||||||
|
@template('motion/import.html')
|
||||||
|
def motion_import(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = MotionImportForm(request.POST, request.FILES)
|
||||||
|
if form.is_valid():
|
||||||
|
import_permitted = form.cleaned_data['import_permitted']
|
||||||
|
try:
|
||||||
|
# check for valid encoding (will raise UnicodeDecodeError if not)
|
||||||
|
request.FILES['csvfile'].read().decode('utf-8')
|
||||||
|
request.FILES['csvfile'].seek(0)
|
||||||
|
|
||||||
|
users_generated = 0
|
||||||
|
motions_generated = 0
|
||||||
|
motions_modified = 0
|
||||||
|
groups_assigned = 0
|
||||||
|
groups_generated = 0
|
||||||
|
with transaction.commit_on_success():
|
||||||
|
dialect = csv.Sniffer().sniff(request.FILES['csvfile'].readline())
|
||||||
|
dialect = csv_ext.patchup(dialect)
|
||||||
|
request.FILES['csvfile'].seek(0)
|
||||||
|
for (lno, line) in enumerate(csv.reader(request.FILES['csvfile'], dialect=dialect)):
|
||||||
|
# basic input verification
|
||||||
|
if lno < 1:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
(number, title, text, reason, first_name, last_name, is_group) = line[:7]
|
||||||
|
if is_group.strip().lower() in ['y', 'j', 't', 'yes', 'ja', 'true', '1', 1]:
|
||||||
|
is_group = True
|
||||||
|
else:
|
||||||
|
is_group = False
|
||||||
|
except ValueError:
|
||||||
|
messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1))
|
||||||
|
continue
|
||||||
|
form = MotionForm({'title': title, 'text': text, 'reason': reason})
|
||||||
|
if not form.is_valid():
|
||||||
|
messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1))
|
||||||
|
continue
|
||||||
|
if number:
|
||||||
|
try:
|
||||||
|
number = abs(long(number))
|
||||||
|
if number < 1:
|
||||||
|
messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1))
|
||||||
|
continue
|
||||||
|
except ValueError:
|
||||||
|
messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if is_group:
|
||||||
|
# fetch existing groups or issue an error message
|
||||||
|
try:
|
||||||
|
user = Group.objects.get(name=last_name)
|
||||||
|
if user.group_as_person == False:
|
||||||
|
messages.error(request, _('Ignoring line %d because the assigned group may not act as a person.') % (lno + 1))
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
user = get_person(user.person_id)
|
||||||
|
|
||||||
|
groups_assigned += 1
|
||||||
|
except Group.DoesNotExist:
|
||||||
|
group = Group()
|
||||||
|
group.group_as_person = True
|
||||||
|
group.description = _('Created by motion import.')
|
||||||
|
group.name = last_name
|
||||||
|
group.save()
|
||||||
|
groups_generated += 1
|
||||||
|
|
||||||
|
user = get_person(group.person_id)
|
||||||
|
else:
|
||||||
|
# fetch existing users or create new users as needed
|
||||||
|
try:
|
||||||
|
user = User.objects.get(first_name=first_name, last_name=last_name)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
user = None
|
||||||
|
if user is None:
|
||||||
|
if not first_name or not last_name:
|
||||||
|
messages.error(request, _('Ignoring line %d because it contains an incomplete first / last name pair.') % (lno + 1))
|
||||||
|
continue
|
||||||
|
|
||||||
|
user = User()
|
||||||
|
user.last_name = last_name
|
||||||
|
user.first_name = first_name
|
||||||
|
user.username = gen_username(first_name, last_name)
|
||||||
|
user.structure_level = ''
|
||||||
|
user.committee = ''
|
||||||
|
user.gender = ''
|
||||||
|
user.type = ''
|
||||||
|
user.default_password = gen_password()
|
||||||
|
user.save()
|
||||||
|
user.reset_password()
|
||||||
|
users_generated += 1
|
||||||
|
# create / modify the motion
|
||||||
|
motion = None
|
||||||
|
if number:
|
||||||
|
try:
|
||||||
|
motion = Motion.objects.get(number=number)
|
||||||
|
motions_modified += 1
|
||||||
|
except Motion.DoesNotExist:
|
||||||
|
motion = None
|
||||||
|
if motion is None:
|
||||||
|
motion = Motion(submitter=user)
|
||||||
|
if number:
|
||||||
|
motion.number = number
|
||||||
|
motions_generated += 1
|
||||||
|
|
||||||
|
motion.title = form.cleaned_data['title']
|
||||||
|
motion.text = form.cleaned_data['text']
|
||||||
|
motion.reason = form.cleaned_data['reason']
|
||||||
|
if import_permitted:
|
||||||
|
motion.status = 'per'
|
||||||
|
|
||||||
|
motion.save(user, trivial_change=True)
|
||||||
|
|
||||||
|
if motions_generated:
|
||||||
|
messages.success(request, ungettext('%d motion was successfully imported.',
|
||||||
|
'%d motions were successfully imported.', motions_generated) % motions_generated)
|
||||||
|
if motions_modified:
|
||||||
|
messages.success(request, ungettext('%d motion was successfully modified.',
|
||||||
|
'%d motions were successfully modified.', motions_modified) % motions_modified)
|
||||||
|
if users_generated:
|
||||||
|
messages.success(request, ungettext('%d new user was added.', '%d new users were added.', users_generated) % users_generated)
|
||||||
|
|
||||||
|
if groups_generated:
|
||||||
|
messages.success(request, ungettext('%d new group was added.', '%d new groups were added.', groups_generated) % groups_generated)
|
||||||
|
|
||||||
|
if groups_assigned:
|
||||||
|
messages.success(request, ungettext('%d group assigned to motions.', '%d groups assigned to motions.', groups_assigned) % groups_assigned)
|
||||||
|
return redirect(reverse('motion_overview'))
|
||||||
|
|
||||||
|
except csv.Error:
|
||||||
|
messages.error(request, _('Import aborted because of severe errors in the input file.'))
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
messages.error(request, _('Import file has wrong character encoding, only UTF-8 is supported!'))
|
||||||
|
else:
|
||||||
|
messages.error(request, _('Please check the form for errors.'))
|
||||||
|
else:
|
||||||
|
messages.warning(request, _("Attention: Existing motions will be modified if you import new motions with the same number."))
|
||||||
|
messages.warning(request, _("Attention: Importing an motions without a number multiple times will create duplicates."))
|
||||||
|
form = MotionImportForm()
|
||||||
|
return {
|
||||||
|
'form': form,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CreateAgendaItem(RedirectView):
|
||||||
|
permission_required = 'agenda.can_manage_agenda'
|
||||||
|
|
||||||
|
def pre_redirect(self, request, *args, **kwargs):
|
||||||
|
self.motion = Motion.objects.get(pk=kwargs['motion_id'])
|
||||||
|
self.item = Item(related_sid=self.motion.sid)
|
||||||
|
self.item.save()
|
||||||
|
|
||||||
|
def get_redirect_url(self, **kwargs):
|
||||||
|
return reverse('item_overview')
|
||||||
|
|
||||||
|
|
||||||
|
class MotionPDF(PDFView):
|
||||||
|
permission_required = 'motion.can_see_motion'
|
||||||
|
top_space = 0
|
||||||
|
|
||||||
|
def get_filename(self):
|
||||||
|
motion_id = self.kwargs['motion_id']
|
||||||
|
if motion_id is None:
|
||||||
|
filename = _("Motions")
|
||||||
|
else:
|
||||||
|
motion = Motion.objects.get(id=motion_id)
|
||||||
|
if motion.number:
|
||||||
|
number = motion.number
|
||||||
|
else:
|
||||||
|
number = ""
|
||||||
|
filename = u'%s%s' % (_("Motion"), str(number))
|
||||||
|
return filename
|
||||||
|
|
||||||
|
def append_to_pdf(self, story):
|
||||||
|
motion_id = self.kwargs['motion_id']
|
||||||
|
if motion_id is None: #print all motions
|
||||||
|
title = config["motion_pdf_title"]
|
||||||
|
story.append(Paragraph(title, stylesheet['Heading1']))
|
||||||
|
preamble = config["motion_pdf_preamble"]
|
||||||
|
if preamble:
|
||||||
|
story.append(Paragraph("%s" % preamble.replace('\r\n','<br/>'), stylesheet['Paragraph']))
|
||||||
|
story.append(Spacer(0,0.75*cm))
|
||||||
|
motions = Motion.objects.all()
|
||||||
|
if not motions: # No motions existing
|
||||||
|
story.append(Paragraph(_("No motions available."), stylesheet['Heading3']))
|
||||||
|
else: # Print all Motions
|
||||||
|
# List of motions
|
||||||
|
for motion in motions:
|
||||||
|
if motion.number:
|
||||||
|
story.append(Paragraph(_("Motion No.")+" %s: %s" % (motion.number, motion.title), stylesheet['Heading3']))
|
||||||
|
else:
|
||||||
|
story.append(Paragraph(_("Motion No.")+" : %s" % (motion.title), stylesheet['Heading3']))
|
||||||
|
# Motions details (each motion on single page)
|
||||||
|
for motion in motions:
|
||||||
|
story.append(PageBreak())
|
||||||
|
story = self.get_motion(motion, story)
|
||||||
|
else: # print selected motion
|
||||||
|
motion = Motion.objects.get(id=motion_id)
|
||||||
|
story = self.get_motion(motion, story)
|
||||||
|
|
||||||
|
def get_motion(self, motion, story):
|
||||||
|
# Preparing Table
|
||||||
|
data = []
|
||||||
|
|
||||||
|
# motion number
|
||||||
|
if motion.number:
|
||||||
|
story.append(Paragraph(_("Motion No.")+" %s" % motion.number, stylesheet['Heading1']))
|
||||||
|
else:
|
||||||
|
story.append(Paragraph(_("Motion No."), stylesheet['Heading1']))
|
||||||
|
|
||||||
|
# submitter
|
||||||
|
cell1a = []
|
||||||
|
cell1a.append(Spacer(0, 0.2 * cm))
|
||||||
|
cell1a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Submitter"), stylesheet['Heading4']))
|
||||||
|
cell1b = []
|
||||||
|
cell1b.append(Spacer(0, 0.2 * cm))
|
||||||
|
cell1b.append(Paragraph("%s" % motion.submitter, stylesheet['Normal']))
|
||||||
|
data.append([cell1a, cell1b])
|
||||||
|
|
||||||
|
if motion.status == "pub":
|
||||||
|
# Cell for the signature
|
||||||
|
cell2a = []
|
||||||
|
cell2b = []
|
||||||
|
cell2a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Signature"), stylesheet['Heading4']))
|
||||||
|
cell2b.append(Paragraph("__________________________________________", stylesheet['Signaturefield']))
|
||||||
|
cell2b.append(Spacer(0, 0.1 * cm))
|
||||||
|
cell2b.append(Spacer(0,0.2*cm))
|
||||||
|
data.append([cell2a, cell2b])
|
||||||
|
|
||||||
|
# supporters
|
||||||
|
if config['motion_min_supporters']:
|
||||||
|
cell3a = []
|
||||||
|
cell3b = []
|
||||||
|
cell3a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font><seqreset id='counter'>" % _("Supporters"), stylesheet['Heading4']))
|
||||||
|
for supporter in motion.supporters:
|
||||||
|
cell3b.append(Paragraph("<seq id='counter'/>. %s" % supporter, stylesheet['Signaturefield']))
|
||||||
|
if motion.status == "pub":
|
||||||
|
for x in range(motion.missing_supporters):
|
||||||
|
cell3b.append(Paragraph("<seq id='counter'/>. __________________________________________",stylesheet['Signaturefield']))
|
||||||
|
cell3b.append(Spacer(0, 0.2 * cm))
|
||||||
|
data.append([cell3a, cell3b])
|
||||||
|
|
||||||
|
# status
|
||||||
|
cell4a = []
|
||||||
|
cell4b = []
|
||||||
|
note = " ".join(motion.notes)
|
||||||
|
cell4a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Status"), stylesheet['Heading4']))
|
||||||
|
if note != "":
|
||||||
|
if motion.status == "pub":
|
||||||
|
cell4b.append(Paragraph(note, stylesheet['Normal']))
|
||||||
|
else:
|
||||||
|
cell4b.append(Paragraph("%s | %s" % (motion.get_status_display(), note), stylesheet['Normal']))
|
||||||
|
else:
|
||||||
|
cell4b.append(Paragraph("%s" % motion.get_status_display(), stylesheet['Normal']))
|
||||||
|
data.append([cell4a, cell4b])
|
||||||
|
|
||||||
|
# Version number (aid)
|
||||||
|
if motion.public_version.aid > 1:
|
||||||
|
cell5a = []
|
||||||
|
cell5b = []
|
||||||
|
cell5a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Version"), stylesheet['Heading4']))
|
||||||
|
cell5b.append(Paragraph("%s" % motion.public_version.aid, stylesheet['Normal']))
|
||||||
|
data.append([cell5a, cell5b])
|
||||||
|
|
||||||
|
# voting results
|
||||||
|
poll_results = motion.get_poll_results()
|
||||||
|
if poll_results:
|
||||||
|
cell6a = []
|
||||||
|
cell6a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Vote results"), stylesheet['Heading4']))
|
||||||
|
cell6b = []
|
||||||
|
ballotcounter = 0
|
||||||
|
for result in poll_results:
|
||||||
|
ballotcounter += 1
|
||||||
|
if len(poll_results) > 1:
|
||||||
|
cell6b.append(Paragraph("%s. %s" % (ballotcounter, _("Vote")), stylesheet['Bold']))
|
||||||
|
cell6b.append(Paragraph("%s: %s <br/> %s: %s <br/> %s: %s <br/> %s: %s <br/> %s: %s" % (_("Yes"), result[0], _("No"), result[1], _("Abstention"), result[2], _("Invalid"), result[3], _("Votes cast"), result[4]), stylesheet['Normal']))
|
||||||
|
cell6b.append(Spacer(0, 0.2*cm))
|
||||||
|
data.append([cell6a, cell6b])
|
||||||
|
|
||||||
|
# Creating Table
|
||||||
|
t = Table(data)
|
||||||
|
t._argW[0] = 4.5 * cm
|
||||||
|
t._argW[1] = 11 * cm
|
||||||
|
t.setStyle(TableStyle([('BOX', (0, 0), (-1, -1), 1, colors.black),
|
||||||
|
('VALIGN', (0,0), (-1,-1), 'TOP')]))
|
||||||
|
story.append(t)
|
||||||
|
story.append(Spacer(0, 1 * cm))
|
||||||
|
|
||||||
|
# title
|
||||||
|
story.append(Paragraph(motion.public_version.title, stylesheet['Heading3']))
|
||||||
|
# text
|
||||||
|
story.append(Paragraph("%s" % motion.public_version.text.replace('\r\n','<br/>'), stylesheet['Paragraph']))
|
||||||
|
# reason
|
||||||
|
if motion.public_version.reason:
|
||||||
|
story.append(Paragraph(_("Reason")+":", stylesheet['Heading3']))
|
||||||
|
story.append(Paragraph("%s" % motion.public_version.reason.replace('\r\n','<br/>'), stylesheet['Paragraph']))
|
||||||
|
return story
|
||||||
|
|
||||||
|
|
||||||
|
class MotionPollPDF(PDFView):
|
||||||
|
permission_required = 'motion.can_manage_motion'
|
||||||
|
top_space = 0
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
self.poll = MotionPoll.objects.get(id=self.kwargs['poll_id'])
|
||||||
|
return super(MotionPollPDF, self).get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get_filename(self):
|
||||||
|
filename = u'%s%s_%s' % (_("Motion"), str(self.poll.motion.number), _("Poll"))
|
||||||
|
return filename
|
||||||
|
|
||||||
|
def get_template(self, buffer):
|
||||||
|
return SimpleDocTemplate(buffer, topMargin=-6, bottomMargin=-6, leftMargin=0, rightMargin=0, showBoundary=False)
|
||||||
|
|
||||||
|
def build_document(self, pdf_document, story):
|
||||||
|
pdf_document.build(story)
|
||||||
|
|
||||||
|
def append_to_pdf(self, story):
|
||||||
|
imgpath = os.path.join(settings.SITE_ROOT, 'static/images/circle.png')
|
||||||
|
circle = "<img src='%s' width='15' height='15'/> " % imgpath
|
||||||
|
cell = []
|
||||||
|
cell.append(Spacer(0,0.8*cm))
|
||||||
|
cell.append(Paragraph(_("Motion No. %s") % self.poll.motion.number, stylesheet['Ballot_title']))
|
||||||
|
cell.append(Paragraph(self.poll.motion.title, stylesheet['Ballot_subtitle']))
|
||||||
|
cell.append(Paragraph(_("%d. Vote") % self.poll.get_ballot(), stylesheet['Ballot_description']))
|
||||||
|
cell.append(Spacer(0,0.5*cm))
|
||||||
|
cell.append(Paragraph(circle + unicode(_("Yes")), stylesheet['Ballot_option']))
|
||||||
|
cell.append(Paragraph(circle + unicode(_("No")), stylesheet['Ballot_option']))
|
||||||
|
cell.append(Paragraph(circle + unicode(_("Abstention")), stylesheet['Ballot_option']))
|
||||||
|
data= []
|
||||||
|
# get ballot papers config values
|
||||||
|
ballot_papers_selection = config["motion_pdf_ballot_papers_selection"]
|
||||||
|
ballot_papers_number = config["motion_pdf_ballot_papers_number"]
|
||||||
|
|
||||||
|
# set number of ballot papers
|
||||||
|
if ballot_papers_selection == "NUMBER_OF_DELEGATES":
|
||||||
|
number = User.objects.filter(type__iexact="delegate").count()
|
||||||
|
elif ballot_papers_selection == "NUMBER_OF_ALL_PARTICIPANTS":
|
||||||
|
number = int(User.objects.count())
|
||||||
|
else: # ballot_papers_selection == "CUSTOM_NUMBER"
|
||||||
|
number = int(ballot_papers_number)
|
||||||
|
number = max(1, number)
|
||||||
|
|
||||||
|
# print ballot papers
|
||||||
|
if number > 0:
|
||||||
|
for user in xrange(number / 2):
|
||||||
|
data.append([cell, cell])
|
||||||
|
rest = number % 2
|
||||||
|
if rest:
|
||||||
|
data.append([cell, ''])
|
||||||
|
t=Table(data, 10.5 * cm, 7.42 * cm)
|
||||||
|
t.setStyle(TableStyle([('GRID', (0, 0), (-1, -1), 0.25, colors.grey),
|
||||||
|
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||||
|
]))
|
||||||
|
story.append(t)
|
||||||
|
|
||||||
|
|
||||||
|
class Config(FormView):
|
||||||
|
permission_required = 'config.can_manage_config'
|
||||||
|
form_class = ConfigForm
|
||||||
|
template_name = 'motion/config.html'
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
return {
|
||||||
|
'motion_min_supporters': config['motion_min_supporters'],
|
||||||
|
'motion_preamble': config['motion_preamble'],
|
||||||
|
'motion_pdf_ballot_papers_selection': config['motion_pdf_ballot_papers_selection'],
|
||||||
|
'motion_pdf_ballot_papers_number': config['motion_pdf_ballot_papers_number'],
|
||||||
|
'motion_pdf_title': config['motion_pdf_title'],
|
||||||
|
'motion_pdf_preamble': config['motion_pdf_preamble'],
|
||||||
|
'motion_allow_trivial_change': config['motion_allow_trivial_change'],
|
||||||
|
}
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
config['motion_min_supporters'] = form.cleaned_data['motion_min_supporters']
|
||||||
|
config['motion_preamble'] = form.cleaned_data['motion_preamble']
|
||||||
|
config['motion_pdf_ballot_papers_selection'] = form.cleaned_data['motion_pdf_ballot_papers_selection']
|
||||||
|
config['motion_pdf_ballot_papers_number'] = form.cleaned_data['motion_pdf_ballot_papers_number']
|
||||||
|
config['motion_pdf_title'] = form.cleaned_data['motion_pdf_title']
|
||||||
|
config['motion_pdf_preamble'] = form.cleaned_data['motion_pdf_preamble']
|
||||||
|
config['motion_allow_trivial_change'] = form.cleaned_data['motion_allow_trivial_change']
|
||||||
|
messages.success(self.request, _('Motion settings successfully saved.'))
|
||||||
|
return super(Config, self).form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
def register_tab(request):
|
||||||
|
selected = True if request.path.startswith('/motion/') else False
|
||||||
|
return Tab(
|
||||||
|
title=_('Motions'),
|
||||||
|
url=reverse('motion_overview'),
|
||||||
|
permission=request.user.has_perm('motion.can_see_motion') or request.user.has_perm('motion.can_support_motion') or request.user.has_perm('motion.can_support_motion') or request.user.has_perm('motion.can_manage_motion'),
|
||||||
|
selected=selected,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_widgets(request):
|
||||||
|
return [
|
||||||
|
Widget(
|
||||||
|
name='motions',
|
||||||
|
display_name=_('Motions'),
|
||||||
|
template='motion/widget.html',
|
||||||
|
context={'motions': Motion.objects.all()},
|
||||||
|
permission_required='projector.can_manage_projector')]
|
@ -14,26 +14,30 @@
|
|||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
|
||||||
from random import choice
|
from random import choice
|
||||||
import string
|
|
||||||
import csv
|
import csv
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import Permission
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from openslides.utils import csv_ext
|
from openslides.utils import csv_ext
|
||||||
|
|
||||||
from openslides.participant.models import User
|
from openslides.participant.models import User, Group
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_PERMS = ['can_see_agenda', 'can_see_projector',
|
||||||
|
'can_see_motion', 'can_see_assignment',
|
||||||
|
'can_see_dashboard']
|
||||||
|
|
||||||
|
|
||||||
def gen_password():
|
def gen_password():
|
||||||
"""
|
"""
|
||||||
generates a random passwort.
|
generates a random passwort.
|
||||||
"""
|
"""
|
||||||
chars = string.letters + string.digits
|
chars = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"
|
||||||
newpassword = ''
|
size = 8
|
||||||
for i in range(8):
|
|
||||||
newpassword += choice(chars)
|
return ''.join([choice(chars) for i in range(size)])
|
||||||
return newpassword
|
|
||||||
|
|
||||||
|
|
||||||
def gen_username(first_name, last_name):
|
def gen_username(first_name, last_name):
|
||||||
@ -48,7 +52,7 @@ def gen_username(first_name, last_name):
|
|||||||
i = 0
|
i = 0
|
||||||
while True:
|
while True:
|
||||||
i += 1
|
i += 1
|
||||||
testname = "%s%s%s" % (first_name, last_name, i)
|
testname = "%s %s %s" % (first_name, last_name, i)
|
||||||
try:
|
try:
|
||||||
User.objects.get(username=testname)
|
User.objects.get(username=testname)
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
@ -72,20 +76,20 @@ def import_users(csv_file):
|
|||||||
dialect=dialect)):
|
dialect=dialect)):
|
||||||
if line_no:
|
if line_no:
|
||||||
try:
|
try:
|
||||||
(first_name, last_name, gender, category, type, committee, comment) = line[:7]
|
(first_name, last_name, gender, structure_level, type, committee, comment) = line[:7]
|
||||||
except ValueError:
|
except ValueError:
|
||||||
error_messages.append(_('Ignoring malformed line %d in import file.') % line_no + 1)
|
error_messages.append(_('Ignoring malformed line %d in import file.') % (line_no + 1))
|
||||||
continue
|
continue
|
||||||
user = User()
|
user = User()
|
||||||
user.last_name = last_name
|
user.last_name = last_name
|
||||||
user.first_name = first_name
|
user.first_name = first_name
|
||||||
user.username = gen_username(first_name, last_name)
|
user.username = gen_username(first_name, last_name)
|
||||||
user.gender = gender
|
user.gender = gender
|
||||||
user.category = category
|
user.structure_level = structure_level
|
||||||
user.type = type
|
user.type = type
|
||||||
user.committee = committee
|
user.committee = committee
|
||||||
user.comment = comment
|
user.comment = comment
|
||||||
user.firstpassword = gen_password()
|
user.default_password = gen_password()
|
||||||
user.save()
|
user.save()
|
||||||
user.reset_password()
|
user.reset_password()
|
||||||
count_success += 1
|
count_success += 1
|
||||||
@ -94,3 +98,23 @@ def import_users(csv_file):
|
|||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
error_messages.appen(_('Import file has wrong character encoding, only UTF-8 is supported!'))
|
error_messages.appen(_('Import file has wrong character encoding, only UTF-8 is supported!'))
|
||||||
return (count_success, error_messages)
|
return (count_success, error_messages)
|
||||||
|
|
||||||
|
|
||||||
|
def get_or_create_registered_group():
|
||||||
|
registered, created = Group.objects.get_or_create(
|
||||||
|
name__iexact='Registered', defaults={'name': 'Registered'})
|
||||||
|
if created:
|
||||||
|
registered.permissions = Permission.objects.filter(
|
||||||
|
codename__in=DEFAULT_PERMS)
|
||||||
|
registered.save()
|
||||||
|
return registered
|
||||||
|
|
||||||
|
|
||||||
|
def get_or_create_anonymous_group():
|
||||||
|
anonymous, created = Group.objects.get_or_create(
|
||||||
|
name__iexact='Anonymous', defaults={'name': 'Anonymous'})
|
||||||
|
if created:
|
||||||
|
anonymous.permissions = Permission.objects.filter(
|
||||||
|
codename__in=DEFAULT_PERMS)
|
||||||
|
anonymous.save()
|
||||||
|
return anonymous
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"pk": 1,
|
"pk": 1,
|
||||||
"model": "auth.group",
|
"model": "auth.group",
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "Beobachter",
|
"name": "Beobachter/in",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
[
|
[
|
||||||
"can_see_agenda",
|
"can_see_agenda",
|
||||||
@ -11,14 +11,14 @@
|
|||||||
"item"
|
"item"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"can_create_application",
|
"can_create_motion",
|
||||||
"application",
|
"motion",
|
||||||
"application"
|
"motion"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"can_see_application",
|
"can_see_motion",
|
||||||
"application",
|
"motion",
|
||||||
"application"
|
"motion"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"can_nominate_other",
|
"can_nominate_other",
|
||||||
@ -44,6 +44,11 @@
|
|||||||
"can_see_projector",
|
"can_see_projector",
|
||||||
"projector",
|
"projector",
|
||||||
"projectorslide"
|
"projectorslide"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"can_see_dashboard",
|
||||||
|
"projector",
|
||||||
|
"projectorslide"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -52,7 +57,7 @@
|
|||||||
"pk": 2,
|
"pk": 2,
|
||||||
"model": "auth.group",
|
"model": "auth.group",
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "Delegierter",
|
"name": "Delegierte/r",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
[
|
[
|
||||||
"can_see_agenda",
|
"can_see_agenda",
|
||||||
@ -60,19 +65,19 @@
|
|||||||
"item"
|
"item"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"can_create_application",
|
"can_create_motion",
|
||||||
"application",
|
"motion",
|
||||||
"application"
|
"motion"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"can_see_application",
|
"can_see_motion",
|
||||||
"application",
|
"motion",
|
||||||
"application"
|
"motion"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"can_support_application",
|
"can_support_motion",
|
||||||
"application",
|
"motion",
|
||||||
"application"
|
"motion"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"can_nominate_other",
|
"can_nominate_other",
|
||||||
@ -98,6 +103,11 @@
|
|||||||
"can_see_projector",
|
"can_see_projector",
|
||||||
"projector",
|
"projector",
|
||||||
"projectorslide"
|
"projectorslide"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"can_see_dashboard",
|
||||||
|
"projector",
|
||||||
|
"projectorslide"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -119,19 +129,19 @@
|
|||||||
"item"
|
"item"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"can_create_application",
|
"can_create_motion",
|
||||||
"application",
|
"motion",
|
||||||
"application"
|
"motion"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"can_manage_application",
|
"can_manage_motion",
|
||||||
"application",
|
"motion",
|
||||||
"application"
|
"motion"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"can_see_application",
|
"can_see_motion",
|
||||||
"application",
|
"motion",
|
||||||
"application"
|
"motion"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"can_manage_assignment",
|
"can_manage_assignment",
|
||||||
@ -177,6 +187,11 @@
|
|||||||
"can_see_projector",
|
"can_see_projector",
|
||||||
"projector",
|
"projector",
|
||||||
"projectorslide"
|
"projectorslide"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"can_see_dashboard",
|
||||||
|
"projector",
|
||||||
|
"projectorslide"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -206,6 +221,11 @@
|
|||||||
"can_see_projector",
|
"can_see_projector",
|
||||||
"projector",
|
"projector",
|
||||||
"projectorslide"
|
"projectorslide"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"can_see_dashboard",
|
||||||
|
"projector",
|
||||||
|
"projectorslide"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -18,33 +18,41 @@ from openslides.utils.forms import (
|
|||||||
CssClassMixin, LocalizedModelMultipleChoiceField)
|
CssClassMixin, LocalizedModelMultipleChoiceField)
|
||||||
|
|
||||||
from openslides.participant.models import User, Group
|
from openslides.participant.models import User, Group
|
||||||
|
from openslides.participant.api import get_or_create_registered_group
|
||||||
|
|
||||||
|
|
||||||
class UserCreateForm(forms.ModelForm, CssClassMixin):
|
class UserCreateForm(forms.ModelForm, CssClassMixin):
|
||||||
groups = forms.ModelMultipleChoiceField(
|
groups = forms.ModelMultipleChoiceField(
|
||||||
queryset=Group.objects.exclude(name__iexact='anonymous'),
|
queryset=Group.objects.exclude(name__iexact='anonymous'),
|
||||||
label=_("User groups"), required=False)
|
label=_("Groups"), required=False)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
if kwargs.get('instance', None) is None:
|
||||||
|
initial = kwargs.setdefault('initial', {})
|
||||||
|
registered = get_or_create_registered_group()
|
||||||
|
initial['groups'] = [registered.pk]
|
||||||
|
super(UserCreateForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ('first_name', 'last_name', 'is_active', 'groups', 'category',
|
fields = ('first_name', 'last_name', 'is_active', 'groups', 'structure_level',
|
||||||
'gender', 'type', 'committee', 'comment', 'default_password')
|
'gender', 'type', 'committee', 'about_me', 'comment', 'default_password')
|
||||||
|
|
||||||
|
|
||||||
class UserUpdateForm(UserCreateForm):
|
class UserUpdateForm(UserCreateForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ('username', 'first_name', 'last_name', 'is_active', 'groups',
|
fields = ('username', 'first_name', 'last_name', 'is_active', 'groups',
|
||||||
'category', 'gender', 'type', 'committee', 'comment',
|
'structure_level', 'gender', 'type', 'committee', 'about_me', 'comment',
|
||||||
'default_password')
|
'default_password')
|
||||||
|
|
||||||
|
|
||||||
class GroupForm(forms.ModelForm, CssClassMixin):
|
class GroupForm(forms.ModelForm, CssClassMixin):
|
||||||
permissions = LocalizedModelMultipleChoiceField(
|
permissions = LocalizedModelMultipleChoiceField(
|
||||||
queryset=Permission.objects.all(), label=_("Persmissions"),
|
queryset=Permission.objects.all(), label=_("Permissions"),
|
||||||
required=False)
|
required=False)
|
||||||
users = forms.ModelMultipleChoiceField(
|
users = forms.ModelMultipleChoiceField(
|
||||||
queryset=User.objects.all(), label=_("Users"), required=False)
|
queryset=User.objects.all(), label=_("Participants"), required=False)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
# Initial users
|
# Initial users
|
||||||
@ -58,6 +66,7 @@ class GroupForm(forms.ModelForm, CssClassMixin):
|
|||||||
instance = forms.ModelForm.save(self, False)
|
instance = forms.ModelForm.save(self, False)
|
||||||
|
|
||||||
old_save_m2m = self.save_m2m
|
old_save_m2m = self.save_m2m
|
||||||
|
|
||||||
def save_m2m():
|
def save_m2m():
|
||||||
old_save_m2m()
|
old_save_m2m()
|
||||||
|
|
||||||
@ -76,13 +85,13 @@ class GroupForm(forms.ModelForm, CssClassMixin):
|
|||||||
# Do not allow to change the name "anonymous" or give another group
|
# Do not allow to change the name "anonymous" or give another group
|
||||||
# this name
|
# this name
|
||||||
data = self.cleaned_data['name']
|
data = self.cleaned_data['name']
|
||||||
if self.instance.name.lower() == 'anonymous':
|
if self.instance.name.lower() in ['anonymous', 'registered']:
|
||||||
# Editing the anonymous-user
|
# Editing the anonymous-user
|
||||||
if self.instance.name.lower() != data.lower():
|
if self.instance.name.lower() != data.lower():
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(
|
||||||
_('You can not edit the name for the anonymous user'))
|
_('You can not edit the name for this group.'))
|
||||||
else:
|
else:
|
||||||
if data.lower() == 'anonymous':
|
if data.lower() in ['anonymous', 'registered']:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(
|
||||||
_('Group name "%s" is reserved for internal use.') % data)
|
_('Group name "%s" is reserved for internal use.') % data)
|
||||||
return data
|
return data
|
||||||
@ -94,7 +103,7 @@ class GroupForm(forms.ModelForm, CssClassMixin):
|
|||||||
class UsersettingsForm(forms.ModelForm, CssClassMixin):
|
class UsersettingsForm(forms.ModelForm, CssClassMixin):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ('username', 'first_name', 'last_name', 'email')
|
fields = ('username', 'first_name', 'last_name', 'gender', 'email', 'committee', 'about_me')
|
||||||
|
|
||||||
|
|
||||||
class UserImportForm(forms.Form, CssClassMixin):
|
class UserImportForm(forms.Form, CssClassMixin):
|
||||||
@ -113,3 +122,7 @@ class ConfigForm(forms.Form, CssClassMixin):
|
|||||||
required=False,
|
required=False,
|
||||||
label=_("Welcome text"),
|
label=_("Welcome text"),
|
||||||
help_text=_("Printed in PDF of first time passwords only."))
|
help_text=_("Printed in PDF of first time passwords only."))
|
||||||
|
participant_sort_users_by_first_name = forms.BooleanField(
|
||||||
|
required=False,
|
||||||
|
label=_("Sort participants by first name"),
|
||||||
|
help_text=_("Disable for sorting by last name"))
|
||||||
|
@ -16,19 +16,24 @@ from django.db.models import signals
|
|||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils.translation import ugettext_lazy as _, ugettext_noop
|
from django.utils.translation import ugettext_lazy as _, ugettext_noop
|
||||||
|
|
||||||
from openslides.utils.person import PersonMixin
|
from openslides.utils.person import PersonMixin, Person
|
||||||
from openslides.utils.person.signals import receive_persons
|
from openslides.utils.person.signals import receive_persons
|
||||||
|
|
||||||
|
from openslides.config.models import config
|
||||||
from openslides.config.signals import default_config_value
|
from openslides.config.signals import default_config_value
|
||||||
|
|
||||||
|
from openslides.projector.api import register_slidemodel
|
||||||
|
from openslides.projector.projector import SlideMixin
|
||||||
|
|
||||||
class User(DjangoUser, PersonMixin):
|
|
||||||
|
class User(DjangoUser, PersonMixin, Person, SlideMixin):
|
||||||
|
prefix = 'user' # This is for the slides
|
||||||
person_prefix = 'user'
|
person_prefix = 'user'
|
||||||
GENDER_CHOICES = (
|
GENDER_CHOICES = (
|
||||||
('male', _('Male')),
|
('male', _('Male')),
|
||||||
('female', _('Female')),
|
('female', _('Female')),
|
||||||
)
|
)
|
||||||
TYPE_CHOICE = (
|
TYPE_CHOICES = (
|
||||||
('delegate', _('Delegate')),
|
('delegate', _('Delegate')),
|
||||||
('observer', _('Observer')),
|
('observer', _('Observer')),
|
||||||
('staff', _('Staff')),
|
('staff', _('Staff')),
|
||||||
@ -36,30 +41,37 @@ class User(DjangoUser, PersonMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
django_user = models.OneToOneField(DjangoUser, editable=False, parent_link=True)
|
django_user = models.OneToOneField(DjangoUser, editable=False, parent_link=True)
|
||||||
category = models.CharField(
|
structure_level = models.CharField(
|
||||||
max_length=100, null=True, blank=True, verbose_name=_("Category"),
|
max_length=100, blank=True, default='', verbose_name=_("Structure level"),
|
||||||
help_text=_('Will be shown behind the name.'))
|
help_text=_('Will be shown after the name.'))
|
||||||
gender = models.CharField(
|
gender = models.CharField(
|
||||||
max_length=50, choices=GENDER_CHOICES, blank=True,
|
max_length=50, choices=GENDER_CHOICES, blank=True,
|
||||||
verbose_name=_("Gender"), help_text=_('Only for filter the userlist.'))
|
verbose_name=_("Gender"), help_text=_('Only for filtering the participant list.'))
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
max_length=100, choices=TYPE_CHOICE, blank=True,
|
max_length=100, choices=TYPE_CHOICES, blank=True,
|
||||||
verbose_name=_("Typ"), help_text=_('Only for filter the userlist.'))
|
verbose_name=_("Typ"), help_text=_('Only for filtering the participant list.'))
|
||||||
committee = models.CharField(
|
committee = models.CharField(
|
||||||
max_length=100, null=True, blank=True, verbose_name=_("Committee"),
|
max_length=100, blank=True, default='', verbose_name=_("Committee"),
|
||||||
help_text=_('Only for filter the userlist.'))
|
help_text=_('Only for filtering the participant list.'))
|
||||||
|
about_me = models.TextField(
|
||||||
|
blank=True, default='', verbose_name=_('About me'),
|
||||||
|
help_text=_('Your profile text'))
|
||||||
comment = models.TextField(
|
comment = models.TextField(
|
||||||
null=True, blank=True, verbose_name=_('Comment'),
|
blank=True, default='', verbose_name=_('Comment'),
|
||||||
help_text=_('Only for notes.'))
|
help_text=_('Only for notes.'))
|
||||||
default_password = models.CharField(
|
default_password = models.CharField(
|
||||||
max_length=100, null=True, blank=True,
|
max_length=100, blank=True, default='',
|
||||||
verbose_name=_("Default password"))
|
verbose_name=_("Default password"))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def clean_name(self):
|
||||||
|
return self.get_full_name() or self.username
|
||||||
|
|
||||||
def get_name_suffix(self):
|
def get_name_suffix(self):
|
||||||
return self.category
|
return self.structure_level
|
||||||
|
|
||||||
def set_name_suffix(self, value):
|
def set_name_suffix(self, value):
|
||||||
self.category = value
|
self.structure_level = value
|
||||||
|
|
||||||
name_suffix = property(get_name_suffix, set_name_suffix)
|
name_suffix = property(get_name_suffix, set_name_suffix)
|
||||||
|
|
||||||
@ -72,25 +84,33 @@ class User(DjangoUser, PersonMixin):
|
|||||||
self.set_password(password)
|
self.set_password(password)
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sort_name(self):
|
||||||
|
if config['participant_sort_users_by_first_name']:
|
||||||
|
return self.first_name.lower()
|
||||||
|
return self.last_name.lower()
|
||||||
|
|
||||||
@models.permalink
|
@models.permalink
|
||||||
def get_absolute_url(self, link='edit'):
|
def get_absolute_url(self, link='view'):
|
||||||
"""
|
"""
|
||||||
Return the URL to this user.
|
Return the URL to this user.
|
||||||
|
|
||||||
link can be:
|
link can be:
|
||||||
|
* view
|
||||||
* edit
|
* edit
|
||||||
* delete
|
* delete
|
||||||
"""
|
"""
|
||||||
|
if link == 'view':
|
||||||
|
return ('user_view', [str(self.id)])
|
||||||
if link == 'edit':
|
if link == 'edit':
|
||||||
return ('user_edit', [str(self.id)])
|
return ('user_edit', [str(self.id)])
|
||||||
if link == 'delete':
|
if link == 'delete':
|
||||||
return ('user_delete', [str(self.id)])
|
return ('user_delete', [str(self.id)])
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
name = self.get_full_name() or self.username
|
|
||||||
if self.name_suffix:
|
if self.name_suffix:
|
||||||
return u"%s (%s)" % (name, self.name_suffix)
|
return u"%s (%s)" % (self.clean_name, self.name_suffix)
|
||||||
return u"%s" % name
|
return u"%s" % self.clean_name
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
# Rename permissions
|
# Rename permissions
|
||||||
@ -99,24 +119,42 @@ class User(DjangoUser, PersonMixin):
|
|||||||
('can_manage_participant',
|
('can_manage_participant',
|
||||||
ugettext_noop("Can manage participant")),
|
ugettext_noop("Can manage participant")),
|
||||||
)
|
)
|
||||||
|
ordering = ('last_name',)
|
||||||
|
|
||||||
|
def slide(self):
|
||||||
|
"""
|
||||||
|
Returns a map with the data for the slides.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
'shown_user': self,
|
||||||
|
'title': self.clean_name,
|
||||||
|
'template': 'projector/UserSlide.html'}
|
||||||
|
|
||||||
|
register_slidemodel(User)
|
||||||
|
|
||||||
|
|
||||||
class Group(DjangoGroup, PersonMixin):
|
class Group(DjangoGroup, PersonMixin, Person, SlideMixin):
|
||||||
|
prefix = 'group' # This is for the slides
|
||||||
person_prefix = 'group'
|
person_prefix = 'group'
|
||||||
|
|
||||||
django_group = models.OneToOneField(DjangoGroup, editable=False, parent_link=True)
|
django_group = models.OneToOneField(DjangoGroup, editable=False, parent_link=True)
|
||||||
group_as_person = models.BooleanField(default=False)
|
group_as_person = models.BooleanField(
|
||||||
description = models.TextField(blank=True)
|
default=False, verbose_name=_("Use this group as participant"),
|
||||||
|
help_text=_('For example as submitter of a motion.'))
|
||||||
|
description = models.TextField(blank=True, verbose_name=_("Description"))
|
||||||
|
|
||||||
@models.permalink
|
@models.permalink
|
||||||
def get_absolute_url(self, link='edit'):
|
def get_absolute_url(self, link='view'):
|
||||||
"""
|
"""
|
||||||
Return the URL to this user.
|
Return the URL to this user group.
|
||||||
|
|
||||||
link can be:
|
link can be:
|
||||||
|
* view
|
||||||
* edit
|
* edit
|
||||||
* delete
|
* delete
|
||||||
"""
|
"""
|
||||||
|
if link == 'view':
|
||||||
|
return ('user_group_view', [str(self.id)])
|
||||||
if link == 'edit':
|
if link == 'edit':
|
||||||
return ('user_group_edit', [str(self.id)])
|
return ('user_group_edit', [str(self.id)])
|
||||||
if link == 'delete':
|
if link == 'delete':
|
||||||
@ -125,19 +163,43 @@ class Group(DjangoGroup, PersonMixin):
|
|||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return unicode(self.name)
|
return unicode(self.name)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('name',)
|
||||||
|
|
||||||
class UsersConnector(object):
|
def slide(self):
|
||||||
|
"""
|
||||||
|
Returns a map with the data for the slides.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
'group': self,
|
||||||
|
'title': self.name,
|
||||||
|
'template': 'projector/GroupSlide.html'}
|
||||||
|
|
||||||
|
register_slidemodel(Group)
|
||||||
|
|
||||||
|
|
||||||
|
class UsersAndGroupsToPersons(object):
|
||||||
|
"""
|
||||||
|
Object to send all Users and Groups or a special User or Group to
|
||||||
|
the Person-API via receice_persons()
|
||||||
|
"""
|
||||||
def __init__(self, person_prefix_filter=None, id_filter=None):
|
def __init__(self, person_prefix_filter=None, id_filter=None):
|
||||||
self.person_prefix_filter = person_prefix_filter
|
self.person_prefix_filter = person_prefix_filter
|
||||||
self.id_filter = id_filter
|
self.id_filter = id_filter
|
||||||
self.users = User.objects.all()
|
if config['participant_sort_users_by_first_name']:
|
||||||
|
self.users = User.objects.all().order_by('first_name')
|
||||||
|
else:
|
||||||
|
self.users = User.objects.all().order_by('last_name')
|
||||||
self.groups = Group.objects.filter(group_as_person=True)
|
self.groups = Group.objects.filter(group_as_person=True)
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
if (not self.person_prefix_filter or
|
if (not self.person_prefix_filter or
|
||||||
self.person_prefix_filter == User.person_prefix):
|
self.person_prefix_filter == User.person_prefix):
|
||||||
if self.id_filter:
|
if self.id_filter:
|
||||||
|
try:
|
||||||
yield self.users.get(pk=self.id_filter)
|
yield self.users.get(pk=self.id_filter)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
for user in self.users:
|
for user in self.users:
|
||||||
yield user
|
yield user
|
||||||
@ -145,18 +207,22 @@ class UsersConnector(object):
|
|||||||
if (not self.person_prefix_filter or
|
if (not self.person_prefix_filter or
|
||||||
self.person_prefix_filter == Group.person_prefix):
|
self.person_prefix_filter == Group.person_prefix):
|
||||||
if self.id_filter:
|
if self.id_filter:
|
||||||
|
try:
|
||||||
yield self.groups.get(pk=self.id_filter)
|
yield self.groups.get(pk=self.id_filter)
|
||||||
|
except Group.DoesNotExist:
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
for group in self.groups:
|
for group in self.groups:
|
||||||
yield group
|
yield group
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
return User.objects.get(pk=key)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(receive_persons, dispatch_uid="participant")
|
@receiver(receive_persons, dispatch_uid="participant")
|
||||||
def receive_persons(sender, **kwargs):
|
def receive_persons(sender, **kwargs):
|
||||||
return UsersConnector(person_prefix_filter=kwargs['person_prefix_filter'],
|
"""
|
||||||
|
Answers to the Person-API
|
||||||
|
"""
|
||||||
|
return UsersAndGroupsToPersons(
|
||||||
|
person_prefix_filter=kwargs['person_prefix_filter'],
|
||||||
id_filter=kwargs['id_filter'])
|
id_filter=kwargs['id_filter'])
|
||||||
|
|
||||||
|
|
||||||
@ -169,11 +235,12 @@ def default_config(sender, key, **kwargs):
|
|||||||
return {
|
return {
|
||||||
'participant_pdf_system_url': 'http://example.com:8000',
|
'participant_pdf_system_url': 'http://example.com:8000',
|
||||||
'participant_pdf_welcometext': _('Welcome to OpenSlides!'),
|
'participant_pdf_welcometext': _('Welcome to OpenSlides!'),
|
||||||
|
'participant_sort_users_by_first_name': False,
|
||||||
}.get(key)
|
}.get(key)
|
||||||
|
|
||||||
|
|
||||||
@receiver(signals.post_save, sender=DjangoUser)
|
@receiver(signals.post_save, sender=DjangoUser)
|
||||||
def user_post_save(sender, instance, signal, *args, **kwargs):
|
def djangouser_post_save(sender, instance, signal, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
instance.user
|
instance.user
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
@ -181,8 +248,18 @@ def user_post_save(sender, instance, signal, *args, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
@receiver(signals.post_save, sender=DjangoGroup)
|
@receiver(signals.post_save, sender=DjangoGroup)
|
||||||
def group_post_save(sender, instance, signal, *args, **kwargs):
|
def djangogroup_post_save(sender, instance, signal, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
instance.group
|
instance.group
|
||||||
except Group.DoesNotExist:
|
except Group.DoesNotExist:
|
||||||
Group(django_group=instance).save_base(raw=True)
|
Group(django_group=instance).save_base(raw=True)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(signals.post_save, sender=User)
|
||||||
|
def user_post_save(sender, instance, *args, **kwargs):
|
||||||
|
from openslides.participant.api import get_or_create_registered_group
|
||||||
|
if not kwargs['created']:
|
||||||
|
return
|
||||||
|
registered = get_or_create_registered_group()
|
||||||
|
instance.groups.add(registered)
|
||||||
|
instance.save()
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
{% include "form.html" %}
|
{% include "form.html" %}
|
||||||
<p>
|
<p>
|
||||||
{% include "formbuttons_save.html" %}
|
{% include "formbuttons_save.html" %}
|
||||||
<a href='{% url config_application %}' class="btn">
|
<a href='{% url config_participant %}' class="btn">
|
||||||
{% trans 'Cancel' %}
|
{% trans 'Cancel' %}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
{% extends "participant/base_participant.html" %}
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
{% load tags %}
|
||||||
|
|
||||||
|
{% block title %}{{ block.super }} – {{ group }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1>{{ group }}</h1>
|
||||||
|
|
||||||
|
<p>{{ group.description }}</p>
|
||||||
|
|
||||||
|
<h2>{% trans "Members" %}</h2>
|
||||||
|
|
||||||
|
{% for member in group.user_set.all %}
|
||||||
|
<p>{{ member }}</p>
|
||||||
|
{% empty %}
|
||||||
|
<p>{% trans "No members available." %}</p>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -5,18 +5,18 @@
|
|||||||
{% block title %}
|
{% block title %}
|
||||||
{{ block.super }} –
|
{{ block.super }} –
|
||||||
{% if group %}
|
{% if group %}
|
||||||
{% trans "Edit user group" %}
|
{% trans "Edit group" %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans "New user group" %}
|
{% trans "New group" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>
|
<h1>
|
||||||
{% if group %}
|
{% if group %}
|
||||||
{% trans "Edit user group" %}
|
{% trans "Edit group" %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans "New user group" %}
|
{% trans "New group" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<small class="pull-right">
|
<small class="pull-right">
|
||||||
<a href="{% url user_group_overview %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Back to overview" %}</a>
|
<a href="{% url user_group_overview %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Back to overview" %}</a>
|
||||||
|
@ -2,34 +2,35 @@
|
|||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load staticfiles %}
|
{% load staticfiles %}
|
||||||
|
{% load tags %}
|
||||||
|
|
||||||
{% block title %}{{ block.super }} – {% trans "User groups" %}{% endblock %}
|
{% block title %}{{ block.super }} – {% trans "Groups" %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{% trans "User groups" %}
|
<h1>{% trans "Groups" %}
|
||||||
<small class="pull-right">
|
<small class="pull-right">
|
||||||
<a href="{% url user_group_new %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'New user group' %}"><i class="icon-plus"></i> {% trans "New" %}</a>
|
<a href="{% url user_group_new %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'New group' %}"><i class="icon-plus"></i> {% trans "New" %}</a>
|
||||||
<a href="{% url user_overview %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Participants" %}</a>
|
<a href="{% url user_overview %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Participants" %}</a>
|
||||||
</small>
|
</small>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<table class="table table-striped table-bordered">
|
<table class="table table-striped table-bordered">
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "User Group" %}</th>
|
<th>{% trans "Group" %}</th>
|
||||||
<th>{% trans "Actions" %}</th>
|
<th>{% trans "Actions" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for group in groups %}
|
{% for group in groups %}
|
||||||
<tr class="{% cycle '' 'odd' %}">
|
<tr class="{% cycle '' 'odd' %}">
|
||||||
<td>{{ group.name }}</td>
|
<td><a href="{% model_url group 'view' %}">{{ group.name }}</a></td>
|
||||||
<td><a href="{% url user_group_edit group.id %}"><img src="{% static 'images/icons/edit.png' %}" title="{% trans 'Edit group' %}"></a>
|
<td><a href="{% url user_group_edit group.id %}"><img src="{% static 'images/icons/edit.png' %}" title="{% trans 'Edit group' %}"></a>
|
||||||
{% if group.name != 'Anonymous' %}
|
{% if group.name|lower != 'anonymous' and group.name|lower != 'registered' %}
|
||||||
<a href="{% url user_group_delete group.id %}"><img src="{% static 'images/icons/delete.png' %}" title="{% trans 'Delete group' %}"></a>
|
<a href="{% url user_group_delete group.id %}"><img src="{% static 'images/icons/delete.png' %}" title="{% trans 'Delete group' %}"></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5"><i>{% trans "No user groups available." %}</i></td>
|
<td colspan="5"><i>{% trans "No groups available." %}</i></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
{% load tags %}
|
||||||
|
|
||||||
|
<ul style="line-height: 180%">
|
||||||
|
{% for group in groups %}
|
||||||
|
{% if group.name != 'Anonymous' and group.name != 'Registered' %}
|
||||||
|
<li class="{% if group.active %}activeline{% endif %}">
|
||||||
|
<a href="{% url projector_activate_slide group.sid %}" class="activate_link {% if group.active %}active{% endif %}">
|
||||||
|
<div></div>
|
||||||
|
</a>
|
||||||
|
<a href="{% model_url group 'delete' %}" title="{% trans 'Delete' %}" class="icon delete right">
|
||||||
|
<span></span>
|
||||||
|
</a>
|
||||||
|
<a href="{% model_url group 'edit' %}" title="{% trans 'Edit' %}" class="icon edit right">
|
||||||
|
<span></span>
|
||||||
|
</a>
|
||||||
|
<a href="{% url projctor_preview_slide group.sid %}" title="{% trans 'Preview' %}" class="icon preview right">
|
||||||
|
<span></span>
|
||||||
|
</a>
|
||||||
|
<a href="{% model_url group 'view' %}">{{ group }}</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% empty %}
|
||||||
|
<li>{% trans 'No groups available.' %}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
@ -14,7 +14,8 @@
|
|||||||
|
|
||||||
<p>{% trans 'Select a CSV file to import participants!' %}</p>
|
<p>{% trans 'Select a CSV file to import participants!' %}</p>
|
||||||
|
|
||||||
<p>{% trans 'Required comma separated values: <code>{first_name, last_name, gender, group, type, committee, comment}</code>' %}
|
<p>{% trans 'Required comma separated values' %}:
|
||||||
|
<code>({% trans 'first_name, last_name, gender, structure level, type, committee, comment' %})</code>
|
||||||
<br>
|
<br>
|
||||||
{% trans 'Required CSV file encoding: UTF-8 (Unicode).' %}
|
{% trans 'Required CSV file encoding: UTF-8 (Unicode).' %}
|
||||||
</p>
|
</p>
|
||||||
|
@ -60,11 +60,11 @@
|
|||||||
<option value="female"{% if 'female' in sortfilter.gender %} selected{% endif %}>{% trans "Female" %}</option>
|
<option value="female"{% if 'female' in sortfilter.gender %} selected{% endif %}>{% trans "Female" %}</option>
|
||||||
<option value=""{% if '' in sortfilter.gender %} selected{% endif %}>{% trans "Not specified" %}</option>
|
<option value=""{% if '' in sortfilter.gender %} selected{% endif %}>{% trans "Not specified" %}</option>
|
||||||
</select>
|
</select>
|
||||||
<select class="span2" name="category" onchange="document.forms['filter'].submit()">
|
<select class="span2" name="structure_level" onchange="document.forms['filter'].submit()">
|
||||||
<option value="---">-- {% trans "Category" %} --</option>
|
<option value="---">-- {% trans "Structure level" %} --</option>
|
||||||
{% for category in categories %}
|
{% for level in structure_levels %}
|
||||||
<option value="{{ category }}"{% if category in sortfilter.category %} selected{% endif %}>
|
<option value="{{ level }}"{% if level in sortfilter.structure_level %} selected{% endif %}>
|
||||||
{{ category }}</option>
|
{{ level }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<select class="span2" name="type" onchange="document.forms['filter'].submit()">
|
<select class="span2" name="type" onchange="document.forms['filter'].submit()">
|
||||||
@ -101,7 +101,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th><a href="?sort=first_name&reverse={% if 'first_name' in sortfilter.sort and 'reverse' not in sortfilter %}1{% else %}---{%endif%}">{% trans "First Name" %}</a></th>
|
<th><a href="?sort=first_name&reverse={% if 'first_name' in sortfilter.sort and 'reverse' not in sortfilter %}1{% else %}---{%endif%}">{% trans "First Name" %}</a></th>
|
||||||
<th><a href="?sort=last_name&reverse={% if 'last_name' in sortfilter.sort and 'reverse' not in sortfilter %}1{% else %}---{%endif%}">{% trans "Last Name" %}</a></th>
|
<th><a href="?sort=last_name&reverse={% if 'last_name' in sortfilter.sort and 'reverse' not in sortfilter %}1{% else %}---{%endif%}">{% trans "Last Name" %}</a></th>
|
||||||
<th class="optional"><a href="?sort=group&reverse={% if 'category' in sortfilter.sort and 'reverse' not in sortfilter %}1{% else %}---{%endif%}">{% trans "Category" %}</a></th>
|
<th class="optional"><a href="?sort=structure_level&reverse={% if 'structure_level' in sortfilter.sort and 'reverse' not in sortfilter %}1{% else %}---{%endif%}">{% trans "Structure level" %}</a></th>
|
||||||
<th class="optional"><a href="?sort=type&reverse={% if 'type' in sortfilter.sort and 'reverse' not in sortfilter %}1{% else %}---{%endif%}">{% trans "Type" %}</a></th>
|
<th class="optional"><a href="?sort=type&reverse={% if 'type' in sortfilter.sort and 'reverse' not in sortfilter %}1{% else %}---{%endif%}">{% trans "Type" %}</a></th>
|
||||||
<th class="optional"><a href="?sort=committee&reverse={% if 'committee' in sortfilter.sort and 'reverse' not in sortfilter %}1{% else %}---{%endif%}">{% trans "Committee" %}</a></th>
|
<th class="optional"><a href="?sort=committee&reverse={% if 'committee' in sortfilter.sort and 'reverse' not in sortfilter %}1{% else %}---{%endif%}">{% trans "Committee" %}</a></th>
|
||||||
{% if perms.participant.can_manage_participant %}
|
{% if perms.participant.can_manage_participant %}
|
||||||
@ -111,10 +111,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
{% for user in users %}
|
{% for user in users %}
|
||||||
<tr class="{% cycle '' 'odd' %}">
|
<tr>
|
||||||
<td>{{ user.first_name }}</td>
|
<td>{{ user.first_name }}</td>
|
||||||
<td>{{ user.last_name }}</td>
|
<td>{{ user.last_name }}</td>
|
||||||
<td class="optional">{{ user.category }}</td>
|
<td class="optional">{{ user.structure_level }}</td>
|
||||||
<td class="optional">{{ user.get_type_display }}</td>
|
<td class="optional">{{ user.get_type_display }}</td>
|
||||||
<td class="optional">{{ user.committee }}</td>
|
<td class="optional">{{ user.committee }}</td>
|
||||||
{% if perms.participant.can_manage_participant %}
|
{% if perms.participant.can_manage_participant %}
|
||||||
@ -129,15 +129,19 @@
|
|||||||
<a href="{% url user_edit user.id %}">
|
<a href="{% url user_edit user.id %}">
|
||||||
<img src="{% static 'images/icons/edit.png' %}" title="{% trans 'Edit participant' %}">
|
<img src="{% static 'images/icons/edit.png' %}" title="{% trans 'Edit participant' %}">
|
||||||
</a>
|
</a>
|
||||||
|
{% if user != request_user %}
|
||||||
<a href="{% url user_delete user.id %}">
|
<a href="{% url user_delete user.id %}">
|
||||||
<img src="{% static 'images/icons/delete.png' %}" title="{% trans 'Delete participant' %}">
|
<img src="{% static 'images/icons/delete.png' %}" title="{% trans 'Delete participant' %}">
|
||||||
</a>
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if user != request_user and not user.is_superuser %}
|
||||||
<a class="status_link deactivate" href="{% url user_status_deactivate user.id %}" title="{% trans 'Change status to inactive' %}"{% if not user.is_active %} style="display:none"{% endif %}>
|
<a class="status_link deactivate" href="{% url user_status_deactivate user.id %}" title="{% trans 'Change status to inactive' %}"{% if not user.is_active %} style="display:none"{% endif %}>
|
||||||
<span></span>
|
<span></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="status_link activate" href="{% url user_status_activate user.id %}" title="{% trans 'Change status to active' %}"{% if user.is_active %} style="display:none"{% endif %}>
|
<a class="status_link activate" href="{% url user_status_activate user.id %}" title="{% trans 'Change status to active' %}"{% if user.is_active %} style="display:none"{% endif %}>
|
||||||
<span></span>
|
<span></span>
|
||||||
</a>
|
</a>
|
||||||
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
{% load tags %}
|
||||||
|
|
||||||
|
<ul style="line-height: 180%">
|
||||||
|
{% trans "I submitted the following motions:" %}
|
||||||
|
{% for motion in submitted_motions %}
|
||||||
|
<li>
|
||||||
|
<a href="{% model_url motion 'view' %}">{{ motion.public_version.title }}</a>
|
||||||
|
({% trans "motion" %}
|
||||||
|
{% if motion.number %}
|
||||||
|
{{ motion.number }})
|
||||||
|
{% else %}
|
||||||
|
<i>[{% trans "no number" %}]</i>)
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% empty %}
|
||||||
|
<li><i>{% trans "None" %}</i></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% if config_motion_min_supporters %}
|
||||||
|
<hr />
|
||||||
|
<ul style="line-height: 180%">
|
||||||
|
{% trans "I support the following motions:" %}
|
||||||
|
{% for motion in supported_motions %}
|
||||||
|
<li>
|
||||||
|
<a href="{% model_url motion 'view' %}">{{ motion.public_version.title }}</a>
|
||||||
|
({% trans "motion" %}
|
||||||
|
{% if motion.number %}
|
||||||
|
{{ motion.number }})
|
||||||
|
{% else %}
|
||||||
|
<i>[{% trans "no number" %}]</i>)
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% empty %}
|
||||||
|
<li><i>{% trans "None" %}</i></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
<ul style="line-height: 180%">
|
||||||
|
{% trans "I am candidate for the following elections:" %}
|
||||||
|
{% for assignment in assignments %}
|
||||||
|
<li><a href="{% model_url assignment 'view' %}">{{ assignment }}</a></li>
|
||||||
|
{% empty %}
|
||||||
|
<li><i>{% trans "None" %}</i></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
@ -0,0 +1,57 @@
|
|||||||
|
{% extends "participant/base_participant.html" %}
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
{% load tags %}
|
||||||
|
|
||||||
|
{% block title %}{{ block.super }} – {{ shown_user }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1>{{ shown_user }}</h1>
|
||||||
|
|
||||||
|
<p>{{ shown_user.email }}</p>
|
||||||
|
|
||||||
|
<h2>{% trans "Groups" %}</h2>
|
||||||
|
<p>
|
||||||
|
{% if shown_user.groups.all %}
|
||||||
|
{{ shown_user.groups.all|join:", " }}
|
||||||
|
{% else %}
|
||||||
|
{% trans "The participant is not member of any group." %}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{% if shown_user.get_gender_display %}
|
||||||
|
<h2>{% trans "Gender" %}</h2>
|
||||||
|
<p>{{ shown_user.get_gender_display }}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if shown_user.get_type_display %}
|
||||||
|
<h2>{% trans "Type" %}</h2>
|
||||||
|
<p>{{ shown_user.get_type_display }}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if shown_user.committee %}
|
||||||
|
<h2>{% trans "Committee" %}</h2>
|
||||||
|
<p>{{ shown_user.committee }}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if shown_user.about_me %}
|
||||||
|
<h2>{% trans "About me" %}</h2>
|
||||||
|
<p>{{ shown_user.about_me }}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if perms.participant.can_manage_participant %}
|
||||||
|
{% if shown_user.comment %}
|
||||||
|
<h2>{% trans "Comment" %}</h2>
|
||||||
|
<p>{{ shown_user.comment }}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<h2>{% trans "Last Login" %}</h2>
|
||||||
|
{% if shown_user.last_login > shown_user.date_joined %}
|
||||||
|
<p>{{ shown_user.last_login }}</p>
|
||||||
|
{% else %}
|
||||||
|
<p>{% trans "The participant has not logged in yet." %}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -0,0 +1,24 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
{% load tags %}
|
||||||
|
|
||||||
|
<ul style="line-height: 180%">
|
||||||
|
{% for user in users %}
|
||||||
|
<li class="{% if user.active %}activeline{% endif %}">
|
||||||
|
<a href="{% url projector_activate_slide user.sid %}" class="activate_link {% if user.active %}active{% endif %}">
|
||||||
|
<div></div>
|
||||||
|
</a>
|
||||||
|
<a href="{% model_url user 'delete' %}" title="{% trans 'Delete' %}" class="icon delete right">
|
||||||
|
<span></span>
|
||||||
|
</a>
|
||||||
|
<a href="{% model_url user 'edit' %}" title="{% trans 'Edit' %}" class="icon edit right">
|
||||||
|
<span></span>
|
||||||
|
</a>
|
||||||
|
<a href="{% url projctor_preview_slide user.sid %}" title="{% trans 'Preview' %}" class="icon preview right">
|
||||||
|
<span></span>
|
||||||
|
</a>
|
||||||
|
<a href="{% model_url user 'view' %}">{{ user }}</a>
|
||||||
|
</li>
|
||||||
|
{% empty %}
|
||||||
|
<li>{% trans 'No participants available.' %}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
22
openslides/participant/templates/projector/GroupSlide.html
Normal file
22
openslides/participant/templates/projector/GroupSlide.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{% extends "base-projector.html" %}
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
{% load tags %}
|
||||||
|
|
||||||
|
{% block title %}{{ block.super }} - {{ title }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="item_fullscreen">{{ group }}
|
||||||
|
<span>
|
||||||
|
<p><i>{{ group.user_set.all.count }} {% trans "participants" %}</i></p>
|
||||||
|
{% comment %}
|
||||||
|
TODO: print fullname (not username) of all group users [see #420]
|
||||||
|
<p>
|
||||||
|
{% if group.user_set.all %}
|
||||||
|
{{ group.user_set.all|join:", " }}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
{% endcomment %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
21
openslides/participant/templates/projector/UserSlide.html
Normal file
21
openslides/participant/templates/projector/UserSlide.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{% extends "base-projector.html" %}
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
{% load tags %}
|
||||||
|
|
||||||
|
{% block title %}{{ block.super }} - {{ title }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="item_fullscreen">{{ shown_user }}
|
||||||
|
<span>
|
||||||
|
{% if shown_user.committee %}
|
||||||
|
<p>{{ shown_user.committee }}</p>
|
||||||
|
{% endif %}
|
||||||
|
<p>
|
||||||
|
{% if shown_user.groups.all %}
|
||||||
|
{{ shown_user.groups.all|join:", " }}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -11,17 +11,18 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from django.conf.urls.defaults import url, patterns
|
from django.conf.urls.defaults import url, patterns
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
|
|
||||||
from openslides.participant.views import (
|
from openslides.participant.views import (
|
||||||
ParticipantsListPDF, ParticipantsPasswordsPDF, Overview, UserCreateView,
|
UserOverview, UserCreateView, UserDetailView, UserUpdateView,
|
||||||
UserUpdateView, UserDeleteView, SetUserStatusView, UserImportView,
|
UserDeleteView, ResetPasswordView, SetUserStatusView, UserImportView,
|
||||||
ResetPasswordView, GroupOverviewView, GroupCreateView, GroupUpdateView,
|
GroupOverview, GroupCreateView, GroupDetailView, GroupUpdateView, GroupDeleteView,
|
||||||
GroupDeleteView)
|
ParticipantsListPDF, ParticipantsPasswordsPDF)
|
||||||
|
|
||||||
urlpatterns = patterns('openslides.participant.views',
|
urlpatterns = patterns('',
|
||||||
|
|
||||||
|
# User
|
||||||
url(r'^$',
|
url(r'^$',
|
||||||
Overview.as_view(),
|
UserOverview.as_view(),
|
||||||
name='user_overview',
|
name='user_overview',
|
||||||
),
|
),
|
||||||
|
|
||||||
@ -30,6 +31,11 @@ urlpatterns = patterns('openslides.participant.views',
|
|||||||
name='user_new',
|
name='user_new',
|
||||||
),
|
),
|
||||||
|
|
||||||
|
url(r'^(?P<pk>\d+)/$',
|
||||||
|
UserDetailView.as_view(),
|
||||||
|
name='user_view',
|
||||||
|
),
|
||||||
|
|
||||||
url(r'^(?P<pk>\d+)/edit/$',
|
url(r'^(?P<pk>\d+)/edit/$',
|
||||||
UserUpdateView.as_view(),
|
UserUpdateView.as_view(),
|
||||||
name='user_edit',
|
name='user_edit',
|
||||||
@ -45,12 +51,6 @@ urlpatterns = patterns('openslides.participant.views',
|
|||||||
name='user_reset_password',
|
name='user_reset_password',
|
||||||
),
|
),
|
||||||
|
|
||||||
url(r'^(?P<pk>\d+)/status/toggle/$',
|
|
||||||
SetUserStatusView.as_view(),
|
|
||||||
{'action': 'toggle'},
|
|
||||||
name='user_status_toggle',
|
|
||||||
),
|
|
||||||
|
|
||||||
url(r'^(?P<pk>\d+)/status/activate/$',
|
url(r'^(?P<pk>\d+)/status/activate/$',
|
||||||
SetUserStatusView.as_view(),
|
SetUserStatusView.as_view(),
|
||||||
{'action': 'activate'},
|
{'action': 'activate'},
|
||||||
@ -63,13 +63,20 @@ urlpatterns = patterns('openslides.participant.views',
|
|||||||
name='user_status_deactivate',
|
name='user_status_deactivate',
|
||||||
),
|
),
|
||||||
|
|
||||||
|
url(r'^(?P<pk>\d+)/status/toggle/$',
|
||||||
|
SetUserStatusView.as_view(),
|
||||||
|
{'action': 'toggle'},
|
||||||
|
name='user_status_toggle',
|
||||||
|
),
|
||||||
|
|
||||||
url(r'^import/$',
|
url(r'^import/$',
|
||||||
UserImportView.as_view(),
|
UserImportView.as_view(),
|
||||||
name='user_import',
|
name='user_import',
|
||||||
),
|
),
|
||||||
|
|
||||||
|
# Group
|
||||||
url(r'^group/$',
|
url(r'^group/$',
|
||||||
GroupOverviewView.as_view(),
|
GroupOverview.as_view(),
|
||||||
name='user_group_overview',
|
name='user_group_overview',
|
||||||
),
|
),
|
||||||
|
|
||||||
@ -78,6 +85,11 @@ urlpatterns = patterns('openslides.participant.views',
|
|||||||
name='user_group_new',
|
name='user_group_new',
|
||||||
),
|
),
|
||||||
|
|
||||||
|
url(r'^group/(?P<pk>\d+)/$',
|
||||||
|
GroupDetailView.as_view(),
|
||||||
|
name='user_group_view',
|
||||||
|
),
|
||||||
|
|
||||||
url(r'^group/(?P<pk>\d+)/edit/$',
|
url(r'^group/(?P<pk>\d+)/edit/$',
|
||||||
GroupUpdateView.as_view(),
|
GroupUpdateView.as_view(),
|
||||||
name='user_group_edit',
|
name='user_group_edit',
|
||||||
@ -88,6 +100,7 @@ urlpatterns = patterns('openslides.participant.views',
|
|||||||
name='user_group_delete',
|
name='user_group_delete',
|
||||||
),
|
),
|
||||||
|
|
||||||
|
# PDF
|
||||||
url(r'^print/$',
|
url(r'^print/$',
|
||||||
ParticipantsListPDF.as_view(),
|
ParticipantsListPDF.as_view(),
|
||||||
name='user_print',
|
name='user_print',
|
||||||
|
@ -28,7 +28,6 @@ from reportlab.platypus import (
|
|||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.forms import PasswordChangeForm
|
from django.contrib.auth.forms import PasswordChangeForm
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.contrib.auth.views import login as django_login
|
from django.contrib.auth.views import login as django_login
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
@ -39,11 +38,12 @@ from openslides.utils.template import Tab
|
|||||||
from openslides.utils.utils import (
|
from openslides.utils.utils import (
|
||||||
template, decodedict, encodedict, delete_default_permissions, html_strong)
|
template, decodedict, encodedict, delete_default_permissions, html_strong)
|
||||||
from openslides.utils.views import (
|
from openslides.utils.views import (
|
||||||
FormView, PDFView, CreateView, UpdateView, DeleteView,
|
FormView, PDFView, CreateView, UpdateView, DeleteView, PermissionMixin,
|
||||||
RedirectView, SingleObjectMixin, ListView, QuestionMixin)
|
RedirectView, SingleObjectMixin, ListView, QuestionMixin, DetailView)
|
||||||
|
|
||||||
from openslides.config.models import config
|
from openslides.config.models import config
|
||||||
|
from openslides.projector.projector import Widget
|
||||||
|
from openslides.motion.models import Motion
|
||||||
|
from openslides.assignment.models import Assignment
|
||||||
from openslides.participant.api import gen_username, gen_password, import_users
|
from openslides.participant.api import gen_username, gen_password, import_users
|
||||||
from openslides.participant.forms import (
|
from openslides.participant.forms import (
|
||||||
UserCreateForm, UserUpdateForm, UsersettingsForm,
|
UserCreateForm, UserUpdateForm, UsersettingsForm,
|
||||||
@ -51,9 +51,9 @@ from openslides.participant.forms import (
|
|||||||
from openslides.participant.models import User, Group
|
from openslides.participant.models import User, Group
|
||||||
|
|
||||||
|
|
||||||
class Overview(ListView):
|
class UserOverview(ListView):
|
||||||
"""
|
"""
|
||||||
Show all participants.
|
Show all participants (users).
|
||||||
"""
|
"""
|
||||||
permission_required = 'participant.can_see_participant'
|
permission_required = 'participant.can_see_participant'
|
||||||
template_name = 'participant/overview.html'
|
template_name = 'participant/overview.html'
|
||||||
@ -66,8 +66,8 @@ class Overview(ListView):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
sortfilter = {}
|
sortfilter = {}
|
||||||
|
|
||||||
for value in [u'gender', u'category', u'type', u'committee', u'status',
|
for value in ['gender', 'structure_level', 'type', 'committee', 'status',
|
||||||
u'sort', u'reverse']:
|
'sort', 'reverse']:
|
||||||
if value in self.request.REQUEST:
|
if value in self.request.REQUEST:
|
||||||
if self.request.REQUEST[value] == '---':
|
if self.request.REQUEST[value] == '---':
|
||||||
try:
|
try:
|
||||||
@ -80,8 +80,8 @@ class Overview(ListView):
|
|||||||
query = User.objects
|
query = User.objects
|
||||||
if 'gender' in sortfilter:
|
if 'gender' in sortfilter:
|
||||||
query = query.filter(gender__iexact=sortfilter['gender'][0])
|
query = query.filter(gender__iexact=sortfilter['gender'][0])
|
||||||
if 'category' in sortfilter:
|
if 'structure_level' in sortfilter:
|
||||||
query = query.filter(category__iexact=sortfilter['category'][0])
|
query = query.filter(structure_level__iexact=sortfilter['structure_level'][0])
|
||||||
if 'type' in sortfilter:
|
if 'type' in sortfilter:
|
||||||
query = query.filter(type__iexact=sortfilter['type'][0])
|
query = query.filter(type__iexact=sortfilter['type'][0])
|
||||||
if 'committee' in sortfilter:
|
if 'committee' in sortfilter:
|
||||||
@ -92,9 +92,12 @@ class Overview(ListView):
|
|||||||
if sortfilter['sort'][0] in ['first_name', 'last_name', 'last_login']:
|
if sortfilter['sort'][0] in ['first_name', 'last_name', 'last_login']:
|
||||||
query = query.order_by(sortfilter['sort'][0])
|
query = query.order_by(sortfilter['sort'][0])
|
||||||
elif (sortfilter['sort'][0] in
|
elif (sortfilter['sort'][0] in
|
||||||
['category', 'type', 'committee', 'comment']):
|
['structure_level', 'type', 'committee', 'comment']):
|
||||||
query = query.order_by(
|
query = query.order_by(
|
||||||
'%s' % sortfilter['sort'][0])
|
'%s' % sortfilter['sort'][0])
|
||||||
|
else:
|
||||||
|
if config['participant_sort_users_by_first_name']:
|
||||||
|
query = query.order_by('first_name')
|
||||||
else:
|
else:
|
||||||
query = query.order_by('last_name')
|
query = query.order_by('last_name')
|
||||||
|
|
||||||
@ -106,7 +109,7 @@ class Overview(ListView):
|
|||||||
return query.all()
|
return query.all()
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(Overview, self).get_context_data(**kwargs)
|
context = super(UserOverview, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
all_users = User.objects.count()
|
all_users = User.objects.count()
|
||||||
|
|
||||||
@ -117,23 +120,47 @@ class Overview(ListView):
|
|||||||
percent = 0
|
percent = 0
|
||||||
|
|
||||||
# list of all existing categories
|
# list of all existing categories
|
||||||
categories = [p['category'] for p in User.objects.values('category')
|
structure_levels = [
|
||||||
.exclude(category='').distinct()]
|
p['structure_level'] for p in
|
||||||
|
User.objects.values('structure_level').exclude(structure_level='').distinct()]
|
||||||
# list of all existing committees
|
# list of all existing committees
|
||||||
committees = [p['committee'] for p in User.objects.values('committee')
|
committees = [
|
||||||
.exclude(committee='').distinct()]
|
p['committee'] for p in
|
||||||
|
User.objects.values('committee').exclude(committee='').distinct()]
|
||||||
|
# context vars
|
||||||
context.update({
|
context.update({
|
||||||
'allusers': all_users,
|
'allusers': all_users,
|
||||||
|
'request_user': self.request.user,
|
||||||
'percent': round(percent, 1),
|
'percent': round(percent, 1),
|
||||||
'categories': categories,
|
'structure_levels': structure_levels,
|
||||||
'committees': committees,
|
'committees': committees,
|
||||||
'cookie': ['participant_sortfilter', urlencode(decodedict(self.sortfilter),
|
'cookie': [
|
||||||
|
'participant_sortfilter', urlencode(decodedict(self.sortfilter),
|
||||||
doseq=True)],
|
doseq=True)],
|
||||||
'sortfilter': self.sortfilter})
|
'sortfilter': self.sortfilter})
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class UserDetailView(DetailView, PermissionMixin):
|
||||||
|
"""
|
||||||
|
Classed based view to show a specific user in the interface.
|
||||||
|
"""
|
||||||
|
permission_required = 'participant.can_see_participant'
|
||||||
|
model = User
|
||||||
|
template_name = 'participant/user_detail.html'
|
||||||
|
context_object_name = 'shown_user'
|
||||||
|
|
||||||
|
|
||||||
|
class GroupDetailView(DetailView, PermissionMixin):
|
||||||
|
"""
|
||||||
|
Classed based view to show a specific group in the interface.
|
||||||
|
"""
|
||||||
|
permission_required = 'participant.can_manage_participant'
|
||||||
|
model = Group
|
||||||
|
template_name = 'participant/group_detail.html'
|
||||||
|
context_object_name = 'group'
|
||||||
|
|
||||||
|
|
||||||
class UserCreateView(CreateView):
|
class UserCreateView(CreateView):
|
||||||
"""
|
"""
|
||||||
Create a new participant.
|
Create a new participant.
|
||||||
@ -147,8 +174,8 @@ class UserCreateView(CreateView):
|
|||||||
apply_url = 'user_edit'
|
apply_url = 'user_edit'
|
||||||
|
|
||||||
def manipulate_object(self, form):
|
def manipulate_object(self, form):
|
||||||
self.object.username = gen_username(form.cleaned_data['first_name'],
|
self.object.username = gen_username(
|
||||||
form.cleaned_data['last_name'])
|
form.cleaned_data['first_name'], form.cleaned_data['last_name'])
|
||||||
if not self.object.default_password:
|
if not self.object.default_password:
|
||||||
self.object.default_password = gen_password()
|
self.object.default_password = gen_password()
|
||||||
self.object.set_password(self.object.default_password)
|
self.object.set_password(self.object.default_password)
|
||||||
@ -175,6 +202,12 @@ class UserDeleteView(DeleteView):
|
|||||||
model = User
|
model = User
|
||||||
url = 'user_overview'
|
url = 'user_overview'
|
||||||
|
|
||||||
|
def pre_redirect(self, request, *args, **kwargs):
|
||||||
|
if self.get_object() == self.request.user:
|
||||||
|
messages.error(request, _("You can not delete yourself."))
|
||||||
|
else:
|
||||||
|
super(UserDeleteView, self).pre_redirect(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class SetUserStatusView(RedirectView, SingleObjectMixin):
|
class SetUserStatusView(RedirectView, SingleObjectMixin):
|
||||||
"""
|
"""
|
||||||
@ -191,6 +224,12 @@ class SetUserStatusView(RedirectView, SingleObjectMixin):
|
|||||||
if action == 'activate':
|
if action == 'activate':
|
||||||
self.object.is_active = True
|
self.object.is_active = True
|
||||||
elif action == 'deactivate':
|
elif action == 'deactivate':
|
||||||
|
if self.get_object().user == self.request.user:
|
||||||
|
messages.error(request, _("You can not deactivate yourself."))
|
||||||
|
return
|
||||||
|
elif self.get_object().is_superuser:
|
||||||
|
messages.error(request, _("You can not deactivate the administrator."))
|
||||||
|
return
|
||||||
self.object.is_active = False
|
self.object.is_active = False
|
||||||
elif action == 'toggle':
|
elif action == 'toggle':
|
||||||
self.object.is_active = not self.object.is_active
|
self.object.is_active = not self.object.is_active
|
||||||
@ -214,6 +253,9 @@ class ParticipantsListPDF(PDFView):
|
|||||||
def append_to_pdf(self, story):
|
def append_to_pdf(self, story):
|
||||||
data = [['#', _('Last Name'), _('First Name'), _('Group'), _('Type'),
|
data = [['#', _('Last Name'), _('First Name'), _('Group'), _('Type'),
|
||||||
_('Committee')]]
|
_('Committee')]]
|
||||||
|
if config['participant_sort_users_by_first_name']:
|
||||||
|
sort = 'first_name'
|
||||||
|
else:
|
||||||
sort = 'last_name'
|
sort = 'last_name'
|
||||||
counter = 0
|
counter = 0
|
||||||
for user in User.objects.all().order_by(sort):
|
for user in User.objects.all().order_by(sort):
|
||||||
@ -222,8 +264,8 @@ class ParticipantsListPDF(PDFView):
|
|||||||
counter,
|
counter,
|
||||||
Paragraph(user.last_name, stylesheet['Tablecell']),
|
Paragraph(user.last_name, stylesheet['Tablecell']),
|
||||||
Paragraph(user.first_name, stylesheet['Tablecell']),
|
Paragraph(user.first_name, stylesheet['Tablecell']),
|
||||||
Paragraph(user.category, stylesheet['Tablecell']),
|
Paragraph(user.structure_level, stylesheet['Tablecell']),
|
||||||
Paragraph(user.type, stylesheet['Tablecell']),
|
Paragraph(user.get_type_display(), stylesheet['Tablecell']),
|
||||||
Paragraph(user.committee, stylesheet['Tablecell'])])
|
Paragraph(user.committee, stylesheet['Tablecell'])])
|
||||||
t = LongTable(data, style=[
|
t = LongTable(data, style=[
|
||||||
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||||
@ -256,7 +298,11 @@ class ParticipantsPasswordsPDF(PDFView):
|
|||||||
data = []
|
data = []
|
||||||
participant_pdf_system_url = config["participant_pdf_system_url"]
|
participant_pdf_system_url = config["participant_pdf_system_url"]
|
||||||
participant_pdf_welcometext = config["participant_pdf_welcometext"]
|
participant_pdf_welcometext = config["participant_pdf_welcometext"]
|
||||||
for user in User.objects.all().order_by('last_name'):
|
if config['participant_sort_users_by_first_name']:
|
||||||
|
sort = 'first_name'
|
||||||
|
else:
|
||||||
|
sort = 'last_name'
|
||||||
|
for user in User.objects.all().order_by(sort):
|
||||||
cell = []
|
cell = []
|
||||||
cell.append(Spacer(0, 0.8 * cm))
|
cell.append(Spacer(0, 0.8 * cm))
|
||||||
cell.append(Paragraph(_("Account for OpenSlides"),
|
cell.append(Paragraph(_("Account for OpenSlides"),
|
||||||
@ -269,7 +315,7 @@ class ParticipantsPasswordsPDF(PDFView):
|
|||||||
cell.append(
|
cell.append(
|
||||||
Paragraph(
|
Paragraph(
|
||||||
_("Password: %s")
|
_("Password: %s")
|
||||||
% (user.firstpassword), stylesheet['Monotype']))
|
% (user.default_password), stylesheet['Monotype']))
|
||||||
cell.append(Spacer(0, 0.5 * cm))
|
cell.append(Spacer(0, 0.5 * cm))
|
||||||
cell.append(
|
cell.append(
|
||||||
Paragraph(
|
Paragraph(
|
||||||
@ -319,9 +365,9 @@ class UserImportView(FormView):
|
|||||||
return super(UserImportView, self).form_valid(form)
|
return super(UserImportView, self).form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
class ResetPasswordView(RedirectView, SingleObjectMixin, QuestionMixin):
|
class ResetPasswordView(SingleObjectMixin, QuestionMixin, RedirectView):
|
||||||
"""
|
"""
|
||||||
Set the Passwort for a user to his firstpassword.
|
Set the Passwort for a user to his default password.
|
||||||
"""
|
"""
|
||||||
permission_required = 'participant.can_manage_participant'
|
permission_required = 'participant.can_manage_participant'
|
||||||
model = User
|
model = User
|
||||||
@ -335,20 +381,14 @@ class ResetPasswordView(RedirectView, SingleObjectMixin, QuestionMixin):
|
|||||||
def get_redirect_url(self, **kwargs):
|
def get_redirect_url(self, **kwargs):
|
||||||
return reverse('user_edit', args=[self.object.id])
|
return reverse('user_edit', args=[self.object.id])
|
||||||
|
|
||||||
def pre_redirect(self, request, *args, **kwargs):
|
def case_yes(self):
|
||||||
self.confirm_form()
|
|
||||||
|
|
||||||
def pre_post_redirect(self, request, *args, **kwargs):
|
|
||||||
if self.get_answer().lower() == 'yes':
|
|
||||||
self.object.reset_password()
|
self.object.reset_password()
|
||||||
messages.success(request,
|
|
||||||
_('The Password for %s was successfully reset.') % html_strong(self.object))
|
|
||||||
|
|
||||||
def get_answer_url(self):
|
def get_success_message(self):
|
||||||
return reverse('user_reset_password', args=[self.object.id])
|
return _('The Password for %s was successfully reset.') % html_strong(self.object)
|
||||||
|
|
||||||
|
|
||||||
class GroupOverviewView(ListView):
|
class GroupOverview(ListView):
|
||||||
"""
|
"""
|
||||||
Overview over all groups.
|
Overview over all groups.
|
||||||
"""
|
"""
|
||||||
@ -400,6 +440,12 @@ class GroupDeleteView(DeleteView):
|
|||||||
model = Group
|
model = Group
|
||||||
url = 'user_group_overview'
|
url = 'user_group_overview'
|
||||||
|
|
||||||
|
def pre_redirect(self, request, *args, **kwargs):
|
||||||
|
if self.get_object().name.lower() in ['anonymous', 'registered']:
|
||||||
|
messages.error(request, _("You can not delete this Group."))
|
||||||
|
else:
|
||||||
|
super(GroupDeleteView, self).pre_redirect(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Config(FormView):
|
class Config(FormView):
|
||||||
"""
|
"""
|
||||||
@ -412,13 +458,16 @@ class Config(FormView):
|
|||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
return {
|
return {
|
||||||
'participant_pdf_system_url': config['participant_pdf_system_url'],
|
'participant_pdf_system_url': config['participant_pdf_system_url'],
|
||||||
'participant_pdf_welcometext': config['participant_pdf_welcometext']}
|
'participant_pdf_welcometext': config['participant_pdf_welcometext'],
|
||||||
|
'participant_sort_users_by_first_name': config['participant_sort_users_by_first_name']}
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
config['participant_pdf_system_url'] = (
|
config['participant_pdf_system_url'] = (
|
||||||
form.cleaned_data['participant_pdf_system_url'])
|
form.cleaned_data['participant_pdf_system_url'])
|
||||||
config['participant_pdf_welcometext'] = (
|
config['participant_pdf_welcometext'] = (
|
||||||
form.cleaned_data['participant_pdf_welcometext'])
|
form.cleaned_data['participant_pdf_welcometext'])
|
||||||
|
config['participant_sort_users_by_first_name'] = (
|
||||||
|
form.cleaned_data['participant_sort_users_by_first_name'])
|
||||||
messages.success(
|
messages.success(
|
||||||
self.request,
|
self.request,
|
||||||
_('Participants settings successfully saved.'))
|
_('Participants settings successfully saved.'))
|
||||||
@ -497,6 +546,67 @@ def register_tab(request):
|
|||||||
title=_('Participants'),
|
title=_('Participants'),
|
||||||
app='participant',
|
app='participant',
|
||||||
url=reverse('user_overview'),
|
url=reverse('user_overview'),
|
||||||
permission=request.user.has_perm('participant.can_see_participant') or
|
permission=(
|
||||||
request.user.has_perm('participant.can_manage_participant'),
|
request.user.has_perm('participant.can_see_participant') or
|
||||||
|
request.user.has_perm('participant.can_manage_participant')),
|
||||||
selected=selected)
|
selected=selected)
|
||||||
|
|
||||||
|
|
||||||
|
def get_widgets(request):
|
||||||
|
"""
|
||||||
|
Returns all widgets of the participant app. This is a user_widget, a
|
||||||
|
group_widget and a personal_info_widget.
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
get_personal_info_widget(request),
|
||||||
|
get_user_widget(request),
|
||||||
|
get_group_widget(request)]
|
||||||
|
|
||||||
|
|
||||||
|
def get_personal_info_widget(request):
|
||||||
|
"""
|
||||||
|
Provides a widget for personal info. It shows your submitted motions
|
||||||
|
and where you are supporter or candidate.
|
||||||
|
"""
|
||||||
|
personal_info_context = {
|
||||||
|
'submitted_motions': Motion.objects.filter(submitter=request.user),
|
||||||
|
'config_motion_min_supporters': config['motion_min_supporters'],
|
||||||
|
'supported_motions': Motion.objects.filter(motionsupporter=request.user),
|
||||||
|
'assignments': Assignment.objects.filter(
|
||||||
|
assignmentcandidate__person=request.user,
|
||||||
|
assignmentcandidate__blocked=False)}
|
||||||
|
return Widget(
|
||||||
|
name='personal_info',
|
||||||
|
display_name=_('My motions and elections'),
|
||||||
|
template='participant/personal_info_widget.html',
|
||||||
|
context=personal_info_context,
|
||||||
|
permission_required=None,
|
||||||
|
default_column=1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_widget(request):
|
||||||
|
"""
|
||||||
|
Provides a widget with all users. This is for short activation of
|
||||||
|
user slides.
|
||||||
|
"""
|
||||||
|
return Widget(
|
||||||
|
name='user',
|
||||||
|
display_name=_('Participants'),
|
||||||
|
template='participant/user_widget.html',
|
||||||
|
context={'users': User.objects.all()},
|
||||||
|
permission_required='projector.can_manage_projector',
|
||||||
|
default_column=1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_group_widget(request):
|
||||||
|
"""
|
||||||
|
Provides a widget with all groups. This is for short activation of
|
||||||
|
group slides.
|
||||||
|
"""
|
||||||
|
return Widget(
|
||||||
|
name='group',
|
||||||
|
display_name=_('Groups'),
|
||||||
|
template='participant/group_widget.html',
|
||||||
|
context={'groups': Group.objects.all()},
|
||||||
|
permission_required='projector.can_manage_projector',
|
||||||
|
default_column=1)
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from openslides.utils.forms import CssClassMixin
|
from openslides.utils.forms import CssClassMixin
|
||||||
|
|
||||||
|
@ -164,7 +164,6 @@ class BasePoll(models.Model):
|
|||||||
"""
|
"""
|
||||||
return self.vote_values
|
return self.vote_values
|
||||||
|
|
||||||
|
|
||||||
def get_vote_class(self):
|
def get_vote_class(self):
|
||||||
"""
|
"""
|
||||||
Return the releatet vote class.
|
Return the releatet vote class.
|
||||||
@ -236,7 +235,7 @@ def print_value(value, percent_base=0):
|
|||||||
elif value == -2:
|
elif value == -2:
|
||||||
return unicode(_('undocumented'))
|
return unicode(_('undocumented'))
|
||||||
elif value is None:
|
elif value is None:
|
||||||
return u''
|
return unicode(_('undocumented'))
|
||||||
if not percent_base:
|
if not percent_base:
|
||||||
return u'%s' % value
|
return u'%s' % value
|
||||||
|
|
||||||
|
@ -11,12 +11,12 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.template.loader import render_to_string
|
from django.core.cache import cache
|
||||||
from django.utils.datastructures import SortedDict
|
from django.utils.datastructures import SortedDict
|
||||||
from django.utils.importlib import import_module
|
from django.utils.importlib import import_module
|
||||||
|
|
||||||
from openslides.config.models import config
|
from openslides.config.models import config
|
||||||
from openslides.projector.projector import SLIDE, Slide, Widget
|
from openslides.projector.projector import SLIDE, Slide
|
||||||
|
|
||||||
|
|
||||||
def split_sid(sid):
|
def split_sid(sid):
|
||||||
@ -85,29 +85,27 @@ def set_active_slide(sid, argument=None):
|
|||||||
"""
|
"""
|
||||||
config["presentation"] = sid
|
config["presentation"] = sid
|
||||||
config['presentation_argument'] = argument
|
config['presentation_argument'] = argument
|
||||||
|
clear_projector_cache()
|
||||||
|
|
||||||
|
|
||||||
def register_slidemodel(model, model_name=None, control_template=None,
|
def clear_projector_cache():
|
||||||
weight=0):
|
cache.delete('projector_content')
|
||||||
|
cache.delete('projector_scrollcontent')
|
||||||
|
cache.delete('projector_data')
|
||||||
|
|
||||||
|
|
||||||
|
def register_slidemodel(model, model_name=None, control_template=None, weight=0):
|
||||||
"""
|
"""
|
||||||
Register a Model as a slide.
|
Register a Model as a slide.
|
||||||
"""
|
"""
|
||||||
|
# TODO: control_template should never be None
|
||||||
if model_name is None:
|
if model_name is None:
|
||||||
model_name = model.prefix
|
model_name = model.prefix
|
||||||
|
|
||||||
if control_template is None:
|
|
||||||
control_template = 'projector/default_control_slidemodel.html'
|
|
||||||
|
|
||||||
category = model.__module__.split('.')[0]
|
category = model.__module__.split('.')[0]
|
||||||
SLIDE[model_name] = Slide(
|
SLIDE[model_name] = Slide(model_slide=True, model=model, category=category,
|
||||||
model_slide=True,
|
key=model.prefix, model_name=model_name,
|
||||||
model=model,
|
control_template=control_template, weight=weight)
|
||||||
category=category,
|
|
||||||
key=model.prefix,
|
|
||||||
model_name=model_name,
|
|
||||||
control_template=control_template,
|
|
||||||
weight=weight,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def register_slidefunc(key, func, control_template=None, weight=0, name=''):
|
def register_slidefunc(key, func, control_template=None, weight=0, name=''):
|
||||||
@ -117,15 +115,9 @@ def register_slidefunc(key, func, control_template=None, weight=0, name=''):
|
|||||||
if control_template is None:
|
if control_template is None:
|
||||||
control_template = 'projector/default_control_slidefunc.html'
|
control_template = 'projector/default_control_slidefunc.html'
|
||||||
category = func.__module__.split('.')[0]
|
category = func.__module__.split('.')[0]
|
||||||
SLIDE[key] = Slide(
|
SLIDE[key] = Slide(model_slide=False, func=func, category=category,
|
||||||
model_slide=False,
|
key=key, control_template=control_template, weight=weight,
|
||||||
func=func,
|
name=name,)
|
||||||
category=category,
|
|
||||||
key=key,
|
|
||||||
control_template=control_template,
|
|
||||||
weight=weight,
|
|
||||||
name=name,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def projector_message_set(message, sid=None):
|
def projector_message_set(message, sid=None):
|
||||||
@ -139,7 +131,7 @@ def projector_message_set(message, sid=None):
|
|||||||
overlay = ProjectorOverlay.objects.get(def_name='Message')
|
overlay = ProjectorOverlay.objects.get(def_name='Message')
|
||||||
except ProjectorOverlay.DoesNotExist:
|
except ProjectorOverlay.DoesNotExist:
|
||||||
overlay = ProjectorOverlay(def_name='Message', active=False)
|
overlay = ProjectorOverlay(def_name='Message', active=False)
|
||||||
overlay.sid=sid
|
overlay.sid = sid
|
||||||
overlay.save()
|
overlay.save()
|
||||||
|
|
||||||
|
|
||||||
@ -158,7 +150,6 @@ def get_all_widgets(request, session=False):
|
|||||||
mod = import_module(app + '.views')
|
mod = import_module(app + '.views')
|
||||||
except ImportError:
|
except ImportError:
|
||||||
continue
|
continue
|
||||||
appname = mod.__name__.split('.')[0]
|
|
||||||
try:
|
try:
|
||||||
modul_widgets = mod.get_widgets(request)
|
modul_widgets = mod.get_widgets(request)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from openslides.utils.forms import CssClassMixin
|
from openslides.utils.forms import CssClassMixin
|
||||||
|
|
||||||
|
@ -19,9 +19,6 @@ from openslides.config.signals import default_config_value
|
|||||||
from openslides.projector.api import register_slidemodel
|
from openslides.projector.api import register_slidemodel
|
||||||
from openslides.projector.projector import SlideMixin
|
from openslides.projector.projector import SlideMixin
|
||||||
|
|
||||||
from openslides.config.models import config
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectorSlide(models.Model, SlideMixin):
|
class ProjectorSlide(models.Model, SlideMixin):
|
||||||
"""
|
"""
|
||||||
@ -31,6 +28,7 @@ class ProjectorSlide(models.Model, SlideMixin):
|
|||||||
|
|
||||||
title = models.CharField(max_length=256, verbose_name=_("Title"))
|
title = models.CharField(max_length=256, verbose_name=_("Title"))
|
||||||
text = models.TextField(null=True, blank=True, verbose_name=_("Text"))
|
text = models.TextField(null=True, blank=True, verbose_name=_("Text"))
|
||||||
|
weight = models.IntegerField(default=0, verbose_name=_("Weight"))
|
||||||
|
|
||||||
def slide(self):
|
def slide(self):
|
||||||
return {
|
return {
|
||||||
@ -55,8 +53,7 @@ class ProjectorSlide(models.Model, SlideMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
register_slidemodel(ProjectorSlide,
|
register_slidemodel(ProjectorSlide, control_template='projector/control_customslide.html')
|
||||||
control_template='projector/control_customslide.html')
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectorOverlay(models.Model):
|
class ProjectorOverlay(models.Model):
|
||||||
|
@ -19,9 +19,9 @@ from openslides.config.models import config
|
|||||||
|
|
||||||
from openslides.projector.signals import projector_overlays
|
from openslides.projector.signals import projector_overlays
|
||||||
|
|
||||||
|
|
||||||
SLIDE = {}
|
SLIDE = {}
|
||||||
|
|
||||||
|
|
||||||
class SlideMixin(object):
|
class SlideMixin(object):
|
||||||
"""
|
"""
|
||||||
A Mixin for a Django-Model, for making the model a slide.
|
A Mixin for a Django-Model, for making the model a slide.
|
||||||
@ -49,15 +49,24 @@ class SlideMixin(object):
|
|||||||
"""
|
"""
|
||||||
Return True, if the the slide is the active slide.
|
Return True, if the the slide is the active slide.
|
||||||
"""
|
"""
|
||||||
from api import get_active_slide
|
if self.id is None:
|
||||||
|
return False
|
||||||
|
from openslides.projector.api import get_active_slide
|
||||||
return get_active_slide(only_sid=True) == self.sid
|
return get_active_slide(only_sid=True) == self.sid
|
||||||
|
|
||||||
def set_active(self):
|
def set_active(self):
|
||||||
"""
|
"""
|
||||||
Appoint this item as the active slide.
|
Appoint this item as the active slide.
|
||||||
"""
|
"""
|
||||||
|
from openslides.projector.api import set_active_slide
|
||||||
set_active_slide(self.sid)
|
set_active_slide(self.sid)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if self.active:
|
||||||
|
from api import clear_projector_cache
|
||||||
|
clear_projector_cache()
|
||||||
|
return super(SlideMixin, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Slide(object):
|
class Slide(object):
|
||||||
"""
|
"""
|
||||||
@ -133,6 +142,9 @@ class Widget(object):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return unicode(self.display_name)
|
return unicode(self.display_name)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return unicode(self.display_name)
|
||||||
|
|
||||||
|
|
||||||
@receiver(projector_overlays, dispatch_uid="projector_countdown")
|
@receiver(projector_overlays, dispatch_uid="projector_countdown")
|
||||||
def countdown(sender, **kwargs):
|
def countdown(sender, **kwargs):
|
||||||
|
@ -12,6 +12,7 @@ body{
|
|||||||
font-family: 'Lucida Grande',"Trebuchet MS",Verdana,sans-serif;
|
font-family: 'Lucida Grande',"Trebuchet MS",Verdana,sans-serif;
|
||||||
font-size : 20px;
|
font-size : 20px;
|
||||||
background-color: #FAFAFB;
|
background-color: #FAFAFB;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** HEADER ***/
|
/*** HEADER ***/
|
||||||
@ -154,6 +155,10 @@ body{
|
|||||||
font-weight:bold;
|
font-weight:bold;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
.item_fullscreen span
|
||||||
|
{
|
||||||
|
font-size: 50%; font-weight:normal;
|
||||||
|
}
|
||||||
|
|
||||||
/* items in a list*/
|
/* items in a list*/
|
||||||
.itemlist li
|
.itemlist li
|
||||||
|
@ -7,8 +7,13 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
{% if slide.text %}
|
{% if slide.text %}
|
||||||
<h1>{{ slide.title }}</h1>
|
<h1>{{ slide.title }}</h1>
|
||||||
{{ slide.text|safe|linebreaks }}
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="item_fullscreen">{{ slide.title }}</div>
|
<div class="item_fullscreen">{{ slide.title }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scrollcontent %}
|
||||||
|
{% if slide.text %}
|
||||||
|
<span>{{ slide.text|safe|linebreaks }}</span>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
@ -20,27 +20,6 @@
|
|||||||
<a href="{% url projector_select_widgets %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Manage widgets' %}"><i class="icon-th-large"></i> {% trans 'Widgets' %}</a>
|
<a href="{% url projector_select_widgets %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Manage widgets' %}"><i class="icon-th-large"></i> {% trans 'Widgets' %}</a>
|
||||||
</small>
|
</small>
|
||||||
</h1>
|
</h1>
|
||||||
{% if perms.projector.can_manage_projector %}
|
|
||||||
<div style="text-align: right; padding: 0 10px 5px 0; margin-top:-20px;">
|
|
||||||
<!-- control projector view -->
|
|
||||||
{% trans "Adjust projector view" %}:
|
|
||||||
<a class="projector_edit" href="{% url projector_bigger %}" title="{% trans 'Zoom in' %}">
|
|
||||||
<img src="{% static 'images/icons/zoom-in.png' %}" />
|
|
||||||
</a>
|
|
||||||
<a class="projector_edit" href="{% url projector_smaller %}" title="{% trans 'Zoom out' %}">
|
|
||||||
<img src="{% static 'images/icons/zoom-out.png' %}" />
|
|
||||||
</a>
|
|
||||||
<a class="projector_edit" href="{% url projector_up %}" title="{% trans 'Scroll text up' %}">
|
|
||||||
<img src="{% static 'images/icons/go-up.png' %}" />
|
|
||||||
</a>
|
|
||||||
<a class="projector_edit" href="{% url projector_down %}" title="{% trans 'Scroll text down' %}">
|
|
||||||
<img src="{% static 'images/icons/go-down.png' %}" />
|
|
||||||
</a>
|
|
||||||
<a class="projector_edit" href="{% url projector_clean %}" title="{% trans 'Reset projector view' %}">
|
|
||||||
<img src="{% static 'images/icons/view-reset.png' %}" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="column" id="col1">
|
<div class="column" id="col1">
|
||||||
{% for name, widget in widgets.items %}
|
{% for name, widget in widgets.items %}
|
||||||
|
@ -4,6 +4,5 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{% get_config 'frontpage_title' %}</h1>
|
<div class="item_fullscreen">{% get_config 'welcome_title' %}</div>
|
||||||
{% get_config 'frontpage_welcometext' %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,6 +1,35 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load tags %}
|
{% load tags %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
|
<!-- projector control buttons -->
|
||||||
|
{% if perms.projector.can_manage_projector %}
|
||||||
|
<div style="float: right;">
|
||||||
|
<p>
|
||||||
|
<a class="projector_edit" href="{% url projector_bigger %}" title="{% trans 'Zoom in' %}">
|
||||||
|
<img src="{% static 'images/icons/zoom-in.png' %}" />
|
||||||
|
</a><br>
|
||||||
|
<a class="projector_edit" href="{% url projector_smaller %}" title="{% trans 'Zoom out' %}">
|
||||||
|
<img src="{% static 'images/icons/zoom-out.png' %}" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a class="projector_edit" href="{% url projector_up %}" title="{% trans 'Scroll text up' %}">
|
||||||
|
<img src="{% static 'images/icons/go-up.png' %}" />
|
||||||
|
</a><br>
|
||||||
|
<a class="projector_edit" href="{% url projector_down %}" title="{% trans 'Scroll text down' %}">
|
||||||
|
<img src="{% static 'images/icons/go-down.png' %}" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a class="projector_edit" href="{% url projector_clean %}" title="{% trans 'Reset projector view' %}">
|
||||||
|
<img src="{% static 'images/icons/view-reset.png' %}" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- projector live view -->
|
||||||
<a href="{% url projector_show %}" target="_blank">
|
<a href="{% url projector_show %}" target="_blank">
|
||||||
<div id="iframewrapper">
|
<div id="iframewrapper">
|
||||||
<iframe id="iframe" src="{% url projector_show %}" frameborder="0"></iframe>
|
<iframe id="iframe" src="{% url projector_show %}" frameborder="0"></iframe>
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
{% load tags %}
|
||||||
|
|
||||||
|
{% if welcometext %}
|
||||||
|
<p>{{ welcometext|safe|linebreaks }}</p>
|
||||||
|
{% endif %}
|
@ -13,31 +13,28 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
from django.core.cache import cache
|
||||||
from django.core.context_processors import csrf
|
from django.core.context_processors import csrf
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.dispatch import receiver
|
from django.shortcuts import redirect
|
||||||
from django.utils.datastructures import SortedDict
|
from django.template import RequestContext
|
||||||
from django.utils.importlib import import_module
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from openslides.utils.template import render_block_to_string, Tab
|
from openslides.utils.template import render_block_to_string, Tab
|
||||||
from openslides.utils.utils import html_strong
|
from openslides.utils.views import (
|
||||||
from openslides.utils.views import (TemplateView, RedirectView, CreateView,
|
TemplateView, RedirectView, CreateView, UpdateView, DeleteView, AjaxMixin)
|
||||||
UpdateView, DeleteView, AjaxMixin)
|
|
||||||
|
|
||||||
from openslides.config.models import config
|
from openslides.config.models import config
|
||||||
|
from .api import (
|
||||||
from openslides.projector.api import (get_active_slide, set_active_slide,
|
get_active_slide, set_active_slide, projector_message_set,
|
||||||
projector_message_set, projector_message_delete, get_slide_from_sid,
|
projector_message_delete, get_slide_from_sid, get_all_widgets,
|
||||||
get_all_widgets)
|
clear_projector_cache)
|
||||||
from openslides.projector.forms import SelectWidgetsForm
|
from .forms import SelectWidgetsForm
|
||||||
from openslides.projector.models import ProjectorOverlay, ProjectorSlide
|
from .models import ProjectorOverlay, ProjectorSlide
|
||||||
from openslides.projector.projector import SLIDE, Widget
|
from .projector import Widget
|
||||||
from openslides.projector.signals import projector_overlays
|
from .signals import projector_overlays
|
||||||
|
|
||||||
|
|
||||||
class DashboardView(TemplateView, AjaxMixin):
|
class DashboardView(TemplateView, AjaxMixin):
|
||||||
@ -70,9 +67,10 @@ class Projector(TemplateView, AjaxMixin):
|
|||||||
if sid is None:
|
if sid is None:
|
||||||
try:
|
try:
|
||||||
data = get_active_slide()
|
data = get_active_slide()
|
||||||
except AttributeError: #TODO: It has to be an Slide.DoesNotExist
|
except AttributeError: # TODO: It has to be an Slide.DoesNotExist
|
||||||
data = None
|
data = None
|
||||||
ajax = 'on'
|
ajax = 'on'
|
||||||
|
active_sid = get_active_slide(True)
|
||||||
else:
|
else:
|
||||||
data = get_slide_from_sid(sid)
|
data = get_slide_from_sid(sid)
|
||||||
ajax = 'off'
|
ajax = 'off'
|
||||||
@ -88,10 +86,10 @@ class Projector(TemplateView, AjaxMixin):
|
|||||||
# Projector Overlays
|
# Projector Overlays
|
||||||
if self.kwargs['sid'] is None:
|
if self.kwargs['sid'] is None:
|
||||||
active_defs = ProjectorOverlay.objects.filter(active=True) \
|
active_defs = ProjectorOverlay.objects.filter(active=True) \
|
||||||
.filter(Q(sid=sid) | Q(sid=None)).values_list('def_name',
|
.filter(Q(sid=active_sid) | Q(sid=None)).values_list(
|
||||||
flat=True)
|
'def_name', flat=True)
|
||||||
for receiver, response in projector_overlays.send(sender=sid,
|
for receiver, response in projector_overlays.send(
|
||||||
register=False, call=active_defs):
|
sender=sid, register=False, call=active_defs):
|
||||||
if response is not None:
|
if response is not None:
|
||||||
data['overlays'].append(response)
|
data['overlays'].append(response)
|
||||||
self._data = data
|
self._data = data
|
||||||
@ -106,19 +104,36 @@ class Projector(TemplateView, AjaxMixin):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
def get_ajax_context(self, **kwargs):
|
def get_ajax_context(self, **kwargs):
|
||||||
content = render_block_to_string(self.get_template_names()[0],
|
content = cache.get('projector_content')
|
||||||
|
if not content:
|
||||||
|
content = render_block_to_string(
|
||||||
|
self.get_template_names()[0],
|
||||||
'content', self.data)
|
'content', self.data)
|
||||||
scrollcontent = render_block_to_string(self.get_template_names()[0],
|
cache.set('projector_content', content, 1)
|
||||||
'scrollcontent', self.data)
|
|
||||||
|
|
||||||
|
scrollcontent = cache.get('projector_scrollcontent')
|
||||||
|
if not scrollcontent:
|
||||||
|
scrollcontent = render_block_to_string(
|
||||||
|
self.get_template_names()[0],
|
||||||
|
'scrollcontent', self.data)
|
||||||
|
cache.set('projector_scrollcontent', scrollcontent, 1)
|
||||||
|
|
||||||
|
# TODO: do not call the hole data-methode, if we only need some vars
|
||||||
|
data = cache.get('projector_data')
|
||||||
|
if not data:
|
||||||
|
data = self.data
|
||||||
|
cache.set('projector_data', data)
|
||||||
|
# clear cache if countdown is enabled
|
||||||
|
if config['countdown_state'] == 'active':
|
||||||
|
clear_projector_cache()
|
||||||
context = super(Projector, self).get_ajax_context(**kwargs)
|
context = super(Projector, self).get_ajax_context(**kwargs)
|
||||||
content_hash = hash(content)
|
content_hash = hash(content)
|
||||||
context.update({
|
context.update({
|
||||||
'content': content,
|
'content': content,
|
||||||
'scrollcontent': scrollcontent,
|
'scrollcontent': scrollcontent,
|
||||||
'time': datetime.now().strftime('%H:%M'),
|
'time': datetime.now().strftime('%H:%M'),
|
||||||
'overlays': self.data['overlays'],
|
'overlays': data['overlays'],
|
||||||
'title': self.data['title'],
|
'title': data['title'],
|
||||||
'bigger': config['bigger'],
|
'bigger': config['bigger'],
|
||||||
'up': config['up'],
|
'up': config['up'],
|
||||||
'content_hash': content_hash,
|
'content_hash': content_hash,
|
||||||
@ -186,8 +201,7 @@ class SelectWidgetsView(TemplateView):
|
|||||||
else:
|
else:
|
||||||
transaction.commit()
|
transaction.commit()
|
||||||
self.request.session['widgets'] = activated_widgets
|
self.request.session['widgets'] = activated_widgets
|
||||||
return self.render_to_response(context)
|
return redirect(reverse('dashboard'))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectorEdit(RedirectView):
|
class ProjectorEdit(RedirectView):
|
||||||
@ -259,6 +273,7 @@ class CountdownEdit(RedirectView):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def get_ajax_context(self, **kwargs):
|
def get_ajax_context(self, **kwargs):
|
||||||
|
clear_projector_cache()
|
||||||
return {
|
return {
|
||||||
'state': config['countdown_state'],
|
'state': config['countdown_state'],
|
||||||
'countdown_time': config['countdown_time'],
|
'countdown_time': config['countdown_time'],
|
||||||
@ -279,14 +294,13 @@ class OverlayMessageView(RedirectView):
|
|||||||
elif 'message-clean' in request.POST:
|
elif 'message-clean' in request.POST:
|
||||||
projector_message_delete()
|
projector_message_delete()
|
||||||
|
|
||||||
|
|
||||||
def get_ajax_context(self, **kwargs):
|
def get_ajax_context(self, **kwargs):
|
||||||
|
clear_projector_cache()
|
||||||
return {
|
return {
|
||||||
'overlay_message': config['projector_message'],
|
'overlay_message': config['projector_message'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ActivateOverlay(RedirectView):
|
class ActivateOverlay(RedirectView):
|
||||||
"""
|
"""
|
||||||
Activate or deactivate an overlay.
|
Activate or deactivate an overlay.
|
||||||
@ -312,6 +326,7 @@ class ActivateOverlay(RedirectView):
|
|||||||
self.overlay.save()
|
self.overlay.save()
|
||||||
|
|
||||||
def get_ajax_context(self, **kwargs):
|
def get_ajax_context(self, **kwargs):
|
||||||
|
clear_projector_cache()
|
||||||
return {
|
return {
|
||||||
'active': self.overlay.active,
|
'active': self.overlay.active,
|
||||||
'def_name': self.overlay.def_name,
|
'def_name': self.overlay.def_name,
|
||||||
@ -355,13 +370,12 @@ def register_tab(request):
|
|||||||
"""
|
"""
|
||||||
Register the projector tab.
|
Register the projector tab.
|
||||||
"""
|
"""
|
||||||
selected = True if request.path.startswith('/projector/') else False
|
selected = request.path.startswith('/projector/')
|
||||||
return Tab(
|
return Tab(
|
||||||
title=_('Dashboard'),
|
title=_('Dashboard'),
|
||||||
app='dashboard',
|
app='dashboard',
|
||||||
url=reverse('dashboard'),
|
url=reverse('dashboard'),
|
||||||
permission=request.user.has_perm('projector.can_manage_projector') or
|
permission=request.user.has_perm('projector.can_see_dashboard'),
|
||||||
request.user.has_perm('projector.can_see_dashboard'),
|
|
||||||
selected=selected,
|
selected=selected,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -372,11 +386,23 @@ def get_widgets(request):
|
|||||||
"""
|
"""
|
||||||
widgets = []
|
widgets = []
|
||||||
|
|
||||||
|
# welcome widget
|
||||||
|
context = {
|
||||||
|
'welcometext': config['welcome_text']}
|
||||||
|
widgets.append(Widget(
|
||||||
|
name='welcome',
|
||||||
|
display_name=config['welcome_title'],
|
||||||
|
template='projector/welcome_widget.html',
|
||||||
|
context=context,
|
||||||
|
permission_required='projector.can_see_dashboard',
|
||||||
|
default_column=1))
|
||||||
|
|
||||||
# Projector live view widget
|
# Projector live view widget
|
||||||
widgets.append(Widget(
|
widgets.append(Widget(
|
||||||
name='live_view',
|
name='live_view',
|
||||||
display_name=_('Projector live view'),
|
display_name=_('Projector live view'),
|
||||||
template='projector/live_view_widget.html',
|
template='projector/live_view_widget.html',
|
||||||
|
context=RequestContext(request, {}),
|
||||||
permission_required='projector.can_see_projector',
|
permission_required='projector.can_see_projector',
|
||||||
default_column=2))
|
default_column=2))
|
||||||
|
|
||||||
@ -389,15 +415,14 @@ def get_widgets(request):
|
|||||||
projector_overlay = ProjectorOverlay.objects.get(
|
projector_overlay = ProjectorOverlay.objects.get(
|
||||||
def_name=name)
|
def_name=name)
|
||||||
except ProjectorOverlay.DoesNotExist:
|
except ProjectorOverlay.DoesNotExist:
|
||||||
projector_overlay = ProjectorOverlay(def_name=name,
|
projector_overlay = ProjectorOverlay(def_name=name, active=False)
|
||||||
active=False)
|
|
||||||
projector_overlay.save()
|
projector_overlay.save()
|
||||||
overlays.append(projector_overlay)
|
overlays.append(projector_overlay)
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'overlays':overlays,
|
'overlays': overlays,
|
||||||
'countdown_time': config['countdown_time'],
|
'countdown_time': config['countdown_time'],
|
||||||
'countdown_state' : config['countdown_state']}
|
'countdown_state': config['countdown_state']}
|
||||||
context.update(csrf(request))
|
context.update(csrf(request))
|
||||||
widgets.append(Widget(
|
widgets.append(Widget(
|
||||||
name='overlays',
|
name='overlays',
|
||||||
@ -407,10 +432,9 @@ def get_widgets(request):
|
|||||||
default_column=2,
|
default_column=2,
|
||||||
context=context))
|
context=context))
|
||||||
|
|
||||||
|
|
||||||
# Custom slide widget
|
# Custom slide widget
|
||||||
context = {
|
context = {
|
||||||
'slides': ProjectorSlide.objects.all(),
|
'slides': ProjectorSlide.objects.all().order_by('weight'),
|
||||||
'welcomepage_is_active': not bool(config["presentation"])}
|
'welcomepage_is_active': not bool(config["presentation"])}
|
||||||
widgets.append(Widget(
|
widgets.append(Widget(
|
||||||
name='custom_slide',
|
name='custom_slide',
|
||||||
|
BIN
openslides/static/images/icons/remove.png
Normal file
BIN
openslides/static/images/icons/remove.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 332 B |
@ -94,7 +94,7 @@
|
|||||||
<hr>
|
<hr>
|
||||||
<footer>
|
<footer>
|
||||||
<small>
|
<small>
|
||||||
© Copyright 2012 | Powered by <a href="http://openslides.org" target="_blank">OpenSlides</a>
|
© Copyright 2011-2012 | Powered by <a href="http://openslides.org" target="_blank">OpenSlides</a> | {% trans "Get <a href='http://openslides.org/support' target='_blank'>professional support</a> for OpenSlides." %}
|
||||||
</small>
|
</small>
|
||||||
</footer>
|
</footer>
|
||||||
</div><!--/content-->
|
</div><!--/content-->
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
{% load staticfiles %}
|
|
||||||
|
|
||||||
{% block title %}{{ block.super }} – {% trans "Home" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>{{ title }}</h1>
|
|
||||||
<p>{{ welcometext|safe|linebreaks }}</p>
|
|
||||||
|
|
||||||
{% trans "You have access to the following pages:" %}
|
|
||||||
<ul>
|
|
||||||
{% for app in apps %}
|
|
||||||
<li><a href="{{ app.url }}">{{ app.title }}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endblock %}
|
|
@ -12,21 +12,18 @@
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.defaults import patterns, url, include
|
from django.conf.urls.defaults import patterns, url, include
|
||||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
|
||||||
from django.shortcuts import redirect
|
|
||||||
from django.utils.importlib import import_module
|
from django.utils.importlib import import_module
|
||||||
|
|
||||||
from openslides.utils.views import FrontPage
|
from openslides.utils.views import RedirectView
|
||||||
|
|
||||||
|
|
||||||
handler500 = 'openslides.utils.views.server_error'
|
handler500 = 'openslides.utils.views.server_error'
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
# frontpage
|
# Redirect to dashboard URL
|
||||||
(r'^$', FrontPage.as_view()),
|
url(r'^$', RedirectView.as_view(url='dashboard'), name='home',),
|
||||||
|
|
||||||
(r'^agenda/', include('openslides.agenda.urls')),
|
(r'^agenda/', include('openslides.agenda.urls')),
|
||||||
(r'^application/', include('openslides.application.urls')),
|
(r'^motion/', include('openslides.motion.urls')),
|
||||||
(r'^assignment/', include('openslides.assignment.urls')),
|
(r'^assignment/', include('openslides.assignment.urls')),
|
||||||
(r'^participant/', include('openslides.participant.urls')),
|
(r'^participant/', include('openslides.participant.urls')),
|
||||||
(r'^config/', include('openslides.config.urls')),
|
(r'^config/', include('openslides.config.urls')),
|
||||||
@ -35,7 +32,7 @@ urlpatterns = patterns('',
|
|||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns += patterns('django.contrib.staticfiles.views',
|
urlpatterns += patterns('django.contrib.staticfiles.views',
|
||||||
url(r'^static/(?P<path>.*)$', 'serve', {'insecure':True}),
|
url(r'^static/(?P<path>.*)$', 'serve', {'insecure': True}),
|
||||||
)
|
)
|
||||||
|
|
||||||
js_info_dict = {
|
js_info_dict = {
|
||||||
|
@ -37,8 +37,8 @@ class AnonymousAuth(object):
|
|||||||
|
|
||||||
- try to return the permissions for the 'Anonymous' group
|
- try to return the permissions for the 'Anonymous' group
|
||||||
"""
|
"""
|
||||||
if not user_obj.is_anonymous() or obj is not None or \
|
if (not user_obj.is_anonymous() or obj is not None or
|
||||||
not config['system_enable_anonymous']:
|
not config['system_enable_anonymous']):
|
||||||
return set()
|
return set()
|
||||||
|
|
||||||
perms = Permission.objects.filter(group__name='Anonymous')
|
perms = Permission.objects.filter(group__name='Anonymous')
|
||||||
@ -60,8 +60,8 @@ class AnonymousAuth(object):
|
|||||||
"""
|
"""
|
||||||
Check if the user as a specific permission
|
Check if the user as a specific permission
|
||||||
"""
|
"""
|
||||||
if not user_obj.is_anonymous() or obj is not None or \
|
if (not user_obj.is_anonymous() or obj is not None or
|
||||||
not config['system_enable_anonymous']:
|
not config['system_enable_anonymous']):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return (perm in self.get_all_permissions(user_obj))
|
return (perm in self.get_all_permissions(user_obj))
|
||||||
@ -70,8 +70,8 @@ class AnonymousAuth(object):
|
|||||||
"""
|
"""
|
||||||
Check if the user has permissions on the module app_label
|
Check if the user has permissions on the module app_label
|
||||||
"""
|
"""
|
||||||
if not user_obj.is_anonymous() or \
|
if (not user_obj.is_anonymous() or
|
||||||
not config['system_enable_anonymous']:
|
not config['system_enable_anonymous']):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
for perm in self.get_all_permissions(user_obj):
|
for perm in self.get_all_permissions(user_obj):
|
||||||
@ -87,10 +87,10 @@ class AnonymousAuth(object):
|
|||||||
"""
|
"""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def anonymous_context_additions(RequestContext):
|
def anonymous_context_additions(RequestContext):
|
||||||
"""
|
"""
|
||||||
Add a variable to the request context that will indicate
|
Add a variable to the request context that will indicate
|
||||||
if anonymous login is possible at all.
|
if anonymous login is possible at all.
|
||||||
"""
|
"""
|
||||||
return {'os_enable_anonymous_login' : config['system_enable_anonymous']}
|
return {'os_enable_anonymous_login': config['system_enable_anonymous']}
|
||||||
|
|
||||||
|
@ -31,4 +31,3 @@ def patchup(dialect):
|
|||||||
return dialect
|
return dialect
|
||||||
|
|
||||||
register_dialect("excel_semikolon", excel_semikolon)
|
register_dialect("excel_semikolon", excel_semikolon)
|
||||||
|
|
||||||
|
@ -1 +1,3 @@
|
|||||||
from fields import JSONField
|
from fields import JSONField
|
||||||
|
|
||||||
|
__all__ = ['JSONField']
|
||||||
|
@ -6,6 +6,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
from django.forms.fields import Field
|
from django.forms.fields import Field
|
||||||
from django.forms.util import ValidationError as FormValidationError
|
from django.forms.util import ValidationError as FormValidationError
|
||||||
|
|
||||||
|
|
||||||
class JSONFormField(Field):
|
class JSONFormField(Field):
|
||||||
def clean(self, value):
|
def clean(self, value):
|
||||||
|
|
||||||
@ -21,6 +22,7 @@ class JSONFormField(Field):
|
|||||||
raise FormValidationError(_("Enter valid JSON"))
|
raise FormValidationError(_("Enter valid JSON"))
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
class JSONField(models.TextField):
|
class JSONField(models.TextField):
|
||||||
"""JSONField is a generic textfield that serializes/unserializes JSON objects"""
|
"""JSONField is a generic textfield that serializes/unserializes JSON objects"""
|
||||||
|
|
||||||
|
@ -1,108 +0,0 @@
|
|||||||
from django.db import models
|
|
||||||
from django.test import TestCase
|
|
||||||
from django.utils import simplejson as json
|
|
||||||
|
|
||||||
from fields import JSONField
|
|
||||||
|
|
||||||
|
|
||||||
class JsonModel(models.Model):
|
|
||||||
json = JSONField()
|
|
||||||
|
|
||||||
|
|
||||||
class ComplexEncoder(json.JSONEncoder):
|
|
||||||
def default(self, obj):
|
|
||||||
if isinstance(obj, complex):
|
|
||||||
return {
|
|
||||||
'__complex__': True,
|
|
||||||
'real': obj.real,
|
|
||||||
'imag': obj.imag,
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.JSONEncoder.default(self, obj)
|
|
||||||
|
|
||||||
|
|
||||||
def as_complex(dct):
|
|
||||||
if '__complex__' in dct:
|
|
||||||
return complex(dct['real'], dct['imag'])
|
|
||||||
return dct
|
|
||||||
|
|
||||||
|
|
||||||
class JSONModelCustomEncoders(models.Model):
|
|
||||||
# A JSON field that can store complex numbers
|
|
||||||
json = JSONField(
|
|
||||||
dump_kwargs={'cls': ComplexEncoder},
|
|
||||||
load_kwargs={'object_hook': as_complex},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class JSONFieldTest(TestCase):
|
|
||||||
"""JSONField Wrapper Tests"""
|
|
||||||
|
|
||||||
def test_json_field_create(self):
|
|
||||||
"""Test saving a JSON object in our JSONField"""
|
|
||||||
|
|
||||||
json_obj = {
|
|
||||||
"item_1": "this is a json blah",
|
|
||||||
"blergh": "hey, hey, hey"}
|
|
||||||
|
|
||||||
obj = JsonModel.objects.create(json=json_obj)
|
|
||||||
new_obj = JsonModel.objects.get(id=obj.id)
|
|
||||||
|
|
||||||
self.failUnlessEqual(new_obj.json, json_obj)
|
|
||||||
|
|
||||||
def test_json_field_modify(self):
|
|
||||||
"""Test modifying a JSON object in our JSONField"""
|
|
||||||
|
|
||||||
json_obj_1 = {'a': 1, 'b': 2}
|
|
||||||
json_obj_2 = {'a': 3, 'b': 4}
|
|
||||||
|
|
||||||
obj = JsonModel.objects.create(json=json_obj_1)
|
|
||||||
|
|
||||||
self.failUnlessEqual(obj.json, json_obj_1)
|
|
||||||
|
|
||||||
obj.json = json_obj_2
|
|
||||||
|
|
||||||
self.failUnlessEqual(obj.json, json_obj_2)
|
|
||||||
|
|
||||||
obj.save()
|
|
||||||
|
|
||||||
self.failUnlessEqual(obj.json, json_obj_2)
|
|
||||||
|
|
||||||
self.assert_(obj)
|
|
||||||
|
|
||||||
def test_json_field_load(self):
|
|
||||||
"""Test loading a JSON object from the DB"""
|
|
||||||
|
|
||||||
json_obj_1 = {'a': 1, 'b': 2}
|
|
||||||
|
|
||||||
obj = JsonModel.objects.create(json=json_obj_1)
|
|
||||||
|
|
||||||
new_obj = JsonModel.objects.get(id=obj.id)
|
|
||||||
|
|
||||||
self.failUnlessEqual(new_obj.json, json_obj_1)
|
|
||||||
|
|
||||||
def test_json_list(self):
|
|
||||||
"""Test storing a JSON list"""
|
|
||||||
|
|
||||||
json_obj = ["my", "list", "of", 1, "objs", {"hello": "there"}]
|
|
||||||
|
|
||||||
obj = JsonModel.objects.create(json=json_obj)
|
|
||||||
new_obj = JsonModel.objects.get(id=obj.id)
|
|
||||||
self.failUnlessEqual(new_obj.json, json_obj)
|
|
||||||
|
|
||||||
def test_empty_objects(self):
|
|
||||||
"""Test storing empty objects"""
|
|
||||||
|
|
||||||
for json_obj in [{}, [], 0, '', False]:
|
|
||||||
obj = JsonModel.objects.create(json=json_obj)
|
|
||||||
new_obj = JsonModel.objects.get(id=obj.id)
|
|
||||||
self.failUnlessEqual(json_obj, obj.json)
|
|
||||||
self.failUnlessEqual(json_obj, new_obj.json)
|
|
||||||
|
|
||||||
def test_custom_encoder(self):
|
|
||||||
"""Test encoder_cls and object_hook"""
|
|
||||||
value = 1 + 3j # A complex number
|
|
||||||
|
|
||||||
obj = JSONModelCustomEncoders.objects.create(json=value)
|
|
||||||
new_obj = JSONModelCustomEncoders.objects.get(pk=obj.pk)
|
|
||||||
self.failUnlessEqual(value, new_obj.json)
|
|
@ -12,12 +12,13 @@
|
|||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
class MinMaxIntegerField(models.IntegerField):
|
class MinMaxIntegerField(models.IntegerField):
|
||||||
def __init__(self, min_value=None, max_value=None, *args, **kwargs):
|
def __init__(self, min_value=None, max_value=None, *args, **kwargs):
|
||||||
self.min_value, self.max_value = min_value, max_value
|
self.min_value, self.max_value = min_value, max_value
|
||||||
super(MinMaxIntegerField, self).__init__(*args, **kwargs)
|
super(MinMaxIntegerField, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def formfield(self, **kwargs):
|
def formfield(self, **kwargs):
|
||||||
defaults = {'min_value': self.min_value, 'max_value' : self.max_value}
|
defaults = {'min_value': self.min_value, 'max_value': self.max_value}
|
||||||
defaults.update(kwargs)
|
defaults.update(kwargs)
|
||||||
return super(MinMaxIntegerField, self).formfield(**defaults)
|
return super(MinMaxIntegerField, self).formfield(**defaults)
|
||||||
|
@ -20,6 +20,7 @@ from reportlab.pdfbase.ttfonts import TTFont
|
|||||||
from reportlab.rl_config import defaultPageSize
|
from reportlab.rl_config import defaultPageSize
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.utils import formats
|
||||||
# Import gettext for python 2.5 support
|
# Import gettext for python 2.5 support
|
||||||
from django.utils.translation import ugettext as _, gettext
|
from django.utils.translation import ugettext as _, gettext
|
||||||
|
|
||||||
@ -27,16 +28,16 @@ from openslides.config.models import config
|
|||||||
|
|
||||||
|
|
||||||
# register new truetype fonts
|
# register new truetype fonts
|
||||||
pdfmetrics.registerFont(TTFont('Ubuntu', path_join(settings.SITE_ROOT,
|
pdfmetrics.registerFont(TTFont(
|
||||||
'static/fonts/Ubuntu-R.ttf')))
|
'Ubuntu', path_join(settings.SITE_ROOT, 'static/fonts/Ubuntu-R.ttf')))
|
||||||
pdfmetrics.registerFont(TTFont('Ubuntu-Bold', path_join(settings.SITE_ROOT,
|
pdfmetrics.registerFont(TTFont(
|
||||||
'static/fonts/Ubuntu-B.ttf')))
|
'Ubuntu-Bold', path_join(settings.SITE_ROOT, 'static/fonts/Ubuntu-B.ttf')))
|
||||||
pdfmetrics.registerFont(TTFont('Ubuntu-Italic', path_join(settings.SITE_ROOT,
|
pdfmetrics.registerFont(TTFont(
|
||||||
'static/fonts/Ubuntu-RI.ttf')))
|
'Ubuntu-Italic', path_join(settings.SITE_ROOT, 'static/fonts/Ubuntu-RI.ttf')))
|
||||||
|
|
||||||
|
|
||||||
# set style information
|
# set style information
|
||||||
PAGE_HEIGHT = defaultPageSize[1];
|
PAGE_HEIGHT = defaultPageSize[1]
|
||||||
PAGE_WIDTH = defaultPageSize[0]
|
PAGE_WIDTH = defaultPageSize[0]
|
||||||
|
|
||||||
|
|
||||||
@ -104,104 +105,104 @@ stylesheet.add(ParagraphStyle(
|
|||||||
leftIndent=0,
|
leftIndent=0,
|
||||||
spaceAfter=15,
|
spaceAfter=15,
|
||||||
))
|
))
|
||||||
stylesheet.add(ParagraphStyle(name = 'Subitem',
|
stylesheet.add(ParagraphStyle(
|
||||||
parent = stylesheet['Normal'],
|
name='Subitem',
|
||||||
fontSize = 10,
|
parent=stylesheet['Normal'],
|
||||||
leading = 10,
|
fontSize=10,
|
||||||
leftIndent = 20,
|
leading=10,
|
||||||
spaceAfter = 15)
|
leftIndent=20,
|
||||||
)
|
spaceAfter=15))
|
||||||
stylesheet.add(ParagraphStyle(name = 'Tablecell',
|
stylesheet.add(ParagraphStyle(
|
||||||
parent = stylesheet['Normal'],
|
name='Tablecell',
|
||||||
fontSize = 9)
|
parent=stylesheet['Normal'],
|
||||||
)
|
fontSize=9))
|
||||||
stylesheet.add(ParagraphStyle(name = 'Signaturefield',
|
stylesheet.add(ParagraphStyle(name='Signaturefield',
|
||||||
parent = stylesheet['Normal'],
|
parent=stylesheet['Normal'],
|
||||||
spaceBefore = 15)
|
spaceBefore=15)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ballot stylesheets
|
# Ballot stylesheets
|
||||||
stylesheet.add(ParagraphStyle(name = 'Ballot_title',
|
stylesheet.add(ParagraphStyle(name='Ballot_title',
|
||||||
parent = stylesheet['Bold'],
|
parent=stylesheet['Bold'],
|
||||||
fontSize = 12,
|
fontSize=12,
|
||||||
leading = 14,
|
leading=14,
|
||||||
leftIndent = 30),
|
leftIndent=30),
|
||||||
)
|
)
|
||||||
stylesheet.add(ParagraphStyle(name = 'Ballot_subtitle',
|
stylesheet.add(ParagraphStyle(name='Ballot_subtitle',
|
||||||
parent = stylesheet['Normal'],
|
parent=stylesheet['Normal'],
|
||||||
fontSize = 10,
|
fontSize=10,
|
||||||
leading = 12,
|
leading=12,
|
||||||
leftIndent = 30,
|
leftIndent=30,
|
||||||
rightIndent = 20,
|
rightIndent=20,
|
||||||
spaceAfter = 5),
|
spaceAfter=5),
|
||||||
)
|
)
|
||||||
stylesheet.add(ParagraphStyle(name = 'Ballot_description',
|
stylesheet.add(ParagraphStyle(name='Ballot_description',
|
||||||
parent = stylesheet['Normal'],
|
parent=stylesheet['Normal'],
|
||||||
fontSize = 7,
|
fontSize=7,
|
||||||
leading = 10,
|
leading=10,
|
||||||
leftIndent = 30),
|
leftIndent=30),
|
||||||
)
|
)
|
||||||
stylesheet.add(ParagraphStyle(name = 'Ballot_option',
|
stylesheet.add(ParagraphStyle(name='Ballot_option',
|
||||||
parent = stylesheet['Normal'],
|
parent=stylesheet['Normal'],
|
||||||
fontSize = 12,
|
fontSize=12,
|
||||||
leading = 24,
|
leading=24,
|
||||||
leftIndent = 30),
|
leftIndent=30),
|
||||||
)
|
)
|
||||||
stylesheet.add(ParagraphStyle(name = 'Monotype',
|
stylesheet.add(ParagraphStyle(name='Monotype',
|
||||||
parent = stylesheet['Normal'],
|
parent=stylesheet['Normal'],
|
||||||
fontName = 'Courier',
|
fontName='Courier',
|
||||||
fontSize = 12,
|
fontSize=12,
|
||||||
leading = 24,
|
leading=24,
|
||||||
leftIndent = 30),
|
leftIndent=30),
|
||||||
)
|
)
|
||||||
stylesheet.add(ParagraphStyle(name = 'Ballot_option_name',
|
stylesheet.add(ParagraphStyle(name='Ballot_option_name',
|
||||||
parent = stylesheet['Normal'],
|
parent=stylesheet['Normal'],
|
||||||
fontSize = 12,
|
fontSize=12,
|
||||||
leading = 15,
|
leading=15,
|
||||||
leftIndent = 30),
|
leftIndent=30),
|
||||||
)
|
)
|
||||||
stylesheet.add(ParagraphStyle(name = 'Ballot_option_group',
|
stylesheet.add(ParagraphStyle(name='Ballot_option_group',
|
||||||
parent = stylesheet['Normal'],
|
parent=stylesheet['Normal'],
|
||||||
fontSize = 8,
|
fontSize=8,
|
||||||
leading = 15,
|
leading=15,
|
||||||
leftIndent = 30),
|
leftIndent=30),
|
||||||
)
|
)
|
||||||
stylesheet.add(ParagraphStyle(name = 'Ballot_option_YNA',
|
stylesheet.add(ParagraphStyle(name='Ballot_option_YNA',
|
||||||
parent = stylesheet['Normal'],
|
parent=stylesheet['Normal'],
|
||||||
fontSize = 12,
|
fontSize=12,
|
||||||
leading = 15,
|
leading=15,
|
||||||
leftIndent = 49,
|
leftIndent=49,
|
||||||
spaceAfter = 18),
|
spaceAfter=18),
|
||||||
)
|
)
|
||||||
stylesheet.add(ParagraphStyle(name = 'Ballot_option_group_right',
|
stylesheet.add(ParagraphStyle(name='Ballot_option_group_right',
|
||||||
parent = stylesheet['Normal'],
|
parent=stylesheet['Normal'],
|
||||||
fontSize = 8,
|
fontSize=8,
|
||||||
leading = 16,
|
leading=16,
|
||||||
leftIndent = 49),
|
leftIndent=49),
|
||||||
)
|
)
|
||||||
stylesheet.add(ParagraphStyle(name = 'Badge_title',
|
stylesheet.add(ParagraphStyle(name='Badge_title',
|
||||||
parent = stylesheet['Bold'],
|
parent=stylesheet['Bold'],
|
||||||
fontSize = 16,
|
fontSize=16,
|
||||||
leading = 22,
|
leading=22,
|
||||||
leftIndent = 30),
|
leftIndent=30),
|
||||||
)
|
)
|
||||||
stylesheet.add(ParagraphStyle(name = 'Badge_subtitle',
|
stylesheet.add(ParagraphStyle(name='Badge_subtitle',
|
||||||
parent = stylesheet['Normal'],
|
parent=stylesheet['Normal'],
|
||||||
fontSize = 12,
|
fontSize=12,
|
||||||
leading = 24,
|
leading=24,
|
||||||
leftIndent = 30),
|
leftIndent=30),
|
||||||
)
|
)
|
||||||
stylesheet.add(ParagraphStyle(
|
stylesheet.add(ParagraphStyle(
|
||||||
name = 'Badge_italic',
|
name='Badge_italic',
|
||||||
parent = stylesheet['Italic'],
|
parent=stylesheet['Italic'],
|
||||||
fontSize = 12,
|
fontSize=12,
|
||||||
leading = 24,
|
leading=24,
|
||||||
leftIndent = 30,
|
leftIndent=30,
|
||||||
))
|
))
|
||||||
stylesheet.add(ParagraphStyle(
|
stylesheet.add(ParagraphStyle(
|
||||||
name = 'Badge_qrcode',
|
name='Badge_qrcode',
|
||||||
fontSize = 12,
|
fontSize=12,
|
||||||
leftIndent = 190,
|
leftIndent=190,
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
@ -222,8 +223,8 @@ def firstPage(canvas, doc):
|
|||||||
|
|
||||||
# time
|
# time
|
||||||
canvas.setFont('Ubuntu', 7)
|
canvas.setFont('Ubuntu', 7)
|
||||||
time = datetime.now().strftime(gettext("%Y-%m-%d %H:%Mh"))
|
time = formats.date_format(datetime.now(), 'DATETIME_FORMAT')
|
||||||
canvas.drawString(15 * cm, 28 * cm, _("Printed: %s") % time)
|
canvas.drawString(15 * cm, 28 * cm, _("As of: %s") % time)
|
||||||
|
|
||||||
# title
|
# title
|
||||||
if doc.title:
|
if doc.title:
|
||||||
|
@ -11,12 +11,17 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from openslides.utils.person.signals import receive_persons
|
from openslides.utils.person.signals import receive_persons
|
||||||
from openslides.utils.person.api import generate_person_id, get_person, Persons
|
from openslides.utils.person.api import (
|
||||||
|
generate_person_id, get_person, Person, Persons)
|
||||||
from openslides.utils.person.forms import PersonFormField, MultiplePersonFormField
|
from openslides.utils.person.forms import PersonFormField, MultiplePersonFormField
|
||||||
from openslides.utils.person.models import PersonField, PersonMixin
|
from openslides.utils.person.models import PersonField, PersonMixin
|
||||||
|
|
||||||
|
__all__ = ['receive_persons', 'generate_person_id', 'get_person', 'Person',
|
||||||
|
'Persons', 'PersonFormField', 'MultiplePersonFormField',
|
||||||
|
'PersonField', 'PersonMixin', 'EmptyPerson']
|
||||||
|
|
||||||
class EmtyPerson(PersonMixin):
|
|
||||||
|
class EmptyPerson(PersonMixin, Person):
|
||||||
@property
|
@property
|
||||||
def person_id(self):
|
def person_id(self):
|
||||||
return 'emtyuser'
|
return 'empty'
|
||||||
|
@ -13,6 +13,45 @@
|
|||||||
from openslides.utils.person.signals import receive_persons
|
from openslides.utils.person.signals import receive_persons
|
||||||
|
|
||||||
|
|
||||||
|
class Person(object):
|
||||||
|
"""
|
||||||
|
Meta-class for all person objects
|
||||||
|
"""
|
||||||
|
def person_id(self):
|
||||||
|
"""
|
||||||
|
Return an id for representation of ths person. Has to be unique.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError('Any person object needs a person_id')
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"""
|
||||||
|
Return a string for this person.
|
||||||
|
"""
|
||||||
|
return str(self.person_id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sort_name(self):
|
||||||
|
"""
|
||||||
|
Return the part of the name, which is used for sorting.
|
||||||
|
For example the pre-name or the last-name
|
||||||
|
"""
|
||||||
|
return self.clean_name.lower()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def clean_name(self):
|
||||||
|
"""
|
||||||
|
Return the name of this person without a suffix
|
||||||
|
"""
|
||||||
|
return unicode(self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name_suffix(self):
|
||||||
|
"""
|
||||||
|
Return a suffix for the person-name.
|
||||||
|
"""
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
class Persons(object):
|
class Persons(object):
|
||||||
"""
|
"""
|
||||||
A Storage for a multiplicity of different Person-Objects.
|
A Storage for a multiplicity of different Person-Objects.
|
||||||
@ -25,13 +64,17 @@ class Persons(object):
|
|||||||
try:
|
try:
|
||||||
return iter(self._cache)
|
return iter(self._cache)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return iter(self.iter_persons())
|
return self.iter_persons()
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return len(list(self.__iter__()))
|
return len(list(self.__iter__()))
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
|
try:
|
||||||
return list(self)[key]
|
return list(self)[key]
|
||||||
|
except IndexError:
|
||||||
|
from openslides.utils.person import EmptyPerson
|
||||||
|
return EmptyPerson()
|
||||||
|
|
||||||
def iter_persons(self):
|
def iter_persons(self):
|
||||||
self._cache = list()
|
self._cache = list()
|
||||||
@ -61,6 +104,6 @@ def get_person(person_id):
|
|||||||
try:
|
try:
|
||||||
person_prefix, id = split_person_id(person_id)
|
person_prefix, id = split_person_id(person_id)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
from openslides.utils.person import EmtyPerson
|
from openslides.utils.person import EmptyPerson
|
||||||
return EmtyPerson()
|
return EmptyPerson()
|
||||||
return Persons(person_prefix_filter=person_prefix, id_filter=id)[0]
|
return Persons(person_prefix_filter=person_prefix, id_filter=id)[0]
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user