Merge remote branch 'upstream/master' into newtemplate

Conflicts:
	openslides/application/views.py
	openslides/assignment/templates/assignment/overview.html
	openslides/assignment/templates/assignment/view.html
	openslides/assignment/views.py
	openslides/locale/de/LC_MESSAGES/django.mo
	openslides/locale/de/LC_MESSAGES/django.po
	openslides/motion/templates/motion/config.html
	openslides/motion/templates/motion/edit.html
	openslides/motion/templates/motion/import.html
	openslides/motion/templates/motion/overview.html
	openslides/motion/templates/motion/poll_view.html
	openslides/motion/templates/motion/view.html
	openslides/participant/templates/participant/base_participant.html
	openslides/participant/templates/participant/config.html
	openslides/participant/templates/participant/group_edit.html
	openslides/participant/templates/participant/group_overview.html
	openslides/participant/templates/participant/overview.html
	openslides/projector/templates/projector/base_projector.html
	openslides/projector/templates/projector/dashboard.html
	openslides/projector/templates/projector/live_view_widget.html
	openslides/static/styles/base.css
	openslides/templates/base.html
	openslides/templates/front_page.html
	openslides/utils/utils.py
This commit is contained in:
Emanuel Schuetze 2013-01-02 23:01:43 +01:00
commit 4ae7f3e243
116 changed files with 7521 additions and 3935 deletions

4
.coveragerc Normal file
View File

@ -0,0 +1,4 @@
[run]
source=openslides
[report]
exclude_lines = def __(unicode|repr)__

4
.gitattributes vendored Normal file
View File

@ -0,0 +1,4 @@
.gitattributes export-ignore
.gitignore export-ignore
/docs export-ignore
/extras export-ignore

7
.gitignore vendored
View File

