diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..8655e4714 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,4 @@ +[run] +source=openslides +[report] +exclude_lines = def __(unicode|repr)__ diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..53cd07125 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +.gitattributes export-ignore +.gitignore export-ignore +/docs export-ignore +/extras export-ignore diff --git a/.gitignore b/.gitignore index c17fd65ee..7f7a1c453 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,10 @@ docs/_build/* *.egg-info build/* dist/* +.DS_Store +settings.py +versiontools* + +# Unit test / coverage reports +.coverage +htmlcov diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..f91d92918 --- /dev/null +++ b/.travis.yml @@ -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 diff --git a/AUTHORS b/AUTHORS index 72ee9b2ef..f4eeb7de7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -5,3 +5,5 @@ Authors of OpenSlides in chronological order of first contribution: Norman Jäckel René Köcher Andy Kittner + Moira Brülisauer (French translation) + Alexis Roussel (French translation) diff --git a/CHANGELOG b/CHANGELOG index bb83ba535..2f86821bf 100644 --- a/CHANGELOG +++ b/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: . (#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] diff --git a/INSTALL.txt b/INSTALL.txt index d27c338ed..f57e7f00e 100644 --- a/INSTALL.txt +++ b/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. diff --git a/MANIFEST.in b/MANIFEST.in index d028d548b..affdfa149 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -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 * diff --git a/README.txt b/README.txt index 3d7096b1d..0485a2203 100644 --- a/README.txt +++ b/README.txt @@ -1,16 +1,16 @@ - ================================== - English README file for OpenSlides - ================================== +================================== +English README file for OpenSlides +================================== -This is OpenSlides, version 1.2 (2012-07-25). +This is OpenSlides, version 1.3.1 (unreleased). What is OpenSlides? =================== -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) +Simply running + 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 - diff --git a/extras/scripts/create_local_settings.py b/extras/scripts/create_local_settings.py new file mode 100644 index 000000000..1702992e0 --- /dev/null +++ b/extras/scripts/create_local_settings.py @@ -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')) diff --git a/extras/win32-portable/create_portable.txt b/extras/win32-portable/create_portable.txt index 6770efd3f..5c5fa2c41 100644 --- a/extras/win32-portable/create_portable.txt +++ b/extras/win32-portable/create_portable.txt @@ -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. diff --git a/extras/win32-portable/prepare_portable.py b/extras/win32-portable/prepare_portable.py index 04c1e0cc7..dbdb15da9 100644 --- a/extras/win32-portable/prepare_portable.py +++ b/extras/win32-portable/prepare_portable.py @@ -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) diff --git a/manage.py b/manage.py index 2ed895048..af82b6f37 100644 --- a/manage.py +++ b/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) diff --git a/openslides/__init__.py b/openslides/__init__.py index f3f4c88a3..7cd9d7464 100644 --- a/openslides/__init__.py +++ b/openslides/__init__.py @@ -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 - else: - sub = '.dev' - - elif version[3] != 'final': - sub = "-" + version[3] + str(version[4]) - + # 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 = '' + 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) diff --git a/openslides/agenda/forms.py b/openslides/agenda/forms.py index bfaa30af6..66a4ee7a7 100644 --- a/openslides/agenda/forms.py +++ b/openslides/agenda/forms.py @@ -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 diff --git a/openslides/agenda/models.py b/openslides/agenda/models.py index d887d553c..0356607dd 100644 --- a/openslides/agenda/models.py +++ b/openslides/agenda/models.py @@ -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 @@ -45,7 +37,7 @@ class Item(MPTTModel, SlideMixin): closed = models.BooleanField(default=False, verbose_name=_("Closed")) weight = models.IntegerField(default=0, verbose_name=_("Weight")) parent = TreeForeignKey('self', null=True, blank=True, - related_name='children') + related_name='children') related_sid = models.CharField(null=True, blank=True, max_length=63) def get_related_slide(self): @@ -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. diff --git a/openslides/agenda/static/javascript/agenda.js b/openslides/agenda/static/javascript/agenda.js index 0b6603c39..93b2db91c 100644 --- a/openslides/agenda/static/javascript/agenda.js +++ b/openslides/agenda/static/javascript/agenda.js @@ -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'); diff --git a/openslides/agenda/views.py b/openslides/agenda/views.py index e87ab4ceb..11b600acd 100644 --- a/openslides/agenda/views.py +++ b/openslides/agenda/views.py @@ -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')] diff --git a/openslides/application/templates/application/widget.html b/openslides/application/templates/application/widget.html deleted file mode 100644 index e41c7b370..000000000 --- a/openslides/application/templates/application/widget.html +++ /dev/null @@ -1,34 +0,0 @@ -{% load staticfiles %} -{% load i18n %} -{% load tags %} - - - diff --git a/openslides/application/urls.py b/openslides/application/urls.py deleted file mode 100644 index 3b3e83e9e..000000000 --- a/openslides/application/urls.py +++ /dev/null @@ -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\d+)/$', - 'view', - name='application_view', - ), - - url(r'^(?P\d+)/agenda/$', - CreateAgendaItem.as_view(), - name='application_create_agenda', - ), - - url(r'^(?P\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\d+)/edit/$', - 'edit', - name='application_edit', - ), - - url(r'^(?P\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\d+)/setnumber/$', - 'set_number', - name='application_set_number', - ), - - url(r'^(?P\d+)/setstatus/(?P[a-z]{3})/$', - 'set_status', - name='application_set_status', - ), - - url(r'^(?P\d+)/permit/$', - 'permit', - name='application_permit', - ), - - url(r'^version/(?P\d+)/permit/$', - 'permit_version', - name='application_version_permit', - ), - - url(r'^version/(?P\d+)/reject/$', - 'reject_version', - name='application_version_reject', - ), - - url(r'^(?P\d+)/notpermit/$', - 'notpermit', - name='application_notpermit', - ), - - url(r'^(?P\d+)/reset/$', - 'reset', - name='application_reset', - ), - - url(r'^(?P\d+)/support/$', - 'support', - name='application_support', - ), - - url(r'^(?P\d+)/unsupport/$', - 'unsupport', - name='application_unsupport', - ), - - url(r'^(?P\d+)/gen_poll/$', - 'gen_poll', - name='application_gen_poll', - ), - - url(r'^print/$', - ApplicationPDF.as_view(), - {'application_id': None}, - name='print_applications', - ), - - url(r'^(?P\d+)/print/$', - ApplicationPDF.as_view(), - name='print_application', - ), - - url(r'^poll/(?P\d+)/print/$', - ApplicationPollPDF.as_view(), - name='print_application_poll', - ), - - url(r'^poll/(?P\d+)/$', - ViewPoll.as_view(), - name='application_poll_view', - ), - - url(r'^poll/(?P\d+)/del/$', - 'delete_poll', - name='application_poll_delete', - ), -) diff --git a/openslides/application/views.py b/openslides/application/views.py deleted file mode 100644 index 41b1d091c..000000000 --- a/openslides/application/views.py +++ /dev/null @@ -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 not 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 %s 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: %s.") % 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 %s.") % application) - continue - - title = application.title - application.delete(force=True) - messages.success(request, _("Motion %s 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 %s.") % self.object) - else: - title = self.object.title - self.object.delete(force=True) - messages.success(request, _("Motion %s was successfully deleted.") % title) - else: - messages.error(request, _("Invalid request")) - - def gen_confirm_form(self, request, message, url): - formbase = '%s
' % (message, url, csrf(request)['csrf_token']) - - if len(self.applications): - for application in self.applications: - formbase += '' % application.id - elif self.object: - formbase += '' % self.object.id - - formbase +='
' % (_("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 %s?') % 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 %s accepted.") % (aversion.aid)) - else: - gen_confirm_form(request, _('Do you really want to authorize version %s?') % 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 %s rejected.") % (aversion.aid)) - else: - messages.error(request, _("ERROR by rejecting the version.") ) - else: - gen_confirm_form(request, _('Do you really want to reject version %s?') % 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','
'), 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("%s:" % _("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("%s:" % _("Supporters"), stylesheet['Heading4'])) - - for supporter in application.supporters: - cell2b.append(Paragraph(".  %s" % unicode(supporter), stylesheet['Signaturefield'])) - if application.status == "pub": - for x in range(0,application.missing_supporters): - cell2b.append(Paragraph(".  __________________________________________",stylesheet['Signaturefield'])) - cell2b.append(Spacer(0,0.2*cm)) - - # status - note = "" - for n in application.notes: - note += "%s " % unicode(n) - cell3a = [] - cell3a.append(Paragraph("%s:" % _("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("%s:" % _("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("%s:" % _("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
%s: %s
%s: %s
%s: %s
%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','
'), 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','
'), 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 = "  " % 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')] diff --git a/openslides/assignment/forms.py b/openslides/assignment/forms.py index cf4dfcba2..9adf16dbd 100644 --- a/openslides/assignment/forms.py +++ b/openslides/assignment/forms.py @@ -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'}), + 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.")))) diff --git a/openslides/assignment/models.py b/openslides/assignment/models.py index bf288cd8e..56269b601 100644 --- a/openslides/assignment/models.py +++ b/openslides/assignment/models.py @@ -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(_('%s 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')] diff --git a/openslides/assignment/templates/assignment/overview.html b/openslides/assignment/templates/assignment/overview.html index 03ce6fd96..ac884f08a 100644 --- a/openslides/assignment/templates/assignment/overview.html +++ b/openslides/assignment/templates/assignment/overview.html @@ -42,7 +42,13 @@ {{ assignment }} - {{ assignment.candidates|length }} / {{ assignment.posts }} + + {% 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 %} + {{ assignment.get_status_display }} diff --git a/openslides/assignment/templates/assignment/view.html b/openslides/assignment/templates/assignment/view.html index 05411eb20..0829675f5 100644 --- a/openslides/assignment/templates/assignment/view.html +++ b/openslides/assignment/templates/assignment/view.html @@ -35,9 +35,10 @@ {% endif %}
+{% if assignment.status != "fin" %}

{% trans "Candidates" %}

    - {% for person in assignment.candidates %} + {% for person in assignment.get_participants %}
  1. {{ person }} {% if perms.assignment.can_manage_assignment %} @@ -45,6 +46,14 @@ {% endif %} {% endif %} + {% if person in assignment.elected %} + | {% trans "elected" %} + {% if perms.assignment.can_manage_assignment %} + {% if assignment.status == "sea" or assignment.status == "vot" %} + + {% endif %} + {% endif %} + {% endif %}
  2. {% empty %}
  3. {% trans "No candidates available." %}
  4. @@ -84,7 +93,24 @@ {% endif %} {% endif %} - +{% endif %} + +{% if perms.assignment.can_manage_assignments and blocked_candidates and assignment.status != "fin" %} +

    {% trans "Blocked Candidates" %}

    +
      + {% for person in blocked_candidates %} +
    • + {{ person }} +
    • + {% empty %} +
    • {% trans "No blocked candidates available." %}
    • + {% endfor %} +
    +{% endif %} + + + +{% if assignment.status != "sea" or polls.exists %}

    {% trans "Election results" %}

    {% if polls.exists %} @@ -174,8 +200,7 @@ {% if assignment.candidates and perms.assignment.can_manage_assignment and assignment.status == "vot" %} {% endif %} - - + {% for poll in polls %} diff --git a/openslides/assignment/templates/projector/Assignment.html b/openslides/assignment/templates/projector/Assignment.html index 8627d6022..35c10f64b 100644 --- a/openslides/assignment/templates/projector/Assignment.html +++ b/openslides/assignment/templates/projector/Assignment.html @@ -62,9 +62,9 @@ {% for candidate, poll_list in vote_results.items %} - - + {% for vote in poll_list %} - {% endfor %} - - + {% for poll in polls %} - {% endfor %} -
    {% trans 'Votes cast' %}
    - {% if candidate in assignment.elected.all %} +
    + {% if candidate in assignment.elected %} @@ -72,8 +72,8 @@ {{ candidate }} - {% if not assignment_publish_winner_results_only or candidate in assignment.elected.all %} + + {% if not assignment_publish_winner_results_only or candidate in assignment.elected %} {% if 'Yes' in vote and 'No' in vote and 'Abstain' in vote %} {{ vote.Yes }}
    {{ vote.No }}
    @@ -92,8 +92,7 @@ {% endfor %}
    {% trans 'Invalid votes' %} @@ -105,7 +104,6 @@ {% endfor %}
    {% trans 'Votes cast' %} @@ -120,10 +118,6 @@
    - {% elif assignment.candidates %} - {% trans "No ballots available." %} {% endif %} -
    {% endblock %} diff --git a/openslides/assignment/views.py b/openslides/assignment/views.py index f49bf097a..aa6640aa4 100644 --- a/openslides/assignment/views.py +++ b/openslides/assignment/views.py @@ -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') @@ -56,10 +48,10 @@ def get_overview(request): query = query.filter(status__iexact=request.GET['status']) try: sort = request.GET['sort'] - if sort in ['name','status']: + 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 %s was nominated successfully.") % (user)) except NameError, e: messages.error(request, e) + else: + messages.success(request, _( + "Candidate %s 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: %s.') % assignment.get_status_display()) except Assignment.DoesNotExist: pass + except NameError, e: + messages.error(request, e) return redirect(reverse('assignment_view', args=[assignment_id])) @@ -173,7 +172,7 @@ def run(request, assignment_id): assignment = Assignment.objects.get(pk=assignment_id) try: assignment.run(request.user, request.user) - messages.success(request, _('You have set your candidature successfully.') ) + messages.success(request, _('You have set your candidature successfully.')) except NameError, e: 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) - try: - if assignment.status == 'sea' or user.has_perm("assignment.can_manage_assignment"): - assignment.delrun(request.user) + if assignment.status == 'sea' or request.user.has_perm("assignment.can_manage_assignment"): + try: + assignment.delrun(request.user, blocked=True) + except Exception, e: + messages.error(request, e) else: - messages.error(request, _('The candidate list is already closed.')) - except Exception, e: - messages.error(request, e) + messages.success( + request, + _("You have withdrawn your candidature successfully. " + "You can not be nominated by other participants anymore.")) else: - messages.success(request, _("You have withdrawn your candidature successfully.") ) + 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 %s was withdrawn successfully.") % (person)) + if not is_blocked: + message = _("Candidate %s was withdrawn successfully.") % person + else: + message = _("%s was unblocked successfully.") % person + messages.success(request, message) else: - gen_confirm_form(request, - _("Do you really want to withdraw %s from the election?") \ - % person, reverse('assignment_delother', args=[assignment_id, user_id])) + if not is_blocked: + message = _("Do you really want to withdraw %s from the election?") % person + else: + message = _("Do you really want to unblock %s 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])) @@ -223,7 +233,7 @@ def set_active(request, assignment_id): @permission_required('assignment.can_manage_assignment') def gen_poll(request, assignment_id): poll = Assignment.objects.get(pk=assignment_id).gen_poll() - messages.success(request, _("New ballot was successfully created.") ) + messages.success(request, _("New ballot was successfully created.")) return redirect(reverse('assignment_poll_view', args=[poll.id])) @@ -262,9 +272,9 @@ def set_publish_status(request, poll_id): return ajax_request({'published': poll.published}) if poll.published: - messages.success(request, _("Ballot successfully published.") ) + messages.success(request, _("Ballot successfully published.")) else: - messages.success(request, _("Ballot successfully unpublished.") ) + messages.success(request, _("Ballot successfully unpublished.")) return redirect(reverse('assignment_view', args=[poll.assignment.id])) @@ -319,8 +329,9 @@ class AssignmentPDF(PDFView): try: assignment_id = self.kwargs['assignment_id'] assignment = Assignment.objects.get(id=assignment_id) - filename = u'%s-%s' % (_("Assignment"), - assignment.name.replace(' ','_')) + filename = u'%s-%s' % ( + _("Assignment"), + assignment.name.replace(' ', '_')) except: filename = _("Elections") return filename @@ -330,23 +341,24 @@ class AssignmentPDF(PDFView): assignment_id = self.kwargs['assignment_id'] except KeyError: assignment_id = None - if assignment_id is None: #print all assignments + if assignment_id is None: # print all assignments title = config["assignment_pdf_title"] story.append(Paragraph(title, stylesheet['Heading1'])) preamble = config["assignment_pdf_preamble"] if preamble: - story.append(Paragraph("%s" % preamble.replace('\r\n', '
    '), + story.append(Paragraph( + "%s" % preamble.replace('\r\n', '
    '), stylesheet['Paragraph'])) story.append(Spacer(0, 0.75 * cm)) - assignments = Assignment.objects.order_by('name') - if not assignments: # No assignments existing - story.append(Paragraph(_("No assignments available."), - stylesheet['Heading3'])) - else: # Print all assignments + assignments = Assignment.objects.all() + if not assignments: # No assignments existing + 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,28 +371,33 @@ 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("%s:" % + cell1a.append(Paragraph( + "%s:" % _("Number of available posts"), stylesheet['Bold'])) cell1b = [] cell1b.append(Paragraph(str(assignment.posts), stylesheet['Paragraph'])) # candidates cell2a = [] - cell2a.append(Paragraph("%s:%s:" % _("Candidates"), stylesheet['Heading4'])) cell2b = [] for candidate in assignment.candidates: - cell2b.append(Paragraph(".  %s" % candidate, + cell2b.append(Paragraph( + ".  %s" % candidate, stylesheet['Signaturefield'])) if assignment.status == "sea": for x in range(0, 2 * assignment.posts): - cell2b.append(Paragraph(".  " - "__________________________________________", - stylesheet['Signaturefield'])) + cell2b.append( + Paragraph( + ".  " + "__________________________________________", + stylesheet['Signaturefield'])) cell2b.append(Spacer(0, 0.2 * cm)) # Vote results @@ -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,25 +426,25 @@ 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") - % {'YES':vote['Yes'], 'NO': vote['No'], - 'ABSTAIN': vote['Abstain']}) + row.append( + _("Y: %(YES)s\nN: %(NO)s\nA: %(ABSTAIN)s") + % {'YES': vote['Yes'], 'NO': vote['No'], + 'ABSTAIN': vote['Abstain']}) elif 'Votes' in vote: row.append(vote['Votes']) else: @@ -448,15 +465,14 @@ class AssignmentPDF(PDFView): footrow_two.append(poll.print_votescast()) data_votes.append(footrow_two) - table_votes=Table(data_votes) - table_votes.setStyle( TableStyle([ + table_votes = Table(data_votes) + table_votes.setStyle(TableStyle([ ('GRID', (0, 0), (-1, -1), 0.5, colors.grey), - ('VALIGN',(0, 0),(-1, -1), 'TOP'), - ('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))), - ])) + ('VALIGN', (0, 0), (-1, -1), 'TOP'), + ('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)))])) # table data = [] @@ -467,17 +483,18 @@ class AssignmentPDF(PDFView): else: data.append([cell2a, cell2b]) data.append([Spacer(0, 0.2 * cm), '']) - t=Table(data) + 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', '
    '), 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) @@ -517,32 +536,37 @@ class AssignmentPollPDF(PDFView): imgpath = os.path.join(settings.SITE_ROOT, 'static/images/circle.png') circle = "  " % imgpath cell = [] - cell.append(Spacer(0,0.8*cm)) - cell.append(Paragraph(_("Election") + ": " + self.poll.assignment.name, + cell.append(Spacer(0, 0.8 * cm)) + 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)) - data= [] + data = [] # get ballot papers config values ballot_papers_selection = config["assignment_pdf_ballot_papers_selection"] ballot_papers_number = config["assignment_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() + number = User.objects.filter(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(User.objects.count()) + else: # ballot_papers_selection == "CUSTOM_NUMBER" number = int(ballot_papers_number) number = max(1, number) @@ -550,16 +574,18 @@ 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 - + _("No") + "  " * 3 + circle+ _("Abstention"), + 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 for user in xrange(number / 2): @@ -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) @@ -611,16 +639,15 @@ class Config(FormView): def get_initial(self): return { 'assignment_publish_winner_results_only': - config['assignment_publish_winner_results_only'], + config['assignment_publish_winner_results_only'], 'assignment_pdf_ballot_papers_selection': - config['assignment_pdf_ballot_papers_selection'], + config['assignment_pdf_ballot_papers_selection'], 'assignment_pdf_ballot_papers_number': - config['assignment_pdf_ballot_papers_number'], + config['assignment_pdf_ballot_papers_number'], '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')] diff --git a/openslides/config/forms.py b/openslides/config/forms.py index cf5fbf3c1..5e7f001d2 100644 --- a/openslides/config/forms.py +++ b/openslides/config/forms.py @@ -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, diff --git a/openslides/config/models.py b/openslides/config/models.py index b4cc915c0..01ac2fd1d 100644 --- a/openslides/config/models.py +++ b/openslides/config/models.py @@ -51,7 +51,7 @@ class Config(object): pass for receiver, value in default_config_value.send(sender='config', - key=key): + key=key): if value is not None: return value if settings.DEBUG: @@ -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.") % - " \ - www.openslides.org", + '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}) diff --git a/openslides/config/templates/config/general.html b/openslides/config/templates/config/general.html index 585adf31e..904913a28 100644 --- a/openslides/config/templates/config/general.html +++ b/openslides/config/templates/config/general.html @@ -25,9 +25,9 @@

    - {% trans "Frontpage" %} + {% trans "Welcome Widget" %} {% for field in form %} - {% if "id_frontpage" in field.label_tag %} + {% if "id_welcome" in field.label_tag %}

    {{ field.errors }} {{ field.required }} diff --git a/openslides/config/views.py b/openslides/config/views.py index 5472be8fa..1c2ccdc4b 100644 --- a/openslides/config/views.py +++ b/openslides/config/views.py @@ -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 diff --git a/openslides/openslides_global_settings.py b/openslides/global_settings.py similarity index 82% rename from openslides/openslides_global_settings.py rename to openslides/global_settings.py index 1da9f28fe..adb51aea7 100644 --- a/openslides/openslides_global_settings.py +++ b/openslides/global_settings.py @@ -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,17 +59,17 @@ 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). # Examples: "http://static.lawrence.com", "http://example.com/static/" -STATIC_URL = '/static/' +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__)) diff --git a/openslides/locale/README.txt b/openslides/locale/README.txt index 2fc227be8..a54166107 100644 --- a/openslides/locale/README.txt +++ b/openslides/locale/README.txt @@ -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): diff --git a/openslides/locale/de/LC_MESSAGES/django.mo b/openslides/locale/de/LC_MESSAGES/django.mo index 8690a8568..49342f7b9 100644 Binary files a/openslides/locale/de/LC_MESSAGES/django.mo and b/openslides/locale/de/LC_MESSAGES/django.mo differ diff --git a/openslides/locale/de/LC_MESSAGES/django.po b/openslides/locale/de/LC_MESSAGES/django.po index f9df2bc45..4831c9e46 100644 --- a/openslides/locale/de/LC_MESSAGES/django.po +++ b/openslides/locale/de/LC_MESSAGES/django.po @@ -7,63 +7,69 @@ msgid "" msgstr "" "Project-Id-Version: OpenSlides 1.x\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-11 19:40+0200\n" +"POT-Creation-Date: 2013-01-02 22:59+0100\n" "PO-Revision-Date: 2012-07-28 11:07+0200\n" -"Last-Translator: Oskar Hahn \n" -"Language-Team: Deutsch \n" +"Last-Translator: Emanuel Schuetze \n" +"Language-Team: support@openslides.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" -#: openslides_global_settings.py:36 +#: global_settings.py:33 msgid "German" msgstr "Deutsch" -#: openslides_global_settings.py:37 +#: global_settings.py:34 msgid "English" msgstr "Englisch" +#: global_settings.py:35 +msgid "French" +msgstr "Französisch" + #: agenda/forms.py:28 msgid "Parent item" msgstr "Elternelement" -#: agenda/models.py:42 application/forms.py:22 application/models.py:540 -#: application/templates/application/view.html:100 config/forms.py:61 -#: projector/models.py:32 +#: agenda/models.py:34 config/forms.py:59 motion/forms.py:22 +#: motion/models.py:539 motion/templates/motion/view.html:100 +#: projector/models.py:29 msgid "Title" msgstr "Titel" -#: agenda/models.py:43 application/forms.py:23 application/models.py:541 -#: application/templates/application/view.html:101 projector/models.py:33 +#: agenda/models.py:35 motion/forms.py:23 motion/models.py:540 +#: motion/templates/motion/view.html:101 projector/models.py:30 msgid "Text" msgstr "Text" -#: agenda/models.py:44 agenda/templates/agenda/overview.html:77 -#: agenda/templates/agenda/view.html:13 participant/models.py:52 +#: agenda/models.py:36 agenda/templates/agenda/overview.html:77 +#: agenda/templates/agenda/view.html:13 participant/models.py:60 #: participant/templates/participant/overview.html:108 +#: participant/templates/participant/user_detail.html:45 msgid "Comment" msgstr "Kommentar" -#: agenda/models.py:45 +#: agenda/models.py:37 msgid "Closed" msgstr "Abgeschlossen" -#: agenda/models.py:46 agenda/templates/agenda/overview.html:83 +#: agenda/models.py:38 agenda/templates/agenda/overview.html:83 +#: projector/models.py:31 msgid "Weight" msgstr "Gewichtung" -#: agenda/models.py:180 +#: agenda/models.py:171 msgid "Can see agenda" msgstr "Darf die Tagesordnung sehen" -#: agenda/models.py:181 +#: agenda/models.py:172 msgid "Can manage agenda" msgstr "Darf die Tagesordung verwalten" -#: agenda/models.py:189 agenda/slides.py:20 agenda/views.py:194 -#: agenda/views.py:195 agenda/views.py:214 +#: agenda/models.py:180 agenda/slides.py:20 agenda/views.py:191 +#: agenda/views.py:192 agenda/views.py:212 agenda/views.py:227 #: agenda/templates/agenda/config.html:8 #: agenda/templates/agenda/overview.html:8 #: agenda/templates/agenda/overview.html:56 @@ -73,34 +79,34 @@ msgstr "Darf die Tagesordung verwalten" msgid "Agenda" msgstr "Tagesordnung" -#: agenda/views.py:57 +#: agenda/views.py:54 msgid "You are not authorized to manage the agenda." msgstr "Sie sind nicht berechtigt die Tagesordnung zu ändern." -#: agenda/views.py:73 +#: agenda/views.py:70 msgid "Errors when reordering of the agenda" msgstr "Fehler beim Neusortieren der Tagesordnung" -#: agenda/views.py:134 +#: agenda/views.py:131 #, python-format msgid "Item %s was successfully modified." msgstr "Eintrag %s wurde erfolgreich bearbeitet." -#: agenda/views.py:155 +#: agenda/views.py:152 #, python-format msgid "Item %s was successfully created." msgstr "Eintrag %s wurde erfolgreich angelegt." -#: agenda/views.py:172 +#: agenda/views.py:169 msgid "Yes, with all child items." msgstr "Ja, mit allen Kindelementen." -#: agenda/views.py:180 +#: agenda/views.py:177 #, python-format msgid "Item %s and his children were successfully deleted." msgstr "Eintrag %s und seine Kindelemente wurde erfolgreich gelöscht." -#: agenda/views.py:185 +#: agenda/views.py:182 #, python-format msgid "Item %s was successfully deleted." msgstr "Eintrag %s wurde erfolgreich gelöscht." @@ -110,38 +116,36 @@ msgid "Agenda settings" msgstr "Tagesordnungs-Einstellungen" #: agenda/templates/agenda/config.html:8 -#: application/templates/application/config.html:8 -#: assignment/templates/assignment/config.html:8 config/views.py:119 +#: assignment/templates/assignment/config.html:8 config/views.py:110 #: config/templates/config/general.html:8 +#: motion/templates/motion/config.html:8 #: participant/templates/participant/config.html:8 msgid "Configuration" msgstr "Konfiguration" #: agenda/templates/agenda/config.html:15 agenda/templates/agenda/edit.html:40 -#: application/templates/application/config.html:15 -#: application/templates/application/edit.html:41 -#: application/templates/application/poll_view.html:75 #: assignment/templates/assignment/config.html:15 #: assignment/templates/assignment/poll_view.html:66 #: config/templates/config/general.html:58 +#: motion/templates/motion/config.html:15 motion/templates/motion/edit.html:41 +#: motion/templates/motion/poll_view.html:76 #: projector/templates/projector/select_widgets.html:28 #: templates/formbuttons_save.html:4 templates/formbuttons_saveapply.html:4 msgid "Save" msgstr "Speichern" #: agenda/templates/agenda/config.html:19 agenda/templates/agenda/edit.html:47 -#: application/templates/application/config.html:19 -#: application/templates/application/edit.html:48 -#: application/templates/application/import.html:41 -#: application/templates/application/poll_view.html:81 #: assignment/templates/assignment/config.html:19 #: assignment/templates/assignment/edit.html:31 #: assignment/templates/assignment/poll_view.html:73 #: config/templates/config/general.html:62 +#: motion/templates/motion/config.html:19 motion/templates/motion/edit.html:48 +#: motion/templates/motion/import.html:44 +#: motion/templates/motion/poll_view.html:82 #: participant/templates/participant/config.html:16 #: participant/templates/participant/edit.html:36 #: participant/templates/participant/group_edit.html:31 -#: participant/templates/participant/import.html:32 +#: participant/templates/participant/import.html:33 msgid "Cancel" msgstr "Abbrechen" @@ -156,36 +160,34 @@ msgid "New item" msgstr "Neuer Eintrag" #: agenda/templates/agenda/edit.html:22 -#: application/templates/application/edit.html:22 -#: application/templates/application/import.html:10 -#: application/templates/application/view.html:29 #: assignment/templates/assignment/edit.html:22 #: assignment/templates/assignment/view.html:19 +#: motion/templates/motion/edit.html:22 motion/templates/motion/import.html:10 +#: motion/templates/motion/view.html:29 #: participant/templates/participant/edit.html:22 #: participant/templates/participant/group_edit.html:22 #: participant/templates/participant/import.html:11 #: projector/templates/projector/new.html:11 #: projector/templates/projector/select_widgets.html:10 msgid "Back to overview" -msgstr "Zurück zur Übersicht" +msgstr "" #: agenda/templates/agenda/edit.html:43 -#: application/templates/application/edit.html:44 -#: application/templates/application/poll_view.html:78 #: assignment/templates/assignment/poll_view.html:69 -#: assignment/templates/assignment/view.html:74 +#: assignment/templates/assignment/view.html:84 +#: motion/templates/motion/edit.html:44 +#: motion/templates/motion/poll_view.html:79 #: projector/templates/projector/control_overlay_message.html:8 #: templates/formbuttons_saveapply.html:7 msgid "Apply" msgstr "Übernehmen" #: agenda/templates/agenda/edit.html:51 -#: application/templates/application/edit.html:52 -#: application/templates/application/import.html:44 #: assignment/templates/assignment/edit.html:34 +#: motion/templates/motion/edit.html:52 motion/templates/motion/import.html:47 #: participant/templates/participant/edit.html:39 #: participant/templates/participant/group_edit.html:34 -#: participant/templates/participant/import.html:35 +#: participant/templates/participant/import.html:36 #: projector/templates/projector/new.html:19 msgid "required" msgstr "erforderlich" @@ -216,46 +218,45 @@ msgstr "Zusammenfassung für diesen Eintrag projizieren" msgid "Do you want to save the changed order of agenda items?" msgstr "Möchten Sie die geänderte Reihenfolge der Einträge speichern?" -#: agenda/templates/agenda/overview.html:50 application/models.py:574 -#: application/views.py:486 application/views.py:798 application/views.py:849 -#: application/templates/application/view.html:216 -#: application/templates/projector/Application.html:37 -#: assignment/models.py:260 assignment/views.py:561 -#: assignment/templates/assignment/view.html:145 -#: assignment/templates/assignment/view.html:149 +#: agenda/templates/agenda/overview.html:50 assignment/models.py:291 +#: assignment/views.py:587 assignment/templates/assignment/view.html:172 +#: assignment/templates/assignment/view.html:176 #: assignment/templates/projector/Assignment.html:78 -#: assignment/templates/projector/Assignment.html:82 utils/utils.py:53 -#: utils/views.py:111 +#: assignment/templates/projector/Assignment.html:82 motion/models.py:574 +#: motion/views.py:830 motion/views.py:881 +#: motion/templates/motion/view.html:214 +#: motion/templates/projector/Motion.html:37 utils/utils.py:49 +#: utils/views.py:108 msgid "Yes" msgstr "Ja" -#: agenda/templates/agenda/overview.html:51 application/models.py:574 -#: application/views.py:486 application/views.py:798 application/views.py:850 -#: application/templates/application/view.html:217 -#: application/templates/projector/Application.html:38 -#: assignment/models.py:260 assignment/views.py:562 -#: assignment/templates/assignment/view.html:146 -#: assignment/templates/projector/Assignment.html:79 utils/utils.py:53 -#: utils/views.py:111 +#: agenda/templates/agenda/overview.html:51 assignment/models.py:291 +#: assignment/views.py:588 assignment/templates/assignment/view.html:173 +#: assignment/templates/projector/Assignment.html:79 motion/models.py:574 +#: motion/views.py:830 motion/views.py:882 +#: motion/templates/motion/view.html:215 +#: motion/templates/projector/Motion.html:38 utils/utils.py:49 +#: utils/views.py:108 msgid "No" msgstr "Nein" #: agenda/templates/agenda/overview.html:59 -#: application/templates/application/overview.html:14 #: assignment/templates/assignment/overview.html:13 -#: participant/templates/participant/group_overview.html:11 +#: motion/templates/motion/overview.html:14 +#: participant/templates/participant/group_overview.html:12 #: participant/templates/participant/overview.html:22 #: projector/templates/projector/custom_slide_widget.html:20 msgid "New" -msgstr "Neu" +msgstr "" #: agenda/templates/agenda/overview.html:61 +#, fuzzy msgid "Print agenda as PDF" -msgstr "Tagesordnung als PDF drucken" +msgstr "Tagesordnung als PDF" #: agenda/templates/agenda/overview.html:66 msgid "Hide closed items" -msgstr "Abgeschlossene Einträge verbergen" +msgstr "Verstecke abgeschlossene Einträge" #: agenda/templates/agenda/overview.html:70 msgid "item" @@ -268,9 +269,9 @@ msgid "Item" msgstr "Eintrag" #: agenda/templates/agenda/overview.html:80 -#: application/templates/application/overview.html:60 #: assignment/templates/assignment/overview.html:39 -#: participant/templates/participant/group_overview.html:19 +#: motion/templates/motion/overview.html:60 +#: participant/templates/participant/group_overview.html:20 #: participant/templates/participant/overview.html:110 msgid "Actions" msgstr "Aktionen" @@ -283,910 +284,37 @@ msgstr "Keine Einträge vorhanden." #: agenda/templates/agenda/widget.html:10 #: agenda/templates/agenda/widget.html:29 -#: application/templates/application/widget.html:17 #: assignment/templates/assignment/widget.html:17 +#: motion/templates/motion/widget.html:17 +#: participant/templates/participant/group_widget.html:17 +#: participant/templates/participant/user_widget.html:16 #: projector/templates/projector/custom_slide_widget.html:11 #: projector/templates/projector/custom_slide_widget.html:36 msgid "Preview" msgstr "Vorschau" #: agenda/templates/agenda/widget.html:23 -#: application/templates/application/widget.html:11 -#: assignment/templates/assignment/view.html:112 +#: assignment/templates/assignment/view.html:139 #: assignment/templates/assignment/widget.html:11 +#: motion/templates/motion/widget.html:11 +#: participant/templates/participant/group_widget.html:11 +#: participant/templates/participant/user_widget.html:10 #: projector/templates/projector/custom_slide_widget.html:33 msgid "Delete" msgstr "Löschen" #: agenda/templates/agenda/widget.html:26 -#: application/templates/application/widget.html:14 -#: assignment/templates/assignment/view.html:111 +#: assignment/templates/assignment/view.html:138 #: assignment/templates/assignment/widget.html:14 +#: motion/templates/motion/widget.html:14 +#: participant/templates/participant/group_widget.html:14 +#: participant/templates/participant/user_widget.html:13 #: projector/templates/projector/custom_slide_widget.html:30 msgid "Edit" msgstr "Bearbeiten" -#: application/forms.py:25 application/models.py:542 application/views.py:817 -#: application/templates/application/view.html:85 -#: application/templates/application/view.html:102 -#: application/templates/projector/Application.html:77 -msgid "Reason" -msgstr "Begründung" - -#: application/forms.py:30 -msgid "Trivial change" -msgstr "Triviale Änderung" - -#: application/forms.py:31 -msgid "Trivial changes don't create a new version." -msgstr "Triviale Änderungen erzeugen keine neue Version." - -#: application/forms.py:44 application/views.py:748 -#: application/templates/application/view.html:168 -msgid "Supporters" -msgstr "Unterstützer/innen" - -#: application/forms.py:50 participant/forms.py:102 -msgid "CSV File" -msgstr "CSV-Datei" - -#: application/forms.py:54 -msgid "Import motions with status \"authorized\"" -msgstr "Anträge als \"Zugelassen\" importieren" - -#: application/forms.py:55 -msgid "Set the initial status for each motion to \"authorized\"" -msgstr "Setzt den initialen Status für jeden Antrag auf \"zugelassen\"" - -#: application/forms.py:63 -msgid "Number of (minimum) required supporters for a motion" -msgstr "Mindestanzahl erforderlicher Unterstützer/innen für einen Antrag" - -#: application/forms.py:67 -msgid "Choose 0 to disable the supporting system" -msgstr "Wähle 0 um das Unterstützersystem zu deaktivieren" - -#: application/forms.py:72 -msgid "Motion preamble" -msgstr "Antragseinleitung" - -#: application/forms.py:77 assignment/forms.py:47 -msgid "Number of ballot papers (selection)" -msgstr "Anzahl der Stimmzettel (Vorauswahl)" - -#: application/forms.py:79 assignment/forms.py:49 -msgid "Number of all delegates" -msgstr "Anzahl aller Delegierten" - -#: application/forms.py:80 assignment/forms.py:50 -msgid "Number of all participants" -msgstr "Anzahl aller Teilnehmer/innen" - -#: application/forms.py:81 assignment/forms.py:51 -msgid "Use the following custom number" -msgstr "Verwende die folgende benutzerdefinierte Anzahl" - -#: application/forms.py:88 assignment/forms.py:58 -msgid "Custom number of ballot papers" -msgstr "Benutzerdefinierte Anzahl von Stimmzetteln" - -#: application/forms.py:93 -msgid "Title for PDF document (all motions)" -msgstr "Titel für PDF-Dokument (alle Anträge)" - -#: application/forms.py:98 -msgid "Preamble text for PDF document (all motions)" -msgstr "Einleitungstext für PDF-Dokument (alle Wahlen) " - -#: application/forms.py:102 -msgid "Allow trivial changes" -msgstr "Triviale Änderungen erlauben" - -#: application/forms.py:103 -msgid "Warning: Trivial changes undermine the motions autorisation system." -msgstr "" -"Warnung: Triviale Änderungen unterlaufen das Zulassungssystem von Anträgen." - -#: application/models.py:45 -msgid "Published" -msgstr "Veröffentlicht" - -#: application/models.py:46 -msgid "Permitted" -msgstr "Zugelassen" - -#: application/models.py:47 application/templates/application/overview.html:39 -#: application/templates/application/view.html:303 -msgid "Accepted" -msgstr "Angenommen" - -#: application/models.py:48 application/templates/application/overview.html:40 -#: application/templates/application/view.html:308 -msgid "Rejected" -msgstr "Abgelehnt" - -#: application/models.py:49 -msgid "Withdrawed" -msgstr "Zurückgezogen" - -#: application/models.py:50 application/templates/application/view.html:323 -msgid "Adjourned" -msgstr "Vertagt" - -# please check! -#: application/models.py:51 application/templates/application/view.html:326 -msgid "Not Concerned" -msgstr "Nicht befasst" - -# please check! -#: application/models.py:52 application/templates/application/view.html:329 -msgid "Commited a bill" -msgstr "Verwiesen (in Ausschuss)" - -#: application/models.py:53 -msgid "Rejected (not authorized)" -msgstr "Verworfen (nicht zulässig)" - -#: application/models.py:54 application/templates/application/overview.html:42 -msgid "Needs Review" -msgstr "Benötigt Review" - -#: application/models.py:66 application/views.py:732 -#: application/templates/application/overview.html:58 -#: application/templates/application/view.html:161 -#: application/templates/projector/Application.html:55 -msgid "Submitter" -msgstr "Antragsteller/in" - -#: application/models.py:103 -#, python-format -msgid "Version %d authorized" -msgstr "Version %d zugelassen" - -#: application/models.py:139 -msgid "Searching for supporters." -msgstr "Auf Unterstützersuche." - -#: application/models.py:141 -msgid "Not yet authorized." -msgstr "Noch nicht zugelassen." - -#: application/models.py:143 -msgid "Not yet authorized changes." -msgstr "Noch nicht zugelassene Änderungen." - -#: application/models.py:220 -#, python-format -msgid "" -"Trivial changes to version %(version)d; changed fields: %(changed_fields)s" -msgstr "" -"Triviale Änderung an Version %(version)d; Geänderte Felder: " -"%(changed_fields)s" - -#: application/models.py:231 -#, python-format -msgid "Version %s created" -msgstr "Version %s erstellt" - -#: application/models.py:241 -msgid "Supporters removed" -msgstr "Unterstützer/innen gelöscht" - -#: application/models.py:250 -#, python-format -msgid "Status reseted to: %s" -msgstr "Status zurückgesetzt auf: %s" - -#: application/models.py:265 -#, python-format -msgid "Supporter: +%s" -msgstr "Unterstützer/in: +%s" - -#: application/models.py:279 -#, python-format -msgid "Supporter: -%s" -msgstr "Unterstützer/in: -%s" - -#: application/models.py:296 -#, python-format -msgid "Number set: %s" -msgstr "Nummer gesetzt: %s" - -#: application/models.py:309 -#, python-format -msgid "Version %s authorized" -msgstr "Version %s zugelassen" - -#: application/models.py:323 -#, python-format -msgid "Version %s not authorized" -msgstr "Version %s nicht zugelassen" - -#: application/models.py:336 assignment/models.py:65 -#, python-format -msgid "%s is not a valid status." -msgstr "%s ist kein gültiger Status." - -#: application/models.py:339 -#, python-format -msgid "The motion status is already '%s.'" -msgstr "Der Antragsstatus ist bereits '%s'." - -#: application/models.py:347 -#, python-format -msgid "" -"The motion status is: '%(currentstatus)s'. You can not set the status to " -"'%(newstatus)s'." -msgstr "" -"Der Antragsstatus ist: '%(currentstatus)s'. Sie können den Status nicht auf " -"'%(newstatus)s' setzen." - -#: application/models.py:355 -msgid "Status modified" -msgstr "Status geändert" - -#: application/models.py:447 -msgid "by" -msgstr "von" - -#: application/models.py:455 application/templates/application/view.html:12 -#: application/templates/application/view.html:23 -#: application/templates/application/widget.html:27 -#: application/templates/projector/Application.html:65 -msgid "no number" -msgstr "ohne Nummer" - -#: application/models.py:456 -#: application/templates/application/overview.html:47 -#: application/templates/application/overview.html:48 -#: application/templates/application/widget.html:23 -msgid "motion" -msgstr "Antrag" - -#: application/models.py:481 -msgid "Poll created" -msgstr "Abstimmung erstellt" - -#: application/models.py:532 -msgid "Can see motions" -msgstr "Darf Anträge sehen" - -#: application/models.py:533 -msgid "Can create motions" -msgstr "Darf Anträge erstellen" - -#: application/models.py:534 -msgid "Can support motions" -msgstr "Darf Anträge unterstützen" - -#: application/models.py:535 -msgid "Can manage motions" -msgstr "Darf Anträge verwalten" - -#: application/models.py:575 assignment/models.py:261 -msgid "Abstain" -msgstr "Enthaltung" - -#: application/models.py:601 -msgid "The Assembly may decide," -msgstr "Die Versammlung möge beschließen," - -#: application/models.py:604 application/templates/application/overview.html:7 -#: application/templates/application/overview.html:11 -msgid "Motions" -msgstr "Anträge" - -#: application/views.py:180 -msgid "You have not the necessary rights to create or edit motions." -msgstr "" -"Sie haben nicht die nötigen Rechte, um Anträge zu erstellen oder zu " -"bearbeiten." - -#: application/views.py:185 -msgid "You can not edit this motion. You are not the submitter." -msgstr "" -"Sie dürfen diesen Antrag nicht bearbeiten. Sie sind nicht der Antragsteller" - -#: application/views.py:248 -msgid "New motion was successfully created." -msgstr "Neuer Antrag wurde erfolgreich angelegt." - -#: application/views.py:250 -msgid "Motion was successfully modified." -msgstr "Antrag wurde erfolgreich geändert." - -#: application/views.py:257 application/views.py:656 assignment/views.py:133 -#: participant/views.py:459 participant/views.py:482 utils/views.py:210 -#: utils/views.py:228 utils/views.py:252 -msgid "Please check the form for errors." -msgstr "Bitte kontrollieren Sie das Formular nach Fehlern." - -#: application/views.py:264 -msgid "" -"Attention: Do you really want to edit this motion? The supporters will " -"not be removed automatically because you can manage motions. Please " -"check if the supports are valid after your changing!" -msgstr "" -"Achtung: Wollen Sie den Antrag wirklich ändern? Die Unterstützer/innen " -"werden nicht automatisch entfernt, da Sie Anträge verwalten dürfen. " -"Prüfen Sie, ob die Unterstützungen noch gültig sind." - -#: application/views.py:266 -#, python-format -msgid "" -"Attention: Do you really want to edit this motion? All %s supporters " -"will be removed! Try to convince the supporters again." -msgstr "" -"Wollen Sie den Antrag wirklich ändern? Alle %s Unterstützer/innen " -"werden dann automatisch entfernt. Versuchen Sie diese erneut zu gewinnen." - -#: application/views.py:298 -msgid "Motion number was successfully set." -msgstr "Antragsnummer wurde erfolgreich gesetzt." - -#: application/views.py:314 -msgid "Motion was successfully authorized." -msgstr "Antrag wurde erfolgreich zugelassen." - -#: application/views.py:329 -msgid "Motion was successfully rejected." -msgstr "Antrag wurde erfolgreich verworfen." - -#: application/views.py:345 -#, python-format -msgid "Motion status was set to: %s." -msgstr "Antragsstatus wurde gesetzt auf: %s." - -#: application/views.py:361 -msgid "Motion status was reset." -msgstr "Antragsstatus wurde zurückgesetzt." - -#: application/views.py:375 -msgid "You have support the motion successfully." -msgstr "Sie haben den Antrag erfolgreich unterstützt." - -#: application/views.py:389 -msgid "You have unsupport the motion successfully." -msgstr "Sie haben dem Antrag erfolgreich Ihre Unterstützung entzogen." - -#: application/views.py:403 -msgid "New vote was successfully created." -msgstr "Neue Abstimmung erfolgreich angelegt." - -#: application/views.py:419 -msgid "Poll deleted" -msgstr "Abstimmung gelöscht" - -#: application/views.py:420 -msgid "Poll was successfully deleted." -msgstr "Abstimmung wurde erfolgreich gelöscht." - -#: application/views.py:422 -#, python-format -msgid "the %s. poll" -msgstr "die %s. Abstimmung" - -#: application/views.py:460 application/views.py:469 -#, python-format -msgid "You can not delete motion %s." -msgstr "Sie können Antrag %s nicht löschen." - -#: application/views.py:465 application/views.py:473 -#, python-format -msgid "Motion %s was successfully deleted." -msgstr "Antrag %s wurde erfolgreich gelöscht." - -#: application/views.py:475 -msgid "Invalid request" -msgstr "Ungültige Anfrage" - -#: application/views.py:494 -msgid "Do you really want to delete multiple motions?" -msgstr "Wollen Sie wirklich mehrere Anträge löschen?" - -#: application/views.py:496 -#, python-format -msgid "Do you really want to delete %s?" -msgstr "Soll %s wirklich gelöscht werden?" - -#: application/views.py:520 -msgid "Poll was updated" -msgstr "Abstimmung wurde aktualisiert" - -#: application/views.py:537 -#, python-format -msgid "Version %s accepted." -msgstr "Version %s akzeptiert." - -#: application/views.py:539 -#, python-format -msgid "Do you really want to authorize version %s?" -msgstr "Soll Version %s wirklich zugelassen werden?" - -#: application/views.py:549 -#, python-format -msgid "Version %s rejected." -msgstr "Version %s zurückgewiesen." - -#: application/views.py:551 -msgid "ERROR by rejecting the version." -msgstr "FEHLER beim Zurückweisen der Version." - -#: application/views.py:553 -#, python-format -msgid "Do you really want to reject version %s?" -msgstr "Soll Version %s wirklich zurückgewiesen werden?" - -#: application/views.py:583 application/views.py:587 application/views.py:593 -#: application/views.py:596 participant/api.py:77 -#, python-format -msgid "Ignoring malformed line %d in import file." -msgstr "Fehlerhafte Zeile %d der Quelldatei wurde ignoriert." - -#: application/views.py:642 -#, python-format -msgid "%d motion was successfully imported." -msgid_plural "%d motions were successfully imported." -msgstr[0] "%d Antrag wurde erfolgreich importiert." -msgstr[1] "%d Anträge wurden erfolgreich importiert." - -#: application/views.py:645 -#, python-format -msgid "%d motion was successfully modified." -msgid_plural "%d motions were successfully modified." -msgstr[0] "%d Antrag wurde erfolgreich geändert." -msgstr[1] "%d Anträge wurden erfolgreich geändert." - -#: application/views.py:648 -#, python-format -msgid "%d new user was added." -msgid_plural "%d new users were added." -msgstr[0] "%d neuer Nutzer wurde erstellt." -msgstr[1] "%d neue Nutzer wurden erstellt." - -#: application/views.py:652 participant/api.py:93 -msgid "Import aborted because of severe errors in the input file." -msgstr "Import auf Grund von schweren Fehlern in der Quelldatei abgebrochen." - -#: application/views.py:654 participant/api.py:95 -msgid "Import file has wrong character encoding, only UTF-8 is supported!" -msgstr "" -"Die Quelldatei benutzt eine ungültige Zeichenkodierung, es wird nur UTF-8 " -"wird unterstützt!" - -#: application/views.py:658 -msgid "" -"Attention: Existing motions will be modified if you import new motions with " -"the same number." -msgstr "" -"Achtung: Existierende Anträge werden geändert wenn Sie neue Anträge mit " -"identischer Nummer importieren." - -#: application/views.py:659 -msgid "" -"Attention: Importing an motions without a number multiple times will create " -"duplicates." -msgstr "" -"Achtung: Bei mehrfachem Import eines Antrags ohne Nummer können Duplikate " -"entstehen." - -#: application/views.py:685 application/views.py:911 -#: application/templates/application/config.html:8 -msgid "Applications" -msgstr "Anträge" - -#: application/views.py:692 application/views.py:831 -msgid "Application" -msgstr "Antrag" - -#: application/views.py:706 -#: application/templates/application/overview.html:101 -msgid "No motions available." -msgstr "Keine Anträge vorhanden." - -#: application/views.py:711 application/views.py:713 application/views.py:725 -#: application/views.py:727 -#: application/templates/projector/Application.html:63 -msgid "Motion No." -msgstr "Antrag Nr." - -#: application/views.py:762 application/templates/application/overview.html:34 -#: application/templates/application/overview.html:57 -#: application/templates/application/view.html:180 -#: application/templates/projector/Application.html:11 -#: assignment/templates/assignment/overview.html:24 -#: assignment/templates/assignment/overview.html:38 -#: assignment/templates/assignment/view.html:211 -#: assignment/templates/projector/Assignment.html:18 -#: participant/templates/participant/overview.html:86 -msgid "Status" -msgstr "Status" - -#: application/views.py:781 application/templates/application/view.html:25 -#: application/templates/application/view.html:98 config/models.py:131 -#: config/templates/config/version.html:5 -#: config/templates/config/version.html:8 -#: config/templates/config/version.html:13 -msgid "Version" -msgstr "Version" - -#: application/views.py:791 application/templates/application/view.html:190 -#: assignment/views.py:395 -msgid "Vote results" -msgstr "Abstimmungsergebnis" - -#: application/views.py:797 application/templates/application/poll_view.html:7 -#: application/templates/application/poll_view.html:14 -#: application/templates/application/view.html:54 -#: application/templates/application/view.html:208 -#: application/templates/projector/Application.html:33 -msgid "Vote" -msgstr "Abstimmung" - -#: application/views.py:798 application/views.py:851 -#: application/templates/application/view.html:218 -#: application/templates/projector/Application.html:39 assignment/views.py:562 -#: assignment/templates/assignment/view.html:147 -#: assignment/templates/projector/Assignment.html:80 -msgid "Abstention" -msgstr "Enthaltung" - -#: application/views.py:798 application/templates/application/view.html:219 -#: application/templates/projector/Application.html:40 -#: assignment/templates/assignment/view.html:168 -#: assignment/templates/projector/Assignment.html:101 -msgid "Invalid" -msgstr "Ungültig" - -#: application/views.py:798 -#: application/templates/application/poll_view.html:61 -#: application/templates/application/view.html:221 -#: application/templates/projector/Application.html:42 assignment/views.py:446 -#: assignment/templates/assignment/poll_view.html:45 -#: assignment/templates/assignment/view.html:180 -#: assignment/templates/assignment/view.html:185 -#: assignment/templates/projector/Assignment.html:111 -#: assignment/templates/projector/Assignment.html:117 poll/models.py:76 -msgid "Votes cast" -msgstr "Abgegebene Stimmen" - -#: application/views.py:831 -msgid "Poll" -msgstr "Abstimmung" - -#: application/views.py:845 -#, python-format -msgid "Application No. %s" -msgstr "Antrag Nr. %s" - -#: application/views.py:847 -#, python-format -msgid "%d. Vote" -msgstr "%d. Abstimmung" - -#: application/views.py:904 -msgid "Motion settings successfully saved." -msgstr "Antrags-Einstellungen wurden erfolgreich gespeichert." - -#: application/templates/application/config.html:5 -msgid "Application settings" -msgstr "Antrags-Einstellungen" - -#: application/templates/application/edit.html:8 -#: application/templates/application/edit.html:17 -#: application/templates/application/overview.html:89 -#: application/templates/application/view.html:39 -msgid "Edit motion" -msgstr "Antrag bearbeiten" - -#: application/templates/application/edit.html:10 -#: application/templates/application/edit.html:19 -#: application/templates/application/overview.html:14 -msgid "New motion" -msgstr "Neuer Antrag" - -#: application/templates/application/import.html:5 -#: application/templates/application/import.html:8 -#: application/templates/application/overview.html:15 -msgid "Import motions" -msgstr "Anträge importieren" - -#: application/templates/application/import.html:14 -msgid "Select a CSV file to import motions!" -msgstr "Wählen Sie eine CSV-Datei zum Importieren von Anträgen aus!" - -#: application/templates/application/import.html:15 -msgid "" -"Required comma separated values: {number, title, text, reason, " -"first_name, last_name} (number and reason " -"are optional and may be empty)" -msgstr "" -"Erforderliche kommaseparierte Werte: {Nummer, Titel, Text, Begründung, " -"Vorname, Nachname} (Nummer und Begründung " -"sind optional und können auch leer sein)" - -#: application/templates/application/import.html:17 -#: participant/templates/participant/import.html:19 -msgid "Required CSV file encoding: UTF-8 (Unicode)." -msgstr "Erforderliches CSV-Datei-Encoding: UTF-8 (Unicode)." - -#: application/templates/application/import.html:20 -#: participant/templates/participant/import.html:22 -msgid "A CSV example file is available in OpenSlides Wiki." -msgstr "Eine CSV-Beispiel-Datei gibt es im OpenSlides Wiki." - -#: application/templates/application/import.html:38 -#: application/templates/application/overview.html:15 -#: participant/templates/participant/import.html:29 -#: participant/templates/participant/overview.html:24 -msgid "Import" -msgstr "Importieren" - -#: application/templates/application/overview.html:17 -msgid "Print all motions as PDF" -msgstr "Alle Anträge als PDF drucken" - -#: application/templates/application/overview.html:25 -msgid "Need supporters" -msgstr "Benötigt Unterstützer/innen" - -#: application/templates/application/overview.html:30 -msgid "Without number" -msgstr "Ohne Nummer" - -#: application/templates/application/overview.html:37 -msgid "Not yet authorized" -msgstr "Noch nicht zugelassen" - -#: application/templates/application/overview.html:38 -msgid "Authorized" -msgstr "Zugelassen" - -#: application/templates/application/overview.html:41 -msgid "Withdrawen (by submitter)" -msgstr "Zurückgezogen (durch Antragsteller/in)" - -#: application/templates/application/overview.html:52 -msgid "#" -msgstr "#" - -#: application/templates/application/overview.html:53 -msgid "Motion title" -msgstr "Antragstitel" - -#: application/templates/application/overview.html:55 -msgid "Number of supporters" -msgstr "Anzahl der Unterstützer/innen" - -#: application/templates/application/overview.html:59 -#: application/templates/application/view.html:244 -msgid "Creation Time" -msgstr "Erstellungszeit" - -#: application/templates/application/overview.html:84 -msgid "Activate motion" -msgstr "Antrag projizieren" - -#: application/templates/application/overview.html:91 -#: application/templates/application/view.html:43 -msgid "Delete motion" -msgstr "Antrag löschen" - -#: application/templates/application/overview.html:94 -msgid "Motion as PDF" -msgstr "Antrag als PDF" - -#: application/templates/application/poll_view.html:7 -#: application/templates/application/poll_view.html:14 -#: application/templates/application/view.html:8 -#: application/templates/application/view.html:21 -#: application/templates/projector/Application.html:7 -#: application/templates/projector/Application.html:65 -msgid "Motion" -msgstr "Antrag" - -#: application/templates/application/poll_view.html:18 -msgid "Back to motion" -msgstr "Zurück zum Antrag" - -#: application/templates/application/poll_view.html:21 -#: application/templates/application/view.html:33 -#: application/templates/application/view.html:318 -msgid "More actions" -msgstr "Mehr Aktionen" - -#: application/templates/application/poll_view.html:27 -#: application/templates/application/view.html:211 -msgid "Delete Vote" -msgstr "Abstimmung löschen" - -#: application/templates/application/poll_view.html:32 -#: application/templates/application/view.html:48 -msgid "Show Application" -msgstr "Antrag projizieren" - -#: application/templates/application/poll_view.html:41 -#: assignment/templates/assignment/poll_view.html:12 -msgid "Special values" -msgstr "Spezielle Werte" - -#: application/templates/application/poll_view.html:41 -#: assignment/templates/assignment/poll_view.html:12 poll/models.py:235 -msgid "majority" -msgstr "Mehrheit" - -#: application/templates/application/poll_view.html:41 -#: assignment/templates/assignment/poll_view.html:12 poll/models.py:237 -msgid "undocumented" -msgstr "nicht erfasst" - -#: application/templates/application/poll_view.html:47 -msgid "Option" -msgstr "Wahlmöglichkeit" - -#: application/templates/application/poll_view.html:48 -#: assignment/models.py:263 -msgid "Votes" -msgstr "Stimmen" - -#: application/templates/application/poll_view.html:57 assignment/views.py:439 -#: assignment/templates/assignment/poll_view.html:35 -#: assignment/templates/assignment/view.html:163 -#: assignment/templates/projector/Assignment.html:97 -msgid "Invalid votes" -msgstr "Ungültige Stimmen" - -#: application/templates/application/poll_view.html:69 -#: assignment/templates/assignment/poll_view.html:59 -msgid "Ballot paper as PDF" -msgstr "Stimmzettel als PDF" - -#: application/templates/application/view.html:30 -msgid "Print this motion as PDF" -msgstr "Alle Anträge als PDF drucken" - -#: application/templates/application/view.html:60 -msgid "New agenda item" -msgstr "Neuer Tagesordnungseintrag" - -#: application/templates/application/view.html:74 -msgid "This is not the newest version." -msgstr "Dies ist nicht die neuste Version." - -#: application/templates/application/view.html:74 -#: application/templates/application/view.html:76 -msgid "Go to version" -msgstr "Gehe zu Version" - -#: application/templates/application/view.html:76 -msgid "This is not the authorized version." -msgstr "Dies ist nicht die zugelassene Version." - -#: application/templates/application/view.html:81 -msgid "Motion text" -msgstr "Antragstext" - -#: application/templates/application/view.html:94 -msgid "Version History" -msgstr "Versionshistorie" - -#: application/templates/application/view.html:99 -msgid "Time" -msgstr "Zeit" - -#: application/templates/application/view.html:109 -msgid "Version authorized" -msgstr "Version %d zugelassen" - -#: application/templates/application/view.html:112 -msgid "Permit Version" -msgstr "Version zulassen" - -#: application/templates/application/view.html:115 -msgid "Reject Version" -msgstr "Version verwerfen" - -#: application/templates/application/view.html:119 -msgid "Version rejected" -msgstr "Version verworfen" - -#: application/templates/application/view.html:129 -#: application/templates/application/view.html:136 -#: application/templates/application/view.html:143 -msgid "unchanged" -msgstr "unverändert" - -#: application/templates/application/view.html:152 -msgid "Log" -msgstr "Log" - -#: application/templates/application/view.html:164 -msgid "Me" -msgstr "Ich" - -#: application/templates/application/view.html:196 -#: application/templates/application/view.html:228 -msgid "New vote" -msgstr "Neue Abstimmung" - -#: application/templates/application/view.html:210 -msgid "Edit Vote" -msgstr "Abstimmung bearbeiten" - -#: application/templates/application/view.html:235 -msgid "No results" -msgstr "Keine Ergebnisse" - -#: application/templates/application/view.html:250 -msgid "Withdraw motion" -msgstr "Antrag zurückziehen" - -#: application/templates/application/view.html:258 -msgid "Unsupport motion" -msgstr "Antrag nicht unterstützen" - -#: application/templates/application/view.html:264 -msgid "Support" -msgstr "Unterstützen" - -#: application/templates/application/view.html:271 -msgid "minimum required supporters" -msgstr "minimal erforderliche Unterstützer/innen" - -#: application/templates/application/view.html:278 -msgid "Manage motion" -msgstr "Antrag verwalten" - -#: application/templates/application/view.html:281 -msgid "Formal validation" -msgstr "Formale Gültigkeitsprüfung" - -#: application/templates/application/view.html:284 -msgid "Publish" -msgstr "Veröffentlichen" - -#: application/templates/application/view.html:287 -msgid "Permit" -msgstr "Zulassen" - -#: application/templates/application/view.html:290 -msgid "Not permit" -msgstr "Nicht zulassen" - -#: application/templates/application/view.html:293 -msgid "Set number" -msgstr "Nummer vergeben" - -#: application/templates/application/view.html:299 -msgid "Result after vote" -msgstr "Ergebnis nach der Abstimmung" - -#: application/templates/application/view.html:332 -msgid "Withdrawed by Submitter" -msgstr "Zurückgezogen durch Antragsteller/in" - -#: application/templates/application/view.html:339 -msgid "For Administration only:" -msgstr "Nur zur Administration:" - -#: application/templates/application/view.html:341 -msgid "Reset" -msgstr "Zurücksetzen" - -#: application/templates/application/widget.html:31 -msgid "No motion available." -msgstr "Keine Antrag vorhanden." - -#: application/templates/projector/Application.html:29 -msgid "Poll result" -msgstr "Abstimmungsergebnis" - -#: application/templates/projector/Application.html:47 -msgid "No poll results available." -msgstr "Keine Abstimmungen vorhanden." - -#: assignment/forms.py:24 assignment/models.py:53 assignment/views.py:368 -#: assignment/templates/assignment/view.html:214 +#: assignment/forms.py:24 assignment/models.py:51 assignment/views.py:381 +#: assignment/templates/assignment/view.html:240 #: assignment/templates/projector/Assignment.html:21 msgid "Number of available posts" msgstr "Anzahl der zur Wahl stehenden Posten" @@ -1200,42 +328,62 @@ msgid "Only publish voting results for selected winners (Projector view only)" msgstr "" "Wahlergebnisse der nicht gewählten Kandidaten auf dem Projektor verbergen" -#: assignment/forms.py:63 +#: assignment/forms.py:46 motion/forms.py:77 +msgid "Number of ballot papers (selection)" +msgstr "Anzahl der Stimmzettel (Vorauswahl)" + +#: assignment/forms.py:48 motion/forms.py:79 +msgid "Number of all delegates" +msgstr "Anzahl aller Delegierten" + +#: assignment/forms.py:49 motion/forms.py:80 +msgid "Number of all participants" +msgstr "Anzahl aller Teilnehmer/innen" + +#: assignment/forms.py:50 motion/forms.py:81 +msgid "Use the following custom number" +msgstr "Verwende die folgende benutzerdefinierte Anzahl" + +#: assignment/forms.py:55 motion/forms.py:88 +msgid "Custom number of ballot papers" +msgstr "Benutzerdefinierte Anzahl von Stimmzetteln" + +#: assignment/forms.py:59 msgid "Title for PDF document (all elections)" msgstr "Titel für PDF-Dokument (alle Wahlen)" -#: assignment/forms.py:68 +#: assignment/forms.py:63 msgid "Preamble text for PDF document (all elections)" msgstr "Einleitungstext für PDF-Dokument (alle Wahlen) " -#: assignment/forms.py:72 +#: assignment/forms.py:67 msgid "Election method" msgstr "Wahlmethode" -#: assignment/forms.py:74 +#: assignment/forms.py:69 msgid "Automatic assign of method." msgstr "Automatische Zuordnung der Methode." -#: assignment/forms.py:75 +#: assignment/forms.py:70 msgid "Always one option per candidate." msgstr "Eine Stimme pro Kandidat/in." -#: assignment/forms.py:76 +#: assignment/forms.py:71 msgid "Always Yes-No-Abstain per candidate." msgstr "Ja, Nein, Enthaltung pro Kandidat/in." #: assignment/models.py:44 assignment/templates/assignment/overview.html:25 -#: assignment/templates/assignment/view.html:223 +#: assignment/templates/assignment/view.html:249 msgid "Searching for candidates" msgstr "Auf Kandidatensuche" #: assignment/models.py:45 assignment/templates/assignment/overview.html:26 -#: assignment/templates/assignment/view.html:227 +#: assignment/templates/assignment/view.html:253 msgid "Voting" msgstr "Im Wahlvorgang" #: assignment/models.py:46 assignment/templates/assignment/overview.html:27 -#: assignment/templates/assignment/view.html:231 +#: assignment/templates/assignment/view.html:257 msgid "Finished" msgstr "Abgeschlossen" @@ -1243,168 +391,213 @@ msgstr "Abgeschlossen" msgid "Name" msgstr "Name" -#: assignment/models.py:51 assignment/templates/assignment/view.html:30 +#: assignment/models.py:50 assignment/templates/assignment/view.html:30 +#: participant/models.py:144 msgid "Description" msgstr "Beschreibung" -#: assignment/models.py:55 +#: assignment/models.py:54 msgid "Comment on the ballot paper" msgstr "Kommentar für den Stimmzettel" +#: assignment/models.py:64 motion/models.py:332 +#, python-format +msgid "%s is not a valid status." +msgstr "%s ist kein gültiger Status." + #: assignment/models.py:67 #, python-format msgid "The assignment status is already %s." msgstr "Der Wahlstatus ist bereits %s." -#: assignment/models.py:79 +#: assignment/models.py:80 #, python-format msgid "%s is already a candidate." msgstr "%s ist bereits ein/e Kandidat/in." -#: assignment/models.py:81 assignment/views.py:189 +#: assignment/models.py:82 assignment/views.py:195 msgid "The candidate list is already closed." msgstr "Die Kandidatenliste ist bereits geschlossen." -#: assignment/models.py:92 +#: assignment/models.py:89 +#, python-format +msgid "%s does not want to be a candidate." +msgstr "%s möchte nicht kandidieren." + +#: assignment/models.py:103 #, python-format msgid "%s is no candidate" msgstr "%s ist kein/e Kandidat/in" -#: assignment/models.py:214 +#: assignment/models.py:244 msgid "Can see assignment" msgstr "Darf Wahlen sehen" -#: assignment/models.py:216 +#: assignment/models.py:246 msgid "Can nominate another person" msgstr "Darf andere Personen für Wahlen vorschlagen" -#: assignment/models.py:217 +#: assignment/models.py:247 msgid "Can nominate themselves" msgstr "Darf selbst für Wahlen kandidieren" -#: assignment/models.py:218 +#: assignment/models.py:248 msgid "Can manage assignment" msgstr "Darf Wahlen verwalten" -#: assignment/models.py:280 +#: assignment/models.py:291 motion/models.py:574 +msgid "Abstain" +msgstr "Enthaltung" + +#: assignment/models.py:293 motion/templates/motion/poll_view.html:49 +msgid "Votes" +msgstr "Stimmen" + +#: assignment/models.py:310 #, python-format msgid "Ballot %d" msgstr "Wahlgang %d" -#: assignment/models.py:289 assignment/views.py:325 assignment/views.py:648 -#: assignment/templates/assignment/config.html:8 +#: assignment/models.py:319 assignment/views.py:336 assignment/views.py:675 +#: assignment/views.py:691 assignment/templates/assignment/config.html:8 #: assignment/templates/assignment/overview.html:6 #: assignment/templates/assignment/overview.html:10 msgid "Elections" msgstr "Wahlen" -#: assignment/views.py:84 +#: assignment/views.py:80 #, python-format msgid "Candidate %s was nominated successfully." msgstr "Kandidat/in %s wurde erfolgreich vorgeschlagen." -#: assignment/views.py:125 +#: assignment/views.py:122 msgid "New election was successfully created." msgstr "Neue Wahl wurde erfolgreich angelegt." -#: assignment/views.py:127 +#: assignment/views.py:124 msgid "Election was successfully modified." msgstr "Wahl wurde erfolgreich geändert." -#: assignment/views.py:152 +#: assignment/views.py:130 motion/views.py:245 motion/views.py:689 +#: participant/views.py:508 participant/views.py:531 utils/views.py:222 +#: utils/views.py:240 utils/views.py:264 +msgid "Please check the form for errors." +msgstr "Bitte kontrollieren Sie das Formular nach Fehlern." + +#: assignment/views.py:149 #, python-format msgid "Election %s was successfully deleted." msgstr "Wahl %s wurde erfolgreich gelöscht." -#: assignment/views.py:165 +#: assignment/views.py:162 #, python-format msgid "Election status was set to: %s." msgstr "Wahlstatus wurde gesetzt auf: %s." -#: assignment/views.py:176 +#: assignment/views.py:175 msgid "You have set your candidature successfully." msgstr "Sie haben Ihre Kandidatur erfolgreich gesetzt." -#: assignment/views.py:193 -msgid "You have withdrawn your candidature successfully." -msgstr "Sie haben Ihre Kandidatur erfolgreich zurückgezogen." +#: assignment/views.py:192 +msgid "" +"You have withdrawn your candidature successfully. You can not be nominated " +"by other participants anymore." +msgstr "" +"Sie haben Ihre Kandidatur erfolgreich zurückgezogen. Sie können nun von " +"anderen Teilnehmer/innen nicht mehr vorgeschlagen werden." -#: assignment/views.py:208 +#: assignment/views.py:213 #, python-format msgid "Candidate %s was withdrawn successfully." msgstr "Die Kandidatur von %s wurde erfolgreich zurückgezogen." -#: assignment/views.py:211 +#: assignment/views.py:215 +#, python-format +msgid "%s was unblocked successfully." +msgstr "%s wurde erfolgreich freigegeben." + +#: assignment/views.py:219 #, python-format msgid "Do you really want to withdraw %s from the election?" msgstr "Soll %s wirklich von der Wahl zurückgezogen werden?" -#: assignment/views.py:226 +#: assignment/views.py:221 +#, python-format +msgid "Do you really want to unblock %s for the election?" +msgstr "Soll %s wirklich für die Wahl freigegeben werden?" + +#: assignment/views.py:236 msgid "New ballot was successfully created." msgstr "Neuer Wahlgang erfolgreich angelegt." -#: assignment/views.py:258 +#: assignment/views.py:268 #, python-format msgid "Ballot ID %d does not exist." msgstr "Wahlgang-ID %d existiert nicht." -#: assignment/views.py:265 +#: assignment/views.py:275 msgid "Ballot successfully published." msgstr "Wahlgang wurde erfolgreich veröffentlicht." -#: assignment/views.py:267 +#: assignment/views.py:277 msgid "Ballot successfully unpublished." msgstr "Wahlgang wurde erfolgreich unveröffentlicht." -#: assignment/views.py:280 +#: assignment/views.py:290 msgid "not elected" msgstr "nicht gewählt" -#: assignment/views.py:283 assignment/views.py:466 +#: assignment/views.py:293 assignment/views.py:482 +#: assignment/templates/assignment/view.html:51 msgid "elected" msgstr "gewählt" -#: assignment/views.py:311 +#: assignment/views.py:321 msgid "Ballot was successfully deleted." msgstr "Abstimmung wurde erfolgreich gelöscht." -#: assignment/views.py:322 +#: assignment/views.py:333 msgid "Assignment" msgstr "Wahl" -#: assignment/views.py:343 assignment/templates/assignment/overview.html:64 +#: assignment/views.py:356 assignment/templates/assignment/overview.html:70 #: assignment/templates/assignment/widget.html:23 msgid "No assignments available." msgstr "Keine Wahlen vorhanden." -#: assignment/views.py:362 +#: assignment/views.py:375 #, python-format msgid "Election: %s" msgstr "Wahlen: %s" -#: assignment/views.py:374 assignment/views.py:407 +#: assignment/views.py:388 assignment/views.py:424 #: assignment/templates/assignment/overview.html:37 #: assignment/templates/assignment/poll_view.html:18 -#: assignment/templates/assignment/view.html:38 -#: assignment/templates/assignment/view.html:100 +#: assignment/templates/assignment/view.html:40 +#: assignment/templates/assignment/view.html:127 #: assignment/templates/projector/Assignment.html:38 #: assignment/templates/projector/Assignment.html:56 msgid "Candidates" msgstr "Kandidaten/innen" -#: assignment/views.py:399 assignment/templates/assignment/poll_view.html:5 +#: assignment/views.py:413 motion/views.py:823 +#: motion/templates/motion/view.html:188 +msgid "Vote results" +msgstr "Abstimmungsergebnis" + +#: assignment/views.py:417 assignment/templates/assignment/poll_view.html:5 #: assignment/templates/assignment/poll_view.html:8 -#: assignment/templates/assignment/view.html:95 -#: assignment/templates/assignment/view.html:103 +#: assignment/templates/assignment/view.html:122 +#: assignment/templates/assignment/view.html:130 #: assignment/templates/projector/Assignment.html:59 msgid "ballot" msgstr "Wahlgang" -#: assignment/views.py:402 +#: assignment/views.py:420 msgid "ballots" msgstr "Wahlgänge" -#: assignment/views.py:428 +#: assignment/views.py:445 #, python-format msgid "" "Y: %(YES)s\n" @@ -1415,7 +608,25 @@ msgstr "" "N: %(NO)s\n" "E: %(ABSTAIN)s" -#: assignment/views.py:505 assignment/views.py:521 +#: assignment/views.py:456 assignment/templates/assignment/poll_view.html:35 +#: assignment/templates/assignment/view.html:190 +#: assignment/templates/projector/Assignment.html:96 +#: motion/templates/motion/poll_view.html:58 +msgid "Invalid votes" +msgstr "Ungültige Stimmen" + +#: assignment/views.py:463 assignment/templates/assignment/poll_view.html:45 +#: assignment/templates/assignment/view.html:206 +#: assignment/templates/assignment/view.html:211 +#: assignment/templates/projector/Assignment.html:109 +#: assignment/templates/projector/Assignment.html:115 motion/views.py:830 +#: motion/templates/motion/poll_view.html:62 +#: motion/templates/motion/view.html:219 +#: motion/templates/projector/Motion.html:42 poll/models.py:76 +msgid "Votes cast" +msgstr "Abgegebene Stimmen" + +#: assignment/views.py:523 assignment/views.py:541 #: assignment/templates/assignment/overview.html:36 #: assignment/templates/assignment/poll_view.html:5 #: assignment/templates/assignment/view.html:6 @@ -1423,24 +634,33 @@ msgstr "" msgid "Election" msgstr "Wahl" -#: assignment/views.py:527 +#: assignment/views.py:548 #, python-format msgid "%d. ballot" msgstr "%d. Wahlgang" -#: assignment/views.py:528 +#: assignment/views.py:550 #, python-format msgid "%d candidate" msgid_plural "%d candidates" msgstr[0] "%d Kandidat/in" msgstr[1] "%d Kandidaten/innen" -#: assignment/views.py:530 +#: assignment/views.py:552 #, python-format -msgid "%d available posts" -msgstr "%d verfügbare Posten" +msgid "%d available post" +msgid_plural "%d available posts" +msgstr[0] "%d verfügbare Posten" +msgstr[1] "%d verfügbare Posten" -#: assignment/views.py:641 +#: assignment/views.py:588 assignment/templates/assignment/view.html:174 +#: assignment/templates/projector/Assignment.html:80 motion/views.py:830 +#: motion/views.py:883 motion/templates/motion/view.html:216 +#: motion/templates/projector/Motion.html:39 +msgid "Abstention" +msgstr "Enthaltung" + +#: assignment/views.py:668 msgid "Election settings successfully saved." msgstr "Wahl-Einstellungen wurden erfolgreich gespeichert." @@ -1450,7 +670,7 @@ msgstr "Wahl-Einstellungen" #: assignment/templates/assignment/edit.html:8 #: assignment/templates/assignment/edit.html:17 -#: assignment/templates/assignment/overview.html:55 +#: assignment/templates/assignment/overview.html:61 msgid "Edit election" msgstr "Wahl bearbeiten" @@ -1461,29 +681,58 @@ msgid "New election" msgstr "Neue Wahl" #: assignment/templates/assignment/overview.html:16 +#, fuzzy msgid "Print all elections as PDF" -msgstr "Alle Anträge als PDF drucken" +msgstr "Alle Wahlen als PDF" #: assignment/templates/assignment/overview.html:22 #: participant/templates/participant/overview.html:55 msgid "Filter" msgstr "Filter" +#: assignment/templates/assignment/overview.html:24 +#: assignment/templates/assignment/overview.html:38 +#: assignment/templates/assignment/view.html:237 +#: assignment/templates/projector/Assignment.html:18 motion/views.py:801 +#: motion/templates/motion/overview.html:36 +#: motion/templates/motion/overview.html:57 +#: motion/templates/motion/view.html:178 +#: motion/templates/projector/Motion.html:11 +#: participant/templates/participant/overview.html:86 +msgid "Status" +msgstr "Status" + #: assignment/templates/assignment/overview.html:32 msgid "election" msgid_plural "elections" msgstr[0] "Wahl" msgstr[1] "Wahlen" +#: assignment/templates/assignment/overview.html:46 +#, python-format +msgctxt "Number of searched candidates for an election" +msgid "posts: %(posts)s" +msgstr "Posten: %(posts)s" + +#: assignment/templates/assignment/overview.html:48 +#, python-format +msgid "candidates: %(candidates)s" +msgstr "Kandidaten/innen: %(candidates)s" + #: assignment/templates/assignment/overview.html:50 +#, python-format +msgid "elected: %(elected)s" +msgstr "Gewählt: %(elected)s" + +#: assignment/templates/assignment/overview.html:56 msgid "Activate election" msgstr "Wahl projizieren" -#: assignment/templates/assignment/overview.html:56 +#: assignment/templates/assignment/overview.html:62 msgid "Delete election" msgstr "Wahl löschen" -#: assignment/templates/assignment/overview.html:58 +#: assignment/templates/assignment/overview.html:64 msgid "Election as PDF" msgstr "Wahl als PDF" @@ -1491,89 +740,130 @@ msgstr "Wahl als PDF" msgid "Short description (for ballot paper)" msgstr "Kurzbeschreibung (für Stimmzettel)" +#: assignment/templates/assignment/poll_view.html:12 +#: motion/templates/motion/poll_view.html:41 +msgid "Special values" +msgstr "Spezielle Werte" + +#: assignment/templates/assignment/poll_view.html:12 +#: motion/templates/motion/poll_view.html:41 poll/models.py:234 +msgid "majority" +msgstr "Mehrheit" + +#: assignment/templates/assignment/poll_view.html:12 +#: motion/templates/motion/poll_view.html:41 poll/models.py:236 +#: poll/models.py:238 +msgid "undocumented" +msgstr "nicht erfasst" + +#: assignment/templates/assignment/poll_view.html:59 +#: motion/templates/motion/poll_view.html:70 +msgid "Ballot paper as PDF" +msgstr "Stimmzettel als PDF" + #: assignment/templates/assignment/view.html:21 msgid "Show election" msgstr "Wahl projizieren" -#: assignment/templates/assignment/view.html:45 +#: assignment/templates/assignment/view.html:47 +#: assignment/templates/assignment/view.html:104 msgid "Remove candidate" msgstr "Kandidate/in entfernen" -#: assignment/templates/assignment/view.html:50 +#: assignment/templates/assignment/view.html:54 +msgid "Mark candidate as not elected" +msgstr "Kandidat/in als nicht gewählt markieren" + +#: assignment/templates/assignment/view.html:60 #: assignment/templates/projector/Assignment.html:44 msgid "No candidates available." msgstr "Keine Kandidaten/innen vorhanden." -#: assignment/templates/assignment/view.html:60 +#: assignment/templates/assignment/view.html:70 msgid "Withdraw self candidature" msgstr "Eigene Kandidatur zurückziehen" -#: assignment/templates/assignment/view.html:64 +#: assignment/templates/assignment/view.html:74 msgid "Self candidature" msgstr "Selbst kandidieren" -#: assignment/templates/assignment/view.html:76 +#: assignment/templates/assignment/view.html:86 msgid "Add new participant" msgstr "Neue/n Teilnehmer/in hinzufügen" -#: assignment/templates/assignment/view.html:88 +#: assignment/templates/assignment/view.html:100 +msgid "Blocked Candidates" +msgstr "Blockierte Kandidaten/innen" + +#: assignment/templates/assignment/view.html:107 +msgid "No blocked candidates available." +msgstr "Keine blockierten Kandidaten/innen vorhanden." + +#: assignment/templates/assignment/view.html:115 #: assignment/templates/projector/Assignment.html:52 msgid "Election results" msgstr "Wahlergebnisse" -#: assignment/templates/assignment/view.html:108 +#: assignment/templates/assignment/view.html:135 msgid "Publish/unpublish results" msgstr "Ergebnisse veröffentlichen/unveröffentlichen" -#: assignment/templates/assignment/view.html:119 -#: assignment/templates/assignment/view.html:201 +#: assignment/templates/assignment/view.html:146 +#: assignment/templates/assignment/view.html:227 msgid "New ballot" msgstr "Neuer Wahlgang" -#: assignment/templates/assignment/view.html:132 +#: assignment/templates/assignment/view.html:159 #: assignment/templates/projector/Assignment.html:69 msgid "Candidate is elected" msgstr "Kandidat/in ist gewählt" -#: assignment/templates/assignment/view.html:151 +#: assignment/templates/assignment/view.html:178 #: assignment/templates/projector/Assignment.html:84 msgid "was not a
    candidate" msgstr "war kein Kandidat" -#: assignment/templates/assignment/view.html:197 -#: assignment/templates/projector/Assignment.html:126 -msgid "No ballots available." -msgstr "Keine Wahlgänge vorhanden." +#: assignment/templates/assignment/view.html:195 +#: assignment/templates/projector/Assignment.html:100 motion/views.py:830 +#: motion/templates/motion/view.html:217 +#: motion/templates/projector/Motion.html:40 +msgid "Invalid" +msgstr "Ungültig" -#: assignment/templates/assignment/view.html:220 +#: assignment/templates/assignment/view.html:223 +#, fuzzy +msgid "No ballots available." +msgstr "Keine Ergebnisse vorhanden." + +#: assignment/templates/assignment/view.html:246 msgid "Change status" msgstr "Status ändern" -#: config/forms.py:24 +#: config/forms.py:22 msgid "Event name" msgstr "Veranstaltungsname" -#: config/forms.py:30 +#: config/forms.py:28 msgid "Short description of event" msgstr "Kurzbeschreibung der Veranstaltung" -#: config/forms.py:38 +#: config/forms.py:36 msgid "Event date" msgstr "Veranstaltungszeitraum" -#: config/forms.py:44 +#: config/forms.py:42 msgid "Event location" msgstr "Veranstaltungsort" -#: config/forms.py:50 +#: config/forms.py:48 msgid "Event organizer" msgstr "Veranstalter" -#: config/forms.py:55 +#: config/forms.py:53 msgid "Allow access for anonymous guest users" msgstr "Erlaube Zugriff für anonyme Gast-Nutzer" -#: config/forms.py:67 participant/forms.py:114 +#: config/forms.py:65 participant/forms.py:123 msgid "Welcome text" msgstr "Willkommenstext" @@ -1581,36 +871,30 @@ msgstr "Willkommenstext" msgid "Can manage configuration" msgstr "Darf die Konfiguration verwalten" -#: config/models.py:84 -msgid "Presentation system for agenda, motions and elections" -msgstr "Präsentationssystem für Tagesordnung, Anträge und Wahlen" +#: config/models.py:83 +msgid "Presentation and assembly system" +msgstr "Präsentations- und Versammlungssystem" + +#: config/models.py:88 +msgid "Welcome to OpenSlides" +msgstr "Willkommen bei OpenSlides" #: config/models.py:89 -msgid "Welcome" -msgstr "Willkommen" +msgid "[Place for your welcome text.]" +msgstr "[Platz für Ihren Begrüßungstext.]" -#: config/models.py:90 participant/models.py:171 -msgid "Welcome to OpenSlides!" -msgstr "Willkommen bei OpenSlides!" - -#: config/models.py:92 -#, python-format -msgid "Get professional support for OpenSlides on %s." -msgstr "Professionelle Unterstützung für OpenSlides gibt es unter %s." - -#: config/models.py:107 config/templates/config/general.html:8 +#: config/models.py:102 config/templates/config/general.html:8 msgid "General" msgstr "Allgemein" -#: config/views.py:78 -msgid "" -"Anonymous access enabled. Please modify the \"Anonymous\" group to fit your " -"required permissions." -msgstr "" -"Anonymer Zugriff aktiviert. Bitte setzen Sie die Rechte der Benutzerrolle " -"\"Anonymous\" passend zum gewünschten Zugriffslevel." +#: config/models.py:126 config/templates/config/version.html:5 +#: config/templates/config/version.html:8 +#: config/templates/config/version.html:13 motion/views.py:815 +#: motion/templates/motion/view.html:25 motion/templates/motion/view.html:98 +msgid "Version" +msgstr "Version" -#: config/views.py:84 +#: config/views.py:69 msgid "General settings successfully saved." msgstr "Allgemeine Einstellungen erfolgreich gespeichert." @@ -1623,190 +907,1085 @@ msgid "Event" msgstr "Veranstaltung" #: config/templates/config/general.html:28 -msgid "Frontpage" -msgstr "Startseite" +msgid "Welcome Widget" +msgstr "Willkommens-Widget" #: config/templates/config/general.html:43 msgid "System" msgstr "System" +#: motion/forms.py:25 motion/models.py:541 motion/views.py:849 +#: motion/templates/motion/view.html:85 motion/templates/motion/view.html:102 +#: motion/templates/projector/Motion.html:77 +msgid "Reason" +msgstr "Begründung" + +#: motion/forms.py:30 +msgid "Trivial change" +msgstr "Triviale Änderung" + +#: motion/forms.py:31 +msgid "Trivial changes don't create a new version." +msgstr "Triviale Änderungen erzeugen keine neue Version." + +#: motion/forms.py:35 motion/models.py:63 motion/views.py:768 +#: motion/templates/motion/overview.html:58 +#: motion/templates/motion/view.html:162 +#: motion/templates/projector/Motion.html:55 +msgid "Submitter" +msgstr "Antragsteller/in" + +#: motion/forms.py:44 motion/views.py:788 +#: motion/templates/motion/view.html:166 +msgid "Supporters" +msgstr "Unterstützer/innen" + +#: motion/forms.py:50 participant/forms.py:111 +msgid "CSV File" +msgstr "CSV-Datei" + +#: motion/forms.py:54 +msgid "Import motions with status \"authorized\"" +msgstr "Anträge als \"Zugelassen\" importieren" + +#: motion/forms.py:55 +msgid "Set the initial status for each motion to \"authorized\"" +msgstr "Setzt den initialen Status für jeden Antrag auf \"zugelassen\"" + +#: motion/forms.py:63 +msgid "Number of (minimum) required supporters for a motion" +msgstr "Mindestanzahl erforderlicher Unterstützer/innen für einen Antrag" + +#: motion/forms.py:67 +msgid "Choose 0 to disable the supporting system" +msgstr "Wähle 0 um das Unterstützersystem zu deaktivieren" + +#: motion/forms.py:72 +msgid "Motion preamble" +msgstr "Antragseinleitung" + +#: motion/forms.py:93 +msgid "Title for PDF document (all motions)" +msgstr "Titel für PDF-Dokument (alle Anträge)" + +#: motion/forms.py:98 +msgid "Preamble text for PDF document (all motions)" +msgstr "Einleitungstext für PDF-Dokument (alle Wahlen) " + +#: motion/forms.py:102 +msgid "Allow trivial changes" +msgstr "Triviale Änderungen erlauben" + +#: motion/forms.py:103 +msgid "Warning: Trivial changes undermine the motions autorisation system." +msgstr "" +"Warnung: Triviale Änderungen unterlaufen das Zulassungssystem von Anträgen." + +#: motion/models.py:42 +msgid "Published" +msgstr "Veröffentlicht" + +#: motion/models.py:43 +msgid "Permitted" +msgstr "Zugelassen" + +#: motion/models.py:44 motion/templates/motion/overview.html:41 +#: motion/templates/motion/view.html:301 +msgid "Accepted" +msgstr "Angenommen" + +#: motion/models.py:45 motion/templates/motion/overview.html:42 +#: motion/templates/motion/view.html:306 +msgid "Rejected" +msgstr "Abgelehnt" + +#: motion/models.py:46 +msgid "Withdrawed" +msgstr "Zurückgezogen" + +#: motion/models.py:47 motion/templates/motion/view.html:321 +msgid "Adjourned" +msgstr "Vertagt" + +# please check! +#: motion/models.py:48 motion/templates/motion/view.html:324 +msgid "Not Concerned" +msgstr "Nicht befasst" + +# please check! +#: motion/models.py:49 motion/templates/motion/view.html:327 +msgid "Commited a bill" +msgstr "Verwiesen (in Ausschuss)" + +#: motion/models.py:50 +msgid "Rejected (not authorized)" +msgstr "Verworfen (nicht zulässig)" + +#: motion/models.py:51 motion/templates/motion/overview.html:44 +msgid "Needs Review" +msgstr "Benötigt Review" + +#: motion/models.py:100 +#, python-format +msgid "Version %d authorized" +msgstr "Version %d zugelassen" + +#: motion/models.py:107 +#, python-format +msgctxt "Rejected means not authorized" +msgid "Version %d rejected" +msgstr "Version %d verworfen" + +#: motion/models.py:136 +msgid "Searching for supporters." +msgstr "Auf Unterstützersuche." + +#: motion/models.py:138 +msgid "Not yet authorized." +msgstr "Noch nicht zugelassen." + +#: motion/models.py:140 +msgid "Not yet authorized changes." +msgstr "Noch nicht zugelassene Änderungen." + +#: motion/models.py:223 +#, python-format +msgid "" +"Trivial changes to version %(version)d; changed fields: %(changed_fields)s" +msgstr "" +"Triviale Änderung an Version %(version)d; Geänderte Felder: " +"%(changed_fields)s" + +#: motion/models.py:235 +#, python-format +msgid "Version %s created" +msgstr "Version %s erstellt" + +#: motion/models.py:244 +msgid "Supporters removed" +msgstr "Unterstützer/innen gelöscht" + +#: motion/models.py:253 +#, python-format +msgid "Status reseted to: %s" +msgstr "Status zurückgesetzt auf: %s" + +#: motion/models.py:265 +#, python-format +msgid "Supporter: +%s" +msgstr "Unterstützer/in: +%s" + +#: motion/models.py:278 +#, python-format +msgid "Supporter: -%s" +msgstr "Unterstützer/in: -%s" + +#: motion/models.py:294 +#, python-format +msgid "Number set: %s" +msgstr "Nummer gesetzt: %s" + +#: motion/models.py:307 +#, python-format +msgid "Version %s authorized" +msgstr "Version %s zugelassen" + +#: motion/models.py:319 +#, python-format +msgid "Version %s not authorized" +msgstr "Version %s nicht zugelassen" + +#: motion/models.py:335 +#, python-format +msgid "The motion status is already '%s.'" +msgstr "Der Antragsstatus ist bereits '%s'." + +#: motion/models.py:343 +#, python-format +msgid "" +"The motion status is: '%(currentstatus)s'. You can not set the status to " +"'%(newstatus)s'." +msgstr "" +"Der Antragsstatus ist: '%(currentstatus)s'. Sie können den Status nicht auf " +"'%(newstatus)s' setzen." + +#: motion/models.py:351 +msgid "Status modified" +msgstr "Status geändert" + +#: motion/models.py:443 motion/models.py:445 +msgid "by" +msgstr "von" + +#: motion/models.py:453 motion/templates/motion/view.html:12 +#: motion/templates/motion/view.html:23 motion/templates/motion/widget.html:27 +#: motion/templates/projector/Motion.html:65 +#: participant/templates/participant/personal_info_widget.html:13 +#: participant/templates/participant/personal_info_widget.html:32 +msgid "no number" +msgstr "ohne Nummer" + +#: motion/models.py:454 motion/templates/motion/widget.html:23 +#: participant/templates/participant/personal_info_widget.html:9 +#: participant/templates/participant/personal_info_widget.html:28 +msgid "motion" +msgstr "Antrag" + +#: motion/models.py:479 +msgid "Poll created" +msgstr "Abstimmung erstellt" + +#: motion/models.py:530 +msgid "Can see motions" +msgstr "Darf Anträge sehen" + +#: motion/models.py:531 +msgid "Can create motions" +msgstr "Darf Anträge erstellen" + +#: motion/models.py:532 +msgid "Can support motions" +msgstr "Darf Anträge unterstützen" + +#: motion/models.py:533 +msgid "Can manage motions" +msgstr "Darf Anträge verwalten" + +#: motion/models.py:600 +msgid "The assembly may decide," +msgstr "Die Versammlung möge beschließen," + +#: motion/models.py:603 motion/views.py:718 motion/views.py:943 +#: motion/views.py:954 motion/templates/motion/base_motion.html:9 +#: motion/templates/motion/config.html:8 +#: motion/templates/motion/overview.html:7 +#: motion/templates/motion/overview.html:11 +msgid "Motions" +msgstr "Anträge" + +#: motion/views.py:173 +msgid "You have not the necessary rights to create or edit motions." +msgstr "" +"Sie haben nicht die nötigen Rechte, um Anträge zu erstellen oder zu " +"bearbeiten." + +#: motion/views.py:178 +msgid "You can not edit this motion." +msgstr "Sie dürfen diesen Antrag nicht bearbeiten." + +#: motion/views.py:236 +msgid "New motion was successfully created." +msgstr "Neuer Antrag wurde erfolgreich angelegt." + +#: motion/views.py:238 +msgid "Motion was successfully modified." +msgstr "Antrag wurde erfolgreich geändert." + +#: motion/views.py:252 +msgid "" +"Attention: Do you really want to edit this motion? The supporters will " +"not be removed automatically because you can manage motions. Please " +"check if the supports are valid after your changing!" +msgstr "" +"Achtung: Wollen Sie den Antrag wirklich ändern? Die Unterstützer/innen " +"werden nicht automatisch entfernt, da Sie Anträge verwalten dürfen. " +"Prüfen Sie, ob die Unterstützungen noch gültig sind." + +#: motion/views.py:254 +#, python-format +msgid "" +"Attention: Do you really want to edit this motion? All %s supporters " +"will be removed! Try to convince the supporters again." +msgstr "" +"Wollen Sie den Antrag wirklich ändern? Alle %s Unterstützer/innen " +"werden dann automatisch entfernt. Versuchen Sie diese erneut zu gewinnen." + +#: motion/views.py:286 +msgid "Motion number was successfully set." +msgstr "Antragsnummer wurde erfolgreich gesetzt." + +#: motion/views.py:302 +msgid "Motion was successfully authorized." +msgstr "Antrag wurde erfolgreich zugelassen." + +#: motion/views.py:317 +msgid "Motion was successfully rejected." +msgstr "Antrag wurde erfolgreich verworfen." + +#: motion/views.py:333 +#, python-format +msgid "Motion status was set to: %s." +msgstr "Antragsstatus wurde gesetzt auf: %s." + +#: motion/views.py:349 +msgid "Motion status was reset." +msgstr "Antragsstatus wurde zurückgesetzt." + +#: motion/views.py:376 +msgid "You can not support this motion." +msgstr "Sie dürfen diesen Antrag nicht unterstützen." + +#: motion/views.py:379 +msgid "You can not unsupport this motion." +msgstr "Sie dürfen Ihre Unterstützung für diesen Antrag nicht entziehen." + +#: motion/views.py:390 +msgid "Do you really want to support this motion?" +msgstr "Wollen Sie wirklich diesen Antrag unterstützen?" + +#: motion/views.py:392 +msgid "Do you really want to unsupport this motion?" +msgstr "Wollen Sie wirklich Ihre Unterstützung für diesen Antrag entziehen?" + +#: motion/views.py:403 +msgid "You have supported this motion successfully." +msgstr "Sie haben den Antrag erfolgreich unterstützt." + +#: motion/views.py:405 +msgid "You have unsupported this motion successfully." +msgstr "Sie haben dem Antrag erfolgreich Ihre Unterstützung entzogen." + +#: motion/views.py:419 +msgid "New vote was successfully created." +msgstr "Neue Abstimmung erfolgreich angelegt." + +#: motion/views.py:435 +msgid "Poll deleted" +msgstr "Abstimmung gelöscht" + +#: motion/views.py:436 +msgid "Poll was successfully deleted." +msgstr "Abstimmung wurde erfolgreich gelöscht." + +#: motion/views.py:438 +#, python-format +msgid "the %s. poll" +msgstr "die %s. Abstimmung" + +#: motion/views.py:479 motion/views.py:488 +#, python-format +msgid "You can not delete motion %s." +msgstr "Sie können Antrag %s nicht löschen." + +#: motion/views.py:484 motion/views.py:492 +#, python-format +msgid "Motion %s was successfully deleted." +msgstr "Antrag %s wurde erfolgreich gelöscht." + +#: motion/views.py:494 +msgid "Invalid request" +msgstr "Ungültige Anfrage" + +#: motion/views.py:518 +msgid "Poll was updated" +msgstr "Abstimmung wurde aktualisiert" + +#: motion/views.py:535 +#, python-format +msgid "Version %s accepted." +msgstr "Version %s akzeptiert." + +#: motion/views.py:537 +#, python-format +msgid "Do you really want to authorize version %s?" +msgstr "Soll Version %s wirklich zugelassen werden?" + +#: motion/views.py:547 +#, python-format +msgid "Version %s rejected." +msgstr "Version %s zurückgewiesen." + +#: motion/views.py:549 +msgid "ERROR by rejecting the version." +msgstr "FEHLER beim Zurückweisen der Version." + +#: motion/views.py:551 +#, python-format +msgid "Do you really want to reject version %s?" +msgstr "Soll Version %s wirklich zurückgewiesen werden?" + +#: motion/views.py:587 motion/views.py:591 motion/views.py:597 +#: motion/views.py:600 participant/api.py:81 +#, python-format +msgid "Ignoring malformed line %d in import file." +msgstr "Fehlerhafte Zeile %d der Quelldatei wurde ignoriert." + +#: motion/views.py:608 +#, python-format +msgid "Ignoring line %d because the assigned group may not act as a person." +msgstr "" +"Fehlerhafte Zeile %d der Quelldatei wurde ignoriert da die verwendete Gruppe " +"nicht als Person auftreten darf." + +#: motion/views.py:617 +msgid "Created by motion import." +msgstr "Erstellt durch Antragsimport." + +#: motion/views.py:631 +#, python-format +msgid "" +"Ignoring line %d because it contains an incomplete first / last name pair." +msgstr "" +"Fehlerhafte Zeile %d der Quelldatei wurde ignoriert, da Vor- bzw. Nachname " +"Leerstrings enthalten." + +#: motion/views.py:669 +#, python-format +msgid "%d motion was successfully imported." +msgid_plural "%d motions were successfully imported." +msgstr[0] "%d Antrag wurde erfolgreich importiert." +msgstr[1] "%d Anträge wurden erfolgreich importiert." + +#: motion/views.py:672 +#, python-format +msgid "%d motion was successfully modified." +msgid_plural "%d motions were successfully modified." +msgstr[0] "%d Antrag wurde erfolgreich geändert." +msgstr[1] "%d Anträge wurden erfolgreich geändert." + +#: motion/views.py:675 +#, python-format +msgid "%d new user was added." +msgid_plural "%d new users were added." +msgstr[0] "%d neuer Nutzer wurde erstellt." +msgstr[1] "%d neue Nutzer wurden erstellt." + +#: motion/views.py:678 +#, python-format +msgid "%d new group was added." +msgid_plural "%d new groups were added." +msgstr[0] "%d neue Gruppe wurde erstellt." +msgstr[1] "%d neue Gruppen wurden erstellt." + +#: motion/views.py:681 +#, python-format +msgid "%d group assigned to motions." +msgid_plural "%d groups assigned to motions." +msgstr[0] "%d Gruppe wurde zugewiesen." +msgstr[1] "%d Gruppen wurden zugewiesen." + +#: motion/views.py:685 participant/api.py:97 +msgid "Import aborted because of severe errors in the input file." +msgstr "Import auf Grund von schweren Fehlern in der Quelldatei abgebrochen." + +#: motion/views.py:687 participant/api.py:99 +msgid "Import file has wrong character encoding, only UTF-8 is supported!" +msgstr "" +"Die Quelldatei benutzt eine ungültige Zeichenkodierung, es wird nur UTF-8 " +"wird unterstützt!" + +#: motion/views.py:691 +msgid "" +"Attention: Existing motions will be modified if you import new motions with " +"the same number." +msgstr "" +"Achtung: Existierende Anträge werden geändert wenn Sie neue Anträge mit " +"identischer Nummer importieren." + +#: motion/views.py:692 +msgid "" +"Attention: Importing an motions without a number multiple times will create " +"duplicates." +msgstr "" +"Achtung: Bei mehrfachem Import eines Antrags ohne Nummer können Duplikate " +"entstehen." + +#: motion/views.py:725 motion/views.py:863 +#: motion/templates/motion/poll_view.html:7 +#: motion/templates/motion/poll_view.html:14 +#: motion/templates/motion/view.html:8 motion/templates/motion/view.html:21 +#: motion/templates/projector/Motion.html:7 +#: motion/templates/projector/Motion.html:65 +msgid "Motion" +msgstr "Antrag" + +#: motion/views.py:739 motion/templates/motion/overview.html:101 +msgid "No motions available." +msgstr "Keine Anträge vorhanden." + +#: motion/views.py:744 motion/views.py:746 motion/views.py:761 +#: motion/views.py:763 motion/templates/motion/base_motion.html:24 +#: motion/templates/projector/Motion.html:63 +msgid "Motion No." +msgstr "Antrag Nr." + +#: motion/views.py:778 +msgid "Signature" +msgstr "Unterschrift" + +#: motion/views.py:829 motion/templates/motion/base_motion.html:55 +#: motion/templates/motion/poll_view.html:7 +#: motion/templates/motion/poll_view.html:14 +#: motion/templates/motion/view.html:54 motion/templates/motion/view.html:206 +#: motion/templates/projector/Motion.html:33 +msgid "Vote" +msgstr "Abstimmung" + +#: motion/views.py:863 +msgid "Poll" +msgstr "Abstimmung" + +#: motion/views.py:877 +#, python-format +msgid "Motion No. %s" +msgstr "Antrag Nr. %s" + +#: motion/views.py:879 +#, python-format +msgid "%d. Vote" +msgstr "%d. Abstimmung" + +#: motion/views.py:936 +msgid "Motion settings successfully saved." +msgstr "Antrags-Einstellungen wurden erfolgreich gespeichert." + +#: motion/templates/motion/base_motion.html:11 +msgid "All motions" +msgstr "Alle Anträge" + +#: motion/templates/motion/base_motion.html:13 +#: motion/templates/motion/edit.html:10 motion/templates/motion/edit.html:19 +#: motion/templates/motion/overview.html:14 +msgid "New motion" +msgstr "Neuer Antrag" + +#: motion/templates/motion/base_motion.html:16 +#: motion/templates/motion/import.html:5 motion/templates/motion/import.html:8 +#: motion/templates/motion/overview.html:17 +msgid "Import motions" +msgstr "Anträge importieren" + +#: motion/templates/motion/base_motion.html:18 +msgid "All motions as PDF" +msgstr "Alle Anträge als PDF" + +#: motion/templates/motion/base_motion.html:34 +msgid "View motion" +msgstr "Antrag anzeigen" + +#: motion/templates/motion/base_motion.html:38 +#: motion/templates/motion/edit.html:8 motion/templates/motion/edit.html:17 +#: motion/templates/motion/overview.html:89 +#: motion/templates/motion/view.html:39 +msgid "Edit motion" +msgstr "Antrag bearbeiten" + +#: motion/templates/motion/base_motion.html:42 +#: motion/templates/motion/overview.html:91 +#: motion/templates/motion/view.html:43 +msgid "Delete motion" +msgstr "Antrag löschen" + +#: motion/templates/motion/base_motion.html:45 +#: motion/templates/motion/overview.html:94 +msgid "Motion as PDF" +msgstr "Antrag als PDF" + +#: motion/templates/motion/base_motion.html:49 +msgid "Show Motion" +msgstr "Antrag projizieren" + +#: motion/templates/motion/base_motion.html:61 +#: motion/templates/motion/view.html:60 +msgid "New agenda item" +msgstr "Neuer Tagesordnungseintrag" + +#: motion/templates/motion/config.html:5 +msgid "Motion settings" +msgstr "Antrags Einstellungen" + +#: motion/templates/motion/import.html:14 +msgid "Select a CSV file to import motions!" +msgstr "Wählen Sie eine CSV-Datei zum Importieren von Anträgen aus!" + +#: motion/templates/motion/import.html:15 +#: participant/templates/participant/import.html:17 +msgid "Required comma separated values" +msgstr "Erforderliche kommaseparierte Werte" + +#: motion/templates/motion/import.html:16 +msgid "number, title, text, reason, first_name, last_name, is_group" +msgstr "Nummer, Titel, Text, Begründung, Vorname, Nachname, Gruppenantrag" + +#: motion/templates/motion/import.html:18 +msgid "" +"number, reason and is_group are " +"optional and may be empty" +msgstr "" +"Nummer, Begründung und Gruppenantrag " +"sind optional und können auch leer sein" + +#: motion/templates/motion/import.html:20 +#: participant/templates/participant/import.html:20 +msgid "Required CSV file encoding: UTF-8 (Unicode)." +msgstr "Erforderliches CSV-Datei-Encoding: UTF-8 (Unicode)." + +#: motion/templates/motion/import.html:23 +#: participant/templates/participant/import.html:23 +msgid "A CSV example file is available in OpenSlides Wiki." +msgstr "Eine CSV-Beispiel-Datei gibt es im OpenSlides Wiki." + +#: motion/templates/motion/import.html:41 +#: motion/templates/motion/overview.html:17 +#: participant/templates/participant/import.html:30 +#: participant/templates/participant/overview.html:24 +msgid "Import" +msgstr "Importieren" + +#: motion/templates/motion/overview.html:19 +#, fuzzy +msgid "Print all motions as PDF" +msgstr "Alle Anträge als PDF" + +#: motion/templates/motion/overview.html:27 +msgid "Need supporters" +msgstr "Benötigt Unterstützer/innen" + +#: motion/templates/motion/overview.html:32 +msgid "Without number" +msgstr "Ohne Nummer" + +#: motion/templates/motion/overview.html:39 +msgid "Not yet authorized" +msgstr "Noch nicht zugelassen" + +#: motion/templates/motion/overview.html:40 +msgid "Authorized" +msgstr "Zugelassen" + +#: motion/templates/motion/overview.html:43 +msgid "Withdrawen (by submitter)" +msgstr "Zurückgezogen (durch Antragsteller/in)" + +#: motion/templates/motion/overview.html:48 +msgctxt "number of motions" +msgid "motion" +msgid_plural "motions" +msgstr[0] "Antrag" +msgstr[1] "Anträge" + +#: motion/templates/motion/overview.html:52 +msgid "#" +msgstr "" + +#: motion/templates/motion/overview.html:53 +msgid "Motion title" +msgstr "Antragstitel" + +#: motion/templates/motion/overview.html:55 +msgid "Number of supporters" +msgstr "Anzahl der Unterstützer/innen" + +#: motion/templates/motion/overview.html:59 +#: motion/templates/motion/view.html:242 +msgid "Creation Time" +msgstr "Erstellungszeit" + +#: motion/templates/motion/overview.html:84 +msgid "Activate motion" +msgstr "Antrag projizieren" + +#: motion/templates/motion/poll_view.html:18 +#, fuzzy +msgid "Back to motion" +msgstr "Antrag bearbeiten" + +#: motion/templates/motion/poll_view.html:21 +#: motion/templates/motion/view.html:33 motion/templates/motion/view.html:316 +#, fuzzy +msgid "More actions" +msgstr "Anträge" + +#: motion/templates/motion/poll_view.html:27 +#: motion/templates/motion/view.html:209 +msgid "Delete Vote" +msgstr "Abstimmung löschen" + +#: motion/templates/motion/poll_view.html:32 +#, fuzzy +msgid "Show Application" +msgstr "Wahl projizieren" + +#: motion/templates/motion/poll_view.html:48 +msgid "Option" +msgstr "Wahlmöglichkeit" + +#: motion/templates/motion/view.html:30 +#, fuzzy +msgid "Print this motion as PDF" +msgstr "Alle Anträge als PDF" + +#: motion/templates/motion/view.html:48 +#, fuzzy +msgid "Show motion" +msgstr "Antrag projizieren" + +#: motion/templates/motion/view.html:74 +msgid "This is not the newest version." +msgstr "Dies ist nicht die neuste Version." + +#: motion/templates/motion/view.html:74 motion/templates/motion/view.html:76 +msgid "Go to version" +msgstr "Gehe zu Version" + +#: motion/templates/motion/view.html:76 +msgid "This is not the authorized version." +msgstr "Dies ist nicht die zugelassene Version." + +#: motion/templates/motion/view.html:81 +#, fuzzy +msgid "Motion text" +msgstr "Antragstitel" + +#: motion/templates/motion/view.html:94 +msgid "Version History" +msgstr "Versionshistorie" + +#: motion/templates/motion/view.html:99 +msgid "Time" +msgstr "Zeit" + +#: motion/templates/motion/view.html:109 +msgid "Version authorized" +msgstr "Version %d zugelassen" + +#: motion/templates/motion/view.html:112 +msgid "Permit Version" +msgstr "Version zulassen" + +#: motion/templates/motion/view.html:115 +msgid "Reject Version" +msgstr "Version verwerfen" + +#: motion/templates/motion/view.html:119 +msgid "Version rejected" +msgstr "Version verworfen" + +#: motion/templates/motion/view.html:129 motion/templates/motion/view.html:136 +#: motion/templates/motion/view.html:143 +msgid "unchanged" +msgstr "unverändert" + +#: motion/templates/motion/view.html:152 +msgid "Log" +msgstr "Log" + +#: motion/templates/motion/view.html:194 motion/templates/motion/view.html:226 +msgid "New vote" +msgstr "Neue Abstimmung" + +#: motion/templates/motion/view.html:208 +msgid "Edit Vote" +msgstr "Abstimmung bearbeiten" + +#: motion/templates/motion/view.html:233 +#, fuzzy +msgid "No results" +msgstr "Abstimmungsergebnis" + +#: motion/templates/motion/view.html:248 +#, fuzzy +msgid "Withdraw motion" +msgstr "Zurückziehen" + +#: motion/templates/motion/view.html:256 +#, fuzzy +msgid "Unsupport motion" +msgstr "Darf Anträge unterstützen" + +#: motion/templates/motion/view.html:262 +msgid "Support" +msgstr "Unterstützen" + +#: motion/templates/motion/view.html:269 +msgid "minimum required supporters" +msgstr "minimal erforderliche Unterstützer/innen" + +#: motion/templates/motion/view.html:276 +msgid "Manage motion" +msgstr "Antrag Verwalten" + +#: motion/templates/motion/view.html:279 +msgid "Formal validation" +msgstr "Formale Gültigkeitsprüfung" + +#: motion/templates/motion/view.html:282 +msgid "Publish" +msgstr "Veröffentlichen" + +#: motion/templates/motion/view.html:285 +msgid "Permit" +msgstr "Zulassen" + +#: motion/templates/motion/view.html:288 +#, fuzzy +msgid "Not permit" +msgstr "Nicht zulassen (verwerfen)" + +#: motion/templates/motion/view.html:291 +msgid "Set number" +msgstr "Setze Nummer" + +#: motion/templates/motion/view.html:297 +msgid "Result after vote" +msgstr "Ergebnis nach der Abstimmung" + +#: motion/templates/motion/view.html:330 +#, fuzzy +msgid "Withdrawed by Submitter" +msgstr "Zurückgezogen durch Antragsteller/in" + +#: motion/templates/motion/view.html:337 +msgid "For Administration only:" +msgstr "Nur zur Administration:" + +#: motion/templates/motion/view.html:339 +msgid "Reset" +msgstr "Zurücksetzen" + +#: motion/templates/motion/widget.html:31 +msgid "No motion available." +msgstr "Keine Antrag vorhanden." + +#: motion/templates/projector/Motion.html:29 +msgid "Poll result" +msgstr "Abstimmungsergebnis" + +#: motion/templates/projector/Motion.html:47 +msgid "No poll results available." +msgstr "Keine Abstimmungsergebnisse vorhanden." + #: participant/__init__.py:3 msgid "Participant" msgstr "Teilnehmer" -#: participant/forms.py:26 -#: participant/templates/participant/group_overview.html:6 -#: participant/templates/participant/group_overview.html:9 -#: participant/templates/participant/overview.html:23 -msgid "User groups" -msgstr "Benutzerrollen" +#: participant/forms.py:27 participant/views.py:608 +#: participant/templates/participant/group_overview.html:7 +#: participant/templates/participant/group_overview.html:10 +#: participant/templates/participant/user_detail.html:14 +msgid "Groups" +msgstr "Gruppen" -#: participant/forms.py:44 -msgid "Persmissions" +#: participant/forms.py:52 +msgid "Permissions" msgstr "Rechte" -#: participant/forms.py:47 -msgid "Users" -msgstr "Nutzer" +#: participant/forms.py:55 participant/views.py:546 participant/views.py:594 +#: participant/templates/participant/config.html:8 +#: participant/templates/participant/group_overview.html:13 +#: participant/templates/participant/overview.html:7 +#: participant/templates/participant/overview.html:18 +#: participant/templates/participant/overview.html:97 +msgid "Participants" +msgstr "Teilnehmer/innen" -#: participant/forms.py:83 -msgid "You can not edit the name for the anonymous user" -msgstr "Sie dürfen den Namen von dem anonymen Nutzer nicht bearbeiten." +#: participant/forms.py:92 +msgid "You can not edit the name for this group." +msgstr "Sie dürfen den Namen dieser Gruppe nicht bearbeiten." -#: participant/forms.py:87 +#: participant/forms.py:96 #, python-format msgid "Group name \"%s\" is reserved for internal use." msgstr "Der Gruppenname \"%s\" ist für interne Verwendung reserviert." -#: participant/forms.py:109 +#: participant/forms.py:118 msgid "System URL" msgstr "System URL" -#: participant/forms.py:110 participant/forms.py:115 +#: participant/forms.py:119 participant/forms.py:124 msgid "Printed in PDF of first time passwords only." msgstr "Erscheint nur im PDF der Erst-Passwörter" -#: participant/models.py:28 participant/templates/participant/overview.html:59 +#: participant/forms.py:127 +msgid "Sort participants by first name" +msgstr "Teilnehmer/innen nach Vornamen sortieren" + +#: participant/forms.py:128 +msgid "Disable for sorting by last name" +msgstr "Deaktivieren für Sortierung nach Nachnamen" + +#: participant/models.py:33 participant/templates/participant/overview.html:59 msgid "Male" msgstr "Männlich" -#: participant/models.py:29 participant/templates/participant/overview.html:60 +#: participant/models.py:34 participant/templates/participant/overview.html:60 msgid "Female" msgstr "Weiblich" -#: participant/models.py:32 participant/templates/participant/overview.html:72 +#: participant/models.py:37 participant/templates/participant/overview.html:72 msgid "Delegate" msgstr "Delegierter" -#: participant/models.py:33 participant/templates/participant/overview.html:73 +#: participant/models.py:38 participant/templates/participant/overview.html:73 msgid "Observer" msgstr "Beobachter" -#: participant/models.py:34 participant/templates/participant/overview.html:74 +#: participant/models.py:39 participant/templates/participant/overview.html:74 msgid "Staff" msgstr "Mitarbeiter" -#: participant/models.py:35 participant/templates/participant/overview.html:75 +#: participant/models.py:40 participant/templates/participant/overview.html:75 msgid "Guest" msgstr "Gast" -#: participant/models.py:40 participant/templates/participant/overview.html:64 +#: participant/models.py:45 participant/templates/participant/overview.html:64 #: participant/templates/participant/overview.html:104 -msgid "Category" -msgstr "Kategorie" +msgid "Structure level" +msgstr "Gliederungsebene" -#: participant/models.py:41 -msgid "Will be shown behind the name." +#: participant/models.py:46 +msgid "Will be shown after the name." msgstr "Wird nach dem Namen angezeigt." -#: participant/models.py:44 participant/templates/participant/overview.html:58 +#: participant/models.py:49 participant/templates/participant/overview.html:58 +#: participant/templates/participant/user_detail.html:24 msgid "Gender" msgstr "Geschlecht" -#: participant/models.py:44 participant/models.py:47 participant/models.py:50 -msgid "Only for filter the userlist." -msgstr "Nur zum Filtern der Benutzerliste." +#: participant/models.py:49 participant/models.py:52 participant/models.py:55 +msgid "Only for filtering the participant list." +msgstr "Nur zum Filtern der Teilnehmerliste." -#: participant/models.py:47 +#: participant/models.py:52 msgid "Typ" msgstr "Typ" -#: participant/models.py:49 participant/views.py:216 +#: participant/models.py:54 participant/views.py:255 #: participant/templates/participant/overview.html:79 #: participant/templates/participant/overview.html:106 +#: participant/templates/participant/user_detail.html:34 msgid "Committee" msgstr "Amt" -#: participant/models.py:53 +#: participant/models.py:57 +#: participant/templates/participant/user_detail.html:39 +msgid "About me" +msgstr "Über mich" + +#: participant/models.py:58 +msgid "Your profile text" +msgstr "Ihr Profiltext" + +#: participant/models.py:61 msgid "Only for notes." msgstr "Nur für Notizen." -#: participant/models.py:56 +#: participant/models.py:64 msgid "Default password" -msgstr "Erst-Passwort" +msgstr "Vorgegebenes Passwort" -#: participant/models.py:98 +#: participant/models.py:118 msgid "Can see participant" msgstr "Darf die Teilnehmer/inen sehen" -#: participant/models.py:100 +#: participant/models.py:120 msgid "Can manage participant" msgstr "Darf die Teilnehmer/inen verwalten" -#: participant/views.py:211 +#: participant/models.py:142 +msgid "Use this group as participant" +msgstr "Verwende diese Gruppe als Teilnehmer/in" + +#: participant/models.py:143 +msgid "For example as submitter of a motion." +msgstr "Zum Beispiel als Antragsteller." + +#: participant/models.py:237 +msgid "Welcome to OpenSlides!" +msgstr "Willkommen bei OpenSlides!" + +#: participant/views.py:207 +msgid "You can not delete yourself." +msgstr "Sie dürfen sich nicht selbst löschen." + +#: participant/views.py:228 +msgid "You can not deactivate yourself." +msgstr "Sie dürfen sich nicht selbst deaktivieren." + +#: participant/views.py:231 +msgid "You can not deactivate the administrator." +msgstr "Sie dürfen den Administrator nicht deaktivieren." + +#: participant/views.py:250 msgid "Participant-list" msgstr "Teilnehmerliste" -#: participant/views.py:212 +#: participant/views.py:251 msgid "List of Participants" msgstr "Teilnehmerliste" -#: participant/views.py:215 +#: participant/views.py:254 #: participant/templates/participant/overview.html:103 msgid "Last Name" msgstr "Nachname" -#: participant/views.py:215 +#: participant/views.py:254 #: participant/templates/participant/overview.html:102 msgid "First Name" msgstr "Vorname" -#: participant/views.py:215 +#: participant/views.py:254 +#: participant/templates/participant/group_overview.html:19 msgid "Group" msgstr "Gruppe" -#: participant/views.py:215 participant/templates/participant/overview.html:71 +#: participant/views.py:254 participant/templates/participant/overview.html:71 #: participant/templates/participant/overview.html:105 +#: participant/templates/participant/user_detail.html:29 msgid "Type" msgstr "Typ" -#: participant/views.py:244 +#: participant/views.py:286 msgid "Participant-passwords" msgstr "Teilnehmer-Passwoerter" -#: participant/views.py:262 +#: participant/views.py:308 msgid "Account for OpenSlides" msgstr "Zugang für OpenSlides" -#: participant/views.py:264 +#: participant/views.py:310 #, python-format msgid "for %s" msgstr "für %s" -#: participant/views.py:267 +#: participant/views.py:313 #, python-format msgid "User: %s" msgstr "Nutzername: %s" -#: participant/views.py:271 +#: participant/views.py:317 #, python-format msgid "Password: %s" msgstr "Passwort: %s" -#: participant/views.py:276 +#: participant/views.py:322 #, python-format msgid "URL: %s" msgstr "URL: %s" -#: participant/views.py:318 +#: participant/views.py:364 #, python-format msgid "%d new participants were successfully imported." msgstr "%d neue Teilnehmer/innen wurden erfolgreich importiert." -#: participant/views.py:329 +#: participant/views.py:375 msgid "Do you really want to reset the password?" msgstr "Soll das Passwort wirklich zurückgesetzt werden?" -#: participant/views.py:345 +#: participant/views.py:388 #, python-format msgid "The Password for %s was successfully reset." msgstr "Das Passwort für %s wurde erfolgreich zurückgesetzt." -#: participant/views.py:424 +#: participant/views.py:445 +msgid "You can not delete this Group." +msgstr "Sie dürfen diese Gruppe nicht löschen." + +#: participant/views.py:473 msgid "Participants settings successfully saved." msgstr "Teilnehmer/innen-Einstellungen wurden erfolgreich gespeichert." -#: participant/views.py:434 +#: participant/views.py:483 #, python-format msgid "" "Installation was successfully! Use %(user)s (password: %(password)s) for " @@ -1817,23 +1996,19 @@ msgstr "" "Die Installation war erfolgreich! Verwenden Sie %(user)s (Passwort: " "%(password)s) für die erste Anmeldung.
    Wichtig: Ändern " "Sie das Passwort nach der ersten Anmeldung! Anderenfalls erscheint diese " -"Meldung weiterhin für jeden und ist ein Sicherheitsrisiko." +"Meldung weiterhin für alle und ist ein Sicherheitsrisiko." -#: participant/views.py:457 +#: participant/views.py:506 msgid "User settings successfully saved." msgstr "Nutzereinstellungen wurden erfolgreich gespeichert." -#: participant/views.py:479 +#: participant/views.py:528 msgid "Password successfully changed." -msgstr "Password wurde erfolgreich geändert." +msgstr "Passwort wurde erfolgreich geändert." -#: participant/views.py:497 participant/templates/participant/config.html:8 -#: participant/templates/participant/group_overview.html:12 -#: participant/templates/participant/overview.html:7 -#: participant/templates/participant/overview.html:18 -#: participant/templates/participant/overview.html:97 -msgid "Participants" -msgstr "Teilnehmer/innen" +#: participant/views.py:580 +msgid "My motions and elections" +msgstr "Meine Anträge und Wahlen" #: participant/templates/participant/config.html:5 msgid "Participant settings" @@ -1855,32 +2030,34 @@ msgstr "Neue/r Teilnehmer/in" msgid "Reset to First Password" msgstr "Auf Erst-Passwort zurücksetzen" +#: participant/templates/participant/group_detail.html:14 +msgid "Members" +msgstr "Mitglieder" + +#: participant/templates/participant/group_detail.html:19 +msgid "No members available." +msgstr "Keine Mitglieder vorhanden." + #: participant/templates/participant/group_edit.html:8 #: participant/templates/participant/group_edit.html:17 -msgid "Edit user group" -msgstr "Benutzerrolle bearbeiten" +#: participant/templates/participant/group_overview.html:25 +msgid "Edit group" +msgstr "Gruppe bearbeiten" #: participant/templates/participant/group_edit.html:10 #: participant/templates/participant/group_edit.html:19 -#: participant/templates/participant/group_overview.html:11 -msgid "New user group" -msgstr "Neue Benutzerrolle" +#: participant/templates/participant/group_overview.html:12 +msgid "New group" +msgstr "Neue Gruppe" -#: participant/templates/participant/group_overview.html:18 -msgid "User Group" -msgstr "Benutzerrolle" - -#: participant/templates/participant/group_overview.html:24 -msgid "Edit group" -msgstr "Benutzerrolle bearbeiten" - -#: participant/templates/participant/group_overview.html:26 +#: participant/templates/participant/group_overview.html:27 msgid "Delete group" -msgstr "Benutzerrolle löschen" +msgstr "Gruppe löschen" -#: participant/templates/participant/group_overview.html:32 -msgid "No user groups available." -msgstr "Keine Benutzerrollen vorhanden." +#: participant/templates/participant/group_overview.html:33 +#: participant/templates/participant/group_widget.html:24 +msgid "No groups available." +msgstr "Keine Gruppen vorhanden." #: participant/templates/participant/import.html:5 #: participant/templates/participant/import.html:9 @@ -1892,13 +2069,10 @@ msgstr "Teilnehmer/innen importieren" msgid "Select a CSV file to import participants!" msgstr "Wählen Sie eine CSV-Datei zum Importieren von Teilnehmer/innen aus!" -#: participant/templates/participant/import.html:17 +#: participant/templates/participant/import.html:18 msgid "" -"Required comma separated values: {first_name, last_name, gender, " -"group, type, committee, comment}" -msgstr "" -"Erforderliche kommaseparierte Werte: {Vorname, Nachname, Geschlecht, " -"Gruppe, Typ, Amt, Kommentar}" +"first_name, last_name, gender, structure level, type, committee, comment" +msgstr "Vorname, Nachname, Geschlecht, Gliederungsebene, Typ, Amt, Kommentar" #: participant/templates/participant/login.html:16 msgid "Your username and password were not accepted. Please try again." @@ -1908,11 +2082,12 @@ msgstr "" #: participant/templates/participant/login.html:29 msgid "Username" -msgstr "Benutzername" +msgstr "" #: participant/templates/participant/login.html:33 +#, fuzzy msgid "Password" -msgstr "Passwort" +msgstr "Passwort: %s" #: participant/templates/participant/login.html:38 #: participant/templates/participant/overview.html:39 templates/base.html:44 @@ -1924,31 +2099,41 @@ msgid "Continue as guest" msgstr "Weiter als Gast" #: participant/templates/participant/overview.html:23 +#, fuzzy msgid "All user groups" -msgstr "Alle Benutzerrollen" +msgstr "Alle Gruppen" + +#: participant/templates/participant/overview.html:23 +#, fuzzy +msgid "User groups" +msgstr "Neue Gruppe" #: participant/templates/participant/overview.html:35 +#, fuzzy msgid "List of participants" -msgstr "Liste der Teilnehmer/innen" +msgstr "Teilnehmerliste" #: participant/templates/participant/overview.html:36 +#, fuzzy msgid "First time passwords" msgstr "Erst-Passwörter als PDF" #: participant/templates/participant/overview.html:44 +#, fuzzy msgid "Print list of participants as PDF" -msgstr "Liste der Teilnehmer/innen als PDF drucken" +msgstr "Teilnehmerliste als PDF" #: participant/templates/participant/overview.html:47 +#, fuzzy msgid "Print first time passwords as PDF" -msgstr "Erst-Passwörter als PDF drucken" +msgstr "Erst-Passwörter als PDF" #: participant/templates/participant/overview.html:61 #: participant/templates/participant/overview.html:76 msgid "Not specified" msgstr "Nicht angegeben" -#: participant/templates/participant/overview.html:87 projector/models.py:66 +#: participant/templates/participant/overview.html:87 projector/models.py:63 msgid "Active" msgstr "Aktiv" @@ -1967,35 +2152,67 @@ msgid "of" msgstr "von" #: participant/templates/participant/overview.html:109 +#: participant/templates/participant/user_detail.html:49 msgid "Last Login" msgstr "Letzer Login" -#: participant/templates/participant/overview.html:133 +#: participant/templates/participant/overview.html:134 msgid "Delete participant" msgstr "Teilnehmer/in löschen" -#: participant/templates/participant/overview.html:135 -msgid "Change status to inactive" -msgstr "Status ändern zu inaktiv" - #: participant/templates/participant/overview.html:138 -msgid "Change status to active" -msgstr "Status ändern zu aktiv" +msgid "Change status to inactive" +msgstr "Status ändern auf inaktiv" -#: participant/templates/participant/overview.html:147 +#: participant/templates/participant/overview.html:141 +msgid "Change status to active" +msgstr "Status ändern auf aktiv" + +#: participant/templates/participant/overview.html:151 +#: participant/templates/participant/user_widget.html:22 msgid "No participants available." msgstr "Keine Teilnehmer/innen vorhanden." #: participant/templates/participant/password_change.html:5 #: participant/templates/participant/password_change.html:8 msgid "Password Settings" -msgstr "Passwort Einstellungen" +msgstr "Passwort-Einstellungen" + +#: participant/templates/participant/personal_info_widget.html:5 +msgid "I submitted the following motions:" +msgstr "Ich habe folgende Anträge gestellt:" + +#: participant/templates/participant/personal_info_widget.html:17 +#: participant/templates/participant/personal_info_widget.html:36 +#: participant/templates/participant/personal_info_widget.html:47 +msgid "None" +msgstr "Keine" + +#: participant/templates/participant/personal_info_widget.html:24 +msgid "I support the following motions:" +msgstr "Ich unterstütze folgende Anträge:" + +#: participant/templates/participant/personal_info_widget.html:43 +msgid "I am candidate for the following elections:" +msgstr "Ich bin Kandidat/in bei folgenden Wahlen:" #: participant/templates/participant/settings.html:5 #: participant/templates/participant/settings.html:8 msgid "Personal Settings" msgstr "Persönliche Einstellungen" +#: participant/templates/participant/user_detail.html:19 +msgid "The participant is not member of any group." +msgstr "Teilnehmer/in ist kein Mitglied einer Gruppe." + +#: participant/templates/participant/user_detail.html:53 +msgid "The participant has not logged in yet." +msgstr "Teilnehmer/in hat sich noch nicht angemeldet." + +#: participant/templates/projector/GroupSlide.html:11 +msgid "participants" +msgstr "Teilnehmer/innen" + #: poll/models.py:95 msgid "Votes invalid" msgstr "Ungültige Stimmen" @@ -2004,39 +2221,40 @@ msgstr "Ungültige Stimmen" msgid "votes" msgstr "Stimmen" -#: projector/models.py:52 +#: projector/models.py:50 msgid "Can manage the projector" msgstr "Darf den Projektor steuern" -#: projector/models.py:53 +#: projector/models.py:51 msgid "Can see the projector" msgstr "Darf den Projektor sehen" -#: projector/models.py:54 +#: projector/models.py:52 msgid "Can see the dashboard" msgstr "Darf das Dashboard sehen" -#: projector/views.py:184 +#: projector/views.py:199 msgid "Errors in the form" msgstr "Fehler im Formular" -#: projector/views.py:360 projector/templates/projector/dashboard.html:18 +#: projector/views.py:375 projector/templates/projector/dashboard.html:18 msgid "Dashboard" msgstr "Dashboard" -#: projector/views.py:378 +#: projector/views.py:403 msgid "Projector live view" msgstr "Projektor-Live-Ansicht" -#: projector/views.py:404 +#: projector/views.py:429 msgid "Overlays" msgstr "Einblendungen" -#: projector/views.py:417 +#: projector/views.py:441 msgid "Custom Slides" msgstr "Benutzerdefinierte Folien" #: projector/templates/projector/control_countdown.html:9 +msgctxt "seconds" msgid "s" msgstr "s" @@ -2065,34 +2283,31 @@ msgid "Welcome Page" msgstr "Willkommensseite" #: projector/templates/projector/dashboard.html:20 +#, fuzzy msgid "Manage widgets" -msgstr "Widgets verwalten" +msgstr "Widgets auswählen" #: projector/templates/projector/dashboard.html:20 msgid "Widgets" -msgstr "Widgets" +msgstr "" -#: projector/templates/projector/dashboard.html:26 -msgid "Adjust projector view" -msgstr "Projektor-Ansicht anpassen" - -#: projector/templates/projector/dashboard.html:27 +#: projector/templates/projector/live_view_widget.html:9 msgid "Zoom in" msgstr "Vergrößern" -#: projector/templates/projector/dashboard.html:30 +#: projector/templates/projector/live_view_widget.html:12 msgid "Zoom out" msgstr "Verkleinern" -#: projector/templates/projector/dashboard.html:33 +#: projector/templates/projector/live_view_widget.html:17 msgid "Scroll text up" msgstr "Text nach oben scrollen" -#: projector/templates/projector/dashboard.html:36 +#: projector/templates/projector/live_view_widget.html:20 msgid "Scroll text down" msgstr "Text nach unten scrollen" -#: projector/templates/projector/dashboard.html:39 +#: projector/templates/projector/live_view_widget.html:25 msgid "Reset projector view" msgstr "Projektor-Ansicht zurücksetzen" @@ -2118,76 +2333,134 @@ msgstr "Seite nicht gefunden." msgid "Server Error" msgstr "Serverfehler" -#: templates/base.html:28 templates/front_page.html:6 +#: templates/base.html:28 msgid "Home" msgstr "Startseite" #: templates/base.html:38 msgid "Profile" -msgstr "Profil" +msgstr "" #: templates/base.html:39 +#, fuzzy msgid "Change password" -msgstr "Passwort ändern" +msgstr "Vorgegebenes Passwort" #: templates/base.html:41 msgid "Logout" msgstr "Abmelden" -#: templates/front_page.html:12 -msgid "You have access to the following pages:" -msgstr "Sie haben Zugriff auf folgende Seiten:" +#: templates/base.html:97 +msgid "" +"Get professional " +"support for OpenSlides." +msgstr "" +"Nutzen Sie unseren professionellen Support für OpenSlides." -#: utils/pdf.py:225 -msgid "%Y-%m-%d %H:%Mh" -msgstr "%d.%m.%Y %H:%Mh" - -#: utils/pdf.py:226 +#: utils/pdf.py:227 #, python-format -msgid "Printed: %s" -msgstr "Gedruckt am: %s" +msgid "As of: %s" +msgstr "Stand: %s" -#: utils/pdf.py:237 utils/pdf.py:246 +#: utils/pdf.py:238 utils/pdf.py:247 #, python-format msgid "Page %s" msgstr "Seite %s" -#: utils/utils.py:66 utils/views.py:271 +#: utils/utils.py:63 utils/views.py:287 #, python-format msgid "Do you really want to delete %s?" msgstr "Soll %s wirklich gelöscht werden?" -#: utils/utils.py:111 +#: utils/utils.py:110 msgid "Sorry, you have no rights to see this page." msgstr "Bedaure, Sie haben keine Berechtigung diese Seite zu sehen." -#: utils/views.py:109 +#: utils/views.py:106 msgid "Are you sure?" msgstr "Sind Sie sicher?" -#: utils/views.py:110 +#: utils/views.py:107 msgid "Thank you for your answer" msgstr "Danke für Ihre Antwort" -#: utils/views.py:232 +#: utils/views.py:244 #, python-format msgid "%s was successfully modified." msgstr "%s wurde erfolgreich bearbeitet." -#: utils/views.py:263 +#: utils/views.py:275 #, python-format msgid "%s was successfully created." msgstr "%s wurde erfolgreich angelegt." -#: utils/views.py:274 +#: utils/views.py:293 #, python-format msgid "%s was successfully deleted." msgstr "%s wurde erfolgreich gelöscht." -#: utils/views.py:305 +#: utils/views.py:308 msgid "undefined-filename" msgstr "undefinierter-dateiname" -#: utils/jsonfield/fields.py:21 +#: utils/jsonfield/fields.py:22 msgid "Enter valid JSON" msgstr "Gebe valides JSON ein" + +#~ msgid "All items" +#~ msgstr "Alle Einträge" + +#~ msgid "View item" +#~ msgstr "Eintrag anzeigen" + +#~ msgid "Show item" +#~ msgstr "Eintrag projizieren" + +#~ msgid "Done" +#~ msgstr "Erledigt" + +#~ msgid "All elections" +#~ msgstr "Alle Wahlen" + +#~ msgid "View election" +#~ msgstr "Wahl anzeigen" + +#~ msgid "Number" +#~ msgstr "Nummer" + +#~ msgid "Enter result" +#~ msgstr "Ergebnis eingeben" + +#~ msgid "Unsupport" +#~ msgstr "Nicht unterstützen" + +#~ msgid "Result after debate" +#~ msgstr "Ergebnis nach der Debatte" + +#~ msgid "All participants" +#~ msgstr "Alle Teilnehmer/innen" + +#~ msgid "View participant" +#~ msgstr "Teilnehmer/in anzeigen" + +#~ msgid "View group" +#~ msgstr "Gruppen anzeigen" + +#~ msgid "Close this notification" +#~ msgstr "Meldung ausblenden" + +#~ msgid "User Settings" +#~ msgstr "Benutzereinstellungen" + +#~ msgid "Overview" +#~ msgstr "Übersicht" + +#~ msgid "Projector view" +#~ msgstr "Projektor-Ansicht" + +#~ msgid "New slide" +#~ msgstr "Neue Folie" + +#~ msgid "Welcome" +#~ msgstr "Willkommen" diff --git a/openslides/locale/de/LC_MESSAGES/djangojs.mo b/openslides/locale/de/LC_MESSAGES/djangojs.mo new file mode 100644 index 000000000..062f0f668 Binary files /dev/null and b/openslides/locale/de/LC_MESSAGES/djangojs.mo differ diff --git a/openslides/locale/de/LC_MESSAGES/djangojs.po b/openslides/locale/de/LC_MESSAGES/djangojs.po new file mode 100644 index 000000000..6e0f7367c --- /dev/null +++ b/openslides/locale/de/LC_MESSAGES/djangojs.po @@ -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 , 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 \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." diff --git a/openslides/locale/fr/LC_MESSAGES/django.mo b/openslides/locale/fr/LC_MESSAGES/django.mo new file mode 100644 index 000000000..22787ea37 Binary files /dev/null and b/openslides/locale/fr/LC_MESSAGES/django.mo differ diff --git a/openslides/locale/fr/LC_MESSAGES/django.po b/openslides/locale/fr/LC_MESSAGES/django.po new file mode 100644 index 000000000..f3a54f26e --- /dev/null +++ b/openslides/locale/fr/LC_MESSAGES/django.po @@ -0,0 +1,2394 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Moira Brülisauer , 2012. +# , 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 12:20+0000\n" +"Last-Translator: moosline \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" + +#: global_settings.py:33 +msgid "German" +msgstr "Allemand" + +#: global_settings.py:34 +msgid "English" +msgstr "Anglais" + +#: global_settings.py:35 +msgid "French" +msgstr "Français" + +#: agenda/forms.py:28 +msgid "Parent item" +msgstr "Elément parent" + +#: agenda/models.py:34 config/forms.py:59 motion/forms.py:22 +#: motion/models.py:539 motion/templates/motion/view.html:246 +#: projector/models.py:29 +msgid "Title" +msgstr "Titre" + +#: agenda/models.py:35 motion/forms.py:23 motion/models.py:540 +#: motion/templates/motion/view.html:247 projector/models.py:30 +msgid "Text" +msgstr "Texte" + +#: agenda/models.py:36 agenda/templates/agenda/overview.html:65 +#: agenda/templates/agenda/view.html:13 participant/models.py:60 +#: participant/templates/participant/overview.html:72 +#: participant/templates/participant/user_detail.html:45 +msgid "Comment" +msgstr "Commentaire" + +#: agenda/models.py:37 +msgid "Closed" +msgstr "Fermé" + +#: agenda/models.py:38 agenda/templates/agenda/overview.html:71 +#: projector/models.py:31 +msgid "Weight" +msgstr "Pondération" + +#: agenda/models.py:171 +msgid "Can see agenda" +msgstr "Peut voir l'ordre du jour" + +#: agenda/models.py:172 +msgid "Can manage agenda" +msgstr "Peut gérer l'ordre du jour" + +#: agenda/models.py:180 agenda/slides.py:20 agenda/views.py:191 +#: agenda/views.py:192 agenda/views.py:212 agenda/views.py:226 +#: agenda/templates/agenda/base_agenda.html:10 +#: agenda/templates/agenda/overview.html:8 +#: agenda/templates/agenda/overview.html:52 +#: agenda/templates/agenda/overview.html:77 +#: agenda/templates/projector/AgendaSummary.html:6 +#: agenda/templates/projector/AgendaSummary.html:10 +msgid "Agenda" +msgstr "Ordre du jour" + +#: agenda/views.py:54 +msgid "You are not authorized to manage the agenda." +msgstr "Vous n'êtes pas autorisé à gérer l'ordre du jour." + +#: agenda/views.py:70 +msgid "Errors when reordering of the agenda" +msgstr "Erreurs en réorganisant de l'ordre du jour" + +#: agenda/views.py:131 +#, python-format +msgid "Item %s was successfully modified." +msgstr "Elément %s a été modifié avec succès." + +#: agenda/views.py:152 +#, python-format +msgid "Item %s was successfully created." +msgstr "Elément %s a été créé avec succès." + +#: agenda/views.py:169 +msgid "Yes, with all child items." +msgstr "Oui, avec tous les sous-éléments." + +#: agenda/views.py:177 +#, python-format +msgid "Item %s and his children were successfully deleted." +msgstr "Elément %s et ses sous-éléments ont été créés avec succès." + +#: agenda/views.py:182 +#, python-format +msgid "Item %s was successfully deleted." +msgstr "Elément %s a été supprimé avec succès." + +#: agenda/templates/agenda/base_agenda.html:12 +msgid "All items" +msgstr "Tous les éléments" + +#: agenda/templates/agenda/base_agenda.html:14 +#: agenda/templates/agenda/edit.html:10 agenda/templates/agenda/edit.html:18 +msgid "New item" +msgstr "Nouvel élément" + +#: agenda/templates/agenda/base_agenda.html:16 +msgid "Agenda as PDF" +msgstr "Ordre du jour en PDF" + +#: agenda/templates/agenda/base_agenda.html:26 +msgid "View item" +msgstr "Afficher l'élément" + +#: agenda/templates/agenda/base_agenda.html:31 +#: agenda/templates/agenda/edit.html:8 agenda/templates/agenda/edit.html:16 +#: agenda/templates/agenda/item_row.html:40 +msgid "Edit item" +msgstr "Modifier l'élément" + +#: agenda/templates/agenda/base_agenda.html:33 +#: agenda/templates/agenda/item_row.html:41 +msgid "Delete item" +msgstr "Supprimer l'élément" + +#: agenda/templates/agenda/base_agenda.html:38 +msgid "Show item" +msgstr "Afficher l'élément" + +#: agenda/templates/agenda/config.html:5 agenda/templates/agenda/config.html:8 +msgid "Agenda settings" +msgstr "Paramètres de l'ordre du jour" + +#: agenda/templates/agenda/config.html:13 agenda/templates/agenda/edit.html:24 +#: assignment/templates/assignment/config.html:13 +#: assignment/templates/assignment/edit.html:26 +#: assignment/templates/assignment/poll_view.html:66 +#: config/templates/config/general.html:56 +#: motion/templates/motion/config.html:13 motion/templates/motion/edit.html:26 +#: motion/templates/motion/poll_view.html:52 +#: participant/templates/participant/config.html:13 +#: participant/templates/participant/edit.html:31 +#: participant/templates/participant/group_edit.html:25 +#: participant/templates/participant/password_change.html:22 +#: participant/templates/participant/settings.html:22 +#: projector/templates/projector/new.html:13 +#: projector/templates/projector/select_widgets.html:22 +msgid "Save" +msgstr "Enregistrer" + +#: agenda/templates/agenda/config.html:17 agenda/templates/agenda/edit.html:31 +#: assignment/templates/assignment/config.html:17 +#: assignment/templates/assignment/edit.html:33 +#: assignment/templates/assignment/poll_view.html:73 +#: config/templates/config/general.html:60 +#: motion/templates/motion/config.html:17 motion/templates/motion/edit.html:33 +#: motion/templates/motion/import.html:30 +#: motion/templates/motion/poll_view.html:59 +#: participant/templates/participant/config.html:17 +#: participant/templates/participant/edit.html:38 +#: participant/templates/participant/group_edit.html:32 +#: participant/templates/participant/import.html:28 +#: participant/templates/participant/password_change.html:26 +#: participant/templates/participant/settings.html:26 +#: projector/templates/projector/new.html:20 +msgid "Cancel" +msgstr "Annuler" + +#: agenda/templates/agenda/edit.html:27 +#: assignment/templates/assignment/edit.html:29 +#: assignment/templates/assignment/poll_view.html:69 +#: assignment/templates/assignment/view.html:92 +#: motion/templates/motion/edit.html:29 +#: motion/templates/motion/poll_view.html:55 +#: participant/templates/participant/edit.html:34 +#: participant/templates/participant/group_edit.html:28 +#: projector/templates/projector/control_overlay_message.html:8 +#: projector/templates/projector/new.html:16 +msgid "Apply" +msgstr "Appliquer" + +#: agenda/templates/agenda/edit.html:35 +#: assignment/templates/assignment/edit.html:37 +#: motion/templates/motion/edit.html:37 motion/templates/motion/import.html:34 +#: participant/templates/participant/edit.html:42 +#: participant/templates/participant/group_edit.html:36 +#: participant/templates/participant/import.html:32 +msgid "required" +msgstr "requis" + +#: agenda/templates/agenda/item_row.html:8 +msgid "Mark item as done" +msgstr "Marquez l'élément comme terminé" + +#: agenda/templates/agenda/item_row.html:12 +msgid "Item closed" +msgstr "Elément fermé" + +#: agenda/templates/agenda/item_row.html:35 +#: agenda/templates/agenda/overview.html:86 +msgid "Activate item" +msgstr "Activez l'élément" + +#: agenda/templates/agenda/item_row.html:44 +#: agenda/templates/agenda/widget.html:35 +msgid "Activate summary for this item" +msgstr "Activez le résumé pour cet élément" + +#: agenda/templates/agenda/overview.html:45 +msgid "Do you want to save the changed order of agenda items?" +msgstr "Voulez-vous enregistrer les modifications de l'ordre du jour?" + +#: agenda/templates/agenda/overview.html:46 assignment/models.py:291 +#: assignment/views.py:587 assignment/templates/assignment/view.html:168 +#: assignment/templates/assignment/view.html:172 +#: assignment/templates/projector/Assignment.html:78 +#: assignment/templates/projector/Assignment.html:82 motion/models.py:574 +#: motion/views.py:830 motion/views.py:881 +#: motion/templates/motion/view.html:79 +#: motion/templates/projector/Motion.html:37 utils/utils.py:49 +#: utils/views.py:108 +msgid "Yes" +msgstr "Oui" + +#: agenda/templates/agenda/overview.html:47 assignment/models.py:291 +#: assignment/views.py:588 assignment/templates/assignment/view.html:169 +#: assignment/templates/projector/Assignment.html:79 motion/models.py:574 +#: motion/views.py:830 motion/views.py:882 +#: motion/templates/motion/view.html:80 +#: motion/templates/projector/Motion.html:38 utils/utils.py:49 +#: utils/views.py:108 +msgid "No" +msgstr "Non" + +#: agenda/templates/agenda/overview.html:55 +#: assignment/templates/assignment/overview.html:12 +#: motion/templates/motion/overview.html:12 +#: participant/templates/participant/overview.html:22 +msgid "Filter" +msgstr "Filtre" + +#: agenda/templates/agenda/overview.html:56 +msgid "Hide closed items" +msgstr "Cachez les élément terminés" + +#: agenda/templates/agenda/overview.html:59 +msgid "item" +msgid_plural "items" +msgstr[0] "élément" +msgstr[1] "éléments" + +#: agenda/templates/agenda/overview.html:62 +msgid "Done" +msgstr "Terminé" + +#: agenda/templates/agenda/overview.html:63 +msgid "Item" +msgstr "Elément" + +#: agenda/templates/agenda/overview.html:68 +#: assignment/templates/assignment/overview.html:28 +#: motion/templates/motion/overview.html:43 +#: participant/templates/participant/group_overview.html:14 +#: participant/templates/participant/overview.html:74 +msgid "Actions" +msgstr "Actions" + +#: agenda/templates/agenda/overview.html:102 +#: agenda/templates/agenda/widget.html:46 +#: projector/templates/projector/custom_slide_widget.html:36 +msgid "No items available." +msgstr "Pas d'éléments disponibles." + +#: agenda/templates/agenda/widget.html:10 +#: agenda/templates/agenda/widget.html:29 +#: assignment/templates/assignment/widget.html:17 +#: motion/templates/motion/widget.html:17 +#: participant/templates/participant/group_widget.html:17 +#: participant/templates/participant/user_widget.html:16 +#: projector/templates/projector/custom_slide_widget.html:11 +#: projector/templates/projector/custom_slide_widget.html:30 +msgid "Preview" +msgstr "Aperçu" + +#: agenda/templates/agenda/widget.html:23 +#: assignment/templates/assignment/view.html:132 +#: assignment/templates/assignment/widget.html:11 +#: motion/templates/motion/widget.html:11 +#: participant/templates/participant/group_widget.html:11 +#: participant/templates/participant/user_widget.html:10 +#: projector/templates/projector/custom_slide_widget.html:24 +msgid "Delete" +msgstr "Supprimer" + +#: agenda/templates/agenda/widget.html:26 +#: assignment/templates/assignment/view.html:131 +#: assignment/templates/assignment/widget.html:14 +#: motion/templates/motion/widget.html:14 +#: participant/templates/participant/group_widget.html:14 +#: participant/templates/participant/user_widget.html:13 +#: projector/templates/projector/custom_slide_widget.html:27 +msgid "Edit" +msgstr "Modifier" + +#: assignment/forms.py:24 assignment/models.py:51 assignment/views.py:381 +#: assignment/templates/assignment/view.html:13 +#: assignment/templates/projector/Assignment.html:21 +msgid "Number of available posts" +msgstr "Nombre des postes disponibles" + +#: assignment/forms.py:34 +msgid "Nominate a participant" +msgstr "Désigner un participant" + +#: assignment/forms.py:41 +msgid "Only publish voting results for selected winners (Projector view only)" +msgstr "" +"Publier seulement les résultat du candidat qui a gagné l'election " +"(Uniquement sur la vue projecteur)" + +#: assignment/forms.py:46 motion/forms.py:77 +msgid "Number of ballot papers (selection)" +msgstr "Nombre de bulletins de vote (sélection)" + +#: assignment/forms.py:48 motion/forms.py:79 +msgid "Number of all delegates" +msgstr "Nombre de délégués" + +#: assignment/forms.py:49 motion/forms.py:80 +msgid "Number of all participants" +msgstr "Nombre de participants" + +#: assignment/forms.py:50 motion/forms.py:81 +msgid "Use the following custom number" +msgstr "Utilisez le nombre personnalisé suivant" + +#: assignment/forms.py:55 motion/forms.py:88 +msgid "Custom number of ballot papers" +msgstr "Nombre personnalisé de bulletins de vote" + +#: assignment/forms.py:59 +msgid "Title for PDF document (all elections)" +msgstr "Titre du fichier PDF (toutes les élections)" + +#: assignment/forms.py:63 +msgid "Preamble text for PDF document (all elections)" +msgstr "Texte de préambule pour le fichier PDF (toutes les élections)" + +#: assignment/forms.py:67 +msgid "Election method" +msgstr "Mode d'élection" + +#: assignment/forms.py:69 +msgid "Automatic assign of method." +msgstr "Assignation automatique de la méthode." + +#: assignment/forms.py:70 +msgid "Always one option per candidate." +msgstr "Toujours une option par candidat" + +#: assignment/forms.py:71 +msgid "Always Yes-No-Abstain per candidate." +msgstr "Toujours Oui-Non-Abstention par candidat." + +#: assignment/models.py:44 assignment/templates/assignment/overview.html:15 +#: assignment/templates/assignment/view.html:23 +msgid "Searching for candidates" +msgstr "Recherche de candidats" + +#: assignment/models.py:45 assignment/templates/assignment/overview.html:16 +#: assignment/templates/assignment/view.html:25 +msgid "Voting" +msgstr "Vote" + +#: assignment/models.py:46 assignment/templates/assignment/overview.html:17 +#: assignment/templates/assignment/view.html:27 +msgid "Finished" +msgstr "Terminé" + +#: assignment/models.py:49 +msgid "Name" +msgstr "Nom" + +#: assignment/models.py:50 participant/models.py:144 +msgid "Description" +msgstr "Description" + +#: assignment/models.py:54 +msgid "Comment on the ballot paper" +msgstr "Commentaire sur le bulletin de vote" + +#: assignment/models.py:64 motion/models.py:332 +#, python-format +msgid "%s is not a valid status." +msgstr "%s est un statut invalide." + +#: assignment/models.py:67 +#, python-format +msgid "The assignment status is already %s." +msgstr "Le statut d'assignation est déjà sur %s" + +#: assignment/models.py:80 +#, python-format +msgid "%s is already a candidate." +msgstr "%s est déja un candidat." + +#: assignment/models.py:82 assignment/views.py:195 +msgid "The candidate list is already closed." +msgstr "La liste des candidats est déjà fermée" + +#: assignment/models.py:89 +#, python-format +msgid "%s does not want to be a candidate." +msgstr "%s ne veut pas etre un candidat" + +#: assignment/models.py:103 +#, python-format +msgid "%s is no candidate" +msgstr "%s n'est pas un candidat" + +#: assignment/models.py:244 +msgid "Can see assignment" +msgstr "Peut voir l'assignation" + +#: assignment/models.py:246 +msgid "Can nominate another person" +msgstr "Peut nonimer une autre personne" + +#: assignment/models.py:247 +msgid "Can nominate themselves" +msgstr "Peut se nominer soi-même" + +#: assignment/models.py:248 +msgid "Can manage assignment" +msgstr "Peut gérer l'assignation" + +#: assignment/models.py:291 motion/models.py:574 +msgid "Abstain" +msgstr "Abstention" + +#: assignment/models.py:293 motion/templates/motion/poll_view.html:22 +msgid "Votes" +msgstr "Votes" + +#: assignment/models.py:310 +#, python-format +msgid "Ballot %d" +msgstr "Vote %d" + +#: assignment/models.py:319 assignment/views.py:336 assignment/views.py:675 +#: assignment/views.py:690 +#: assignment/templates/assignment/base_assignment.html:14 +#: assignment/templates/assignment/overview.html:6 +#: assignment/templates/assignment/overview.html:9 +msgid "Elections" +msgstr "Elections" + +#: assignment/views.py:80 +#, python-format +msgid "Candidate %s was nominated successfully." +msgstr "Le candidat %s a été nominé avec succès. " + +#: assignment/views.py:122 +msgid "New election was successfully created." +msgstr "La nouvelle élection a été créée avec succès." + +#: assignment/views.py:124 +msgid "Election was successfully modified." +msgstr "L'élection a été modifiée avec succès." + +#: assignment/views.py:130 motion/views.py:245 motion/views.py:689 +#: participant/views.py:508 participant/views.py:531 utils/views.py:222 +#: utils/views.py:240 utils/views.py:264 +msgid "Please check the form for errors." +msgstr "S'il vous plaît, vérifier si il a des erreurs dans le formulaire." + +#: assignment/views.py:149 +#, python-format +msgid "Election %s was successfully deleted." +msgstr "L'election %s a été supprimée avec succès." + +#: assignment/views.py:162 +#, python-format +msgid "Election status was set to: %s." +msgstr "Le statut de l'élection a été changé sur %s." + +#: assignment/views.py:175 +msgid "You have set your candidature successfully." +msgstr "Vous avez inséré votre candidature avec succès." + +#: assignment/views.py:192 +msgid "" +"You have withdrawn your candidature successfully. You can not be nominated " +"by other participants anymore." +msgstr "" +"Vous avez retiré votre candidature avec succès. On ne peut plus vous " +"nominer comme candidat" + +#: assignment/views.py:213 +#, python-format +msgid "Candidate %s was withdrawn successfully." +msgstr "Le candidat %s a été rejeté avec succès." + +#: assignment/views.py:215 +#, python-format +msgid "%s was unblocked successfully." +msgstr "Le candidat %s a été nominé avec succès." + +#: assignment/views.py:219 +#, python-format +msgid "Do you really want to withdraw %s from the election?" +msgstr "Voulez-vous vraiment exclure %s de cette élection?" + +#: assignment/views.py:221 +#, python-format +msgid "Do you really want to unblock %s for the election?" +msgstr "" +"Voulez-vous vraiment permettre %s de participer à nouveau à cette " +"élection?" + +#: assignment/views.py:236 +msgid "New ballot was successfully created." +msgstr "Le nouveau vote a été créé avec succès" + +#: assignment/views.py:268 +#, python-format +msgid "Ballot ID %d does not exist." +msgstr "L'identifiant %d de vote n'existe pas." + +#: assignment/views.py:275 +msgid "Ballot successfully published." +msgstr "Le vote a été publié avec succès." + +#: assignment/views.py:277 +msgid "Ballot successfully unpublished." +msgstr "Le vote a été retiré avec succès." + +#: assignment/views.py:290 +msgid "not elected" +msgstr "non élu" + +#: assignment/views.py:293 assignment/views.py:482 +#: assignment/templates/assignment/view.html:48 +msgid "elected" +msgstr "élu" + +#: assignment/views.py:321 +msgid "Ballot was successfully deleted." +msgstr "Le vote a été supprimé avec succès." + +#: assignment/views.py:333 +msgid "Assignment" +msgstr "Assignation" + +#: assignment/views.py:356 assignment/templates/assignment/overview.html:59 +#: assignment/templates/assignment/widget.html:23 +msgid "No assignments available." +msgstr "Aucune assignation disponible." + +#: assignment/views.py:375 +#, python-format +msgid "Election: %s" +msgstr "Election: %s" + +#: assignment/views.py:388 assignment/views.py:424 +#: assignment/templates/assignment/overview.html:26 +#: assignment/templates/assignment/poll_view.html:18 +#: assignment/templates/assignment/view.html:37 +#: assignment/templates/assignment/view.html:120 +#: assignment/templates/projector/Assignment.html:38 +#: assignment/templates/projector/Assignment.html:56 +msgid "Candidates" +msgstr "Candidats" + +#: assignment/views.py:413 motion/views.py:823 +#: motion/templates/motion/view.html:44 +msgid "Vote results" +msgstr "Résultat du vote" + +#: assignment/views.py:417 +#: assignment/templates/assignment/base_assignment.html:71 +#: assignment/templates/assignment/poll_view.html:5 +#: assignment/templates/assignment/poll_view.html:8 +#: assignment/templates/assignment/view.html:123 +#: assignment/templates/projector/Assignment.html:59 +msgid "ballot" +msgstr "vote" + +#: assignment/views.py:420 +msgid "ballots" +msgstr "votes" + +#: assignment/views.py:445 +#, python-format +msgid "" +"Y: %(YES)s\n" +"N: %(NO)s\n" +"A: %(ABSTAIN)s" +msgstr "" +"O: %(YES)s\n" +"N: %(NO)s\n" +"A: %(ABSTAIN)s" + +#: assignment/views.py:456 assignment/templates/assignment/poll_view.html:35 +#: assignment/templates/assignment/view.html:186 +#: assignment/templates/projector/Assignment.html:96 +#: motion/templates/motion/poll_view.html:31 +msgid "Invalid votes" +msgstr "Votes invalides" + +#: assignment/views.py:463 assignment/templates/assignment/poll_view.html:45 +#: assignment/templates/assignment/view.html:202 +#: assignment/templates/assignment/view.html:207 +#: assignment/templates/projector/Assignment.html:109 +#: assignment/templates/projector/Assignment.html:115 motion/views.py:830 +#: motion/templates/motion/poll_view.html:35 +#: motion/templates/motion/view.html:84 +#: motion/templates/projector/Motion.html:42 poll/models.py:76 +msgid "Votes cast" +msgstr "Nombre de votants" + +#: assignment/views.py:523 assignment/views.py:541 +#: assignment/templates/assignment/overview.html:25 +#: assignment/templates/assignment/poll_view.html:5 +#: assignment/templates/assignment/view.html:6 +#: assignment/templates/projector/Assignment.html:27 +msgid "Election" +msgstr "Election" + +#: assignment/views.py:548 +#, python-format +msgid "%d. ballot" +msgstr "%d. vote" + +#: assignment/views.py:550 +#, python-format +msgid "%d candidate" +msgid_plural "%d candidates" +msgstr[0] "%d candidat" +msgstr[1] "%d candidats" + +#: assignment/views.py:552 +#, python-format +msgid "%d available post" +msgid_plural "%d available posts" +msgstr[0] "%d poste disponible" +msgstr[1] "%d postes disponibles" + +#: assignment/views.py:588 assignment/templates/assignment/view.html:170 +#: assignment/templates/projector/Assignment.html:80 motion/views.py:830 +#: motion/views.py:883 motion/templates/motion/view.html:81 +#: motion/templates/projector/Motion.html:39 +msgid "Abstention" +msgstr "Abstention" + +#: assignment/views.py:668 +msgid "Election settings successfully saved." +msgstr "Les paramètres de l'élection ont été enregistrés avec succès." + +#: assignment/templates/assignment/base_assignment.html:16 +msgid "All elections" +msgstr "Toutes les élections" + +#: assignment/templates/assignment/base_assignment.html:18 +#: assignment/templates/assignment/edit.html:10 +#: assignment/templates/assignment/edit.html:19 +msgid "New election" +msgstr "Nouvelle élection" + +#: assignment/templates/assignment/base_assignment.html:20 +msgid "All elections as PDF" +msgstr "Toutes les élections en PDF" + +#: assignment/templates/assignment/base_assignment.html:31 +msgid "View election" +msgstr "Afficher l'élection" + +#: assignment/templates/assignment/base_assignment.html:39 +#: assignment/templates/assignment/edit.html:8 +#: assignment/templates/assignment/edit.html:17 +#: assignment/templates/assignment/overview.html:50 +msgid "Edit election" +msgstr "Modifier l'élection" + +#: assignment/templates/assignment/base_assignment.html:44 +#: assignment/templates/assignment/overview.html:51 +msgid "Delete election" +msgstr "Supprimer l'élection" + +#: assignment/templates/assignment/base_assignment.html:50 +#: assignment/templates/assignment/overview.html:53 +msgid "Election as PDF" +msgstr "L'élection en PDF" + +#: assignment/templates/assignment/base_assignment.html:56 +msgid "Show election" +msgstr "Projeter l'élection" + +#: assignment/templates/assignment/base_assignment.html:63 +#: motion/templates/motion/base_motion.html:61 +msgid "New agenda item" +msgstr "Nouveau point dans l'ordre du jour" + +#: assignment/templates/assignment/config.html:5 +#: assignment/templates/assignment/config.html:8 +msgid "Election settings" +msgstr "Paramètres de l'élection" + +#: assignment/templates/assignment/overview.html:14 +#: assignment/templates/assignment/overview.html:27 +#: assignment/templates/assignment/view.html:11 +#: assignment/templates/projector/Assignment.html:18 motion/views.py:801 +#: motion/templates/motion/overview.html:20 +#: motion/templates/motion/overview.html:40 +#: motion/templates/motion/view.html:34 +#: motion/templates/projector/Motion.html:11 +#: participant/templates/participant/overview.html:52 +msgid "Status" +msgstr "Statut" + +#: assignment/templates/assignment/overview.html:22 +msgid "election" +msgid_plural "elections" +msgstr[0] "élection" +msgstr[1] "élections" + +#: assignment/templates/assignment/overview.html:35 +#, python-format +msgctxt "Number of searched candidates for an election" +msgid "posts: %(posts)s" +msgstr "postes: %(posts)s" + +#: assignment/templates/assignment/overview.html:37 +#, python-format +msgid "candidates: %(candidates)s" +msgstr "candidats: %(candidates)s" + +#: assignment/templates/assignment/overview.html:39 +#, python-format +msgid "elected: %(elected)s" +msgstr "élus: %(elected)s" + +#: assignment/templates/assignment/overview.html:45 +msgid "Activate election" +msgstr "Activer l'élection" + +#: assignment/templates/assignment/poll_view.html:10 +msgid "Short description (for ballot paper)" +msgstr "Une courte déscription (pour le bulletin de vote)" + +#: assignment/templates/assignment/poll_view.html:12 +#: motion/templates/motion/poll_view.html:14 +msgid "Special values" +msgstr "Valeurs spéciales" + +#: assignment/templates/assignment/poll_view.html:12 +#: motion/templates/motion/poll_view.html:14 poll/models.py:234 +msgid "majority" +msgstr "majorité" + +#: assignment/templates/assignment/poll_view.html:12 +#: motion/templates/motion/poll_view.html:14 poll/models.py:236 +#: poll/models.py:238 +msgid "undocumented" +msgstr "non documenté" + +#: assignment/templates/assignment/poll_view.html:59 +#: motion/templates/motion/poll_view.html:45 +msgid "Ballot paper as PDF" +msgstr "bulletin de vote en PDF" + +#: assignment/templates/assignment/view.html:21 +msgid "Change status" +msgstr "Modifier le statut" + +#: assignment/templates/assignment/view.html:44 +#: assignment/templates/assignment/view.html:106 +msgid "Remove candidate" +msgstr "Enlever le candidat" + +#: assignment/templates/assignment/view.html:51 +msgid "Mark candidate as not elected" +msgstr "Marquer le candidat comme pas elu." + +#: assignment/templates/assignment/view.html:57 +#: assignment/templates/projector/Assignment.html:44 +msgid "No candidates available." +msgstr "Aucun candidat n'est disponible" + +#: assignment/templates/assignment/view.html:69 +msgid "Withdraw self candidature" +msgstr "Retirer sa propre candidature" + +#: assignment/templates/assignment/view.html:75 +msgid "Self candidature" +msgstr "Se proposer comme candidat" + +#: assignment/templates/assignment/view.html:86 +msgid "Add new participant" +msgstr "Ajouter un nouveau participant" + +#: assignment/templates/assignment/view.html:102 +msgid "Blocked Candidates" +msgstr "Candidats bloqués" + +#: assignment/templates/assignment/view.html:109 +msgid "No blocked candidates available." +msgstr "Aucun candidat n'est disponible" + +#: assignment/templates/assignment/view.html:115 +#: assignment/templates/projector/Assignment.html:52 +msgid "Election results" +msgstr "Résultat des élections" + +#: assignment/templates/assignment/view.html:128 +msgid "Publish/unpublish results" +msgstr "Publier/dépublier les résultats" + +#: assignment/templates/assignment/view.html:140 +#: assignment/templates/assignment/view.html:223 +msgid "New ballot" +msgstr "Nouveau vote" + +#: assignment/templates/assignment/view.html:155 +#: assignment/templates/projector/Assignment.html:69 +msgid "Candidate is elected" +msgstr "Le candidat est élu" + +#: assignment/templates/assignment/view.html:174 +#: assignment/templates/projector/Assignment.html:84 +msgid "was not a
    candidate" +msgstr "n'était pas un
    candidat" + +#: assignment/templates/assignment/view.html:191 +#: assignment/templates/projector/Assignment.html:100 motion/views.py:830 +#: motion/templates/motion/view.html:82 +#: motion/templates/projector/Motion.html:40 +msgid "Invalid" +msgstr "Invalide" + +#: assignment/templates/assignment/view.html:219 +msgid "No results available." +msgstr "Aucun resultat n'est disponible" + +#: config/forms.py:22 +msgid "Event name" +msgstr "Nom de l'événement" + +#: config/forms.py:28 +msgid "Short description of event" +msgstr "Une courte description de l'événement" + +#: config/forms.py:36 +msgid "Event date" +msgstr "Date de l'événement" + +#: config/forms.py:42 +msgid "Event location" +msgstr "Lieu de l'événement" + +#: config/forms.py:48 +msgid "Event organizer" +msgstr "Organisateur de l'événement" + +#: config/forms.py:53 +msgid "Allow access for anonymous guest users" +msgstr "Permettre l'accès anomyme" + +#: config/forms.py:65 participant/forms.py:123 +msgid "Welcome text" +msgstr "Texte de bienvenue" + +#: config/models.py:39 +msgid "Can manage configuration" +msgstr "Peut gérer la configuration" + +#: config/models.py:83 +msgid "Presentation and assembly system" +msgstr "Système de présentation et d'assemblée" + +#: config/models.py:88 +msgid "Welcome to OpenSlides" +msgstr "Bienvenue sur OpenSlides!" + +#: config/models.py:89 +msgid "[Place for your welcome text.]" +msgstr "[Insérer votre texte ici.]" + +#: config/models.py:102 +msgid "General" +msgstr "Général" + +#: config/models.py:126 config/templates/config/version.html:5 +#: config/templates/config/version.html:8 +#: config/templates/config/version.html:11 motion/views.py:815 +#: motion/templates/motion/view.html:214 motion/templates/motion/view.html:244 +msgid "Version" +msgstr "Version" + +#: config/views.py:69 +msgid "General settings successfully saved." +msgstr "Les paramètres généraux ont été enregistrés avec succès." + +#: config/views.py:110 config/templates/config/base_config.html:7 +msgid "Configuration" +msgstr "Configuration" + +#: config/templates/config/general.html:5 +#: config/templates/config/general.html:8 +msgid "General settings" +msgstr "Paramètres généraux" + +#: config/templates/config/general.html:11 +msgid "Event" +msgstr "Evénement" + +#: config/templates/config/general.html:26 +msgid "Welcome Widget" +msgstr "Widget de bienvenue" + +#: config/templates/config/general.html:41 +msgid "System" +msgstr "Système" + +#: motion/forms.py:25 motion/models.py:541 motion/views.py:849 +#: motion/templates/motion/view.html:229 motion/templates/motion/view.html:249 +#: motion/templates/projector/Motion.html:77 +msgid "Reason" +msgstr "Motivation" + +#: motion/forms.py:30 +msgid "Trivial change" +msgstr "Changement trivial" + +#: motion/forms.py:31 +msgid "Trivial changes don't create a new version." +msgstr "Des changement triviaux ne créent pas une nouvelle version." + +#: motion/forms.py:35 motion/models.py:63 motion/views.py:768 +#: motion/templates/motion/overview.html:41 +#: motion/templates/motion/view.html:18 +#: motion/templates/projector/Motion.html:55 +msgid "Submitter" +msgstr "Requérant" + +#: motion/forms.py:44 motion/views.py:788 motion/templates/motion/view.html:22 +msgid "Supporters" +msgstr "Partisants" + +#: motion/forms.py:50 participant/forms.py:111 +msgid "CSV File" +msgstr "Fichier CSV" + +#: motion/forms.py:54 +msgid "Import motions with status \"authorized\"" +msgstr "Importer les motions avec le statut de \"autorisé\"" + +#: motion/forms.py:55 +msgid "Set the initial status for each motion to \"authorized\"" +msgstr "Définir chaque motion à son statut initial de \"autorisé\" " + +#: motion/forms.py:63 +msgid "Number of (minimum) required supporters for a motion" +msgstr "Minimum de personnes nécessaire au soutient d'une motion" + +#: motion/forms.py:67 +msgid "Choose 0 to disable the supporting system" +msgstr "Choisissez 0 pour desactiver le système de soutien" + +#: motion/forms.py:72 +msgid "Motion preamble" +msgstr "Préambule de la motion" + +#: motion/forms.py:93 +msgid "Title for PDF document (all motions)" +msgstr "Titre pour le document PDF (toutes les motions)" + +#: motion/forms.py:98 +msgid "Preamble text for PDF document (all motions)" +msgstr "Texte de préambule pour le document PDF (toutes les motions)" + +#: motion/forms.py:102 +msgid "Allow trivial changes" +msgstr "Pemettre des changements triviaux" + +#: motion/forms.py:103 +msgid "Warning: Trivial changes undermine the motions autorisation system." +msgstr "" +"Attention: Les changements triviaux perturbent le système d'autorisation de " +"motion." + +#: motion/models.py:42 +msgid "Published" +msgstr "Publié" + +#: motion/models.py:43 +msgid "Permitted" +msgstr "Permis" + +#: motion/models.py:44 motion/templates/motion/overview.html:24 +#: motion/templates/motion/view.html:167 +msgid "Accepted" +msgstr "Accepté" + +#: motion/models.py:45 motion/templates/motion/overview.html:25 +#: motion/templates/motion/view.html:172 +msgid "Rejected" +msgstr "Rejeté" + +#: motion/models.py:46 +msgid "Withdrawed" +msgstr "Retiré" + +#: motion/models.py:47 motion/templates/motion/view.html:180 +msgid "Adjourned" +msgstr "Ajourné" + +#: motion/models.py:48 motion/templates/motion/view.html:183 +msgid "Not Concerned" +msgstr "Non concerné" + +#: motion/models.py:49 motion/templates/motion/view.html:186 +msgid "Commited a bill" +msgstr "Transféré à (une commission)" + +#: motion/models.py:50 +msgid "Rejected (not authorized)" +msgstr "Rejeté (non autorisé)" + +#: motion/models.py:51 motion/templates/motion/overview.html:27 +msgid "Needs Review" +msgstr "Doit être revu" + +#: motion/models.py:100 +#, python-format +msgid "Version %d authorized" +msgstr "Version %d autorisée" + +#: motion/models.py:107 +#, python-format +msgctxt "Rejected means not authorized" +msgid "Version %d rejected" +msgstr "Version %d rejetée" + +#: motion/models.py:136 +msgid "Searching for supporters." +msgstr "Recherche de soutien." + +#: motion/models.py:138 +msgid "Not yet authorized." +msgstr "N'est pas encore autorisé." + +#: motion/models.py:140 +msgid "Not yet authorized changes." +msgstr "Les changements ne sont pas encore approuvés." + +#: motion/models.py:223 +#, python-format +msgid "" +"Trivial changes to version %(version)d; changed fields: %(changed_fields)s" +msgstr "" +"Changements triviaux à la version %(version)d; les champs modifiés: " +"%(changed_fields)s" + +#: motion/models.py:235 +#, python-format +msgid "Version %s created" +msgstr "Version %s créée" + +#: motion/models.py:244 +msgid "Supporters removed" +msgstr "Tous les partisans supprimés" + +#: motion/models.py:253 +#, python-format +msgid "Status reseted to: %s" +msgstr "Statut remis à %s" + +#: motion/models.py:265 +#, python-format +msgid "Supporter: +%s" +msgstr "Partisants: +%s" + +#: motion/models.py:278 +#, python-format +msgid "Supporter: -%s" +msgstr "Partisants: -%s" + +#: motion/models.py:294 +#, python-format +msgid "Number set: %s" +msgstr "Numéro inséré: %s" + +#: motion/models.py:307 +#, python-format +msgid "Version %s authorized" +msgstr "Version %s autorisée" + +#: motion/models.py:319 +#, python-format +msgid "Version %s not authorized" +msgstr "Version %s non autorisée" + +#: motion/models.py:335 +#, python-format +msgid "The motion status is already '%s.'" +msgstr "La motion a déja le statut '%s'." + +#: motion/models.py:343 +#, python-format +msgid "" +"The motion status is: '%(currentstatus)s'. You can not set the status to " +"'%(newstatus)s'." +msgstr "" +"Le statut de la motion est: '%(currentstatus)s'. Vous ne pouvez pas le " +"changer le statut sur '%(newstatus)s'." + +#: motion/models.py:351 +msgid "Status modified" +msgstr "Statut modifié" + +#: motion/models.py:443 motion/models.py:445 +msgid "by" +msgstr "par" + +#: motion/models.py:453 motion/templates/motion/view.html:210 +#: motion/templates/motion/widget.html:27 +#: motion/templates/projector/Motion.html:65 +#: participant/templates/participant/personal_info_widget.html:13 +#: participant/templates/participant/personal_info_widget.html:32 +msgid "no number" +msgstr "pas de nombre" + +#: motion/models.py:454 motion/templates/motion/widget.html:23 +#: participant/templates/participant/personal_info_widget.html:9 +#: participant/templates/participant/personal_info_widget.html:28 +msgid "motion" +msgstr "motion" + +#: motion/models.py:479 +msgid "Poll created" +msgstr "Sondage créé" + +#: motion/models.py:530 +msgid "Can see motions" +msgstr "Peut voir les motions" + +#: motion/models.py:531 +msgid "Can create motions" +msgstr "Peut créer des motions" + +#: motion/models.py:532 +msgid "Can support motions" +msgstr "Peut soutenir les motions" + +#: motion/models.py:533 +msgid "Can manage motions" +msgstr "Peut gérer les motions" + +#: motion/models.py:600 +msgid "The assembly may decide," +msgstr "Je demande a l'Assemblée de décider sur " + +#: motion/models.py:603 motion/views.py:718 motion/views.py:943 +#: motion/views.py:954 motion/templates/motion/base_motion.html:9 +#: motion/templates/motion/overview.html:7 +#: motion/templates/motion/overview.html:10 +msgid "Motions" +msgstr "Motions" + +#: motion/views.py:173 +msgid "You have not the necessary rights to create or edit motions." +msgstr "Vous n'avez pas l'autorisation de créer ou modifier des motions." + +#: motion/views.py:178 +msgid "You can not edit this motion." +msgstr "Vous ne pouvez pas modifier cette motion." + +#: motion/views.py:236 +msgid "New motion was successfully created." +msgstr "La nouvelle motion a été créée avec succès." + +#: motion/views.py:238 +msgid "Motion was successfully modified." +msgstr "La motion a été modifiée avec succès." + +#: motion/views.py:252 +msgid "" +"Attention: Do you really want to edit this motion? The supporters will " +"not be removed automatically because you can manage motions. Please " +"check if the supports are valid after your changing!" +msgstr "" +"Attention: Voulez vous vraiment modifier cette motion? Les partisants ne " +"seront pas automatiquement supprimés car vous avez l'autorisation de " +"gérer les motions. S'il vous plait vérifiez si les partisans sont valables " +"après votre modification!" + +#: motion/views.py:254 +#, python-format +msgid "" +"Attention: Do you really want to edit this motion? All %s supporters " +"will be removed! Try to convince the supporters again." +msgstr "" +"Attention: Voulez-vous vraiment modifier cette motion? Tout les %s " +"partisans seront supprimés! Essayez de convaincre les partisans à nouveau." + +#: motion/views.py:286 +msgid "Motion number was successfully set." +msgstr "Le numéro de la motion a été mis avec succès." + +#: motion/views.py:302 +msgid "Motion was successfully authorized." +msgstr "La motion a été autorisée avec succès." + +#: motion/views.py:317 +msgid "Motion was successfully rejected." +msgstr "La motion a été rejetée avec succès. " + +#: motion/views.py:333 +#, python-format +msgid "Motion status was set to: %s." +msgstr "Le statut de la motion a été changé sur: %s" + +#: motion/views.py:349 +msgid "Motion status was reset." +msgstr "Le statut de la motion a été remis à zéro." + +#: motion/views.py:376 +msgid "You can not support this motion." +msgstr "Vous ne pouvez pas soutenir cette motion. " + +#: motion/views.py:379 +msgid "You can not unsupport this motion." +msgstr "Vous ne pouvez pas retirer votre soutien pour cette motion. " + +#: motion/views.py:390 +msgid "Do you really want to support this motion?" +msgstr "Voulez-vous vraiment soutenir cette motion?" + +#: motion/views.py:392 +msgid "Do you really want to unsupport this motion?" +msgstr "Voulez-vous vraiment retirer votre soutien pour cette motion?" + +#: motion/views.py:403 +msgid "You have supported this motion successfully." +msgstr "" +"Vous soutenez maintenant cette motion, vous avez été ajouté avec succès." + +#: motion/views.py:405 +msgid "You have unsupported this motion successfully." +msgstr "Vous avez retiré votre soutien pour cette motion avec succès. " + +#: motion/views.py:419 +msgid "New vote was successfully created." +msgstr "Le nouveau vote a été créé avec succès." + +#: motion/views.py:435 +msgid "Poll deleted" +msgstr "Sondage supprimé" + +#: motion/views.py:436 +msgid "Poll was successfully deleted." +msgstr "Le sondage a été supprimé avec succès." + +#: motion/views.py:438 +#, python-format +msgid "the %s. poll" +msgstr "le %s. sondage" + +#: motion/views.py:479 motion/views.py:488 +#, python-format +msgid "You can not delete motion %s." +msgstr "Vous ne pouvez pas supprimer la motion %s." + +#: motion/views.py:484 motion/views.py:492 +#, python-format +msgid "Motion %s was successfully deleted." +msgstr "La motion %s a été supprimée avec succès." + +#: motion/views.py:494 +msgid "Invalid request" +msgstr "Demande invalide" + +#: motion/views.py:518 +msgid "Poll was updated" +msgstr "Le sondage a été actualisé" + +#: motion/views.py:535 +#, python-format +msgid "Version %s accepted." +msgstr "Version %s acceptée." + +#: motion/views.py:537 +#, python-format +msgid "Do you really want to authorize version %s?" +msgstr "Voulez-vous vraiment autoriser cette version %s" + +#: motion/views.py:547 +#, python-format +msgid "Version %s rejected." +msgstr "Version %s rejetée." + +#: motion/views.py:549 +msgid "ERROR by rejecting the version." +msgstr "ERREUR en rejetant la version." + +#: motion/views.py:551 +#, python-format +msgid "Do you really want to reject version %s?" +msgstr "Voulez vous vraiment rejeter la version %s?" + +#: motion/views.py:587 motion/views.py:591 motion/views.py:597 +#: motion/views.py:600 participant/api.py:81 +#, python-format +msgid "Ignoring malformed line %d in import file." +msgstr "La ligne %d mal formée dans le fichier d'entrée a été ignorée" + +#: motion/views.py:608 +#, python-format +msgid "Ignoring line %d because the assigned group may not act as a person." +msgstr "" +"La ligne %d a été ignorée car le group assigné ne peut agir en tant que " +"personne." + +#: motion/views.py:617 +msgid "Created by motion import." +msgstr "Motion a été créé par l'import" + +#: motion/views.py:631 +#, python-format +msgid "" +"Ignoring line %d because it contains an incomplete first / last name pair." +msgstr "La ligne %d a été ignorée car le nom ou prénom sont incomplets" + +#: motion/views.py:669 +#, python-format +msgid "%d motion was successfully imported." +msgid_plural "%d motions were successfully imported." +msgstr[0] "%d la motion a été importée avec succès." +msgstr[1] "%d les motions ont été importées avec succès." + +#: motion/views.py:672 +#, python-format +msgid "%d motion was successfully modified." +msgid_plural "%d motions were successfully modified." +msgstr[0] "%d la motion a été modifiée avec succès." +msgstr[1] "%d les motions ont été modifiées avec succès." + +#: motion/views.py:675 +#, python-format +msgid "%d new user was added." +msgid_plural "%d new users were added." +msgstr[0] "%d nouvel utilisateur a été créé." +msgstr[1] "%d nouveaux utilisateurs ont été créés." + +#: motion/views.py:678 +#, python-format +msgid "%d new group was added." +msgid_plural "%d new groups were added." +msgstr[0] "%d le nouveau groupe a été ajouter" +msgstr[1] "%d nouveaux groupes ont été ajouter" + +#: motion/views.py:681 +#, python-format +msgid "%d group assigned to motions." +msgid_plural "%d groups assigned to motions." +msgstr[0] "%d groupe a été assigné aux motions." +msgstr[1] "%d groupes ont été assignés aux motions." + +#: motion/views.py:685 participant/api.py:97 +msgid "Import aborted because of severe errors in the input file." +msgstr "" +"l'importation a été interrompue en raison d'erreurs graves dans le fichier " +"d'entrée" + +#: motion/views.py:687 participant/api.py:99 +msgid "Import file has wrong character encoding, only UTF-8 is supported!" +msgstr "" +"Le fichier d'entrée a un mauvais encodage des caractères, seul UTF-8 est " +"pris en charge!" + +#: motion/views.py:691 +msgid "" +"Attention: Existing motions will be modified if you import new motions with " +"the same number." +msgstr "" +"Attention: Les motions existantes seront modifiées si vous importez de " +"nouvelles motions avec le même numéro." + +#: motion/views.py:692 +msgid "" +"Attention: Importing an motions without a number multiple times will create " +"duplicates." +msgstr "" +"Attention: L'importation d'une motion sans numéro à plusieurs reprises " +"créera des doublons." + +#: motion/views.py:725 motion/views.py:863 +#: motion/templates/motion/poll_view.html:7 +#: motion/templates/motion/poll_view.html:12 +#: motion/templates/motion/view.html:7 motion/templates/motion/view.html:206 +#: motion/templates/motion/view.html:225 +#: motion/templates/projector/Motion.html:7 +#: motion/templates/projector/Motion.html:65 +msgid "Motion" +msgstr "Motion" + +#: motion/views.py:739 motion/templates/motion/overview.html:84 +msgid "No motions available." +msgstr "Aucune motion disponible." + +#: motion/views.py:744 motion/views.py:746 motion/views.py:761 +#: motion/views.py:763 motion/templates/motion/base_motion.html:24 +#: motion/templates/projector/Motion.html:63 +msgid "Motion No." +msgstr "Motion No." + +#: motion/views.py:778 +msgid "Signature" +msgstr "Signature" + +#: motion/views.py:829 motion/templates/motion/base_motion.html:55 +#: motion/templates/motion/poll_view.html:8 +#: motion/templates/motion/poll_view.html:13 +#: motion/templates/motion/view.html:66 motion/templates/motion/view.html:74 +#: motion/templates/projector/Motion.html:33 +msgid "Vote" +msgstr "Vote" + +#: motion/views.py:863 +msgid "Poll" +msgstr "Sondage" + +#: motion/views.py:877 +#, python-format +msgid "Motion No. %s" +msgstr "Motion No %s" + +#: motion/views.py:879 +#, python-format +msgid "%d. Vote" +msgstr "%d. Vote" + +#: motion/views.py:936 +msgid "Motion settings successfully saved." +msgstr "Les paramètres des motions ont été enregistrés avec succès" + +#: motion/templates/motion/base_motion.html:11 +msgid "All motions" +msgstr "Toutes les motions" + +#: motion/templates/motion/base_motion.html:13 +#: motion/templates/motion/edit.html:10 motion/templates/motion/edit.html:18 +msgid "New motion" +msgstr "Nouvelle motion" + +#: motion/templates/motion/base_motion.html:16 +#: motion/templates/motion/import.html:5 motion/templates/motion/import.html:8 +msgid "Import motions" +msgstr "Importer des motions" + +#: motion/templates/motion/base_motion.html:18 +msgid "All motions as PDF" +msgstr "Toutes les motions dans un PDF" + +#: motion/templates/motion/base_motion.html:34 +msgid "View motion" +msgstr "Afficher la motion" + +#: motion/templates/motion/base_motion.html:38 +#: motion/templates/motion/edit.html:8 motion/templates/motion/edit.html:16 +#: motion/templates/motion/overview.html:72 +msgid "Edit motion" +msgstr "Modifier la motion" + +#: motion/templates/motion/base_motion.html:42 +#: motion/templates/motion/overview.html:74 +msgid "Delete motion" +msgstr "Supprimer la motion" + +#: motion/templates/motion/base_motion.html:45 +#: motion/templates/motion/overview.html:77 +msgid "Motion as PDF" +msgstr "Motion dans un PDF" + +#: motion/templates/motion/base_motion.html:49 +msgid "Show Motion" +msgstr "Lancer l'animation" + +#: motion/templates/motion/config.html:5 motion/templates/motion/config.html:8 +msgid "Motion settings" +msgstr "Paramètres de l'animation" + +#: motion/templates/motion/import.html:9 +msgid "Select a CSV file to import motions!" +msgstr "Sélectionnez un fichier CSV pour importer les motions!" + +#: motion/templates/motion/import.html:11 +#: participant/templates/participant/import.html:11 +msgid "Required comma separated values" +msgstr "Valeurs séparées par des virgules (CSV) nécessaires" + +#: motion/templates/motion/import.html:12 +msgid "number, title, text, reason, first_name, last_name, is_group" +msgstr "numéro, titre, texte, motivation, prénom, nom, groupe" + +#: motion/templates/motion/import.html:14 +msgid "" +"number, reason and is_group are " +"optional and may be empty" +msgstr "" +"numero, motivation et est_groupe sont " +"optional et peuvent être laisser vide" + +#: motion/templates/motion/import.html:16 +#: participant/templates/participant/import.html:14 +msgid "Required CSV file encoding: UTF-8 (Unicode)." +msgstr "Le fichier CSV requiert un encodage de caractères UTF-8 (Unicode)" + +#: motion/templates/motion/import.html:19 +#: participant/templates/participant/import.html:17 +msgid "A CSV example file is available in OpenSlides Wiki." +msgstr "Un exemple de fichier CSV est disponible sur le Wiki de Openslides. " + +#: motion/templates/motion/import.html:26 +#: participant/templates/participant/import.html:24 +msgid "Import" +msgstr "Importation" + +#: motion/templates/motion/overview.html:15 +msgid "Need supporters" +msgstr "A besoin de partisans" + +#: motion/templates/motion/overview.html:18 +msgid "Without number" +msgstr "Sans numéro" + +#: motion/templates/motion/overview.html:22 +msgid "Not yet authorized" +msgstr "N'est pas encore autorisé" + +#: motion/templates/motion/overview.html:23 +msgid "Authorized" +msgstr "Autorisé" + +#: motion/templates/motion/overview.html:26 +msgid "Withdrawen (by submitter)" +msgstr "retiré (par le requérant)" + +#: motion/templates/motion/overview.html:32 +msgctxt "number of motions" +msgid "motion" +msgid_plural "motions" +msgstr[0] "motion" +msgstr[1] "motions" + +#: motion/templates/motion/overview.html:35 +msgid "Number" +msgstr "Numéro" + +#: motion/templates/motion/overview.html:36 +msgid "Motion title" +msgstr "Titre de la motion" + +#: motion/templates/motion/overview.html:38 +msgid "Number of supporters" +msgstr "Nombre de partisans" + +#: motion/templates/motion/overview.html:42 +#: motion/templates/motion/view.html:109 +msgid "Creation Time" +msgstr "Temps de création" + +#: motion/templates/motion/overview.html:67 +msgid "Activate motion" +msgstr "Activer la motion" + +#: motion/templates/motion/poll_view.html:21 +msgid "Option" +msgstr "Option" + +#: motion/templates/motion/view.html:51 motion/templates/motion/view.html:91 +msgid "New vote" +msgstr "Nouveau vote" + +#: motion/templates/motion/view.html:67 +msgid "Edit Vote" +msgstr "Modifier le vote" + +#: motion/templates/motion/view.html:70 +msgid "Delete Vote" +msgstr "Supprimer le vote" + +#: motion/templates/motion/view.html:99 +msgid "Enter result" +msgstr "Insérer le résultat" + +#: motion/templates/motion/view.html:116 +msgid "Withdraw" +msgstr "Retirer" + +#: motion/templates/motion/view.html:124 +msgid "Unsupport" +msgstr "Ne plus soutenir" + +#: motion/templates/motion/view.html:130 +msgid "Support" +msgstr "Soutenir" + +#: motion/templates/motion/view.html:136 +msgid "minimum required supporters" +msgstr "Nombres minimum de partisans requis" + +#: motion/templates/motion/view.html:143 +msgid "Manage motion" +msgstr "Gérer la motion" + +#: motion/templates/motion/view.html:146 +msgid "Formal validation" +msgstr "Validation formelle" + +#: motion/templates/motion/view.html:148 +msgid "Publish" +msgstr "Publier" + +#: motion/templates/motion/view.html:151 +msgid "Permit" +msgstr "Permettre" + +#: motion/templates/motion/view.html:154 +msgid "Not permit (reject)" +msgstr "Non autorisé (rejeter)" + +#: motion/templates/motion/view.html:157 +msgid "Set number" +msgstr "Insérer un numéro" + +#: motion/templates/motion/view.html:164 +msgid "Result after vote" +msgstr "Résultat après le vote" + +#: motion/templates/motion/view.html:178 +msgid "Result after debate" +msgstr "Résultat après la discussion" + +#: motion/templates/motion/view.html:189 +msgid "Withdrawed by submitter" +msgstr "Retiré par le requérant" + +#: motion/templates/motion/view.html:194 +msgid "For Administration only:" +msgstr "Seulement pour les administrateurs" + +#: motion/templates/motion/view.html:196 +msgid "Reset" +msgstr "Réinitialiser" + +#: motion/templates/motion/view.html:219 +msgid "This is not the newest version." +msgstr "Ce n'est pas la version la plus récente" + +#: motion/templates/motion/view.html:219 motion/templates/motion/view.html:221 +msgid "Go to version" +msgstr "Aller à la version" + +#: motion/templates/motion/view.html:221 +msgid "This is not the authorized version." +msgstr "Ce n'est pas la version autorisée" + +#: motion/templates/motion/view.html:239 +msgid "Version History" +msgstr "Historique des versions" + +#: motion/templates/motion/view.html:245 +msgid "Time" +msgstr "Temps" + +#: motion/templates/motion/view.html:256 +msgid "Version authorized" +msgstr "Version autorisée" + +#: motion/templates/motion/view.html:259 +msgid "Permit Version" +msgstr "Permettre la version" + +#: motion/templates/motion/view.html:262 +msgid "Reject Version" +msgstr "Rejeter la version " + +#: motion/templates/motion/view.html:266 +msgid "Version rejected" +msgstr "Version rejetée" + +#: motion/templates/motion/view.html:276 motion/templates/motion/view.html:283 +#: motion/templates/motion/view.html:290 +msgid "unchanged" +msgstr "non modifié" + +#: motion/templates/motion/view.html:299 +msgid "Log" +msgstr "Log" + +#: motion/templates/motion/widget.html:31 +msgid "No motion available." +msgstr "Aucune motion disponible" + +#: motion/templates/projector/Motion.html:29 +msgid "Poll result" +msgstr "Résultat du sondage" + +#: motion/templates/projector/Motion.html:47 +msgid "No poll results available." +msgstr "Aucun résultat de sondage disponible" + +#: participant/__init__.py:3 +msgid "Participant" +msgstr "Participant" + +#: participant/forms.py:27 participant/views.py:607 +#: participant/templates/participant/group_overview.html:7 +#: participant/templates/participant/group_overview.html:10 +#: participant/templates/participant/user_detail.html:14 +msgid "Groups" +msgstr "Groupe" + +#: participant/forms.py:52 +msgid "Permissions" +msgstr "Permissions" + +#: participant/forms.py:55 participant/views.py:546 participant/views.py:593 +#: participant/templates/participant/base_participant.html:12 +#: participant/templates/participant/overview.html:7 +#: participant/templates/participant/overview.html:18 +#: participant/templates/participant/overview.html:62 +msgid "Participants" +msgstr "Participants" + +#: participant/forms.py:92 +msgid "You can not edit the name for this group." +msgstr "Vous ne pouvez pas modifier le nom de ce groupe." + +#: participant/forms.py:96 +#, python-format +msgid "Group name \"%s\" is reserved for internal use." +msgstr "Ce nom de groupe \"%s\" est réservé pour une utilisation interne." + +#: participant/forms.py:118 +msgid "System URL" +msgstr "URL du système" + +#: participant/forms.py:119 participant/forms.py:124 +msgid "Printed in PDF of first time passwords only." +msgstr "" +"Imprimé dans le PDF avec la liste des premiers mots de passe seulement." + +#: participant/forms.py:127 +msgid "Sort participants by first name" +msgstr "Classer les participants par prénom" + +#: participant/forms.py:128 +msgid "Disable for sorting by last name" +msgstr "Désactiver le classement par nom" + +#: participant/models.py:33 participant/templates/participant/overview.html:25 +msgid "Male" +msgstr "Masculin" + +#: participant/models.py:34 participant/templates/participant/overview.html:26 +msgid "Female" +msgstr "Féminin" + +#: participant/models.py:37 participant/templates/participant/overview.html:38 +msgid "Delegate" +msgstr "Délégué" + +#: participant/models.py:38 participant/templates/participant/overview.html:39 +msgid "Observer" +msgstr "Observateur" + +#: participant/models.py:39 participant/templates/participant/overview.html:40 +msgid "Staff" +msgstr "Personnel" + +#: participant/models.py:40 participant/templates/participant/overview.html:41 +msgid "Guest" +msgstr "Invité" + +#: participant/models.py:45 participant/templates/participant/overview.html:30 +#: participant/templates/participant/overview.html:68 +msgid "Structure level" +msgstr "Niveau de structure" + +#: participant/models.py:46 +msgid "Will be shown after the name." +msgstr "Apparait après le nom." + +#: participant/models.py:49 participant/templates/participant/overview.html:24 +#: participant/templates/participant/user_detail.html:24 +msgid "Gender" +msgstr "Sexe" + +#: participant/models.py:49 participant/models.py:52 participant/models.py:55 +msgid "Only for filtering the participant list." +msgstr "Seulement pour filtrer la liste des utilisateurs." + +#: participant/models.py:52 +msgid "Typ" +msgstr "Type" + +#: participant/models.py:54 participant/views.py:255 +#: participant/templates/participant/overview.html:45 +#: participant/templates/participant/overview.html:70 +#: participant/templates/participant/user_detail.html:34 +msgid "Committee" +msgstr "Comité" + +#: participant/models.py:57 +#: participant/templates/participant/user_detail.html:39 +msgid "About me" +msgstr "A propos de moi" + +#: participant/models.py:58 +msgid "Your profile text" +msgstr "Le texte de votre profil" + +#: participant/models.py:61 +msgid "Only for notes." +msgstr "Seulement pour des notes." + +#: participant/models.py:64 +msgid "Default password" +msgstr "Premier mot de passe" + +#: participant/models.py:118 +msgid "Can see participant" +msgstr "Peut voir les participants" + +#: participant/models.py:120 +msgid "Can manage participant" +msgstr "Peut gérer des participants" + +#: participant/models.py:142 +msgid "Use this group as participant" +msgstr "Utiliser ce groupe comme participant" + +#: participant/models.py:143 +msgid "For example as submitter of a motion." +msgstr "Par exemple, comme initiant d'une motion." + +#: participant/models.py:237 +msgid "Welcome to OpenSlides!" +msgstr "Bienvenue sur OpenSlides!" + +#: participant/views.py:207 +msgid "You can not delete yourself." +msgstr "Vous ne pouvez pas vous suprimer vous même." + +#: participant/views.py:228 +msgid "You can not deactivate yourself." +msgstr "Vous ne pouvez pas vous désactiver vous-même" + +#: participant/views.py:231 +msgid "You can not deactivate the administrator." +msgstr "Vous ne pouvez pas désactiver l'administrateur." + +#: participant/views.py:250 +msgid "Participant-list" +msgstr "Liste des participants" + +#: participant/views.py:251 +msgid "List of Participants" +msgstr "La liste des participants" + +#: participant/views.py:254 participant/templates/participant/overview.html:67 +msgid "Last Name" +msgstr "Nom" + +#: participant/views.py:254 participant/templates/participant/overview.html:66 +msgid "First Name" +msgstr "Prénom" + +#: participant/views.py:254 +#: participant/templates/participant/group_overview.html:13 +msgid "Group" +msgstr "Groupe" + +#: participant/views.py:254 participant/templates/participant/overview.html:37 +#: participant/templates/participant/overview.html:69 +#: participant/templates/participant/user_detail.html:29 +msgid "Type" +msgstr "Type" + +#: participant/views.py:286 +msgid "Participant-passwords" +msgstr "Mot de passe du participant" + +#: participant/views.py:308 +msgid "Account for OpenSlides" +msgstr "Compte pour OpenSlides" + +#: participant/views.py:310 +#, python-format +msgid "for %s" +msgstr "pour %s" + +#: participant/views.py:313 +#, python-format +msgid "User: %s" +msgstr "Utilisateur: %s" + +#: participant/views.py:317 +#, python-format +msgid "Password: %s" +msgstr "Mot de passe: %s" + +#: participant/views.py:322 +#, python-format +msgid "URL: %s" +msgstr "URL: %s" + +#: participant/views.py:364 +#, python-format +msgid "%d new participants were successfully imported." +msgstr "%d nouveaux participants ont été importés avec succès. " + +#: participant/views.py:375 +msgid "Do you really want to reset the password?" +msgstr "Voulez-vous vraiment reinitialser le mot de passe?" + +#: participant/views.py:388 +#, python-format +msgid "The Password for %s was successfully reset." +msgstr "Le mot de passe de %s a été initialisé avec succès" + +#: participant/views.py:445 +msgid "You can not delete this Group." +msgstr "Vous ne pouvez pas suprimer se groupe." + +#: participant/views.py:473 +msgid "Participants settings successfully saved." +msgstr "" +"Les modifications des paramètres des participants ont été appliquées avec " +"succès" + +#: participant/views.py:483 +#, python-format +msgid "" +"Installation was successfully! Use %(user)s (password: %(password)s) for " +"first login.
    Important: Please change the password after " +"first login! Otherwise this message still appears for everyone and could be " +"a security risk." +msgstr "" +"L'installation a été terminée avec succès! Utilisez %(user)s (mot de passe: " +"%(password)s) pour la première connexion.
    Important: " +"S'il vous plait, changez le mot de passe après la première connexion. Sinon, " +"ce message apparaîtra toujours pour tout le monde et pourrait représenter un " +"risque de sécurité." + +#: participant/views.py:506 +msgid "User settings successfully saved." +msgstr "Les paramètres d'utilisateurs ont été enregistrés avec succès." + +#: participant/views.py:528 +msgid "Password successfully changed." +msgstr "Le mot de passe a été changé avec succès." + +#: participant/views.py:579 +msgid "My motions and elections" +msgstr "Mes motions et élections" + +#: participant/templates/participant/base_participant.html:15 +msgid "All participants" +msgstr "Tout les participants" + +#: participant/templates/participant/base_participant.html:18 +#: participant/templates/participant/edit.html:10 +#: participant/templates/participant/edit.html:19 +msgid "New participant" +msgstr "Nouveau participant" + +#: participant/templates/participant/base_participant.html:19 +msgid "All groups" +msgstr "Tout les groupes" + +#: participant/templates/participant/base_participant.html:20 +#: participant/templates/participant/group_edit.html:10 +#: participant/templates/participant/group_edit.html:18 +msgid "New group" +msgstr "Nouveau groupe" + +#: participant/templates/participant/base_participant.html:21 +#: participant/templates/participant/import.html:5 +#: participant/templates/participant/import.html:8 +msgid "Import participants" +msgstr "Importer des participants" + +#: participant/templates/participant/base_participant.html:24 +msgid "List of participants as PDF" +msgstr "La liste des participants en PDF" + +#: participant/templates/participant/base_participant.html:27 +msgid "First time passwords as PDF" +msgstr "La liste des premiers mots de passe en PDF" + +#: participant/templates/participant/base_participant.html:38 +msgid "View participant" +msgstr "Afficher participant" + +#: participant/templates/participant/base_participant.html:45 +#: participant/templates/participant/edit.html:8 +#: participant/templates/participant/edit.html:17 +#: participant/templates/participant/overview.html:94 +msgid "Edit participant" +msgstr "Modifier le participant" + +#: participant/templates/participant/base_participant.html:50 +#: participant/templates/participant/overview.html:98 +msgid "Delete participant" +msgstr "Supprimer le participant" + +#: participant/templates/participant/base_participant.html:60 +msgid "View group" +msgstr "Afficher le groupe" + +#: participant/templates/participant/base_participant.html:65 +#: participant/templates/participant/group_edit.html:8 +#: participant/templates/participant/group_edit.html:16 +#: participant/templates/participant/group_overview.html:19 +msgid "Edit group" +msgstr "Modifier le groupe" + +#: participant/templates/participant/base_participant.html:71 +#: participant/templates/participant/group_overview.html:21 +msgid "Delete group" +msgstr "Supprimer le groupe" + +#: participant/templates/participant/config.html:5 +#: participant/templates/participant/config.html:8 +msgid "Participant settings" +msgstr "Paramètres du participant" + +#: participant/templates/participant/edit.html:26 +msgid "Reset to First Password" +msgstr "Initialiser au premier mot de passe" + +#: participant/templates/participant/group_detail.html:14 +msgid "Members" +msgstr "Membres" + +#: participant/templates/participant/group_detail.html:19 +msgid "No members available." +msgstr "Aucun membre n'est disponible" + +#: participant/templates/participant/group_overview.html:27 +#: participant/templates/participant/group_widget.html:24 +msgid "No groups available." +msgstr "Aucun groupe n'est disponible" + +#: participant/templates/participant/import.html:9 +msgid "Select a CSV file to import participants!" +msgstr "Selectionnez un fichier CSV pour importer des participants" + +#: participant/templates/participant/import.html:12 +msgid "" +"first_name, last_name, gender, structure level, type, committee, comment" +msgstr "prénom, nom, sexe, position, type, comité, commentaire" + +#: participant/templates/participant/login.html:8 +#: participant/templates/participant/login.html:16 +#: participant/templates/participant/login.html:58 templates/base.html:32 +msgid "Login" +msgstr "Connexion" + +#: participant/templates/participant/login.html:21 +#: participant/templates/participant/login.html:34 templates/base.html:57 +#: templates/base.html.py:64 +msgid "Close this notification" +msgstr "Fermez cette notification" + +#: participant/templates/participant/login.html:27 +msgid "Your username and password were not accepted. Please try again." +msgstr "" +"Votre nom d'utilisateur et votre mot de passe n'ont pas été acceptés. " +"Veuillez essayer à nouveau." + +#: participant/templates/participant/login.html:62 +msgid "Continue as guest" +msgstr "Continuer en tant qu'invité" + +#: participant/templates/participant/overview.html:27 +#: participant/templates/participant/overview.html:42 +msgid "Not specified" +msgstr "Non spécifié" + +#: participant/templates/participant/overview.html:53 projector/models.py:63 +msgid "Active" +msgstr "Actif" + +#: participant/templates/participant/overview.html:54 +msgid "Inactive" +msgstr "Inactif" + +#: participant/templates/participant/overview.html:60 +msgid "participant" +msgid_plural "participants" +msgstr[0] "participant" +msgstr[1] "participants" + +#: participant/templates/participant/overview.html:62 +msgid "of" +msgstr "de" + +#: participant/templates/participant/overview.html:73 +#: participant/templates/participant/user_detail.html:49 +msgid "Last Login" +msgstr "Dernière connexion" + +#: participant/templates/participant/overview.html:102 +msgid "Change status to inactive" +msgstr "Changer le statut sur inactif" + +#: participant/templates/participant/overview.html:105 +msgid "Change status to active" +msgstr "Changer le statut sur actif" + +#: participant/templates/participant/overview.html:115 +#: participant/templates/participant/user_widget.html:22 +msgid "No participants available." +msgstr "Aucun participant disponible" + +#: participant/templates/participant/password_change.html:5 +#: participant/templates/participant/password_change.html:11 +#: participant/templates/participant/password_change.html:16 +#: participant/templates/participant/settings.html:11 +msgid "Password Settings" +msgstr "Paramètres du mot de passe" + +#: participant/templates/participant/password_change.html:8 +#: participant/templates/participant/settings.html:8 templates/base.html:29 +msgid "User Settings" +msgstr "Paramètres d'utilisateur" + +#: participant/templates/participant/password_change.html:10 +#: participant/templates/participant/settings.html:5 +#: participant/templates/participant/settings.html:10 +#: participant/templates/participant/settings.html:16 +msgid "Personal Settings" +msgstr "Paramètres personnels" + +#: participant/templates/participant/personal_info_widget.html:5 +msgid "I submitted the following motions:" +msgstr "Je propose les motions suivantes:" + +#: participant/templates/participant/personal_info_widget.html:17 +#: participant/templates/participant/personal_info_widget.html:36 +#: participant/templates/participant/personal_info_widget.html:47 +msgid "None" +msgstr "Aucun" + +#: participant/templates/participant/personal_info_widget.html:24 +msgid "I support the following motions:" +msgstr "Je soutien les motions suivantes" + +#: participant/templates/participant/personal_info_widget.html:43 +msgid "I am candidate for the following elections:" +msgstr "Je suis candidat aux élections suivantes" + +#: participant/templates/participant/user_detail.html:19 +msgid "The participant is not member of any group." +msgstr "Le participant n'est membre d'aucun groupe" + +#: participant/templates/participant/user_detail.html:53 +msgid "The participant has not logged in yet." +msgstr "Le participant ne s'est pas encore authentifié." + +#: participant/templates/projector/GroupSlide.html:11 +msgid "participants" +msgstr "participants" + +#: poll/models.py:95 +msgid "Votes invalid" +msgstr "Votes invalides" + +#: poll/models.py:128 +msgid "votes" +msgstr "votes" + +#: projector/models.py:50 +msgid "Can manage the projector" +msgstr "Peut gérer le projecteur" + +#: projector/models.py:51 +msgid "Can see the projector" +msgstr "Peut voir le projecteur" + +#: projector/models.py:52 +msgid "Can see the dashboard" +msgstr "Peut voir la vue d'ensemble" + +#: projector/views.py:199 +msgid "Errors in the form" +msgstr "Erreurs dans le formulaire" + +#: projector/views.py:375 projector/templates/projector/base_projector.html:7 +#: projector/templates/projector/base_projector.html:12 +#: projector/templates/projector/dashboard.html:17 +msgid "Dashboard" +msgstr "Vue d'ensemble" + +#: projector/views.py:402 +msgid "Projector live view" +msgstr "Vue projecteur live" + +#: projector/views.py:428 +msgid "Overlays" +msgstr "Superpositions" + +#: projector/views.py:440 +msgid "Custom Slides" +msgstr "Diapositives personnalisées" + +#: projector/templates/projector/base_projector.html:15 +msgid "Overview" +msgstr "Vue d'ensemble" + +#: projector/templates/projector/base_projector.html:18 +#: projector/templates/projector/select_widgets.html:5 +#: projector/templates/projector/select_widgets.html:8 +msgid "Select widgets" +msgstr "Sélectionner les composants" + +#: projector/templates/projector/base_projector.html:22 +msgid "Projector view" +msgstr "Vue projecteur" + +#: projector/templates/projector/control_countdown.html:6 +msgctxt "seconds" +msgid "s" +msgstr "s" + +#: projector/templates/projector/control_countdown.html:7 +msgid "Save as default" +msgstr "Enregistrer par défault " + +#: projector/templates/projector/control_countdown.html:10 +#: projector/templates/projector/control_countdown.html:11 +msgid "Reset countdown" +msgstr "Remettre le compte à rebours à zéro" + +#: projector/templates/projector/control_countdown.html:13 +msgid "Start countdown" +msgstr "Démarrer le compte à rebours" + +#: projector/templates/projector/control_countdown.html:16 +msgid "Stop countdown" +msgstr "Arrêter le compte à rebours " + +#: projector/templates/projector/control_overlay_message.html:11 +msgid "Clean message" +msgstr "Supprimer le message" + +#: projector/templates/projector/custom_slide_widget.html:14 +msgid "Welcome Page" +msgstr "Page d'accueil" + +#: projector/templates/projector/custom_slide_widget.html:41 +msgid "New slide" +msgstr "nouvelle diapositive" + +#: projector/templates/projector/live_view_widget.html:10 +msgid "Zoom in" +msgstr "Zoom avant" + +#: projector/templates/projector/live_view_widget.html:13 +msgid "Zoom out" +msgstr "Zoom arrière" + +#: projector/templates/projector/live_view_widget.html:18 +msgid "Scroll text up" +msgstr "Faire défiler le texte vers le haut" + +#: projector/templates/projector/live_view_widget.html:21 +msgid "Scroll text down" +msgstr "Faire défiler le texte vers le bas" + +#: projector/templates/projector/live_view_widget.html:26 +msgid "Reset projector view" +msgstr "Remettre la vue projecteur à zéro" + +#: projector/templates/projector/new.html:6 +#: projector/templates/projector/new.html:9 +msgid "Custom slide" +msgstr "Diapositive personnalisée" + +#: projector/templates/projector/select_widgets.html:17 +msgid "No widgets available" +msgstr "Pas d'éléments disponibles." + +#: templates/404.html:7 +msgid "Page not found." +msgstr "N'a pas pu trouver la page." + +#: templates/500.html:7 +msgid "Server Error" +msgstr "Erreur du serveur" + +#: templates/base.html:21 +msgid "Home" +msgstr "Accueil" + +#: templates/base.html:27 +msgid "Logout" +msgstr "Déconnecter" + +#: templates/base.html:30 +msgid "Welcome" +msgstr "Bienvenue" + +#: templates/base.html:79 +msgid "" +"Get professional " +"support for OpenSlides." +msgstr "" +"Obtenir un support " +"professionel pour OpenSlides." + +#: utils/pdf.py:227 +#, python-format +msgid "As of: %s" +msgstr "A partir de: %s" + +#: utils/pdf.py:238 utils/pdf.py:247 +#, python-format +msgid "Page %s" +msgstr "Page %s" + +#: utils/utils.py:63 utils/views.py:287 +#, python-format +msgid "Do you really want to delete %s?" +msgstr "Voulez-vous vraiment supprimer %s?" + +#: utils/utils.py:110 +msgid "Sorry, you have no rights to see this page." +msgstr "Désolé, vous n'avez pas le droit de voir cette page" + +#: utils/views.py:106 +msgid "Are you sure?" +msgstr "Etes-vous sûr?" + +#: utils/views.py:107 +msgid "Thank you for your answer" +msgstr "Merci pour votre réponse" + +#: utils/views.py:244 +#, python-format +msgid "%s was successfully modified." +msgstr "%s a été modifié avec succès." + +#: utils/views.py:275 +#, python-format +msgid "%s was successfully created." +msgstr "%s a été créé avec succès." + +#: utils/views.py:293 +#, python-format +msgid "%s was successfully deleted." +msgstr "%s a été supprimé avec succès" + +#: utils/views.py:308 +msgid "undefined-filename" +msgstr "nom de fichier indéterminé" + +#: utils/jsonfield/fields.py:22 +msgid "Enter valid JSON" +msgstr "Entrez un JSON valide" diff --git a/openslides/locale/fr/LC_MESSAGES/djangojs.mo b/openslides/locale/fr/LC_MESSAGES/djangojs.mo new file mode 100644 index 000000000..cca03a977 Binary files /dev/null and b/openslides/locale/fr/LC_MESSAGES/djangojs.mo differ diff --git a/openslides/locale/fr/LC_MESSAGES/djangojs.po b/openslides/locale/fr/LC_MESSAGES/djangojs.po new file mode 100644 index 000000000..2745843ce --- /dev/null +++ b/openslides/locale/fr/LC_MESSAGES/djangojs.po @@ -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: +# , 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 \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" diff --git a/openslides/main.py b/openslides/main.py old mode 100644 new mode 100755 index 17d682eb8..bc23fb3bd --- a/openslides/main.py +++ b/openslides/main.py @@ -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,46 +313,71 @@ 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") - 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.restype = ctypes.c_uint32 + portable_dir = fs2unicode(os.path.dirname(os.path.abspath(sys.executable))) + return os.path.join(portable_dir, *args) - CSIDL_LOCAL_APPDATA = 0x001c - MAX_PATH = 260 - buf = ctypes.create_unicode_buffer(MAX_PATH) - 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") +def get_portable_db_path(): + return get_portable_path('openslides', 'database.sqlite') - main(argv, opt_defaults={ "settings": default_settings }) + +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.restype = ctypes.c_uint32 + + CSIDL_LOCAL_APPDATA = 0x001c + MAX_PATH = 260 + + buf = ctypes.create_unicode_buffer(MAX_PATH) + res = SHGetFolderPath(0, CSIDL_LOCAL_APPDATA, 0, 0, buf) + if res != 0: + raise Exception("Could not deterime APPDATA path") + + return os.path.join(buf.value, *args) if __name__ == "__main__": diff --git a/openslides/application/__init__.py b/openslides/motion/__init__.py similarity index 100% rename from openslides/application/__init__.py rename to openslides/motion/__init__.py diff --git a/openslides/application/forms.py b/openslides/motion/forms.py similarity index 62% rename from openslides/application/forms.py rename to openslides/motion/forms.py index a6cc0bfad..6fbfc9cff 100644 --- a/openslides/application/forms.py +++ b/openslides/motion/forms.py @@ -1,77 +1,77 @@ #!/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'}), + widget=forms.FileInput(attrs={'size': '50'}), label=_("CSV File"), ) import_permitted = forms.BooleanField( required=False, label=_("Import motions with status \"authorized\""), help_text=_('Set the initial status for each motion to ' - '"authorized"'), + '"authorized"'), ) class ConfigForm(forms.Form, CssClassMixin): - application_min_supporters = forms.IntegerField( - widget=forms.TextInput(attrs={'class':'small-input'}), + motion_min_supporters = forms.IntegerField( + widget=forms.TextInput(attrs={'class': 'small-input'}), label=_("Number of (minimum) required supporters for a motion"), initial=4, min_value=0, 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,26 +81,26 @@ class ConfigForm(forms.Form, CssClassMixin): ("CUSTOM_NUMBER", _("Use the following custom number")), ] ) - application_pdf_ballot_papers_number = forms.IntegerField( - widget=forms.TextInput(attrs={'class':'small-input'}), + 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.'), + 'autorisation system.'), required=False, ) diff --git a/openslides/application/models.py b/openslides/motion/models.py similarity index 61% rename from openslides/application/models.py rename to openslides/motion/models.py index 4c06b55a6..7127e521c 100644 --- a/openslides/application/models.py +++ b/openslides/motion/models.py @@ -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')), @@ -51,7 +48,7 @@ class Application(models.Model, SlideMixin): ('noc', _('Not Concerned')), ('com', _('Commited a bill')), ('nop', _('Rejected (not authorized)')), - ('rev', _('Needs Review')), # Where is this status used? + ('rev', _('Needs Review')), # Where is this status used? #additional actions: # edit # delete @@ -65,19 +62,19 @@ class Application(models.Model, SlideMixin): submitter = PersonField(verbose_name=_("Submitter")) number = models.PositiveSmallIntegerField(blank=True, null=True, - unique=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,14 +82,14 @@ 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 else: return self.last_version - def accept_version(self, version, user = None): + def accept_version(self, version, user=None): """ accept a Version """ @@ -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): + 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; " - "changed fields: %(changed_fields)s") - % dict(version = last_version.aid, - changed_fields = ", ".join(field_names))) - return # Done + 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', ''), - text=getattr(self, 'text', ''), - reason=getattr(self, 'reason', ''), - application=self) + version = AVersion( + title=getattr(self, 'title', ''), + text=getattr(self, 'text', ''), + reason=getattr(self, 'reason', ''), + 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,19 +320,19 @@ 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 if error: - #TODO: Use the Right Error + # TODO: Use the Right Error 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.\'') \ + # TODO: Use the Right Error + 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: - self.log += u" (%s %s)" % (_("by"), _propper_unicode(user.username)) + 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,20 +527,21 @@ 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): - title = models.CharField(max_length=100, verbose_name = _("Title")) - text = models.TextField(verbose_name = _("Text")) - reason = models.TextField(null=True, blank=True, verbose_name = _("Reason")) - rejected = models.BooleanField() # = Not Permitted + title = models.CharField(max_length=100, verbose_name=_("Title")) + text = models.TextField(verbose_name=_("Text")) + 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) diff --git a/openslides/motion/templates/motion/base_motion.html b/openslides/motion/templates/motion/base_motion.html new file mode 100644 index 000000000..1466bfcf2 --- /dev/null +++ b/openslides/motion/templates/motion/base_motion.html @@ -0,0 +1,66 @@ +{% extends "base.html" %} + +{% load tags %} +{% load i18n %} +{% load staticfiles %} + +{% block submenu %} + {% url motion_overview as url_motionoverview %} +

    {% trans "Motions" %}

    + + + {# second submenu #} + {% if motion %} +
    +

    {% trans "Motion No." %} + {% if motion.number != None %} + {{ motion.number }} + {% else %} + [-] + {% endif %} +

    + + {% endif %} +{% endblock %} diff --git a/openslides/application/templates/application/config.html b/openslides/motion/templates/motion/config.html similarity index 69% rename from openslides/application/templates/application/config.html rename to openslides/motion/templates/motion/config.html index 9193f5525..0dda79f7c 100644 --- a/openslides/application/templates/application/config.html +++ b/openslides/motion/templates/motion/config.html @@ -2,10 +2,10 @@ {% load i18n %} -{% block title %}{{ block.super }} – {% trans "Application settings" %}{% endblock %} +{% block title %}{{ block.super }} – {% trans "Motion settings" %}{% endblock %} {% block content %} -

    {% trans "Configuration" %}: {% trans "Applications" %} +

    {% trans "Configuration" %}: {% trans "Motions" %} {% block config_submenu %}{{ block.super }}{% endblock %}

    {% csrf_token %} @@ -14,8 +14,8 @@ - - diff --git a/openslides/application/templates/application/edit.html b/openslides/motion/templates/motion/edit.html similarity index 89% rename from openslides/application/templates/application/edit.html rename to openslides/motion/templates/motion/edit.html index edae46880..045d66dc3 100644 --- a/openslides/application/templates/application/edit.html +++ b/openslides/motion/templates/motion/edit.html @@ -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 %}

    - {% if application %} + {% if motion %} {% trans "Edit motion" %} {% else %} {% trans "New motion" %} @@ -43,13 +43,12 @@ - - * {% trans "required" %} - {% endblock %} diff --git a/openslides/application/templates/application/import.html b/openslides/motion/templates/motion/import.html similarity index 82% rename from openslides/application/templates/application/import.html rename to openslides/motion/templates/motion/import.html index e92dbad56..ba04ca732 100644 --- a/openslides/application/templates/application/import.html +++ b/openslides/motion/templates/motion/import.html @@ -12,7 +12,10 @@

    {% trans 'Select a CSV file to import motions!' %}

    -

    {% trans 'Required comma separated values: {number, title, text, reason, first_name, last_name} (number and reason are optional and may be empty)' %} +

    {% trans 'Required comma separated values' %}: + ({% trans 'number, title, text, reason, first_name, last_name, is_group' %}) +
    + {% trans 'number, reason and is_group are optional and may be empty' %}.
    {% trans 'Required CSV file encoding: UTF-8 (Unicode).' %}

    @@ -37,7 +40,7 @@ - + {% trans 'Cancel' %} diff --git a/openslides/application/templates/application/overview.html b/openslides/motion/templates/motion/overview.html similarity index 75% rename from openslides/application/templates/application/overview.html rename to openslides/motion/templates/motion/overview.html index 7145990dc..2bec26f5f 100644 --- a/openslides/application/templates/application/overview.html +++ b/openslides/motion/templates/motion/overview.html @@ -43,10 +43,9 @@ - - {{ applications|length }} - {% blocktrans count counter=applications|length %}motion{% plural %}motions{% endblocktrans %} + {{ motions|length }} + {% blocktrans count counter=motions|length context "number of motions"%}motion{% plural %}motions{% endblocktrans %} @@ -60,39 +59,39 @@ - {% 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 %} - - + {% if motion.active %}activeline{% endif %}"> + + {% if min_supporters > 0 %} - + {% endif %} - - - + + diff --git a/openslides/application/templates/application/poll_view.html b/openslides/motion/templates/motion/poll_view.html similarity index 89% rename from openslides/application/templates/application/poll_view.html rename to openslides/motion/templates/motion/poll_view.html index e459abe33..3dee1c3af 100644 --- a/openslides/application/templates/application/poll_view.html +++ b/openslides/motion/templates/motion/poll_view.html @@ -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 %}

    - {{ application.title }} + {{ motion.public_version.title }} - {% trans "Motion" %} {{ application.number }}, {{ ballot }}. {% trans "Vote" %} + {% trans "Motion" %} {{ motion.number }}, {{ ballot }}. {% trans "Vote" %}
    @@ -39,6 +39,7 @@

    {% trans "Special values" %}: -1 = {% trans 'majority' %}; -2 = {% trans 'undocumented' %} +
    {% csrf_token %} {{ pre_form }} @@ -65,7 +66,7 @@ {{ post_form }}

    - + {% trans 'Ballot paper as PDF' %}

    @@ -77,7 +78,7 @@ - + {% trans 'Cancel' %} diff --git a/openslides/application/templates/application/view.html b/openslides/motion/templates/motion/view.html similarity index 61% rename from openslides/application/templates/application/view.html rename to openslides/motion/templates/motion/view.html index f1ad2ca2c..7ab3eb32d 100644 --- a/openslides/application/templates/application/view.html +++ b/openslides/motion/templates/motion/view.html @@ -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 @@

    {{ version.title }} - {% if application.number != None %} - {% trans "Motion" %} {{ application.number }}, + {% if motion.number != None %} + {% trans "Motion" %} {{ motion.number }}, {% else %} [{% trans "no number" %}], {% endif %} @@ -26,8 +26,8 @@
    - {% trans "Back to overview" %} - PDF + {% trans "Back to overview" %} + PDF
    {% trans 'More actions' %} @@ -36,28 +36,28 @@ @@ -68,12 +68,12 @@
    - {% 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." %} {% trans "Go to version" %} {{ application.last_version.aid }}. + {% if version == motion.public_version %} + {% trans "This is not the newest version." %} {% trans "Go to version" %} {{ motion.last_version.aid }}. {% else %} - {% trans "This is not the authorized version." %} {% trans "Go to version" %} {{ application.public_version.aid }}. + {% trans "This is not the authorized version." %} {% trans "Go to version" %} {{ motion.public_version.aid }}. {% endif %} {% endif %} @@ -90,7 +90,7 @@ {% endif %}
    - {% if application.versions|length > 1 %} + {% if motion.versions|length > 1 %}

    {% trans "Version History" %}:

    {% trans "Creation Time" %} {% trans "Actions" %}
    {% if application.number %}{{ application.number }}{% else %}-{% endif %}{{ application.public_version.title }}{% if motion.number %}{{ motion.number }}{% else %}-{% endif %}{{ motion.public_version.title }}{{ application.supporter.count }}{{ motion.count_supporters }}{% if application.status != "pub" %} - {{ application.get_status_display }}
    +
    {% if motion.status != "pub" %} + {{ motion.get_status_display }}
    {% endif %} - {% for note in application.notes %} + {% for note in motion.notes %} {{ note }} {% if not forloop.last %}
    {%endif%} {% endfor %}
    {{ application.submitter }}{{ application.creation_time }}{{ motion.submitter }}{{ motion.creation_time }} {% if perms.projector.can_manage_projector %} - + {% endif %} - {% if perms.application.can_manage_application %} - + {% if perms.motion.can_manage_motion %} + {% if "delete" in useractions %} - + {% endif %} {% endif %} - +
    @@ -101,18 +101,18 @@ - {% for revision in application.versions %} + {% for revision in motion.versions %}
    {% trans "Text" %} {% trans "Reason" %}
    - {% if application.status != "pub" %} - {% if revision == application.permitted %} + {% if motion.status != "pub" %} + {% if revision == motion.permitted %} {% else %} - {% if perms.application.can_manage_application %} - + {% if perms.motion.can_manage_motion %} + {% endif %} - {% if not revision.rejected and revision.id > application.permitted.id and perms.application.can_manage_application %} - + {% if not revision.rejected and revision.id > motion.permitted.id and perms.motion.can_manage_motion %} + {% endif %} {% endif %} {% if revision.rejected %} @@ -148,51 +148,49 @@
    {% endif %} - {% if perms.application.can_manage_application %} + {% if perms.motion.can_manage_motion %}

    {% trans "Log" %}:

    - {{ application.log|linebreaks }} + {{ motion.log|linebreaks }} {% endif %} +
    {% trans "Submitter" %}:
    - {{ application.submitter }} - {% if user == application.submitter.user %} - - {% endif %} + {{ motion.submitter }} {% if min_supporters > 0 %}
    {% trans "Supporters" %}: *
    - {% if not application.supporters %} + {% if not motion.supporters %} - {% else %}
      - {% for supporter in application.supporters %} -
    1. {{ supporter }}
    2. + {% for supporter in motion.supporters %} +
    3. {{ supporter }}
    4. {% endfor %}
    {% endif %} {% endif %}
    {% trans "Status" %}:
    - {% if application.status != "pub" %} - {% trans application.get_status_display %} + {% if motion.status != "pub" %} + {% trans motion.get_status_display %}
    {% endif %} - {% for note in application.notes %} + {% for note in motion.notes %} {{ note }} {% if not forloop.last %}
    {% endif %} {% endfor %}
    {% trans "Vote results" %}:
    - {% 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 %} - + {% trans 'New vote' %} {% else %} @@ -204,11 +202,11 @@ {% endif %}
      {% for poll in polls %} - {% if perms.application.can_manage_application or poll.has_votes %} + {% if perms.motion.can_manage_motion or poll.has_votes %}
    1. {% trans "Vote" %} - {% if perms.application.can_manage_application %} - - + {% if perms.motion.can_manage_motion %} + + {% endif %}
      {% if poll.has_votes %} @@ -221,17 +219,17 @@ {{ poll.print_votescast }}
    {% endwith %} - {% if perms.application.can_manage_application %} + {% if perms.motion.can_manage_motion %} {% if forloop.last %} {% if "genpoll" in actions %} - + {% trans 'New vote' %} {% endif %} {% endif %} {% endif %} {% else %} - {% if perms.application.can_manage_application %} + {% if perms.motion.can_manage_motion %} {% trans 'No results' %} {% endif %} {% endif %} @@ -242,25 +240,25 @@ {% endwith %}
    {% trans "Creation Time" %}:
    - {{ application.creation_time }} + {{ motion.creation_time }} - {% if "wit" in actions and user == application.submitter.user %} + {% if "wit" in actions and user == motion.submitter.user %}

    - + {% trans 'Withdraw motion' %} {% endif %} - {% if perms.application.can_support_application and min_supporters > 0 %} + {% if perms.motion.can_support_motion and min_supporters > 0 %} {% if "unsupport" in actions %}

    - + {% trans 'Unsupport motion' %} {% endif %} {% if "support" in actions %}

    - + {% trans 'Support' %} {% endif %} @@ -272,8 +270,8 @@ {% endif %}
    - {% if perms.application.can_manage_application %} - + {% if perms.motion.can_manage_motion %} +

    {% trans "Manage motion" %}

    @@ -281,16 +279,16 @@
    {% trans "Formal validation" %}:
    {% if "pub" in actions %} - {% trans 'Publish' %} + {% trans 'Publish' %} {% endif %} {% if "per" in actions %} - {% trans 'Permit' %} + {% trans 'Permit' %} {% endif %} {% if "nop" in actions %} - {% trans 'Not permit' %} + {% trans 'Not permit' %} {% endif %} {% if "setnumber" in actions %} - {% trans 'Set number' %} + {% trans 'Set number' %} {% endif %}
    {% endif %} @@ -299,12 +297,12 @@
    {% trans "Result after vote" %}:
    {% if "acc" in actions %} - + {% trans 'Accepted' %} {% endif %} {% if "rej" in actions %} - + {% trans 'Rejected' %} {% endif %} @@ -320,16 +318,16 @@
    @@ -337,11 +335,11 @@


    {% trans "For Administration only:" %}
    - + {% trans 'Reset' %}
    - {% endif %} {# end perms.application.can_support_application #} + {% endif %} {# end perms.motion.can_support_motion #} {% endblock %} diff --git a/openslides/motion/templates/motion/widget.html b/openslides/motion/templates/motion/widget.html new file mode 100644 index 000000000..0495d7ac9 --- /dev/null +++ b/openslides/motion/templates/motion/widget.html @@ -0,0 +1,34 @@ +{% load staticfiles %} +{% load i18n %} +{% load tags %} + + + diff --git a/openslides/application/templates/projector/Application.html b/openslides/motion/templates/projector/Motion.html similarity index 74% rename from openslides/application/templates/projector/Application.html rename to openslides/motion/templates/projector/Motion.html index 8f6ff6401..ab094313f 100644 --- a/openslides/application/templates/projector/Application.html +++ b/openslides/motion/templates/projector/Motion.html @@ -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 %}

    - {% if application.number != None %} - {% trans "Motion No." %} {{ application.number }} + {% if motion.number != None %} + {% trans "Motion No." %} {{ motion.number }} {% else %} {% trans "Motion" %} [{% trans "no number" %}] {% endif %}

    - {{ application.public_version.title }} + {{ motion.public_version.title }}
    {% endblock %} {% block scrollcontent %}

    -

    {{ application.public_version.text|linebreaks }}
    - {% if application.public_version.reason %} +
    {{ motion.public_version.text|linebreaks }}
    + {% if motion.public_version.reason %}

    {% trans "Reason" %}:

    - {{ application.public_version.reason|linebreaks }}
    + {{ motion.public_version.reason|linebreaks }} {% endif %}

    {% endblock %} diff --git a/openslides/motion/urls.py b/openslides/motion/urls.py new file mode 100644 index 000000000..177a5b04f --- /dev/null +++ b/openslides/motion/urls.py @@ -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\d+)/$', + 'view', + name='motion_view', + ), + + url(r'^(?P\d+)/agenda/$', + CreateAgendaItem.as_view(), + name='motion_create_agenda', + ), + + url(r'^(?P\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\d+)/edit/$', + 'edit', + name='motion_edit', + ), + + url(r'^(?P\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\d+)/setnumber/$', + 'set_number', + name='motion_set_number', + ), + + url(r'^(?P\d+)/setstatus/(?P[a-z]{3})/$', + 'set_status', + name='motion_set_status', + ), + + url(r'^(?P\d+)/permit/$', + 'permit', + name='motion_permit', + ), + + url(r'^version/(?P\d+)/permit/$', + 'permit_version', + name='motion_version_permit', + ), + + url(r'^version/(?P\d+)/reject/$', + 'reject_version', + name='motion_version_reject', + ), + + url(r'^(?P\d+)/notpermit/$', + 'notpermit', + name='motion_notpermit', + ), + + url(r'^(?P\d+)/reset/$', + 'reset', + name='motion_reset', + ), + + url(r'^(?P\d+)/support/$', + SupportView.as_view(support=True), + name='motion_support', + ), + + url(r'^(?P\d+)/unsupport/$', + SupportView.as_view(support=False), + name='motion_unsupport', + ), + + url(r'^(?P\d+)/gen_poll/$', + 'gen_poll', + name='motion_gen_poll', + ), + + url(r'^print/$', + MotionPDF.as_view(), + {'motion_id': None}, + name='print_motions', + ), + + url(r'^(?P\d+)/print/$', + MotionPDF.as_view(), + name='print_motion', + ), + + url(r'^poll/(?P\d+)/print/$', + MotionPollPDF.as_view(), + name='print_motion_poll', + ), + + url(r'^poll/(?P\d+)/$', + ViewPoll.as_view(), + name='motion_poll_view', + ), + + url(r'^poll/(?P\d+)/del/$', + 'delete_poll', + name='motion_poll_delete', + ), +) diff --git a/openslides/motion/views.py b/openslides/motion/views.py new file mode 100644 index 000000000..76b2ba4c3 --- /dev/null +++ b/openslides/motion/views.py @@ -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 not 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 %s 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: %s.") % 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 %s.") % motion) + continue + + title = motion.title + motion.delete(force=True) + messages.success(request, _("Motion %s 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 %s.") % self.object) + elif self.get_answer() == 'yes': + title = self.object.title + self.object.delete(force=True) + messages.success(request, _("Motion %s 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 %s accepted.") % (aversion.aid)) + else: + gen_confirm_form(request, _('Do you really want to authorize version %s?') % 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 %s rejected.") % (aversion.aid)) + else: + messages.error(request, _("ERROR by rejecting the version.") ) + else: + gen_confirm_form(request, _('Do you really want to reject version %s?') % 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','
    '), 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("%s:" % _("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("%s:" % _("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("%s:" % _("Supporters"), stylesheet['Heading4'])) + for supporter in motion.supporters: + cell3b.append(Paragraph(".  %s" % supporter, stylesheet['Signaturefield'])) + if motion.status == "pub": + for x in range(motion.missing_supporters): + cell3b.append(Paragraph(".  __________________________________________",stylesheet['Signaturefield'])) + cell3b.append(Spacer(0, 0.2 * cm)) + data.append([cell3a, cell3b]) + + # status + cell4a = [] + cell4b = [] + note = " ".join(motion.notes) + cell4a.append(Paragraph("%s:" % _("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("%s:" % _("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("%s:" % _("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
    %s: %s
    %s: %s
    %s: %s
    %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','
    '), 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','
    '), 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 = "  " % 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')] diff --git a/openslides/participant/api.py b/openslides/participant/api.py index 6728016cd..fb5325628 100644 --- a/openslides/participant/api.py +++ b/openslides/participant/api.py @@ -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): @@ -48,7 +52,7 @@ def gen_username(first_name, last_name): i = 0 while True: i += 1 - testname = "%s%s%s" % (first_name, last_name, i) + testname = "%s %s %s" % (first_name, last_name, i) try: User.objects.get(username=testname) except User.DoesNotExist: @@ -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 diff --git a/initial_data.json b/openslides/participant/fixtures/groups_de.json similarity index 76% rename from initial_data.json rename to openslides/participant/fixtures/groups_de.json index 5f7c8e30c..b662beff6 100644 --- a/initial_data.json +++ b/openslides/participant/fixtures/groups_de.json @@ -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" ] ] } diff --git a/openslides/participant/forms.py b/openslides/participant/forms.py index 4fb13a570..8085f313f 100644 --- a/openslides/participant/forms.py +++ b/openslides/participant/forms.py @@ -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,12 +66,13 @@ class GroupForm(forms.ModelForm, CssClassMixin): instance = forms.ModelForm.save(self, False) old_save_m2m = self.save_m2m - def save_m2m(): - old_save_m2m() - instance.user_set.clear() - for user in self.cleaned_data['users']: - instance.user_set.add(user) + def save_m2m(): + old_save_m2m() + + instance.user_set.clear() + for user in self.cleaned_data['users']: + instance.user_set.add(user) self.save_m2m = save_m2m if commit: @@ -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")) diff --git a/openslides/participant/models.py b/openslides/participant/models.py index b97ed119c..6cad7364f 100644 --- a/openslides/participant/models.py +++ b/openslides/participant/models.py @@ -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: - yield self.users.get(pk=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,19 +207,23 @@ class UsersConnector(object): if (not self.person_prefix_filter or self.person_prefix_filter == Group.person_prefix): if self.id_filter: - yield self.groups.get(pk=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'], - id_filter=kwargs['id_filter']) + """ + Answers to the Person-API + """ + return UsersAndGroupsToPersons( + person_prefix_filter=kwargs['person_prefix_filter'], + id_filter=kwargs['id_filter']) @receiver(default_config_value, dispatch_uid="participant_default_config") @@ -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() diff --git a/openslides/participant/templates/participant/config.html b/openslides/participant/templates/participant/config.html index ea661a65c..283fb40b9 100644 --- a/openslides/participant/templates/participant/config.html +++ b/openslides/participant/templates/participant/config.html @@ -12,7 +12,7 @@ {% include "form.html" %}

    {% include "formbuttons_save.html" %} - + {% trans 'Cancel' %}

    diff --git a/openslides/participant/templates/participant/group_detail.html b/openslides/participant/templates/participant/group_detail.html new file mode 100644 index 000000000..fb456c31c --- /dev/null +++ b/openslides/participant/templates/participant/group_detail.html @@ -0,0 +1,22 @@ +{% extends "participant/base_participant.html" %} + +{% load i18n %} +{% load tags %} + +{% block title %}{{ block.super }} – {{ group }}{% endblock %} + +{% block content %} + +

    {{ group }}

    + +

    {{ group.description }}

    + +

    {% trans "Members" %}

    + +{% for member in group.user_set.all %} +

    {{ member }}

    +{% empty %} +

    {% trans "No members available." %}

    +{% endfor %} + +{% endblock %} diff --git a/openslides/participant/templates/participant/group_edit.html b/openslides/participant/templates/participant/group_edit.html index 788cc8ccd..a392cf0dd 100644 --- a/openslides/participant/templates/participant/group_edit.html +++ b/openslides/participant/templates/participant/group_edit.html @@ -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 %}

    {% if group %} - {% trans "Edit user group" %} + {% trans "Edit group" %} {% else %} - {% trans "New user group" %} + {% trans "New group" %} {% endif %} {% trans "Back to overview" %} diff --git a/openslides/participant/templates/participant/group_overview.html b/openslides/participant/templates/participant/group_overview.html index 04dde9e2f..6f7de45f5 100644 --- a/openslides/participant/templates/participant/group_overview.html +++ b/openslides/participant/templates/participant/group_overview.html @@ -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 %} -

    {% trans "User groups" %} +

    {% trans "Groups" %} - {% trans "New" %} + {% trans "New" %} {% trans "Participants" %}

    - + {% for group in groups %} - + {% empty %} - + {% endfor %}
    {% trans "User Group" %}{% trans "Group" %} {% trans "Actions" %}
    {{ group.name }}{{ group.name }} - {% if group.name != 'Anonymous' %} + {% if group.name|lower != 'anonymous' and group.name|lower != 'registered' %} {% endif %}
    {% trans "No user groups available." %}{% trans "No groups available." %}
    diff --git a/openslides/participant/templates/participant/group_widget.html b/openslides/participant/templates/participant/group_widget.html new file mode 100644 index 000000000..5c6e285a7 --- /dev/null +++ b/openslides/participant/templates/participant/group_widget.html @@ -0,0 +1,26 @@ +{% load i18n %} +{% load tags %} + +
      +{% for group in groups %} + {% if group.name != 'Anonymous' and group.name != 'Registered' %} +
    • + +
      +
      + + + + + + + + + + {{ group }} +
    • + {% endif %} +{% empty %} +
    • {% trans 'No groups available.' %}
    • +{% endfor %} +
    diff --git a/openslides/participant/templates/participant/import.html b/openslides/participant/templates/participant/import.html index 14b4ae516..74d475886 100644 --- a/openslides/participant/templates/participant/import.html +++ b/openslides/participant/templates/participant/import.html @@ -14,7 +14,8 @@

    {% trans 'Select a CSV file to import participants!' %}

    -

    {% trans 'Required comma separated values: {first_name, last_name, gender, group, type, committee, comment}' %} +

    {% trans 'Required comma separated values' %}: + ({% trans 'first_name, last_name, gender, structure level, type, committee, comment' %})
    {% trans 'Required CSV file encoding: UTF-8 (Unicode).' %}

    diff --git a/openslides/participant/templates/participant/overview.html b/openslides/participant/templates/participant/overview.html index 91796a651..9528c230e 100644 --- a/openslides/participant/templates/participant/overview.html +++ b/openslides/participant/templates/participant/overview.html @@ -60,11 +60,11 @@ - + + {% for level in structure_levels %} + {% endfor %} - - - - """ - % (message, url, csrf(request)['csrf_token'], _("Yes"), _("No"))) + messages.warning( + request, + """ + %s +
    + + + +
    + """ + % (message, url, csrf(request)['csrf_token'], _("Yes"), _("No"))) def del_confirm_form(request, object, name=None, delete_link=None): @@ -63,27 +59,28 @@ def del_confirm_form(request, object, name=None, delete_link=None): name = object if delete_link is None: delete_link = object.get_absolute_url('delete') - gen_confirm_form(request, _('Do you really want to delete %s?') + gen_confirm_form( + request, _('Do you really want to delete %s?') % html_strong(name), delete_link) -def render_response(req, *args, **kwargs): - kwargs['context_instance'] = RequestContext(req) - return render_to_response(*args, **kwargs) - - def template(template_name): + """ + Decorator to set a template for a view. + + Deprecated. Use class based views instead. + """ def renderer(func): def wrapper(request, *args, **kwargs): output = func(request, *args, **kwargs) if not isinstance(output, dict): return output context = {} - template_manipulation.send(sender='utils_template', request=request, - context=context) + template_manipulation.send( + sender='utils_template', request=request, context=context) output.update(context) - response = render_to_response(template_name, output, - context_instance=RequestContext(request)) + response = render_to_response( + template_name, output, context_instance=RequestContext(request)) if 'cookie' in output: response.set_cookie(output['cookie'][0], output['cookie'][1]) return response @@ -95,22 +92,26 @@ def permission_required(perm, login_url=None): """ Decorator for views that checks whether a user has a particular permission enabled, redirecting to the log-in page if necessary. + + Deprecated. """ def renderer(func): def wrapper(request, *args, **kw): if request.user.has_perm(perm): return func(request, *args, **kw) if request.user.is_authenticated(): - return render_to_forbitten(request) + return render_to_forbidden(request) return redirect(reverse('user_login')) return wrapper return renderer -def render_to_forbitten(request, error= - ugettext_lazy("Sorry, you have no rights to see this page.")): - return HttpResponseForbidden(render_to_string('403.html', - {'error': error}, context_instance=RequestContext(request))) +def render_to_forbidden(request, + error=ugettext_lazy("Sorry, you have no rights to see this page.")): + # TODO: Integrate this function into the PermissionMixin once the + # above function is deleted. + return HttpResponseForbidden(render_to_string( + '403.html', {'error': error}, context_instance=RequestContext(request))) def delete_default_permissions(**kwargs): @@ -118,27 +119,27 @@ def delete_default_permissions(**kwargs): Deletes the permissions, django creates by default for the admin. """ for p in Permission.objects.all(): - if p.codename.startswith('add') \ - or p.codename.startswith('delete') \ - or p.codename.startswith('change'): + if (p.codename.startswith('add') or + p.codename.startswith('delete') or + p.codename.startswith('change')): p.delete() def ajax_request(data): """ generates a HTTPResponse-Object with json-Data for a - ajax response + ajax response. + + Deprecated. """ return HttpResponse(json.dumps(data)) def _propper_unicode(text): - res = '' if not isinstance(text, unicode): - res = u"%s" % text.decode('UTF-8') + return u"%s" % text.decode('UTF-8') else: - res = text - return res + return text def decodedict(dict): diff --git a/openslides/utils/views.py b/openslides/utils/views.py index 25fb0119f..9e94d7191 100644 --- a/openslides/utils/views.py +++ b/openslides/utils/views.py @@ -22,8 +22,7 @@ except ImportError: # Is this exception realy necessary? from StringIO import StringIO -from reportlab.platypus import (SimpleDocTemplate, Paragraph, Frame, PageBreak, - Spacer, Table, LongTable, TableStyle, Image) +from reportlab.platypus import SimpleDocTemplate, Spacer from reportlab.lib.units import cm from django.contrib import messages @@ -34,9 +33,9 @@ from django.conf import settings from django.dispatch import receiver from django.http import HttpResponseServerError, HttpResponse, HttpResponseRedirect from django.utils.decorators import method_decorator -from django.utils.translation import ugettext as _, ugettext_noop, ugettext_lazy +from django.utils.translation import ugettext as _, ugettext_lazy from django.utils.importlib import import_module -from django.template import loader, RequestContext +from django.template import RequestContext from django.template.loader import render_to_string from django.views.generic import ( TemplateView as _TemplateView, @@ -50,9 +49,7 @@ from django.views.generic import ( from django.views.generic.detail import SingleObjectMixin from django.views.generic.list import TemplateResponseMixin -from openslides.config.models import config - -from openslides.utils.utils import render_to_forbitten, html_strong +from openslides.utils.utils import render_to_forbidden, html_strong from openslides.utils.signals import template_manipulation from openslides.utils.pdf import firstPage, laterPages @@ -64,8 +61,8 @@ View = _View class SetCookieMixin(object): def render_to_response(self, context, **response_kwargs): - response = TemplateResponseMixin.render_to_response(self, context, - **response_kwargs) + response = TemplateResponseMixin.render_to_response( + self, context, **response_kwargs) if 'cookie' in context: response.set_cookie(context['cookie'][0], context['cookie'][1]) return response @@ -80,20 +77,20 @@ class LoginMixin(object): class PermissionMixin(object): permission_required = NO_PERMISSION_REQUIRED - def has_permission(self, request): + def has_permission(self, request, *args, **kwargs): if self.permission_required == NO_PERMISSION_REQUIRED: return True else: return request.user.has_perm(self.permission_required) def dispatch(self, request, *args, **kwargs): - if not self.has_permission(request): + if not self.has_permission(request, *args, **kwargs): if not request.user.is_authenticated(): path = request.get_full_path() - return HttpResponseRedirect("%s?next=%s" % (settings.LOGIN_URL, - path)) + return HttpResponseRedirect( + "%s?next=%s" % (settings.LOGIN_URL, path)) else: - return render_to_forbitten(request) + return render_to_forbidden(request) return _View.dispatch(self, request, *args, **kwargs) @@ -110,58 +107,73 @@ class QuestionMixin(object): success_message = ugettext_lazy('Thank you for your answer') answer_options = [('yes', ugettext_lazy("Yes")), ('no', ugettext_lazy("No"))] - def get_answer_options(self): - return self.answer_options + def pre_redirect(self, request, *args, **kwargs): + # Prints the question in a GET request + self.confirm_form() def get_question(self): return unicode(self.question) + def get_answer_options(self): + return self.answer_options + + def get_answer_url(self): + try: + return self.answer_url + except AttributeError: + return self.request.path + + def confirm_form(self): + option_fields = "\n".join([ + '' % (option[0], unicode(option[1])) + for option in self.get_answer_options()]) + messages.warning( + self.request, + """ + %(message)s +
    + + %(option_fields)s +
    + """ % {'message': self.get_question(), + 'url': self.get_answer_url(), + 'csrf': csrf(self.request)['csrf_token'], + 'option_fields': option_fields}) + + def pre_post_redirect(self, request, *args, **kwargs): + # Reacts on the response of the user in a POST-request. + # TODO: call the methodes for all possible answers. + if self.get_answer() == 'yes': + self.case_yes() + messages.success(request, self.get_success_message()) + def get_answer(self): for option in self.get_answer_options(): if option[0] in self.request.POST: return option[0] return None - def get_answer_url(self): - return self.answer_url + def case_yes(self): + # TODO: raise a warning + pass - def confirm_form(self): - option_fields = "\n".join([ - '' % (option[0], unicode(option[1])) - for option in self.get_answer_options()]) - messages.warning(self.request, - """ - %(message)s -
    - - %(option_fields)s -
    - """ % { - 'message': self.get_question(), - 'url': self.get_answer_url(), - 'csrf': csrf(self.request)['csrf_token'], - 'option_fields': option_fields}) - - def pre_redirect(self, request, *args, **kwargs): - self.confirm_form(request, self.object) - - def pre_post_redirect(self, request, *args, **kwargs): - messages.success(request) + def get_success_message(self): + return self.success_message class TemplateView(PermissionMixin, _TemplateView): def get_context_data(self, **kwargs): context = super(TemplateView, self).get_context_data(**kwargs) - template_manipulation.send(sender=self.__class__, request=self.request, - context=context) + template_manipulation.send( + sender=self.__class__, request=self.request, context=context) return context class ListView(PermissionMixin, SetCookieMixin, _ListView): def get_context_data(self, **kwargs): context = super(ListView, self).get_context_data(**kwargs) - template_manipulation.send(sender=self.__class__, request=self.request, - context=context) + template_manipulation.send( + sender=self.__class__, request=self.request, context=context) return context @@ -202,8 +214,8 @@ class FormView(PermissionMixin, _FormView): def get_context_data(self, **kwargs): context = super(FormView, self).get_context_data(**kwargs) - template_manipulation.send(sender=self.__class__, request=self.request, - context=context) + template_manipulation.send( + sender=self.__class__, request=self.request, context=context) return context def form_invalid(self, form): @@ -220,8 +232,8 @@ class UpdateView(PermissionMixin, _UpdateView): def get_context_data(self, **kwargs): context = super(UpdateView, self).get_context_data(**kwargs) - template_manipulation.send(sender=self.__class__, request=self.request, - context=context) + template_manipulation.send( + sender=self.__class__, request=self.request, context=context) return context def form_invalid(self, form): @@ -241,8 +253,8 @@ class CreateView(PermissionMixin, _CreateView): def get_context_data(self, **kwargs): context = super(CreateView, self).get_context_data(**kwargs) - template_manipulation.send(sender=self.__class__, request=self.request, - context=context) + template_manipulation.send( + sender=self.__class__, request=self.request, context=context) return context def get_apply_url(self): @@ -266,33 +278,24 @@ class CreateView(PermissionMixin, _CreateView): pass -class DeleteView(RedirectView, SingleObjectMixin, QuestionMixin): - def get_question(self): - return _('Do you really want to delete %s?') % html_strong(self.object) - - def get_success_message(self): - return _('%s was successfully deleted.') % html_strong(self.object) - - def pre_redirect(self, request, *args, **kwargs): - self.confirm_form() - - def pre_post_redirect(self, request, *args, **kwargs): - if self.get_answer().lower() == 'yes': - self.object.delete() - messages.success(request, self.get_success_message()) - +class DeleteView(SingleObjectMixin, QuestionMixin, RedirectView): def get(self, request, *args, **kwargs): self.object = self.get_object() return super(DeleteView, self).get(request, *args, **kwargs) - def get_answer_url(self): - return self.object.get_absolute_url('delete') + def get_question(self): + return _('Do you really want to delete %s?') % html_strong(self.object) + + def case_yes(self): + self.object.delete() + + def get_success_message(self): + return _('%s was successfully deleted.') % html_strong(self.object) class DetailView(TemplateView, SingleObjectMixin): def get(self, request, *args, **kwargs): self.object = self.get_object() - context = self.get_context_data(object=self.object) return super(DetailView, self).get(request, *args, **kwargs) def get_context_data(self, **kwargs): @@ -322,8 +325,8 @@ class PDFView(PermissionMixin, View): return SimpleDocTemplate(buffer) def build_document(self, pdf_document, story): - pdf_document.build(story, onFirstPage=firstPage, - onLaterPages=laterPages) + pdf_document.build( + story, onFirstPage=firstPage, onLaterPages=laterPages) def render_to_response(self, filename): response = HttpResponse(mimetype='application/pdf') @@ -333,7 +336,7 @@ class PDFView(PermissionMixin, View): buffer = StringIO() pdf_document = self.get_template(buffer) pdf_document.title = self.get_document_title() - story = [Spacer(1, self.get_top_space()*cm)] + story = [Spacer(1, self.get_top_space() * cm)] self.append_to_pdf(story) @@ -344,51 +347,18 @@ class PDFView(PermissionMixin, View): response.write(pdf) return response - def get_filename(self): - return self.filename - def get(self, request, *args, **kwargs): return self.render_to_response(self.get_filename()) -class FrontPage(TemplateView): - template_name = 'front_page.html' - - def has_permission(self, request): - if request.user.is_authenticated() or config['system_enable_anonymous']: - return True - return False - - def get_context_data(self, **kwargs): - context = super(FrontPage, self).get_context_data(**kwargs) - apps = [] - for app in settings.INSTALLED_APPS: - try: - mod = import_module(app + '.views') - tab = mod.register_tab(self.request) - except (ImportError, AttributeError): - continue - if tab.permission: - apps.append(tab) - if config['show_help_text']: - messages.info(self.request, config['help_text']) - context.update({ - 'apps': apps, - 'title': config['frontpage_title'], - 'welcometext': config['frontpage_welcometext'], - }) - return context - - def server_error(request, template_name='500.html'): """ 500 error handler. Templates: `500.html` """ - t = loader.get_template("500.html") - return HttpResponseServerError(render_to_string('500.html', - context_instance=RequestContext(request))) + return HttpResponseServerError(render_to_string( + template_name, context_instance=RequestContext(request))) @receiver(template_manipulation, dispatch_uid="send_register_tab") diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..e43764974 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +Django==1.4.2 +django-mptt +reportlab +PIL +simplejson +coverage +django-discover-runner +pep8 diff --git a/setup.py b/setup.py index 6b2911a46..c283ec9aa 100644 --- a/setup.py +++ b/setup.py @@ -7,20 +7,27 @@ :license: GNU GPL, see LICENSE for more details. """ +# for python 2.5 support +from __future__ import with_statement + from setuptools import setup from setuptools import find_packages from openslides import get_version +with open('README.txt') as file: + long_description = file.read() + setup( name='openslides', - description='Presentation-System', + description='Presentation and assembly system', + long_description=long_description, version=get_version(), url='http://openslides.org', author='OpenSlides-Team', author_email='support@openslides.org', license='GPL2+', - packages=find_packages(), + packages=find_packages(exclude=['tests']), include_package_data = True, classifiers = [ # http://pypi.python.org/pypi?%3Aaction=list_classifiers diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/agenda/__init__.py b/tests/agenda/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/agenda/models.py b/tests/agenda/models.py new file mode 100644 index 000000000..f61694356 --- /dev/null +++ b/tests/agenda/models.py @@ -0,0 +1,18 @@ +from django.db import models + +from openslides.projector.projector import SlideMixin +from openslides.projector.api import register_slidemodel + + +class ReleatedItem(SlideMixin, models.Model): + prefix = 'releateditem' + + name = models.CharField(max_length='255') + + def get_agenda_title(self): + return self.name + + def get_agenda_title_supplement(self): + return 'test item' + +register_slidemodel(ReleatedItem) diff --git a/openslides/agenda/tests.py b/tests/agenda/tests.py similarity index 65% rename from openslides/agenda/tests.py rename to tests/agenda/tests.py index ca56e009d..05c582f66 100644 --- a/openslides/agenda/tests.py +++ b/tests/agenda/tests.py @@ -12,12 +12,15 @@ from django.test import TestCase from django.test.client import Client -from django.contrib.auth.models import User from django.db.models.query import EmptyQuerySet from openslides.projector.api import get_active_slide - +from openslides.participant.models import User from openslides.agenda.models import Item +from openslides.agenda.slides import agenda_show + +from .models import ReleatedItem + class ItemTest(TestCase): def setUp(self): @@ -25,6 +28,8 @@ class ItemTest(TestCase): self.item2 = Item.objects.create(title='item2') self.item3 = Item.objects.create(title='item1A', parent=self.item1) self.item4 = Item.objects.create(title='item1Aa', parent=self.item3) + self.releated = ReleatedItem.objects.create(name='foo') + self.item5 = Item.objects.create(title='item5', related_sid=self.releated.sid) def testClosed(self): self.assertFalse(self.item1.closed) @@ -46,10 +51,6 @@ class ItemTest(TestCase): self.assertTrue(self.item3 in self.item1.get_children()) self.assertFalse(self.item4 in self.item1.get_children()) - l = Item.objects.all() - self.assertEqual(str(l), - "[, , , ]") - def testForms(self): for item in Item.objects.all(): initial = item.weight_form.initial @@ -64,6 +65,36 @@ class ItemTest(TestCase): self.item1.related_sid = 'foobar' self.assertFalse(self.item1.get_related_slide() is None) + def test_title_supplement(self): + self.assertEqual(self.item1.get_title_supplement(), '') + + def test_delete_item(self): + new_item1 = Item.objects.create() + new_item2 = Item.objects.create(parent=new_item1) + new_item3 = Item.objects.create(parent=new_item2) + new_item1.delete() + self.assertTrue(new_item3 in Item.objects.all()) + new_item2.delete(with_children=True) + self.assertFalse(new_item3 in Item.objects.all()) + + def test_absolute_url(self): + self.assertEqual(self.item1.get_absolute_url(), '/agenda/1/') + self.assertEqual(self.item1.get_absolute_url('edit'), '/agenda/1/edit/') + self.assertEqual(self.item1.get_absolute_url('delete'), '/agenda/1/del/') + + def test_agenda_slide(self): + data = agenda_show() + self.assertEqual(list(data['items']), list(Item.objects.all().filter(parent=None))) + self.assertEqual(data['template'], 'projector/AgendaSummary.html') + self.assertEqual(data['title'], 'Agenda') + + def test_releated_item(self): + self.assertEqual(self.item5.get_title(), self.releated.name) + self.assertEqual(self.item5.get_title_supplement(), 'test item') + self.assertEqual(self.item5.get_related_type(), 'releateditem') + self.assertEqual(self.item5.print_related_type(), 'Releateditem') + + class ViewTest(TestCase): def setUp(self): @@ -71,8 +102,10 @@ class ViewTest(TestCase): self.item2 = Item.objects.create(title='item2') self.refreshItems() - self.admin = User.objects.create_user('testadmin', '', 'default') - self.anonym = User.objects.create_user('testanoym', '', 'default') + self.admin, created = User.objects.get_or_create(username='testadmin') + self.anonym, created = User.objects.get_or_create(username='testanonym') + self.admin.reset_password('default') + self.anonym.reset_password('default') self.admin.is_superuser = True self.admin.save() @@ -91,6 +124,13 @@ class ViewTest(TestCase): def anonymClient(self): return Client() + def testOverview(self): + c = self.adminClient + + response = c.get('/agenda/') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context['items']), len(Item.objects.all())) + def testActivate(self): c = self.adminClient @@ -122,6 +162,14 @@ class ViewTest(TestCase): self.refreshItems() self.assertEqual(response.status_code, 404) + # Test ajax + response = c.get('/agenda/%d/close/' % self.item1.id, + HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response.status_code, 200) + response = c.get('/agenda/%d/open/' % self.item1.id, + HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response.status_code, 200) + def testEdit(self): c = self.adminClient @@ -131,7 +179,7 @@ class ViewTest(TestCase): response = c.get('/agenda/%d/edit/' % 1000) self.assertEqual(response.status_code, 404) - data = {'title': 'newitem1', 'text': 'item1-text', 'weight':'0'} + data = {'title': 'newitem1', 'text': 'item1-text', 'weight': '0'} response = c.post('/agenda/%d/edit/' % self.item1.id, data) self.assertEqual(response.status_code, 302) self.refreshItems() @@ -143,4 +191,3 @@ class ViewTest(TestCase): self.assertEqual(response.status_code, 200) self.refreshItems() self.assertEqual(self.item1.title, 'newitem1') - diff --git a/tests/test_init.py b/tests/test_init.py new file mode 100644 index 000000000..411860d79 --- /dev/null +++ b/tests/test_init.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + Unit test for OpenSlides __init__.py + + :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. + :license: GNU GPL, see LICENSE for more details. +""" + +from django.test import TestCase + +from openslides import get_version, get_git_commit_id + +class InitTest(TestCase): + def test_get_version(self): + """ + Tests the method during development process and for releases. + """ + self.assertEqual(get_version(version=(1, 3, 0, 'beta', 2), release=False), '1.3b2-dev') + self.assertEqual(get_version(version=(1, 0, 0, 'final', 0), release=False), '1.0-dev') + self.assertEqual(get_version(version=(2, 5, 3, 'alpha', 0), release=False), '2.5.3a0-dev') + self.assertEqual(get_version(version=(1, 3, 0, 'beta', 2), release=True), '1.3b2') + self.assertEqual(get_version(version=(1, 0, 0, 'final', 0), release=True), '1.0') + self.assertEqual(get_version(version=(2, 5, 3, 'alpha', 0), release=True), '2.5.3a0') + self.assertEqual(get_version(version=(2, 5, 3, 'final', 0), release=True), '2.5.3') + + def test_get_git_commit_id(self): + """ + Tests the lenght of the git commit id. + """ + git_commit_id = get_git_commit_id() + if not git_commit_id == 'unknown': + self.assertEqual(len(git_commit_id), 40) diff --git a/openslides/application/tests.py b/tests/test_motion.py similarity index 74% rename from openslides/application/tests.py rename to tests/test_motion.py index bd3ecb73a..26c506c37 100644 --- a/openslides/application/tests.py +++ b/tests/test_motion.py @@ -1,32 +1,32 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ - openslides.application.tests + openslides.motion.tests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Unit tests for the application app. + Unit tests for the motion app. :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ from django.test import TestCase -from django.test.client import Client from openslides.participant.models import User -from openslides.application.models import Application, AVersion +from openslides.motion.models import Motion -class ApplicationTest(TestCase): + +class MotionTest(TestCase): def setUp(self): self.admin = User(username='testadmin') self.admin.save() self.anonym = User(username='testanoym') self.anonym.save() - self.app1 = Application(submitter=self.admin) + self.app1 = Motion(submitter=self.admin) self.app1.save() def refresh(self): - self.app1 = Application.objects.get(pk=self.app1.id) + self.app1 = Motion.objects.get(pk=self.app1.id) def testVersion(self): self.assertTrue(self.app1.versions.exists()) @@ -39,4 +39,3 @@ class ApplicationTest(TestCase): self.assertEqual(self.app1.versions.count(), 2) self.assertEqual(self.app1.last_version, self.app1.versions[1]) - diff --git a/openslides/participant/tests.py b/tests/test_participant.py similarity index 91% rename from openslides/participant/tests.py rename to tests/test_participant.py index 8418e994d..4227dc66b 100644 --- a/openslides/participant/tests.py +++ b/tests/test_participant.py @@ -11,8 +11,6 @@ """ from django.test import TestCase -from django.test.client import Client -from django.contrib.auth.hashers import check_password from openslides.utils.person import get_person, Persons from openslides.participant.api import gen_username, gen_password @@ -35,10 +33,10 @@ class UserTest(TestCase): self.assertEqual(self.django_user1, self.user1.django_user) def test_repr(self): - self.assertEqual(unicode(self.user1), u'Max Mustermann') + self.assertEqual(unicode(self.user1), 'Max Mustermann') - def test_name_surfix(self): - self.user1.category = u'München' + def test_name_suffix(self): + self.user1.structure_level = u'München' self.user1.save() self.assertEqual(unicode(self.user1), u'Max Mustermann (München)')