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
|
||||
build/*
|
||||
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>
|
||||
René Köcher <shirk@bitspin.org>
|
||||
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
|
||||
|
||||
|
||||
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)
|
||||
========================
|
||||
[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
|
||||
-------
|
||||
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.
|
||||
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:
|
||||
|
||||
OpenSlides requires following programs, which should be
|
||||
installed first:
|
||||
+ Python Programming Language 2 (>= 2.5),
|
||||
+ Setuptools
|
||||
+ Python Programming Language 2 (>= 2.5)
|
||||
+ virtualenv (>= 1.4.1)
|
||||
+ ReportLab Toolkit
|
||||
+ 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/:
|
||||
|
||||
@ -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
|
||||
|
||||
d) Install ReportLab Toolkit, Python Imaging Library (PIL), Django
|
||||
and django-mptt:
|
||||
2. Install OpenSlides:
|
||||
|
||||
Open command line (cmd) and run:
|
||||
|
||||
easy_install django django-mptt reportlab pil
|
||||
|
||||
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
|
||||
easy_install openslides
|
||||
|
||||
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
|
||||
admin account are created. Please change the password after
|
||||
@ -75,76 +178,4 @@ I. Installation on Windows (32/64bit)
|
||||
Username: admin
|
||||
Password: admin
|
||||
|
||||
Use 'python start.py --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.).
|
||||
Use 'openslides --help' to show all start options.
|
||||
|
@ -1,6 +1,5 @@
|
||||
include AUTHORS
|
||||
include CHANGELOG
|
||||
include initial_data.json
|
||||
include INSTALL.txt
|
||||
include LICENSE
|
||||
include manage.py
|
||||
@ -12,16 +11,14 @@ recursive-include openslides/templates *
|
||||
|
||||
recursive-include openslides/agenda/templates *
|
||||
recursive-include openslides/agenda/static *
|
||||
recursive-include openslides/application/templates *
|
||||
recursive-include openslides/application/static *
|
||||
recursive-include openslides/motion/templates *
|
||||
recursive-include openslides/assignment/templates *
|
||||
recursive-include openslides/assignment/static *
|
||||
recursive-include openslides/config/templates *
|
||||
recursive-include openslides/config/static *
|
||||
recursive-include openslides/participant/templates *
|
||||
recursive-include openslides/participant/static *
|
||||
include openslides/participant/fixtures/groups_de.json
|
||||
recursive-include openslides/poll/templates *
|
||||
recursive-include openslides/poll/static *
|
||||
recursive-include openslides/projector/templates *
|
||||
recursive-include openslides/projector/static *
|
||||
|
||||
|
43
README.txt
43
README.txt
@ -2,15 +2,15 @@
|
||||
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?
|
||||
===================
|
||||
OpenSlides is a free, web-based presentation system for displaying and
|
||||
controlling of agenda, applications and elections of an assembly.
|
||||
OpenSlides is a free web-based presentation and assembly system for
|
||||
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
|
||||
@ -18,14 +18,16 @@ Getting started
|
||||
Install and start OpenSlides as described in the INSTALL.txt.
|
||||
|
||||
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
|
||||
==============================
|
||||
Simply running
|
||||
openslides.exe (on Windows) or
|
||||
python start.py (on Linux/MacOS)
|
||||
openslides.exe (on Windows)
|
||||
or
|
||||
openslides (on Linux/MacOS)
|
||||
|
||||
will start OpenSlides using djangos development server. It will also
|
||||
try to open OpenSlides in your default webbrowser.
|
||||
|
||||
@ -47,27 +49,35 @@ Command line options
|
||||
--------------------
|
||||
The following command line options are available:
|
||||
|
||||
-h, --help
|
||||
Shows all options
|
||||
|
||||
-a, --address=ADDRESS
|
||||
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
|
||||
|
||||
--syncdb
|
||||
Create/ update the database
|
||||
Creates/updates database before starting the server
|
||||
|
||||
--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:
|
||||
openslides.exe -a 127.0.0.1
|
||||
or
|
||||
python start.py -a 127.0.0.1
|
||||
openslides -a 127.0.0.1
|
||||
|
||||
Example 2: Like above, but also specify the port as 8080
|
||||
openslides.exe -a 127.0.0.01 -p 8080
|
||||
or
|
||||
python start.py -a 127.0.0.1 -p 8080
|
||||
openslides -a 127.0.0.01 -p 8080
|
||||
|
||||
|
||||
Supported operating systems and browsers
|
||||
@ -82,4 +92,3 @@ Browsers:
|
||||
IE 7+
|
||||
Chrome
|
||||
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
|
||||
|
||||
2.) Install OpenSlides by running python setup.py install in the top directory
|
||||
of OpenSlides.
|
||||
NOTE: This step must be repeated whenever you make changes to OpenSlides
|
||||
2.) Run in the main directory of the OpenSlides checkout:
|
||||
|
||||
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.
|
||||
|
@ -18,8 +18,6 @@ import zipfile
|
||||
import distutils.ccompiler
|
||||
import distutils.sysconfig
|
||||
|
||||
from contextlib import nested
|
||||
|
||||
import pkg_resources
|
||||
|
||||
sys.path.insert(0, os.getcwd())
|
||||
@ -80,10 +78,7 @@ SITE_PACKAGES = {
|
||||
"pil": {
|
||||
# NOTE: PIL is a special case, see copy_pil
|
||||
"copy": [],
|
||||
},
|
||||
"openslides": {
|
||||
"copy" : ["openslides"],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
PY_DLLS = [
|
||||
@ -92,6 +87,7 @@ PY_DLLS = [
|
||||
"_sqlite3.pyd",
|
||||
"_socket.pyd",
|
||||
"select.pyd",
|
||||
"_ctypes.pyd",
|
||||
]
|
||||
|
||||
MSVCR_PUBLIC_KEY = "1fc8b3b9a1e18e3b"
|
||||
@ -297,9 +293,13 @@ def main():
|
||||
raise
|
||||
|
||||
os.makedirs(odir)
|
||||
out_site_packages = os.path.join(odir, "site-packages")
|
||||
|
||||
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():
|
||||
sys.stdout.write("Using prebuild openslides.exe\n")
|
||||
@ -307,8 +307,8 @@ def main():
|
||||
shutil.copyfile("extras/win32-portable/openslides.exe",
|
||||
os.path.join(odir, "openslides.exe"))
|
||||
|
||||
shutil.copyfile("initial_data.json",
|
||||
os.path.join(odir, "initial_data.json"))
|
||||
shutil.copyfile("openslides/participant/fixtures/groups_de.json",
|
||||
os.path.join(odir, "groups_de.json"))
|
||||
|
||||
copy_dlls(odir)
|
||||
copy_msvcr(odir)
|
||||
|
15
manage.py
15
manage.py
@ -7,14 +7,11 @@
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.core.management import execute_manager
|
||||
|
||||
try:
|
||||
from openslides import settings
|
||||
except ImportError:
|
||||
import sys
|
||||
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
|
||||
sys.exit(1)
|
||||
import os, sys
|
||||
from django.core.management import execute_from_command_line
|
||||
from openslides.main import get_user_config_path, setup_django_environment
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
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."""
|
||||
# TODO: Get the Version Hash from GIT.
|
||||
|
||||
def get_version(version=None, release=None):
|
||||
"""
|
||||
Derives a PEP386-compliant version number from VERSION. Adds id of
|
||||
the current git commit.
|
||||
"""
|
||||
if version is None:
|
||||
version = VERSION
|
||||
if release is None:
|
||||
release = RELEASE
|
||||
assert len(version) == 5
|
||||
assert version[3] in ('alpha', 'beta', 'rc', 'final')
|
||||
|
||||
# Now build the two parts of the version number:
|
||||
# main = X.Y[.Z]
|
||||
# sub = .devN - for pre-alpha releases
|
||||
# | {a|b|c}N - for alpha, beta and rc releases
|
||||
|
||||
parts = 2 if version[2] == 0 else 3
|
||||
main = '.'.join(str(x) for x in version[:parts])
|
||||
|
||||
sub = ''
|
||||
if version[3] == 'alpha' and version[4] == 0:
|
||||
mercurial_version = hg_version()
|
||||
if mercurial_version != 'unknown':
|
||||
sub = '.dev%s' % mercurial_version
|
||||
# sub = {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
|
||||
main = '.'.join(str(x) for x in version[:main_parts])
|
||||
if version[3] != 'final':
|
||||
mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'c'}
|
||||
sub = mapping[version[3]] + str(version[4])
|
||||
else:
|
||||
sub = '.dev'
|
||||
|
||||
elif version[3] != 'final':
|
||||
sub = "-" + version[3] + str(version[4])
|
||||
|
||||
sub = ''
|
||||
if not release:
|
||||
sub += '-dev'
|
||||
return main + sub
|
||||
|
||||
|
||||
def hg_version():
|
||||
import socket
|
||||
import os
|
||||
import sys
|
||||
from os.path import realpath, join, dirname
|
||||
def get_git_commit_id():
|
||||
"""
|
||||
Catches the commit id of the git head.
|
||||
"""
|
||||
try:
|
||||
from mercurial import ui as hgui
|
||||
from mercurial.localrepo import localrepository
|
||||
from mercurial.node import short as shorthex
|
||||
from mercurial.error import RepoError
|
||||
nomercurial = False
|
||||
except 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())
|
||||
git_head = open('.git/HEAD', 'r').read().rstrip()
|
||||
if git_head[:5] == 'ref: ':
|
||||
git_commit_id = open('.git/%s' % git_head[5:], 'r').read().rstrip()
|
||||
else:
|
||||
version = '%(num)s:%(id)s' % {
|
||||
'num': ctx.rev(), 'id': shorthex(ctx.node())
|
||||
}
|
||||
except TypeError:
|
||||
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)
|
||||
git_commit_id = git_head
|
||||
except IOError:
|
||||
git_commit_id = 'unknown'
|
||||
return str(git_commit_id)
|
||||
|
@ -24,8 +24,8 @@ class ItemForm(forms.ModelForm, CssClassMixin):
|
||||
"""
|
||||
Form to create of update an item.
|
||||
"""
|
||||
parent = TreeNodeChoiceField(queryset=Item.objects.all(),
|
||||
label=_("Parent item"), required=False)
|
||||
parent = TreeNodeChoiceField(
|
||||
queryset=Item.objects.all(), label=_("Parent item"), required=False)
|
||||
|
||||
class Meta:
|
||||
model = Item
|
||||
|
@ -10,12 +10,6 @@
|
||||
: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.core.urlresolvers import reverse
|
||||
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 openslides.config.models import config
|
||||
|
||||
from openslides.projector.projector import SlideMixin
|
||||
from openslides.projector.api import (register_slidemodel, get_slide_from_sid,
|
||||
register_slidefunc, split_sid)
|
||||
|
||||
from openslides.projector.api import (
|
||||
register_slidemodel, get_slide_from_sid, register_slidefunc)
|
||||
from openslides.agenda.slides import agenda_show
|
||||
|
||||
|
||||
@ -84,7 +76,6 @@ class Item(MPTTModel, SlideMixin):
|
||||
return self.title
|
||||
return self.get_related_slide().get_agenda_title()
|
||||
|
||||
|
||||
def get_title_supplement(self):
|
||||
"""
|
||||
return a supplement for the title.
|
||||
|
@ -24,7 +24,7 @@ function hideClosedSlides(hide) {
|
||||
hideLine($(this));
|
||||
});
|
||||
hidden = $('#menu-overview tr:hidden').size();
|
||||
$('#hiddencount').text(', davon ' + hidden + ' verborgen.');
|
||||
$('#hiddencount').text(interpolate(gettext(', of which %s are hidden.'), [hidden]));
|
||||
} else {
|
||||
$('#menu-overview tr').show();
|
||||
$('#hidelink').attr('title','hide');
|
||||
|
@ -11,7 +11,6 @@
|
||||
"""
|
||||
from reportlab.platypus import Paragraph
|
||||
|
||||
from django.core.context_processors import csrf
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib import messages
|
||||
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 openslides.utils.pdf import stylesheet
|
||||
from openslides.utils.views import (TemplateView, RedirectView, UpdateView,
|
||||
CreateView, DeleteView, PDFView, DetailView)
|
||||
from openslides.utils.views import (
|
||||
TemplateView, RedirectView, UpdateView, CreateView, DeleteView, PDFView,
|
||||
DetailView)
|
||||
from openslides.utils.template import Tab
|
||||
from openslides.utils.utils import html_strong
|
||||
|
||||
from openslides.config.models import config
|
||||
|
||||
from openslides.projector.api import get_active_slide
|
||||
from openslides.projector.projector import Widget, SLIDE
|
||||
|
||||
from openslides.agenda.models import Item
|
||||
from openslides.agenda.forms import ItemOrderForm, ItemForm
|
||||
from .models import Item
|
||||
from .forms import ItemOrderForm, ItemForm
|
||||
|
||||
|
||||
class Overview(TemplateView):
|
||||
@ -53,7 +49,8 @@ class Overview(TemplateView):
|
||||
def post(self, request, *args, **kwargs):
|
||||
context = self.get_context_data(**kwargs)
|
||||
if not request.user.has_perm('agenda.can_manage_agenda'):
|
||||
messages.error(request,
|
||||
messages.error(
|
||||
request,
|
||||
_('You are not authorized to manage the agenda.'))
|
||||
return self.render_to_response(context)
|
||||
transaction.commit()
|
||||
@ -69,8 +66,8 @@ class Overview(TemplateView):
|
||||
Model.save(item)
|
||||
else:
|
||||
transaction.rollback()
|
||||
messages.error(request,
|
||||
_('Errors when reordering of the agenda'))
|
||||
messages.error(
|
||||
request, _('Errors when reordering of the agenda'))
|
||||
return self.render_to_response(context)
|
||||
Item.objects.rebuild()
|
||||
# TODO: assure, that it is a valid tree
|
||||
@ -130,8 +127,8 @@ class ItemUpdate(UpdateView):
|
||||
apply_url = 'item_edit'
|
||||
|
||||
def get_success_url(self):
|
||||
messages.success(self.request,
|
||||
_("Item %s was successfully modified.") \
|
||||
messages.success(
|
||||
self.request, _("Item %s was successfully modified.")
|
||||
% html_strong(self.request.POST['title']))
|
||||
if 'apply' in self.request.POST:
|
||||
return ''
|
||||
@ -151,8 +148,8 @@ class ItemCreate(CreateView):
|
||||
apply_url = 'item_edit'
|
||||
|
||||
def get_success_url(self):
|
||||
messages.success(self.request,
|
||||
_("Item %s was successfully created.") \
|
||||
messages.success(
|
||||
self.request, _("Item %s was successfully created.")
|
||||
% html_strong(self.request.POST['title']))
|
||||
if 'apply' in self.request.POST:
|
||||
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):
|
||||
if self.get_answer() == 'all':
|
||||
self.object.delete(with_children=True)
|
||||
messages.success(request,
|
||||
_("Item %s and his children were successfully deleted.") \
|
||||
messages.success(
|
||||
request, _("Item %s and his children were successfully deleted.")
|
||||
% html_strong(self.object))
|
||||
elif self.get_answer() == 'yes':
|
||||
self.object.delete(with_children=False)
|
||||
messages.success(request,
|
||||
_("Item %s was successfully deleted.") \
|
||||
messages.success(
|
||||
request, _("Item %s was successfully deleted.")
|
||||
% html_strong(self.object))
|
||||
|
||||
|
||||
@ -199,7 +196,8 @@ class AgendaPDF(PDFView):
|
||||
ancestors = item.get_ancestors()
|
||||
if ancestors:
|
||||
space = " " * 6 * ancestors.count()
|
||||
story.append(Paragraph("%s%s" % (space, item.get_title()),
|
||||
story.append(Paragraph(
|
||||
"%s%s" % (space, item.get_title()),
|
||||
stylesheet['Subitem']))
|
||||
else:
|
||||
story.append(Paragraph(item.get_title(), stylesheet['Item']))
|
||||
@ -214,10 +212,9 @@ def register_tab(request):
|
||||
title=_('Agenda'),
|
||||
app='agenda',
|
||||
url=reverse('item_overview'),
|
||||
permission=request.user.has_perm('agenda.can_see_agenda')
|
||||
or request.user.has_perm('agenda.can_manage_agenda'),
|
||||
selected=selected,
|
||||
)
|
||||
permission=(request.user.has_perm('agenda.can_see_agenda') or
|
||||
request.user.has_perm('agenda.can_manage_agenda')),
|
||||
selected=selected)
|
||||
|
||||
|
||||
def get_widgets(request):
|
||||
@ -227,8 +224,9 @@ def get_widgets(request):
|
||||
return [
|
||||
Widget(
|
||||
name='agenda',
|
||||
display_name=_('Agenda'),
|
||||
template='agenda/widget.html',
|
||||
context={
|
||||
'agenda': SLIDE['agenda'],
|
||||
'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.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.person import PersonFormField
|
||||
@ -20,8 +20,8 @@ from openslides.assignment.models import Assignment
|
||||
|
||||
|
||||
class AssignmentForm(forms.ModelForm, CssClassMixin):
|
||||
posts = forms.IntegerField(min_value=1, initial=1,
|
||||
label=_("Number of available posts"))
|
||||
posts = forms.IntegerField(
|
||||
min_value=1, initial=1, label=_("Number of available posts"))
|
||||
|
||||
class Meta:
|
||||
model = Assignment
|
||||
@ -39,8 +39,7 @@ class ConfigForm(forms.Form, CssClassMixin):
|
||||
assignment_publish_winner_results_only = forms.BooleanField(
|
||||
required=False,
|
||||
label=_("Only publish voting results for selected winners "
|
||||
"(Projector view only)")
|
||||
)
|
||||
"(Projector view only)"))
|
||||
assignment_pdf_ballot_papers_selection = forms.ChoiceField(
|
||||
widget=forms.Select(),
|
||||
required=False,
|
||||
@ -48,31 +47,25 @@ class ConfigForm(forms.Form, CssClassMixin):
|
||||
choices=(
|
||||
("NUMBER_OF_DELEGATES", _("Number of all delegates")),
|
||||
("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(
|
||||
widget=forms.TextInput(attrs={'class': 'small-input'}),
|
||||
required=False,
|
||||
min_value=1,
|
||||
label=_("Custom number of ballot papers")
|
||||
)
|
||||
label=_("Custom number of ballot papers"))
|
||||
assignment_pdf_title = forms.CharField(
|
||||
widget=forms.TextInput(),
|
||||
required=False,
|
||||
label=_("Title for PDF document (all elections)")
|
||||
)
|
||||
label=_("Title for PDF document (all elections)"))
|
||||
assignment_pdf_preamble = forms.CharField(
|
||||
widget=forms.Textarea(),
|
||||
required=False,
|
||||
label=_("Preamble text for PDF document (all elections)")
|
||||
)
|
||||
assignment_poll_vote_values = forms.ChoiceField(widget=forms.Select(),
|
||||
label=_("Preamble text for PDF document (all elections)"))
|
||||
assignment_poll_vote_values = forms.ChoiceField(
|
||||
widget=forms.Select(),
|
||||
required=False,
|
||||
label=_("Election method"),
|
||||
choices=(
|
||||
("auto", _("Automatic assign of method.")),
|
||||
("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 openslides.utils.person import PersonField
|
||||
|
||||
from openslides.config.models import config
|
||||
from openslides.config.signals import default_config_value
|
||||
|
||||
from openslides.projector.api import register_slidemodel
|
||||
from openslides.projector.projector import SlideMixin
|
||||
|
||||
from openslides.poll.models import (BasePoll, CountInvalid, CountVotesCast,
|
||||
BaseOption, PublishPollMixin, BaseVote)
|
||||
|
||||
from openslides.poll.models import (
|
||||
BasePoll, CountInvalid, CountVotesCast, BaseOption, PublishPollMixin, BaseVote)
|
||||
from openslides.agenda.models import Item
|
||||
|
||||
|
||||
@ -33,10 +29,14 @@ class AssignmentCandidate(models.Model):
|
||||
assignment = models.ForeignKey("Assignment")
|
||||
person = PersonField(db_index=True)
|
||||
elected = models.BooleanField(default=False)
|
||||
blocked = models.BooleanField(default=False)
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.person)
|
||||
|
||||
class Meta:
|
||||
unique_together = ("assignment", "person")
|
||||
|
||||
|
||||
class Assignment(models.Model, SlideMixin):
|
||||
prefix = 'assignment'
|
||||
@ -47,11 +47,10 @@ class Assignment(models.Model, SlideMixin):
|
||||
)
|
||||
|
||||
name = models.CharField(max_length=100, verbose_name=_("Name"))
|
||||
description = models.TextField(null=True, blank=True,
|
||||
verbose_name=_("Description"))
|
||||
posts = models.PositiveSmallIntegerField(
|
||||
verbose_name=_("Number of available posts"))
|
||||
polldescription = models.CharField(max_length=100, null=True, blank=True,
|
||||
description = models.TextField(null=True, blank=True, verbose_name=_("Description"))
|
||||
posts = models.PositiveSmallIntegerField(verbose_name=_("Number of available posts"))
|
||||
polldescription = models.CharField(
|
||||
max_length=100, null=True, blank=True,
|
||||
verbose_name=_("Comment on the ballot paper"))
|
||||
status = models.CharField(max_length=3, choices=STATUS, default='sea')
|
||||
|
||||
@ -64,14 +63,16 @@ class Assignment(models.Model, SlideMixin):
|
||||
if error:
|
||||
raise NameError(_('%s is not a valid status.') % status)
|
||||
if self.status == status:
|
||||
raise NameError(_('The assignment status is already %s.')
|
||||
% self.status)
|
||||
raise NameError(
|
||||
_('The assignment status is already %s.') % self.status)
|
||||
self.status = status
|
||||
self.save()
|
||||
|
||||
def run(self, candidate, person=None):
|
||||
"""
|
||||
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.
|
||||
# Use other Exceptions
|
||||
@ -79,26 +80,54 @@ class Assignment(models.Model, SlideMixin):
|
||||
raise NameError(_('<b>%s</b> is already a candidate.') % candidate)
|
||||
if not person.has_perm("assignment.can_manage_assignment") and self.status != 'sea':
|
||||
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
|
||||
"""
|
||||
if self.is_candidate(candidate):
|
||||
self.assignment_candidats.get(person=candidate).delete()
|
||||
else:
|
||||
# TODO: Use an OpenSlides Error
|
||||
try:
|
||||
candidature = self.assignment_candidates.get(person=candidate)
|
||||
except AssignmentCandidate.DoesNotExist:
|
||||
raise Exception(_('%s is no candidate') % candidate)
|
||||
|
||||
def is_candidate(self, person):
|
||||
if self.assignment_candidats.filter(person=person).exists():
|
||||
return True
|
||||
if not candidature.blocked:
|
||||
if blocked:
|
||||
candidature.blocked = True
|
||||
candidature.save()
|
||||
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
|
||||
|
||||
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
|
||||
def assignment_candidats(self):
|
||||
def assignment_candidates(self):
|
||||
return AssignmentCandidate.objects.filter(assignment=self)
|
||||
|
||||
@property
|
||||
@ -110,11 +139,9 @@ class Assignment(models.Model, SlideMixin):
|
||||
return self.get_participants(only_elected=True)
|
||||
|
||||
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:
|
||||
# TODO: Use right Exception
|
||||
raise Exception("only_elected and only_candidate can not both be Treu")
|
||||
assert not (only_elected and only_candidate)
|
||||
|
||||
if only_elected:
|
||||
candidates = candidates.filter(elected=True)
|
||||
@ -122,12 +149,15 @@ class Assignment(models.Model, SlideMixin):
|
||||
if only_candidate:
|
||||
candidates = candidates.filter(elected=False)
|
||||
|
||||
participants = []
|
||||
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):
|
||||
candidate = self.assignment_candidats.get(person=person)
|
||||
candidate = self.assignment_candidates.get(person=person)
|
||||
candidate.elected = value
|
||||
candidate.save()
|
||||
|
||||
@ -140,7 +170,6 @@ class Assignment(models.Model, SlideMixin):
|
||||
poll.set_options([{'candidate': person} for person in self.candidates])
|
||||
return poll
|
||||
|
||||
|
||||
def vote_results(self, only_published):
|
||||
"""
|
||||
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)
|
||||
return vote_results_dict
|
||||
|
||||
|
||||
def get_agenda_title(self):
|
||||
return self.name
|
||||
|
||||
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):
|
||||
item.delete()
|
||||
super(Assignment, self).delete()
|
||||
@ -188,10 +216,12 @@ class Assignment(models.Model, SlideMixin):
|
||||
"""
|
||||
return the slide dict
|
||||
"""
|
||||
polls = self.poll_set
|
||||
data = super(Assignment, self).slide()
|
||||
data['assignment'] = self
|
||||
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['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_manage_assignment', ugettext_noop("Can manage assignment")),
|
||||
)
|
||||
ordering = ('name',)
|
||||
|
||||
register_slidemodel(Assignment)
|
||||
|
||||
@ -251,14 +282,13 @@ class AssignmentPoll(BasePoll, CountInvalid, CountVotesCast, PublishPollMixin):
|
||||
self.yesnoabstain = True
|
||||
else:
|
||||
# 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
|
||||
else:
|
||||
self.yesnoabstain = False
|
||||
self.save()
|
||||
if self.yesnoabstain:
|
||||
return [ugettext_noop('Yes'), ugettext_noop('No'),
|
||||
ugettext_noop('Abstain')]
|
||||
return [ugettext_noop('Yes'), ugettext_noop('No'), ugettext_noop('Abstain')]
|
||||
else:
|
||||
return [ugettext_noop('Votes')]
|
||||
|
||||
|
@ -42,7 +42,13 @@
|
||||
<tr class="{% cycle '' 'odd' %}
|
||||
{% if assignment.active %}activeline{% endif %}">
|
||||
<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>
|
||||
<span style="width: 1px; white-space: nowrap;">
|
||||
|
@ -35,9 +35,10 @@
|
||||
{% endif %}
|
||||
<br>
|
||||
<!-- Candidates -->
|
||||
{% if assignment.status != "fin" %}
|
||||
<h4>{% trans "Candidates" %}</h4>
|
||||
<ol>
|
||||
{% for person in assignment.candidates %}
|
||||
{% for person in assignment.get_participants %}
|
||||
<li>
|
||||
{{ person }}
|
||||
{% 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>
|
||||
{% 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>
|
||||
{% empty %}
|
||||
<li style="list-style: none outside none; margin-left: -25px;"><i>{% trans "No candidates available." %}</i></li>
|
||||
@ -84,7 +93,24 @@
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% 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>
|
||||
{% if polls.exists %}
|
||||
<table class="table table-striped table-bordered">
|
||||
@ -175,7 +201,6 @@
|
||||
<td></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
|
||||
<tr class="info total">
|
||||
<td><strong>{% trans 'Votes cast' %}</strong></td>
|
||||
{% for poll in polls %}
|
||||
|
@ -62,9 +62,9 @@
|
||||
</tr>
|
||||
|
||||
{% for candidate, poll_list in vote_results.items %}
|
||||
<tr class="{% cycle 'odd' '' %}">
|
||||
<td class="candidate{% if candidate in assignment.elected.all %} elected{% endif %}">
|
||||
{% if candidate in assignment.elected.all %}
|
||||
<tr class="{% cycle 'odd' '' as rowcolors %}">
|
||||
<td class="candidate{% if candidate in assignment.elected %} elected{% endif %}">
|
||||
{% if candidate in assignment.elected %}
|
||||
<a class="elected">
|
||||
<img src="{% static 'images/icons/voting-yes.png' %}" title="{% trans 'Candidate is elected' %}">
|
||||
</a>
|
||||
@ -72,8 +72,8 @@
|
||||
{{ candidate }}
|
||||
</td>
|
||||
{% for vote in poll_list %}
|
||||
<td style="white-space:nowrap;"{% if candidate in assignment.elected.all %} class="elected"{% endif %}>
|
||||
{% if not assignment_publish_winner_results_only or candidate in assignment.elected.all %}
|
||||
<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 %}
|
||||
{% 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-no.png' %}" title="{% trans 'No' %}"> {{ vote.No }}<br>
|
||||
@ -92,8 +92,7 @@
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
<tr>
|
||||
<tr class="{% cycle rowcolors %}">
|
||||
<td>{% trans 'Invalid votes' %}</td>
|
||||
{% for poll in polls %}
|
||||
<td style="white-space:nowrap;">
|
||||
@ -105,7 +104,6 @@
|
||||
|
||||
{% endfor %}
|
||||
</tr>
|
||||
|
||||
<tr class="total">
|
||||
<td>
|
||||
<strong>{% trans 'Votes cast' %}</strong>
|
||||
@ -120,10 +118,6 @@
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
{% elif assignment.candidates %}
|
||||
<i>{% trans "No ballots available." %}</i>
|
||||
{% endif %}
|
||||
<br>
|
||||
{% endblock %}
|
||||
|
@ -13,39 +13,31 @@
|
||||
import os
|
||||
|
||||
from reportlab.lib import colors
|
||||
from reportlab.platypus import (SimpleDocTemplate, PageBreak, Paragraph,
|
||||
Spacer, Table, TableStyle)
|
||||
from reportlab.platypus import (
|
||||
SimpleDocTemplate, PageBreak, Paragraph, Spacer, Table, TableStyle)
|
||||
from reportlab.lib.units import cm
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import User
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.translation import ungettext, ugettext as _
|
||||
|
||||
from openslides.utils.pdf import stylesheet
|
||||
from openslides.utils.template import Tab
|
||||
from openslides.utils.utils import (template, permission_required,
|
||||
gen_confirm_form, del_confirm_form, ajax_request)
|
||||
from openslides.utils.utils import (
|
||||
template, permission_required, gen_confirm_form, del_confirm_form, ajax_request)
|
||||
from openslides.utils.views import FormView, DeleteView, PDFView, RedirectView
|
||||
from openslides.utils.person import get_person
|
||||
|
||||
from openslides.config.models import config
|
||||
|
||||
from openslides.participant.models import User
|
||||
|
||||
from openslides.projector.projector import Widget
|
||||
|
||||
from openslides.poll.views import PollFormView
|
||||
|
||||
from openslides.agenda.models import Item
|
||||
|
||||
from openslides.assignment.models import (Assignment, AssignmentPoll,
|
||||
AssignmentOption)
|
||||
from openslides.assignment.forms import (AssignmentForm, AssignmentRunForm,
|
||||
ConfigForm)
|
||||
from openslides.assignment.models import Assignment, AssignmentPoll
|
||||
from openslides.assignment.forms import (
|
||||
AssignmentForm, AssignmentRunForm, ConfigForm)
|
||||
|
||||
|
||||
@permission_required('assignment.can_see_assignment')
|
||||
@ -59,7 +51,7 @@ def get_overview(request):
|
||||
if sort in ['name', 'status']:
|
||||
query = query.order_by(sort)
|
||||
except KeyError:
|
||||
query = query.order_by('name')
|
||||
pass
|
||||
if 'reverse' in request.GET:
|
||||
query = query.reverse()
|
||||
|
||||
@ -81,14 +73,16 @@ def view(request, assignment_id=None):
|
||||
user = form.cleaned_data['candidate']
|
||||
try:
|
||||
assignment.run(user, request.user)
|
||||
messages.success(request, _("Candidate <b>%s</b> was nominated successfully.") % (user))
|
||||
except NameError, e:
|
||||
messages.error(request, e)
|
||||
else:
|
||||
messages.success(request, _(
|
||||
"Candidate <b>%s</b> was nominated successfully.")
|
||||
% user)
|
||||
else:
|
||||
if request.user.has_perm('assignment.can_nominate_other'):
|
||||
form = AssignmentRunForm()
|
||||
|
||||
|
||||
polls = assignment.poll_set.all()
|
||||
if not request.user.has_perm('assignment.can_manage_assignment'):
|
||||
polls = assignment.poll_set.filter(published=True)
|
||||
@ -97,13 +91,16 @@ def view(request, assignment_id=None):
|
||||
polls = assignment.poll_set.all()
|
||||
vote_results = assignment.vote_results(only_published=False)
|
||||
|
||||
blocked_candidates = [
|
||||
candidate.person for candidate in
|
||||
assignment.assignment_candidates.filter(blocked=True)]
|
||||
return {
|
||||
'assignment': assignment,
|
||||
'blocked_candidates': blocked_candidates,
|
||||
'form': form,
|
||||
'vote_results': vote_results,
|
||||
'polls': polls,
|
||||
'user_is_candidate': assignment.is_candidate(request.user)
|
||||
}
|
||||
'user_is_candidate': assignment.is_candidate(request.user)}
|
||||
|
||||
|
||||
@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())
|
||||
except Assignment.DoesNotExist:
|
||||
pass
|
||||
except NameError, e:
|
||||
messages.error(request, e)
|
||||
return redirect(reverse('assignment_view', args=[assignment_id]))
|
||||
|
||||
|
||||
@ -182,15 +181,19 @@ def run(request, assignment_id):
|
||||
@login_required
|
||||
def delrun(request, assignment_id):
|
||||
assignment = Assignment.objects.get(pk=assignment_id)
|
||||
if assignment.status == 'sea' or request.user.has_perm("assignment.can_manage_assignment"):
|
||||
try:
|
||||
if assignment.status == 'sea' or user.has_perm("assignment.can_manage_assignment"):
|
||||
assignment.delrun(request.user)
|
||||
else:
|
||||
messages.error(request, _('The candidate list is already closed.'))
|
||||
assignment.delrun(request.user, blocked=True)
|
||||
except Exception, e:
|
||||
messages.error(request, e)
|
||||
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]))
|
||||
|
||||
|
||||
@ -198,18 +201,25 @@ def delrun(request, assignment_id):
|
||||
def delother(request, assignment_id, user_id):
|
||||
assignment = Assignment.objects.get(pk=assignment_id)
|
||||
person = get_person(user_id)
|
||||
is_blocked = assignment.is_blocked(person)
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
assignment.delrun(person)
|
||||
assignment.delrun(person, blocked=False)
|
||||
except Exception, e:
|
||||
messages.error(request, e)
|
||||
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:
|
||||
gen_confirm_form(request,
|
||||
_("Do you really want to withdraw <b>%s</b> from the election?") \
|
||||
% person, reverse('assignment_delother', args=[assignment_id, user_id]))
|
||||
message = _("<b>%s</b> was unblocked successfully.") % person
|
||||
messages.success(request, message)
|
||||
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]))
|
||||
|
||||
|
||||
@ -319,7 +329,8 @@ class AssignmentPDF(PDFView):
|
||||
try:
|
||||
assignment_id = self.kwargs['assignment_id']
|
||||
assignment = Assignment.objects.get(id=assignment_id)
|
||||
filename = u'%s-%s' % (_("Assignment"),
|
||||
filename = u'%s-%s' % (
|
||||
_("Assignment"),
|
||||
assignment.name.replace(' ', '_'))
|
||||
except:
|
||||
filename = _("Elections")
|
||||
@ -335,18 +346,19 @@ class AssignmentPDF(PDFView):
|
||||
story.append(Paragraph(title, stylesheet['Heading1']))
|
||||
preamble = config["assignment_pdf_preamble"]
|
||||
if preamble:
|
||||
story.append(Paragraph("%s" % preamble.replace('\r\n', '<br/>'),
|
||||
story.append(Paragraph(
|
||||
"%s" % preamble.replace('\r\n', '<br/>'),
|
||||
stylesheet['Paragraph']))
|
||||
story.append(Spacer(0, 0.75 * cm))
|
||||
assignments = Assignment.objects.order_by('name')
|
||||
assignments = Assignment.objects.all()
|
||||
if not assignments: # No assignments existing
|
||||
story.append(Paragraph(_("No assignments available."),
|
||||
stylesheet['Heading3']))
|
||||
story.append(Paragraph(
|
||||
_("No assignments available."), stylesheet['Heading3']))
|
||||
else: # Print all assignments
|
||||
# List of assignments
|
||||
for assignment in assignments:
|
||||
story.append(Paragraph(assignment.name,
|
||||
stylesheet['Heading3']))
|
||||
story.append(Paragraph(
|
||||
assignment.name, stylesheet['Heading3']))
|
||||
# Assignment details (each assignment on single page)
|
||||
for assignment in assignments:
|
||||
story.append(PageBreak())
|
||||
@ -359,26 +371,31 @@ class AssignmentPDF(PDFView):
|
||||
|
||||
def get_assignment(self, assignment, story):
|
||||
# title
|
||||
story.append(Paragraph(_("Election: %s") % assignment.name,
|
||||
stylesheet['Heading1']))
|
||||
story.append(Paragraph(
|
||||
_("Election: %s") % assignment.name, stylesheet['Heading1']))
|
||||
story.append(Spacer(0, 0.5 * cm))
|
||||
# posts
|
||||
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']))
|
||||
cell1b = []
|
||||
cell1b.append(Paragraph(str(assignment.posts), stylesheet['Paragraph']))
|
||||
# candidates
|
||||
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']))
|
||||
cell2b = []
|
||||
for candidate in assignment.candidates:
|
||||
cell2b.append(Paragraph("<seq id='counter'/>. %s" % candidate,
|
||||
cell2b.append(Paragraph(
|
||||
"<seq id='counter'/>. %s" % candidate,
|
||||
stylesheet['Signaturefield']))
|
||||
if assignment.status == "sea":
|
||||
for x in range(0, 2 * assignment.posts):
|
||||
cell2b.append(Paragraph("<seq id='counter'/>. "
|
||||
cell2b.append(
|
||||
Paragraph(
|
||||
"<seq id='counter'/>. "
|
||||
"__________________________________________",
|
||||
stylesheet['Signaturefield']))
|
||||
cell2b.append(Spacer(0, 0.2 * cm))
|
||||
@ -392,15 +409,15 @@ class AssignmentPDF(PDFView):
|
||||
|
||||
# Left side
|
||||
cell3a = []
|
||||
cell3a.append(Paragraph("%s:" % (_("Vote results")),
|
||||
stylesheet['Heading4']))
|
||||
cell3a.append(Paragraph(
|
||||
"%s:" % (_("Vote results")), stylesheet['Heading4']))
|
||||
|
||||
if polls.count() == 1:
|
||||
cell3a.append(Paragraph("%s %s" % (polls.count(), _("ballot")),
|
||||
stylesheet['Normal']))
|
||||
cell3a.append(Paragraph(
|
||||
"%s %s" % (polls.count(), _("ballot")), stylesheet['Normal']))
|
||||
elif polls.count() > 1:
|
||||
cell3a.append(Paragraph("%s %s" % (polls.count(), _("ballots")),
|
||||
stylesheet['Normal']))
|
||||
cell3a.append(Paragraph(
|
||||
"%s %s" % (polls.count(), _("ballots")), stylesheet['Normal']))
|
||||
|
||||
# Add table head row
|
||||
headrow = []
|
||||
@ -409,23 +426,23 @@ class AssignmentPDF(PDFView):
|
||||
headrow.append("%s." % poll.get_ballot())
|
||||
data_votes.append(headrow)
|
||||
|
||||
|
||||
# Add result rows
|
||||
elected_candidates = list(assignment.elected)
|
||||
for candidate, poll_list in vote_results.iteritems():
|
||||
row = []
|
||||
|
||||
candidate_string = candidate.user.get_full_name()
|
||||
candidate_string = candidate.clean_name
|
||||
if candidate in elected_candidates:
|
||||
candidate_string = "* " + candidate_string
|
||||
if candidate.group:
|
||||
candidate_string += "\n(%s)" % candidate.group
|
||||
if candidate.name_suffix:
|
||||
candidate_string += "\n(%s)" % candidate.name_suffix
|
||||
row.append(candidate_string)
|
||||
for vote in poll_list:
|
||||
if vote == None:
|
||||
if vote is None:
|
||||
row.append('–')
|
||||
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(
|
||||
_("Y: %(YES)s\nN: %(NO)s\nA: %(ABSTAIN)s")
|
||||
% {'YES': vote['Yes'], 'NO': vote['No'],
|
||||
'ABSTAIN': vote['Abstain']})
|
||||
elif 'Votes' in vote:
|
||||
@ -455,8 +472,7 @@ class AssignmentPDF(PDFView):
|
||||
('LINEABOVE', (0, 0), (-1, 0), 2, colors.black),
|
||||
('LINEABOVE', (0, 1), (-1, 1), 1, 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
|
||||
data = []
|
||||
@ -470,14 +486,15 @@ class AssignmentPDF(PDFView):
|
||||
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'),
|
||||
]))
|
||||
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))
|
||||
|
||||
# text
|
||||
story.append(Paragraph("%s" % assignment.description.replace('\r\n',
|
||||
story.append(Paragraph(
|
||||
"%s" % assignment.description.replace('\r\n',
|
||||
'<br/>'), stylesheet['Paragraph']))
|
||||
|
||||
|
||||
@ -502,13 +519,15 @@ class AssignmentPollPDF(PDFView):
|
||||
return super(AssignmentPollPDF, self).get(request, *args, **kwargs)
|
||||
|
||||
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())
|
||||
return filename
|
||||
|
||||
def get_template(self, buffer):
|
||||
return SimpleDocTemplate(buffer, topMargin=-6, bottomMargin=-6,
|
||||
leftMargin=0, rightMargin=0, showBoundary=False)
|
||||
return SimpleDocTemplate(
|
||||
buffer, topMargin=-6, bottomMargin=-6, leftMargin=0, rightMargin=0,
|
||||
showBoundary=False)
|
||||
|
||||
def build_document(self, pdf_document, story):
|
||||
pdf_document.build(story)
|
||||
@ -518,17 +537,22 @@ class AssignmentPollPDF(PDFView):
|
||||
circle = "<img src='%s' width='15' height='15'/> " % imgpath
|
||||
cell = []
|
||||
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']))
|
||||
cell.append(Paragraph(self.poll.assignment.polldescription,
|
||||
cell.append(Paragraph(
|
||||
self.poll.assignment.polldescription,
|
||||
stylesheet['Ballot_subtitle']))
|
||||
options = self.poll.get_options().order_by('candidate')
|
||||
options = self.poll.get_options()
|
||||
|
||||
ballot_string = _("%d. ballot") % self.poll.get_ballot()
|
||||
candidate_string = ungettext("%d candidate", "%d candidates",
|
||||
len(options)) % len(options)
|
||||
available_posts_string = _("%d available posts") % self.poll.assignment.posts
|
||||
cell.append(Paragraph("%s, %s, %s" % (ballot_string, candidate_string,
|
||||
candidate_string = ungettext(
|
||||
"%d candidate", "%d candidates", len(options)) % len(options)
|
||||
available_posts_string = ungettext(
|
||||
"%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']))
|
||||
cell.append(Spacer(0, 0.4 * cm))
|
||||
|
||||
@ -539,9 +563,9 @@ class AssignmentPollPDF(PDFView):
|
||||
|
||||
# set number of ballot papers
|
||||
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":
|
||||
number = int(Profile.objects.count())
|
||||
number = int(User.objects.count())
|
||||
else: # ballot_papers_selection == "CUSTOM_NUMBER"
|
||||
number = int(ballot_papers_number)
|
||||
number = max(1, number)
|
||||
@ -550,15 +574,17 @@ class AssignmentPollPDF(PDFView):
|
||||
if self.poll.yesnoabstain:
|
||||
for option in options:
|
||||
candidate = option.candidate
|
||||
cell.append(Paragraph(candidate.user.get_full_name(),
|
||||
stylesheet['Ballot_option_name']))
|
||||
if candidate.name_surfix:
|
||||
cell.append(Paragraph("(%s)" % candidate.name_surfix,
|
||||
cell.append(Paragraph(
|
||||
candidate.clean_name, stylesheet['Ballot_option_name']))
|
||||
if candidate.name_suffix:
|
||||
cell.append(Paragraph(
|
||||
"(%s)" % candidate.name_suffix,
|
||||
stylesheet['Ballot_option_group']))
|
||||
else:
|
||||
cell.append(Paragraph(" ",
|
||||
stylesheet['Ballot_option_group']))
|
||||
cell.append(Paragraph(circle + _("Yes") + " " * 3 + circle
|
||||
cell.append(Paragraph(
|
||||
" ", stylesheet['Ballot_option_group']))
|
||||
cell.append(Paragraph(
|
||||
circle + _("Yes") + " " * 3 + circle
|
||||
+ _("No") + " " * 3 + circle + _("Abstention"),
|
||||
stylesheet['Ballot_option_YNA']))
|
||||
# print ballot papers
|
||||
@ -576,14 +602,16 @@ class AssignmentPollPDF(PDFView):
|
||||
else:
|
||||
for option in options:
|
||||
candidate = option.candidate
|
||||
cell.append(Paragraph(circle + candidate.user.get_full_name(),
|
||||
cell.append(Paragraph(
|
||||
circle + candidate.clean_name,
|
||||
stylesheet['Ballot_option_name']))
|
||||
if candidate.group:
|
||||
cell.append(Paragraph("(%s)" % candidate.group,
|
||||
if candidate.name_suffix:
|
||||
cell.append(Paragraph(
|
||||
"(%s)" % candidate.name_suffix,
|
||||
stylesheet['Ballot_option_group_right']))
|
||||
else:
|
||||
cell.append(Paragraph(" ",
|
||||
stylesheet['Ballot_option_group_right']))
|
||||
cell.append(Paragraph(
|
||||
" ", stylesheet['Ballot_option_group_right']))
|
||||
# print ballot papers
|
||||
for user in xrange(number / 2):
|
||||
data.append([cell, cell])
|
||||
@ -597,9 +625,9 @@ class AssignmentPollPDF(PDFView):
|
||||
else:
|
||||
t = Table(data, 10.5 * cm, 29.7 * cm)
|
||||
|
||||
t.setStyle(TableStyle([('GRID', (0, 0), (-1, -1), 0.25, colors.grey),
|
||||
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||
]))
|
||||
t.setStyle(TableStyle([
|
||||
('GRID', (0, 0), (-1, -1), 0.25, colors.grey),
|
||||
('VALIGN', (0, 0), (-1, -1), 'TOP')]))
|
||||
story.append(t)
|
||||
|
||||
|
||||
@ -619,8 +647,7 @@ class Config(FormView):
|
||||
'assignment_pdf_title': config['assignment_pdf_title'],
|
||||
'assignment_pdf_preamble': config['assignment_pdf_preamble'],
|
||||
'assignment_poll_vote_values':
|
||||
config['assignment_poll_vote_values'],
|
||||
}
|
||||
config['assignment_poll_vote_values']}
|
||||
|
||||
def form_valid(self, form):
|
||||
if form.cleaned_data['assignment_publish_winner_results_only']:
|
||||
@ -637,8 +664,8 @@ class Config(FormView):
|
||||
form.cleaned_data['assignment_pdf_preamble']
|
||||
config['assignment_poll_vote_values'] = \
|
||||
form.cleaned_data['assignment_poll_vote_values']
|
||||
messages.success(self.request,
|
||||
_('Election settings successfully saved.'))
|
||||
messages.success(
|
||||
self.request, _('Election settings successfully saved.'))
|
||||
return super(Config, self).form_valid(form)
|
||||
|
||||
|
||||
@ -648,10 +675,11 @@ def register_tab(request):
|
||||
title=_('Elections'),
|
||||
app='assignment',
|
||||
url=reverse('assignment_overview'),
|
||||
permission=request.user.has_perm('assignment.can_see_assignment')
|
||||
or request.user.has_perm('assignment.can_nominate_other')
|
||||
or request.user.has_perm('assignment.can_nominate_self')
|
||||
or request.user.has_perm('assignment.can_manage_assignment'),
|
||||
permission=(
|
||||
request.user.has_perm('assignment.can_see_assignment') or
|
||||
request.user.has_perm('assignment.can_nominate_other') or
|
||||
request.user.has_perm('assignment.can_nominate_self') or
|
||||
request.user.has_perm('assignment.can_manage_assignment')),
|
||||
selected=selected,
|
||||
)
|
||||
|
||||
@ -660,6 +688,7 @@ def get_widgets(request):
|
||||
return [
|
||||
Widget(
|
||||
name='assignments',
|
||||
display_name=_('Elections'),
|
||||
template='assignment/widget.html',
|
||||
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.config.models import config
|
||||
|
||||
|
||||
class GeneralConfigForm(forms.Form, CssClassMixin):
|
||||
event_name = forms.CharField(
|
||||
@ -56,13 +54,13 @@ class GeneralConfigForm(forms.Form, CssClassMixin):
|
||||
required=False,
|
||||
)
|
||||
|
||||
frontpage_title = forms.CharField(
|
||||
welcome_title = forms.CharField(
|
||||
widget=forms.TextInput(),
|
||||
label=_("Title"),
|
||||
required=False,
|
||||
)
|
||||
|
||||
frontpage_welcometext = forms.CharField(
|
||||
welcome_text = forms.CharField(
|
||||
widget=forms.Textarea(),
|
||||
label=_("Welcome text"),
|
||||
required=False,
|
||||
|
@ -69,7 +69,6 @@ class Config(object):
|
||||
def __contains__(self, item):
|
||||
return ConfigStore.objects.filter(key=item).exists()
|
||||
|
||||
|
||||
config = Config()
|
||||
|
||||
|
||||
@ -81,17 +80,13 @@ def default_config(sender, key, **kwargs):
|
||||
return {
|
||||
'event_name': 'OpenSlides',
|
||||
'event_description':
|
||||
_('Presentation system for agenda, motions and elections'),
|
||||
_('Presentation and assembly system'),
|
||||
'event_date': '',
|
||||
'event_location': '',
|
||||
'event_organizer': '',
|
||||
'presentation': '',
|
||||
'frontpage_title': _('Welcome'),
|
||||
'frontpage_welcometext': _('Welcome to OpenSlides!'),
|
||||
'show_help_text': True,
|
||||
'help_text': _("Get professional support for OpenSlides on %s.") %
|
||||
"<a href='http://openslides.org/' target='_blank'> \
|
||||
www.openslides.org</a>",
|
||||
'welcome_title': _('Welcome to OpenSlides'),
|
||||
'welcome_text': _('[Place for your welcome text.]'),
|
||||
'system_enable_anonymous': False,
|
||||
}.get(key)
|
||||
|
||||
@ -127,11 +122,9 @@ def set_submenu(sender, request, context, **kwargs):
|
||||
(reverse('config_%s' % appname), _(title), selected)
|
||||
)
|
||||
|
||||
menu_links.append (
|
||||
(reverse('config_version'), _('Version'),
|
||||
request.path == reverse('config_version'))
|
||||
)
|
||||
menu_links.append((
|
||||
reverse('config_version'), _('Version'),
|
||||
request.path == reverse('config_version')))
|
||||
|
||||
context.update({
|
||||
'menu_links': menu_links,
|
||||
})
|
||||
'menu_links': menu_links})
|
||||
|
@ -25,9 +25,9 @@
|
||||
</fieldset>
|
||||
<p></p>
|
||||
<fieldset>
|
||||
<legend>{% trans "Frontpage" %}</legend>
|
||||
<legend>{% trans "Welcome Widget" %}</legend>
|
||||
{% for field in form %}
|
||||
{% if "id_frontpage" in field.label_tag %}
|
||||
{% if "id_welcome" in field.label_tag %}
|
||||
<p>
|
||||
{{ field.errors }}
|
||||
{{ field.required }}
|
||||
|
@ -12,18 +12,18 @@
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.models import Group, Permission
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.importlib import import_module
|
||||
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.views import FormView, TemplateView
|
||||
from .forms import GeneralConfigForm
|
||||
from .models import config
|
||||
|
||||
from openslides.config.forms import GeneralConfigForm
|
||||
from openslides.config.models import config
|
||||
# TODO: Do not import the participant module in config
|
||||
from openslides.participant.api import get_or_create_anonymous_group
|
||||
|
||||
|
||||
class GeneralConfig(FormView):
|
||||
@ -41,8 +41,8 @@ class GeneralConfig(FormView):
|
||||
'event_date': config['event_date'],
|
||||
'event_location': config['event_location'],
|
||||
'event_organizer': config['event_organizer'],
|
||||
'frontpage_title': config['frontpage_title'],
|
||||
'frontpage_welcometext': config['frontpage_welcometext'],
|
||||
'welcome_title': config['welcome_title'],
|
||||
'welcome_text': config['welcome_text'],
|
||||
'system_enable_anonymous': config['system_enable_anonymous'],
|
||||
}
|
||||
|
||||
@ -54,34 +54,19 @@ class GeneralConfig(FormView):
|
||||
config['event_location'] = form.cleaned_data['event_location']
|
||||
config['event_organizer'] = form.cleaned_data['event_organizer']
|
||||
|
||||
# frontpage
|
||||
config['frontpage_title'] = form.cleaned_data['frontpage_title']
|
||||
config['frontpage_welcometext'] = \
|
||||
form.cleaned_data['frontpage_welcometext']
|
||||
# welcome widget
|
||||
config['welcome_title'] = form.cleaned_data['welcome_title']
|
||||
config['welcome_text'] = form.cleaned_data['welcome_text']
|
||||
|
||||
# system
|
||||
if form.cleaned_data['system_enable_anonymous']:
|
||||
config['system_enable_anonymous'] = True
|
||||
# check for Anonymous group and (re)create it as needed
|
||||
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.'))
|
||||
get_or_create_anonymous_group()
|
||||
else:
|
||||
config['system_enable_anonymous'] = False
|
||||
|
||||
messages.success(self.request,
|
||||
_('General settings successfully saved.'))
|
||||
messages.success(
|
||||
self.request, _('General settings successfully saved.'))
|
||||
return super(GeneralConfig, self).form_valid(form)
|
||||
|
||||
|
||||
@ -94,7 +79,14 @@ class VersionConfig(TemplateView):
|
||||
|
||||
def get_context_data(self, **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:
|
||||
try:
|
||||
mod = import_module(plugin)
|
||||
@ -105,7 +97,6 @@ class VersionConfig(TemplateView):
|
||||
plugin_name = mod.NAME
|
||||
except AttributeError:
|
||||
plugin_name = mod.__name__.split('.')[0]
|
||||
|
||||
context['versions'].append((plugin_name, plugin_version))
|
||||
return context
|
||||
|
||||
|
@ -13,15 +13,12 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
_fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
|
||||
def _fs2unicode(s):
|
||||
if isinstance(s, unicode):
|
||||
return s
|
||||
return s.decode(_fs_encoding)
|
||||
from openslides.main import fs2unicode
|
||||
|
||||
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',)
|
||||
|
||||
LOGIN_URL = '/login/'
|
||||
@ -35,6 +32,7 @@ ugettext = lambda s: s
|
||||
LANGUAGES = (
|
||||
('de', ugettext('German')),
|
||||
('en', ugettext('English')),
|
||||
('fr', ugettext('French')),
|
||||
)
|
||||
|
||||
|
||||
@ -47,12 +45,12 @@ USE_I18N = True
|
||||
USE_L10N = True
|
||||
|
||||
LOCALE_PATHS = (
|
||||
_fs2unicode(os.path.join(SITE_ROOT, 'locale')),
|
||||
fs2unicode(os.path.join(SITE_ROOT, 'locale')),
|
||||
)
|
||||
|
||||
# Absolute path to the directory that holds media.
|
||||
# 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
|
||||
# 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``
|
||||
# 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
|
||||
# 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)
|
||||
# Examples: "/home/media/lawrence.com/extra-static/"
|
||||
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)
|
||||
@ -105,7 +103,7 @@ TEMPLATE_DIRS = (
|
||||
# "C:/www/django/templates".
|
||||
# Always use forward slashes, even on Windows.
|
||||
# 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 = (
|
||||
@ -119,7 +117,7 @@ INSTALLED_APPS = (
|
||||
'openslides.poll',
|
||||
'openslides.projector',
|
||||
'openslides.agenda',
|
||||
'openslides.application',
|
||||
'openslides.motion',
|
||||
'openslides.assignment',
|
||||
'openslides.participant',
|
||||
'openslides.config',
|
||||
@ -131,6 +129,15 @@ TEMPLATE_CONTEXT_PROCESSORS = (
|
||||
'django.core.context_processors.request',
|
||||
'django.core.context_processors.i18n',
|
||||
'django.core.context_processors.static',
|
||||
'openslides.utils.utils.revision',
|
||||
'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):
|
||||
$ 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
|
||||
Don't forget the js-file: locale/de/LC_MESSAGES/djangojs.po
|
||||
(Search for "fuzzy" and empty msgstr entries.)
|
||||
|
||||
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
|
||||
from __future__ import with_statement
|
||||
|
||||
import os
|
||||
import sys
|
||||
import optparse
|
||||
import socket
|
||||
import time
|
||||
import threading
|
||||
import base64
|
||||
import ctypes
|
||||
import optparse
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
import webbrowser
|
||||
|
||||
import django.conf
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
from openslides import get_version
|
||||
|
||||
CONFIG_TEMPLATE = """#!/usr/bin/env python
|
||||
# -*- 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
|
||||
# (Default for relaeses: 'False')
|
||||
# (Default for releases: 'False')
|
||||
DEBUG = False
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
|
||||
DBPATH = %(dbpath)r
|
||||
DBPATH = %(dbpath)s
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
@ -64,51 +69,89 @@ INSTALLED_APPS += INSTALLED_PLUGINS
|
||||
|
||||
KEY_LENGTH = 30
|
||||
|
||||
|
||||
_fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
|
||||
def _fs2unicode(s):
|
||||
if isinstance(s, unicode):
|
||||
return s
|
||||
return s.decode(_fs_encoding)
|
||||
# sentinel used to signal that the database ought to be stored
|
||||
# relative to the portable's directory
|
||||
_portable_db_path = object()
|
||||
|
||||
|
||||
def main(argv=None, opt_defaults=None):
|
||||
def process_options(argv=None):
|
||||
if argv is None:
|
||||
argv = sys.argv[1:]
|
||||
|
||||
parser = optparse.OptionParser(
|
||||
description="Run openslides using django's builtin webserver")
|
||||
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("-a", "--address", help="IP Address to listen on.")
|
||||
parser.add_option("-p", "--port", type="int", help="Port to listen on.")
|
||||
parser.add_option(
|
||||
"--syncdb", action="store_true",
|
||||
help="Update/create database before starting the server")
|
||||
help="Update/create database before starting the server.")
|
||||
parser.add_option(
|
||||
"--reset-admin", action="store_true",
|
||||
help="Make sure the user 'admin' exists and uses 'admin' as password")
|
||||
parser.add_option("-s", "--settings", help="Path to the setting.")
|
||||
help="Make sure the user 'admin' exists and uses 'admin' as password.")
|
||||
parser.add_option(
|
||||
"--no-reload", action="store_true", help="Do not reload the development server")
|
||||
|
||||
if not opt_defaults is None:
|
||||
parser.set_defaults(**opt_defaults)
|
||||
"-s", "--settings", help="Path to the openslides configuration.")
|
||||
parser.add_option(
|
||||
"--no-reload", action="store_true",
|
||||
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)
|
||||
if opts.version:
|
||||
print get_version()
|
||||
exit(0)
|
||||
if args:
|
||||
sys.stderr.write("This command does not take arguments!\n\n")
|
||||
parser.print_help()
|
||||
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
|
||||
settings = opts.settings or \
|
||||
os.path.join(os.path.expanduser('~'),'.openslides','openslides_personal_settings.py')
|
||||
settings_path = opts.settings
|
||||
if settings_path is None:
|
||||
settings_path = get_user_config_path('openslides', 'settings.py')
|
||||
|
||||
# Create settings if necessary
|
||||
if not os.path.exists(settings):
|
||||
create_settings(settings)
|
||||
if not os.path.exists(settings_path):
|
||||
create_settings(settings_path, database_path)
|
||||
|
||||
# Set the django environment to the settings
|
||||
setup_django_environment(settings)
|
||||
setup_django_environment(settings_path)
|
||||
|
||||
# Find url to openslides
|
||||
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)
|
||||
|
||||
|
||||
def create_settings(settings):
|
||||
path_to_dir = os.path.dirname(settings)
|
||||
def create_settings(settings_path, database_path=None):
|
||||
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)),
|
||||
dbpath=_fs2unicode((os.path.join(path_to_dir, 'database.db'))))
|
||||
dbpath=dbpath_value)
|
||||
|
||||
if not os.path.exists(path_to_dir):
|
||||
os.makedirs(path_to_dir)
|
||||
if not os.path.exists(settings_module):
|
||||
os.makedirs(settings_module)
|
||||
|
||||
with open(settings, 'w') as settings_file:
|
||||
settings_file.write(setting_content)
|
||||
if not os.path.exists(os.path.dirname(database_path)):
|
||||
os.makedirs(os.path.dirname(database_path))
|
||||
|
||||
with open(settings_path, 'w') as file:
|
||||
file.write(settings_content)
|
||||
|
||||
|
||||
def setup_django_environment(settings):
|
||||
sys.path.append(os.path.dirname(settings))
|
||||
setting_module = os.path.basename(settings)[:-3]
|
||||
os.environ[django.conf.ENVIRONMENT_VARIABLE] = setting_module
|
||||
def setup_django_environment(settings_path):
|
||||
settings_file = os.path.basename(settings_path)
|
||||
settings_module_name = "".join(settings_file.split('.')[:-1])
|
||||
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):
|
||||
@ -186,6 +245,7 @@ def database_exists():
|
||||
from openslides.participant.models import User
|
||||
|
||||
try:
|
||||
# TODO: Use another model, the User could be deactivated
|
||||
User.objects.count()
|
||||
except DatabaseError:
|
||||
return False
|
||||
@ -200,6 +260,7 @@ def run_syncdb():
|
||||
# now initialize the database
|
||||
argv = ["", "syncdb", "--noinput"]
|
||||
execute_from_command_line(argv)
|
||||
execute_from_command_line(["", "loaddata", "groups_de"])
|
||||
|
||||
|
||||
def set_system_url(url):
|
||||
@ -223,7 +284,7 @@ def create_or_reset_admin_user():
|
||||
except User.DoesNotExist:
|
||||
admin = User()
|
||||
admin.username = 'admin'
|
||||
admin.last_name = 'Admin User'
|
||||
admin.last_name = 'Administrator'
|
||||
print("Created default admin user")
|
||||
|
||||
admin.is_superuser = True
|
||||
@ -252,33 +313,60 @@ def start_browser(url):
|
||||
t = threading.Thread(target=f)
|
||||
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
|
||||
# since it is essentially a small wrapper that embeds the
|
||||
# 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:
|
||||
default_settings = os.path.join(portable_dir, "openslides",
|
||||
"openslides_personal_settings.py")
|
||||
else:
|
||||
import ctypes
|
||||
exename = os.path.basename(sys.executable).lower()
|
||||
if exename != "openslides.exe":
|
||||
raise Exception(
|
||||
"Cannot determine portable path when "
|
||||
"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")
|
||||
SHGetFolderPath = shell32.SHGetFolderPathW
|
||||
SHGetFolderPath.argtypes = (ctypes.c_void_p, ctypes.c_int,
|
||||
ctypes.c_void_p, ctypes.c_uint32, ctypes.c_wchar_p)
|
||||
SHGetFolderPath.argtypes = (
|
||||
ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_uint32,
|
||||
ctypes.c_wchar_p)
|
||||
SHGetFolderPath.restype = ctypes.c_uint32
|
||||
|
||||
CSIDL_LOCAL_APPDATA = 0x001c
|
||||
@ -288,10 +376,8 @@ def win32_portable_main(argv=None):
|
||||
res = SHGetFolderPath(0, CSIDL_LOCAL_APPDATA, 0, 0, buf)
|
||||
if res != 0:
|
||||
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__":
|
||||
|
@ -1,50 +1,50 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- 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.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
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.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"))
|
||||
text = forms.CharField(widget=forms.Textarea(), label=_("Text"))
|
||||
reason = forms.CharField(widget=forms.Textarea(), required=False,
|
||||
label=_("Reason"))
|
||||
reason = forms.CharField(
|
||||
widget=forms.Textarea(), required=False, label=_("Reason"))
|
||||
|
||||
|
||||
class ApplicationFormTrivialChanges(ApplicationForm):
|
||||
trivial_change = forms.BooleanField(required=False,
|
||||
label=_("Trivial change"),
|
||||
class MotionFormTrivialChanges(MotionForm):
|
||||
trivial_change = forms.BooleanField(
|
||||
required=False, label=_("Trivial change"),
|
||||
help_text=_("Trivial changes don't create a new version."))
|
||||
|
||||
|
||||
class ApplicationManagerForm(forms.ModelForm, CssClassMixin):
|
||||
submitter = PersonFormField()
|
||||
class MotionManagerForm(forms.ModelForm, CssClassMixin):
|
||||
submitter = PersonFormField(label=_("Submitter"))
|
||||
|
||||
class Meta:
|
||||
model = Application
|
||||
model = Motion
|
||||
exclude = ('number', 'status', 'permitted', 'log', 'supporter')
|
||||
|
||||
|
||||
class ApplicationManagerFormSupporter(ApplicationManagerForm):
|
||||
class MotionManagerFormSupporter(MotionManagerForm):
|
||||
# TODO: Do not show the submitter in the user-list
|
||||
supporter = MultiplePersonFormField(required=False, label=_("Supporters"))
|
||||
|
||||
|
||||
class ApplicationImportForm(forms.Form, CssClassMixin):
|
||||
class MotionImportForm(forms.Form, CssClassMixin):
|
||||
csvfile = forms.FileField(
|
||||
widget=forms.FileInput(attrs={'size': '50'}),
|
||||
label=_("CSV File"),
|
||||
@ -58,7 +58,7 @@ class ApplicationImportForm(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'}),
|
||||
label=_("Number of (minimum) required supporters for a motion"),
|
||||
initial=4,
|
||||
@ -66,12 +66,12 @@ class ConfigForm(forms.Form, CssClassMixin):
|
||||
max_value=8,
|
||||
help_text=_("Choose 0 to disable the supporting system"),
|
||||
)
|
||||
application_preamble = forms.CharField(
|
||||
motion_preamble = forms.CharField(
|
||||
widget=forms.TextInput(),
|
||||
required=False,
|
||||
label=_("Motion preamble")
|
||||
)
|
||||
application_pdf_ballot_papers_selection = forms.ChoiceField(
|
||||
motion_pdf_ballot_papers_selection = forms.ChoiceField(
|
||||
widget=forms.Select(),
|
||||
required=False,
|
||||
label=_("Number of ballot papers (selection)"),
|
||||
@ -81,24 +81,24 @@ class ConfigForm(forms.Form, CssClassMixin):
|
||||
("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'}),
|
||||
required=False,
|
||||
min_value=1,
|
||||
label=_("Custom number of ballot papers")
|
||||
)
|
||||
application_pdf_title = forms.CharField(
|
||||
motion_pdf_title = forms.CharField(
|
||||
widget=forms.TextInput(),
|
||||
required=False,
|
||||
label=_("Title for PDF document (all motions)")
|
||||
)
|
||||
application_pdf_preamble = forms.CharField(
|
||||
motion_pdf_preamble = forms.CharField(
|
||||
widget=forms.Textarea(),
|
||||
required=False,
|
||||
label=_("Preamble text for PDF document (all motions)")
|
||||
)
|
||||
|
||||
application_allow_trivial_change = forms.BooleanField(
|
||||
motion_allow_trivial_change = forms.BooleanField(
|
||||
label=_("Allow trivial changes"),
|
||||
help_text=_('Warning: Trivial changes undermine the motions '
|
||||
'autorisation system.'),
|
@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- 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.
|
||||
: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.person import PersonField
|
||||
|
||||
from openslides.config.models import config
|
||||
from openslides.config.signals import default_config_value
|
||||
|
||||
from openslides.poll.models import (BaseOption, BasePoll, CountVotesCast,
|
||||
CountInvalid, BaseVote)
|
||||
|
||||
from openslides.poll.models import (
|
||||
BaseOption, BasePoll, CountVotesCast, CountInvalid, BaseVote)
|
||||
from openslides.participant.models import User
|
||||
from openslides.projector.api import register_slidemodel
|
||||
from openslides.projector.models import SlideMixin
|
||||
|
||||
from openslides.agenda.models import Item
|
||||
|
||||
|
||||
class ApplicationSupporter(models.Model):
|
||||
application = models.ForeignKey("Application")
|
||||
class MotionSupporter(models.Model):
|
||||
motion = models.ForeignKey("Motion")
|
||||
person = PersonField()
|
||||
|
||||
|
||||
class Application(models.Model, SlideMixin):
|
||||
prefix = "application"
|
||||
class Motion(models.Model, SlideMixin):
|
||||
prefix = "motion"
|
||||
STATUS = (
|
||||
('pub', _('Published')),
|
||||
('per', _('Permitted')),
|
||||
@ -67,17 +64,17 @@ class Application(models.Model, SlideMixin):
|
||||
number = models.PositiveSmallIntegerField(blank=True, null=True,
|
||||
unique=True)
|
||||
status = models.CharField(max_length=3, choices=STATUS, default='pub')
|
||||
permitted = models.ForeignKey('AVersion', related_name='permitted', \
|
||||
permitted = models.ForeignKey('AVersion', related_name='permitted',
|
||||
null=True, blank=True)
|
||||
log = models.TextField(blank=True, null=True)
|
||||
|
||||
@property
|
||||
def last_version(self):
|
||||
"""
|
||||
Return last version of the application.
|
||||
Return last version of the motion.
|
||||
"""
|
||||
try:
|
||||
return AVersion.objects.filter(application=self).order_by('id') \
|
||||
return AVersion.objects.filter(motion=self).order_by('id') \
|
||||
.reverse()[0]
|
||||
except IndexError:
|
||||
return None
|
||||
@ -85,7 +82,7 @@ class Application(models.Model, SlideMixin):
|
||||
@property
|
||||
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:
|
||||
return self.permitted
|
||||
@ -100,29 +97,29 @@ class Application(models.Model, SlideMixin):
|
||||
self.save(nonewversion=True)
|
||||
version.rejected = False
|
||||
version.save()
|
||||
self.writelog(_("Version %d authorized") % (version.aid, ),
|
||||
user)
|
||||
self.writelog(_("Version %d authorized") % version.aid, user)
|
||||
|
||||
def reject_version(self, version, user=None):
|
||||
if version.id > self.permitted.id:
|
||||
version.rejected = True
|
||||
version.save()
|
||||
self.writelog(pgettext("Rejected means not authorized", "Version %d rejected")
|
||||
% (version.aid, ), user)
|
||||
self.writelog(pgettext(
|
||||
"Rejected means not authorized", "Version %d rejected")
|
||||
% version.aid, user)
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
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
|
||||
def creation_time(self):
|
||||
"""
|
||||
Return the time of the creation of the application.
|
||||
Return the time of the creation of the motion.
|
||||
"""
|
||||
try:
|
||||
return self.versions[0].time
|
||||
@ -132,60 +129,63 @@ class Application(models.Model, SlideMixin):
|
||||
@property
|
||||
def notes(self):
|
||||
"""
|
||||
Return some information of the application.
|
||||
Return some information of the motion.
|
||||
"""
|
||||
note = []
|
||||
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:
|
||||
note.append(_("Not yet authorized."))
|
||||
note.append(ugettext("Not yet authorized."))
|
||||
elif self.unpermitted_changes and self.permitted:
|
||||
note.append(_("Not yet authorized changes."))
|
||||
note.append(ugettext("Not yet authorized changes."))
|
||||
return note
|
||||
|
||||
@property
|
||||
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.
|
||||
TODO: rename the property in unchecked__changes
|
||||
"""
|
||||
if (self.last_version != self.permitted
|
||||
and not self.last_version.rejected):
|
||||
if (self.last_version != self.permitted and
|
||||
not self.last_version.rejected):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
def supporters(self):
|
||||
for object in self.applicationsupporter_set.all():
|
||||
yield object.person
|
||||
return sorted([object.person for object in self.motionsupporter_set.all()],
|
||||
key=lambda person: person.sort_name)
|
||||
|
||||
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
|
||||
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":
|
||||
return self.count_supporters() >= min_supporters
|
||||
else:
|
||||
return True
|
||||
|
||||
def count_supporters(self):
|
||||
return self.applicationsupporter_set.count()
|
||||
return self.motionsupporter_set.count()
|
||||
|
||||
@property
|
||||
def missing_supporters(self):
|
||||
"""
|
||||
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()
|
||||
if delta > 0:
|
||||
return delta
|
||||
@ -194,15 +194,16 @@ class Application(models.Model, SlideMixin):
|
||||
|
||||
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:
|
||||
return
|
||||
last_version = self.last_version
|
||||
fields = ["text", "title", "reason"]
|
||||
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 not changed_fields:
|
||||
return # No changes
|
||||
@ -214,35 +215,37 @@ class Application(models.Model, SlideMixin):
|
||||
last_version.save()
|
||||
|
||||
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]
|
||||
|
||||
self.writelog(_("Trivial changes to version %(version)d; "
|
||||
self.writelog(
|
||||
_("Trivial changes to version %(version)d; "
|
||||
"changed fields: %(changed_fields)s")
|
||||
% dict(version=last_version.aid,
|
||||
changed_fields=", ".join(field_names)))
|
||||
return # Done
|
||||
|
||||
version = AVersion(title=getattr(self, 'title', ''),
|
||||
version = AVersion(
|
||||
title=getattr(self, 'title', ''),
|
||||
text=getattr(self, 'text', ''),
|
||||
reason=getattr(self, 'reason', ''),
|
||||
application=self)
|
||||
motion=self)
|
||||
version.save()
|
||||
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:
|
||||
is_manager = False
|
||||
|
||||
supporters = self.applicationsupporter_set.all()
|
||||
if (self.status == "pub"
|
||||
and supporters
|
||||
and not is_manager):
|
||||
supporters = self.motionsupporter_set.all()
|
||||
if (self.status == "pub" and
|
||||
supporters and not is_manager):
|
||||
supporters.delete()
|
||||
self.writelog(_("Supporters removed"), user)
|
||||
|
||||
def reset(self, user):
|
||||
"""
|
||||
Reset the application.
|
||||
Reset the motion.
|
||||
"""
|
||||
self.status = "pub"
|
||||
self.permitted = None
|
||||
@ -251,44 +254,39 @@ class Application(models.Model, SlideMixin):
|
||||
|
||||
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:
|
||||
# TODO: Use own Exception
|
||||
raise NameError('Supporter can not be the submitter of a ' \
|
||||
'application.')
|
||||
if self.permitted is not None:
|
||||
# TODO: Use own Exception
|
||||
raise NameError('This application is already permitted.')
|
||||
raise NameError('Supporter can not be the submitter of a '
|
||||
'motion.')
|
||||
if not self.is_supporter(person):
|
||||
ApplicationSupporter(application=self, person=person).save()
|
||||
MotionSupporter(motion=self, person=person).save()
|
||||
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:
|
||||
object = self.applicationsupporter_set.get(user=user).delete()
|
||||
except ApplicationSupporter.DoesNotExist:
|
||||
self.motionsupporter_set.get(person=person).delete()
|
||||
except MotionSupporter.DoesNotExist:
|
||||
# TODO: Don't do nothing but raise a precise exception for the view
|
||||
pass
|
||||
else:
|
||||
self.writelog(_("Supporter: -%s") % (user))
|
||||
self.writelog(_("Supporter: -%s") % (person))
|
||||
|
||||
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:
|
||||
# TODO: Use own Exception
|
||||
raise NameError('This application has already a number.')
|
||||
raise NameError('This motion has already a number.')
|
||||
if number is None:
|
||||
try:
|
||||
number = Application.objects.aggregate(Max('number')) \
|
||||
['number__max'] + 1
|
||||
number = Motion.objects.aggregate(Max('number'))['number__max'] + 1
|
||||
except TypeError:
|
||||
number = 1
|
||||
self.number = number
|
||||
@ -298,7 +296,7 @@ class Application(models.Model, SlideMixin):
|
||||
|
||||
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")
|
||||
aversion = self.last_version
|
||||
@ -311,12 +309,10 @@ class Application(models.Model, SlideMixin):
|
||||
|
||||
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")
|
||||
#TODO: reject last version
|
||||
aversion = self.last_version
|
||||
#self.permitted = aversion
|
||||
if self.number is None:
|
||||
self.set_number()
|
||||
self.save()
|
||||
@ -324,10 +320,10 @@ class Application(models.Model, SlideMixin):
|
||||
|
||||
def set_status(self, user, status, force=False):
|
||||
"""
|
||||
Set the status of the application.
|
||||
Set the status of the motion.
|
||||
"""
|
||||
error = True
|
||||
for a, b in Application.STATUS:
|
||||
for a, b in Motion.STATUS:
|
||||
if status == a:
|
||||
error = False
|
||||
break
|
||||
@ -336,7 +332,7 @@ class Application(models.Model, SlideMixin):
|
||||
raise NameError(_('%s is not a valid status.') % status)
|
||||
if self.status == status:
|
||||
# TODO: Use the Right Error
|
||||
raise NameError(_('The motion status is already \'%s.\'') \
|
||||
raise NameError(_('The motion status is already \'%s.\'')
|
||||
% self.status)
|
||||
|
||||
actions = []
|
||||
@ -352,7 +348,7 @@ class Application(models.Model, SlideMixin):
|
||||
oldstatus = self.get_status_display()
|
||||
self.status = status
|
||||
self.save()
|
||||
self.writelog(_("Status modified")+": %s -> %s" \
|
||||
self.writelog(_("Status modified") + ": %s -> %s"
|
||||
% (oldstatus, self.get_status_display()), user)
|
||||
|
||||
def get_allowed_actions(self, user):
|
||||
@ -361,25 +357,25 @@ class Application(models.Model, SlideMixin):
|
||||
"""
|
||||
actions = []
|
||||
|
||||
# check if user allowed to withdraw an application
|
||||
# check if user allowed to withdraw an motion
|
||||
if ((self.status == "pub"
|
||||
and self.number
|
||||
and user == self.submitter)
|
||||
or (self.status == "pub"
|
||||
and self.number
|
||||
and user.has_perm("application.can_manage_application"))
|
||||
and user.has_perm("motion.can_manage_motion"))
|
||||
or (self.status == "per"
|
||||
and user == self.submitter)
|
||||
or (self.status == "per"
|
||||
and user.has_perm("application.can_manage_application"))):
|
||||
and user.has_perm("motion.can_manage_motion"))):
|
||||
actions.append("wit")
|
||||
#Check if the user can review the application
|
||||
#Check if the user can review the motion
|
||||
if (self.status == "rev"
|
||||
and (self.submitter == user
|
||||
or user.has_perm("application.can_manage_application"))):
|
||||
or user.has_perm("motion.can_manage_motion"))):
|
||||
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"
|
||||
and user != self.submitter
|
||||
and not self.is_supporter(user)):
|
||||
@ -388,22 +384,22 @@ class Application(models.Model, SlideMixin):
|
||||
if self.status == "pub" and self.is_supporter(user):
|
||||
actions.append("unsupport")
|
||||
|
||||
#Check if the user can edit the application
|
||||
#Check if the user can edit the motion
|
||||
if (user == self.submitter \
|
||||
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")
|
||||
|
||||
# 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
|
||||
if (user.has_perm("applicatoin.can_delete_all_applications") or
|
||||
(user.has_perm("application.can_manage_application") and
|
||||
if (user.has_perm("motion.can_delete_all_motions") or
|
||||
(user.has_perm("motion.can_manage_motion") and
|
||||
self.number is None) or
|
||||
(self.submitter == user and self.number is None)):
|
||||
actions.append("delete")
|
||||
|
||||
#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
|
||||
|
||||
if self.status == "pub":
|
||||
@ -427,24 +423,26 @@ class Application(models.Model, SlideMixin):
|
||||
|
||||
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
|
||||
"""
|
||||
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.')
|
||||
|
||||
|
||||
for item in Item.objects.filter(related_sid=self.sid):
|
||||
item.delete()
|
||||
super(Application, self).delete()
|
||||
super(Motion, self).delete()
|
||||
|
||||
def writelog(self, text, user=None):
|
||||
if not self.log:
|
||||
self.log = ""
|
||||
self.log += u"%s | %s" % (datetime.now().strftime("%d.%m.%Y %H:%M:%S"), _propper_unicode(text))
|
||||
if user is not None:
|
||||
if isinstance(user, User):
|
||||
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.save()
|
||||
|
||||
@ -458,7 +456,7 @@ class Application(models.Model, SlideMixin):
|
||||
def __getattr__(self, name):
|
||||
"""
|
||||
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'):
|
||||
try:
|
||||
@ -473,9 +471,9 @@ class Application(models.Model, SlideMixin):
|
||||
|
||||
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.set_options()
|
||||
self.writelog(_("Poll created"), user)
|
||||
@ -483,7 +481,7 @@ class Application(models.Model, SlideMixin):
|
||||
|
||||
@property
|
||||
def polls(self):
|
||||
return self.applicationpoll_set.all()
|
||||
return self.motionpoll_set.all()
|
||||
|
||||
@property
|
||||
def results(self):
|
||||
@ -497,29 +495,29 @@ class Application(models.Model, SlideMixin):
|
||||
for poll in self.polls:
|
||||
for option in poll.get_options():
|
||||
if option.get_votes().exists():
|
||||
results.append((option['Yes'], option['No'],
|
||||
results.append((
|
||||
option['Yes'], option['No'],
|
||||
option['Abstain'], poll.print_votesinvalid(),
|
||||
poll.print_votescast()))
|
||||
return results
|
||||
|
||||
|
||||
def slide(self):
|
||||
"""
|
||||
return the slide dict
|
||||
"""
|
||||
data = super(Application, self).slide()
|
||||
data['application'] = self
|
||||
data = super(Motion, self).slide()
|
||||
data['motion'] = self
|
||||
data['title'] = self.title
|
||||
data['template'] = 'projector/Application.html'
|
||||
data['template'] = 'projector/Motion.html'
|
||||
return data
|
||||
|
||||
def get_absolute_url(self, link='view'):
|
||||
if link == 'view':
|
||||
return reverse('application_view', args=[str(self.id)])
|
||||
return reverse('motion_view', args=[str(self.id)])
|
||||
if link == 'edit':
|
||||
return reverse('application_edit', args=[str(self.id)])
|
||||
return reverse('motion_edit', args=[str(self.id)])
|
||||
if link == 'delete':
|
||||
return reverse('application_delete', args=[str(self.id)])
|
||||
return reverse('motion_delete', args=[str(self.id)])
|
||||
|
||||
def __unicode__(self):
|
||||
try:
|
||||
@ -529,11 +527,12 @@ class Application(models.Model, SlideMixin):
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
('can_see_application', ugettext_noop("Can see motions")),
|
||||
('can_create_application', ugettext_noop("Can create motions")),
|
||||
('can_support_application', ugettext_noop("Can support motions")),
|
||||
('can_manage_application', ugettext_noop("Can manage motions")),
|
||||
('can_see_motion', ugettext_noop("Can see motions")),
|
||||
('can_create_motion', ugettext_noop("Can create motions")),
|
||||
('can_support_motion', ugettext_noop("Can support motions")),
|
||||
('can_manage_motion', ugettext_noop("Can manage motions")),
|
||||
)
|
||||
ordering = ('number',)
|
||||
|
||||
|
||||
class AVersion(models.Model):
|
||||
@ -542,7 +541,7 @@ class AVersion(models.Model):
|
||||
reason = models.TextField(null=True, blank=True, verbose_name=_("Reason"))
|
||||
rejected = models.BooleanField() # = Not Permitted
|
||||
time = models.DateTimeField(auto_now=True)
|
||||
application = models.ForeignKey(Application)
|
||||
motion = models.ForeignKey(Motion)
|
||||
|
||||
def __unicode__(self):
|
||||
return "%s %s" % (self.id, self.title)
|
||||
@ -553,31 +552,31 @@ class AVersion(models.Model):
|
||||
return self._aid
|
||||
except AttributeError:
|
||||
self._aid = AVersion.objects \
|
||||
.filter(application=self.application) \
|
||||
.filter(motion=self.motion) \
|
||||
.filter(id__lte=self.id).count()
|
||||
return self._aid
|
||||
|
||||
register_slidemodel(Application)
|
||||
register_slidemodel(Motion)
|
||||
|
||||
|
||||
class ApplicationVote(BaseVote):
|
||||
option = models.ForeignKey('ApplicationOption')
|
||||
class MotionVote(BaseVote):
|
||||
option = models.ForeignKey('MotionOption')
|
||||
|
||||
|
||||
class ApplicationOption(BaseOption):
|
||||
poll = models.ForeignKey('ApplicationPoll')
|
||||
vote_class = ApplicationVote
|
||||
class MotionOption(BaseOption):
|
||||
poll = models.ForeignKey('MotionPoll')
|
||||
vote_class = MotionVote
|
||||
|
||||
|
||||
class ApplicationPoll(BasePoll, CountInvalid, CountVotesCast):
|
||||
option_class = ApplicationOption
|
||||
vote_values = [ugettext_noop('Yes'), ugettext_noop('No'),
|
||||
ugettext_noop('Abstain')]
|
||||
class MotionPoll(BasePoll, CountInvalid, CountVotesCast):
|
||||
option_class = MotionOption
|
||||
vote_values = [
|
||||
ugettext_noop('Yes'), ugettext_noop('No'), ugettext_noop('Abstain')]
|
||||
|
||||
application = models.ForeignKey(Application)
|
||||
motion = models.ForeignKey(Motion)
|
||||
|
||||
def get_application(self):
|
||||
return self.application
|
||||
def get_motion(self):
|
||||
return self.motion
|
||||
|
||||
def set_options(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)
|
||||
|
||||
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):
|
||||
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):
|
||||
return {
|
||||
'application_min_supporters': 0,
|
||||
'application_preamble': _('The Assembly may decide,'),
|
||||
'application_pdf_ballot_papers_selection': 'CUSTOM_NUMBER',
|
||||
'application_pdf_ballot_papers_number': '8',
|
||||
'application_pdf_title': _('Motions'),
|
||||
'application_pdf_preamble': '',
|
||||
'application_allow_trivial_change': False,
|
||||
'motion_min_supporters': 0,
|
||||
'motion_preamble': _('The assembly may decide,'),
|
||||
'motion_pdf_ballot_papers_selection': 'CUSTOM_NUMBER',
|
||||
'motion_pdf_ballot_papers_number': '8',
|
||||
'motion_pdf_title': _('Motions'),
|
||||
'motion_pdf_preamble': '',
|
||||
'motion_allow_trivial_change': False,
|
||||
}.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 %}
|
||||
|
||||
{% block title %}{{ block.super }} – {% trans "Application settings" %}{% endblock %}
|
||||
{% block title %}{{ block.super }} – {% trans "Motion settings" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "Configuration" %}: {% trans "Applications" %}
|
||||
<h1>{% trans "Configuration" %}: {% trans "Motions" %}
|
||||
{% block config_submenu %}{{ block.super }}{% endblock %}
|
||||
</h1>
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
@ -14,8 +14,8 @@
|
||||
<button class="button" type="submit">
|
||||
<span class="icon ok">{% trans 'Save' %}</span>
|
||||
</button>
|
||||
<a href='{% url config_application %}'>
|
||||
<button class="button" type="button" onclick="window.location='{% url config_application %}'">
|
||||
<a href='{% url config_motion %}'>
|
||||
<button class="button" type="button" onclick="window.location='{% url config_motion %}'">
|
||||
<span class="icon cancel">{% trans 'Cancel' %}</span>
|
||||
</button>
|
||||
</a>
|
@ -4,7 +4,7 @@
|
||||
|
||||
{% block title %}
|
||||
{{ block.super }} –
|
||||
{% if application %}
|
||||
{% if motion %}
|
||||
{% trans "Edit motion" %}
|
||||
{% else %}
|
||||
{% trans "New motion" %}
|
||||
@ -13,7 +13,7 @@
|
||||
|
||||
{% block content %}
|
||||
<h1>
|
||||
{% if application %}
|
||||
{% if motion %}
|
||||
{% trans "Edit motion" %}
|
||||
{% else %}
|
||||
{% trans "New motion" %}
|
||||
@ -43,13 +43,12 @@
|
||||
<button class="btn" type="submit" name="apply">
|
||||
{% trans 'Apply' %}
|
||||
</button>
|
||||
<a href='{% url application_overview %}'>
|
||||
<button class="btn" type="button" onclick="window.location='{% url application_overview %}'">
|
||||
{% trans 'Cancel' %}</span>
|
||||
<a href='{% url motion_overview %}'>
|
||||
<button class="btn" type="button" onclick="window.location='{% url motion_overview %}'">
|
||||
{% trans 'Cancel' %}
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
<small>* {% trans "required" %}</small>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
@ -12,7 +12,10 @@
|
||||
</h1>
|
||||
|
||||
<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>
|
||||
{% trans 'Required CSV file encoding: UTF-8 (Unicode).' %}
|
||||
</p>
|
||||
@ -37,7 +40,7 @@
|
||||
<button class="btn btn-primary" type="submit">
|
||||
{% trans 'Import' %}
|
||||
</button>
|
||||
<a href='{% url application_overview %}' class="btn">
|
||||
<a href='{% url motionn_overview %}' class="btn">
|
||||
{% trans 'Cancel' %}
|
||||
</a>
|
||||
</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="rev" {% if 'rev' in request.GET.statusvalue %}selected{% endif %}>{% trans "Needs Review" %}</option>
|
||||
</select>
|
||||
|
||||
</form>
|
||||
<small><i>{{ applications|length }}
|
||||
{% blocktrans count counter=applications|length %}motion{% plural %}motions{% endblocktrans %}
|
||||
<small><i>{{ motions|length }}
|
||||
{% blocktrans count counter=motions|length context "number of motions"%}motion{% plural %}motions{% endblocktrans %}
|
||||
</i></small>
|
||||
<table class="table table-striped table-bordered">
|
||||
<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 style="width: 1px;">{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
{% for app_info in applications %}
|
||||
{% with application=app_info.application useractions=app_info.actions %}
|
||||
{% for app_info in motions %}
|
||||
{% with motion=app_info.motion useractions=app_info.actions %}
|
||||
<tr class="{% cycle '' 'odd' %}
|
||||
{% if application.active %}activeline{% endif %}">
|
||||
<td>{% if application.number %}{{ application.number }}{% else %}-{% endif %}</td>
|
||||
<td><a href="{% url application_view application.id %}">{{ application.public_version.title }}</a></td>
|
||||
{% if motion.active %}activeline{% endif %}">
|
||||
<td>{% if motion.number %}{{ motion.number }}{% else %}-{% endif %}</td>
|
||||
<td><a href="{% url motion_view motion.id %}">{{ motion.public_version.title }}</a></td>
|
||||
{% if min_supporters > 0 %}
|
||||
<td>{{ application.supporter.count }}</td>
|
||||
<td>{{ motion.count_supporters }}</td>
|
||||
{% endif %}
|
||||
<td>{% if application.status != "pub" %}
|
||||
{{ application.get_status_display }}<br>
|
||||
<td>{% if motion.status != "pub" %}
|
||||
{{ motion.get_status_display }}<br>
|
||||
{% endif %}
|
||||
{% for note in application.notes %}
|
||||
{% for note in motion.notes %}
|
||||
{{ note }}
|
||||
{% if not forloop.last %}<br>{%endif%}
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>{{ application.submitter }}</td>
|
||||
<td class="optional">{{ application.creation_time }}</td>
|
||||
<td>{{ motion.submitter }}</td>
|
||||
<td class="optional">{{ motion.creation_time }}</td>
|
||||
<td>
|
||||
<span style="width: 1px; white-space: nowrap;">
|
||||
{% 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>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.application.can_manage_application %}
|
||||
<a href="{% url application_edit application.id %}"><img src="{% static 'images/icons/edit.png' %}" title="{% trans 'Edit motion' %}"></a>
|
||||
{% if perms.motion.can_manage_motion %}
|
||||
<a href="{% url motion_edit motion.id %}"><img src="{% static 'images/icons/edit.png' %}" title="{% trans 'Edit motion' %}"></a>
|
||||
{% 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 %}
|
||||
<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>
|
||||
</td>
|
||||
</tr>
|
@ -4,14 +4,14 @@
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}
|
||||
{{ block.super }} - {% trans "Motion" %} {{ application.number }}, {{ ballot }}. {% trans "Vote" %}
|
||||
{{ block.super }} - {% trans "Motion" %} {{ motion.number }}, {{ ballot }}. {% trans "Vote" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>
|
||||
{{ application.title }}
|
||||
{{ motion.public_version.title }}
|
||||
<small>
|
||||
{% trans "Motion" %} {{ application.number }}, {{ ballot }}. {% trans "Vote" %}
|
||||
{% trans "Motion" %} {{ motion.number }}, {{ ballot }}. {% trans "Vote" %}
|
||||
</small>
|
||||
<small class="pull-right">
|
||||
<div class="btn-toolbar">
|
||||
@ -39,6 +39,7 @@
|
||||
</h1>
|
||||
|
||||
<i class="helptext">{% trans "Special values" %}: -1 = {% trans 'majority' %}; -2 = {% trans 'undocumented' %}</i>
|
||||
|
||||
<form action="" method="post" class="small-form">{% csrf_token %}
|
||||
{{ pre_form }}
|
||||
<span id="poll_id" style="display:none">{{ poll.id }}</span>
|
||||
@ -65,7 +66,7 @@
|
||||
{{ post_form }}
|
||||
<!-- ballot paper button -->
|
||||
<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' %}
|
||||
</a>
|
||||
</p>
|
||||
@ -77,7 +78,7 @@
|
||||
<button type="submit" name="apply" class="btn">
|
||||
{% trans 'Apply' %}
|
||||
</button>
|
||||
<a href='{% url application_view application.id %}' class="btn">
|
||||
<a href='{% url motion_view motion.id %}' class="btn">
|
||||
{% trans 'Cancel' %}
|
||||
</a>
|
||||
</div>
|
@ -6,8 +6,8 @@
|
||||
|
||||
{% block title %}
|
||||
{{ block.super }} - {% trans "Motion" %}
|
||||
{% if application.number != None %}
|
||||
{{ application.number }}
|
||||
{% if motion.number != None %}
|
||||
{{ motion.number }}
|
||||
{% else %}
|
||||
[{% trans "no number" %}]
|
||||
{% endif %}
|
||||
@ -17,8 +17,8 @@
|
||||
<h1>
|
||||
{{ version.title }}
|
||||
<small>
|
||||
{% if application.number != None %}
|
||||
{% trans "Motion" %} {{ application.number }},
|
||||
{% if motion.number != None %}
|
||||
{% trans "Motion" %} {{ motion.number }},
|
||||
{% else %}
|
||||
<i>[{% trans "no number" %}]</i>,
|
||||
{% endif %}
|
||||
@ -26,8 +26,8 @@
|
||||
</small>
|
||||
<small class="pull-right">
|
||||
<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 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 motion_overview %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Back to overview" %}</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">
|
||||
<a data-toggle="dropdown" href="#" class="btn btn-mini dropdown-toggle">
|
||||
{% trans 'More actions' %}
|
||||
@ -36,28 +36,28 @@
|
||||
<ul class="dropdown-menu pull-right">
|
||||
<!-- edit -->
|
||||
{% 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 %}
|
||||
<!-- delete -->
|
||||
{% 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 %}
|
||||
<!-- activate projector -->
|
||||
{% if perms.projector.can_manage_projector %}
|
||||
<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>
|
||||
{% endif %}
|
||||
<!-- edit poll -->
|
||||
{% if perms.application.can_manage_application %}
|
||||
{% for poll in application.polls %}
|
||||
<li><a href="{% url application_poll_view poll.id %}"><i class="icon-edit"></i> {{ forloop.counter }}. {% trans "Vote" %}</a></li>
|
||||
{% if perms.motion.can_manage_motion %}
|
||||
{% for poll in motion.polls %}
|
||||
<li><a href="{% url motion_poll_view poll.id %}"><i class="icon-edit"></i> {{ forloop.counter }}. {% trans "Vote" %}</a></li>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<!-- create agenda item -->
|
||||
{% if perms.agenda.can_manage_agenda %}
|
||||
<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>
|
||||
{% endif %}
|
||||
</ul>
|
||||
@ -68,12 +68,12 @@
|
||||
|
||||
<div class="row-fluid">
|
||||
<div class="span9">
|
||||
{% if application.public_version != application.last_version %}
|
||||
{% if motion.public_version != motion.last_version %}
|
||||
⋅
|
||||
{% if version == application.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>
|
||||
{% if version == motion.public_version %}
|
||||
{% 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 %}
|
||||
{% 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 %}
|
||||
|
||||
@ -90,7 +90,7 @@
|
||||
{% endif %}
|
||||
<br>
|
||||
<!-- Version history -->
|
||||
{% if application.versions|length > 1 %}
|
||||
{% if motion.versions|length > 1 %}
|
||||
<h4>{% trans "Version History" %}:</h4>
|
||||
<table class="table table-striped table-bordered">
|
||||
<tr>
|
||||
@ -101,18 +101,18 @@
|
||||
<th>{% trans "Text" %}</th>
|
||||
<th>{% trans "Reason" %}</th>
|
||||
</tr>
|
||||
{% for revision in application.versions %}
|
||||
{% for revision in motion.versions %}
|
||||
<tr>
|
||||
<td style="white-space:nowrap;">
|
||||
{% if application.status != "pub" %}
|
||||
{% if revision == application.permitted %}
|
||||
{% if motion.status != "pub" %}
|
||||
{% if revision == motion.permitted %}
|
||||
<img title="{% trans 'Version authorized' %}" src="{% static 'images/icons/accept.png' %}">
|
||||
{% else %}
|
||||
{% if perms.application.can_manage_application %}
|
||||
<a href="{% url application_version_permit revision.id %}"><img title="{% trans 'Permit Version' %}" src="{% static 'images/icons/accept-grey.png' %}"></a>
|
||||
{% if perms.motion.can_manage_motion %}
|
||||
<a href="{% url motion_version_permit revision.id %}"><img title="{% trans 'Permit Version' %}" src="{% static 'images/icons/accept-grey.png' %}"></a>
|
||||
{% endif %}
|
||||
{% if not revision.rejected and revision.id > application.permitted.id and perms.application.can_manage_application %}
|
||||
<a href="{% url application_version_reject revision.id %}"><img title="{% trans 'Reject Version' %}" src="{% static 'images/icons/reject-grey.png' %}"></a>
|
||||
{% if not revision.rejected and revision.id > motion.permitted.id and perms.motion.can_manage_motion %}
|
||||
<a href="{% url motion_version_reject revision.id %}"><img title="{% trans 'Reject Version' %}" src="{% static 'images/icons/reject-grey.png' %}"></a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if revision.rejected %}
|
||||
@ -148,29 +148,27 @@
|
||||
</table>
|
||||
{% endif %}
|
||||
<!-- Log -->
|
||||
{% if perms.application.can_manage_application %}
|
||||
{% if perms.motion.can_manage_motion %}
|
||||
<h4>{% trans "Log" %}:</h4>
|
||||
<small>{{ application.log|linebreaks }}</small>
|
||||
<small>{{ motion.log|linebreaks }}</small>
|
||||
{% endif %}
|
||||
|
||||
</div> <!--/span-->
|
||||
|
||||
|
||||
<div class="span3">
|
||||
<div class="well">
|
||||
<!-- Submitter -->
|
||||
<h5>{% trans "Submitter" %}:</h5>
|
||||
{{ application.submitter }}
|
||||
{% if user == application.submitter.user %}
|
||||
<img src="{% static 'images/icons/user-information.png' %}" title="{% trans 'Me' %}">
|
||||
{% endif %}
|
||||
{{ motion.submitter }}
|
||||
<!-- Supporters -->
|
||||
{% if min_supporters > 0 %}
|
||||
<h5>{% trans "Supporters" %}: *</h5>
|
||||
{% if not application.supporters %}
|
||||
{% if not motion.supporters %}
|
||||
-
|
||||
{% else %}
|
||||
<ol>
|
||||
{% for supporter in application.supporters %}
|
||||
{% for supporter in motion.supporters %}
|
||||
<li>{{ supporter }}</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
@ -178,21 +176,21 @@
|
||||
{% endif %}
|
||||
<!-- Status -->
|
||||
<h5>{% trans "Status" %}:</h5>
|
||||
{% if application.status != "pub" %}
|
||||
{% trans application.get_status_display %}
|
||||
{% if motion.status != "pub" %}
|
||||
{% trans motion.get_status_display %}
|
||||
<br>
|
||||
{% endif %}
|
||||
{% for note in application.notes %}
|
||||
{% for note in motion.notes %}
|
||||
{{ note }}
|
||||
{% if not forloop.last %}<br>{% endif %}
|
||||
{% endfor %}
|
||||
<!-- Vote results -->
|
||||
<h5>{% trans "Vote results" %}:</h5>
|
||||
{% with application.polls as polls %}
|
||||
{% with motion.polls as polls %}
|
||||
{% if not polls.exists %}
|
||||
{% if perms.application.can_manage_application %}
|
||||
{% if perms.motion.can_manage_motion %}
|
||||
{% 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' %}
|
||||
</a>
|
||||
{% else %}
|
||||
@ -204,11 +202,11 @@
|
||||
{% endif %}
|
||||
<ol>
|
||||
{% 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" %}
|
||||
{% if perms.application.can_manage_application %}
|
||||
<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 application_poll_delete poll.id %}" title="{% trans 'Delete Vote' %}"><i class="icon-remove"></i></a>
|
||||
{% if perms.motion.can_manage_motion %}
|
||||
<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 motion_poll_delete poll.id %}" title="{% trans 'Delete Vote' %}"><i class="icon-remove"></i></a>
|
||||
{% endif %}
|
||||
<br>
|
||||
{% if poll.has_votes %}
|
||||
@ -221,17 +219,17 @@
|
||||
<img src="{% static 'images/icons/voting-total.png' %}" title="{% trans 'Votes cast' %}"> {{ poll.print_votescast }}
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% if perms.application.can_manage_application %}
|
||||
{% if perms.motion.can_manage_motion %}
|
||||
{% if forloop.last %}
|
||||
{% 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' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if perms.application.can_manage_application %}
|
||||
{% if perms.motion.can_manage_motion %}
|
||||
<span class="label label-info">{% trans 'No results' %}</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@ -242,25 +240,25 @@
|
||||
{% endwith %}
|
||||
<!-- Creation Time -->
|
||||
<h5>{% trans "Creation Time" %}:</h5>
|
||||
{{ application.creation_time }}
|
||||
{{ motion.creation_time }}
|
||||
<!-- Widthdraw button -->
|
||||
{% if "wit" in actions and user == application.submitter.user %}
|
||||
{% if "wit" in actions and user == motion.submitter.user %}
|
||||
<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>
|
||||
</a>
|
||||
{% endif %}
|
||||
<!-- 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 %}
|
||||
<br><br>
|
||||
<a href='{% url application_unsupport application.id %}' class="btn">
|
||||
<a href='{% url motion_unsupport motion.id %}' class="btn">
|
||||
{% trans 'Unsupport motion' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if "support" in actions %}
|
||||
<br><br>
|
||||
<a href='{% url application_support application.id %}'>
|
||||
<a href='{% url motion_support motion.id %}'>
|
||||
{% trans 'Support' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -272,8 +270,8 @@
|
||||
{% endif %}
|
||||
</div> <!--/well-->
|
||||
|
||||
{% if perms.application.can_manage_application %}
|
||||
<!-- Manage application box -->
|
||||
{% if perms.motion.can_manage_motion %}
|
||||
<!-- Manage motion box -->
|
||||
<div class="well">
|
||||
<h4>{% trans "Manage motion" %}</h4>
|
||||
<!-- Formal validation -->
|
||||
@ -281,16 +279,16 @@
|
||||
<h5>{% trans "Formal validation" %}:</h5>
|
||||
<div class="btn-group btn-group-vertical">
|
||||
{% 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 %}
|
||||
{% 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 %}
|
||||
{% 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 %}
|
||||
{% 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 %}
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -299,12 +297,12 @@
|
||||
<h5>{% trans "Result after vote" %}:</h5>
|
||||
<div class="btn-group btn-group-vertical">
|
||||
{% 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' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% 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' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -320,16 +318,16 @@
|
||||
</a>
|
||||
<ul class="dropdown-menu pull-right">
|
||||
{% 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 %}
|
||||
{% 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 %}
|
||||
{% 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 %}
|
||||
{% 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 %}
|
||||
</ul>
|
||||
</div>
|
||||
@ -337,11 +335,11 @@
|
||||
<p></p>
|
||||
<hr>
|
||||
<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' %}
|
||||
</a>
|
||||
</div> <!--/well-->
|
||||
{% endif %} {# end perms.application.can_support_application #}
|
||||
{% endif %} {# end perms.motion.can_support_motion #}
|
||||
</div> <!--/span-->
|
||||
</div> <!--/row-->
|
||||
{% 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 staticfiles %}
|
||||
|
||||
{% block title %}{{ block.super }} - {% trans "Motion" %} {{ application.number }}{% endblock %}
|
||||
{% block title %}{{ block.super }} - {% trans "Motion" %} {{ motion.number }}{% endblock %}
|
||||
{% block content %}
|
||||
<div id="sidebar">
|
||||
<div class="box">
|
||||
<p><b>{% trans "Status" %}:</b><br>
|
||||
{% if application.status != "pub" %}
|
||||
{% if application.status == "acc" %}
|
||||
{% if motion.status != "pub" %}
|
||||
{% if motion.status == "acc" %}
|
||||
<img src="{% static 'images/icons/voting-yes.png' %}">
|
||||
{% endif %}
|
||||
{% if application.status == "rej" %}
|
||||
{% if motion.status == "rej" %}
|
||||
<img src="{% static 'images/icons/voting-no.png' %}">
|
||||
{% endif %}
|
||||
{% trans application.get_status_display %}
|
||||
{% trans motion.get_status_display %}
|
||||
{% else %}
|
||||
{% for note in application.notes %}
|
||||
{% for note in motion.notes %}
|
||||
{{ note }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
{% with application.polls as polls %}
|
||||
{% with motion.polls as polls %}
|
||||
{% if polls.exists and polls.0.has_votes %}
|
||||
<p><b>{% trans "Poll result" %}:</b>
|
||||
{% for poll in polls %}
|
||||
@ -53,29 +53,29 @@
|
||||
{% endwith %}
|
||||
|
||||
<p><b>{% trans "Submitter" %}:</b><br>
|
||||
{{ application.submitter }}
|
||||
{{ motion.submitter }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1>
|
||||
{% if application.number != None %}
|
||||
{% trans "Motion No." %} {{ application.number }}
|
||||
{% if motion.number != None %}
|
||||
{% trans "Motion No." %} {{ motion.number }}
|
||||
{% else %}
|
||||
{% trans "Motion" %} <i>[{% trans "no number" %}]</i>
|
||||
{% endif %}
|
||||
</h1>
|
||||
<b>{{ application.public_version.title }}</b>
|
||||
<b>{{ motion.public_version.title }}</b>
|
||||
<hr>
|
||||
{% endblock %}
|
||||
|
||||
{% block scrollcontent %}
|
||||
<p>
|
||||
<div class="text">{{ application.public_version.text|linebreaks }}</div>
|
||||
{% if application.public_version.reason %}
|
||||
<div class="text">{{ motion.public_version.text|linebreaks }}</div>
|
||||
{% if motion.public_version.reason %}
|
||||
<br>
|
||||
<div class="reason"><p><b>{% trans "Reason" %}:</b></p>
|
||||
{{ application.public_version.reason|linebreaks }}</div>
|
||||
{{ motion.public_version.reason|linebreaks }}</div>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% 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 random import choice
|
||||
import string
|
||||
import csv
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.db import transaction
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
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():
|
||||
"""
|
||||
generates a random passwort.
|
||||
"""
|
||||
chars = string.letters + string.digits
|
||||
newpassword = ''
|
||||
for i in range(8):
|
||||
newpassword += choice(chars)
|
||||
return newpassword
|
||||
chars = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"
|
||||
size = 8
|
||||
|
||||
return ''.join([choice(chars) for i in range(size)])
|
||||
|
||||
|
||||
def gen_username(first_name, last_name):
|
||||
@ -72,20 +76,20 @@ def import_users(csv_file):
|
||||
dialect=dialect)):
|
||||
if line_no:
|
||||
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:
|
||||
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
|
||||
user = User()
|
||||
user.last_name = last_name
|
||||
user.first_name = first_name
|
||||
user.username = gen_username(first_name, last_name)
|
||||
user.gender = gender
|
||||
user.category = category
|
||||
user.structure_level = structure_level
|
||||
user.type = type
|
||||
user.committee = committee
|
||||
user.comment = comment
|
||||
user.firstpassword = gen_password()
|
||||
user.default_password = gen_password()
|
||||
user.save()
|
||||
user.reset_password()
|
||||
count_success += 1
|
||||
@ -94,3 +98,23 @@ def import_users(csv_file):
|
||||
except UnicodeDecodeError:
|
||||
error_messages.appen(_('Import file has wrong character encoding, only UTF-8 is supported!'))
|
||||
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,
|
||||
"model": "auth.group",
|
||||
"fields": {
|
||||
"name": "Beobachter",
|
||||
"name": "Beobachter/in",
|
||||
"permissions": [
|
||||
[
|
||||
"can_see_agenda",
|
||||
@ -11,14 +11,14 @@
|
||||
"item"
|
||||
],
|
||||
[
|
||||
"can_create_application",
|
||||
"application",
|
||||
"application"
|
||||
"can_create_motion",
|
||||
"motion",
|
||||
"motion"
|
||||
],
|
||||
[
|
||||
"can_see_application",
|
||||
"application",
|
||||
"application"
|
||||
"can_see_motion",
|
||||
"motion",
|
||||
"motion"
|
||||
],
|
||||
[
|
||||
"can_nominate_other",
|
||||
@ -44,6 +44,11 @@
|
||||
"can_see_projector",
|
||||
"projector",
|
||||
"projectorslide"
|
||||
],
|
||||
[
|
||||
"can_see_dashboard",
|
||||
"projector",
|
||||
"projectorslide"
|
||||
]
|
||||
]
|
||||
}
|
||||
@ -52,7 +57,7 @@
|
||||
"pk": 2,
|
||||
"model": "auth.group",
|
||||
"fields": {
|
||||
"name": "Delegierter",
|
||||
"name": "Delegierte/r",
|
||||
"permissions": [
|
||||
[
|
||||
"can_see_agenda",
|
||||
@ -60,19 +65,19 @@
|
||||
"item"
|
||||
],
|
||||
[
|
||||
"can_create_application",
|
||||
"application",
|
||||
"application"
|
||||
"can_create_motion",
|
||||
"motion",
|
||||
"motion"
|
||||
],
|
||||
[
|
||||
"can_see_application",
|
||||
"application",
|
||||
"application"
|
||||
"can_see_motion",
|
||||
"motion",
|
||||
"motion"
|
||||
],
|
||||
[
|
||||
"can_support_application",
|
||||
"application",
|
||||
"application"
|
||||
"can_support_motion",
|
||||
"motion",
|
||||
"motion"
|
||||
],
|
||||
[
|
||||
"can_nominate_other",
|
||||
@ -98,6 +103,11 @@
|
||||
"can_see_projector",
|
||||
"projector",
|
||||
"projectorslide"
|
||||
],
|
||||
[
|
||||
"can_see_dashboard",
|
||||
"projector",
|
||||
"projectorslide"
|
||||
]
|
||||
]
|
||||
}
|
||||
@ -119,19 +129,19 @@
|
||||
"item"
|
||||
],
|
||||
[
|
||||
"can_create_application",
|
||||
"application",
|
||||
"application"
|
||||
"can_create_motion",
|
||||
"motion",
|
||||
"motion"
|
||||
],
|
||||
[
|
||||
"can_manage_application",
|
||||
"application",
|
||||
"application"
|
||||
"can_manage_motion",
|
||||
"motion",
|
||||
"motion"
|
||||
],
|
||||
[
|
||||
"can_see_application",
|
||||
"application",
|
||||
"application"
|
||||
"can_see_motion",
|
||||
"motion",
|
||||
"motion"
|
||||
],
|
||||
[
|
||||
"can_manage_assignment",
|
||||
@ -177,6 +187,11 @@
|
||||
"can_see_projector",
|
||||
"projector",
|
||||
"projectorslide"
|
||||
],
|
||||
[
|
||||
"can_see_dashboard",
|
||||
"projector",
|
||||
"projectorslide"
|
||||
]
|
||||
]
|
||||
}
|
||||
@ -206,6 +221,11 @@
|
||||
"can_see_projector",
|
||||
"projector",
|
||||
"projectorslide"
|
||||
],
|
||||
[
|
||||
"can_see_dashboard",
|
||||
"projector",
|
||||
"projectorslide"
|
||||
]
|
||||
]
|
||||
}
|
@ -18,33 +18,41 @@ from openslides.utils.forms import (
|
||||
CssClassMixin, LocalizedModelMultipleChoiceField)
|
||||
|
||||
from openslides.participant.models import User, Group
|
||||
from openslides.participant.api import get_or_create_registered_group
|
||||
|
||||
|
||||
class UserCreateForm(forms.ModelForm, CssClassMixin):
|
||||
groups = forms.ModelMultipleChoiceField(
|
||||
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:
|
||||
model = User
|
||||
fields = ('first_name', 'last_name', 'is_active', 'groups', 'category',
|
||||
'gender', 'type', 'committee', 'comment', 'default_password')
|
||||
fields = ('first_name', 'last_name', 'is_active', 'groups', 'structure_level',
|
||||
'gender', 'type', 'committee', 'about_me', 'comment', 'default_password')
|
||||
|
||||
|
||||
class UserUpdateForm(UserCreateForm):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('username', 'first_name', 'last_name', 'is_active', 'groups',
|
||||
'category', 'gender', 'type', 'committee', 'comment',
|
||||
'structure_level', 'gender', 'type', 'committee', 'about_me', 'comment',
|
||||
'default_password')
|
||||
|
||||
|
||||
class GroupForm(forms.ModelForm, CssClassMixin):
|
||||
permissions = LocalizedModelMultipleChoiceField(
|
||||
queryset=Permission.objects.all(), label=_("Persmissions"),
|
||||
queryset=Permission.objects.all(), label=_("Permissions"),
|
||||
required=False)
|
||||
users = forms.ModelMultipleChoiceField(
|
||||
queryset=User.objects.all(), label=_("Users"), required=False)
|
||||
queryset=User.objects.all(), label=_("Participants"), required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Initial users
|
||||
@ -58,6 +66,7 @@ class GroupForm(forms.ModelForm, CssClassMixin):
|
||||
instance = forms.ModelForm.save(self, False)
|
||||
|
||||
old_save_m2m = self.save_m2m
|
||||
|
||||
def 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
|
||||
# this name
|
||||
data = self.cleaned_data['name']
|
||||
if self.instance.name.lower() == 'anonymous':
|
||||
if self.instance.name.lower() in ['anonymous', 'registered']:
|
||||
# Editing the anonymous-user
|
||||
if self.instance.name.lower() != data.lower():
|
||||
raise forms.ValidationError(
|
||||
_('You can not edit the name for the anonymous user'))
|
||||
_('You can not edit the name for this group.'))
|
||||
else:
|
||||
if data.lower() == 'anonymous':
|
||||
if data.lower() in ['anonymous', 'registered']:
|
||||
raise forms.ValidationError(
|
||||
_('Group name "%s" is reserved for internal use.') % data)
|
||||
return data
|
||||
@ -94,7 +103,7 @@ class GroupForm(forms.ModelForm, CssClassMixin):
|
||||
class UsersettingsForm(forms.ModelForm, CssClassMixin):
|
||||
class Meta:
|
||||
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):
|
||||
@ -113,3 +122,7 @@ class ConfigForm(forms.Form, CssClassMixin):
|
||||
required=False,
|
||||
label=_("Welcome text"),
|
||||
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.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.config.models import config
|
||||
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'
|
||||
GENDER_CHOICES = (
|
||||
('male', _('Male')),
|
||||
('female', _('Female')),
|
||||
)
|
||||
TYPE_CHOICE = (
|
||||
TYPE_CHOICES = (
|
||||
('delegate', _('Delegate')),
|
||||
('observer', _('Observer')),
|
||||
('staff', _('Staff')),
|
||||
@ -36,30 +41,37 @@ class User(DjangoUser, PersonMixin):
|
||||
)
|
||||
|
||||
django_user = models.OneToOneField(DjangoUser, editable=False, parent_link=True)
|
||||
category = models.CharField(
|
||||
max_length=100, null=True, blank=True, verbose_name=_("Category"),
|
||||
help_text=_('Will be shown behind the name.'))
|
||||
structure_level = models.CharField(
|
||||
max_length=100, blank=True, default='', verbose_name=_("Structure level"),
|
||||
help_text=_('Will be shown after the name.'))
|
||||
gender = models.CharField(
|
||||
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(
|
||||
max_length=100, choices=TYPE_CHOICE, blank=True,
|
||||
verbose_name=_("Typ"), help_text=_('Only for filter the userlist.'))
|
||||
max_length=100, choices=TYPE_CHOICES, blank=True,
|
||||
verbose_name=_("Typ"), help_text=_('Only for filtering the participant list.'))
|
||||
committee = models.CharField(
|
||||
max_length=100, null=True, blank=True, verbose_name=_("Committee"),
|
||||
help_text=_('Only for filter the userlist.'))
|
||||
max_length=100, blank=True, default='', verbose_name=_("Committee"),
|
||||
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(
|
||||
null=True, blank=True, verbose_name=_('Comment'),
|
||||
blank=True, default='', verbose_name=_('Comment'),
|
||||
help_text=_('Only for notes.'))
|
||||
default_password = models.CharField(
|
||||
max_length=100, null=True, blank=True,
|
||||
max_length=100, blank=True, default='',
|
||||
verbose_name=_("Default password"))
|
||||
|
||||
@property
|
||||
def clean_name(self):
|
||||
return self.get_full_name() or self.username
|
||||
|
||||
def get_name_suffix(self):
|
||||
return self.category
|
||||
return self.structure_level
|
||||
|
||||
def set_name_suffix(self, value):
|
||||
self.category = value
|
||||
self.structure_level = value
|
||||
|
||||
name_suffix = property(get_name_suffix, set_name_suffix)
|
||||
|
||||
@ -72,25 +84,33 @@ class User(DjangoUser, PersonMixin):
|
||||
self.set_password(password)
|
||||
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
|
||||
def get_absolute_url(self, link='edit'):
|
||||
def get_absolute_url(self, link='view'):
|
||||
"""
|
||||
Return the URL to this user.
|
||||
|
||||
link can be:
|
||||
* view
|
||||
* edit
|
||||
* delete
|
||||
"""
|
||||
if link == 'view':
|
||||
return ('user_view', [str(self.id)])
|
||||
if link == 'edit':
|
||||
return ('user_edit', [str(self.id)])
|
||||
if link == 'delete':
|
||||
return ('user_delete', [str(self.id)])
|
||||
|
||||
def __unicode__(self):
|
||||
name = self.get_full_name() or self.username
|
||||
if self.name_suffix:
|
||||
return u"%s (%s)" % (name, self.name_suffix)
|
||||
return u"%s" % name
|
||||
return u"%s (%s)" % (self.clean_name, self.name_suffix)
|
||||
return u"%s" % self.clean_name
|
||||
|
||||
class Meta:
|
||||
# Rename permissions
|
||||
@ -99,24 +119,42 @@ class User(DjangoUser, PersonMixin):
|
||||
('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'
|
||||
|
||||
django_group = models.OneToOneField(DjangoGroup, editable=False, parent_link=True)
|
||||
group_as_person = models.BooleanField(default=False)
|
||||
description = models.TextField(blank=True)
|
||||
group_as_person = models.BooleanField(
|
||||
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
|
||||
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:
|
||||
* view
|
||||
* edit
|
||||
* delete
|
||||
"""
|
||||
if link == 'view':
|
||||
return ('user_group_view', [str(self.id)])
|
||||
if link == 'edit':
|
||||
return ('user_group_edit', [str(self.id)])
|
||||
if link == 'delete':
|
||||
@ -125,19 +163,43 @@ class Group(DjangoGroup, PersonMixin):
|
||||
def __unicode__(self):
|
||||
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):
|
||||
self.person_prefix_filter = person_prefix_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)
|
||||
|
||||
def __iter__(self):
|
||||
if (not self.person_prefix_filter or
|
||||
self.person_prefix_filter == User.person_prefix):
|
||||
if self.id_filter:
|
||||
try:
|
||||
yield self.users.get(pk=self.id_filter)
|
||||
except User.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
for user in self.users:
|
||||
yield user
|
||||
@ -145,18 +207,22 @@ class UsersConnector(object):
|
||||
if (not self.person_prefix_filter or
|
||||
self.person_prefix_filter == Group.person_prefix):
|
||||
if self.id_filter:
|
||||
try:
|
||||
yield self.groups.get(pk=self.id_filter)
|
||||
except Group.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
for group in self.groups:
|
||||
yield group
|
||||
|
||||
def __getitem__(self, key):
|
||||
return User.objects.get(pk=key)
|
||||
|
||||
|
||||
@receiver(receive_persons, dispatch_uid="participant")
|
||||
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'])
|
||||
|
||||
|
||||
@ -169,11 +235,12 @@ def default_config(sender, key, **kwargs):
|
||||
return {
|
||||
'participant_pdf_system_url': 'http://example.com:8000',
|
||||
'participant_pdf_welcometext': _('Welcome to OpenSlides!'),
|
||||
'participant_sort_users_by_first_name': False,
|
||||
}.get(key)
|
||||
|
||||
|
||||
@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:
|
||||
instance.user
|
||||
except User.DoesNotExist:
|
||||
@ -181,8 +248,18 @@ def user_post_save(sender, instance, signal, *args, **kwargs):
|
||||
|
||||
|
||||
@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:
|
||||
instance.group
|
||||
except Group.DoesNotExist:
|
||||
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" %}
|
||||
<p>
|
||||
{% include "formbuttons_save.html" %}
|
||||
<a href='{% url config_application %}' class="btn">
|
||||
<a href='{% url config_participant %}' class="btn">
|
||||
{% trans 'Cancel' %}
|
||||
</a>
|
||||
</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.super }} –
|
||||
{% if group %}
|
||||
{% trans "Edit user group" %}
|
||||
{% trans "Edit group" %}
|
||||
{% else %}
|
||||
{% trans "New user group" %}
|
||||
{% trans "New group" %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>
|
||||
{% if group %}
|
||||
{% trans "Edit user group" %}
|
||||
{% trans "Edit group" %}
|
||||
{% else %}
|
||||
{% trans "New user group" %}
|
||||
{% trans "New group" %}
|
||||
{% endif %}
|
||||
<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>
|
||||
|
@ -2,34 +2,35 @@
|
||||
|
||||
{% load i18n %}
|
||||
{% load staticfiles %}
|
||||
{% load tags %}
|
||||
|
||||
{% block title %}{{ block.super }} – {% trans "User groups" %}{% endblock %}
|
||||
{% block title %}{{ block.super }} – {% trans "Groups" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "User groups" %}
|
||||
<h1>{% trans "Groups" %}
|
||||
<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>
|
||||
</small>
|
||||
</h1>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<tr>
|
||||
<th>{% trans "User Group" %}</th>
|
||||
<th>{% trans "Group" %}</th>
|
||||
<th>{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
{% for group in groups %}
|
||||
<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>
|
||||
{% 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>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="5"><i>{% trans "No user groups available." %}</i></td>
|
||||
<td colspan="5"><i>{% trans "No groups available." %}</i></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</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 '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>
|
||||
{% trans 'Required CSV file encoding: UTF-8 (Unicode).' %}
|
||||
</p>
|
||||
|
@ -60,11 +60,11 @@
|
||||
<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>
|
||||
</select>
|
||||
<select class="span2" name="category" onchange="document.forms['filter'].submit()">
|
||||
<option value="---">-- {% trans "Category" %} --</option>
|
||||
{% for category in categories %}
|
||||
<option value="{{ category }}"{% if category in sortfilter.category %} selected{% endif %}>
|
||||
{{ category }}</option>
|
||||
<select class="span2" name="structure_level" onchange="document.forms['filter'].submit()">
|
||||
<option value="---">-- {% trans "Structure level" %} --</option>
|
||||
{% for level in structure_levels %}
|
||||
<option value="{{ level }}"{% if level in sortfilter.structure_level %} selected{% endif %}>
|
||||
{{ level }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<select class="span2" name="type" onchange="document.forms['filter'].submit()">
|
||||
@ -101,7 +101,7 @@
|
||||
<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=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=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 %}
|
||||
@ -111,10 +111,10 @@
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% for user in users %}
|
||||
<tr class="{% cycle '' 'odd' %}">
|
||||
<tr>
|
||||
<td>{{ user.first_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.committee }}</td>
|
||||
{% if perms.participant.can_manage_participant %}
|
||||
@ -129,15 +129,19 @@
|
||||
<a href="{% url user_edit user.id %}">
|
||||
<img src="{% static 'images/icons/edit.png' %}" title="{% trans 'Edit participant' %}">
|
||||
</a>
|
||||
{% if user != request_user %}
|
||||
<a href="{% url user_delete user.id %}">
|
||||
<img src="{% static 'images/icons/delete.png' %}" title="{% trans 'Delete participant' %}">
|
||||
</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 %}>
|
||||
<span></span>
|
||||
</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 %}>
|
||||
<span></span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
{% 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.core.urlresolvers import reverse
|
||||
|
||||
from openslides.participant.views import (
|
||||
ParticipantsListPDF, ParticipantsPasswordsPDF, Overview, UserCreateView,
|
||||
UserUpdateView, UserDeleteView, SetUserStatusView, UserImportView,
|
||||
ResetPasswordView, GroupOverviewView, GroupCreateView, GroupUpdateView,
|
||||
GroupDeleteView)
|
||||
UserOverview, UserCreateView, UserDetailView, UserUpdateView,
|
||||
UserDeleteView, ResetPasswordView, SetUserStatusView, UserImportView,
|
||||
GroupOverview, GroupCreateView, GroupDetailView, GroupUpdateView, GroupDeleteView,
|
||||
ParticipantsListPDF, ParticipantsPasswordsPDF)
|
||||
|
||||
urlpatterns = patterns('openslides.participant.views',
|
||||
urlpatterns = patterns('',
|
||||
|
||||
# User
|
||||
url(r'^$',
|
||||
Overview.as_view(),
|
||||
UserOverview.as_view(),
|
||||
name='user_overview',
|
||||
),
|
||||
|
||||
@ -30,6 +31,11 @@ urlpatterns = patterns('openslides.participant.views',
|
||||
name='user_new',
|
||||
),
|
||||
|
||||
url(r'^(?P<pk>\d+)/$',
|
||||
UserDetailView.as_view(),
|
||||
name='user_view',
|
||||
),
|
||||
|
||||
url(r'^(?P<pk>\d+)/edit/$',
|
||||
UserUpdateView.as_view(),
|
||||
name='user_edit',
|
||||
@ -45,12 +51,6 @@ urlpatterns = patterns('openslides.participant.views',
|
||||
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/$',
|
||||
SetUserStatusView.as_view(),
|
||||
{'action': 'activate'},
|
||||
@ -63,13 +63,20 @@ urlpatterns = patterns('openslides.participant.views',
|
||||
name='user_status_deactivate',
|
||||
),
|
||||
|
||||
url(r'^(?P<pk>\d+)/status/toggle/$',
|
||||
SetUserStatusView.as_view(),
|
||||
{'action': 'toggle'},
|
||||
name='user_status_toggle',
|
||||
),
|
||||
|
||||
url(r'^import/$',
|
||||
UserImportView.as_view(),
|
||||
name='user_import',
|
||||
),
|
||||
|
||||
# Group
|
||||
url(r'^group/$',
|
||||
GroupOverviewView.as_view(),
|
||||
GroupOverview.as_view(),
|
||||
name='user_group_overview',
|
||||
),
|
||||
|
||||
@ -78,6 +85,11 @@ urlpatterns = patterns('openslides.participant.views',
|
||||
name='user_group_new',
|
||||
),
|
||||
|
||||
url(r'^group/(?P<pk>\d+)/$',
|
||||
GroupDetailView.as_view(),
|
||||
name='user_group_view',
|
||||
),
|
||||
|
||||
url(r'^group/(?P<pk>\d+)/edit/$',
|
||||
GroupUpdateView.as_view(),
|
||||
name='user_group_edit',
|
||||
@ -88,6 +100,7 @@ urlpatterns = patterns('openslides.participant.views',
|
||||
name='user_group_delete',
|
||||
),
|
||||
|
||||
# PDF
|
||||
url(r'^print/$',
|
||||
ParticipantsListPDF.as_view(),
|
||||
name='user_print',
|
||||
|
@ -28,7 +28,6 @@ from reportlab.platypus import (
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
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.core.urlresolvers import reverse
|
||||
from django.shortcuts import redirect
|
||||
@ -39,11 +38,12 @@ from openslides.utils.template import Tab
|
||||
from openslides.utils.utils import (
|
||||
template, decodedict, encodedict, delete_default_permissions, html_strong)
|
||||
from openslides.utils.views import (
|
||||
FormView, PDFView, CreateView, UpdateView, DeleteView,
|
||||
RedirectView, SingleObjectMixin, ListView, QuestionMixin)
|
||||
|
||||
FormView, PDFView, CreateView, UpdateView, DeleteView, PermissionMixin,
|
||||
RedirectView, SingleObjectMixin, ListView, QuestionMixin, DetailView)
|
||||
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.forms import (
|
||||
UserCreateForm, UserUpdateForm, UsersettingsForm,
|
||||
@ -51,9 +51,9 @@ from openslides.participant.forms import (
|
||||
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'
|
||||
template_name = 'participant/overview.html'
|
||||
@ -66,8 +66,8 @@ class Overview(ListView):
|
||||
except KeyError:
|
||||
sortfilter = {}
|
||||
|
||||
for value in [u'gender', u'category', u'type', u'committee', u'status',
|
||||
u'sort', u'reverse']:
|
||||
for value in ['gender', 'structure_level', 'type', 'committee', 'status',
|
||||
'sort', 'reverse']:
|
||||
if value in self.request.REQUEST:
|
||||
if self.request.REQUEST[value] == '---':
|
||||
try:
|
||||
@ -80,8 +80,8 @@ class Overview(ListView):
|
||||
query = User.objects
|
||||
if 'gender' in sortfilter:
|
||||
query = query.filter(gender__iexact=sortfilter['gender'][0])
|
||||
if 'category' in sortfilter:
|
||||
query = query.filter(category__iexact=sortfilter['category'][0])
|
||||
if 'structure_level' in sortfilter:
|
||||
query = query.filter(structure_level__iexact=sortfilter['structure_level'][0])
|
||||
if 'type' in sortfilter:
|
||||
query = query.filter(type__iexact=sortfilter['type'][0])
|
||||
if 'committee' in sortfilter:
|
||||
@ -92,9 +92,12 @@ class Overview(ListView):
|
||||
if sortfilter['sort'][0] in ['first_name', 'last_name', 'last_login']:
|
||||
query = query.order_by(sortfilter['sort'][0])
|
||||
elif (sortfilter['sort'][0] in
|
||||
['category', 'type', 'committee', 'comment']):
|
||||
['structure_level', 'type', 'committee', 'comment']):
|
||||
query = query.order_by(
|
||||
'%s' % sortfilter['sort'][0])
|
||||
else:
|
||||
if config['participant_sort_users_by_first_name']:
|
||||
query = query.order_by('first_name')
|
||||
else:
|
||||
query = query.order_by('last_name')
|
||||
|
||||
@ -106,7 +109,7 @@ class Overview(ListView):
|
||||
return query.all()
|
||||
|
||||
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()
|
||||
|
||||
@ -117,23 +120,47 @@ class Overview(ListView):
|
||||
percent = 0
|
||||
|
||||
# list of all existing categories
|
||||
categories = [p['category'] for p in User.objects.values('category')
|
||||
.exclude(category='').distinct()]
|
||||
|
||||
structure_levels = [
|
||||
p['structure_level'] for p in
|
||||
User.objects.values('structure_level').exclude(structure_level='').distinct()]
|
||||
# list of all existing committees
|
||||
committees = [p['committee'] for p in User.objects.values('committee')
|
||||
.exclude(committee='').distinct()]
|
||||
committees = [
|
||||
p['committee'] for p in
|
||||
User.objects.values('committee').exclude(committee='').distinct()]
|
||||
# context vars
|
||||
context.update({
|
||||
'allusers': all_users,
|
||||
'request_user': self.request.user,
|
||||
'percent': round(percent, 1),
|
||||
'categories': categories,
|
||||
'structure_levels': structure_levels,
|
||||
'committees': committees,
|
||||
'cookie': ['participant_sortfilter', urlencode(decodedict(self.sortfilter),
|
||||
'cookie': [
|
||||
'participant_sortfilter', urlencode(decodedict(self.sortfilter),
|
||||
doseq=True)],
|
||||
'sortfilter': self.sortfilter})
|
||||
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):
|
||||
"""
|
||||
Create a new participant.
|
||||
@ -147,8 +174,8 @@ class UserCreateView(CreateView):
|
||||
apply_url = 'user_edit'
|
||||
|
||||
def manipulate_object(self, form):
|
||||
self.object.username = gen_username(form.cleaned_data['first_name'],
|
||||
form.cleaned_data['last_name'])
|
||||
self.object.username = gen_username(
|
||||
form.cleaned_data['first_name'], form.cleaned_data['last_name'])
|
||||
if not self.object.default_password:
|
||||
self.object.default_password = gen_password()
|
||||
self.object.set_password(self.object.default_password)
|
||||
@ -175,6 +202,12 @@ class UserDeleteView(DeleteView):
|
||||
model = User
|
||||
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):
|
||||
"""
|
||||
@ -191,6 +224,12 @@ class SetUserStatusView(RedirectView, SingleObjectMixin):
|
||||
if action == 'activate':
|
||||
self.object.is_active = True
|
||||
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
|
||||
elif action == 'toggle':
|
||||
self.object.is_active = not self.object.is_active
|
||||
@ -214,6 +253,9 @@ class ParticipantsListPDF(PDFView):
|
||||
def append_to_pdf(self, story):
|
||||
data = [['#', _('Last Name'), _('First Name'), _('Group'), _('Type'),
|
||||
_('Committee')]]
|
||||
if config['participant_sort_users_by_first_name']:
|
||||
sort = 'first_name'
|
||||
else:
|
||||
sort = 'last_name'
|
||||
counter = 0
|
||||
for user in User.objects.all().order_by(sort):
|
||||
@ -222,8 +264,8 @@ class ParticipantsListPDF(PDFView):
|
||||
counter,
|
||||
Paragraph(user.last_name, stylesheet['Tablecell']),
|
||||
Paragraph(user.first_name, stylesheet['Tablecell']),
|
||||
Paragraph(user.category, stylesheet['Tablecell']),
|
||||
Paragraph(user.type, stylesheet['Tablecell']),
|
||||
Paragraph(user.structure_level, stylesheet['Tablecell']),
|
||||
Paragraph(user.get_type_display(), stylesheet['Tablecell']),
|
||||
Paragraph(user.committee, stylesheet['Tablecell'])])
|
||||
t = LongTable(data, style=[
|
||||
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||
@ -256,7 +298,11 @@ class ParticipantsPasswordsPDF(PDFView):
|
||||
data = []
|
||||
participant_pdf_system_url = config["participant_pdf_system_url"]
|
||||
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.append(Spacer(0, 0.8 * cm))
|
||||
cell.append(Paragraph(_("Account for OpenSlides"),
|
||||
@ -269,7 +315,7 @@ class ParticipantsPasswordsPDF(PDFView):
|
||||
cell.append(
|
||||
Paragraph(
|
||||
_("Password: %s")
|
||||
% (user.firstpassword), stylesheet['Monotype']))
|
||||
% (user.default_password), stylesheet['Monotype']))
|
||||
cell.append(Spacer(0, 0.5 * cm))
|
||||
cell.append(
|
||||
Paragraph(
|
||||
@ -319,9 +365,9 @@ class UserImportView(FormView):
|
||||
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'
|
||||
model = User
|
||||
@ -335,20 +381,14 @@ class ResetPasswordView(RedirectView, SingleObjectMixin, QuestionMixin):
|
||||
def get_redirect_url(self, **kwargs):
|
||||
return reverse('user_edit', args=[self.object.id])
|
||||
|
||||
def pre_redirect(self, request, *args, **kwargs):
|
||||
self.confirm_form()
|
||||
|
||||
def pre_post_redirect(self, request, *args, **kwargs):
|
||||
if self.get_answer().lower() == 'yes':
|
||||
def case_yes(self):
|
||||
self.object.reset_password()
|
||||
messages.success(request,
|
||||
_('The Password for %s was successfully reset.') % html_strong(self.object))
|
||||
|
||||
def get_answer_url(self):
|
||||
return reverse('user_reset_password', args=[self.object.id])
|
||||
def get_success_message(self):
|
||||
return _('The Password for %s was successfully reset.') % html_strong(self.object)
|
||||
|
||||
|
||||
class GroupOverviewView(ListView):
|
||||
class GroupOverview(ListView):
|
||||
"""
|
||||
Overview over all groups.
|
||||
"""
|
||||
@ -400,6 +440,12 @@ class GroupDeleteView(DeleteView):
|
||||
model = Group
|
||||
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):
|
||||
"""
|
||||
@ -412,13 +458,16 @@ class Config(FormView):
|
||||
def get_initial(self):
|
||||
return {
|
||||
'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):
|
||||
config['participant_pdf_system_url'] = (
|
||||
form.cleaned_data['participant_pdf_system_url'])
|
||||
config['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(
|
||||
self.request,
|
||||
_('Participants settings successfully saved.'))
|
||||
@ -497,6 +546,67 @@ def register_tab(request):
|
||||
title=_('Participants'),
|
||||
app='participant',
|
||||
url=reverse('user_overview'),
|
||||
permission=request.user.has_perm('participant.can_see_participant') or
|
||||
request.user.has_perm('participant.can_manage_participant'),
|
||||
permission=(
|
||||
request.user.has_perm('participant.can_see_participant') or
|
||||
request.user.has_perm('participant.can_manage_participant')),
|
||||
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.utils.translation import ugettext_lazy as _
|
||||
|
||||
from openslides.utils.forms import CssClassMixin
|
||||
|
||||
|
@ -164,7 +164,6 @@ class BasePoll(models.Model):
|
||||
"""
|
||||
return self.vote_values
|
||||
|
||||
|
||||
def get_vote_class(self):
|
||||
"""
|
||||
Return the releatet vote class.
|
||||
@ -236,7 +235,7 @@ def print_value(value, percent_base=0):
|
||||
elif value == -2:
|
||||
return unicode(_('undocumented'))
|
||||
elif value is None:
|
||||
return u''
|
||||
return unicode(_('undocumented'))
|
||||
if not percent_base:
|
||||
return u'%s' % value
|
||||
|
||||
|
@ -11,12 +11,12 @@
|
||||
"""
|
||||
|
||||
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.importlib import import_module
|
||||
|
||||
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):
|
||||
@ -85,29 +85,27 @@ def set_active_slide(sid, argument=None):
|
||||
"""
|
||||
config["presentation"] = sid
|
||||
config['presentation_argument'] = argument
|
||||
clear_projector_cache()
|
||||
|
||||
|
||||
def register_slidemodel(model, model_name=None, control_template=None,
|
||||
weight=0):
|
||||
def clear_projector_cache():
|
||||
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.
|
||||
"""
|
||||
# TODO: control_template should never be None
|
||||
if model_name is None:
|
||||
model_name = model.prefix
|
||||
|
||||
if control_template is None:
|
||||
control_template = 'projector/default_control_slidemodel.html'
|
||||
|
||||
category = model.__module__.split('.')[0]
|
||||
SLIDE[model_name] = Slide(
|
||||
model_slide=True,
|
||||
model=model,
|
||||
category=category,
|
||||
key=model.prefix,
|
||||
model_name=model_name,
|
||||
control_template=control_template,
|
||||
weight=weight,
|
||||
)
|
||||
SLIDE[model_name] = Slide(model_slide=True, model=model, 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=''):
|
||||
@ -117,15 +115,9 @@ def register_slidefunc(key, func, control_template=None, weight=0, name=''):
|
||||
if control_template is None:
|
||||
control_template = 'projector/default_control_slidefunc.html'
|
||||
category = func.__module__.split('.')[0]
|
||||
SLIDE[key] = Slide(
|
||||
model_slide=False,
|
||||
func=func,
|
||||
category=category,
|
||||
key=key,
|
||||
control_template=control_template,
|
||||
weight=weight,
|
||||
name=name,
|
||||
)
|
||||
SLIDE[key] = Slide(model_slide=False, func=func, category=category,
|
||||
key=key, control_template=control_template, weight=weight,
|
||||
name=name,)
|
||||
|
||||
|
||||
def projector_message_set(message, sid=None):
|
||||
@ -158,7 +150,6 @@ def get_all_widgets(request, session=False):
|
||||
mod = import_module(app + '.views')
|
||||
except ImportError:
|
||||
continue
|
||||
appname = mod.__name__.split('.')[0]
|
||||
try:
|
||||
modul_widgets = mod.get_widgets(request)
|
||||
except AttributeError:
|
||||
|
@ -11,7 +11,6 @@
|
||||
"""
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
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.projector import SlideMixin
|
||||
|
||||
from openslides.config.models import config
|
||||
|
||||
|
||||
|
||||
class ProjectorSlide(models.Model, SlideMixin):
|
||||
"""
|
||||
@ -31,6 +28,7 @@ class ProjectorSlide(models.Model, SlideMixin):
|
||||
|
||||
title = models.CharField(max_length=256, verbose_name=_("Title"))
|
||||
text = models.TextField(null=True, blank=True, verbose_name=_("Text"))
|
||||
weight = models.IntegerField(default=0, verbose_name=_("Weight"))
|
||||
|
||||
def slide(self):
|
||||
return {
|
||||
@ -55,8 +53,7 @@ class ProjectorSlide(models.Model, SlideMixin):
|
||||
)
|
||||
|
||||
|
||||
register_slidemodel(ProjectorSlide,
|
||||
control_template='projector/control_customslide.html')
|
||||
register_slidemodel(ProjectorSlide, control_template='projector/control_customslide.html')
|
||||
|
||||
|
||||
class ProjectorOverlay(models.Model):
|
||||
|
@ -19,9 +19,9 @@ from openslides.config.models import config
|
||||
|
||||
from openslides.projector.signals import projector_overlays
|
||||
|
||||
|
||||
SLIDE = {}
|
||||
|
||||
|
||||
class SlideMixin(object):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
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
|
||||
|
||||
def set_active(self):
|
||||
"""
|
||||
Appoint this item as the active slide.
|
||||
"""
|
||||
from openslides.projector.api import set_active_slide
|
||||
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):
|
||||
"""
|
||||
@ -133,6 +142,9 @@ class Widget(object):
|
||||
def __repr__(self):
|
||||
return unicode(self.display_name)
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.display_name)
|
||||
|
||||
|
||||
@receiver(projector_overlays, dispatch_uid="projector_countdown")
|
||||
def countdown(sender, **kwargs):
|
||||
|
@ -12,6 +12,7 @@ body{
|
||||
font-family: 'Lucida Grande',"Trebuchet MS",Verdana,sans-serif;
|
||||
font-size : 20px;
|
||||
background-color: #FAFAFB;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/*** HEADER ***/
|
||||
@ -154,6 +155,10 @@ body{
|
||||
font-weight:bold;
|
||||
text-align: center;
|
||||
}
|
||||
.item_fullscreen span
|
||||
{
|
||||
font-size: 50%; font-weight:normal;
|
||||
}
|
||||
|
||||
/* items in a list*/
|
||||
.itemlist li
|
||||
|
@ -7,8 +7,13 @@
|
||||
{% block content %}
|
||||
{% if slide.text %}
|
||||
<h1>{{ slide.title }}</h1>
|
||||
{{ slide.text|safe|linebreaks }}
|
||||
{% else %}
|
||||
<div class="item_fullscreen">{{ slide.title }}</div>
|
||||
{% endif %}
|
||||
{% 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>
|
||||
</small>
|
||||
</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">
|
||||
{% for name, widget in widgets.items %}
|
||||
|
@ -4,6 +4,5 @@
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% get_config 'frontpage_title' %}</h1>
|
||||
{% get_config 'frontpage_welcometext' %}
|
||||
<div class="item_fullscreen">{% get_config 'welcome_title' %}</div>
|
||||
{% endblock %}
|
||||
|
@ -1,6 +1,35 @@
|
||||
{% load i18n %}
|
||||
{% 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">
|
||||
<div id="iframewrapper">
|
||||
<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 time import time
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.core.cache import cache
|
||||
from django.core.context_processors import csrf
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
from django.dispatch import receiver
|
||||
from django.utils.datastructures import SortedDict
|
||||
from django.utils.importlib import import_module
|
||||
from django.shortcuts import redirect
|
||||
from django.template import RequestContext
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from openslides.utils.template import render_block_to_string, Tab
|
||||
from openslides.utils.utils import html_strong
|
||||
from openslides.utils.views import (TemplateView, RedirectView, CreateView,
|
||||
UpdateView, DeleteView, AjaxMixin)
|
||||
|
||||
from openslides.utils.views import (
|
||||
TemplateView, RedirectView, CreateView, UpdateView, DeleteView, AjaxMixin)
|
||||
from openslides.config.models import config
|
||||
|
||||
from openslides.projector.api import (get_active_slide, set_active_slide,
|
||||
projector_message_set, projector_message_delete, get_slide_from_sid,
|
||||
get_all_widgets)
|
||||
from openslides.projector.forms import SelectWidgetsForm
|
||||
from openslides.projector.models import ProjectorOverlay, ProjectorSlide
|
||||
from openslides.projector.projector import SLIDE, Widget
|
||||
from openslides.projector.signals import projector_overlays
|
||||
from .api import (
|
||||
get_active_slide, set_active_slide, projector_message_set,
|
||||
projector_message_delete, get_slide_from_sid, get_all_widgets,
|
||||
clear_projector_cache)
|
||||
from .forms import SelectWidgetsForm
|
||||
from .models import ProjectorOverlay, ProjectorSlide
|
||||
from .projector import Widget
|
||||
from .signals import projector_overlays
|
||||
|
||||
|
||||
class DashboardView(TemplateView, AjaxMixin):
|
||||
@ -73,6 +70,7 @@ class Projector(TemplateView, AjaxMixin):
|
||||
except AttributeError: # TODO: It has to be an Slide.DoesNotExist
|
||||
data = None
|
||||
ajax = 'on'
|
||||
active_sid = get_active_slide(True)
|
||||
else:
|
||||
data = get_slide_from_sid(sid)
|
||||
ajax = 'off'
|
||||
@ -88,10 +86,10 @@ class Projector(TemplateView, AjaxMixin):
|
||||
# Projector Overlays
|
||||
if self.kwargs['sid'] is None:
|
||||
active_defs = ProjectorOverlay.objects.filter(active=True) \
|
||||
.filter(Q(sid=sid) | Q(sid=None)).values_list('def_name',
|
||||
flat=True)
|
||||
for receiver, response in projector_overlays.send(sender=sid,
|
||||
register=False, call=active_defs):
|
||||
.filter(Q(sid=active_sid) | Q(sid=None)).values_list(
|
||||
'def_name', flat=True)
|
||||
for receiver, response in projector_overlays.send(
|
||||
sender=sid, register=False, call=active_defs):
|
||||
if response is not None:
|
||||
data['overlays'].append(response)
|
||||
self._data = data
|
||||
@ -106,19 +104,36 @@ class Projector(TemplateView, AjaxMixin):
|
||||
return context
|
||||
|
||||
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)
|
||||
scrollcontent = render_block_to_string(self.get_template_names()[0],
|
||||
'scrollcontent', self.data)
|
||||
cache.set('projector_content', content, 1)
|
||||
|
||||
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)
|
||||
content_hash = hash(content)
|
||||
context.update({
|
||||
'content': content,
|
||||
'scrollcontent': scrollcontent,
|
||||
'time': datetime.now().strftime('%H:%M'),
|
||||
'overlays': self.data['overlays'],
|
||||
'title': self.data['title'],
|
||||
'overlays': data['overlays'],
|
||||
'title': data['title'],
|
||||
'bigger': config['bigger'],
|
||||
'up': config['up'],
|
||||
'content_hash': content_hash,
|
||||
@ -186,8 +201,7 @@ class SelectWidgetsView(TemplateView):
|
||||
else:
|
||||
transaction.commit()
|
||||
self.request.session['widgets'] = activated_widgets
|
||||
return self.render_to_response(context)
|
||||
|
||||
return redirect(reverse('dashboard'))
|
||||
|
||||
|
||||
class ProjectorEdit(RedirectView):
|
||||
@ -259,6 +273,7 @@ class CountdownEdit(RedirectView):
|
||||
pass
|
||||
|
||||
def get_ajax_context(self, **kwargs):
|
||||
clear_projector_cache()
|
||||
return {
|
||||
'state': config['countdown_state'],
|
||||
'countdown_time': config['countdown_time'],
|
||||
@ -279,14 +294,13 @@ class OverlayMessageView(RedirectView):
|
||||
elif 'message-clean' in request.POST:
|
||||
projector_message_delete()
|
||||
|
||||
|
||||
def get_ajax_context(self, **kwargs):
|
||||
clear_projector_cache()
|
||||
return {
|
||||
'overlay_message': config['projector_message'],
|
||||
}
|
||||
|
||||
|
||||
|
||||
class ActivateOverlay(RedirectView):
|
||||
"""
|
||||
Activate or deactivate an overlay.
|
||||
@ -312,6 +326,7 @@ class ActivateOverlay(RedirectView):
|
||||
self.overlay.save()
|
||||
|
||||
def get_ajax_context(self, **kwargs):
|
||||
clear_projector_cache()
|
||||
return {
|
||||
'active': self.overlay.active,
|
||||
'def_name': self.overlay.def_name,
|
||||
@ -355,13 +370,12 @@ def register_tab(request):
|
||||
"""
|
||||
Register the projector tab.
|
||||
"""
|
||||
selected = True if request.path.startswith('/projector/') else False
|
||||
selected = request.path.startswith('/projector/')
|
||||
return Tab(
|
||||
title=_('Dashboard'),
|
||||
app='dashboard',
|
||||
url=reverse('dashboard'),
|
||||
permission=request.user.has_perm('projector.can_manage_projector') or
|
||||
request.user.has_perm('projector.can_see_dashboard'),
|
||||
permission=request.user.has_perm('projector.can_see_dashboard'),
|
||||
selected=selected,
|
||||
)
|
||||
|
||||
@ -372,11 +386,23 @@ def get_widgets(request):
|
||||
"""
|
||||
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
|
||||
widgets.append(Widget(
|
||||
name='live_view',
|
||||
display_name=_('Projector live view'),
|
||||
template='projector/live_view_widget.html',
|
||||
context=RequestContext(request, {}),
|
||||
permission_required='projector.can_see_projector',
|
||||
default_column=2))
|
||||
|
||||
@ -389,8 +415,7 @@ def get_widgets(request):
|
||||
projector_overlay = ProjectorOverlay.objects.get(
|
||||
def_name=name)
|
||||
except ProjectorOverlay.DoesNotExist:
|
||||
projector_overlay = ProjectorOverlay(def_name=name,
|
||||
active=False)
|
||||
projector_overlay = ProjectorOverlay(def_name=name, active=False)
|
||||
projector_overlay.save()
|
||||
overlays.append(projector_overlay)
|
||||
|
||||
@ -407,10 +432,9 @@ def get_widgets(request):
|
||||
default_column=2,
|
||||
context=context))
|
||||
|
||||
|
||||
# Custom slide widget
|
||||
context = {
|
||||
'slides': ProjectorSlide.objects.all(),
|
||||
'slides': ProjectorSlide.objects.all().order_by('weight'),
|
||||
'welcomepage_is_active': not bool(config["presentation"])}
|
||||
widgets.append(Widget(
|
||||
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>
|
||||
<footer>
|
||||
<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>
|
||||
</footer>
|
||||
</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.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 openslides.utils.views import FrontPage
|
||||
|
||||
from openslides.utils.views import RedirectView
|
||||
|
||||
handler500 = 'openslides.utils.views.server_error'
|
||||
|
||||
urlpatterns = patterns('',
|
||||
# frontpage
|
||||
(r'^$', FrontPage.as_view()),
|
||||
# Redirect to dashboard URL
|
||||
url(r'^$', RedirectView.as_view(url='dashboard'), name='home',),
|
||||
|
||||
(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'^participant/', include('openslides.participant.urls')),
|
||||
(r'^config/', include('openslides.config.urls')),
|
||||
|
@ -37,8 +37,8 @@ class AnonymousAuth(object):
|
||||
|
||||
- try to return the permissions for the 'Anonymous' group
|
||||
"""
|
||||
if not user_obj.is_anonymous() or obj is not None or \
|
||||
not config['system_enable_anonymous']:
|
||||
if (not user_obj.is_anonymous() or obj is not None or
|
||||
not config['system_enable_anonymous']):
|
||||
return set()
|
||||
|
||||
perms = Permission.objects.filter(group__name='Anonymous')
|
||||
@ -60,8 +60,8 @@ class AnonymousAuth(object):
|
||||
"""
|
||||
Check if the user as a specific permission
|
||||
"""
|
||||
if not user_obj.is_anonymous() or obj is not None or \
|
||||
not config['system_enable_anonymous']:
|
||||
if (not user_obj.is_anonymous() or obj is not None or
|
||||
not config['system_enable_anonymous']):
|
||||
return False
|
||||
|
||||
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
|
||||
"""
|
||||
if not user_obj.is_anonymous() or \
|
||||
not config['system_enable_anonymous']:
|
||||
if (not user_obj.is_anonymous() or
|
||||
not config['system_enable_anonymous']):
|
||||
return False
|
||||
|
||||
for perm in self.get_all_permissions(user_obj):
|
||||
@ -87,10 +87,10 @@ class AnonymousAuth(object):
|
||||
"""
|
||||
return None
|
||||
|
||||
|
||||
def anonymous_context_additions(RequestContext):
|
||||
"""
|
||||
Add a variable to the request context that will indicate
|
||||
if anonymous login is possible at all.
|
||||
"""
|
||||
return {'os_enable_anonymous_login': config['system_enable_anonymous']}
|
||||
|
||||
|
@ -31,4 +31,3 @@ def patchup(dialect):
|
||||
return dialect
|
||||
|
||||
register_dialect("excel_semikolon", excel_semikolon)
|
||||
|
||||
|
@ -1 +1,3 @@
|
||||
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.util import ValidationError as FormValidationError
|
||||
|
||||
|
||||
class JSONFormField(Field):
|
||||
def clean(self, value):
|
||||
|
||||
@ -21,6 +22,7 @@ class JSONFormField(Field):
|
||||
raise FormValidationError(_("Enter valid JSON"))
|
||||
return value
|
||||
|
||||
|
||||
class JSONField(models.TextField):
|
||||
"""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,6 +12,7 @@
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class MinMaxIntegerField(models.IntegerField):
|
||||
def __init__(self, min_value=None, max_value=None, *args, **kwargs):
|
||||
self.min_value, self.max_value = min_value, max_value
|
||||
|
@ -20,6 +20,7 @@ from reportlab.pdfbase.ttfonts import TTFont
|
||||
from reportlab.rl_config import defaultPageSize
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils import formats
|
||||
# Import gettext for python 2.5 support
|
||||
from django.utils.translation import ugettext as _, gettext
|
||||
|
||||
@ -27,16 +28,16 @@ from openslides.config.models import config
|
||||
|
||||
|
||||
# register new truetype fonts
|
||||
pdfmetrics.registerFont(TTFont('Ubuntu', path_join(settings.SITE_ROOT,
|
||||
'static/fonts/Ubuntu-R.ttf')))
|
||||
pdfmetrics.registerFont(TTFont('Ubuntu-Bold', path_join(settings.SITE_ROOT,
|
||||
'static/fonts/Ubuntu-B.ttf')))
|
||||
pdfmetrics.registerFont(TTFont('Ubuntu-Italic', path_join(settings.SITE_ROOT,
|
||||
'static/fonts/Ubuntu-RI.ttf')))
|
||||
pdfmetrics.registerFont(TTFont(
|
||||
'Ubuntu', path_join(settings.SITE_ROOT, 'static/fonts/Ubuntu-R.ttf')))
|
||||
pdfmetrics.registerFont(TTFont(
|
||||
'Ubuntu-Bold', path_join(settings.SITE_ROOT, 'static/fonts/Ubuntu-B.ttf')))
|
||||
pdfmetrics.registerFont(TTFont(
|
||||
'Ubuntu-Italic', path_join(settings.SITE_ROOT, 'static/fonts/Ubuntu-RI.ttf')))
|
||||
|
||||
|
||||
# set style information
|
||||
PAGE_HEIGHT = defaultPageSize[1];
|
||||
PAGE_HEIGHT = defaultPageSize[1]
|
||||
PAGE_WIDTH = defaultPageSize[0]
|
||||
|
||||
|
||||
@ -104,17 +105,17 @@ stylesheet.add(ParagraphStyle(
|
||||
leftIndent=0,
|
||||
spaceAfter=15,
|
||||
))
|
||||
stylesheet.add(ParagraphStyle(name = 'Subitem',
|
||||
stylesheet.add(ParagraphStyle(
|
||||
name='Subitem',
|
||||
parent=stylesheet['Normal'],
|
||||
fontSize=10,
|
||||
leading=10,
|
||||
leftIndent=20,
|
||||
spaceAfter = 15)
|
||||
)
|
||||
stylesheet.add(ParagraphStyle(name = 'Tablecell',
|
||||
spaceAfter=15))
|
||||
stylesheet.add(ParagraphStyle(
|
||||
name='Tablecell',
|
||||
parent=stylesheet['Normal'],
|
||||
fontSize = 9)
|
||||
)
|
||||
fontSize=9))
|
||||
stylesheet.add(ParagraphStyle(name='Signaturefield',
|
||||
parent=stylesheet['Normal'],
|
||||
spaceBefore=15)
|
||||
@ -222,8 +223,8 @@ def firstPage(canvas, doc):
|
||||
|
||||
# time
|
||||
canvas.setFont('Ubuntu', 7)
|
||||
time = datetime.now().strftime(gettext("%Y-%m-%d %H:%Mh"))
|
||||
canvas.drawString(15 * cm, 28 * cm, _("Printed: %s") % time)
|
||||
time = formats.date_format(datetime.now(), 'DATETIME_FORMAT')
|
||||
canvas.drawString(15 * cm, 28 * cm, _("As of: %s") % time)
|
||||
|
||||
# title
|
||||
if doc.title:
|
||||
|
@ -11,12 +11,17 @@
|
||||
"""
|
||||
|
||||
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.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
|
||||
def person_id(self):
|
||||
return 'emtyuser'
|
||||
return 'empty'
|
||||
|
@ -13,6 +13,45 @@
|
||||
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):
|
||||
"""
|
||||
A Storage for a multiplicity of different Person-Objects.
|
||||
@ -25,13 +64,17 @@ class Persons(object):
|
||||
try:
|
||||
return iter(self._cache)
|
||||
except AttributeError:
|
||||
return iter(self.iter_persons())
|
||||
return self.iter_persons()
|
||||
|
||||
def __len__(self):
|
||||
return len(list(self.__iter__()))
|
||||
|
||||
def __getitem__(self, key):
|
||||
try:
|
||||
return list(self)[key]
|
||||
except IndexError:
|
||||
from openslides.utils.person import EmptyPerson
|
||||
return EmptyPerson()
|
||||
|
||||
def iter_persons(self):
|
||||
self._cache = list()
|
||||
@ -61,6 +104,6 @@ def get_person(person_id):
|
||||
try:
|
||||
person_prefix, id = split_person_id(person_id)
|
||||
except TypeError:
|
||||
from openslides.utils.person import EmtyPerson
|
||||
return EmtyPerson()
|
||||
from openslides.utils.person import EmptyPerson
|
||||
return EmptyPerson()
|
||||
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