@ -11,3 +11,10 @@ docs/_build/*
*.egg-info
build/*
dist/*
.DS_Store
settings.py
versiontools*
# Unit test / coverage reports
.coverage
htmlcov

11
.travis.yml Normal file
View File

@ -0,0 +1,11 @@
language: python
python:
- "2.5"
- "2.6"
- "2.7"
install:
- pip install -r requirements.txt --use-mirrors
- python extras/scripts/create_local_settings.py
script:
- coverage run ./manage.py test tests && coverage report -m
- pep8 --max-line-length=150 --exclude="urls.py,motion/" --statistics openslides

View File

@ -5,3 +5,5 @@ Authors of OpenSlides in chronological order of first contribution:
Norman Jäckel <mail@normanjaeckel.de>
René Köcher <shirk@bitspin.org>
Andy Kittner <andkit@gmx.net>
Moira Brülisauer <moira.bruelisauer@piratenpartei.ch> (French translation)
Alexis Roussel <alexis.roussel@partipirate.ch> (French translation)

View File

@ -2,6 +2,80 @@ CHANGELOG of OpenSlides
http://openslides.org
Version 1.3 (2012-12-10)
========================
[http://dev.openslides.org/milestone/1.3]
Projector:
- New public dashboard which allows access for all users per default. (#361)
(changed from the old, limited projector control page)
- New dashboard widgets:
* welcome widget (shows static welcome title and text)
* participant widget
* group widget
* personal widget (shows my motions and my elections)
- Hide scrollbar in projector view.
- Added cache for AJAX version of the projector view.
- Moved projector control icons into projector live widget. (#403)
- New weight field for custom slides (to order custom slides in widget).
- Fixed drag'n'drop behaviour of widgets into empty dashboard column.
- Fixed permissions for agenda, motion and assignment widgets (set to projector.can_manage_projector).
Agenda:
- Fixed slide error if agenda item deleted. (#330)
Motions:
- Translation: Changed 'application' to 'motion'.
- Fixed: Manager could not edit supporters. (#336)
- Fixed attribute error for anonymous users in motion view. (#329)
- Set default sorting of motions by number (in widget).
- CSV import allows to import group as submitter. (#419)
- Updated motion code for new user API.
- Rewrote motion views as class based views.
Elections:
- User can block himself/herself from candidate list after delete his/her candidature.
- Show blocked candidates in separate list.
- Mark elected candidates in candidate list. (#374)
- Show linebreaks in description. (#392)
- Set default sorting of elections by name (in widget).
- Fixed redirect from a poll which does not exists anymore.
- Changed default permissions of anonymous user to see elections. (#334)
- Updated assignment code for new user API.
Participants:
- New user and group API.
- New group option to handle a group as participant (and thus e.g. as submitter of motion).
- CSV import does not delete existing users anymore and append users as new users.
- New user field 'about me'. (#390)
- New config option for sorting users by first or last name (in participant lists, elections and motions). (#303)
- Allowed whitespaces in username, default: <firstname lastname>. (#326)
- New user and group slides. (#176)
- Don't allow to deactivate the administrator or themself.
- Don't allow to delete themself.
- Renamed participant field 'groups' to 'structure level' (German: Gliederungsebene).
- Rewrote participant views as class based views.
- Made OpenSlides user a child model of Django user model.
- Appended tests.
- Fixed error to allow admins to delete anonymous group
Other:
- Added French translation (Thanks to Moira).
- Updated setup.py to make an openslides python package.
- Removed frontpage (welcome widget contains it's content) and redirect '/' to dashboard url.
- Added LOCALE_PATHS to openslides_settings to avoid deprication in Django 1.5.
- Redesigned the DeleteView (append QuestionMixin to send question via the django message API).
- Fixed encoding error in settings.py. (#349)
- Renamed openslides_settings.py to openslides_global_settings.py.
- New default path to database file (XDG_DATA_HOME, e.g. ~/.local/share/openslides/).
- New default path to settings file (XDG_CONFIG_HOME, e.g. ~/.config/openslides/).
- Added special handling to determine location of database and settings file in portable version.
- Don't use similar characters in generated passwords (no 'Il10oO').
- Localised the datetime in PDF header. (#296)
- Used specific session cookie name. (#332)
- Moved code repository from hg to git (incl. some required updates, e.g. version string function).
- Updated German translations.
- Several code optimizations.
- Several minor and medium issues and errors were fixed.
Version 1.2 (2012-07-25)
========================
[http://dev.openslides.org/milestone/1.2]

View File

@ -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.

View File

@ -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 *

View File

@ -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

View File

@ -0,0 +1,15 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
script_path = os.path.realpath(os.path.dirname(__file__))
sys.path.append(os.path.join(script_path, '..', '..'))
from openslides.main import create_settings
if __name__ == "__main__":
cwd = os.getcwd()
create_settings(os.path.join(cwd, 'settings.py'),
os.path.join(cwd, 'database.sqlite'))

View File

@ -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.

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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.

View File

@ -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');

View File

@ -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 = "&nbsp;" * 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')]

View File

@ -1,34 +0,0 @@
{% load staticfiles %}
{% load i18n %}
{% load tags %}
<ul style="line-height: 180%">
{% for application in applications %}
<li class="{% if application.active %}activeline{% endif %}">
<a href="{% url projector_activate_slide application.sid %}" class="activate_link {% if application.active %}active{% endif %}">
<div></div>
</a>
<a href="{% model_url application 'delete' %}" title="{% trans 'Delete' %}" class="icon delete right">
<span></span>
</a>
<a href="{% model_url application 'edit' %}" title="{% trans 'Edit' %}" class="icon edit right">
<span></span>
</a>
<a href="{% url projctor_preview_slide application.sid %}" title="{% trans 'Preview' %}" class="icon preview right">
<span></span>
</a>
<a href="{% model_url application 'view' %}">
{{ application.public_version.title }}
</a>
({% trans "motion" %}
{% if application.number %}
{{ application.number }})
{% else %}
<i>[{% trans "no number" %}]</i>)
{% endif %}
</li>
{% empty %}
<li>{% trans 'No motion available.' %}</li>
{% endfor %}
</ul>

View File

@ -1,141 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.application.urls
~~~~~~~~~~~~~~~~~~~~~~~~~~~
URL list for the application app.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from django.conf.urls.defaults import url, patterns
from openslides.application.views import (ApplicationDelete, ViewPoll,
ApplicationPDF, ApplicationPollPDF, CreateAgendaItem)
urlpatterns = patterns('openslides.application.views',
url(r'^$',
'overview',
name='application_overview',
),
url(r'^(?P<application_id>\d+)/$',
'view',
name='application_view',
),
url(r'^(?P<application_id>\d+)/agenda/$',
CreateAgendaItem.as_view(),
name='application_create_agenda',
),
url(r'^(?P<application_id>\d+)/newest/$',
'view',
{'newest': True},
name='application_view_newest',
),
url(r'^new/$',
'edit',
name='application_new',
),
url(r'^import/$',
'application_import',
name='application_import',
),
url(r'^(?P<application_id>\d+)/edit/$',
'edit',
name='application_edit',
),
url(r'^(?P<application_id>\d+)/del/$',
ApplicationDelete.as_view(),
name='application_delete',
),
url(r'^del/$',
ApplicationDelete.as_view(),
{ 'application_id' : None , 'application_ids' : None },
name='application_delete',
),
url(r'^(?P<application_id>\d+)/setnumber/$',
'set_number',
name='application_set_number',
),
url(r'^(?P<application_id>\d+)/setstatus/(?P<status>[a-z]{3})/$',
'set_status',
name='application_set_status',
),
url(r'^(?P<application_id>\d+)/permit/$',
'permit',
name='application_permit',
),
url(r'^version/(?P<aversion_id>\d+)/permit/$',
'permit_version',
name='application_version_permit',
),
url(r'^version/(?P<aversion_id>\d+)/reject/$',
'reject_version',
name='application_version_reject',
),
url(r'^(?P<application_id>\d+)/notpermit/$',
'notpermit',
name='application_notpermit',
),
url(r'^(?P<application_id>\d+)/reset/$',
'reset',
name='application_reset',
),
url(r'^(?P<application_id>\d+)/support/$',
'support',
name='application_support',
),
url(r'^(?P<application_id>\d+)/unsupport/$',
'unsupport',
name='application_unsupport',
),
url(r'^(?P<application_id>\d+)/gen_poll/$',
'gen_poll',
name='application_gen_poll',
),
url(r'^print/$',
ApplicationPDF.as_view(),
{'application_id': None},
name='print_applications',
),
url(r'^(?P<application_id>\d+)/print/$',
ApplicationPDF.as_view(),
name='print_application',
),
url(r'^poll/(?P<poll_id>\d+)/print/$',
ApplicationPollPDF.as_view(),
name='print_application_poll',
),
url(r'^poll/(?P<poll_id>\d+)/$',
ViewPoll.as_view(),
name='application_poll_view',
),
url(r'^poll/(?P<poll_id>\d+)/del/$',
'delete_poll',
name='application_poll_delete',
),
)

View File

@ -1,925 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.application.views
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Views for the application app.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
# for python 2.5 support
from __future__ import with_statement
import csv
import os
try:
from urlparse import parse_qs
except ImportError: # python <= 2.5
from cgi import parse_qs
from reportlab.lib import colors
from reportlab.lib.units import cm
from reportlab.platypus import (SimpleDocTemplate, PageBreak, Paragraph, Spacer,
Table, TableStyle)
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.core.context_processors import csrf
from django.core.urlresolvers import reverse
from django.db import transaction
from django.shortcuts import redirect
from django.utils.translation import ugettext as _, ungettext
from openslides.utils import csv_ext
from openslides.utils.pdf import stylesheet
from openslides.utils.template import Tab
from openslides.utils.utils import (template, permission_required,
del_confirm_form, gen_confirm_form)
from openslides.utils.views import PDFView, RedirectView, DeleteView, FormView
from openslides.utils.person import get_person
from openslides.config.models import config
from openslides.projector.projector import Widget
from openslides.poll.views import PollFormView
from openslides.participant.api import gen_username, gen_password
from openslides.participant.models import User
from openslides.agenda.models import Item
from openslides.application.models import Application, AVersion, ApplicationPoll
from openslides.application.forms import (ApplicationForm,
ApplicationFormTrivialChanges, ApplicationManagerForm,
ApplicationManagerFormSupporter, ApplicationImportForm, ConfigForm)
@permission_required('application.can_see_application')
@template('application/overview.html')
def overview(request):
"""
View all applications
"""
try:
sortfilter = parse_qs(request.COOKIES['votecollector_sortfilter'])
for value in sortfilter:
sortfilter[value] = sortfilter[value][0]
except KeyError:
sortfilter = {}
for value in [u'sort', u'reverse', u'number', u'status', u'needsup', u'statusvalue']:
if value in request.REQUEST:
if request.REQUEST[value] == '0':
try:
del sortfilter[value]
except KeyError:
pass
else:
sortfilter[value] = request.REQUEST[value]
query = Application.objects.all()
if 'number' in sortfilter:
query = query.filter(number=None)
if 'status' in sortfilter:
if 'statusvalue' in sortfilter and 'on' in sortfilter['status']:
query = query.filter(status__iexact=sortfilter['statusvalue'])
if 'sort' in sortfilter:
if sortfilter['sort'] == 'title':
sort = 'aversion__title'
elif sortfilter['sort'] == 'time':
sort = 'aversion__time'
else:
sort = sortfilter['sort']
query = query.order_by(sort)
if sort.startswith('aversion_'):
# limit result to last version of an application
query = query.filter(aversion__id__in=[x.last_version.id for x in Application.objects.all()])
if 'reverse' in sortfilter:
query = query.reverse()
# todo: rewrite this with a .filter()
if 'needsup' in sortfilter:
applications = []
for application in query.all():
if not application.enough_supporters:
applications.append(application)
else:
applications = query
if type(applications) is not list:
applications = list(query.all())
# not the most efficient way to do this but 'get_allowed_actions'
# is not callable from within djangos templates..
for (i, application) in enumerate(applications):
try:
applications[i] = {
'actions' : application.get_allowed_actions(request.user),
'application' : application
}
except:
# todo: except what?
applications[i] = {
'actions' : [],
'application' : application
}
return {
'applications': applications,
'min_supporters': int(config['application_min_supporters']),
}
@permission_required('application.can_see_application')
@template('application/view.html')
def view(request, application_id, newest=False):
"""
View one application.
"""
application = Application.objects.get(pk=application_id)
if newest:
version = application.last_version
else:
version = application.public_version
revisions = application.versions
actions = application.get_allowed_actions(user=request.user)
return {
'application': application,
'revisions': revisions,
'actions': actions,
'min_supporters': int(config['application_min_supporters']),
'version': version,
#'results': application.results
}
@login_required
@template('application/edit.html')
def edit(request, application_id=None):
"""
View a form to edit or create a application.
"""
if request.user.has_perm('application.can_manage_application'):
is_manager = True
else:
is_manager = False
if not is_manager \
and not request.user.has_perm('application.can_create_application'):
messages.error(request, _("You have not the necessary rights to create or edit motions."))
return redirect(reverse('application_overview'))
if application_id is not None:
application = Application.objects.get(id=application_id)
if not request.user == application.submitter and not is_manager:
messages.error(request, _("You can not edit this motion. You are not the submitter."))
return redirect(reverse('application_view', args=[application.id]))
actions = application.get_allowed_actions(user=request.user)
else:
application = None
actions = None
formclass = ApplicationFormTrivialChanges \
if config['application_allow_trivial_change'] and application_id \
else ApplicationForm
managerformclass = ApplicationManagerFormSupporter \
if config['application_min_supporters'] \
else ApplicationManagerForm
if request.method == 'POST':
dataform = formclass(request.POST, prefix="data")
valid = dataform.is_valid()
if is_manager:
managerform = managerformclass(request.POST,
instance=application,
prefix="manager")
valid = valid and managerform.is_valid()
else:
managerform = None
if valid:
del_supporters = True
if is_manager:
if application: # Edit application
original_supporters = list(application.supporters)
else:
original_supporters = []
application = managerform.save(commit=False)
elif application_id is None:
application = Application(submitter=request.user)
application.title = dataform.cleaned_data['title']
application.text = dataform.cleaned_data['text']
application.reason = dataform.cleaned_data['reason']
try:
trivial_change = config['application_allow_trivial_change'] \
and dataform.cleaned_data['trivial_change']
except KeyError:
trivial_change = False
application.save(request.user, trivial_change=trivial_change)
if is_manager:
try:
new_supporters = set(managerform.cleaned_data['supporter'])
except KeyError:
# The managerform has no field for the supporters
pass
else:
old_supporters = set(application.supporters)
# add new supporters
for supporter in new_supporters.difference(old_supporters):
application.support(supporter)
# remove old supporters
for supporter in old_supporters.difference(new_supporters):
application.unsupport(supporter)
if application_id is None:
messages.success(request, _('New motion was successfully created.'))
else:
messages.success(request, _('Motion was successfully modified.'))
if not 'apply' in request.POST:
return redirect(reverse('application_view', args=[application.id]))
if application_id is None:
return redirect(reverse('application_edit', args=[application.id]))
else:
messages.error(request, _('Please check the form for errors.'))
else:
if application_id is None:
initial = {'text': config['application_preamble']}
else:
if application.status == "pub" and application.supporters:
if request.user.has_perm('application.can_manage_application'):
messages.warning(request, _("Attention: Do you really want to edit this motion? The supporters will <b>not</b> be removed automatically because you can manage motions. Please check if the supports are valid after your changing!"))
else:
messages.warning(request, _("Attention: Do you really want to edit this motion? All <b>%s</b> supporters will be removed! Try to convince the supporters again.") % application.supporter.count() )
initial = {'title': application.title,
'text': application.text,
'reason': application.reason}
dataform = formclass(initial=initial, prefix="data")
if is_manager:
if application_id is None:
initial = {'submitter': request.user.person_id}
else:
initial = {'submitter': application.submitter.person_id,
'supporter': [supporter.person_id for supporter in application.supporters]}
managerform = managerformclass(initial=initial,
instance=application, prefix="manager")
else:
managerform = None
return {
'form': dataform,
'managerform': managerform,
'application': application,
'actions': actions,
}
@permission_required('application.can_manage_application')
@template('application/view.html')
def set_number(request, application_id):
"""
set a number for an application.
"""
try:
Application.objects.get(pk=application_id).set_number(user=request.user)
messages.success(request, _("Motion number was successfully set."))
except Application.DoesNotExist:
pass
except NameError:
pass
return redirect(reverse('application_view', args=[application_id]))
@permission_required('application.can_manage_application')
@template('application/view.html')
def permit(request, application_id):
"""
permit an application.
"""
try:
Application.objects.get(pk=application_id).permit(user=request.user)
messages.success(request, _("Motion was successfully authorized."))
except Application.DoesNotExist:
pass
except NameError, e:
messages.error(request, e)
return redirect(reverse('application_view', args=[application_id]))
@permission_required('application.can_manage_application')
@template('application/view.html')
def notpermit(request, application_id):
"""
reject (not permit) an application.
"""
try:
Application.objects.get(pk=application_id).notpermit(user=request.user)
messages.success(request, _("Motion was successfully rejected."))
except Application.DoesNotExist:
pass
except NameError, e:
messages.error(request, e)
return redirect(reverse('application_view', args=[application_id]))
@template('application/view.html')
def set_status(request, application_id=None, status=None):
"""
set a status of an application.
"""
try:
if status is not None:
application = Application.objects.get(pk=application_id)
application.set_status(user=request.user, status=status)
messages.success(request, _("Motion status was set to: <b>%s</b>.") % application.get_status_display())
except Application.DoesNotExist:
pass
except NameError, e:
messages.error(request, e)
return redirect(reverse('application_view', args=[application_id]))
@permission_required('application.can_manage_application')
@template('application/view.html')
def reset(request, application_id):
"""
reset an application.
"""
try:
Application.objects.get(pk=application_id).reset(user=request.user)
messages.success(request, _("Motion status was reset.") )
except Application.DoesNotExist:
pass
return redirect(reverse('application_view', args=[application_id]))
@permission_required('application.can_support_application')
@template('application/view.html')
def support(request, application_id):
"""
support an application.
"""
try:
Application.objects.get(pk=application_id).support(user=request.user)
messages.success(request, _("You have support the motion successfully.") )
except Application.DoesNotExist:
pass
return redirect(reverse('application_view', args=[application_id]))
@permission_required('application.can_support_application')
@template('application/view.html')
def unsupport(request, application_id):
"""
unsupport an application.
"""
try:
Application.objects.get(pk=application_id).unsupport(user=request.user)
messages.success(request, _("You have unsupport the motion successfully.") )
except Application.DoesNotExist:
pass
return redirect(reverse('application_view', args=[application_id]))
@permission_required('application.can_manage_application')
@template('application/view.html')
def gen_poll(request, application_id):
"""
gen a poll for this application.
"""
try:
poll = Application.objects.get(pk=application_id).gen_poll(user=request.user)
messages.success(request, _("New vote was successfully created.") )
except Application.DoesNotExist:
pass # TODO: do not call poll after this excaption
return redirect(reverse('application_poll_view', args=[poll.id]))
@permission_required('application.can_manage_application')
def delete_poll(request, poll_id):
"""
delete a poll from this application
"""
poll = ApplicationPoll.objects.get(pk=poll_id)
application = poll.application
count = application.polls.filter(id__lte=poll_id).count()
if request.method == 'POST':
poll.delete()
application.writelog(_("Poll deleted"), request.user)
messages.success(request, _('Poll was successfully deleted.'))
else:
del_confirm_form(request, poll, name=_("the %s. poll") % count, delete_link=reverse('application_poll_delete', args=[poll_id]))
return redirect(reverse('application_view', args=[application.id]))
class ApplicationDelete(DeleteView):
"""
Delete one or more Applications.
"""
permission_required = 'application.can_manage_application'
model = Application
url = 'application_overview'
def get_object(self):
self.applications = []
if self.kwargs.get('application_id', None):
try:
return Application.objects.get(id=int(self.kwargs['application_id']))
except Application.DoesNotExist:
return None
if self.kwargs.get('application_ids', []):
for appid in self.kwargs['application_ids']:
try:
self.applications.append(Application.objects.get(id=int(appid)))
except Application.DoesNotExist:
pass
if self.applications:
return self.applications[0]
return None
def pre_post_redirect(self, request, *args, **kwargs):
self.object = self.get_object()
if len(self.applications):
for application in self.applications:
if not 'delete' in application.get_allowed_actions(user=request.user):
messages.error(request, _("You can not delete motion <b>%s</b>.") % application)
continue
title = application.title
application.delete(force=True)
messages.success(request, _("Motion <b>%s</b> was successfully deleted.") % title)
elif self.object:
if not 'delete' in self.object.get_allowed_actions(user=request.user):
messages.error(request, _("You can not delete motion <b>%s</b>.") % self.object)
else:
title = self.object.title
self.object.delete(force=True)
messages.success(request, _("Motion <b>%s</b> was successfully deleted.") % title)
else:
messages.error(request, _("Invalid request"))
def gen_confirm_form(self, request, message, url):
formbase = '%s<form action="%s" method="post"><input type="hidden" value="%s" name="csrfmiddlewaretoken">' % (message, url, csrf(request)['csrf_token'])
if len(self.applications):
for application in self.applications:
formbase += '<input type="hidden" name="application_ids" value="%s">' % application.id
elif self.object:
formbase += '<input type="hidden" name="application_id" value="%s">' % self.object.id
formbase +='<input type="submit" value="%s" /> <input type="button" value="%s"></form>' % (_("Yes"), _("No"))
messages.warning(request, formbase)
def confirm_form(self, request, object, item=None):
self.object = self.get_object()
if len(self.applications):
self.gen_confirm_form(request, _('Do you really want to delete multiple motions?') % self.object.get_absolute_url('delete'))
else:
self.gen_confirm_form(request, _('Do you really want to delete <b>%s</b>?') % self.object, self.object.get_absolute_url('delete'))
class ViewPoll(PollFormView):
permission_required = 'application.can_manage_application'
poll_class = ApplicationPoll
template_name = 'application/poll_view.html'
def get_context_data(self, **kwargs):
context = super(ViewPoll, self).get_context_data(**kwargs)
self.application = self.poll.get_application()
context['application'] = self.application
context['ballot'] = self.poll.get_ballot()
context['actions'] = self.application.get_allowed_actions(user=self.request.user)
return context
def get_modelform_class(self):
cls = super(ViewPoll, self).get_modelform_class()
user = self.request.user
class ViewPollFormClass(cls):
def save(self, commit = True):
instance = super(ViewPollFormClass, self).save(commit)
application = instance.application
application.writelog(_("Poll was updated"), user)
return instance
return ViewPollFormClass
def get_success_url(self):
if not 'apply' in self.request.POST:
return reverse('application_view', args=[self.poll.application.id])
return ''
@permission_required('application.can_manage_application')
def permit_version(request, aversion_id):
aversion = AVersion.objects.get(pk=aversion_id)
application = aversion.application
if request.method == 'POST':
application.accept_version(aversion, user=request.user)
messages.success(request, _("Version <b>%s</b> accepted.") % (aversion.aid))
else:
gen_confirm_form(request, _('Do you really want to authorize version <b>%s</b>?') % aversion.aid, reverse('application_version_permit', args=[aversion.id]))
return redirect(reverse('application_view', args=[application.id]))
@permission_required('application.can_manage_application')
def reject_version(request, aversion_id):
aversion = AVersion.objects.get(pk=aversion_id)
application = aversion.application
if request.method == 'POST':
if application.reject_version(aversion, user=request.user):
messages.success(request, _("Version <b>%s</b> rejected.") % (aversion.aid))
else:
messages.error(request, _("ERROR by rejecting the version.") )
else:
gen_confirm_form(request, _('Do you really want to reject version <b>%s</b>?') % aversion.aid, reverse('application_version_reject', args=[aversion.id]))
return redirect(reverse('application_view', args=[application.id]))
@permission_required('application.can_manage_application')
@template('application/import.html')
def application_import(request):
if request.method == 'POST':
form = ApplicationImportForm(request.POST, request.FILES)
if form.is_valid():
import_permitted = form.cleaned_data['import_permitted']
try:
# check for valid encoding (will raise UnicodeDecodeError if not)
request.FILES['csvfile'].read().decode('utf-8')
request.FILES['csvfile'].seek(0)
users_generated = 0
applications_generated = 0
applications_modified = 0
with transaction.commit_on_success():
dialect = csv.Sniffer().sniff(request.FILES['csvfile'].readline())
dialect = csv_ext.patchup(dialect)
request.FILES['csvfile'].seek(0)
for (lno, line) in enumerate(csv.reader(request.FILES['csvfile'], dialect=dialect)):
# basic input verification
if lno < 1:
continue
try:
(number, title, text, reason, first_name, last_name) = line[:6]
except ValueError:
messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1))
continue
form = ApplicationForm({'title': title, 'text': text, 'reason': reason})
if not form.is_valid():
messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1))
continue
if number:
try:
number = abs(long(number))
if number < 1:
messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1))
continue
except ValueError:
messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1))
continue
# fetch existing users or create new users as needed
try:
user = User.objects.get(first_name=first_name, last_name=last_name)
except User.DoesNotExist:
user = None
if user is None:
user = User()
user.last_name = last_name
user.first_name = first_name
user.username = gen_username(first_name, last_name)
user.save()
profile = Profile()
profile.user = user
profile.group = ''
profile.committee = ''
profile.gender = 'none'
profile.type = 'guest'
profile.firstpassword = gen_password()
profile.user.set_password(profile.firstpassword)
profile.save()
users_generated += 1
# create / modify the application
application = None
if number:
try:
application = Application.objects.get(number=number)
applications_modified += 1
except Application.DoesNotExist:
application = None
if application is None:
application = Application(submitter=user)
if number:
application.number = number
applications_generated += 1
application.title = form.cleaned_data['title']
application.text = form.cleaned_data['text']
application.reason = form.cleaned_data['reason']
if import_permitted:
application.status = 'per'
application.save(user, trivial_change=True)
if applications_generated:
messages.success(request, ungettext('%d motion was successfully imported.',
'%d motions were successfully imported.', applications_generated) % applications_generated)
if applications_modified:
messages.success(request, ungettext('%d motion was successfully modified.',
'%d motions were successfully modified.', applications_modified) % applications_modified)
if users_generated:
messages.success(request, ungettext('%d new user was added.', '%d new users were added.', users_generated) % users_generated)
return redirect(reverse('application_overview'))
except csv.Error:
message.error(request, _('Import aborted because of severe errors in the input file.'))
except UnicodeDecodeError:
messages.error(request, _('Import file has wrong character encoding, only UTF-8 is supported!'))
else:
messages.error(request, _('Please check the form for errors.'))
else:
messages.warning(request, _("Attention: Existing motions will be modified if you import new motions with the same number."))
messages.warning(request, _("Attention: Importing an motions without a number multiple times will create duplicates."))
form = ApplicationImportForm()
return {
'form': form,
}
class CreateAgendaItem(RedirectView):
permission_required = 'agenda.can_manage_agenda'
def pre_redirect(self, request, *args, **kwargs):
self.application = Application.objects.get(pk=kwargs['application_id'])
self.item = Item(related_sid=self.application.sid)
self.item.save()
def get_redirect_url(self, **kwargs):
return reverse('item_overview')
class ApplicationPDF(PDFView):
permission_required = 'application.can_see_application'
top_space = 0
def get_filename(self):
application_id = self.kwargs['application_id']
if application_id is None:
filename = _("Applications")
else:
application = Application.objects.get(id=application_id)
if application.number:
number = application.number
else:
number = ""
filename = u'%s%s' % (_("Application"), str(number))
return filename
def append_to_pdf(self, story):
application_id = self.kwargs['application_id']
if application_id is None: #print all applications
title = config["application_pdf_title"]
story.append(Paragraph(title, stylesheet['Heading1']))
preamble = config["application_pdf_preamble"]
if preamble:
story.append(Paragraph("%s" % preamble.replace('\r\n','<br/>'), stylesheet['Paragraph']))
story.append(Spacer(0,0.75*cm))
applications = Application.objects.order_by('number')
if not applications: # No applications existing
story.append(Paragraph(_("No motions available."), stylesheet['Heading3']))
else: # Print all Applications
# List of applications
for application in applications:
if application.number:
story.append(Paragraph(_("Motion No.")+" %s: %s" % (application.number, application.title), stylesheet['Heading3']))
else:
story.append(Paragraph(_("Motion No.")+"&nbsp;&nbsp;&nbsp;: %s" % (application.title), stylesheet['Heading3']))
# Applications details (each application on single page)
for application in applications:
story.append(PageBreak())
story = self.get_application(application, story)
else: # print selected application
application = Application.objects.get(id=application_id)
story = self.get_application(application, story)
def get_application(self, application, story):
# application number
if application.number:
story.append(Paragraph(_("Motion No.")+" %s" % application.number, stylesheet['Heading1']))
else:
story.append(Paragraph(_("Motion No."), stylesheet['Heading1']))
# submitter
cell1a = []
cell1a.append(Spacer(0,0.2*cm))
cell1a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Submitter"), stylesheet['Heading4']))
cell1b = []
cell1b.append(Spacer(0,0.2*cm))
if application.status == "pub":
cell1b.append(Paragraph("__________________________________________",stylesheet['Signaturefield']))
cell1b.append(Spacer(0,0.1*cm))
cell1b.append(Paragraph("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"+unicode(application.submitter), stylesheet['Small']))
cell1b.append(Spacer(0,0.2*cm))
else:
cell1b.append(Paragraph(unicode(application.submitter), stylesheet['Normal']))
# supporters
cell2a = []
cell2b = []
if config['application_min_supporters']:
cell2a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font><seqreset id='counter'>" % _("Supporters"), stylesheet['Heading4']))
for supporter in application.supporters:
cell2b.append(Paragraph("<seq id='counter'/>.&nbsp; %s" % unicode(supporter), stylesheet['Signaturefield']))
if application.status == "pub":
for x in range(0,application.missing_supporters):
cell2b.append(Paragraph("<seq id='counter'/>.&nbsp; __________________________________________",stylesheet['Signaturefield']))
cell2b.append(Spacer(0,0.2*cm))
# status
note = ""
for n in application.notes:
note += "%s " % unicode(n)
cell3a = []
cell3a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Status"), stylesheet['Heading4']))
cell3b = []
if note != "":
if application.status == "pub":
cell3b.append(Paragraph(note, stylesheet['Normal']))
else:
cell3b.append(Paragraph("%s | %s" % (application.get_status_display(), note), stylesheet['Normal']))
else:
cell3b.append(Paragraph("%s" % application.get_status_display(), stylesheet['Normal']))
# table
data = []
data.append([cell1a,cell1b])
data.append([cell2a,cell2b])
data.append([cell3a,cell3b])
# Version number (aid)
if application.public_version.aid > 1:
cell4a = []
cell4a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Version"), stylesheet['Heading4']))
cell4b = []
cell4b.append(Paragraph("%s" % application.public_version.aid, stylesheet['Normal']))
data.append([cell4a,cell4b])
poll_results = application.get_poll_results()
# voting results
if poll_results:
cell5a = []
cell5a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Vote results"), stylesheet['Heading4']))
cell5b = []
ballotcounter = 0
for result in poll_results:
ballotcounter += 1
if len(poll_results) > 1:
cell5b.append(Paragraph("%s. %s" % (ballotcounter, _("Vote")), stylesheet['Bold']))
cell5b.append(Paragraph("%s: %s <br/> %s: %s <br/> %s: %s <br/> %s: %s <br/> %s: %s" % (_("Yes"), result[0], _("No"), result[1], _("Abstention"), result[2], _("Invalid"), result[3], _("Votes cast"), result[4]), stylesheet['Normal']))
cell5b.append(Spacer(0,0.2*cm))
data.append([cell5a,cell5b])
t=Table(data)
t._argW[0]=4.5*cm
t._argW[1]=11*cm
t.setStyle(TableStyle([ ('BOX', (0,0), (-1,-1), 1, colors.black),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
story.append(t)
story.append(Spacer(0,1*cm))
# title
story.append(Paragraph(application.public_version.title, stylesheet['Heading3']))
# text
story.append(Paragraph("%s" % application.public_version.text.replace('\r\n','<br/>'), stylesheet['Paragraph']))
# reason
if application.public_version.reason:
story.append(Paragraph(_("Reason")+":", stylesheet['Heading3']))
story.append(Paragraph("%s" % application.public_version.reason.replace('\r\n','<br/>'), stylesheet['Paragraph']))
return story
class ApplicationPollPDF(PDFView):
permission_required = 'application.can_manage_application'
top_space = 0
def get(self, request, *args, **kwargs):
self.poll = ApplicationPoll.objects.get(id=self.kwargs['poll_id'])
return super(ApplicationPollPDF, self).get(request, *args, **kwargs)
def get_filename(self):
filename = u'%s%s_%s' % (_("Application"), str(self.poll.application.number), _("Poll"))
return filename
def get_template(self, buffer):
return SimpleDocTemplate(buffer, topMargin=-6, bottomMargin=-6, leftMargin=0, rightMargin=0, showBoundary=False)
def build_document(self, pdf_document, story):
pdf_document.build(story)
def append_to_pdf(self, story):
imgpath = os.path.join(settings.SITE_ROOT, 'static/images/circle.png')
circle = "<img src='%s' width='15' height='15'/>&nbsp;&nbsp;" % 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')]

View File

@ -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."))))

View File

@ -16,16 +16,12 @@ from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _, ugettext_noop
from openslides.utils.person import PersonField
from openslides.config.models import config
from openslides.config.signals import default_config_value
from openslides.projector.api import register_slidemodel
from openslides.projector.projector import SlideMixin
from openslides.poll.models import (BasePoll, CountInvalid, CountVotesCast,
BaseOption, PublishPollMixin, BaseVote)
from openslides.poll.models import (
BasePoll, CountInvalid, CountVotesCast, BaseOption, PublishPollMixin, BaseVote)
from openslides.agenda.models import Item
@ -33,10 +29,14 @@ class AssignmentCandidate(models.Model):
assignment = models.ForeignKey("Assignment")
person = PersonField(db_index=True)
elected = models.BooleanField(default=False)
blocked = models.BooleanField(default=False)
def __unicode__(self):
return unicode(self.person)
class Meta:
unique_together = ("assignment", "person")
class Assignment(models.Model, SlideMixin):
prefix = 'assignment'
@ -47,11 +47,10 @@ class Assignment(models.Model, SlideMixin):
)
name = models.CharField(max_length=100, verbose_name=_("Name"))
description = models.TextField(null=True, blank=True,
verbose_name=_("Description"))
posts = models.PositiveSmallIntegerField(
verbose_name=_("Number of available posts"))
polldescription = models.CharField(max_length=100, null=True, blank=True,
description = models.TextField(null=True, blank=True, verbose_name=_("Description"))
posts = models.PositiveSmallIntegerField(verbose_name=_("Number of available posts"))
polldescription = models.CharField(
max_length=100, null=True, blank=True,
verbose_name=_("Comment on the ballot paper"))
status = models.CharField(max_length=3, choices=STATUS, default='sea')
@ -64,14 +63,16 @@ class Assignment(models.Model, SlideMixin):
if error:
raise NameError(_('%s is not a valid status.') % status)
if self.status == status:
raise NameError(_('The assignment status is already %s.')
% self.status)
raise NameError(
_('The assignment status is already %s.') % self.status)
self.status = status
self.save()
def run(self, candidate, person=None):
"""
run for a vote
candidate: The user who will be a candidate
person: The user who chooses the candidate
"""
# TODO: don't make any permission checks here.
# Use other Exceptions
@ -79,26 +80,54 @@ class Assignment(models.Model, SlideMixin):
raise NameError(_('<b>%s</b> is already a candidate.') % candidate)
if not person.has_perm("assignment.can_manage_assignment") and self.status != 'sea':
raise NameError(_('The candidate list is already closed.'))
AssignmentCandidate(assignment=self, person=candidate, elected=False).save()
candidature = self.assignment_candidates.filter(person=candidate)
if candidature and candidate != person and \
not person.has_perm("assignment.can_manage_assignment"):
# if the candidature is blocked and anotherone tries to run the
# candidate
raise NameError(
_('%s does not want to be a candidate.') % candidate)
elif candidature:
candidature[0].blocked = False
candidature[0].save()
else:
AssignmentCandidate(assignment=self, person=candidate).save()
def delrun(self, candidate):
def delrun(self, candidate, blocked=True):
"""
stop running for a vote
"""
if self.is_candidate(candidate):
self.assignment_candidats.get(person=candidate).delete()
else:
# TODO: Use an OpenSlides Error
try:
candidature = self.assignment_candidates.get(person=candidate)
except AssignmentCandidate.DoesNotExist:
raise Exception(_('%s is no candidate') % candidate)
def is_candidate(self, person):
if self.assignment_candidats.filter(person=person).exists():
return True
if not candidature.blocked:
if blocked:
candidature.blocked = True
candidature.save()
else:
candidature.delete()
else:
candidature.delete()
def is_candidate(self, person):
"""
return True, if person is a candidate.
"""
try:
return self.assignment_candidates.filter(person=person).exclude(blocked=True).exists()
except AttributeError:
return False
def is_blocked(self, person):
"""
return True, if the person is blockt for candidature.
"""
return self.assignment_candidates.filter(person=person).filter(blocked=True).exists()
@property
def assignment_candidats(self):
def assignment_candidates(self):
return AssignmentCandidate.objects.filter(assignment=self)
@property
@ -110,11 +139,9 @@ class Assignment(models.Model, SlideMixin):
return self.get_participants(only_elected=True)
def get_participants(self, only_elected=False, only_candidate=False):
candidates = self.assignment_candidats
candidates = self.assignment_candidates.exclude(blocked=True)
if only_elected and only_candidate:
# TODO: Use right Exception
raise Exception("only_elected and only_candidate can not both be Treu")
assert not (only_elected and only_candidate)
if only_elected:
candidates = candidates.filter(elected=True)
@ -122,12 +149,15 @@ class Assignment(models.Model, SlideMixin):
if only_candidate:
candidates = candidates.filter(elected=False)
participants = []
for candidate in candidates.all():
yield candidate.person
participants.append(candidate.person)
participants.sort(key=lambda person: person.sort_name)
return participants
#return candidates.values_list('person', flat=True)
def set_elected(self, person, value=True):
candidate = self.assignment_candidats.get(person=person)
candidate = self.assignment_candidates.get(person=person)
candidate.elected = value
candidate.save()
@ -140,7 +170,6 @@ class Assignment(models.Model, SlideMixin):
poll.set_options([{'candidate': person} for person in self.candidates])
return poll
def vote_results(self, only_published):
"""
returns a table represented as a list with all candidates from all
@ -174,12 +203,11 @@ class Assignment(models.Model, SlideMixin):
vote_results_dict[candidate].append(votes)
return vote_results_dict
def get_agenda_title(self):
return self.name
def delete(self):
# Remove any Agenda-Item, which is related to this application.
# Remove any Agenda-Item, which is related to this assignment.
for item in Item.objects.filter(related_sid=self.sid):
item.delete()
super(Assignment, self).delete()
@ -188,10 +216,12 @@ class Assignment(models.Model, SlideMixin):
"""
return the slide dict
"""
polls = self.poll_set
data = super(Assignment, self).slide()
data['assignment'] = self
data['title'] = self.name
data['polls'] = self.poll_set.filter(published=True)
data['some_polls_available'] = polls.exists()
data['polls'] = polls.filter(published=True)
data['vote_results'] = self.vote_results(only_published=True)
data['assignment_publish_winner_results_only'] = \
config['assignment_publish_winner_results_only']
@ -217,6 +247,7 @@ class Assignment(models.Model, SlideMixin):
('can_nominate_self', ugettext_noop("Can nominate themselves")),
('can_manage_assignment', ugettext_noop("Can manage assignment")),
)
ordering = ('name',)
register_slidemodel(Assignment)
@ -251,14 +282,13 @@ class AssignmentPoll(BasePoll, CountInvalid, CountVotesCast, PublishPollMixin):
self.yesnoabstain = True
else:
# candidates <= available posts -> yes/no/abstain
if self.assignment.assignment_candidats.filter(elected=False).count() <= (self.assignment.posts):
if len(self.assignment.candidates) <= (self.assignment.posts - len(self.assignment.elected)):
self.yesnoabstain = True
else:
self.yesnoabstain = False
self.save()
if self.yesnoabstain:
return [ugettext_noop('Yes'), ugettext_noop('No'),
ugettext_noop('Abstain')]
return [ugettext_noop('Yes'), ugettext_noop('No'), ugettext_noop('Abstain')]
else:
return [ugettext_noop('Votes')]

View File

@ -42,7 +42,13 @@
<tr class="{% cycle '' 'odd' %}
{% if assignment.active %}activeline{% endif %}">
<td><a href="{% url assignment_view assignment.id %}">{{ assignment }}</a></td>
<td>{{ assignment.candidates|length }} / {{ assignment.posts }}</td>
<td>
{% blocktrans with posts=assignment.posts context "Number of searched candidates for an election"%}posts: {{ posts }}{% endblocktrans %}
{% if assignment.status != 'fin' %}
| {% blocktrans with candidates=assignment.get_participants|length %}candidates: {{ candidates }}{% endblocktrans %}
{% endif %}
| {% blocktrans with elected=assignment.elected|length %}elected: {{ elected }}{% endblocktrans %}
</td>
<td class="optional">{{ assignment.get_status_display }}</td>
<td>
<span style="width: 1px; white-space: nowrap;">

View File

@ -35,9 +35,10 @@
{% endif %}
<br>
<!-- Candidates -->
{% if assignment.status != "fin" %}
<h4>{% trans "Candidates" %}</h4>
<ol>
{% for person in assignment.candidates %}
{% for person in assignment.get_participants %}
<li>
{{ person }}
{% if perms.assignment.can_manage_assignment %}
@ -45,6 +46,14 @@
<a href="{% url assignment_delother assignment.id person.person_id %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Remove candidate' %}"><i class="icon-remove"></i></a>
{% endif %}
{% endif %}
{% if person in assignment.elected %}
| <b>{% trans "elected" %}</b>
{% if perms.assignment.can_manage_assignment %}
{% if assignment.status == "sea" or assignment.status == "vot" %}
<a href="{% url assignment_user_not_elected assignment.id person.person_id %}"><img src="{% static 'images/icons/dialog-cancel.png' %}" title="{% trans 'Mark candidate as not elected' %}"></a>
{% endif %}
{% endif %}
{% endif %}
</li>
{% empty %}
<li style="list-style: none outside none; margin-left: -25px;"><i>{% trans "No candidates available." %}</i></li>
@ -84,7 +93,24 @@
</form>
{% endif %}
{% endif %}
<!-- Results -->
{% endif %}
{% if perms.assignment.can_manage_assignments and blocked_candidates and assignment.status != "fin" %}
<h3>{% trans "Blocked Candidates" %}</h3>
<ul>
{% for person in blocked_candidates %}
<li>
{{ person }}<a href="{% url assignment_delother assignment.id person.person_id %}"><img src="{% static 'images/icons/delete.png' %}" title="{% trans 'Remove candidate' %}"></a>
</li>
{% empty %}
<li>{% trans "No blocked candidates available." %}</li>
{% endfor %}
</ul>
{% endif %}
<!-- Results -->
{% if assignment.status != "sea" or polls.exists %}
<h4>{% trans "Election results" %}</h4>
{% if polls.exists %}
<table class="table table-striped table-bordered">
@ -174,8 +200,7 @@
{% if assignment.candidates and perms.assignment.can_manage_assignment and assignment.status == "vot" %}
<td></td>
{% endif %}
</tr>
</tr>
<tr class="info total">
<td><strong>{% trans 'Votes cast' %}</strong></td>
{% for poll in polls %}

View File

@ -62,9 +62,9 @@
</tr>
{% for candidate, poll_list in vote_results.items %}
<tr class="{% cycle 'odd' '' %}">
<td class="candidate{% if candidate in assignment.elected.all %} elected{% endif %}">
{% if candidate in assignment.elected.all %}
<tr class="{% cycle 'odd' '' as rowcolors %}">
<td class="candidate{% if candidate in assignment.elected %} elected{% endif %}">
{% if candidate in assignment.elected %}
<a class="elected">
<img src="{% static 'images/icons/voting-yes.png' %}" title="{% trans 'Candidate is elected' %}">
</a>
@ -72,8 +72,8 @@
{{ candidate }}
</td>
{% for vote in poll_list %}
<td style="white-space:nowrap;"{% if candidate in assignment.elected.all %} class="elected"{% endif %}>
{% if not assignment_publish_winner_results_only or candidate in assignment.elected.all %}
<td style="white-space:nowrap;"{% if candidate in assignment.elected %} class="elected"{% endif %}>
{% if not assignment_publish_winner_results_only or candidate in assignment.elected %}
{% if 'Yes' in vote and 'No' in vote and 'Abstain' in vote %}
<img src="{% static 'images/icons/voting-yes.png' %}" title="{% trans 'Yes' %}"> {{ vote.Yes }}<br>
<img src="{% static 'images/icons/voting-no.png' %}" title="{% trans 'No' %}"> {{ vote.No }}<br>
@ -92,8 +92,7 @@
{% endfor %}
</tr>
{% endfor %}
<tr>
<tr class="{% cycle rowcolors %}">
<td>{% trans 'Invalid votes' %}</td>
{% for poll in polls %}
<td style="white-space:nowrap;">
@ -105,7 +104,6 @@
{% endfor %}
</tr>
<tr class="total">
<td>
<strong>{% trans 'Votes cast' %}</strong>
@ -120,10 +118,6 @@
</td>
{% endfor %}
</tr>
</table>
{% elif assignment.candidates %}
<i>{% trans "No ballots available." %}</i>
{% endif %}
<br>
{% endblock %}

View File

@ -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 <b>%s</b> was nominated successfully.") % (user))
except NameError, e:
messages.error(request, e)
else:
messages.success(request, _(
"Candidate <b>%s</b> was nominated successfully.")
% user)
else:
if request.user.has_perm('assignment.can_nominate_other'):
form = AssignmentRunForm()
polls = assignment.poll_set.all()
if not request.user.has_perm('assignment.can_manage_assignment'):
polls = assignment.poll_set.filter(published=True)
@ -97,13 +91,16 @@ def view(request, assignment_id=None):
polls = assignment.poll_set.all()
vote_results = assignment.vote_results(only_published=False)
blocked_candidates = [
candidate.person for candidate in
assignment.assignment_candidates.filter(blocked=True)]
return {
'assignment': assignment,
'blocked_candidates': blocked_candidates,
'form': form,
'vote_results': vote_results,
'polls': polls,
'user_is_candidate': assignment.is_candidate(request.user)
}
'user_is_candidate': assignment.is_candidate(request.user)}
@permission_required('assignment.can_manage_assignment')
@ -165,6 +162,8 @@ def set_status(request, assignment_id=None, status=None):
messages.success(request, _('Election status was set to: <b>%s</b>.') % assignment.get_status_display())
except Assignment.DoesNotExist:
pass
except NameError, e:
messages.error(request, e)
return redirect(reverse('assignment_view', args=[assignment_id]))
@ -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 <b>%s</b> was withdrawn successfully.") % (person))
if not is_blocked:
message = _("Candidate <b>%s</b> was withdrawn successfully.") % person
else:
message = _("<b>%s</b> was unblocked successfully.") % person
messages.success(request, message)
else:
gen_confirm_form(request,
_("Do you really want to withdraw <b>%s</b> from the election?") \
% person, reverse('assignment_delother', args=[assignment_id, user_id]))
if not is_blocked:
message = _("Do you really want to withdraw <b>%s</b> from the election?") % person
else:
message = _("Do you really want to unblock <b>%s</b> for the election?") % person
gen_confirm_form(request, message, reverse('assignment_delother', args=[assignment_id, user_id]))
return redirect(reverse('assignment_view', args=[assignment_id]))
@ -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', '<br/>'),
story.append(Paragraph(
"%s" % preamble.replace('\r\n', '<br/>'),
stylesheet['Paragraph']))
story.append(Spacer(0, 0.75 * cm))
assignments = Assignment.objects.order_by('name')
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("<font name='Ubuntu-Bold'>%s:</font>" %
cell1a.append(Paragraph(
"<font name='Ubuntu-Bold'>%s:</font>" %
_("Number of available posts"), stylesheet['Bold']))
cell1b = []
cell1b.append(Paragraph(str(assignment.posts), stylesheet['Paragraph']))
# candidates
cell2a = []
cell2a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font><seqreset" \
cell2a.append(Paragraph(
"<font name='Ubuntu-Bold'>%s:</font><seqreset"
" id='counter'>" % _("Candidates"), stylesheet['Heading4']))
cell2b = []
for candidate in assignment.candidates:
cell2b.append(Paragraph("<seq id='counter'/>.&nbsp; %s" % candidate,
cell2b.append(Paragraph(
"<seq id='counter'/>.&nbsp; %s" % candidate,
stylesheet['Signaturefield']))
if assignment.status == "sea":
for x in range(0, 2 * assignment.posts):
cell2b.append(Paragraph("<seq id='counter'/>.&nbsp; "
"__________________________________________",
stylesheet['Signaturefield']))
cell2b.append(
Paragraph(
"<seq id='counter'/>.&nbsp; "
"__________________________________________",
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',
'<br/>'), stylesheet['Paragraph']))
@ -502,13 +519,15 @@ class AssignmentPollPDF(PDFView):
return super(AssignmentPollPDF, self).get(request, *args, **kwargs)
def get_filename(self):
filename = u'%s-%s_%s' % (_("Election"), self.poll.assignment.name.replace(' ', '_'),
filename = u'%s-%s_%s' % (
_("Election"), self.poll.assignment.name.replace(' ', '_'),
self.poll.get_ballot())
return filename
def get_template(self, buffer):
return SimpleDocTemplate(buffer, topMargin=-6, bottomMargin=-6,
leftMargin=0, rightMargin=0, showBoundary=False)
return SimpleDocTemplate(
buffer, topMargin=-6, bottomMargin=-6, leftMargin=0, rightMargin=0,
showBoundary=False)
def build_document(self, pdf_document, story):
pdf_document.build(story)
@ -517,32 +536,37 @@ class AssignmentPollPDF(PDFView):
imgpath = os.path.join(settings.SITE_ROOT, 'static/images/circle.png')
circle = "<img src='%s' width='15' height='15'/>&nbsp;&nbsp;" % 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("&nbsp;",
stylesheet['Ballot_option_group']))
cell.append(Paragraph(circle + _("Yes") + "&nbsp; " * 3 + circle
+ _("No") + "&nbsp; " * 3 + circle+ _("Abstention"),
cell.append(Paragraph(
"&nbsp;", stylesheet['Ballot_option_group']))
cell.append(Paragraph(
circle + _("Yes") + "&nbsp; " * 3 + circle
+ _("No") + "&nbsp; " * 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("&nbsp;",
stylesheet['Ballot_option_group_right']))
cell.append(Paragraph(
"&nbsp;", 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')]

View File

@ -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,

View File

@ -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.") %
"<a href='http://openslides.org/' target='_blank'> \
www.openslides.org</a>",
'welcome_title': _('Welcome to OpenSlides'),
'welcome_text': _('[Place for your welcome text.]'),
'system_enable_anonymous': False,
}.get(key)
@ -127,11 +122,9 @@ def set_submenu(sender, request, context, **kwargs):
(reverse('config_%s' % appname), _(title), selected)
)
menu_links.append (
(reverse('config_version'), _('Version'),
request.path == reverse('config_version'))
)
menu_links.append((
reverse('config_version'), _('Version'),
request.path == reverse('config_version')))
context.update({
'menu_links': menu_links,
})
'menu_links': menu_links})

View File

@ -25,9 +25,9 @@
</fieldset>
<p></p>
<fieldset>
<legend>{% trans "Frontpage" %}</legend>
<legend>{% trans "Welcome Widget" %}</legend>
{% for field in form %}
{% if "id_frontpage" in field.label_tag %}
{% if "id_welcome" in field.label_tag %}
<p>
{{ field.errors }}
{{ field.required }}

View File

@ -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

View File

@ -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__))

View File

@ -6,8 +6,10 @@ Instruction to update translation for OpenSlides:
2. Update the German po file (locale/de/LC_MESSAGES/django.po):
$ django-admin.py makemessages -l de
$ django-admin.py makemessages -l de -d djangojs
3. Edit the German po file: locale/de/LC_MESSAGES/django.po
Don't forget the js-file: locale/de/LC_MESSAGES/djangojs.po
(Search for "fuzzy" and empty msgstr entries.)
4. Update the German mo file (locale/de/LC_MESSAGES/django.mo):

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -0,0 +1,22 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: OpenSlides 1.3\n"
"Report-Msgid-Bugs-To: support@openslides.org\n"
"POT-Creation-Date: 2012-12-09 11:12+0100\n"
"PO-Revision-Date: 2012-07-28 11:07+0200\n"
"Last-Translator: Oskar Hahn <mail@oshahn.de>\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: agenda/static/javascript/agenda.js:27
#, c-format
msgid ", of which %s are hidden."
msgstr ", davon %s verborgen."

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -0,0 +1,25 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# <moosline@savvy.ch>, 2012.
msgid ""
msgstr ""
"Project-Id-Version: OpenSlides\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-12-09 14:17+0100\n"
"PO-Revision-Date: 2012-12-09 11:46+0000\n"
"Last-Translator: moosline <moosline@savvy.ch>\n"
"Language-Team: French (http://www.transifex.com/projects/p/openslides/"
"language/fr/)\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: agenda/static/javascript/agenda.js:27
#, c-format
msgid ", of which %s are hidden."
msgstr ", dans %s sont cachés"

234
openslides/main.py Normal file → Executable file
View File

@ -13,29 +13,34 @@
# for python 2.5 support
from __future__ import with_statement
import os
import sys
import optparse
import socket
import time
import threading
import base64
import ctypes
import optparse
import os
import socket
import sys
import tempfile
import threading
import time
import webbrowser
import django.conf
from django.core.management import execute_from_command_line
from openslides import get_version
CONFIG_TEMPLATE = """#!/usr/bin/env python
# -*- coding: utf-8 -*-
from openslides.openslides_global_settings import *
import openslides.main
from openslides.global_settings import *
# Use 'DEBUG = True' to get more details for server errors
# (Default for relaeses: 'False')
# (Default for releases: 'False')
DEBUG = False
TEMPLATE_DEBUG = DEBUG
DBPATH = %(dbpath)r
DBPATH = %(dbpath)s
DATABASES = {
'default': {
@ -64,51 +69,89 @@ INSTALLED_APPS += INSTALLED_PLUGINS
KEY_LENGTH = 30
_fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
def _fs2unicode(s):
if isinstance(s, unicode):
return s
return s.decode(_fs_encoding)
# sentinel used to signal that the database ought to be stored
# relative to the portable's directory
_portable_db_path = object()
def main(argv=None, opt_defaults=None):
def process_options(argv=None):
if argv is None:
argv = sys.argv[1:]
parser = optparse.OptionParser(
description="Run openslides using django's builtin webserver")
parser.add_option("-a", "--address", help="IP Address to listen on")
parser.add_option("-p", "--port", type="int", help="Port to listen on")
parser.add_option("-a", "--address", help="IP Address to listen on.")
parser.add_option("-p", "--port", type="int", help="Port to listen on.")
parser.add_option(
"--syncdb", action="store_true",
help="Update/create database before starting the server")
help="Update/create database before starting the server.")
parser.add_option(
"--reset-admin", action="store_true",
help="Make sure the user 'admin' exists and uses 'admin' as password")
parser.add_option("-s", "--settings", help="Path to the setting.")
help="Make sure the user 'admin' exists and uses 'admin' as password.")
parser.add_option(
"--no-reload", action="store_true", help="Do not reload the development server")
if not opt_defaults is None:
parser.set_defaults(**opt_defaults)
"-s", "--settings", help="Path to the openslides configuration.")
parser.add_option(
"--no-reload", action="store_true",
help="Do not reload the development server.")
parser.add_option(
"--version", action="store_true",
help="Show version and exit.")
opts, args = parser.parse_args(argv)
if opts.version:
print get_version()
exit(0)
if args:
sys.stderr.write("This command does not take arguments!\n\n")
parser.print_help()
sys.exit(1)
return opts
def main(argv=None):
opts = process_options(argv)
_main(opts)
def win32_portable_main(argv=None):
"""special entry point for the win32 portable version"""
opts = process_options(argv)
database_path = None
if opts.settings is None:
portable_dir = get_portable_path()
try:
fd, test_file = tempfile.mkstemp(dir=portable_dir)
except OSError:
portable_dir_writeable = False
else:
portable_dir_writeable = True
os.close(fd)
os.unlink(test_file)
if portable_dir_writeable:
opts.settings = os.path.join(
portable_dir, "openslides", "settings.py")
database_path = _portable_db_path
_main(opts, database_path=database_path)
def _main(opts, database_path=None):
# Find the path to the settings
settings = opts.settings or \
os.path.join(os.path.expanduser('~'),'.openslides','openslides_personal_settings.py')
settings_path = opts.settings
if settings_path is None:
settings_path = get_user_config_path('openslides', 'settings.py')
# Create settings if necessary
if not os.path.exists(settings):
create_settings(settings)
if not os.path.exists(settings_path):
create_settings(settings_path, database_path)
# Set the django environment to the settings
setup_django_environment(settings)
setup_django_environment(settings_path)
# Find url to openslides
addr, port = detect_listen_opts(opts.address, opts.port)
@ -135,24 +178,40 @@ def main(argv=None, opt_defaults=None):
start_openslides(addr, port, start_browser_url=url, extra_args=extra_args)
def create_settings(settings):
path_to_dir = os.path.dirname(settings)
def create_settings(settings_path, database_path=None):
settings_module = os.path.dirname(settings_path)
setting_content = CONFIG_TEMPLATE % dict(
if database_path is _portable_db_path:
database_path = get_portable_db_path()
dbpath_value = 'openslides.main.get_portable_db_path()'
else:
if database_path is None:
database_path = get_user_data_path('openslides', 'database.sqlite')
dbpath_value = repr(fs2unicode(database_path))
settings_content = CONFIG_TEMPLATE % dict(
default_key=base64.b64encode(os.urandom(KEY_LENGTH)),
dbpath=_fs2unicode((os.path.join(path_to_dir, 'database.db'))))
dbpath=dbpath_value)
if not os.path.exists(path_to_dir):
os.makedirs(path_to_dir)
if not os.path.exists(settings_module):
os.makedirs(settings_module)
with open(settings, 'w') as settings_file:
settings_file.write(setting_content)
if not os.path.exists(os.path.dirname(database_path)):
os.makedirs(os.path.dirname(database_path))
with open(settings_path, 'w') as file:
file.write(settings_content)
def setup_django_environment(settings):
sys.path.append(os.path.dirname(settings))
setting_module = os.path.basename(settings)[:-3]
os.environ[django.conf.ENVIRONMENT_VARIABLE] = setting_module
def setup_django_environment(settings_path):
settings_file = os.path.basename(settings_path)
settings_module_name = "".join(settings_file.split('.')[:-1])
if '.' in settings_module_name:
print "'.' is not an allowed character in the settings-file"
sys.exit(1)
settings_module_dir = os.path.dirname(settings_path)
sys.path.append(settings_module_dir)
os.environ[django.conf.ENVIRONMENT_VARIABLE] = '%s' % settings_module_name
def detect_listen_opts(address, port):
@ -186,6 +245,7 @@ def database_exists():
from openslides.participant.models import User
try:
# TODO: Use another model, the User could be deactivated
User.objects.count()
except DatabaseError:
return False
@ -200,6 +260,7 @@ def run_syncdb():
# now initialize the database
argv = ["", "syncdb", "--noinput"]
execute_from_command_line(argv)
execute_from_command_line(["", "loaddata", "groups_de"])
def set_system_url(url):
@ -223,7 +284,7 @@ def create_or_reset_admin_user():
except User.DoesNotExist:
admin = User()
admin.username = 'admin'
admin.last_name = 'Admin User'
admin.last_name = 'Administrator'
print("Created default admin user")
admin.is_superuser = True
@ -252,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__":

View File

@ -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,
)

View File

@ -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)

View File

@ -0,0 +1,66 @@
{% extends "base.html" %}
{% load tags %}
{% load i18n %}
{% load staticfiles %}
{% block submenu %}
{% url motion_overview as url_motionoverview %}
<h4>{% trans "Motions" %}</h4>
<ul>
<li class="{% if request.path == url_motionoverview %}selected{% endif %}"><a href="{% url motion_overview %}">{% trans "All motions" %}</a></li>
{% if perms.motion.can_create_motion or perms.motion.can_manage_motion %}
<li class="{% active request '/motion/new' %}"><a href="{% url motion_new %}">{% trans "New motion" %}</a></li>
{% endif %}
{% if perms.motion.can_manage_motion %}
<li class="{% active request '/motion/import' %}"><a href="{% url motion_import %}">{% trans 'Import motions' %}</a></li>
{% endif %}
<li><a href="{% url print_motions %}"><img src="{% static 'images/icons/pdf.png' %}"> {% trans 'All motions as PDF' %}</a></li>
</ul>
{# second submenu #}
{% if motion %}
<br>
<h3>{% trans "Motion No." %}
{% if motion.number != None %}
{{ motion.number }}
{% else %}
<i>[-]</i>
{% endif %}
</h3>
<ul>
{# view motion #}
{% url motion_view motion.id as url_motionview %}
<li class="{% if request.path == url_motionview %}selected{% endif %}"><a href="{% url motion_view motion.id %}">{% trans 'View motion' %}</a></li>
{# edit motion #}
{% if "edit" in actions %}
{% url motion_edit motion.id as url_motionedit %}
<li class="{% if request.path == url_motionedit %}selected{% endif %}"><a href="{% url motion_edit motion.id %}"><img src="{% static 'images/icons/edit.png' %}"> {% trans 'Edit motion' %}</a></li>
{% endif %}
{# delete motion #}
{% if "delete" in actions %}
<li><a href="{% url motion_delete motion.id %}"><img src="{% static 'images/icons/delete.png' %}"> {% trans 'Delete motion' %}</a></li>
{% endif %}
{# PDF #}
<li><a href="{% url print_motion motion.id %}"><img src="{% static 'images/icons/pdf.png' %}"> {% trans 'Motion as PDF' %}</a></li>
{# activate and polls #}
{% if perms.projector.can_manage_projector %}
<li>
<a class="activate_link {% if item.active %}active{% endif %}" href="{% url projector_activate_slide motion.sid %}"><img src="{% static 'images/icons/projector.png' %}"> {% trans 'Show Motion' %}</a>
</li>
{% endif %}
{% if perms.motion.can_manage_motion %}
{% for poll in motion.polls %}
{% url motion_poll_view poll.id as url_motionpollview %}
<li class="{% if request.path == url_motionpollview %}selected{% endif %}"><a href="{% url motion_poll_view poll.id %}"><img src="{% static 'images/icons/edit.png' %}"> {{ forloop.counter }}. {% trans "Vote" %}</a></li>
{% endfor %}
{% endif %}
{# Agenda Item #}
{% if perms.agenda.can_manage_agenda %}
<li>
<a href="{% url motion_create_agenda motion.id %}">{% trans 'New agenda item' %}</a>
</li>
{% endif %}
</ul>
{% endif %}
{% endblock %}

View File

@ -2,10 +2,10 @@
{% load i18n %}
{% block title %}{{ block.super }} {% trans "Application settings" %}{% endblock %}
{% block title %}{{ block.super }} {% trans "Motion settings" %}{% endblock %}
{% block content %}
<h1>{% trans "Configuration" %}: {% trans "Applications" %}
<h1>{% trans "Configuration" %}: {% trans "Motions" %}
{% block config_submenu %}{{ block.super }}{% endblock %}
</h1>
<form action="" method="post">{% csrf_token %}
@ -14,8 +14,8 @@
<button class="button" type="submit">
<span class="icon ok">{% trans 'Save' %}</span>
</button>
<a href='{% url config_application %}'>
<button class="button" type="button" onclick="window.location='{% url config_application %}'">
<a href='{% url config_motion %}'>
<button class="button" type="button" onclick="window.location='{% url config_motion %}'">
<span class="icon cancel">{% trans 'Cancel' %}</span>
</button>
</a>

View File

@ -4,7 +4,7 @@
{% block title %}
{{ block.super }}
{% if application %}
{% if motion %}
{% trans "Edit motion" %}
{% else %}
{% trans "New motion" %}
@ -13,7 +13,7 @@
{% block content %}
<h1>
{% if application %}
{% if motion %}
{% trans "Edit motion" %}
{% else %}
{% trans "New motion" %}
@ -43,13 +43,12 @@
<button class="btn" type="submit" name="apply">
{% trans 'Apply' %}
</button>
<a href='{% url application_overview %}'>
<button class="btn" type="button" onclick="window.location='{% url application_overview %}'">
{% trans 'Cancel' %}</span>
<a href='{% url motion_overview %}'>
<button class="btn" type="button" onclick="window.location='{% url motion_overview %}'">
{% trans 'Cancel' %}
</button>
</a>
</div>
<small>* {% trans "required" %}</small>
</form>
{% endblock %}

View File

@ -12,7 +12,10 @@
</h1>
<p>{% trans 'Select a CSV file to import motions!' %}</p>
<p>{% trans 'Required comma separated values: <code>{number, title, text, reason, first_name, last_name}</code> (<code>number</code> and <code>reason</code> are optional and may be empty)' %}
<p>{% trans 'Required comma separated values' %}:
<code>({% trans 'number, title, text, reason, first_name, last_name, is_group' %})</code>
<br>
{% trans '<code>number</code>, <code>reason</code> and <code>is_group</code> are optional and may be empty' %}.
<br>
{% trans 'Required CSV file encoding: UTF-8 (Unicode).' %}
</p>
@ -37,7 +40,7 @@
<button class="btn btn-primary" type="submit">
{% trans 'Import' %}
</button>
<a href='{% url application_overview %}' class="btn">
<a href='{% url motionn_overview %}' class="btn">
{% trans 'Cancel' %}
</a>
</div>

View File

@ -43,10 +43,9 @@
<option value="wit" {% if 'on' in request.GET.status and 'wit' in request.GET.statusvalue %}selected{% endif %}>{% trans "Withdrawen (by submitter)" %}</option>
<option value="rev" {% if 'rev' in request.GET.statusvalue %}selected{% endif %}>{% trans "Needs Review" %}</option>
</select>
</form>
<small><i>{{ applications|length }}
{% blocktrans count counter=applications|length %}motion{% plural %}motions{% endblocktrans %}
<small><i>{{ motions|length }}
{% blocktrans count counter=motions|length context "number of motions"%}motion{% plural %}motions{% endblocktrans %}
</i></small>
<table class="table table-striped table-bordered">
<tr>
@ -60,39 +59,39 @@
<th class="optional"><a href="?sort=time{% if 'time' in request.GET.sort and 'reverse' not in request.GET %}&reverse{%endif%}">{% trans "Creation Time" %}<a></th>
<th style="width: 1px;">{% trans "Actions" %}</th>
</tr>
{% for app_info in applications %}
{% with application=app_info.application useractions=app_info.actions %}
{% for app_info in motions %}
{% with motion=app_info.motion useractions=app_info.actions %}
<tr class="{% cycle '' 'odd' %}
{% if application.active %}activeline{% endif %}">
<td>{% if application.number %}{{ application.number }}{% else %}-{% endif %}</td>
<td><a href="{% url application_view application.id %}">{{ application.public_version.title }}</a></td>
{% if motion.active %}activeline{% endif %}">
<td>{% if motion.number %}{{ motion.number }}{% else %}-{% endif %}</td>
<td><a href="{% url motion_view motion.id %}">{{ motion.public_version.title }}</a></td>
{% if min_supporters > 0 %}
<td>{{ application.supporter.count }}</td>
<td>{{ motion.count_supporters }}</td>
{% endif %}
<td>{% if application.status != "pub" %}
{{ application.get_status_display }}<br>
<td>{% if motion.status != "pub" %}
{{ motion.get_status_display }}<br>
{% endif %}
{% for note in application.notes %}
{% for note in motion.notes %}
{{ note }}
{% if not forloop.last %}<br>{%endif%}
{% endfor %}
</td>
<td>{{ application.submitter }}</td>
<td class="optional">{{ application.creation_time }}</td>
<td>{{ motion.submitter }}</td>
<td class="optional">{{ motion.creation_time }}</td>
<td>
<span style="width: 1px; white-space: nowrap;">
{% if perms.projector.can_manage_projector %}
<a class="activate_link {% if application.active %}active{% endif %}" href="{% url projector_activate_slide application.sid %}" title="{% trans 'Activate motion' %}">
<a class="activate_link {% if motion.active %}active{% endif %}" href="{% url projector_activate_slide motion.sid %}" title="{% trans 'Activate motion' %}">
<span></span>
</a>
{% endif %}
{% if perms.application.can_manage_application %}
<a href="{% url application_edit application.id %}"><img src="{% static 'images/icons/edit.png' %}" title="{% trans 'Edit motion' %}"></a>
{% if perms.motion.can_manage_motion %}
<a href="{% url motion_edit motion.id %}"><img src="{% static 'images/icons/edit.png' %}" title="{% trans 'Edit motion' %}"></a>
{% if "delete" in useractions %}
<a href="{% url application_delete application.id %}"><img src="{% static 'images/icons/delete.png' %}" title="{% trans 'Delete motion' %}"></a>
<a href="{% url motion_delete motion.id %}"><img src="{% static 'images/icons/delete.png' %}" title="{% trans 'Delete motion' %}"></a>
{% endif %}
{% endif %}
<a href="{% url print_application application.id %}" title="{% trans 'Motion as PDF' %}"><img src="{% static 'images/icons/pdf.png' %}"></a>
<a href="{% url print_motion motion.id %}" title="{% trans 'Motion as PDF' %}"><img src="{% static 'images/icons/pdf.png' %}"></a>
</span>
</td>
</tr>

View File

@ -4,14 +4,14 @@
{% load staticfiles %}
{% block title %}
{{ block.super }} - {% trans "Motion" %} {{ application.number }}, {{ ballot }}. {% trans "Vote" %}
{{ block.super }} - {% trans "Motion" %} {{ motion.number }}, {{ ballot }}. {% trans "Vote" %}
{% endblock %}
{% block content %}
<h1>
{{ application.title }}
{{ motion.public_version.title }}
<small>
{% trans "Motion" %} {{ application.number }}, {{ ballot }}. {% trans "Vote" %}
{% trans "Motion" %} {{ motion.number }}, {{ ballot }}. {% trans "Vote" %}
</small>
<small class="pull-right">
<div class="btn-toolbar">
@ -39,6 +39,7 @@
</h1>
<i class="helptext">{% trans "Special values" %}: -1 = {% trans 'majority' %}; -2 = {% trans 'undocumented' %}</i>
<form action="" method="post" class="small-form">{% csrf_token %}
{{ pre_form }}
<span id="poll_id" style="display:none">{{ poll.id }}</span>
@ -65,7 +66,7 @@
{{ post_form }}
<!-- ballot paper button -->
<p>
<a href='{% url print_application_poll poll.id %}' class="btn">
<a href='{% url print_motion_poll poll.id %}' class="btn">
<i class="icon-print"></i> {% trans 'Ballot paper as PDF' %}
</a>
</p>
@ -77,7 +78,7 @@
<button type="submit" name="apply" class="btn">
{% trans 'Apply' %}
</button>
<a href='{% url application_view application.id %}' class="btn">
<a href='{% url motion_view motion.id %}' class="btn">
{% trans 'Cancel' %}
</a>
</div>

View File

@ -6,8 +6,8 @@
{% block title %}
{{ block.super }} - {% trans "Motion" %}
{% if application.number != None %}
{{ application.number }}
{% if motion.number != None %}
{{ motion.number }}
{% else %}
[{% trans "no number" %}]
{% endif %}
@ -17,8 +17,8 @@
<h1>
{{ version.title }}
<small>
{% if application.number != None %}
{% trans "Motion" %} {{ application.number }},
{% if motion.number != None %}
{% trans "Motion" %} {{ motion.number }},
{% else %}
<i>[{% trans "no number" %}]</i>,
{% endif %}
@ -26,8 +26,8 @@
</small>
<small class="pull-right">
<div class="btn-toolbar">
<a href="{% url application_overview %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Back to overview" %}</a>
<a href="{% url print_application application.id %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Print this motion as PDF' %}"><i class="icon-print"></i> PDF</a>
<a href="{% url motion_overview %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Back to overview" %}</a>
<a href="{% url print_motion motion.id %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Print this motion as PDF' %}"><i class="icon-print"></i> PDF</a>
<div class="btn-group">
<a data-toggle="dropdown" href="#" class="btn btn-mini dropdown-toggle">
{% trans 'More actions' %}
@ -36,28 +36,28 @@
<ul class="dropdown-menu pull-right">
<!-- edit -->
{% if "edit" in actions %}
<li><a href="{% url application_edit application.id %}"><i class="icon-edit"></i> {% trans 'Edit motion' %}</a></li>
<li><a href="{% url motion_edit motion.id %}"><i class="icon-edit"></i> {% trans 'Edit motion' %}</a></li>
{% endif %}
<!-- delete -->
{% if "delete" in actions %}
<li><a href="{% url application_delete application.id %}"><i class="icon-remove"></i> {% trans 'Delete motion' %}</a></li>
<li><a href="{% url motion_delete motion.id %}"><i class="icon-remove"></i> {% trans 'Delete motion' %}</a></li>
{% endif %}
<!-- activate projector -->
{% if perms.projector.can_manage_projector %}
<li>
<a class="activate_link {% if item.active %}active{% endif %}" href="{% url projector_activate_slide application.sid %}"><i class="icon-facetime-video"></i> {% trans 'Show Application' %}</a>
<a class="activate_link {% if item.active %}active{% endif %}" href="{% url projector_activate_slide motion.sid %}"><i class="icon-facetime-video"></i> {% trans 'Show motion' %}</a>
</li>
{% endif %}
<!-- edit poll -->
{% if perms.application.can_manage_application %}
{% for poll in application.polls %}
<li><a href="{% url application_poll_view poll.id %}"><i class="icon-edit"></i> {{ forloop.counter }}. {% trans "Vote" %}</a></li>
{% if perms.motion.can_manage_motion %}
{% for poll in motion.polls %}
<li><a href="{% url motion_poll_view poll.id %}"><i class="icon-edit"></i> {{ forloop.counter }}. {% trans "Vote" %}</a></li>
{% endfor %}
{% endif %}
<!-- create agenda item -->
{% if perms.agenda.can_manage_agenda %}
<li>
<a href="{% url application_create_agenda application.id %}"><i class="icon-plus"></i> {% trans 'New agenda item' %}</a>
<a href="{% url motion_create_agenda motion.id %}"><i class="icon-plus"></i> {% trans 'New agenda item' %}</a>
</li>
{% endif %}
</ul>
@ -68,12 +68,12 @@
<div class="row-fluid">
<div class="span9">
{% if application.public_version != application.last_version %}
{% if motion.public_version != motion.last_version %}
&#8901;
{% if version == application.public_version %}
{% trans "This is not the newest version." %} <a href="{% url application_view_newest application.id %}">{% trans "Go to version" %} {{ application.last_version.aid }}.</a>
{% if version == motion.public_version %}
{% trans "This is not the newest version." %} <a href="{% url motion_view_newest motion.id %}">{% trans "Go to version" %} {{ motion.last_version.aid }}.</a>
{% else %}
{% trans "This is not the authorized version." %} <a href="{% url application_view application.id %}">{% trans "Go to version" %} {{ application.public_version.aid }}.</a>
{% trans "This is not the authorized version." %} <a href="{% url motion_view motion.id %}">{% trans "Go to version" %} {{ motion.public_version.aid }}.</a>
{% endif %}
{% endif %}
@ -90,7 +90,7 @@
{% endif %}
<br>
<!-- Version history -->
{% if application.versions|length > 1 %}
{% if motion.versions|length > 1 %}
<h4>{% trans "Version History" %}:</h4>
<table class="table table-striped table-bordered">
<tr>
@ -101,18 +101,18 @@
<th>{% trans "Text" %}</th>
<th>{% trans "Reason" %}</th>
</tr>
{% for revision in application.versions %}
{% for revision in motion.versions %}
<tr>
<td style="white-space:nowrap;">
{% if application.status != "pub" %}
{% if revision == application.permitted %}
{% if motion.status != "pub" %}
{% if revision == motion.permitted %}
<img title="{% trans 'Version authorized' %}" src="{% static 'images/icons/accept.png' %}">
{% else %}
{% if perms.application.can_manage_application %}
<a href="{% url application_version_permit revision.id %}"><img title="{% trans 'Permit Version' %}" src="{% static 'images/icons/accept-grey.png' %}"></a>
{% if perms.motion.can_manage_motion %}
<a href="{% url motion_version_permit revision.id %}"><img title="{% trans 'Permit Version' %}" src="{% static 'images/icons/accept-grey.png' %}"></a>
{% endif %}
{% if not revision.rejected and revision.id > application.permitted.id and perms.application.can_manage_application %}
<a href="{% url application_version_reject revision.id %}"><img title="{% trans 'Reject Version' %}" src="{% static 'images/icons/reject-grey.png' %}"></a>
{% if not revision.rejected and revision.id > motion.permitted.id and perms.motion.can_manage_motion %}
<a href="{% url motion_version_reject revision.id %}"><img title="{% trans 'Reject Version' %}" src="{% static 'images/icons/reject-grey.png' %}"></a>
{% endif %}
{% endif %}
{% if revision.rejected %}
@ -148,51 +148,49 @@
</table>
{% endif %}
<!-- Log -->
{% if perms.application.can_manage_application %}
{% if perms.motion.can_manage_motion %}
<h4>{% trans "Log" %}:</h4>
<small>{{ application.log|linebreaks }}</small>
<small>{{ motion.log|linebreaks }}</small>
{% endif %}
</div> <!--/span-->
<div class="span3">
<div class="well">
<!-- Submitter -->
<h5>{% trans "Submitter" %}:</h5>
{{ application.submitter }}
{% if user == application.submitter.user %}
<img src="{% static 'images/icons/user-information.png' %}" title="{% trans 'Me' %}">
{% endif %}
{{ motion.submitter }}
<!-- Supporters -->
{% if min_supporters > 0 %}
<h5>{% trans "Supporters" %}: *</h5>
{% if not application.supporters %}
{% if not motion.supporters %}
-
{% else %}
<ol>
{% for supporter in application.supporters %}
<li> {{ supporter }}</li>
{% for supporter in motion.supporters %}
<li>{{ supporter }}</li>
{% endfor %}
</ol>
{% endif %}
{% endif %}
<!-- Status -->
<h5>{% trans "Status" %}:</h5>
{% if application.status != "pub" %}
{% trans application.get_status_display %}
{% if motion.status != "pub" %}
{% trans motion.get_status_display %}
<br>
{% endif %}
{% for note in application.notes %}
{% for note in motion.notes %}
{{ note }}
{% if not forloop.last %}<br>{% endif %}
{% endfor %}
<!-- Vote results -->
<h5>{% trans "Vote results" %}:</h5>
{% with application.polls as polls %}
{% with motion.polls as polls %}
{% if not polls.exists %}
{% if perms.application.can_manage_application %}
{% if perms.motion.can_manage_motion %}
{% if "genpoll" in actions %}
<a href='{% url application_gen_poll application.id %}' class="btn btn-mini">
<a href='{% url motion_gen_poll motion.id %}' class="btn btn-mini">
<i class="icon-signal"></i> {% trans 'New vote' %}
</a>
{% else %}
@ -204,11 +202,11 @@
{% endif %}
<ol>
{% for poll in polls %}
{% if perms.application.can_manage_application or poll.has_votes %}
{% if perms.motion.can_manage_motion or poll.has_votes %}
<li>{% trans "Vote" %}
{% if perms.application.can_manage_application %}
<a class="btn btn-mini" href="{% url application_poll_view poll.id %}" title="{% trans 'Edit Vote' %}"><i class="icon-edit"></i></a>
<a class="btn btn-mini" href="{% url application_poll_delete poll.id %}" title="{% trans 'Delete Vote' %}"><i class="icon-remove"></i></a>
{% if perms.motion.can_manage_motion %}
<a class="btn btn-mini" href="{% url motion_poll_view poll.id %}" title="{% trans 'Edit Vote' %}"><i class="icon-edit"></i></a>
<a class="btn btn-mini" href="{% url motion_poll_delete poll.id %}" title="{% trans 'Delete Vote' %}"><i class="icon-remove"></i></a>
{% endif %}
<br>
{% if poll.has_votes %}
@ -221,17 +219,17 @@
<img src="{% static 'images/icons/voting-total.png' %}" title="{% trans 'Votes cast' %}"> {{ poll.print_votescast }}
</div>
{% endwith %}
{% if perms.application.can_manage_application %}
{% if perms.motion.can_manage_motion %}
{% if forloop.last %}
{% if "genpoll" in actions %}
<a href='{% url application_gen_poll application.id %}' class="btn btn-mini">
<a href='{% url motion_gen_poll motion.id %}' class="btn btn-mini">
<i class="icon-signal"></i> {% trans 'New vote' %}
</a>
{% endif %}
{% endif %}
{% endif %}
{% else %}
{% if perms.application.can_manage_application %}
{% if perms.motion.can_manage_motion %}
<span class="label label-info">{% trans 'No results' %}</span>
{% endif %}
{% endif %}
@ -242,25 +240,25 @@
{% endwith %}
<!-- Creation Time -->
<h5>{% trans "Creation Time" %}:</h5>
{{ application.creation_time }}
{{ motion.creation_time }}
<!-- Widthdraw button -->
{% if "wit" in actions and user == application.submitter.user %}
{% if "wit" in actions and user == motion.submitter.user %}
<br><br>
<a href='{% url application_set_status application.id 'wit' %}' class="btn">
<a href='{% url motion_set_status motion.id 'wit' %}' class="btn">
<span class="icon revert">{% trans 'Withdraw motion' %}</span>
</a>
{% endif %}
<!-- Support/Unsupport button -->
{% if perms.application.can_support_application and min_supporters > 0 %}
{% if perms.motion.can_support_motion and min_supporters > 0 %}
{% if "unsupport" in actions %}
<br><br>
<a href='{% url application_unsupport application.id %}' class="btn">
<a href='{% url motion_unsupport motion.id %}' class="btn">
{% trans 'Unsupport motion' %}
</a>
{% endif %}
{% if "support" in actions %}
<br><br>
<a href='{% url application_support application.id %}'>
<a href='{% url motion_support motion.id %}'>
{% trans 'Support' %}
</a>
{% endif %}
@ -272,8 +270,8 @@
{% endif %}
</div> <!--/well-->
{% if perms.application.can_manage_application %}
<!-- Manage application box -->
{% if perms.motion.can_manage_motion %}
<!-- Manage motion box -->
<div class="well">
<h4>{% trans "Manage motion" %}</h4>
<!-- Formal validation -->
@ -281,16 +279,16 @@
<h5>{% trans "Formal validation" %}:</h5>
<div class="btn-group btn-group-vertical">
{% if "pub" in actions %}
<a href='{% url application_set_status application.id 'pub' %}' class="btn">{% trans 'Publish' %}</a>
<a href='{% url motion_set_status motion.id 'pub' %}' class="btn">{% trans 'Publish' %}</a>
{% endif %}
{% if "per" in actions %}
<a href='{% url application_permit application.id %}' class="btn btn-info">{% trans 'Permit' %}</a>
<a href='{% url motion_permit motion.id %}' class="btn btn-info">{% trans 'Permit' %}</a>
{% endif %}
{% if "nop" in actions %}
<a href='{% url application_notpermit application.id %}' class="btn">{% trans 'Not permit' %}</a>
<a href='{% url motion_notpermit motion.id %}' class="btn">{% trans 'Not permit' %}</a>
{% endif %}
{% if "setnumber" in actions %}
<a href='{% url application_set_number application.id %}' class="btn">{% trans 'Set number' %}</a>
<a href='{% url motion_set_number motion.id %}' class="btn">{% trans 'Set number' %}</a>
{% endif %}
</div>
{% endif %}
@ -299,12 +297,12 @@
<h5>{% trans "Result after vote" %}:</h5>
<div class="btn-group btn-group-vertical">
{% if "acc" in actions %}
<a href='{% url application_set_status application.id 'acc' %}' class="btn btn-success">
<a href='{% url motion_set_status motion.id 'acc' %}' class="btn btn-success">
<i class="icon-ok icon-white"></i> {% trans 'Accepted' %}
</a>
{% endif %}
{% if "rej" in actions %}
<a href='{% url application_set_status application.id 'rej' %}' class="btn btn-danger">
<a href='{% url motion_set_status motion.id 'rej' %}' class="btn btn-danger">
<i class="icon-ban-circle icon-white"></i> {% trans 'Rejected' %}
</a>
{% endif %}
@ -320,16 +318,16 @@
</a>
<ul class="dropdown-menu pull-right">
{% if "adj" in actions %}
<li><a href='{% url application_set_status application.id 'adj' %}'>{% trans 'Adjourned' %}</a></li>
<li><a href='{% url motion_set_status motion.id 'adj' %}'>{% trans 'Adjourned' %}</a></li>
{% endif %}
{% if "noc" in actions %}
<li><a href='{% url application_set_status application.id 'noc' %}'>{% trans 'Not Concerned' %}</a></li>
<li><a href='{% url motion_set_status motion.id 'noc' %}'>{% trans 'Not Concerned' %}</a></li>
{% endif %}
{% if "com" in actions %}
<li><a href='{% url application_set_status application.id 'com' %}'>{% trans 'Commited a bill' %}</a></li>
<li><a href='{% url motion_set_status motion.id 'com' %}'>{% trans 'Commited a bill' %}</a></li>
{% endif %}
{% if "wit" in actions %}
<li><a href='{% url application_set_status application.id 'wit' %}'>{% trans 'Withdrawed by Submitter' %}</a></li>
<li><a href='{% url motion_set_status motion.id 'wit' %}'>{% trans 'Withdrawed by Submitter' %}</a></li>
{% endif %}
</ul>
</div>
@ -337,11 +335,11 @@
<p></p>
<hr>
<h5>{% trans "For Administration only:" %}</h5>
<a href='{% url application_reset application.id %}' class="btn btn-danger">
<a href='{% url motion_reset motion.id %}' class="btn btn-danger">
{% trans 'Reset' %}
</a>
</div> <!--/well-->
{% endif %} {# end perms.application.can_support_application #}
{% endif %} {# end perms.motion.can_support_motion #}
</div> <!--/span-->
</div> <!--/row-->
{% endblock %}

View File

@ -0,0 +1,34 @@
{% load staticfiles %}
{% load i18n %}
{% load tags %}
<ul style="line-height: 180%">
{% for motion in motions %}
<li class="{% if motion.active %}activeline{% endif %}">
<a href="{% url projector_activate_slide motion.sid %}" class="activate_link {% if motion.active %}active{% endif %}">
<div></div>
</a>
<a href="{% model_url motion 'delete' %}" title="{% trans 'Delete' %}" class="icon delete right">
<span></span>
</a>
<a href="{% model_url motion 'edit' %}" title="{% trans 'Edit' %}" class="icon edit right">
<span></span>
</a>
<a href="{% url projctor_preview_slide motion.sid %}" title="{% trans 'Preview' %}" class="icon preview right">
<span></span>
</a>
<a href="{% model_url motion 'view' %}">
{{ motion.public_version.title }}
</a>
({% trans "motion" %}
{% if motion.number %}
{{ motion.number }})
{% else %}
<i>[{% trans "no number" %}]</i>)
{% endif %}
</li>
{% empty %}
<li>{% trans 'No motion available.' %}</li>
{% endfor %}
</ul>

View File

@ -4,27 +4,27 @@
{% load i18n %}
{% load staticfiles %}
{% block title %}{{ block.super }} - {% trans "Motion" %} {{ application.number }}{% endblock %}
{% block title %}{{ block.super }} - {% trans "Motion" %} {{ motion.number }}{% endblock %}
{% block content %}
<div id="sidebar">
<div class="box">
<p><b>{% trans "Status" %}:</b><br>
{% if application.status != "pub" %}
{% if application.status == "acc" %}
{% if motion.status != "pub" %}
{% if motion.status == "acc" %}
<img src="{% static 'images/icons/voting-yes.png' %}">
{% endif %}
{% if application.status == "rej" %}
{% if motion.status == "rej" %}
<img src="{% static 'images/icons/voting-no.png' %}">
{% endif %}
{% trans application.get_status_display %}
{% trans motion.get_status_display %}
{% else %}
{% for note in application.notes %}
{% for note in motion.notes %}
{{ note }}
{% endfor %}
{% endif %}
</p>
{% with application.polls as polls %}
{% with motion.polls as polls %}
{% if polls.exists and polls.0.has_votes %}
<p><b>{% trans "Poll result" %}:</b>
{% for poll in polls %}
@ -53,29 +53,29 @@
{% endwith %}
<p><b>{% trans "Submitter" %}:</b><br>
{{ application.submitter }}
{{ motion.submitter }}
</p>
</div>
</div>
<h1>
{% if application.number != None %}
{% trans "Motion No." %} {{ application.number }}
{% if motion.number != None %}
{% trans "Motion No." %} {{ motion.number }}
{% else %}
{% trans "Motion" %} <i>[{% trans "no number" %}]</i>
{% endif %}
</h1>
<b>{{ application.public_version.title }}</b>
<b>{{ motion.public_version.title }}</b>
<hr>
{% endblock %}
{% block scrollcontent %}
<p>
<div class="text">{{ application.public_version.text|linebreaks }}</div>
{% if application.public_version.reason %}
<div class="text">{{ motion.public_version.text|linebreaks }}</div>
{% if motion.public_version.reason %}
<br>
<div class="reason"><p><b>{% trans "Reason" %}:</b></p>
{{ application.public_version.reason|linebreaks }}</div>
{{ motion.public_version.reason|linebreaks }}</div>
{% endif %}
</p>
{% endblock %}

141
openslides/motion/urls.py Normal file
View File

@ -0,0 +1,141 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.motion.urls
~~~~~~~~~~~~~~~~~~~~~~~~~~~
URL list for the motion app.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from django.conf.urls.defaults import url, patterns
from openslides.motion.views import (MotionDelete, ViewPoll,
MotionPDF, MotionPollPDF, CreateAgendaItem, SupportView)
urlpatterns = patterns('openslides.motion.views',
url(r'^$',
'overview',
name='motion_overview',
),
url(r'^(?P<motion_id>\d+)/$',
'view',
name='motion_view',
),
url(r'^(?P<motion_id>\d+)/agenda/$',
CreateAgendaItem.as_view(),
name='motion_create_agenda',
),
url(r'^(?P<motion_id>\d+)/newest/$',
'view',
{'newest': True},
name='motion_view_newest',
),
url(r'^new/$',
'edit',
name='motion_new',
),
url(r'^import/$',
'motion_import',
name='motion_import',
),
url(r'^(?P<motion_id>\d+)/edit/$',
'edit',
name='motion_edit',
),
url(r'^(?P<motion_id>\d+)/del/$',
MotionDelete.as_view(),
name='motion_delete',
),
url(r'^del/$',
MotionDelete.as_view(),
{ 'motion_id' : None , 'motion_ids' : None },
name='motion_delete',
),
url(r'^(?P<motion_id>\d+)/setnumber/$',
'set_number',
name='motion_set_number',
),
url(r'^(?P<motion_id>\d+)/setstatus/(?P<status>[a-z]{3})/$',
'set_status',
name='motion_set_status',
),
url(r'^(?P<motion_id>\d+)/permit/$',
'permit',
name='motion_permit',
),
url(r'^version/(?P<aversion_id>\d+)/permit/$',
'permit_version',
name='motion_version_permit',
),
url(r'^version/(?P<aversion_id>\d+)/reject/$',
'reject_version',
name='motion_version_reject',
),
url(r'^(?P<motion_id>\d+)/notpermit/$',
'notpermit',
name='motion_notpermit',
),
url(r'^(?P<motion_id>\d+)/reset/$',
'reset',
name='motion_reset',
),
url(r'^(?P<motion_id>\d+)/support/$',
SupportView.as_view(support=True),
name='motion_support',
),
url(r'^(?P<motion_id>\d+)/unsupport/$',
SupportView.as_view(support=False),
name='motion_unsupport',
),
url(r'^(?P<motion_id>\d+)/gen_poll/$',
'gen_poll',
name='motion_gen_poll',
),
url(r'^print/$',
MotionPDF.as_view(),
{'motion_id': None},
name='print_motions',
),
url(r'^(?P<motion_id>\d+)/print/$',
MotionPDF.as_view(),
name='print_motion',
),
url(r'^poll/(?P<poll_id>\d+)/print/$',
MotionPollPDF.as_view(),
name='print_motion_poll',
),
url(r'^poll/(?P<poll_id>\d+)/$',
ViewPoll.as_view(),
name='motion_poll_view',
),
url(r'^poll/(?P<poll_id>\d+)/del/$',
'delete_poll',
name='motion_poll_delete',
),
)

957
openslides/motion/views.py Normal file
View File

@ -0,0 +1,957 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.motion.views
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Views for the motion app.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
# for python 2.5 support
from __future__ import with_statement
import csv
import os
try:
from urlparse import parse_qs
except ImportError: # python <= 2.5
from cgi import parse_qs
from reportlab.lib import colors
from reportlab.lib.units import cm
from reportlab.platypus import (
SimpleDocTemplate, PageBreak, Paragraph, Spacer, Table, TableStyle)
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import reverse
from django.db import transaction
from django.shortcuts import redirect
from django.utils.translation import ugettext as _, ungettext
from openslides.utils import csv_ext
from openslides.utils.pdf import stylesheet
from openslides.utils.template import Tab
from openslides.utils.utils import (
template, permission_required, del_confirm_form, gen_confirm_form)
from openslides.utils.views import (
PDFView, RedirectView, DeleteView, FormView, SingleObjectMixin,
QuestionMixin)
from openslides.utils.person import get_person
from openslides.config.models import config
from openslides.projector.projector import Widget
from openslides.poll.views import PollFormView
from openslides.participant.api import gen_username, gen_password
from openslides.participant.models import User, Group
from openslides.agenda.models import Item
from openslides.motion.models import Motion, AVersion, MotionPoll
from openslides.motion.forms import (
MotionForm, MotionFormTrivialChanges, MotionManagerForm,
MotionManagerFormSupporter, MotionImportForm, ConfigForm)
@permission_required('motion.can_see_motion')
@template('motion/overview.html')
def overview(request):
"""
View all motions
"""
try:
sortfilter = parse_qs(request.COOKIES['votecollector_sortfilter'])
for value in sortfilter:
sortfilter[value] = sortfilter[value][0]
except KeyError:
sortfilter = {}
for value in [u'sort', u'reverse', u'number', u'status', u'needsup', u'statusvalue']:
if value in request.REQUEST:
if request.REQUEST[value] == '0':
try:
del sortfilter[value]
except KeyError:
pass
else:
sortfilter[value] = request.REQUEST[value]
query = Motion.objects.all()
if 'number' in sortfilter:
query = query.filter(number=None)
if 'status' in sortfilter:
if 'statusvalue' in sortfilter and 'on' in sortfilter['status']:
query = query.filter(status__iexact=sortfilter['statusvalue'])
if 'sort' in sortfilter:
if sortfilter['sort'] == 'title':
sort = 'aversion__title'
elif sortfilter['sort'] == 'time':
sort = 'aversion__time'
else:
sort = sortfilter['sort']
query = query.order_by(sort)
if sort.startswith('aversion_'):
# limit result to last version of an motion
query = query.filter(aversion__id__in=[x.last_version.id for x in Motion.objects.all()])
if 'reverse' in sortfilter:
query = query.reverse()
# todo: rewrite this with a .filter()
if 'needsup' in sortfilter:
motions = []
for motion in query.all():
if not motion.enough_supporters:
motions.append(motion)
else:
motions = query
if type(motions) is not list:
motions = list(query.all())
# not the most efficient way to do this but 'get_allowed_actions'
# is not callable from within djangos templates..
for (i, motion) in enumerate(motions):
try:
motions[i] = {
'actions': motion.get_allowed_actions(request.user),
'motion': motion
}
except:
# todo: except what?
motions[i] = {
'actions': [],
'motion': motion
}
return {
'motions': motions,
'min_supporters': int(config['motion_min_supporters']),
}
@permission_required('motion.can_see_motion')
@template('motion/view.html')
def view(request, motion_id, newest=False):
"""
View one motion.
"""
motion = Motion.objects.get(pk=motion_id)
if newest:
version = motion.last_version
else:
version = motion.public_version
revisions = motion.versions
actions = motion.get_allowed_actions(user=request.user)
return {
'motion': motion,
'revisions': revisions,
'actions': actions,
'min_supporters': int(config['motion_min_supporters']),
'version': version,
#'results': motion.results
}
@login_required
@template('motion/edit.html')
def edit(request, motion_id=None):
"""
View a form to edit or create a motion.
"""
if request.user.has_perm('motion.can_manage_motion'):
is_manager = True
else:
is_manager = False
if not is_manager \
and not request.user.has_perm('motion.can_create_motion'):
messages.error(request, _("You have not the necessary rights to create or edit motions."))
return redirect(reverse('motion_overview'))
if motion_id is not None:
motion = Motion.objects.get(id=motion_id)
if not 'edit' in motion.get_allowed_actions(request.user):
messages.error(request, _("You can not edit this motion."))
return redirect(reverse('motion_view', args=[motion.id]))
actions = motion.get_allowed_actions(user=request.user)
else:
motion = None
actions = None
formclass = MotionFormTrivialChanges \
if config['motion_allow_trivial_change'] and motion_id \
else MotionForm
managerformclass = MotionManagerFormSupporter \
if config['motion_min_supporters'] \
else MotionManagerForm
if request.method == 'POST':
dataform = formclass(request.POST, prefix="data")
valid = dataform.is_valid()
if is_manager:
managerform = managerformclass(request.POST,
instance=motion,
prefix="manager")
valid = valid and managerform.is_valid()
else:
managerform = None
if valid:
if is_manager:
motion = managerform.save(commit=False)
elif motion_id is None:
motion = Motion(submitter=request.user)
motion.title = dataform.cleaned_data['title']
motion.text = dataform.cleaned_data['text']
motion.reason = dataform.cleaned_data['reason']
try:
trivial_change = config['motion_allow_trivial_change'] \
and dataform.cleaned_data['trivial_change']
except KeyError:
trivial_change = False
motion.save(request.user, trivial_change=trivial_change)
if is_manager:
try:
new_supporters = set(managerform.cleaned_data['supporter'])
except KeyError:
# The managerform has no field for the supporters
pass
else:
old_supporters = set(motion.supporters)
# add new supporters
for supporter in new_supporters.difference(old_supporters):
motion.support(supporter)
# remove old supporters
for supporter in old_supporters.difference(new_supporters):
motion.unsupport(supporter)
if motion_id is None:
messages.success(request, _('New motion was successfully created.'))
else:
messages.success(request, _('Motion was successfully modified.'))
if not 'apply' in request.POST:
return redirect(reverse('motion_view', args=[motion.id]))
if motion_id is None:
return redirect(reverse('motion_edit', args=[motion.id]))
else:
messages.error(request, _('Please check the form for errors.'))
else:
if motion_id is None:
initial = {'text': config['motion_preamble']}
else:
if motion.status == "pub" and motion.supporters:
if request.user.has_perm('motion.can_manage_motion'):
messages.warning(request, _("Attention: Do you really want to edit this motion? The supporters will <b>not</b> be removed automatically because you can manage motions. Please check if the supports are valid after your changing!"))
else:
messages.warning(request, _("Attention: Do you really want to edit this motion? All <b>%s</b> supporters will be removed! Try to convince the supporters again.") % motion.count_supporters() )
initial = {'title': motion.title,
'text': motion.text,
'reason': motion.reason}
dataform = formclass(initial=initial, prefix="data")
if is_manager:
if motion_id is None:
initial = {'submitter': request.user.person_id}
else:
initial = {'submitter': motion.submitter.person_id,
'supporter': [supporter.person_id for supporter in motion.supporters]}
managerform = managerformclass(initial=initial,
instance=motion, prefix="manager")
else:
managerform = None
return {
'form': dataform,
'managerform': managerform,
'motion': motion,
'actions': actions,
}
@permission_required('motion.can_manage_motion')
@template('motion/view.html')
def set_number(request, motion_id):
"""
set a number for an motion.
"""
try:
Motion.objects.get(pk=motion_id).set_number(user=request.user)
messages.success(request, _("Motion number was successfully set."))
except Motion.DoesNotExist:
pass
except NameError:
pass
return redirect(reverse('motion_view', args=[motion_id]))
@permission_required('motion.can_manage_motion')
@template('motion/view.html')
def permit(request, motion_id):
"""
permit an motion.
"""
try:
Motion.objects.get(pk=motion_id).permit(user=request.user)
messages.success(request, _("Motion was successfully authorized."))
except Motion.DoesNotExist:
pass
except NameError, e:
messages.error(request, e)
return redirect(reverse('motion_view', args=[motion_id]))
@permission_required('motion.can_manage_motion')
@template('motion/view.html')
def notpermit(request, motion_id):
"""
reject (not permit) an motion.
"""
try:
Motion.objects.get(pk=motion_id).notpermit(user=request.user)
messages.success(request, _("Motion was successfully rejected."))
except Motion.DoesNotExist:
pass
except NameError, e:
messages.error(request, e)
return redirect(reverse('motion_view', args=[motion_id]))
@template('motion/view.html')
def set_status(request, motion_id=None, status=None):
"""
set a status of an motion.
"""
try:
if status is not None:
motion = Motion.objects.get(pk=motion_id)
motion.set_status(user=request.user, status=status)
messages.success(request, _("Motion status was set to: <b>%s</b>.") % motion.get_status_display())
except Motion.DoesNotExist:
pass
except NameError, e:
messages.error(request, e)
return redirect(reverse('motion_view', args=[motion_id]))
@permission_required('motion.can_manage_motion')
@template('motion/view.html')
def reset(request, motion_id):
"""
reset an motion.
"""
try:
Motion.objects.get(pk=motion_id).reset(user=request.user)
messages.success(request, _("Motion status was reset.") )
except Motion.DoesNotExist:
pass
return redirect(reverse('motion_view', args=[motion_id]))
class SupportView(SingleObjectMixin, QuestionMixin, RedirectView):
"""
Classed based view to support or unsupport a motion. Use
support=True or support=False in urls.py
"""
permission_required = 'motion.can_support_motion'
model = Motion
pk_url_kwarg = 'motion_id'
support = True
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return super(SupportView, self).get(request, *args, **kwargs)
def check_allowed_actions(self, request):
"""
Checks whether request.user can support or unsupport the motion.
Returns True or False.
"""
allowed_actions = self.object.get_allowed_actions(request.user)
if self.support and not 'support' in allowed_actions:
messages.error(request, _('You can not support this motion.'))
return False
elif not self.support and not 'unsupport' in allowed_actions:
messages.error(request, _('You can not unsupport this motion.'))
return False
else:
return True
def pre_redirect(self, request, *args, **kwargs):
if self.check_allowed_actions(request):
super(SupportView, self).pre_redirect(request, *args, **kwargs)
def get_question(self):
if self.support:
return _('Do you really want to support this motion?')
else:
return _('Do you really want to unsupport this motion?')
def case_yes(self):
if self.check_allowed_actions(self.request):
if self.support:
self.object.support(person=self.request.user)
else:
self.object.unsupport(person=self.request.user)
def get_success_message(self):
if self.support:
return _("You have supported this motion successfully.")
else:
return _("You have unsupported this motion successfully.")
def get_redirect_url(self, **kwargs):
return reverse('motion_view', args=[kwargs[self.pk_url_kwarg]])
@permission_required('motion.can_manage_motion')
@template('motion/view.html')
def gen_poll(request, motion_id):
"""
gen a poll for this motion.
"""
try:
poll = Motion.objects.get(pk=motion_id).gen_poll(user=request.user)
messages.success(request, _("New vote was successfully created.") )
except Motion.DoesNotExist:
pass # TODO: do not call poll after this excaption
return redirect(reverse('motion_poll_view', args=[poll.id]))
@permission_required('motion.can_manage_motion')
def delete_poll(request, poll_id):
"""
delete a poll from this motion
"""
poll = MotionPoll.objects.get(pk=poll_id)
motion = poll.motion
count = motion.polls.filter(id__lte=poll_id).count()
if request.method == 'POST':
poll.delete()
motion.writelog(_("Poll deleted"), request.user)
messages.success(request, _('Poll was successfully deleted.'))
else:
del_confirm_form(request, poll, name=_("the %s. poll") % count, delete_link=reverse('motion_poll_delete', args=[poll_id]))
return redirect(reverse('motion_view', args=[motion.id]))
class MotionDelete(DeleteView):
"""
Delete one or more Motions.
"""
model = Motion
url = 'motion_overview'
def has_permission(self, request, *args, **kwargs):
self.kwargs = kwargs
return self.get_object().get_allowed_actions(request.user)
def get_object(self):
self.motions = []
if self.kwargs.get('motion_id', None):
try:
return Motion.objects.get(id=int(self.kwargs['motion_id']))
except Motion.DoesNotExist:
return None
if self.kwargs.get('motion_ids', []):
for appid in self.kwargs['motion_ids']:
try:
self.motions.append(Motion.objects.get(id=int(appid)))
except Motion.DoesNotExist:
pass
if self.motions:
return self.motions[0]
return None
def pre_post_redirect(self, request, *args, **kwargs):
self.object = self.get_object()
if len(self.motions):
for motion in self.motions:
if not 'delete' in motion.get_allowed_actions(user=request.user):
messages.error(request, _("You can not delete motion <b>%s</b>.") % motion)
continue
title = motion.title
motion.delete(force=True)
messages.success(request, _("Motion <b>%s</b> was successfully deleted.") % title)
elif self.object:
if not 'delete' in self.object.get_allowed_actions(user=request.user):
messages.error(request, _("You can not delete motion <b>%s</b>.") % self.object)
elif self.get_answer() == 'yes':
title = self.object.title
self.object.delete(force=True)
messages.success(request, _("Motion <b>%s</b> was successfully deleted.") % title)
else:
messages.error(request, _("Invalid request"))
class ViewPoll(PollFormView):
permission_required = 'motion.can_manage_motion'
poll_class = MotionPoll
template_name = 'motion/poll_view.html'
def get_context_data(self, **kwargs):
context = super(ViewPoll, self).get_context_data(**kwargs)
self.motion = self.poll.get_motion()
context['motion'] = self.motion
context['ballot'] = self.poll.get_ballot()
context['actions'] = self.motion.get_allowed_actions(user=self.request.user)
return context
def get_modelform_class(self):
cls = super(ViewPoll, self).get_modelform_class()
user = self.request.user
class ViewPollFormClass(cls):
def save(self, commit = True):
instance = super(ViewPollFormClass, self).save(commit)
motion = instance.motion
motion.writelog(_("Poll was updated"), user)
return instance
return ViewPollFormClass
def get_success_url(self):
if not 'apply' in self.request.POST:
return reverse('motion_view', args=[self.poll.motion.id])
return ''
@permission_required('motion.can_manage_motion')
def permit_version(request, aversion_id):
aversion = AVersion.objects.get(pk=aversion_id)
motion = aversion.motion
if request.method == 'POST':
motion.accept_version(aversion, user=request.user)
messages.success(request, _("Version <b>%s</b> accepted.") % (aversion.aid))
else:
gen_confirm_form(request, _('Do you really want to authorize version <b>%s</b>?') % aversion.aid, reverse('motion_version_permit', args=[aversion.id]))
return redirect(reverse('motion_view', args=[motion.id]))
@permission_required('motion.can_manage_motion')
def reject_version(request, aversion_id):
aversion = AVersion.objects.get(pk=aversion_id)
motion = aversion.motion
if request.method == 'POST':
if motion.reject_version(aversion, user=request.user):
messages.success(request, _("Version <b>%s</b> rejected.") % (aversion.aid))
else:
messages.error(request, _("ERROR by rejecting the version.") )
else:
gen_confirm_form(request, _('Do you really want to reject version <b>%s</b>?') % aversion.aid, reverse('motion_version_reject', args=[aversion.id]))
return redirect(reverse('motion_view', args=[motion.id]))
@permission_required('motion.can_manage_motion')
@template('motion/import.html')
def motion_import(request):
if request.method == 'POST':
form = MotionImportForm(request.POST, request.FILES)
if form.is_valid():
import_permitted = form.cleaned_data['import_permitted']
try:
# check for valid encoding (will raise UnicodeDecodeError if not)
request.FILES['csvfile'].read().decode('utf-8')
request.FILES['csvfile'].seek(0)
users_generated = 0
motions_generated = 0
motions_modified = 0
groups_assigned = 0
groups_generated = 0
with transaction.commit_on_success():
dialect = csv.Sniffer().sniff(request.FILES['csvfile'].readline())
dialect = csv_ext.patchup(dialect)
request.FILES['csvfile'].seek(0)
for (lno, line) in enumerate(csv.reader(request.FILES['csvfile'], dialect=dialect)):
# basic input verification
if lno < 1:
continue
try:
(number, title, text, reason, first_name, last_name, is_group) = line[:7]
if is_group.strip().lower() in ['y', 'j', 't', 'yes', 'ja', 'true', '1', 1]:
is_group = True
else:
is_group = False
except ValueError:
messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1))
continue
form = MotionForm({'title': title, 'text': text, 'reason': reason})
if not form.is_valid():
messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1))
continue
if number:
try:
number = abs(long(number))
if number < 1:
messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1))
continue
except ValueError:
messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1))
continue
if is_group:
# fetch existing groups or issue an error message
try:
user = Group.objects.get(name=last_name)
if user.group_as_person == False:
messages.error(request, _('Ignoring line %d because the assigned group may not act as a person.') % (lno + 1))
continue
else:
user = get_person(user.person_id)
groups_assigned += 1
except Group.DoesNotExist:
group = Group()
group.group_as_person = True
group.description = _('Created by motion import.')
group.name = last_name
group.save()
groups_generated += 1
user = get_person(group.person_id)
else:
# fetch existing users or create new users as needed
try:
user = User.objects.get(first_name=first_name, last_name=last_name)
except User.DoesNotExist:
user = None
if user is None:
if not first_name or not last_name:
messages.error(request, _('Ignoring line %d because it contains an incomplete first / last name pair.') % (lno + 1))
continue
user = User()
user.last_name = last_name
user.first_name = first_name
user.username = gen_username(first_name, last_name)
user.structure_level = ''
user.committee = ''
user.gender = ''
user.type = ''
user.default_password = gen_password()
user.save()
user.reset_password()
users_generated += 1
# create / modify the motion
motion = None
if number:
try:
motion = Motion.objects.get(number=number)
motions_modified += 1
except Motion.DoesNotExist:
motion = None
if motion is None:
motion = Motion(submitter=user)
if number:
motion.number = number
motions_generated += 1
motion.title = form.cleaned_data['title']
motion.text = form.cleaned_data['text']
motion.reason = form.cleaned_data['reason']
if import_permitted:
motion.status = 'per'
motion.save(user, trivial_change=True)
if motions_generated:
messages.success(request, ungettext('%d motion was successfully imported.',
'%d motions were successfully imported.', motions_generated) % motions_generated)
if motions_modified:
messages.success(request, ungettext('%d motion was successfully modified.',
'%d motions were successfully modified.', motions_modified) % motions_modified)
if users_generated:
messages.success(request, ungettext('%d new user was added.', '%d new users were added.', users_generated) % users_generated)
if groups_generated:
messages.success(request, ungettext('%d new group was added.', '%d new groups were added.', groups_generated) % groups_generated)
if groups_assigned:
messages.success(request, ungettext('%d group assigned to motions.', '%d groups assigned to motions.', groups_assigned) % groups_assigned)
return redirect(reverse('motion_overview'))
except csv.Error:
messages.error(request, _('Import aborted because of severe errors in the input file.'))
except UnicodeDecodeError:
messages.error(request, _('Import file has wrong character encoding, only UTF-8 is supported!'))
else:
messages.error(request, _('Please check the form for errors.'))
else:
messages.warning(request, _("Attention: Existing motions will be modified if you import new motions with the same number."))
messages.warning(request, _("Attention: Importing an motions without a number multiple times will create duplicates."))
form = MotionImportForm()
return {
'form': form,
}
class CreateAgendaItem(RedirectView):
permission_required = 'agenda.can_manage_agenda'
def pre_redirect(self, request, *args, **kwargs):
self.motion = Motion.objects.get(pk=kwargs['motion_id'])
self.item = Item(related_sid=self.motion.sid)
self.item.save()
def get_redirect_url(self, **kwargs):
return reverse('item_overview')
class MotionPDF(PDFView):
permission_required = 'motion.can_see_motion'
top_space = 0
def get_filename(self):
motion_id = self.kwargs['motion_id']
if motion_id is None:
filename = _("Motions")
else:
motion = Motion.objects.get(id=motion_id)
if motion.number:
number = motion.number
else:
number = ""
filename = u'%s%s' % (_("Motion"), str(number))
return filename
def append_to_pdf(self, story):
motion_id = self.kwargs['motion_id']
if motion_id is None: #print all motions
title = config["motion_pdf_title"]
story.append(Paragraph(title, stylesheet['Heading1']))
preamble = config["motion_pdf_preamble"]
if preamble:
story.append(Paragraph("%s" % preamble.replace('\r\n','<br/>'), stylesheet['Paragraph']))
story.append(Spacer(0,0.75*cm))
motions = Motion.objects.all()
if not motions: # No motions existing
story.append(Paragraph(_("No motions available."), stylesheet['Heading3']))
else: # Print all Motions
# List of motions
for motion in motions:
if motion.number:
story.append(Paragraph(_("Motion No.")+" %s: %s" % (motion.number, motion.title), stylesheet['Heading3']))
else:
story.append(Paragraph(_("Motion No.")+"&nbsp;&nbsp;&nbsp;: %s" % (motion.title), stylesheet['Heading3']))
# Motions details (each motion on single page)
for motion in motions:
story.append(PageBreak())
story = self.get_motion(motion, story)
else: # print selected motion
motion = Motion.objects.get(id=motion_id)
story = self.get_motion(motion, story)
def get_motion(self, motion, story):
# Preparing Table
data = []
# motion number
if motion.number:
story.append(Paragraph(_("Motion No.")+" %s" % motion.number, stylesheet['Heading1']))
else:
story.append(Paragraph(_("Motion No."), stylesheet['Heading1']))
# submitter
cell1a = []
cell1a.append(Spacer(0, 0.2 * cm))
cell1a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Submitter"), stylesheet['Heading4']))
cell1b = []
cell1b.append(Spacer(0, 0.2 * cm))
cell1b.append(Paragraph("%s" % motion.submitter, stylesheet['Normal']))
data.append([cell1a, cell1b])
if motion.status == "pub":
# Cell for the signature
cell2a = []
cell2b = []
cell2a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Signature"), stylesheet['Heading4']))
cell2b.append(Paragraph("__________________________________________", stylesheet['Signaturefield']))
cell2b.append(Spacer(0, 0.1 * cm))
cell2b.append(Spacer(0,0.2*cm))
data.append([cell2a, cell2b])
# supporters
if config['motion_min_supporters']:
cell3a = []
cell3b = []
cell3a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font><seqreset id='counter'>" % _("Supporters"), stylesheet['Heading4']))
for supporter in motion.supporters:
cell3b.append(Paragraph("<seq id='counter'/>.&nbsp; %s" % supporter, stylesheet['Signaturefield']))
if motion.status == "pub":
for x in range(motion.missing_supporters):
cell3b.append(Paragraph("<seq id='counter'/>.&nbsp; __________________________________________",stylesheet['Signaturefield']))
cell3b.append(Spacer(0, 0.2 * cm))
data.append([cell3a, cell3b])
# status
cell4a = []
cell4b = []
note = " ".join(motion.notes)
cell4a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Status"), stylesheet['Heading4']))
if note != "":
if motion.status == "pub":
cell4b.append(Paragraph(note, stylesheet['Normal']))
else:
cell4b.append(Paragraph("%s | %s" % (motion.get_status_display(), note), stylesheet['Normal']))
else:
cell4b.append(Paragraph("%s" % motion.get_status_display(), stylesheet['Normal']))
data.append([cell4a, cell4b])
# Version number (aid)
if motion.public_version.aid > 1:
cell5a = []
cell5b = []
cell5a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Version"), stylesheet['Heading4']))
cell5b.append(Paragraph("%s" % motion.public_version.aid, stylesheet['Normal']))
data.append([cell5a, cell5b])
# voting results
poll_results = motion.get_poll_results()
if poll_results:
cell6a = []
cell6a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Vote results"), stylesheet['Heading4']))
cell6b = []
ballotcounter = 0
for result in poll_results:
ballotcounter += 1
if len(poll_results) > 1:
cell6b.append(Paragraph("%s. %s" % (ballotcounter, _("Vote")), stylesheet['Bold']))
cell6b.append(Paragraph("%s: %s <br/> %s: %s <br/> %s: %s <br/> %s: %s <br/> %s: %s" % (_("Yes"), result[0], _("No"), result[1], _("Abstention"), result[2], _("Invalid"), result[3], _("Votes cast"), result[4]), stylesheet['Normal']))
cell6b.append(Spacer(0, 0.2*cm))
data.append([cell6a, cell6b])
# Creating Table
t = Table(data)
t._argW[0] = 4.5 * cm
t._argW[1] = 11 * cm
t.setStyle(TableStyle([('BOX', (0, 0), (-1, -1), 1, colors.black),
('VALIGN', (0,0), (-1,-1), 'TOP')]))
story.append(t)
story.append(Spacer(0, 1 * cm))
# title
story.append(Paragraph(motion.public_version.title, stylesheet['Heading3']))
# text
story.append(Paragraph("%s" % motion.public_version.text.replace('\r\n','<br/>'), stylesheet['Paragraph']))
# reason
if motion.public_version.reason:
story.append(Paragraph(_("Reason")+":", stylesheet['Heading3']))
story.append(Paragraph("%s" % motion.public_version.reason.replace('\r\n','<br/>'), stylesheet['Paragraph']))
return story
class MotionPollPDF(PDFView):
permission_required = 'motion.can_manage_motion'
top_space = 0
def get(self, request, *args, **kwargs):
self.poll = MotionPoll.objects.get(id=self.kwargs['poll_id'])
return super(MotionPollPDF, self).get(request, *args, **kwargs)
def get_filename(self):
filename = u'%s%s_%s' % (_("Motion"), str(self.poll.motion.number), _("Poll"))
return filename
def get_template(self, buffer):
return SimpleDocTemplate(buffer, topMargin=-6, bottomMargin=-6, leftMargin=0, rightMargin=0, showBoundary=False)
def build_document(self, pdf_document, story):
pdf_document.build(story)
def append_to_pdf(self, story):
imgpath = os.path.join(settings.SITE_ROOT, 'static/images/circle.png')
circle = "<img src='%s' width='15' height='15'/>&nbsp;&nbsp;" % 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')]

View File

@ -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

View File

@ -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"
]
]
}

View File

@ -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"))

View File

@ -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()

View File

@ -12,7 +12,7 @@
{% include "form.html" %}
<p>
{% include "formbuttons_save.html" %}
<a href='{% url config_application %}' class="btn">
<a href='{% url config_participant %}' class="btn">
{% trans 'Cancel' %}
</a>
</p>

View File

@ -0,0 +1,22 @@
{% extends "participant/base_participant.html" %}
{% load i18n %}
{% load tags %}
{% block title %}{{ block.super }} {{ group }}{% endblock %}
{% block content %}
<h1>{{ group }}</h1>
<p>{{ group.description }}</p>
<h2>{% trans "Members" %}</h2>
{% for member in group.user_set.all %}
<p>{{ member }}</p>
{% empty %}
<p>{% trans "No members available." %}</p>
{% endfor %}
{% endblock %}

View File

@ -5,18 +5,18 @@
{% block title %}
{{ block.super }}
{% if group %}
{% trans "Edit user group" %}
{% trans "Edit group" %}
{% else %}
{% trans "New user group" %}
{% trans "New group" %}
{% endif %}
{% endblock %}
{% block content %}
<h1>
{% if group %}
{% trans "Edit user group" %}
{% trans "Edit group" %}
{% else %}
{% trans "New user group" %}
{% trans "New group" %}
{% endif %}
<small class="pull-right">
<a href="{% url user_group_overview %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Back to overview" %}</a>

View File

@ -2,34 +2,35 @@
{% load i18n %}
{% load staticfiles %}
{% load tags %}
{% block title %}{{ block.super }} {% trans "User groups" %}{% endblock %}
{% block title %}{{ block.super }} {% trans "Groups" %}{% endblock %}
{% block content %}
<h1>{% trans "User groups" %}
<h1>{% trans "Groups" %}
<small class="pull-right">
<a href="{% url user_group_new %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'New user group' %}"><i class="icon-plus"></i> {% trans "New" %}</a>
<a href="{% url user_group_new %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'New group' %}"><i class="icon-plus"></i> {% trans "New" %}</a>
<a href="{% url user_overview %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Participants" %}</a>
</small>
</h1>
<table class="table table-striped table-bordered">
<tr>
<th>{% trans "User Group" %}</th>
<th>{% trans "Group" %}</th>
<th>{% trans "Actions" %}</th>
</tr>
{% for group in groups %}
<tr class="{% cycle '' 'odd' %}">
<td>{{ group.name }}</td>
<td><a href="{% model_url group 'view' %}">{{ group.name }}</a></td>
<td><a href="{% url user_group_edit group.id %}"><img src="{% static 'images/icons/edit.png' %}" title="{% trans 'Edit group' %}"></a>
{% if group.name != 'Anonymous' %}
{% if group.name|lower != 'anonymous' and group.name|lower != 'registered' %}
<a href="{% url user_group_delete group.id %}"><img src="{% static 'images/icons/delete.png' %}" title="{% trans 'Delete group' %}"></a>
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="5"><i>{% trans "No user groups available." %}</i></td>
<td colspan="5"><i>{% trans "No groups available." %}</i></td>
</tr>
{% endfor %}
</table>

View File

@ -0,0 +1,26 @@
{% load i18n %}
{% load tags %}
<ul style="line-height: 180%">
{% for group in groups %}
{% if group.name != 'Anonymous' and group.name != 'Registered' %}
<li class="{% if group.active %}activeline{% endif %}">
<a href="{% url projector_activate_slide group.sid %}" class="activate_link {% if group.active %}active{% endif %}">
<div></div>
</a>
<a href="{% model_url group 'delete' %}" title="{% trans 'Delete' %}" class="icon delete right">
<span></span>
</a>
<a href="{% model_url group 'edit' %}" title="{% trans 'Edit' %}" class="icon edit right">
<span></span>
</a>
<a href="{% url projctor_preview_slide group.sid %}" title="{% trans 'Preview' %}" class="icon preview right">
<span></span>
</a>
<a href="{% model_url group 'view' %}">{{ group }}</a>
</li>
{% endif %}
{% empty %}
<li>{% trans 'No groups available.' %}</li>
{% endfor %}
</ul>

View File

@ -14,7 +14,8 @@
<p>{% trans 'Select a CSV file to import participants!' %}</p>
<p>{% trans 'Required comma separated values: <code>{first_name, last_name, gender, group, type, committee, comment}</code>' %}
<p>{% trans 'Required comma separated values' %}:
<code>({% trans 'first_name, last_name, gender, structure level, type, committee, comment' %})</code>
<br>
{% trans 'Required CSV file encoding: UTF-8 (Unicode).' %}
</p>

View File

@ -60,11 +60,11 @@
<option value="female"{% if 'female' in sortfilter.gender %} selected{% endif %}>{% trans "Female" %}</option>
<option value=""{% if '' in sortfilter.gender %} selected{% endif %}>{% trans "Not specified" %}</option>
</select>
<select class="span2" name="category" onchange="document.forms['filter'].submit()">
<option value="---">-- {% trans "Category" %} --</option>
{% for category in categories %}
<option value="{{ category }}"{% if category in sortfilter.category %} selected{% endif %}>
{{ category }}</option>
<select class="span2" name="structure_level" onchange="document.forms['filter'].submit()">
<option value="---">-- {% trans "Structure level" %} --</option>
{% for level in structure_levels %}
<option value="{{ level }}"{% if level in sortfilter.structure_level %} selected{% endif %}>
{{ level }}</option>
{% endfor %}
</select>
<select class="span2" name="type" onchange="document.forms['filter'].submit()">
@ -101,7 +101,7 @@
<tr>
<th><a href="?sort=first_name&reverse={% if 'first_name' in sortfilter.sort and 'reverse' not in sortfilter %}1{% else %}---{%endif%}">{% trans "First Name" %}</a></th>
<th><a href="?sort=last_name&reverse={% if 'last_name' in sortfilter.sort and 'reverse' not in sortfilter %}1{% else %}---{%endif%}">{% trans "Last Name" %}</a></th>
<th class="optional"><a href="?sort=group&reverse={% if 'category' in sortfilter.sort and 'reverse' not in sortfilter %}1{% else %}---{%endif%}">{% trans "Category" %}</a></th>
<th class="optional"><a href="?sort=structure_level&reverse={% if 'structure_level' in sortfilter.sort and 'reverse' not in sortfilter %}1{% else %}---{%endif%}">{% trans "Structure level" %}</a></th>
<th class="optional"><a href="?sort=type&reverse={% if 'type' in sortfilter.sort and 'reverse' not in sortfilter %}1{% else %}---{%endif%}">{% trans "Type" %}</a></th>
<th class="optional"><a href="?sort=committee&reverse={% if 'committee' in sortfilter.sort and 'reverse' not in sortfilter %}1{% else %}---{%endif%}">{% trans "Committee" %}</a></th>
{% if perms.participant.can_manage_participant %}
@ -111,10 +111,10 @@
{% endif %}
</tr>
{% for user in users %}
<tr class="{% cycle '' 'odd' %}">
<tr>
<td>{{ user.first_name }}</td>
<td>{{ user.last_name }}</td>
<td class="optional">{{ user.category }}</td>
<td class="optional">{{ user.structure_level }}</td>
<td class="optional">{{ user.get_type_display }}</td>
<td class="optional">{{ user.committee }}</td>
{% if perms.participant.can_manage_participant %}
@ -129,15 +129,19 @@
<a href="{% url user_edit user.id %}">
<img src="{% static 'images/icons/edit.png' %}" title="{% trans 'Edit participant' %}">
</a>
{% if user != request_user %}
<a href="{% url user_delete user.id %}">
<img src="{% static 'images/icons/delete.png' %}" title="{% trans 'Delete participant' %}">
</a>
{% endif %}
{% if user != request_user and not user.is_superuser %}
<a class="status_link deactivate" href="{% url user_status_deactivate user.id %}" title="{% trans 'Change status to inactive' %}"{% if not user.is_active %} style="display:none"{% endif %}>
<span></span>
</a>
<a class="status_link activate" href="{% url user_status_activate user.id %}" title="{% trans 'Change status to active' %}"{% if user.is_active %} style="display:none"{% endif %}>
<span></span>
</a>
{% endif %}
</span>
</td>
{% endif %}

View File

@ -0,0 +1,49 @@
{% load i18n %}
{% load tags %}
<ul style="line-height: 180%">
{% trans "I submitted the following motions:" %}
{% for motion in submitted_motions %}
<li>
<a href="{% model_url motion 'view' %}">{{ motion.public_version.title }}</a>
({% trans "motion" %}
{% if motion.number %}
{{ motion.number }})
{% else %}
<i>[{% trans "no number" %}]</i>)
{% endif %}
</li>
{% empty %}
<li><i>{% trans "None" %}</i></li>
{% endfor %}
</ul>
{% if config_motion_min_supporters %}
<hr />
<ul style="line-height: 180%">
{% trans "I support the following motions:" %}
{% for motion in supported_motions %}
<li>
<a href="{% model_url motion 'view' %}">{{ motion.public_version.title }}</a>
({% trans "motion" %}
{% if motion.number %}
{{ motion.number }})
{% else %}
<i>[{% trans "no number" %}]</i>)
{% endif %}
</li>
{% empty %}
<li><i>{% trans "None" %}</i></li>
{% endfor %}
</ul>
{% endif %}
<hr />
<ul style="line-height: 180%">
{% trans "I am candidate for the following elections:" %}
{% for assignment in assignments %}
<li><a href="{% model_url assignment 'view' %}">{{ assignment }}</a></li>
{% empty %}
<li><i>{% trans "None" %}</i></li>
{% endfor %}
</ul>

View File

@ -0,0 +1,57 @@
{% extends "participant/base_participant.html" %}
{% load i18n %}
{% load tags %}
{% block title %}{{ block.super }} {{ shown_user }}{% endblock %}
{% block content %}
<h1>{{ shown_user }}</h1>
<p>{{ shown_user.email }}</p>
<h2>{% trans "Groups" %}</h2>
<p>
{% if shown_user.groups.all %}
{{ shown_user.groups.all|join:", " }}
{% else %}
{% trans "The participant is not member of any group." %}
{% endif %}
</p>
{% if shown_user.get_gender_display %}
<h2>{% trans "Gender" %}</h2>
<p>{{ shown_user.get_gender_display }}</p>
{% endif %}
{% if shown_user.get_type_display %}
<h2>{% trans "Type" %}</h2>
<p>{{ shown_user.get_type_display }}</p>
{% endif %}
{% if shown_user.committee %}
<h2>{% trans "Committee" %}</h2>
<p>{{ shown_user.committee }}</p>
{% endif %}
{% if shown_user.about_me %}
<h2>{% trans "About me" %}</h2>
<p>{{ shown_user.about_me }}</p>
{% endif %}
{% if perms.participant.can_manage_participant %}
{% if shown_user.comment %}
<h2>{% trans "Comment" %}</h2>
<p>{{ shown_user.comment }}</p>
{% endif %}
<h2>{% trans "Last Login" %}</h2>
{% if shown_user.last_login > shown_user.date_joined %}
<p>{{ shown_user.last_login }}</p>
{% else %}
<p>{% trans "The participant has not logged in yet." %}</p>
{% endif %}
{% endif %}
{% endblock %}

View File

@ -0,0 +1,24 @@
{% load i18n %}
{% load tags %}
<ul style="line-height: 180%">
{% for user in users %}
<li class="{% if user.active %}activeline{% endif %}">
<a href="{% url projector_activate_slide user.sid %}" class="activate_link {% if user.active %}active{% endif %}">
<div></div>
</a>
<a href="{% model_url user 'delete' %}" title="{% trans 'Delete' %}" class="icon delete right">
<span></span>
</a>
<a href="{% model_url user 'edit' %}" title="{% trans 'Edit' %}" class="icon edit right">
<span></span>
</a>
<a href="{% url projctor_preview_slide user.sid %}" title="{% trans 'Preview' %}" class="icon preview right">
<span></span>
</a>
<a href="{% model_url user 'view' %}">{{ user }}</a>
</li>
{% empty %}
<li>{% trans 'No participants available.' %}</li>
{% endfor %}
</ul>

View File

@ -0,0 +1,22 @@
{% extends "base-projector.html" %}
{% load i18n %}
{% load tags %}
{% block title %}{{ block.super }} - {{ title }}{% endblock %}
{% block content %}
<div class="item_fullscreen">{{ group }}
<span>
<p><i>{{ group.user_set.all.count }} {% trans "participants" %}</i></p>
{% comment %}
TODO: print fullname (not username) of all group users [see #420]
<p>
{% if group.user_set.all %}
{{ group.user_set.all|join:", " }}
{% endif %}
</p>
{% endcomment %}
</span>
</div>
{% endblock %}

View File

@ -0,0 +1,21 @@
{% extends "base-projector.html" %}
{% load i18n %}
{% load tags %}
{% block title %}{{ block.super }} - {{ title }}{% endblock %}
{% block content %}
<div class="item_fullscreen">{{ shown_user }}
<span>
{% if shown_user.committee %}
<p>{{ shown_user.committee }}</p>
{% endif %}
<p>
{% if shown_user.groups.all %}
{{ shown_user.groups.all|join:", " }}
{% endif %}
</p>
</span>
</div>
{% endblock %}

View File

@ -11,17 +11,18 @@
"""
from django.conf.urls.defaults import url, patterns
from django.core.urlresolvers import reverse
from openslides.participant.views import (
ParticipantsListPDF, ParticipantsPasswordsPDF, Overview, UserCreateView,
UserUpdateView, UserDeleteView, SetUserStatusView, UserImportView,
ResetPasswordView, GroupOverviewView, GroupCreateView, GroupUpdateView,
GroupDeleteView)
UserOverview, UserCreateView, UserDetailView, UserUpdateView,
UserDeleteView, ResetPasswordView, SetUserStatusView, UserImportView,
GroupOverview, GroupCreateView, GroupDetailView, GroupUpdateView, GroupDeleteView,
ParticipantsListPDF, ParticipantsPasswordsPDF)
urlpatterns = patterns('openslides.participant.views',
urlpatterns = patterns('',
# User
url(r'^$',
Overview.as_view(),
UserOverview.as_view(),
name='user_overview',
),
@ -30,6 +31,11 @@ urlpatterns = patterns('openslides.participant.views',
name='user_new',
),
url(r'^(?P<pk>\d+)/$',
UserDetailView.as_view(),
name='user_view',
),
url(r'^(?P<pk>\d+)/edit/$',
UserUpdateView.as_view(),
name='user_edit',
@ -45,12 +51,6 @@ urlpatterns = patterns('openslides.participant.views',
name='user_reset_password',
),
url(r'^(?P<pk>\d+)/status/toggle/$',
SetUserStatusView.as_view(),
{'action': 'toggle'},
name='user_status_toggle',
),
url(r'^(?P<pk>\d+)/status/activate/$',
SetUserStatusView.as_view(),
{'action': 'activate'},
@ -63,13 +63,20 @@ urlpatterns = patterns('openslides.participant.views',
name='user_status_deactivate',
),
url(r'^(?P<pk>\d+)/status/toggle/$',
SetUserStatusView.as_view(),
{'action': 'toggle'},
name='user_status_toggle',
),
url(r'^import/$',
UserImportView.as_view(),
name='user_import',
),
# Group
url(r'^group/$',
GroupOverviewView.as_view(),
GroupOverview.as_view(),
name='user_group_overview',
),
@ -78,6 +85,11 @@ urlpatterns = patterns('openslides.participant.views',
name='user_group_new',
),
url(r'^group/(?P<pk>\d+)/$',
GroupDetailView.as_view(),
name='user_group_view',
),
url(r'^group/(?P<pk>\d+)/edit/$',
GroupUpdateView.as_view(),
name='user_group_edit',
@ -88,6 +100,7 @@ urlpatterns = patterns('openslides.participant.views',
name='user_group_delete',
),
# PDF
url(r'^print/$',
ParticipantsListPDF.as_view(),
name='user_print',

View File

@ -28,7 +28,6 @@ from reportlab.platypus import (
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth.models import User
from django.contrib.auth.views import login as django_login
from django.core.urlresolvers import reverse
from django.shortcuts import redirect
@ -39,11 +38,12 @@ from openslides.utils.template import Tab
from openslides.utils.utils import (
template, decodedict, encodedict, delete_default_permissions, html_strong)
from openslides.utils.views import (
FormView, PDFView, CreateView, UpdateView, DeleteView,
RedirectView, SingleObjectMixin, ListView, QuestionMixin)
FormView, PDFView, CreateView, UpdateView, DeleteView, PermissionMixin,
RedirectView, SingleObjectMixin, ListView, QuestionMixin, DetailView)
from openslides.config.models import config
from openslides.projector.projector import Widget
from openslides.motion.models import Motion
from openslides.assignment.models import Assignment
from openslides.participant.api import gen_username, gen_password, import_users
from openslides.participant.forms import (
UserCreateForm, UserUpdateForm, UsersettingsForm,
@ -51,9 +51,9 @@ from openslides.participant.forms import (
from openslides.participant.models import User, Group
class Overview(ListView):
class UserOverview(ListView):
"""
Show all participants.
Show all participants (users).
"""
permission_required = 'participant.can_see_participant'
template_name = 'participant/overview.html'
@ -66,8 +66,8 @@ class Overview(ListView):
except KeyError:
sortfilter = {}
for value in [u'gender', u'category', u'type', u'committee', u'status',
u'sort', u'reverse']:
for value in ['gender', 'structure_level', 'type', 'committee', 'status',
'sort', 'reverse']:
if value in self.request.REQUEST:
if self.request.REQUEST[value] == '---':
try:
@ -80,8 +80,8 @@ class Overview(ListView):
query = User.objects
if 'gender' in sortfilter:
query = query.filter(gender__iexact=sortfilter['gender'][0])
if 'category' in sortfilter:
query = query.filter(category__iexact=sortfilter['category'][0])
if 'structure_level' in sortfilter:
query = query.filter(structure_level__iexact=sortfilter['structure_level'][0])
if 'type' in sortfilter:
query = query.filter(type__iexact=sortfilter['type'][0])
if 'committee' in sortfilter:
@ -92,11 +92,14 @@ class Overview(ListView):
if sortfilter['sort'][0] in ['first_name', 'last_name', 'last_login']:
query = query.order_by(sortfilter['sort'][0])
elif (sortfilter['sort'][0] in
['category', 'type', 'committee', 'comment']):
['structure_level', 'type', 'committee', 'comment']):
query = query.order_by(
'%s' % sortfilter['sort'][0])
else:
query = query.order_by('last_name')
if config['participant_sort_users_by_first_name']:
query = query.order_by('first_name')
else:
query = query.order_by('last_name')
if 'reverse' in sortfilter:
query = query.reverse()
@ -106,7 +109,7 @@ class Overview(ListView):
return query.all()
def get_context_data(self, **kwargs):
context = super(Overview, self).get_context_data(**kwargs)
context = super(UserOverview, self).get_context_data(**kwargs)
all_users = User.objects.count()
@ -117,23 +120,47 @@ class Overview(ListView):
percent = 0
# list of all existing categories
categories = [p['category'] for p in User.objects.values('category')
.exclude(category='').distinct()]
structure_levels = [
p['structure_level'] for p in
User.objects.values('structure_level').exclude(structure_level='').distinct()]
# list of all existing committees
committees = [p['committee'] for p in User.objects.values('committee')
.exclude(committee='').distinct()]
committees = [
p['committee'] for p in
User.objects.values('committee').exclude(committee='').distinct()]
# context vars
context.update({
'allusers': all_users,
'request_user': self.request.user,
'percent': round(percent, 1),
'categories': categories,
'structure_levels': structure_levels,
'committees': committees,
'cookie': ['participant_sortfilter', urlencode(decodedict(self.sortfilter),
'cookie': [
'participant_sortfilter', urlencode(decodedict(self.sortfilter),
doseq=True)],
'sortfilter': self.sortfilter})
return context
class UserDetailView(DetailView, PermissionMixin):
"""
Classed based view to show a specific user in the interface.
"""
permission_required = 'participant.can_see_participant'
model = User
template_name = 'participant/user_detail.html'
context_object_name = 'shown_user'
class GroupDetailView(DetailView, PermissionMixin):
"""
Classed based view to show a specific group in the interface.
"""
permission_required = 'participant.can_manage_participant'
model = Group
template_name = 'participant/group_detail.html'
context_object_name = 'group'
class UserCreateView(CreateView):
"""
Create a new participant.
@ -147,8 +174,8 @@ class UserCreateView(CreateView):
apply_url = 'user_edit'
def manipulate_object(self, form):
self.object.username = gen_username(form.cleaned_data['first_name'],
form.cleaned_data['last_name'])
self.object.username = gen_username(
form.cleaned_data['first_name'], form.cleaned_data['last_name'])
if not self.object.default_password:
self.object.default_password = gen_password()
self.object.set_password(self.object.default_password)
@ -175,6 +202,12 @@ class UserDeleteView(DeleteView):
model = User
url = 'user_overview'
def pre_redirect(self, request, *args, **kwargs):
if self.get_object() == self.request.user:
messages.error(request, _("You can not delete yourself."))
else:
super(UserDeleteView, self).pre_redirect(request, *args, **kwargs)
class SetUserStatusView(RedirectView, SingleObjectMixin):
"""
@ -191,6 +224,12 @@ class SetUserStatusView(RedirectView, SingleObjectMixin):
if action == 'activate':
self.object.is_active = True
elif action == 'deactivate':
if self.get_object().user == self.request.user:
messages.error(request, _("You can not deactivate yourself."))
return
elif self.get_object().is_superuser:
messages.error(request, _("You can not deactivate the administrator."))
return
self.object.is_active = False
elif action == 'toggle':
self.object.is_active = not self.object.is_active
@ -214,7 +253,10 @@ class ParticipantsListPDF(PDFView):
def append_to_pdf(self, story):
data = [['#', _('Last Name'), _('First Name'), _('Group'), _('Type'),
_('Committee')]]
sort = 'last_name'
if config['participant_sort_users_by_first_name']:
sort = 'first_name'
else:
sort = 'last_name'
counter = 0
for user in User.objects.all().order_by(sort):
counter += 1
@ -222,8 +264,8 @@ class ParticipantsListPDF(PDFView):
counter,
Paragraph(user.last_name, stylesheet['Tablecell']),
Paragraph(user.first_name, stylesheet['Tablecell']),
Paragraph(user.category, stylesheet['Tablecell']),
Paragraph(user.type, stylesheet['Tablecell']),
Paragraph(user.structure_level, stylesheet['Tablecell']),
Paragraph(user.get_type_display(), stylesheet['Tablecell']),
Paragraph(user.committee, stylesheet['Tablecell'])])
t = LongTable(data, style=[
('VALIGN', (0, 0), (-1, -1), 'TOP'),
@ -256,7 +298,11 @@ class ParticipantsPasswordsPDF(PDFView):
data = []
participant_pdf_system_url = config["participant_pdf_system_url"]
participant_pdf_welcometext = config["participant_pdf_welcometext"]
for user in User.objects.all().order_by('last_name'):
if config['participant_sort_users_by_first_name']:
sort = 'first_name'
else:
sort = 'last_name'
for user in User.objects.all().order_by(sort):
cell = []
cell.append(Spacer(0, 0.8 * cm))
cell.append(Paragraph(_("Account for OpenSlides"),
@ -269,7 +315,7 @@ class ParticipantsPasswordsPDF(PDFView):
cell.append(
Paragraph(
_("Password: %s")
% (user.firstpassword), stylesheet['Monotype']))
% (user.default_password), stylesheet['Monotype']))
cell.append(Spacer(0, 0.5 * cm))
cell.append(
Paragraph(
@ -319,9 +365,9 @@ class UserImportView(FormView):
return super(UserImportView, self).form_valid(form)
class ResetPasswordView(RedirectView, SingleObjectMixin, QuestionMixin):
class ResetPasswordView(SingleObjectMixin, QuestionMixin, RedirectView):
"""
Set the Passwort for a user to his firstpassword.
Set the Passwort for a user to his default password.
"""
permission_required = 'participant.can_manage_participant'
model = User
@ -335,20 +381,14 @@ class ResetPasswordView(RedirectView, SingleObjectMixin, QuestionMixin):
def get_redirect_url(self, **kwargs):
return reverse('user_edit', args=[self.object.id])
def pre_redirect(self, request, *args, **kwargs):
self.confirm_form()
def case_yes(self):
self.object.reset_password()
def pre_post_redirect(self, request, *args, **kwargs):
if self.get_answer().lower() == 'yes':
self.object.reset_password()
messages.success(request,
_('The Password for %s was successfully reset.') % html_strong(self.object))
def get_answer_url(self):
return reverse('user_reset_password', args=[self.object.id])
def get_success_message(self):
return _('The Password for %s was successfully reset.') % html_strong(self.object)
class GroupOverviewView(ListView):
class GroupOverview(ListView):
"""
Overview over all groups.
"""
@ -400,6 +440,12 @@ class GroupDeleteView(DeleteView):
model = Group
url = 'user_group_overview'
def pre_redirect(self, request, *args, **kwargs):
if self.get_object().name.lower() in ['anonymous', 'registered']:
messages.error(request, _("You can not delete this Group."))
else:
super(GroupDeleteView, self).pre_redirect(request, *args, **kwargs)
class Config(FormView):
"""
@ -412,13 +458,16 @@ class Config(FormView):
def get_initial(self):
return {
'participant_pdf_system_url': config['participant_pdf_system_url'],
'participant_pdf_welcometext': config['participant_pdf_welcometext']}
'participant_pdf_welcometext': config['participant_pdf_welcometext'],
'participant_sort_users_by_first_name': config['participant_sort_users_by_first_name']}
def form_valid(self, form):
config['participant_pdf_system_url'] = (
form.cleaned_data['participant_pdf_system_url'])
config['participant_pdf_welcometext'] = (
form.cleaned_data['participant_pdf_welcometext'])
config['participant_sort_users_by_first_name'] = (
form.cleaned_data['participant_sort_users_by_first_name'])
messages.success(
self.request,
_('Participants settings successfully saved.'))
@ -497,6 +546,67 @@ def register_tab(request):
title=_('Participants'),
app='participant',
url=reverse('user_overview'),
permission=request.user.has_perm('participant.can_see_participant') or
request.user.has_perm('participant.can_manage_participant'),
permission=(
request.user.has_perm('participant.can_see_participant') or
request.user.has_perm('participant.can_manage_participant')),
selected=selected)
def get_widgets(request):
"""
Returns all widgets of the participant app. This is a user_widget, a
group_widget and a personal_info_widget.
"""
return [
get_personal_info_widget(request),
get_user_widget(request),
get_group_widget(request)]
def get_personal_info_widget(request):
"""
Provides a widget for personal info. It shows your submitted motions
and where you are supporter or candidate.
"""
personal_info_context = {
'submitted_motions': Motion.objects.filter(submitter=request.user),
'config_motion_min_supporters': config['motion_min_supporters'],
'supported_motions': Motion.objects.filter(motionsupporter=request.user),
'assignments': Assignment.objects.filter(
assignmentcandidate__person=request.user,
assignmentcandidate__blocked=False)}
return Widget(
name='personal_info',
display_name=_('My motions and elections'),
template='participant/personal_info_widget.html',
context=personal_info_context,
permission_required=None,
default_column=1)
def get_user_widget(request):
"""
Provides a widget with all users. This is for short activation of
user slides.
"""
return Widget(
name='user',
display_name=_('Participants'),
template='participant/user_widget.html',
context={'users': User.objects.all()},
permission_required='projector.can_manage_projector',
default_column=1)
def get_group_widget(request):
"""
Provides a widget with all groups. This is for short activation of
group slides.
"""
return Widget(
name='group',
display_name=_('Groups'),
template='participant/group_widget.html',
context={'groups': Group.objects.all()},
permission_required='projector.can_manage_projector',
default_column=1)

View File

@ -11,7 +11,6 @@
"""
from django import forms
from django.utils.translation import ugettext_lazy as _
from openslides.utils.forms import CssClassMixin

View File

@ -47,7 +47,7 @@ class BaseVote(models.Model):
Subclasses have to define a option-field, which are a subclass of
BaseOption.
"""
weight = models.IntegerField(default=1, null=True) # Use MinMaxIntegerField
weight = models.IntegerField(default=1, null=True) # Use MinMaxIntegerField
value = models.CharField(max_length=255, null=True)
def print_weight(self, raw=False):
@ -73,7 +73,7 @@ class BaseVote(models.Model):
class CountVotesCast(models.Model):
votescast = MinMaxIntegerField(null=True, blank=True, min_value=-2,
verbose_name=_("Votes cast"))
verbose_name=_("Votes cast"))
def append_pollform_fields(self, fields):
fields.append('votescast')
@ -92,7 +92,7 @@ class CountVotesCast(models.Model):
class CountInvalid(models.Model):
votesinvalid = MinMaxIntegerField(null=True, blank=True, min_value=-2,
verbose_name=_("Votes invalid"))
verbose_name=_("Votes invalid"))
def append_pollform_fields(self, fields):
fields.append('votesinvalid')
@ -164,7 +164,6 @@ class BasePoll(models.Model):
"""
return self.vote_values
def get_vote_class(self):
"""
Return the releatet vote class.
@ -212,7 +211,7 @@ class BasePoll(models.Model):
"""
from openslides.poll.forms import OptionForm
return OptionForm(extra=self.get_form_values(kwargs['formid']),
**kwargs)
**kwargs)
def get_vote_forms(self, **kwargs):
"""
@ -236,7 +235,7 @@ def print_value(value, percent_base=0):
elif value == -2:
return unicode(_('undocumented'))
elif value is None:
return u''
return unicode(_('undocumented'))
if not percent_base:
return u'%s' % value

View File

@ -35,7 +35,7 @@ class PollFormView(TemplateView):
context['forms'] = self.poll.get_vote_forms()
FormClass = self.get_modelform_class()
context['pollform'] = FormClass(instance=self.poll,
prefix='pollform')
prefix='pollform')
return context
def get_success_url(self):
@ -52,7 +52,7 @@ class PollFormView(TemplateView):
FormClass = self.get_modelform_class()
pollform = FormClass(data=self.request.POST, instance=self.poll,
prefix='pollform')
prefix='pollform')
error = False
for form in option_forms:

View File

@ -11,12 +11,12 @@
"""
from django.conf import settings
from django.template.loader import render_to_string
from django.core.cache import cache
from django.utils.datastructures import SortedDict
from django.utils.importlib import import_module
from openslides.config.models import config
from openslides.projector.projector import SLIDE, Slide, Widget
from openslides.projector.projector import SLIDE, Slide
def split_sid(sid):
@ -85,29 +85,27 @@ def set_active_slide(sid, argument=None):
"""
config["presentation"] = sid
config['presentation_argument'] = argument
clear_projector_cache()
def register_slidemodel(model, model_name=None, control_template=None,
weight=0):
def clear_projector_cache():
cache.delete('projector_content')
cache.delete('projector_scrollcontent')
cache.delete('projector_data')
def register_slidemodel(model, model_name=None, control_template=None, weight=0):
"""
Register a Model as a slide.
"""
# TODO: control_template should never be None
if model_name is None:
model_name = model.prefix
if control_template is None:
control_template = 'projector/default_control_slidemodel.html'
category = model.__module__.split('.')[0]
SLIDE[model_name] = Slide(
model_slide=True,
model=model,
category=category,
key=model.prefix,
model_name=model_name,
control_template=control_template,
weight=weight,
)
SLIDE[model_name] = Slide(model_slide=True, model=model, category=category,
key=model.prefix, model_name=model_name,
control_template=control_template, weight=weight)
def register_slidefunc(key, func, control_template=None, weight=0, name=''):
@ -117,15 +115,9 @@ def register_slidefunc(key, func, control_template=None, weight=0, name=''):
if control_template is None:
control_template = 'projector/default_control_slidefunc.html'
category = func.__module__.split('.')[0]
SLIDE[key] = Slide(
model_slide=False,
func=func,
category=category,
key=key,
control_template=control_template,
weight=weight,
name=name,
)
SLIDE[key] = Slide(model_slide=False, func=func, category=category,
key=key, control_template=control_template, weight=weight,
name=name,)
def projector_message_set(message, sid=None):
@ -139,7 +131,7 @@ def projector_message_set(message, sid=None):
overlay = ProjectorOverlay.objects.get(def_name='Message')
except ProjectorOverlay.DoesNotExist:
overlay = ProjectorOverlay(def_name='Message', active=False)
overlay.sid=sid
overlay.sid = sid
overlay.save()
@ -158,7 +150,6 @@ def get_all_widgets(request, session=False):
mod = import_module(app + '.views')
except ImportError:
continue
appname = mod.__name__.split('.')[0]
try:
modul_widgets = mod.get_widgets(request)
except AttributeError:

View File

@ -11,7 +11,6 @@
"""
from django import forms
from django.utils.translation import ugettext_lazy as _
from openslides.utils.forms import CssClassMixin

View File

@ -19,9 +19,6 @@ from openslides.config.signals import default_config_value
from openslides.projector.api import register_slidemodel
from openslides.projector.projector import SlideMixin
from openslides.config.models import config
class ProjectorSlide(models.Model, SlideMixin):
"""
@ -31,6 +28,7 @@ class ProjectorSlide(models.Model, SlideMixin):
title = models.CharField(max_length=256, verbose_name=_("Title"))
text = models.TextField(null=True, blank=True, verbose_name=_("Text"))
weight = models.IntegerField(default=0, verbose_name=_("Weight"))
def slide(self):
return {
@ -55,8 +53,7 @@ class ProjectorSlide(models.Model, SlideMixin):
)
register_slidemodel(ProjectorSlide,
control_template='projector/control_customslide.html')
register_slidemodel(ProjectorSlide, control_template='projector/control_customslide.html')
class ProjectorOverlay(models.Model):

View File

@ -19,9 +19,9 @@ from openslides.config.models import config
from openslides.projector.signals import projector_overlays
SLIDE = {}
class SlideMixin(object):
"""
A Mixin for a Django-Model, for making the model a slide.
@ -49,15 +49,24 @@ class SlideMixin(object):
"""
Return True, if the the slide is the active slide.
"""
from api import get_active_slide
if self.id is None:
return False
from openslides.projector.api import get_active_slide
return get_active_slide(only_sid=True) == self.sid
def set_active(self):
"""
Appoint this item as the active slide.
"""
from openslides.projector.api import set_active_slide
set_active_slide(self.sid)
def save(self, *args, **kwargs):
if self.active:
from api import clear_projector_cache
clear_projector_cache()
return super(SlideMixin, self).save(*args, **kwargs)
class Slide(object):
"""
@ -106,7 +115,7 @@ class Widget(object):
Class for a Widget for the Projector-Tab.
"""
def __init__(self, name, html=None, template=None, context={},
permission_required=None, display_name=None, default_column=1):
permission_required=None, display_name=None, default_column=1):
self.name = name
if display_name is None:
self.display_name = name.capitalize()
@ -133,6 +142,9 @@ class Widget(object):
def __repr__(self):
return unicode(self.display_name)
def __unicode__(self):
return unicode(self.display_name)
@receiver(projector_overlays, dispatch_uid="projector_countdown")
def countdown(sender, **kwargs):

View File

@ -12,6 +12,7 @@ body{
font-family: 'Lucida Grande',"Trebuchet MS",Verdana,sans-serif;
font-size : 20px;
background-color: #FAFAFB;
overflow: hidden;
}
/*** HEADER ***/
@ -154,6 +155,10 @@ body{
font-weight:bold;
text-align: center;
}
.item_fullscreen span
{
font-size: 50%; font-weight:normal;
}
/* items in a list*/
.itemlist li

View File

@ -7,8 +7,13 @@
{% block content %}
{% if slide.text %}
<h1>{{ slide.title }}</h1>
{{ slide.text|safe|linebreaks }}
{% else %}
<div class="item_fullscreen">{{ slide.title }}</div>
{% endif %}
{% endblock %}
{% block scrollcontent %}
{% if slide.text %}
<span>{{ slide.text|safe|linebreaks }}</span>
{% endif %}
{% endblock %}

View File

@ -20,27 +20,6 @@
<a href="{% url projector_select_widgets %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Manage widgets' %}"><i class="icon-th-large"></i> {% trans 'Widgets' %}</a>
</small>
</h1>
{% if perms.projector.can_manage_projector %}
<div style="text-align: right; padding: 0 10px 5px 0; margin-top:-20px;">
<!-- control projector view -->
{% trans "Adjust projector view" %}:
<a class="projector_edit" href="{% url projector_bigger %}" title="{% trans 'Zoom in' %}">
<img src="{% static 'images/icons/zoom-in.png' %}" />
</a>
<a class="projector_edit" href="{% url projector_smaller %}" title="{% trans 'Zoom out' %}">
<img src="{% static 'images/icons/zoom-out.png' %}" />
</a>
<a class="projector_edit" href="{% url projector_up %}" title="{% trans 'Scroll text up' %}">
<img src="{% static 'images/icons/go-up.png' %}" />
</a>
<a class="projector_edit" href="{% url projector_down %}" title="{% trans 'Scroll text down' %}">
<img src="{% static 'images/icons/go-down.png' %}" />
</a>
<a class="projector_edit" href="{% url projector_clean %}" title="{% trans 'Reset projector view' %}">
<img src="{% static 'images/icons/view-reset.png' %}" />
</a>
</div>
{% endif %}
<div class="column" id="col1">
{% for name, widget in widgets.items %}

View File

@ -4,6 +4,5 @@
{% load i18n %}
{% block content %}
<h1>{% get_config 'frontpage_title' %}</h1>
{% get_config 'frontpage_welcometext' %}
<div class="item_fullscreen">{% get_config 'welcome_title' %}</div>
{% endblock %}

View File

@ -1,6 +1,35 @@
{% load i18n %}
{% load tags %}
{% load staticfiles %}
<!-- projector control buttons -->
{% if perms.projector.can_manage_projector %}
<div style="float: right;">
<p>
<a class="projector_edit" href="{% url projector_bigger %}" title="{% trans 'Zoom in' %}">
<img src="{% static 'images/icons/zoom-in.png' %}" />
</a><br>
<a class="projector_edit" href="{% url projector_smaller %}" title="{% trans 'Zoom out' %}">
<img src="{% static 'images/icons/zoom-out.png' %}" />
</a>
</p>
<p>
<a class="projector_edit" href="{% url projector_up %}" title="{% trans 'Scroll text up' %}">
<img src="{% static 'images/icons/go-up.png' %}" />
</a><br>
<a class="projector_edit" href="{% url projector_down %}" title="{% trans 'Scroll text down' %}">
<img src="{% static 'images/icons/go-down.png' %}" />
</a>
</p>
<p>
<a class="projector_edit" href="{% url projector_clean %}" title="{% trans 'Reset projector view' %}">
<img src="{% static 'images/icons/view-reset.png' %}" />
</a>
</p>
</div>
{% endif %}
<!-- projector live view -->
<a href="{% url projector_show %}" target="_blank">
<div id="iframewrapper">
<iframe id="iframe" src="{% url projector_show %}" frameborder="0"></iframe>

View File

@ -0,0 +1,6 @@
{% load i18n %}
{% load tags %}
{% if welcometext %}
<p>{{ welcometext|safe|linebreaks }}</p>
{% endif %}

View File

@ -13,31 +13,28 @@
from datetime import datetime
from time import time
from django.conf import settings
from django.contrib import messages
from django.core.cache import cache
from django.core.context_processors import csrf
from django.core.urlresolvers import reverse
from django.db import transaction
from django.db.models import Q
from django.dispatch import receiver
from django.utils.datastructures import SortedDict
from django.utils.importlib import import_module
from django.shortcuts import redirect
from django.template import RequestContext
from django.utils.translation import ugettext_lazy as _
from openslides.utils.template import render_block_to_string, Tab
from openslides.utils.utils import html_strong
from openslides.utils.views import (TemplateView, RedirectView, CreateView,
UpdateView, DeleteView, AjaxMixin)
from openslides.utils.views import (
TemplateView, RedirectView, CreateView, UpdateView, DeleteView, AjaxMixin)
from openslides.config.models import config
from openslides.projector.api import (get_active_slide, set_active_slide,
projector_message_set, projector_message_delete, get_slide_from_sid,
get_all_widgets)
from openslides.projector.forms import SelectWidgetsForm
from openslides.projector.models import ProjectorOverlay, ProjectorSlide
from openslides.projector.projector import SLIDE, Widget
from openslides.projector.signals import projector_overlays
from .api import (
get_active_slide, set_active_slide, projector_message_set,
projector_message_delete, get_slide_from_sid, get_all_widgets,
clear_projector_cache)
from .forms import SelectWidgetsForm
from .models import ProjectorOverlay, ProjectorSlide
from .projector import Widget
from .signals import projector_overlays
class DashboardView(TemplateView, AjaxMixin):
@ -70,9 +67,10 @@ class Projector(TemplateView, AjaxMixin):
if sid is None:
try:
data = get_active_slide()
except AttributeError: #TODO: It has to be an Slide.DoesNotExist
except AttributeError: # TODO: It has to be an Slide.DoesNotExist
data = None
ajax = 'on'
active_sid = get_active_slide(True)
else:
data = get_slide_from_sid(sid)
ajax = 'off'
@ -88,10 +86,10 @@ class Projector(TemplateView, AjaxMixin):
# Projector Overlays
if self.kwargs['sid'] is None:
active_defs = ProjectorOverlay.objects.filter(active=True) \
.filter(Q(sid=sid) | Q(sid=None)).values_list('def_name',
flat=True)
for receiver, response in projector_overlays.send(sender=sid,
register=False, call=active_defs):
.filter(Q(sid=active_sid) | Q(sid=None)).values_list(
'def_name', flat=True)
for receiver, response in projector_overlays.send(
sender=sid, register=False, call=active_defs):
if response is not None:
data['overlays'].append(response)
self._data = data
@ -106,19 +104,36 @@ class Projector(TemplateView, AjaxMixin):
return context
def get_ajax_context(self, **kwargs):
content = render_block_to_string(self.get_template_names()[0],
'content', self.data)
scrollcontent = render_block_to_string(self.get_template_names()[0],
'scrollcontent', self.data)
content = cache.get('projector_content')
if not content:
content = render_block_to_string(
self.get_template_names()[0],
'content', self.data)
cache.set('projector_content', content, 1)
scrollcontent = cache.get('projector_scrollcontent')
if not scrollcontent:
scrollcontent = render_block_to_string(
self.get_template_names()[0],
'scrollcontent', self.data)
cache.set('projector_scrollcontent', scrollcontent, 1)
# TODO: do not call the hole data-methode, if we only need some vars
data = cache.get('projector_data')
if not data:
data = self.data
cache.set('projector_data', data)
# clear cache if countdown is enabled
if config['countdown_state'] == 'active':
clear_projector_cache()
context = super(Projector, self).get_ajax_context(**kwargs)
content_hash = hash(content)
context.update({
'content': content,
'scrollcontent': scrollcontent,
'time': datetime.now().strftime('%H:%M'),
'overlays': self.data['overlays'],
'title': self.data['title'],
'overlays': data['overlays'],
'title': data['title'],
'bigger': config['bigger'],
'up': config['up'],
'content_hash': content_hash,
@ -186,8 +201,7 @@ class SelectWidgetsView(TemplateView):
else:
transaction.commit()
self.request.session['widgets'] = activated_widgets
return self.render_to_response(context)
return redirect(reverse('dashboard'))
class ProjectorEdit(RedirectView):
@ -259,6 +273,7 @@ class CountdownEdit(RedirectView):
pass
def get_ajax_context(self, **kwargs):
clear_projector_cache()
return {
'state': config['countdown_state'],
'countdown_time': config['countdown_time'],
@ -279,14 +294,13 @@ class OverlayMessageView(RedirectView):
elif 'message-clean' in request.POST:
projector_message_delete()
def get_ajax_context(self, **kwargs):
clear_projector_cache()
return {
'overlay_message': config['projector_message'],
}
class ActivateOverlay(RedirectView):
"""
Activate or deactivate an overlay.
@ -312,6 +326,7 @@ class ActivateOverlay(RedirectView):
self.overlay.save()
def get_ajax_context(self, **kwargs):
clear_projector_cache()
return {
'active': self.overlay.active,
'def_name': self.overlay.def_name,
@ -355,13 +370,12 @@ def register_tab(request):
"""
Register the projector tab.
"""
selected = True if request.path.startswith('/projector/') else False
selected = request.path.startswith('/projector/')
return Tab(
title=_('Dashboard'),
app='dashboard',
url=reverse('dashboard'),
permission=request.user.has_perm('projector.can_manage_projector') or
request.user.has_perm('projector.can_see_dashboard'),
permission=request.user.has_perm('projector.can_see_dashboard'),
selected=selected,
)
@ -372,11 +386,23 @@ def get_widgets(request):
"""
widgets = []
# welcome widget
context = {
'welcometext': config['welcome_text']}
widgets.append(Widget(
name='welcome',
display_name=config['welcome_title'],
template='projector/welcome_widget.html',
context=context,
permission_required='projector.can_see_dashboard',
default_column=1))
# Projector live view widget
widgets.append(Widget(
name='live_view',
display_name=_('Projector live view'),
template='projector/live_view_widget.html',
context=RequestContext(request, {}),
permission_required='projector.can_see_projector',
default_column=2))
@ -389,15 +415,14 @@ def get_widgets(request):
projector_overlay = ProjectorOverlay.objects.get(
def_name=name)
except ProjectorOverlay.DoesNotExist:
projector_overlay = ProjectorOverlay(def_name=name,
active=False)
projector_overlay = ProjectorOverlay(def_name=name, active=False)
projector_overlay.save()
overlays.append(projector_overlay)
context = {
'overlays':overlays,
'countdown_time': config['countdown_time'],
'countdown_state' : config['countdown_state']}
'overlays': overlays,
'countdown_time': config['countdown_time'],
'countdown_state': config['countdown_state']}
context.update(csrf(request))
widgets.append(Widget(
name='overlays',
@ -407,10 +432,9 @@ def get_widgets(request):
default_column=2,
context=context))
# Custom slide widget
context = {
'slides': ProjectorSlide.objects.all(),
'slides': ProjectorSlide.objects.all().order_by('weight'),
'welcomepage_is_active': not bool(config["presentation"])}
widgets.append(Widget(
name='custom_slide',

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

View File

@ -238,4 +238,4 @@ form .required label:after {
.optional {
display: none;
}
}
}

View File

@ -94,7 +94,7 @@
<hr>
<footer>
<small>
&copy; Copyright 2012 | Powered by <a href="http://openslides.org" target="_blank">OpenSlides</a>
&copy; Copyright 2011-2012 | Powered by <a href="http://openslides.org" target="_blank">OpenSlides</a> | {% trans "Get <a href='http://openslides.org/support' target='_blank'>professional support</a> for OpenSlides." %}
</small>
</footer>
</div><!--/content-->

View File

@ -1,18 +0,0 @@
{% extends "base.html" %}
{% load i18n %}
{% load staticfiles %}
{% block title %}{{ block.super }} {% trans "Home" %}{% endblock %}
{% block content %}
<h1>{{ title }}</h1>
<p>{{ welcometext|safe|linebreaks }}</p>
{% trans "You have access to the following pages:" %}
<ul>
{% for app in apps %}
<li><a href="{{ app.url }}">{{ app.title }}</a></li>
{% endfor %}
</ul>
{% endblock %}

View File

@ -12,21 +12,18 @@
from django.conf import settings
from django.conf.urls.defaults import patterns, url, include
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.shortcuts import redirect
from django.utils.importlib import import_module
from openslides.utils.views import FrontPage
from openslides.utils.views import RedirectView
handler500 = 'openslides.utils.views.server_error'
urlpatterns = patterns('',
# frontpage
(r'^$', FrontPage.as_view()),
# Redirect to dashboard URL
url(r'^$', RedirectView.as_view(url='dashboard'), name='home',),
(r'^agenda/', include('openslides.agenda.urls')),
(r'^application/', include('openslides.application.urls')),
(r'^motion/', include('openslides.motion.urls')),
(r'^assignment/', include('openslides.assignment.urls')),
(r'^participant/', include('openslides.participant.urls')),
(r'^config/', include('openslides.config.urls')),
@ -35,7 +32,7 @@ urlpatterns = patterns('',
)
urlpatterns += patterns('django.contrib.staticfiles.views',
url(r'^static/(?P<path>.*)$', 'serve', {'insecure':True}),
url(r'^static/(?P<path>.*)$', 'serve', {'insecure': True}),
)
js_info_dict = {

View File

@ -37,8 +37,8 @@ class AnonymousAuth(object):
- try to return the permissions for the 'Anonymous' group
"""
if not user_obj.is_anonymous() or obj is not None or \
not config['system_enable_anonymous']:
if (not user_obj.is_anonymous() or obj is not None or
not config['system_enable_anonymous']):
return set()
perms = Permission.objects.filter(group__name='Anonymous')
@ -60,8 +60,8 @@ class AnonymousAuth(object):
"""
Check if the user as a specific permission
"""
if not user_obj.is_anonymous() or obj is not None or \
not config['system_enable_anonymous']:
if (not user_obj.is_anonymous() or obj is not None or
not config['system_enable_anonymous']):
return False
return (perm in self.get_all_permissions(user_obj))
@ -70,8 +70,8 @@ class AnonymousAuth(object):
"""
Check if the user has permissions on the module app_label
"""
if not user_obj.is_anonymous() or \
not config['system_enable_anonymous']:
if (not user_obj.is_anonymous() or
not config['system_enable_anonymous']):
return False
for perm in self.get_all_permissions(user_obj):
@ -87,10 +87,10 @@ class AnonymousAuth(object):
"""
return None
def anonymous_context_additions(RequestContext):
"""
Add a variable to the request context that will indicate
if anonymous login is possible at all.
"""
return {'os_enable_anonymous_login' : config['system_enable_anonymous']}
return {'os_enable_anonymous_login': config['system_enable_anonymous']}

View File

@ -25,10 +25,9 @@ class excel_semikolon(Dialect):
def patchup(dialect):
if dialect:
if dialect.delimiter in [excel_semikolon.delimiter, excel.delimiter] and \
dialect.quotechar == excel_semikolon.quotechar:
dialect.quotechar == excel_semikolon.quotechar:
# walks like a duck and talks like a duck.. must be one
dialect.doublequote = True
return dialect
register_dialect("excel_semikolon", excel_semikolon)

View File

@ -1 +1,3 @@
from fields import JSONField
from fields import JSONField
__all__ = ['JSONField']

View File

@ -6,6 +6,7 @@ from django.utils.translation import ugettext_lazy as _
from django.forms.fields import Field
from django.forms.util import ValidationError as FormValidationError
class JSONFormField(Field):
def clean(self, value):
@ -21,6 +22,7 @@ class JSONFormField(Field):
raise FormValidationError(_("Enter valid JSON"))
return value
class JSONField(models.TextField):
"""JSONField is a generic textfield that serializes/unserializes JSON objects"""

View File

@ -1,108 +0,0 @@
from django.db import models
from django.test import TestCase
from django.utils import simplejson as json
from fields import JSONField
class JsonModel(models.Model):
json = JSONField()
class ComplexEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, complex):
return {
'__complex__': True,
'real': obj.real,
'imag': obj.imag,
}
return json.JSONEncoder.default(self, obj)
def as_complex(dct):
if '__complex__' in dct:
return complex(dct['real'], dct['imag'])
return dct
class JSONModelCustomEncoders(models.Model):
# A JSON field that can store complex numbers
json = JSONField(
dump_kwargs={'cls': ComplexEncoder},
load_kwargs={'object_hook': as_complex},
)
class JSONFieldTest(TestCase):
"""JSONField Wrapper Tests"""
def test_json_field_create(self):
"""Test saving a JSON object in our JSONField"""
json_obj = {
"item_1": "this is a json blah",
"blergh": "hey, hey, hey"}
obj = JsonModel.objects.create(json=json_obj)
new_obj = JsonModel.objects.get(id=obj.id)
self.failUnlessEqual(new_obj.json, json_obj)
def test_json_field_modify(self):
"""Test modifying a JSON object in our JSONField"""
json_obj_1 = {'a': 1, 'b': 2}
json_obj_2 = {'a': 3, 'b': 4}
obj = JsonModel.objects.create(json=json_obj_1)
self.failUnlessEqual(obj.json, json_obj_1)
obj.json = json_obj_2
self.failUnlessEqual(obj.json, json_obj_2)
obj.save()
self.failUnlessEqual(obj.json, json_obj_2)
self.assert_(obj)
def test_json_field_load(self):
"""Test loading a JSON object from the DB"""
json_obj_1 = {'a': 1, 'b': 2}
obj = JsonModel.objects.create(json=json_obj_1)
new_obj = JsonModel.objects.get(id=obj.id)
self.failUnlessEqual(new_obj.json, json_obj_1)
def test_json_list(self):
"""Test storing a JSON list"""
json_obj = ["my", "list", "of", 1, "objs", {"hello": "there"}]
obj = JsonModel.objects.create(json=json_obj)
new_obj = JsonModel.objects.get(id=obj.id)
self.failUnlessEqual(new_obj.json, json_obj)
def test_empty_objects(self):
"""Test storing empty objects"""
for json_obj in [{}, [], 0, '', False]:
obj = JsonModel.objects.create(json=json_obj)
new_obj = JsonModel.objects.get(id=obj.id)
self.failUnlessEqual(json_obj, obj.json)
self.failUnlessEqual(json_obj, new_obj.json)
def test_custom_encoder(self):
"""Test encoder_cls and object_hook"""
value = 1 + 3j # A complex number
obj = JSONModelCustomEncoders.objects.create(json=value)
new_obj = JSONModelCustomEncoders.objects.get(pk=obj.pk)
self.failUnlessEqual(value, new_obj.json)

View File

@ -12,12 +12,13 @@
from django.db import models
class MinMaxIntegerField(models.IntegerField):
def __init__(self, min_value=None, max_value=None, *args, **kwargs):
self.min_value, self.max_value = min_value, max_value
super(MinMaxIntegerField, self).__init__(*args, **kwargs)
def formfield(self, **kwargs):
defaults = {'min_value': self.min_value, 'max_value' : self.max_value}
defaults = {'min_value': self.min_value, 'max_value': self.max_value}
defaults.update(kwargs)
return super(MinMaxIntegerField, self).formfield(**defaults)

View File

@ -20,6 +20,7 @@ from reportlab.pdfbase.ttfonts import TTFont
from reportlab.rl_config import defaultPageSize
from django.conf import settings
from django.utils import formats
# Import gettext for python 2.5 support
from django.utils.translation import ugettext as _, gettext
@ -27,16 +28,16 @@ from openslides.config.models import config
# register new truetype fonts
pdfmetrics.registerFont(TTFont('Ubuntu', path_join(settings.SITE_ROOT,
'static/fonts/Ubuntu-R.ttf')))
pdfmetrics.registerFont(TTFont('Ubuntu-Bold', path_join(settings.SITE_ROOT,
'static/fonts/Ubuntu-B.ttf')))
pdfmetrics.registerFont(TTFont('Ubuntu-Italic', path_join(settings.SITE_ROOT,
'static/fonts/Ubuntu-RI.ttf')))
pdfmetrics.registerFont(TTFont(
'Ubuntu', path_join(settings.SITE_ROOT, 'static/fonts/Ubuntu-R.ttf')))
pdfmetrics.registerFont(TTFont(
'Ubuntu-Bold', path_join(settings.SITE_ROOT, 'static/fonts/Ubuntu-B.ttf')))
pdfmetrics.registerFont(TTFont(
'Ubuntu-Italic', path_join(settings.SITE_ROOT, 'static/fonts/Ubuntu-RI.ttf')))
# set style information
PAGE_HEIGHT = defaultPageSize[1];
PAGE_HEIGHT = defaultPageSize[1]
PAGE_WIDTH = defaultPageSize[0]
@ -104,104 +105,104 @@ stylesheet.add(ParagraphStyle(
leftIndent=0,
spaceAfter=15,
))
stylesheet.add(ParagraphStyle(name = 'Subitem',
parent = stylesheet['Normal'],
fontSize = 10,
leading = 10,
leftIndent = 20,
spaceAfter = 15)
)
stylesheet.add(ParagraphStyle(name = 'Tablecell',
parent = stylesheet['Normal'],
fontSize = 9)
)
stylesheet.add(ParagraphStyle(name = 'Signaturefield',
parent = stylesheet['Normal'],
spaceBefore = 15)
stylesheet.add(ParagraphStyle(
name='Subitem',
parent=stylesheet['Normal'],
fontSize=10,
leading=10,
leftIndent=20,
spaceAfter=15))
stylesheet.add(ParagraphStyle(
name='Tablecell',
parent=stylesheet['Normal'],
fontSize=9))
stylesheet.add(ParagraphStyle(name='Signaturefield',
parent=stylesheet['Normal'],
spaceBefore=15)
)
# Ballot stylesheets
stylesheet.add(ParagraphStyle(name = 'Ballot_title',
parent = stylesheet['Bold'],
fontSize = 12,
leading = 14,
leftIndent = 30),
stylesheet.add(ParagraphStyle(name='Ballot_title',
parent=stylesheet['Bold'],
fontSize=12,
leading=14,
leftIndent=30),
)
stylesheet.add(ParagraphStyle(name = 'Ballot_subtitle',
parent = stylesheet['Normal'],
fontSize = 10,
leading = 12,
leftIndent = 30,
rightIndent = 20,
spaceAfter = 5),
stylesheet.add(ParagraphStyle(name='Ballot_subtitle',
parent=stylesheet['Normal'],
fontSize=10,
leading=12,
leftIndent=30,
rightIndent=20,
spaceAfter=5),
)
stylesheet.add(ParagraphStyle(name = 'Ballot_description',
parent = stylesheet['Normal'],
fontSize = 7,
leading = 10,
leftIndent = 30),
stylesheet.add(ParagraphStyle(name='Ballot_description',
parent=stylesheet['Normal'],
fontSize=7,
leading=10,
leftIndent=30),
)
stylesheet.add(ParagraphStyle(name = 'Ballot_option',
parent = stylesheet['Normal'],
fontSize = 12,
leading = 24,
leftIndent = 30),
stylesheet.add(ParagraphStyle(name='Ballot_option',
parent=stylesheet['Normal'],
fontSize=12,
leading=24,
leftIndent=30),
)
stylesheet.add(ParagraphStyle(name = 'Monotype',
parent = stylesheet['Normal'],
fontName = 'Courier',
fontSize = 12,
leading = 24,
leftIndent = 30),
stylesheet.add(ParagraphStyle(name='Monotype',
parent=stylesheet['Normal'],
fontName='Courier',
fontSize=12,
leading=24,
leftIndent=30),
)
stylesheet.add(ParagraphStyle(name = 'Ballot_option_name',
parent = stylesheet['Normal'],
fontSize = 12,
leading = 15,
leftIndent = 30),
stylesheet.add(ParagraphStyle(name='Ballot_option_name',
parent=stylesheet['Normal'],
fontSize=12,
leading=15,
leftIndent=30),
)
stylesheet.add(ParagraphStyle(name = 'Ballot_option_group',
parent = stylesheet['Normal'],
fontSize = 8,
leading = 15,
leftIndent = 30),
stylesheet.add(ParagraphStyle(name='Ballot_option_group',
parent=stylesheet['Normal'],
fontSize=8,
leading=15,
leftIndent=30),
)
stylesheet.add(ParagraphStyle(name = 'Ballot_option_YNA',
parent = stylesheet['Normal'],
fontSize = 12,
leading = 15,
leftIndent = 49,
spaceAfter = 18),
stylesheet.add(ParagraphStyle(name='Ballot_option_YNA',
parent=stylesheet['Normal'],
fontSize=12,
leading=15,
leftIndent=49,
spaceAfter=18),
)
stylesheet.add(ParagraphStyle(name = 'Ballot_option_group_right',
parent = stylesheet['Normal'],
fontSize = 8,
leading = 16,
leftIndent = 49),
stylesheet.add(ParagraphStyle(name='Ballot_option_group_right',
parent=stylesheet['Normal'],
fontSize=8,
leading=16,
leftIndent=49),
)
stylesheet.add(ParagraphStyle(name = 'Badge_title',
parent = stylesheet['Bold'],
fontSize = 16,
leading = 22,
leftIndent = 30),
stylesheet.add(ParagraphStyle(name='Badge_title',
parent=stylesheet['Bold'],
fontSize=16,
leading=22,
leftIndent=30),
)
stylesheet.add(ParagraphStyle(name = 'Badge_subtitle',
parent = stylesheet['Normal'],
fontSize = 12,
leading = 24,
leftIndent = 30),
stylesheet.add(ParagraphStyle(name='Badge_subtitle',
parent=stylesheet['Normal'],
fontSize=12,
leading=24,
leftIndent=30),
)
stylesheet.add(ParagraphStyle(
name = 'Badge_italic',
parent = stylesheet['Italic'],
fontSize = 12,
leading = 24,
leftIndent = 30,
name='Badge_italic',
parent=stylesheet['Italic'],
fontSize=12,
leading=24,
leftIndent=30,
))
stylesheet.add(ParagraphStyle(
name = 'Badge_qrcode',
fontSize = 12,
leftIndent = 190,
name='Badge_qrcode',
fontSize=12,
leftIndent=190,
))
@ -212,18 +213,18 @@ def firstPage(canvas, doc):
canvas.setFillGray(0.4)
title_line = u"%s | %s" % (config["event_name"],
config["event_description"])
config["event_description"])
if len(title_line) > 75:
title_line = "%s ..." % title_line[:70]
canvas.drawString(2.75 * cm, 28 * cm, title_line)
if config["event_date"] and config["event_location"]:
canvas.drawString(2.75 * cm, 27.6 * cm, u"%s, %s"
% (config["event_date"], config["event_location"]))
% (config["event_date"], config["event_location"]))
# time
canvas.setFont('Ubuntu', 7)
time = datetime.now().strftime(gettext("%Y-%m-%d %H:%Mh"))
canvas.drawString(15 * cm, 28 * cm, _("Printed: %s") % time)
time = formats.date_format(datetime.now(), 'DATETIME_FORMAT')
canvas.drawString(15 * cm, 28 * cm, _("As of: %s") % time)
# title
if doc.title:

Some files were not shown because too many files have changed in this diff Show More