Merge branch 'master' into Version_String

Conflicts:
	openslides/__init__.py
	openslides/config/views.py
This commit is contained in:
Norman Jäckel 2012-11-28 17:54:29 +01:00
commit 589c209c79
69 changed files with 1851 additions and 1714 deletions

4
.coveragerc Normal file
View File

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

6
.gitignore vendored
View File

@ -12,3 +12,9 @@ docs/_build/*
build/* build/*
dist/* dist/*
.DS_Store .DS_Store
settings.py
versiontools*
# Unit test / coverage reports
.coverage
htmlcov

12
.travis.yml Normal file
View File

@ -0,0 +1,12 @@
language: python
python:
- "2.5"
- "2.6"
- "2.7"
install:
- pip install -r requirements.txt --use-mirrors
- pip install coverage django-discover-runner pep8
- 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> Norman Jäckel <mail@normanjaeckel.de>
René Köcher <shirk@bitspin.org> René Köcher <shirk@bitspin.org>
Andy Kittner <andkit@gmx.net> Andy Kittner <andkit@gmx.net>
Moira Brülisauer <moira.bruelisauer@piratenpartei.ch> (French translation)
Alexis Roussel <alexis.roussel@partipirate.ch> (French translation)

View File

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

View File

@ -3,15 +3,56 @@ Installation Instructions for OpenSlides 1.3
Content Content
------- -------
I. Installation on Windows (32/64bit) I. Installation on GNU/Linux and MacOSX
II. Installation on GNU/Linux and MacOSX II. Installation on Windows (32/64bit)
If you need help ask on OpenSlides users mailing list. If you need help ask on OpenSlides users mailing list.
See http://openslides.org for more information. See http://openslides.org for more information.
I. Installation on Windows (32/64bit) I. Installation on GNU/Linux and MacOSX
------------------------------------- ----------------------------------------
Make sure that you have installed Python (>= 2.5) on your system.
You can setup a virtualenv environment to install OpenSlides as non-root user.
Run before you start install OpenSlides:
$ virtualenv .venv
$ source .venv/bin/activate
1. Install OpenSlides:
$ pip install openslides
OpenSlides will installed the following required python packages:
+ Django
+ django-mptt
+ ReportLab Toolkit
+ Python Imaging Library (PIL)
2. 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 Windows (32/64bit)
--------------------------------------
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: 1. Install requirements:
@ -76,73 +117,3 @@ I. Installation on Windows (32/64bit)
Use 'python start.py --help' to show all start options. 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 OpenSlides' github repository
https://github.com/OpenSlides/OpenSlides.
This requires Git, see http://git-scm.com/.
Open command line (cmd) and run:
git clone git://github.com/OpenSlides/OpenSlides.git
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.).

View File

@ -1,6 +1,5 @@
include AUTHORS include AUTHORS
include CHANGELOG include CHANGELOG
include initial_data.json
include INSTALL.txt include INSTALL.txt
include LICENSE include LICENSE
include manage.py include manage.py
@ -12,16 +11,14 @@ recursive-include openslides/templates *
recursive-include openslides/agenda/templates * recursive-include openslides/agenda/templates *
recursive-include openslides/agenda/static * recursive-include openslides/agenda/static *
recursive-include openslides/application/templates * recursive-include openslides/motion/templates *
recursive-include openslides/application/static *
recursive-include openslides/assignment/templates * recursive-include openslides/assignment/templates *
recursive-include openslides/assignment/static * recursive-include openslides/assignment/static *
recursive-include openslides/config/templates * recursive-include openslides/config/templates *
recursive-include openslides/config/static *
recursive-include openslides/participant/templates * recursive-include openslides/participant/templates *
recursive-include openslides/participant/static * recursive-include openslides/participant/static *
include openslides/participant/fixtures/groups_de.json
recursive-include openslides/poll/templates * recursive-include openslides/poll/templates *
recursive-include openslides/poll/static *
recursive-include openslides/projector/templates * recursive-include openslides/projector/templates *
recursive-include openslides/projector/static * recursive-include openslides/projector/static *

View File

@ -1,8 +1,8 @@
================================== ==================================
English README file for OpenSlides English README file for OpenSlides
================================== ==================================
This is OpenSlides, version 1.3-beta2 (2012-11-09). This is OpenSlides, version 1.3-rc1 (2012-11-27).
What is OpenSlides? What is OpenSlides?
@ -10,7 +10,7 @@ What is OpenSlides?
OpenSlides is a free, web-based presentation system for displaying and OpenSlides is a free, web-based presentation system for displaying and
controlling of agenda, applications and elections of an assembly. controlling of agenda, applications and elections of an assembly.
See http://www.openslides.org for more information. See http://openslides.org for more information.
Getting started Getting started
@ -18,14 +18,15 @@ Getting started
Install and start OpenSlides as described in the INSTALL.txt. Install and start OpenSlides as described in the INSTALL.txt.
If you need help please contact the OpenSlides team on public mailing If you need help please contact the OpenSlides team on public mailing
list or read the OpenSlides manual. See http://www.openslides.org. list or read the OpenSlides manual. See http://openslides.org.
The start script of OpenSlides The start script of OpenSlides
============================== ==============================
Simply running Simply running
openslides.exe (on Windows) or openslides.exe (on Windows) or
python start.py (on Linux/MacOS) python start.py (on Linux/MacOS)
will start OpenSlides using djangos development server. It will also will start OpenSlides using djangos development server. It will also
try to open OpenSlides in your default webbrowser. try to open OpenSlides in your default webbrowser.

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

@ -87,6 +87,7 @@ PY_DLLS = [
"_sqlite3.pyd", "_sqlite3.pyd",
"_socket.pyd", "_socket.pyd",
"select.pyd", "select.pyd",
"_ctypes.pyd",
] ]
MSVCR_PUBLIC_KEY = "1fc8b3b9a1e18e3b" MSVCR_PUBLIC_KEY = "1fc8b3b9a1e18e3b"
@ -306,8 +307,8 @@ def main():
shutil.copyfile("extras/win32-portable/openslides.exe", shutil.copyfile("extras/win32-portable/openslides.exe",
os.path.join(odir, "openslides.exe")) os.path.join(odir, "openslides.exe"))
shutil.copyfile("initial_data.json", shutil.copyfile("openslides/participant/fixtures/groups_de.json",
os.path.join(odir, "initial_data.json")) os.path.join(odir, "groups_de.json"))
copy_dlls(odir) copy_dlls(odir)
copy_msvcr(odir) copy_msvcr(odir)

View File

@ -24,8 +24,8 @@ class ItemForm(forms.ModelForm, CssClassMixin):
""" """
Form to create of update an item. Form to create of update an item.
""" """
parent = TreeNodeChoiceField(queryset=Item.objects.all(), parent = TreeNodeChoiceField(
label=_("Parent item"), required=False) queryset=Item.objects.all(), label=_("Parent item"), required=False)
class Meta: class Meta:
model = Item model = Item

View File

@ -10,12 +10,6 @@
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
try:
import json
except ImportError:
# for python 2.5 support
import simplejson as json
from django.db import models from django.db import models
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _, ugettext_noop, ugettext from django.utils.translation import ugettext_lazy as _, ugettext_noop, ugettext
@ -23,11 +17,9 @@ from django.utils.translation import ugettext_lazy as _, ugettext_noop, ugettext
from mptt.models import MPTTModel, TreeForeignKey from mptt.models import MPTTModel, TreeForeignKey
from openslides.config.models import config from openslides.config.models import config
from openslides.projector.projector import SlideMixin from openslides.projector.projector import SlideMixin
from openslides.projector.api import (register_slidemodel, get_slide_from_sid, from openslides.projector.api import (
register_slidefunc, split_sid) register_slidemodel, get_slide_from_sid, register_slidefunc)
from openslides.agenda.slides import agenda_show from openslides.agenda.slides import agenda_show
@ -45,7 +37,7 @@ class Item(MPTTModel, SlideMixin):
closed = models.BooleanField(default=False, verbose_name=_("Closed")) closed = models.BooleanField(default=False, verbose_name=_("Closed"))
weight = models.IntegerField(default=0, verbose_name=_("Weight")) weight = models.IntegerField(default=0, verbose_name=_("Weight"))
parent = TreeForeignKey('self', null=True, blank=True, parent = TreeForeignKey('self', null=True, blank=True,
related_name='children') related_name='children')
related_sid = models.CharField(null=True, blank=True, max_length=63) related_sid = models.CharField(null=True, blank=True, max_length=63)
def get_related_slide(self): def get_related_slide(self):
@ -84,7 +76,6 @@ class Item(MPTTModel, SlideMixin):
return self.title return self.title
return self.get_related_slide().get_agenda_title() return self.get_related_slide().get_agenda_title()
def get_title_supplement(self): def get_title_supplement(self):
""" """
return a supplement for the title. return a supplement for the title.

View File

@ -11,7 +11,6 @@
""" """
from reportlab.platypus import Paragraph from reportlab.platypus import Paragraph
from django.core.context_processors import csrf
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib import messages from django.contrib import messages
from django.db import transaction from django.db import transaction
@ -20,18 +19,15 @@ from django.utils.translation import ugettext as _, ugettext_lazy
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from openslides.utils.pdf import stylesheet from openslides.utils.pdf import stylesheet
from openslides.utils.views import (TemplateView, RedirectView, UpdateView, from openslides.utils.views import (
CreateView, DeleteView, PDFView, DetailView) TemplateView, RedirectView, UpdateView, CreateView, DeleteView, PDFView,
DetailView)
from openslides.utils.template import Tab from openslides.utils.template import Tab
from openslides.utils.utils import html_strong from openslides.utils.utils import html_strong
from openslides.config.models import config
from openslides.projector.api import get_active_slide from openslides.projector.api import get_active_slide
from openslides.projector.projector import Widget, SLIDE from openslides.projector.projector import Widget, SLIDE
from .models import Item
from openslides.agenda.models import Item from .forms import ItemOrderForm, ItemForm
from openslides.agenda.forms import ItemOrderForm, ItemForm
class Overview(TemplateView): class Overview(TemplateView):
@ -53,7 +49,8 @@ class Overview(TemplateView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs) context = self.get_context_data(**kwargs)
if not request.user.has_perm('agenda.can_manage_agenda'): if not request.user.has_perm('agenda.can_manage_agenda'):
messages.error(request, messages.error(
request,
_('You are not authorized to manage the agenda.')) _('You are not authorized to manage the agenda.'))
return self.render_to_response(context) return self.render_to_response(context)
transaction.commit() transaction.commit()
@ -69,8 +66,8 @@ class Overview(TemplateView):
Model.save(item) Model.save(item)
else: else:
transaction.rollback() transaction.rollback()
messages.error(request, messages.error(
_('Errors when reordering of the agenda')) request, _('Errors when reordering of the agenda'))
return self.render_to_response(context) return self.render_to_response(context)
Item.objects.rebuild() Item.objects.rebuild()
# TODO: assure, that it is a valid tree # TODO: assure, that it is a valid tree
@ -130,8 +127,8 @@ class ItemUpdate(UpdateView):
apply_url = 'item_edit' apply_url = 'item_edit'
def get_success_url(self): def get_success_url(self):
messages.success(self.request, messages.success(
_("Item %s was successfully modified.") \ self.request, _("Item %s was successfully modified.")
% html_strong(self.request.POST['title'])) % html_strong(self.request.POST['title']))
if 'apply' in self.request.POST: if 'apply' in self.request.POST:
return '' return ''
@ -151,8 +148,8 @@ class ItemCreate(CreateView):
apply_url = 'item_edit' apply_url = 'item_edit'
def get_success_url(self): def get_success_url(self):
messages.success(self.request, messages.success(
_("Item %s was successfully created.") \ self.request, _("Item %s was successfully created.")
% html_strong(self.request.POST['title'])) % html_strong(self.request.POST['title']))
if 'apply' in self.request.POST: if 'apply' in self.request.POST:
return reverse(self.get_apply_url(), args=[self.object.id]) return reverse(self.get_apply_url(), args=[self.object.id])
@ -176,13 +173,13 @@ class ItemDelete(DeleteView):
def pre_post_redirect(self, request, *args, **kwargs): def pre_post_redirect(self, request, *args, **kwargs):
if self.get_answer() == 'all': if self.get_answer() == 'all':
self.object.delete(with_children=True) self.object.delete(with_children=True)
messages.success(request, messages.success(
_("Item %s and his children were successfully deleted.") request, _("Item %s and his children were successfully deleted.")
% html_strong(self.object)) % html_strong(self.object))
elif self.get_answer() == 'yes': elif self.get_answer() == 'yes':
self.object.delete(with_children=False) self.object.delete(with_children=False)
messages.success(request, messages.success(
_("Item %s was successfully deleted.") request, _("Item %s was successfully deleted.")
% html_strong(self.object)) % html_strong(self.object))
@ -199,7 +196,8 @@ class AgendaPDF(PDFView):
ancestors = item.get_ancestors() ancestors = item.get_ancestors()
if ancestors: if ancestors:
space = "&nbsp;" * 6 * ancestors.count() 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'])) stylesheet['Subitem']))
else: else:
story.append(Paragraph(item.get_title(), stylesheet['Item'])) story.append(Paragraph(item.get_title(), stylesheet['Item']))
@ -213,10 +211,9 @@ def register_tab(request):
return Tab( return Tab(
title=_('Agenda'), title=_('Agenda'),
url=reverse('item_overview'), url=reverse('item_overview'),
permission=request.user.has_perm('agenda.can_see_agenda') permission=(request.user.has_perm('agenda.can_see_agenda') or
or request.user.has_perm('agenda.can_manage_agenda'), request.user.has_perm('agenda.can_manage_agenda')),
selected=selected, selected=selected)
)
def get_widgets(request): def get_widgets(request):

View File

@ -11,7 +11,7 @@
""" """
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _, ugettext_noop from django.utils.translation import ugettext_lazy as _
from openslides.utils.forms import CssClassMixin from openslides.utils.forms import CssClassMixin
from openslides.utils.person import PersonFormField from openslides.utils.person import PersonFormField
@ -20,8 +20,8 @@ from openslides.assignment.models import Assignment
class AssignmentForm(forms.ModelForm, CssClassMixin): class AssignmentForm(forms.ModelForm, CssClassMixin):
posts = forms.IntegerField(min_value=1, initial=1, posts = forms.IntegerField(
label=_("Number of available posts")) min_value=1, initial=1, label=_("Number of available posts"))
class Meta: class Meta:
model = Assignment model = Assignment
@ -39,8 +39,7 @@ class ConfigForm(forms.Form, CssClassMixin):
assignment_publish_winner_results_only = forms.BooleanField( assignment_publish_winner_results_only = forms.BooleanField(
required=False, required=False,
label=_("Only publish voting results for selected winners " label=_("Only publish voting results for selected winners "
"(Projector view only)") "(Projector view only)"))
)
assignment_pdf_ballot_papers_selection = forms.ChoiceField( assignment_pdf_ballot_papers_selection = forms.ChoiceField(
widget=forms.Select(), widget=forms.Select(),
required=False, required=False,
@ -48,31 +47,25 @@ class ConfigForm(forms.Form, CssClassMixin):
choices=( choices=(
("NUMBER_OF_DELEGATES", _("Number of all delegates")), ("NUMBER_OF_DELEGATES", _("Number of all delegates")),
("NUMBER_OF_ALL_PARTICIPANTS", _("Number of all participants")), ("NUMBER_OF_ALL_PARTICIPANTS", _("Number of all participants")),
("CUSTOM_NUMBER", _("Use the following custom number")) ("CUSTOM_NUMBER", _("Use the following custom number"))))
)
)
assignment_pdf_ballot_papers_number = forms.IntegerField( assignment_pdf_ballot_papers_number = forms.IntegerField(
widget=forms.TextInput(attrs={'class':'small-input'}), widget=forms.TextInput(attrs={'class': 'small-input'}),
required=False, required=False,
min_value=1, min_value=1,
label=_("Custom number of ballot papers") label=_("Custom number of ballot papers"))
)
assignment_pdf_title = forms.CharField( assignment_pdf_title = forms.CharField(
widget=forms.TextInput(), widget=forms.TextInput(),
required=False, required=False,
label=_("Title for PDF document (all elections)") label=_("Title for PDF document (all elections)"))
)
assignment_pdf_preamble = forms.CharField( assignment_pdf_preamble = forms.CharField(
widget=forms.Textarea(), widget=forms.Textarea(),
required=False, required=False,
label=_("Preamble text for PDF document (all elections)") label=_("Preamble text for PDF document (all elections)"))
) assignment_poll_vote_values = forms.ChoiceField(
assignment_poll_vote_values = forms.ChoiceField(widget=forms.Select(), widget=forms.Select(),
required=False, required=False,
label=_("Election method"), label=_("Election method"),
choices=( choices=(
("auto", _("Automatic assign of method.")), ("auto", _("Automatic assign of method.")),
("votes", _("Always one option per candidate.")), ("votes", _("Always one option per candidate.")),
("yesnoabstain", _("Always Yes-No-Abstain per candidate.")), ("yesnoabstain", _("Always Yes-No-Abstain per candidate."))))
)
)

View File

@ -16,16 +16,12 @@ from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _, ugettext_noop from django.utils.translation import ugettext_lazy as _, ugettext_noop
from openslides.utils.person import PersonField from openslides.utils.person import PersonField
from openslides.config.models import config from openslides.config.models import config
from openslides.config.signals import default_config_value from openslides.config.signals import default_config_value
from openslides.projector.api import register_slidemodel from openslides.projector.api import register_slidemodel
from openslides.projector.projector import SlideMixin from openslides.projector.projector import SlideMixin
from openslides.poll.models import (
from openslides.poll.models import (BasePoll, CountInvalid, CountVotesCast, BasePoll, CountInvalid, CountVotesCast, BaseOption, PublishPollMixin, BaseVote)
BaseOption, PublishPollMixin, BaseVote)
from openslides.agenda.models import Item from openslides.agenda.models import Item
@ -51,11 +47,10 @@ class Assignment(models.Model, SlideMixin):
) )
name = models.CharField(max_length=100, verbose_name=_("Name")) name = models.CharField(max_length=100, verbose_name=_("Name"))
description = models.TextField(null=True, blank=True, description = models.TextField(null=True, blank=True, verbose_name=_("Description"))
verbose_name=_("Description")) posts = models.PositiveSmallIntegerField(verbose_name=_("Number of available posts"))
posts = models.PositiveSmallIntegerField( polldescription = models.CharField(
verbose_name=_("Number of available posts")) max_length=100, null=True, blank=True,
polldescription = models.CharField(max_length=100, null=True, blank=True,
verbose_name=_("Comment on the ballot paper")) verbose_name=_("Comment on the ballot paper"))
status = models.CharField(max_length=3, choices=STATUS, default='sea') status = models.CharField(max_length=3, choices=STATUS, default='sea')
@ -68,8 +63,8 @@ class Assignment(models.Model, SlideMixin):
if error: if error:
raise NameError(_('%s is not a valid status.') % status) raise NameError(_('%s is not a valid status.') % status)
if self.status == status: if self.status == status:
raise NameError(_('The assignment status is already %s.') raise NameError(
% self.status) _('The assignment status is already %s.') % self.status)
self.status = status self.status = status
self.save() self.save()
@ -85,16 +80,16 @@ class Assignment(models.Model, SlideMixin):
raise NameError(_('<b>%s</b> is already a candidate.') % candidate) raise NameError(_('<b>%s</b> is already a candidate.') % candidate)
if not person.has_perm("assignment.can_manage_assignment") and self.status != 'sea': if not person.has_perm("assignment.can_manage_assignment") and self.status != 'sea':
raise NameError(_('The candidate list is already closed.')) raise NameError(_('The candidate list is already closed.'))
candidation = self.assignment_candidates.filter(person=candidate) candidature = self.assignment_candidates.filter(person=candidate)
if candidation and candidate != person and \ if candidature and candidate != person and \
not person.has_perm("assignment.can_manage_assignment"): not person.has_perm("assignment.can_manage_assignment"):
# if the candidation is blocked and anotherone tries to run the # if the candidature is blocked and anotherone tries to run the
# candidate # candidate
raise NameError( raise NameError(
_('%s does not want to be a candidate.') % candidate) _('%s does not want to be a candidate.') % candidate)
elif candidation: elif candidature:
candidation[0].blocked = False candidature[0].blocked = False
candidation[0].save() candidature[0].save()
else: else:
AssignmentCandidate(assignment=self, person=candidate).save() AssignmentCandidate(assignment=self, person=candidate).save()
@ -103,36 +98,33 @@ class Assignment(models.Model, SlideMixin):
stop running for a vote stop running for a vote
""" """
try: try:
candidation = self.assignment_candidates.get(person=candidate) candidature = self.assignment_candidates.get(person=candidate)
except AssignmentCandidate.DoesNotExist: except AssignmentCandidate.DoesNotExist:
raise Exception(_('%s is no candidate') % candidate) raise Exception(_('%s is no candidate') % candidate)
if not candidation.blocked: if not candidature.blocked:
if blocked: if blocked:
candidation.blocked = True candidature.blocked = True
candidation.save() candidature.save()
else: else:
candidation.delete() candidature.delete()
else: else:
candidation.delete() candidature.delete()
def is_candidate(self, person): def is_candidate(self, person):
""" """
return True, if person is a candidate. return True, if person is a candidate.
""" """
try: try:
return self.assignment_candidates.filter(person=person) \ return self.assignment_candidates.filter(person=person).exclude(blocked=True).exists()
.exclude(blocked=True).exists()
except AttributeError: except AttributeError:
return False return False
def is_blocked(self, person): def is_blocked(self, person):
""" """
return True, if the person is blockt for candidation. return True, if the person is blockt for candidature.
""" """
return self.assignment_candidates.filter(person=person) \ return self.assignment_candidates.filter(person=person).filter(blocked=True).exists()
.filter(blocked=True).exists()
@property @property
def assignment_candidates(self): def assignment_candidates(self):
@ -164,7 +156,6 @@ class Assignment(models.Model, SlideMixin):
return participants return participants
#return candidates.values_list('person', flat=True) #return candidates.values_list('person', flat=True)
def set_elected(self, person, value=True): def set_elected(self, person, value=True):
candidate = self.assignment_candidates.get(person=person) candidate = self.assignment_candidates.get(person=person)
candidate.elected = value candidate.elected = value
@ -212,7 +203,6 @@ class Assignment(models.Model, SlideMixin):
vote_results_dict[candidate].append(votes) vote_results_dict[candidate].append(votes)
return vote_results_dict return vote_results_dict
def get_agenda_title(self): def get_agenda_title(self):
return self.name return self.name
@ -298,8 +288,7 @@ class AssignmentPoll(BasePoll, CountInvalid, CountVotesCast, PublishPollMixin):
self.yesnoabstain = False self.yesnoabstain = False
self.save() self.save()
if self.yesnoabstain: if self.yesnoabstain:
return [ugettext_noop('Yes'), ugettext_noop('No'), return [ugettext_noop('Yes'), ugettext_noop('No'), ugettext_noop('Abstain')]
ugettext_noop('Abstain')]
else: else:
return [ugettext_noop('Votes')] return [ugettext_noop('Votes')]

View File

@ -13,39 +13,31 @@
import os import os
from reportlab.lib import colors from reportlab.lib import colors
from reportlab.platypus import (SimpleDocTemplate, PageBreak, Paragraph, from reportlab.platypus import (
Spacer, Table, TableStyle) SimpleDocTemplate, PageBreak, Paragraph, Spacer, Table, TableStyle)
from reportlab.lib.units import cm from reportlab.lib.units import cm
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils.translation import ungettext, ugettext as _ from django.utils.translation import ungettext, ugettext as _
from openslides.utils.pdf import stylesheet from openslides.utils.pdf import stylesheet
from openslides.utils.template import Tab from openslides.utils.template import Tab
from openslides.utils.utils import (template, permission_required, from openslides.utils.utils import (
gen_confirm_form, del_confirm_form, ajax_request) template, permission_required, gen_confirm_form, del_confirm_form, ajax_request)
from openslides.utils.views import FormView, DeleteView, PDFView, RedirectView from openslides.utils.views import FormView, DeleteView, PDFView, RedirectView
from openslides.utils.person import get_person from openslides.utils.person import get_person
from openslides.config.models import config from openslides.config.models import config
from openslides.participant.models import User from openslides.participant.models import User
from openslides.projector.projector import Widget from openslides.projector.projector import Widget
from openslides.poll.views import PollFormView from openslides.poll.views import PollFormView
from openslides.agenda.models import Item from openslides.agenda.models import Item
from openslides.assignment.models import Assignment, AssignmentPoll
from openslides.assignment.models import (Assignment, AssignmentPoll, from openslides.assignment.forms import (
AssignmentOption) AssignmentForm, AssignmentRunForm, ConfigForm)
from openslides.assignment.forms import (AssignmentForm, AssignmentRunForm,
ConfigForm)
@permission_required('assignment.can_see_assignment') @permission_required('assignment.can_see_assignment')
@ -56,7 +48,7 @@ def get_overview(request):
query = query.filter(status__iexact=request.GET['status']) query = query.filter(status__iexact=request.GET['status'])
try: try:
sort = request.GET['sort'] sort = request.GET['sort']
if sort in ['name','status']: if sort in ['name', 'status']:
query = query.order_by(sort) query = query.order_by(sort)
except KeyError: except KeyError:
pass pass
@ -91,7 +83,6 @@ def view(request, assignment_id=None):
if request.user.has_perm('assignment.can_nominate_other'): if request.user.has_perm('assignment.can_nominate_other'):
form = AssignmentRunForm() form = AssignmentRunForm()
polls = assignment.poll_set.all() polls = assignment.poll_set.all()
if not request.user.has_perm('assignment.can_manage_assignment'): if not request.user.has_perm('assignment.can_manage_assignment'):
polls = assignment.poll_set.filter(published=True) polls = assignment.poll_set.filter(published=True)
@ -100,7 +91,8 @@ def view(request, assignment_id=None):
polls = assignment.poll_set.all() polls = assignment.poll_set.all()
vote_results = assignment.vote_results(only_published=False) vote_results = assignment.vote_results(only_published=False)
blocked_candidates = [candidate.person for candidate in \ blocked_candidates = [
candidate.person for candidate in
assignment.assignment_candidates.filter(blocked=True)] assignment.assignment_candidates.filter(blocked=True)]
return { return {
'assignment': assignment, 'assignment': assignment,
@ -180,7 +172,7 @@ def run(request, assignment_id):
assignment = Assignment.objects.get(pk=assignment_id) assignment = Assignment.objects.get(pk=assignment_id)
try: try:
assignment.run(request.user, request.user) assignment.run(request.user, request.user)
messages.success(request, _('You have set your candidature successfully.') ) messages.success(request, _('You have set your candidature successfully.'))
except NameError, e: except NameError, e:
messages.error(request, e) messages.error(request, e)
return redirect(reverse('assignment_view', args=[assignment_id])) return redirect(reverse('assignment_view', args=[assignment_id]))
@ -195,7 +187,8 @@ def delrun(request, assignment_id):
except Exception, e: except Exception, e:
messages.error(request, e) messages.error(request, e)
else: else:
messages.success(request, messages.success(
request,
_("You have withdrawn your candidature successfully. " _("You have withdrawn your candidature successfully. "
"You can not be nominated by other participants anymore.")) "You can not be nominated by other participants anymore."))
else: else:
@ -240,7 +233,7 @@ def set_active(request, assignment_id):
@permission_required('assignment.can_manage_assignment') @permission_required('assignment.can_manage_assignment')
def gen_poll(request, assignment_id): def gen_poll(request, assignment_id):
poll = Assignment.objects.get(pk=assignment_id).gen_poll() poll = Assignment.objects.get(pk=assignment_id).gen_poll()
messages.success(request, _("New ballot was successfully created.") ) messages.success(request, _("New ballot was successfully created."))
return redirect(reverse('assignment_poll_view', args=[poll.id])) return redirect(reverse('assignment_poll_view', args=[poll.id]))
@ -279,9 +272,9 @@ def set_publish_status(request, poll_id):
return ajax_request({'published': poll.published}) return ajax_request({'published': poll.published})
if poll.published: if poll.published:
messages.success(request, _("Ballot successfully published.") ) messages.success(request, _("Ballot successfully published."))
else: else:
messages.success(request, _("Ballot successfully unpublished.") ) messages.success(request, _("Ballot successfully unpublished."))
return redirect(reverse('assignment_view', args=[poll.assignment.id])) return redirect(reverse('assignment_view', args=[poll.assignment.id]))
@ -336,8 +329,9 @@ class AssignmentPDF(PDFView):
try: try:
assignment_id = self.kwargs['assignment_id'] assignment_id = self.kwargs['assignment_id']
assignment = Assignment.objects.get(id=assignment_id) assignment = Assignment.objects.get(id=assignment_id)
filename = u'%s-%s' % (_("Assignment"), filename = u'%s-%s' % (
assignment.name.replace(' ','_')) _("Assignment"),
assignment.name.replace(' ', '_'))
except: except:
filename = _("Elections") filename = _("Elections")
return filename return filename
@ -347,23 +341,24 @@ class AssignmentPDF(PDFView):
assignment_id = self.kwargs['assignment_id'] assignment_id = self.kwargs['assignment_id']
except KeyError: except KeyError:
assignment_id = None assignment_id = None
if assignment_id is None: #print all assignments if assignment_id is None: # print all assignments
title = config["assignment_pdf_title"] title = config["assignment_pdf_title"]
story.append(Paragraph(title, stylesheet['Heading1'])) story.append(Paragraph(title, stylesheet['Heading1']))
preamble = config["assignment_pdf_preamble"] preamble = config["assignment_pdf_preamble"]
if preamble: if preamble:
story.append(Paragraph("%s" % preamble.replace('\r\n', '<br/>'), story.append(Paragraph(
"%s" % preamble.replace('\r\n', '<br/>'),
stylesheet['Paragraph'])) stylesheet['Paragraph']))
story.append(Spacer(0, 0.75 * cm)) story.append(Spacer(0, 0.75 * cm))
assignments = Assignment.objects.all() assignments = Assignment.objects.all()
if not assignments: # No assignments existing if not assignments: # No assignments existing
story.append(Paragraph(_("No assignments available."), story.append(Paragraph(
stylesheet['Heading3'])) _("No assignments available."), stylesheet['Heading3']))
else: # Print all assignments else: # Print all assignments
# List of assignments # List of assignments
for assignment in assignments: for assignment in assignments:
story.append(Paragraph(assignment.name, story.append(Paragraph(
stylesheet['Heading3'])) assignment.name, stylesheet['Heading3']))
# Assignment details (each assignment on single page) # Assignment details (each assignment on single page)
for assignment in assignments: for assignment in assignments:
story.append(PageBreak()) story.append(PageBreak())
@ -376,28 +371,33 @@ class AssignmentPDF(PDFView):
def get_assignment(self, assignment, story): def get_assignment(self, assignment, story):
# title # title
story.append(Paragraph(_("Election: %s") % assignment.name, story.append(Paragraph(
stylesheet['Heading1'])) _("Election: %s") % assignment.name, stylesheet['Heading1']))
story.append(Spacer(0, 0.5 * cm)) story.append(Spacer(0, 0.5 * cm))
# posts # posts
cell1a = [] cell1a = []
cell1a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % cell1a.append(Paragraph(
"<font name='Ubuntu-Bold'>%s:</font>" %
_("Number of available posts"), stylesheet['Bold'])) _("Number of available posts"), stylesheet['Bold']))
cell1b = [] cell1b = []
cell1b.append(Paragraph(str(assignment.posts), stylesheet['Paragraph'])) cell1b.append(Paragraph(str(assignment.posts), stylesheet['Paragraph']))
# candidates # candidates
cell2a = [] cell2a = []
cell2a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font><seqreset" \ cell2a.append(Paragraph(
"<font name='Ubuntu-Bold'>%s:</font><seqreset"
" id='counter'>" % _("Candidates"), stylesheet['Heading4'])) " id='counter'>" % _("Candidates"), stylesheet['Heading4']))
cell2b = [] cell2b = []
for candidate in assignment.candidates: for candidate in assignment.candidates:
cell2b.append(Paragraph("<seq id='counter'/>.&nbsp; %s" % candidate, cell2b.append(Paragraph(
"<seq id='counter'/>.&nbsp; %s" % candidate,
stylesheet['Signaturefield'])) stylesheet['Signaturefield']))
if assignment.status == "sea": if assignment.status == "sea":
for x in range(0, 2 * assignment.posts): for x in range(0, 2 * assignment.posts):
cell2b.append(Paragraph("<seq id='counter'/>.&nbsp; " cell2b.append(
"__________________________________________", Paragraph(
stylesheet['Signaturefield'])) "<seq id='counter'/>.&nbsp; "
"__________________________________________",
stylesheet['Signaturefield']))
cell2b.append(Spacer(0, 0.2 * cm)) cell2b.append(Spacer(0, 0.2 * cm))
# Vote results # Vote results
@ -409,15 +409,15 @@ class AssignmentPDF(PDFView):
# Left side # Left side
cell3a = [] cell3a = []
cell3a.append(Paragraph("%s:" % (_("Vote results")), cell3a.append(Paragraph(
stylesheet['Heading4'])) "%s:" % (_("Vote results")), stylesheet['Heading4']))
if polls.count() == 1: if polls.count() == 1:
cell3a.append(Paragraph("%s %s" % (polls.count(), _("ballot")), cell3a.append(Paragraph(
stylesheet['Normal'])) "%s %s" % (polls.count(), _("ballot")), stylesheet['Normal']))
elif polls.count() > 1: elif polls.count() > 1:
cell3a.append(Paragraph("%s %s" % (polls.count(), _("ballots")), cell3a.append(Paragraph(
stylesheet['Normal'])) "%s %s" % (polls.count(), _("ballots")), stylesheet['Normal']))
# Add table head row # Add table head row
headrow = [] headrow = []
@ -426,7 +426,6 @@ class AssignmentPDF(PDFView):
headrow.append("%s." % poll.get_ballot()) headrow.append("%s." % poll.get_ballot())
data_votes.append(headrow) data_votes.append(headrow)
# Add result rows # Add result rows
elected_candidates = list(assignment.elected) elected_candidates = list(assignment.elected)
for candidate, poll_list in vote_results.iteritems(): for candidate, poll_list in vote_results.iteritems():
@ -439,12 +438,13 @@ class AssignmentPDF(PDFView):
candidate_string += "\n(%s)" % candidate.name_suffix candidate_string += "\n(%s)" % candidate.name_suffix
row.append(candidate_string) row.append(candidate_string)
for vote in poll_list: for vote in poll_list:
if vote == None: if vote is None:
row.append('') row.append('')
elif 'Yes' in vote and 'No' in vote and 'Abstain' in vote: elif 'Yes' in vote and 'No' in vote and 'Abstain' in vote:
row.append(_("Y: %(YES)s\nN: %(NO)s\nA: %(ABSTAIN)s") row.append(
% {'YES':vote['Yes'], 'NO': vote['No'], _("Y: %(YES)s\nN: %(NO)s\nA: %(ABSTAIN)s")
'ABSTAIN': vote['Abstain']}) % {'YES': vote['Yes'], 'NO': vote['No'],
'ABSTAIN': vote['Abstain']})
elif 'Votes' in vote: elif 'Votes' in vote:
row.append(vote['Votes']) row.append(vote['Votes'])
else: else:
@ -465,15 +465,14 @@ class AssignmentPDF(PDFView):
footrow_two.append(poll.print_votescast()) footrow_two.append(poll.print_votescast())
data_votes.append(footrow_two) data_votes.append(footrow_two)
table_votes=Table(data_votes) table_votes = Table(data_votes)
table_votes.setStyle( TableStyle([ table_votes.setStyle(TableStyle([
('GRID', (0, 0), (-1, -1), 0.5, colors.grey), ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
('VALIGN',(0, 0),(-1, -1), 'TOP'), ('VALIGN', (0, 0), (-1, -1), 'TOP'),
('LINEABOVE',(0, 0),(-1, 0), 2, colors.black), ('LINEABOVE', (0, 0), (-1, 0), 2, colors.black),
('LINEABOVE',(0, 1),(-1, 1), 1, colors.black), ('LINEABOVE', (0, 1), (-1, 1), 1, colors.black),
('LINEBELOW',(0, -1),(-1, -1), 2, colors.black), ('LINEBELOW', (0, -1), (-1, -1), 2, colors.black),
('ROWBACKGROUNDS', (0, 1), (-1, -1), (colors.white, (.9, .9, .9))), ('ROWBACKGROUNDS', (0, 1), (-1, -1), (colors.white, (.9, .9, .9)))]))
]))
# table # table
data = [] data = []
@ -484,17 +483,18 @@ class AssignmentPDF(PDFView):
else: else:
data.append([cell2a, cell2b]) data.append([cell2a, cell2b])
data.append([Spacer(0, 0.2 * cm), '']) data.append([Spacer(0, 0.2 * cm), ''])
t=Table(data) t = Table(data)
t._argW[0] = 4.5 * cm t._argW[0] = 4.5 * cm
t._argW[1] = 11 * cm t._argW[1] = 11 * cm
t.setStyle(TableStyle([ ('BOX', (0,0), (-1, -1), 1, colors.black), t.setStyle(TableStyle([
('VALIGN', (0, 0), (-1, -1), 'TOP'), ('BOX', (0, 0), (-1, -1), 1, colors.black),
])) ('VALIGN', (0, 0), (-1, -1), 'TOP')]))
story.append(t) story.append(t)
story.append(Spacer(0, 1 * cm)) story.append(Spacer(0, 1 * cm))
# text # text
story.append(Paragraph("%s" % assignment.description.replace('\r\n', story.append(Paragraph(
"%s" % assignment.description.replace('\r\n',
'<br/>'), stylesheet['Paragraph'])) '<br/>'), stylesheet['Paragraph']))
@ -519,13 +519,15 @@ class AssignmentPollPDF(PDFView):
return super(AssignmentPollPDF, self).get(request, *args, **kwargs) return super(AssignmentPollPDF, self).get(request, *args, **kwargs)
def get_filename(self): def get_filename(self):
filename = u'%s-%s_%s' % (_("Election"), self.poll.assignment.name.replace(' ', '_'), filename = u'%s-%s_%s' % (
_("Election"), self.poll.assignment.name.replace(' ', '_'),
self.poll.get_ballot()) self.poll.get_ballot())
return filename return filename
def get_template(self, buffer): def get_template(self, buffer):
return SimpleDocTemplate(buffer, topMargin=-6, bottomMargin=-6, return SimpleDocTemplate(
leftMargin=0, rightMargin=0, showBoundary=False) buffer, topMargin=-6, bottomMargin=-6, leftMargin=0, rightMargin=0,
showBoundary=False)
def build_document(self, pdf_document, story): def build_document(self, pdf_document, story):
pdf_document.build(story) pdf_document.build(story)
@ -534,23 +536,27 @@ class AssignmentPollPDF(PDFView):
imgpath = os.path.join(settings.SITE_ROOT, 'static/images/circle.png') imgpath = os.path.join(settings.SITE_ROOT, 'static/images/circle.png')
circle = "<img src='%s' width='15' height='15'/>&nbsp;&nbsp;" % imgpath circle = "<img src='%s' width='15' height='15'/>&nbsp;&nbsp;" % imgpath
cell = [] cell = []
cell.append(Spacer(0,0.8*cm)) cell.append(Spacer(0, 0.8 * cm))
cell.append(Paragraph(_("Election") + ": " + self.poll.assignment.name, cell.append(Paragraph(
_("Election") + ": " + self.poll.assignment.name,
stylesheet['Ballot_title'])) stylesheet['Ballot_title']))
cell.append(Paragraph(self.poll.assignment.polldescription, cell.append(Paragraph(
self.poll.assignment.polldescription,
stylesheet['Ballot_subtitle'])) stylesheet['Ballot_subtitle']))
options = self.poll.get_options() options = self.poll.get_options()
ballot_string = _("%d. ballot") % self.poll.get_ballot() ballot_string = _("%d. ballot") % self.poll.get_ballot()
candidate_string = ungettext("%d candidate", "%d candidates", candidate_string = ungettext(
len(options)) % len(options) "%d candidate", "%d candidates", len(options)) % len(options)
available_posts_string = ungettext("%d available post", "%d available posts", available_posts_string = ungettext(
"%d available post", "%d available posts",
self.poll.assignment.posts) % self.poll.assignment.posts self.poll.assignment.posts) % self.poll.assignment.posts
cell.append(Paragraph("%s, %s, %s" % (ballot_string, candidate_string, cell.append(Paragraph(
"%s, %s, %s" % (ballot_string, candidate_string,
available_posts_string), stylesheet['Ballot_description'])) available_posts_string), stylesheet['Ballot_description']))
cell.append(Spacer(0, 0.4 * cm)) cell.append(Spacer(0, 0.4 * cm))
data= [] data = []
# get ballot papers config values # get ballot papers config values
ballot_papers_selection = config["assignment_pdf_ballot_papers_selection"] ballot_papers_selection = config["assignment_pdf_ballot_papers_selection"]
ballot_papers_number = config["assignment_pdf_ballot_papers_number"] ballot_papers_number = config["assignment_pdf_ballot_papers_number"]
@ -560,7 +566,7 @@ class AssignmentPollPDF(PDFView):
number = User.objects.filter(type__iexact="delegate").count() number = User.objects.filter(type__iexact="delegate").count()
elif ballot_papers_selection == "NUMBER_OF_ALL_PARTICIPANTS": elif ballot_papers_selection == "NUMBER_OF_ALL_PARTICIPANTS":
number = int(User.objects.count()) number = int(User.objects.count())
else: # ballot_papers_selection == "CUSTOM_NUMBER" else: # ballot_papers_selection == "CUSTOM_NUMBER"
number = int(ballot_papers_number) number = int(ballot_papers_number)
number = max(1, number) number = max(1, number)
@ -568,16 +574,18 @@ class AssignmentPollPDF(PDFView):
if self.poll.yesnoabstain: if self.poll.yesnoabstain:
for option in options: for option in options:
candidate = option.candidate candidate = option.candidate
cell.append(Paragraph(candidate.clean_name, cell.append(Paragraph(
stylesheet['Ballot_option_name'])) candidate.clean_name, stylesheet['Ballot_option_name']))
if candidate.name_suffix: if candidate.name_suffix:
cell.append(Paragraph("(%s)" % candidate.name_suffix, cell.append(Paragraph(
"(%s)" % candidate.name_suffix,
stylesheet['Ballot_option_group'])) stylesheet['Ballot_option_group']))
else: else:
cell.append(Paragraph("&nbsp;", cell.append(Paragraph(
stylesheet['Ballot_option_group'])) "&nbsp;", stylesheet['Ballot_option_group']))
cell.append(Paragraph(circle + _("Yes") + "&nbsp; " * 3 + circle cell.append(Paragraph(
+ _("No") + "&nbsp; " * 3 + circle+ _("Abstention"), circle + _("Yes") + "&nbsp; " * 3 + circle
+ _("No") + "&nbsp; " * 3 + circle + _("Abstention"),
stylesheet['Ballot_option_YNA'])) stylesheet['Ballot_option_YNA']))
# print ballot papers # print ballot papers
for user in xrange(number / 2): for user in xrange(number / 2):
@ -594,14 +602,16 @@ class AssignmentPollPDF(PDFView):
else: else:
for option in options: for option in options:
candidate = option.candidate candidate = option.candidate
cell.append(Paragraph(circle + candidate.clean_name, cell.append(Paragraph(
circle + candidate.clean_name,
stylesheet['Ballot_option_name'])) stylesheet['Ballot_option_name']))
if candidate.name_suffix: if candidate.name_suffix:
cell.append(Paragraph("(%s)" % candidate.name_suffix, cell.append(Paragraph(
"(%s)" % candidate.name_suffix,
stylesheet['Ballot_option_group_right'])) stylesheet['Ballot_option_group_right']))
else: else:
cell.append(Paragraph("&nbsp;", cell.append(Paragraph(
stylesheet['Ballot_option_group_right'])) "&nbsp;", stylesheet['Ballot_option_group_right']))
# print ballot papers # print ballot papers
for user in xrange(number / 2): for user in xrange(number / 2):
data.append([cell, cell]) data.append([cell, cell])
@ -615,9 +625,9 @@ class AssignmentPollPDF(PDFView):
else: else:
t = Table(data, 10.5 * cm, 29.7 * cm) t = Table(data, 10.5 * cm, 29.7 * cm)
t.setStyle(TableStyle([('GRID', (0, 0), (-1, -1), 0.25, colors.grey), t.setStyle(TableStyle([
('VALIGN', (0, 0), (-1, -1), 'TOP'), ('GRID', (0, 0), (-1, -1), 0.25, colors.grey),
])) ('VALIGN', (0, 0), (-1, -1), 'TOP')]))
story.append(t) story.append(t)
@ -629,16 +639,15 @@ class Config(FormView):
def get_initial(self): def get_initial(self):
return { return {
'assignment_publish_winner_results_only': 'assignment_publish_winner_results_only':
config['assignment_publish_winner_results_only'], config['assignment_publish_winner_results_only'],
'assignment_pdf_ballot_papers_selection': 'assignment_pdf_ballot_papers_selection':
config['assignment_pdf_ballot_papers_selection'], config['assignment_pdf_ballot_papers_selection'],
'assignment_pdf_ballot_papers_number': '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_title': config['assignment_pdf_title'],
'assignment_pdf_preamble': config['assignment_pdf_preamble'], 'assignment_pdf_preamble': config['assignment_pdf_preamble'],
'assignment_poll_vote_values': 'assignment_poll_vote_values':
config['assignment_poll_vote_values'], config['assignment_poll_vote_values']}
}
def form_valid(self, form): def form_valid(self, form):
if form.cleaned_data['assignment_publish_winner_results_only']: if form.cleaned_data['assignment_publish_winner_results_only']:
@ -655,8 +664,8 @@ class Config(FormView):
form.cleaned_data['assignment_pdf_preamble'] form.cleaned_data['assignment_pdf_preamble']
config['assignment_poll_vote_values'] = \ config['assignment_poll_vote_values'] = \
form.cleaned_data['assignment_poll_vote_values'] form.cleaned_data['assignment_poll_vote_values']
messages.success(self.request, messages.success(
_('Election settings successfully saved.')) self.request, _('Election settings successfully saved.'))
return super(Config, self).form_valid(form) return super(Config, self).form_valid(form)
@ -665,10 +674,11 @@ def register_tab(request):
return Tab( return Tab(
title=_('Elections'), title=_('Elections'),
url=reverse('assignment_overview'), url=reverse('assignment_overview'),
permission=request.user.has_perm('assignment.can_see_assignment') permission=(
or request.user.has_perm('assignment.can_nominate_other') request.user.has_perm('assignment.can_see_assignment') or
or request.user.has_perm('assignment.can_nominate_self') request.user.has_perm('assignment.can_nominate_other') or
or request.user.has_perm('assignment.can_manage_assignment'), request.user.has_perm('assignment.can_nominate_self') or
request.user.has_perm('assignment.can_manage_assignment')),
selected=selected, selected=selected,
) )

View File

@ -15,8 +15,6 @@ from django.utils.translation import ugettext_lazy as _
from openslides.utils.forms import CssClassMixin from openslides.utils.forms import CssClassMixin
from openslides.config.models import config
class GeneralConfigForm(forms.Form, CssClassMixin): class GeneralConfigForm(forms.Form, CssClassMixin):
event_name = forms.CharField( event_name = forms.CharField(

View File

@ -51,7 +51,7 @@ class Config(object):
pass pass
for receiver, value in default_config_value.send(sender='config', for receiver, value in default_config_value.send(sender='config',
key=key): key=key):
if value is not None: if value is not None:
return value return value
if settings.DEBUG: if settings.DEBUG:
@ -69,7 +69,6 @@ class Config(object):
def __contains__(self, item): def __contains__(self, item):
return ConfigStore.objects.filter(key=item).exists() return ConfigStore.objects.filter(key=item).exists()
config = Config() config = Config()
@ -81,7 +80,7 @@ def default_config(sender, key, **kwargs):
return { return {
'event_name': 'OpenSlides', 'event_name': 'OpenSlides',
'event_description': 'event_description':
_('Presentation and assembly system'), _('Presentation and assembly system'),
'event_date': '', 'event_date': '',
'event_location': '', 'event_location': '',
'event_organizer': '', 'event_organizer': '',
@ -123,11 +122,9 @@ def set_submenu(sender, request, context, **kwargs):
(reverse('config_%s' % appname), _(title), selected) (reverse('config_%s' % appname), _(title), selected)
) )
menu_links.append ( menu_links.append((
(reverse('config_version'), _('Version'), reverse('config_version'), _('Version'),
request.path == reverse('config_version')) request.path == reverse('config_version')))
)
context.update({ context.update({
'menu_links': menu_links, 'menu_links': menu_links})
})

View File

@ -12,18 +12,18 @@
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.models import Group, Permission
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.importlib import import_module from django.utils.importlib import import_module
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from openslides import get_version, get_git_commit_id, RELEASE from openslides import get_version, get_git_commit_id, RELEASE
from openslides.utils.template import Tab from openslides.utils.template import Tab
from openslides.utils.views import FormView, TemplateView from openslides.utils.views import FormView, TemplateView
from .forms import GeneralConfigForm
from .models import config
from openslides.config.forms import GeneralConfigForm # TODO: Do not import the participant module in config
from openslides.config.models import config from openslides.participant.api import get_or_create_anonymous_group
class GeneralConfig(FormView): class GeneralConfig(FormView):
@ -61,27 +61,12 @@ class GeneralConfig(FormView):
# system # system
if form.cleaned_data['system_enable_anonymous']: if form.cleaned_data['system_enable_anonymous']:
config['system_enable_anonymous'] = True config['system_enable_anonymous'] = True
# check for Anonymous group and (re)create it as needed get_or_create_anonymous_group()
try:
anonymous = Group.objects.get(name='Anonymous')
except Group.DoesNotExist:
default_perms = ['can_see_agenda', 'can_see_projector',
'can_see_motion', 'can_see_assignment',
'can_see_dashboard']
anonymous = Group()
anonymous.name = 'Anonymous'
anonymous.save()
anonymous.permissions = Permission.objects.filter(
codename__in=default_perms)
anonymous.save()
messages.success(self.request,
_('Anonymous access enabled. Please modify the "Anonymous" ' \
'group to fit your required permissions.'))
else: else:
config['system_enable_anonymous'] = False config['system_enable_anonymous'] = False
messages.success(self.request, messages.success(
_('General settings successfully saved.')) self.request, _('General settings successfully saved.'))
return super(GeneralConfig, self).form_valid(form) return super(GeneralConfig, self).form_valid(form)

View File

@ -13,15 +13,12 @@
import os import os
import sys import sys
_fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() from openslides.main import fs2unicode
def _fs2unicode(s):
if isinstance(s, unicode):
return s
return s.decode(_fs_encoding)
SITE_ROOT = os.path.realpath(os.path.dirname(__file__)) SITE_ROOT = os.path.realpath(os.path.dirname(__file__))
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend', AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'openslides.utils.auth.AnonymousAuth',) 'openslides.utils.auth.AnonymousAuth',)
LOGIN_URL = '/login/' LOGIN_URL = '/login/'
@ -48,12 +45,12 @@ USE_I18N = True
USE_L10N = True USE_L10N = True
LOCALE_PATHS = ( LOCALE_PATHS = (
_fs2unicode(os.path.join(SITE_ROOT, 'locale')), fs2unicode(os.path.join(SITE_ROOT, 'locale')),
) )
# Absolute path to the directory that holds media. # Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/" # Example: "/home/media/media.lawrence.com/"
MEDIA_ROOT = _fs2unicode(os.path.join(SITE_ROOT, './static/')) MEDIA_ROOT = fs2unicode(os.path.join(SITE_ROOT, './static/'))
# URL that handles the media served from MEDIA_ROOT. Make sure to use a # URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash if there is a path component (optional in other cases). # trailing slash if there is a path component (optional in other cases).
@ -62,17 +59,17 @@ MEDIA_URL = ''
# Absolute path to the directory that holds static media from ``collectstatic`` # Absolute path to the directory that holds static media from ``collectstatic``
# Example: "/home/media/static.lawrence.com/" # Example: "/home/media/static.lawrence.com/"
STATIC_ROOT = _fs2unicode(os.path.join(SITE_ROOT, '../site-static')) STATIC_ROOT = fs2unicode(os.path.join(SITE_ROOT, '../site-static'))
# URL that handles the media served from STATIC_ROOT. Make sure to use a # URL that handles the media served from STATIC_ROOT. Make sure to use a
# trailing slash if there is a path component (optional in other cases). # trailing slash if there is a path component (optional in other cases).
# Examples: "http://static.lawrence.com", "http://example.com/static/" # Examples: "http://static.lawrence.com", "http://example.com/static/"
STATIC_URL = '/static/' STATIC_URL = '/static/'
# Additional directories containing static files (not application specific) # Additional directories containing static files (not application specific)
# Examples: "/home/media/lawrence.com/extra-static/" # Examples: "/home/media/lawrence.com/extra-static/"
STATICFILES_DIRS = ( STATICFILES_DIRS = (
_fs2unicode(os.path.join(SITE_ROOT, 'static')), fs2unicode(os.path.join(SITE_ROOT, 'static')),
) )
#XXX: Note this setting (as well as our workaround finder) #XXX: Note this setting (as well as our workaround finder)
@ -106,7 +103,7 @@ TEMPLATE_DIRS = (
# "C:/www/django/templates". # "C:/www/django/templates".
# Always use forward slashes, even on Windows. # Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths. # Don't forget to use absolute paths, not relative paths.
_fs2unicode(os.path.join(SITE_ROOT, 'templates')), fs2unicode(os.path.join(SITE_ROOT, 'templates')),
) )
INSTALLED_APPS = ( INSTALLED_APPS = (
@ -141,3 +138,6 @@ CACHES = {
'LOCATION': 'openslidecache' 'LOCATION': 'openslidecache'
} }
} }
TEST_RUNNER = 'discover_runner.DiscoverRunner'
TEST_DISCOVER_TOP_LEVEL = os.path.dirname(os.path.dirname(__file__))

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -13,13 +13,15 @@
# for python 2.5 support # for python 2.5 support
from __future__ import with_statement from __future__ import with_statement
import os
import sys
import optparse
import socket
import time
import threading
import base64 import base64
import ctypes
import optparse
import os
import socket
import sys
import tempfile
import threading
import time
import webbrowser import webbrowser
import django.conf import django.conf
@ -28,14 +30,15 @@ from django.core.management import execute_from_command_line
CONFIG_TEMPLATE = """#!/usr/bin/env python CONFIG_TEMPLATE = """#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import openslides.main
from openslides.global_settings import * from openslides.global_settings import *
# Use 'DEBUG = True' to get more details for server errors # Use 'DEBUG = True' to get more details for server errors
# (Default for relaeses: 'False') # (Default for releases: 'False')
DEBUG = False DEBUG = False
TEMPLATE_DEBUG = DEBUG TEMPLATE_DEBUG = DEBUG
DBPATH = %(dbpath)r DBPATH = %(dbpath)s
DATABASES = { DATABASES = {
'default': { 'default': {
@ -64,15 +67,12 @@ INSTALLED_APPS += INSTALLED_PLUGINS
KEY_LENGTH = 30 KEY_LENGTH = 30
# sentinel used to signal that the database ought to be stored
_fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() # relative to the portable's directory
def _fs2unicode(s): _portable_db_path = object()
if isinstance(s, unicode):
return s
return s.decode(_fs_encoding)
def main(argv=None, opt_defaults=None, database_path=None): def process_options(argv=None):
if argv is None: if argv is None:
argv = sys.argv[1:] argv = sys.argv[1:]
@ -86,12 +86,11 @@ def main(argv=None, opt_defaults=None, database_path=None):
parser.add_option( parser.add_option(
"--reset-admin", action="store_true", "--reset-admin", action="store_true",
help="Make sure the user 'admin' exists and uses 'admin' as password") help="Make sure the user 'admin' exists and uses 'admin' as password")
parser.add_option("-s", "--settings", help="Path to the openslides configuration.")
parser.add_option( parser.add_option(
"--no-reload", action="store_true", help="Do not reload the development server") "-s", "--settings", help="Path to the openslides configuration.")
parser.add_option(
if not opt_defaults is None: "--no-reload", action="store_true",
parser.set_defaults(**opt_defaults) help="Do not reload the development server")
opts, args = parser.parse_args(argv) opts, args = parser.parse_args(argv)
if args: if args:
@ -99,12 +98,45 @@ def main(argv=None, opt_defaults=None, database_path=None):
parser.print_help() parser.print_help()
sys.exit(1) sys.exit(1)
return opts
def main(argv=None):
opts = process_options(argv)
_main(opts)
def win32_portable_main(argv=None):
"""special entry point for the win32 portable version"""
opts = process_options(argv)
database_path = None
if opts.settings is None:
portable_dir = get_portable_path()
try:
fd, test_file = tempfile.mkstemp(dir=portable_dir)
except OSError:
portable_dir_writeable = False
else:
portable_dir_writeable = True
os.close(fd)
os.unlink(test_file)
if portable_dir_writeable:
opts.settings = os.path.join(
portable_dir, "openslides", "settings.py")
database_path = _portable_db_path
_main(opts, database_path=database_path)
def _main(opts, database_path=None):
# Find the path to the settings # Find the path to the settings
settings_path = opts.settings settings_path = opts.settings
if settings_path is None: if settings_path is None:
config_home = os.environ.get('XDG_CONFIG_HOME', \ settings_path = get_user_config_path('openslides', 'settings.py')
os.path.join(os.path.expanduser('~'), '.config'))
settings_path = os.path.join(config_home, 'openslides', 'settings.py')
# Create settings if necessary # Create settings if necessary
if not os.path.exists(settings_path): if not os.path.exists(settings_path):
@ -141,14 +173,17 @@ def main(argv=None, opt_defaults=None, database_path=None):
def create_settings(settings_path, database_path=None): def create_settings(settings_path, database_path=None):
settings_module = os.path.dirname(settings_path) settings_module = os.path.dirname(settings_path)
if database_path is None: if database_path is _portable_db_path:
data_home = os.environ.get('XDG_DATA_HOME', \ database_path = get_portable_db_path()
os.path.join(os.path.expanduser('~'), '.local', 'share')) dbpath_value = 'openslides.main.get_portable_db_path()'
database_path = os.path.join(data_home, 'openslides', 'database.sqlite') 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( settings_content = CONFIG_TEMPLATE % dict(
default_key=base64.b64encode(os.urandom(KEY_LENGTH)), default_key=base64.b64encode(os.urandom(KEY_LENGTH)),
dbpath=_fs2unicode(database_path)) dbpath=dbpath_value)
if not os.path.exists(settings_module): if not os.path.exists(settings_module):
os.makedirs(settings_module) os.makedirs(settings_module)
@ -217,6 +252,7 @@ def run_syncdb():
# now initialize the database # now initialize the database
argv = ["", "syncdb", "--noinput"] argv = ["", "syncdb", "--noinput"]
execute_from_command_line(argv) execute_from_command_line(argv)
execute_from_command_line(["", "loaddata", "groups_de"])
def set_system_url(url): def set_system_url(url):
@ -269,51 +305,71 @@ def start_browser(url):
t = threading.Thread(target=f) t = threading.Thread(target=f)
t.start() t.start()
def win32_portable_main(argv=None):
"""special entry point for the win32 portable version"""
import tempfile
def fs2unicode(s):
if isinstance(s, unicode):
return s
fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
return s.decode(fs_encoding)
def get_user_config_path(*args):
if sys.platform == "win32":
return win32_get_app_data_path(*args)
config_home = os.environ.get(
'XDG_CONFIG_HOME', os.path.join(os.path.expanduser('~'), '.config'))
return os.path.join(fs2unicode(config_home), *args)
def get_user_data_path(*args):
if sys.platform == "win32":
return win32_get_app_data_path(*args)
data_home = os.environ.get(
'XDG_DATA_HOME', os.path.join(
os.path.expanduser('~'), '.local', 'share'))
return os.path.join(fs2unicode(data_home), *args)
def get_portable_path(*args):
# NOTE: sys.executable will be the path to openslides.exe # NOTE: sys.executable will be the path to openslides.exe
# since it is essentially a small wrapper that embeds the # since it is essentially a small wrapper that embeds the
# python interpreter # python interpreter
portable_dir = os.path.dirname(os.path.abspath(sys.executable))
try:
fd, test_file = tempfile.mkstemp(dir=portable_dir)
except OSError:
portable_dir_writeable = False
else:
portable_dir_writeable = True
os.close(fd)
os.unlink(test_file)
if portable_dir_writeable: exename = os.path.basename(sys.executable).lower()
default_settings = os.path.join(portable_dir, "openslides", if exename != "openslides.exe":
"openslides_personal_settings.py") raise Exception(
database_path = os.path.join(portable_dir, "openslides", "Cannot determine portable path when "
"database.sqlite") "not running as portable")
else:
import ctypes
shell32 = ctypes.WinDLL("shell32.dll") portable_dir = fs2unicode(os.path.dirname(os.path.abspath(sys.executable)))
SHGetFolderPath = shell32.SHGetFolderPathW return os.path.join(portable_dir, *args)
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) def get_portable_db_path():
res = SHGetFolderPath(0, CSIDL_LOCAL_APPDATA, 0, 0, buf) return get_portable_path('openslides', 'database.sqlite')
if res != 0:
raise Exception("Could not deterime APPDATA path")
default_settings = os.path.join(buf.value, "openslides",
"openslides_personal_settings.py")
database_path = os.path.join(buf.value, "openslides",
"database.sqlite")
main(argv, opt_defaults={ "settings": default_settings },
database_path=database_path) 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__": if __name__ == "__main__":

View File

@ -11,7 +11,7 @@
""" """
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _, ugettext_noop from django.utils.translation import ugettext_lazy as _
from openslides.utils.forms import CssClassMixin from openslides.utils.forms import CssClassMixin
from openslides.utils.person import PersonFormField, MultiplePersonFormField from openslides.utils.person import PersonFormField, MultiplePersonFormField
@ -21,18 +21,18 @@ from openslides.motion.models import Motion
class MotionForm(forms.Form, CssClassMixin): class MotionForm(forms.Form, CssClassMixin):
title = forms.CharField(widget=forms.TextInput(), label=_("Title")) title = forms.CharField(widget=forms.TextInput(), label=_("Title"))
text = forms.CharField(widget=forms.Textarea(), label=_("Text")) text = forms.CharField(widget=forms.Textarea(), label=_("Text"))
reason = forms.CharField(widget=forms.Textarea(), required=False, reason = forms.CharField(
label=_("Reason")) widget=forms.Textarea(), required=False, label=_("Reason"))
class MotionFormTrivialChanges(MotionForm): class MotionFormTrivialChanges(MotionForm):
trivial_change = forms.BooleanField(required=False, trivial_change = forms.BooleanField(
label=_("Trivial change"), required=False, label=_("Trivial change"),
help_text=_("Trivial changes don't create a new version.")) help_text=_("Trivial changes don't create a new version."))
class MotionManagerForm(forms.ModelForm, CssClassMixin): class MotionManagerForm(forms.ModelForm, CssClassMixin):
submitter = PersonFormField(label = _("Submitter")) submitter = PersonFormField(label=_("Submitter"))
class Meta: class Meta:
model = Motion model = Motion
@ -46,20 +46,20 @@ class MotionManagerFormSupporter(MotionManagerForm):
class MotionImportForm(forms.Form, CssClassMixin): class MotionImportForm(forms.Form, CssClassMixin):
csvfile = forms.FileField( csvfile = forms.FileField(
widget=forms.FileInput(attrs={'size':'50'}), widget=forms.FileInput(attrs={'size': '50'}),
label=_("CSV File"), label=_("CSV File"),
) )
import_permitted = forms.BooleanField( import_permitted = forms.BooleanField(
required=False, required=False,
label=_("Import motions with status \"authorized\""), label=_("Import motions with status \"authorized\""),
help_text=_('Set the initial status for each motion to ' help_text=_('Set the initial status for each motion to '
'"authorized"'), '"authorized"'),
) )
class ConfigForm(forms.Form, CssClassMixin): class ConfigForm(forms.Form, CssClassMixin):
motion_min_supporters = forms.IntegerField( motion_min_supporters = forms.IntegerField(
widget=forms.TextInput(attrs={'class':'small-input'}), widget=forms.TextInput(attrs={'class': 'small-input'}),
label=_("Number of (minimum) required supporters for a motion"), label=_("Number of (minimum) required supporters for a motion"),
initial=4, initial=4,
min_value=0, min_value=0,
@ -82,7 +82,7 @@ class ConfigForm(forms.Form, CssClassMixin):
] ]
) )
motion_pdf_ballot_papers_number = forms.IntegerField( motion_pdf_ballot_papers_number = forms.IntegerField(
widget=forms.TextInput(attrs={'class':'small-input'}), widget=forms.TextInput(attrs={'class': 'small-input'}),
required=False, required=False,
min_value=1, min_value=1,
label=_("Custom number of ballot papers") label=_("Custom number of ballot papers")
@ -101,6 +101,6 @@ class ConfigForm(forms.Form, CssClassMixin):
motion_allow_trivial_change = forms.BooleanField( motion_allow_trivial_change = forms.BooleanField(
label=_("Allow trivial changes"), label=_("Allow trivial changes"),
help_text=_('Warning: Trivial changes undermine the motions ' help_text=_('Warning: Trivial changes undermine the motions '
'autorisation system.'), 'autorisation system.'),
required=False, required=False,
) )

View File

@ -21,18 +21,13 @@ from django.utils.translation import ugettext_lazy as _, ugettext_noop, ugettext
from openslides.utils.utils import _propper_unicode from openslides.utils.utils import _propper_unicode
from openslides.utils.person import PersonField from openslides.utils.person import PersonField
from openslides.config.models import config from openslides.config.models import config
from openslides.config.signals import default_config_value from openslides.config.signals import default_config_value
from openslides.poll.models import (
from openslides.poll.models import (BaseOption, BasePoll, CountVotesCast, BaseOption, BasePoll, CountVotesCast, CountInvalid, BaseVote)
CountInvalid, BaseVote) from openslides.participant.models import User
from openslides.participant.models import User, Group
from openslides.projector.api import register_slidemodel from openslides.projector.api import register_slidemodel
from openslides.projector.models import SlideMixin from openslides.projector.models import SlideMixin
from openslides.agenda.models import Item from openslides.agenda.models import Item
@ -53,7 +48,7 @@ class Motion(models.Model, SlideMixin):
('noc', _('Not Concerned')), ('noc', _('Not Concerned')),
('com', _('Commited a bill')), ('com', _('Commited a bill')),
('nop', _('Rejected (not authorized)')), ('nop', _('Rejected (not authorized)')),
('rev', _('Needs Review')), # Where is this status used? ('rev', _('Needs Review')), # Where is this status used?
#additional actions: #additional actions:
# edit # edit
# delete # delete
@ -67,9 +62,9 @@ class Motion(models.Model, SlideMixin):
submitter = PersonField(verbose_name=_("Submitter")) submitter = PersonField(verbose_name=_("Submitter"))
number = models.PositiveSmallIntegerField(blank=True, null=True, number = models.PositiveSmallIntegerField(blank=True, null=True,
unique=True) unique=True)
status = models.CharField(max_length=3, choices=STATUS, default='pub') status = models.CharField(max_length=3, choices=STATUS, default='pub')
permitted = models.ForeignKey('AVersion', related_name='permitted', \ permitted = models.ForeignKey('AVersion', related_name='permitted',
null=True, blank=True) null=True, blank=True)
log = models.TextField(blank=True, null=True) log = models.TextField(blank=True, null=True)
@ -94,7 +89,7 @@ class Motion(models.Model, SlideMixin):
else: else:
return self.last_version return self.last_version
def accept_version(self, version, user = None): def accept_version(self, version, user=None):
""" """
accept a Version accept a Version
""" """
@ -102,15 +97,15 @@ class Motion(models.Model, SlideMixin):
self.save(nonewversion=True) self.save(nonewversion=True)
version.rejected = False version.rejected = False
version.save() version.save()
self.writelog(_("Version %d authorized") % (version.aid, ), self.writelog(_("Version %d authorized") % version.aid, user)
user)
def reject_version(self, version, user = None): def reject_version(self, version, user=None):
if version.id > self.permitted.id: if version.id > self.permitted.id:
version.rejected = True version.rejected = True
version.save() version.save()
self.writelog(pgettext("Rejected means not authorized", "Version %d rejected") self.writelog(pgettext(
% (version.aid, ), user) "Rejected means not authorized", "Version %d rejected")
% version.aid, user)
return True return True
return False return False
@ -154,8 +149,8 @@ class Motion(models.Model, SlideMixin):
is not the lastone and the lastone is not rejected. is not the lastone and the lastone is not rejected.
TODO: rename the property in unchecked__changes TODO: rename the property in unchecked__changes
""" """
if (self.last_version != self.permitted if (self.last_version != self.permitted and
and not self.last_version.rejected): not self.last_version.rejected):
return True return True
else: else:
return False return False
@ -207,7 +202,8 @@ class Motion(models.Model, SlideMixin):
last_version = self.last_version last_version = self.last_version
fields = ["text", "title", "reason"] fields = ["text", "title", "reason"]
if last_version is not None: if last_version is not None:
changed_fields = [f for f in fields changed_fields = [
f for f in fields
if getattr(last_version, f) != getattr(self, f)] if getattr(last_version, f) != getattr(self, f)]
if not changed_fields: if not changed_fields:
return # No changes return # No changes
@ -219,19 +215,22 @@ class Motion(models.Model, SlideMixin):
last_version.save() last_version.save()
meta = AVersion._meta meta = AVersion._meta
field_names = [unicode(meta.get_field(f).verbose_name) field_names = [
unicode(meta.get_field(f).verbose_name)
for f in changed_fields] for f in changed_fields]
self.writelog(_("Trivial changes to version %(version)d; " self.writelog(
"changed fields: %(changed_fields)s") _("Trivial changes to version %(version)d; "
% dict(version = last_version.aid, "changed fields: %(changed_fields)s")
changed_fields = ", ".join(field_names))) % dict(version=last_version.aid,
return # Done changed_fields=", ".join(field_names)))
return # Done
version = AVersion(title=getattr(self, 'title', ''), version = AVersion(
text=getattr(self, 'text', ''), title=getattr(self, 'title', ''),
reason=getattr(self, 'reason', ''), text=getattr(self, 'text', ''),
motion=self) reason=getattr(self, 'reason', ''),
motion=self)
version.save() version.save()
self.writelog(_("Version %s created") % version.aid, user) self.writelog(_("Version %s created") % version.aid, user)
is_manager = user.has_perm('motion.can_manage_motion') is_manager = user.has_perm('motion.can_manage_motion')
@ -239,9 +238,8 @@ class Motion(models.Model, SlideMixin):
is_manager = False is_manager = False
supporters = self.motionsupporter_set.all() supporters = self.motionsupporter_set.all()
if (self.status == "pub" if (self.status == "pub" and
and supporters supporters and not is_manager):
and not is_manager):
supporters.delete() supporters.delete()
self.writelog(_("Supporters removed"), user) self.writelog(_("Supporters removed"), user)
@ -272,7 +270,7 @@ class Motion(models.Model, SlideMixin):
remove a supporter from the list of supporters of the motion remove a supporter from the list of supporters of the motion
""" """
try: try:
object = self.motionsupporter_set.get(person=person).delete() self.motionsupporter_set.get(person=person).delete()
except MotionSupporter.DoesNotExist: except MotionSupporter.DoesNotExist:
# TODO: Don't do nothing but raise a precise exception for the view # TODO: Don't do nothing but raise a precise exception for the view
pass pass
@ -288,8 +286,7 @@ class Motion(models.Model, SlideMixin):
raise NameError('This motion has already a number.') raise NameError('This motion has already a number.')
if number is None: if number is None:
try: try:
number = Motion.objects.aggregate(Max('number')) \ number = Motion.objects.aggregate(Max('number'))['number__max'] + 1
['number__max'] + 1
except TypeError: except TypeError:
number = 1 number = 1
self.number = number self.number = number
@ -316,8 +313,6 @@ class Motion(models.Model, SlideMixin):
""" """
self.set_status(user, "nop") self.set_status(user, "nop")
#TODO: reject last version #TODO: reject last version
aversion = self.last_version
#self.permitted = aversion
if self.number is None: if self.number is None:
self.set_number() self.set_number()
self.save() self.save()
@ -333,11 +328,11 @@ class Motion(models.Model, SlideMixin):
error = False error = False
break break
if error: if error:
#TODO: Use the Right Error # TODO: Use the Right Error
raise NameError(_('%s is not a valid status.') % status) raise NameError(_('%s is not a valid status.') % status)
if self.status == status: if self.status == status:
#TODO: Use the Right Error # TODO: Use the Right Error
raise NameError(_('The motion status is already \'%s.\'') \ raise NameError(_('The motion status is already \'%s.\'')
% self.status) % self.status)
actions = [] actions = []
@ -353,7 +348,7 @@ class Motion(models.Model, SlideMixin):
oldstatus = self.get_status_display() oldstatus = self.get_status_display()
self.status = status self.status = status
self.save() self.save()
self.writelog(_("Status modified")+": %s -> %s" \ self.writelog(_("Status modified") + ": %s -> %s"
% (oldstatus, self.get_status_display()), user) % (oldstatus, self.get_status_display()), user)
def get_allowed_actions(self, user): def get_allowed_actions(self, user):
@ -432,10 +427,9 @@ class Motion(models.Model, SlideMixin):
allready a number allready a number
""" """
if self.number and not force: if self.number and not force:
raise NameError('The motion has already a number. ' \ raise NameError('The motion has already a number. '
'You can not delete it.') 'You can not delete it.')
for item in Item.objects.filter(related_sid=self.sid): for item in Item.objects.filter(related_sid=self.sid):
item.delete() item.delete()
super(Motion, self).delete() super(Motion, self).delete()
@ -501,12 +495,12 @@ class Motion(models.Model, SlideMixin):
for poll in self.polls: for poll in self.polls:
for option in poll.get_options(): for option in poll.get_options():
if option.get_votes().exists(): if option.get_votes().exists():
results.append((option['Yes'], option['No'], results.append((
option['Yes'], option['No'],
option['Abstain'], poll.print_votesinvalid(), option['Abstain'], poll.print_votesinvalid(),
poll.print_votescast())) poll.print_votescast()))
return results return results
def slide(self): def slide(self):
""" """
return the slide dict return the slide dict
@ -542,10 +536,10 @@ class Motion(models.Model, SlideMixin):
class AVersion(models.Model): class AVersion(models.Model):
title = models.CharField(max_length=100, verbose_name = _("Title")) title = models.CharField(max_length=100, verbose_name=_("Title"))
text = models.TextField(verbose_name = _("Text")) text = models.TextField(verbose_name=_("Text"))
reason = models.TextField(null=True, blank=True, verbose_name = _("Reason")) reason = models.TextField(null=True, blank=True, verbose_name=_("Reason"))
rejected = models.BooleanField() # = Not Permitted rejected = models.BooleanField() # = Not Permitted
time = models.DateTimeField(auto_now=True) time = models.DateTimeField(auto_now=True)
motion = models.ForeignKey(Motion) motion = models.ForeignKey(Motion)
@ -576,8 +570,8 @@ class MotionOption(BaseOption):
class MotionPoll(BasePoll, CountInvalid, CountVotesCast): class MotionPoll(BasePoll, CountInvalid, CountVotesCast):
option_class = MotionOption option_class = MotionOption
vote_values = [ugettext_noop('Yes'), ugettext_noop('No'), vote_values = [
ugettext_noop('Abstain')] ugettext_noop('Yes'), ugettext_noop('No'), ugettext_noop('Abstain')]
motion = models.ForeignKey(Motion) motion = models.ForeignKey(Motion)

View File

@ -8,7 +8,10 @@
<h1>{% trans "Import motions" %}</h1> <h1>{% trans "Import motions" %}</h1>
<p>{% trans 'Select a CSV file to import motions!' %}</p> <p>{% trans 'Select a CSV file to import motions!' %}</p>
<p>{% trans 'Required comma separated values: <code>{number, title, text, reason, first_name, last_name, is_group}</code> (<code>number</code>, <code>reason</code> and <code>is_group</code> are optional and may be empty)' %} <p>{% trans 'Required comma separated values' %}:
<code>({% trans 'number, title, text, reason, first_name, last_name, is_group' %})</code>
<br>
{% trans '<code>number</code>, <code>reason</code> and <code>is_group</code> are optional and may be empty' %}.
<br> <br>
{% trans 'Required CSV file encoding: UTF-8 (Unicode).' %} {% trans 'Required CSV file encoding: UTF-8 (Unicode).' %}
</p> </p>

View File

@ -18,19 +18,17 @@ import os
try: try:
from urlparse import parse_qs from urlparse import parse_qs
except ImportError: # python <= 2.5 except ImportError: # python <= 2.5
from cgi import parse_qs from cgi import parse_qs
from reportlab.lib import colors from reportlab.lib import colors
from reportlab.lib.units import cm from reportlab.lib.units import cm
from reportlab.platypus import (SimpleDocTemplate, PageBreak, Paragraph, Spacer, from reportlab.platypus import (
Table, TableStyle) SimpleDocTemplate, PageBreak, Paragraph, Spacer, Table, TableStyle)
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.core.context_processors import csrf
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db import transaction from django.db import transaction
from django.shortcuts import redirect from django.shortcuts import redirect
@ -39,26 +37,21 @@ from django.utils.translation import ugettext as _, ungettext
from openslides.utils import csv_ext from openslides.utils import csv_ext
from openslides.utils.pdf import stylesheet from openslides.utils.pdf import stylesheet
from openslides.utils.template import Tab from openslides.utils.template import Tab
from openslides.utils.utils import (template, permission_required, from openslides.utils.utils import (
del_confirm_form, gen_confirm_form) template, permission_required, del_confirm_form, gen_confirm_form)
from openslides.utils.views import (PDFView, RedirectView, DeleteView, from openslides.utils.views import (
FormView, SingleObjectMixin, QuestionMixin) PDFView, RedirectView, DeleteView, FormView, SingleObjectMixin,
QuestionMixin)
from openslides.utils.person import get_person from openslides.utils.person import get_person
from openslides.config.models import config from openslides.config.models import config
from openslides.projector.projector import Widget from openslides.projector.projector import Widget
from openslides.poll.views import PollFormView from openslides.poll.views import PollFormView
from openslides.participant.api import gen_username, gen_password from openslides.participant.api import gen_username, gen_password
from openslides.participant.models import User, Group from openslides.participant.models import User, Group
from openslides.agenda.models import Item from openslides.agenda.models import Item
from openslides.motion.models import Motion, AVersion, MotionPoll from openslides.motion.models import Motion, AVersion, MotionPoll
from openslides.motion.forms import (MotionForm, from openslides.motion.forms import (
MotionFormTrivialChanges, MotionManagerForm, MotionForm, MotionFormTrivialChanges, MotionManagerForm,
MotionManagerFormSupporter, MotionImportForm, ConfigForm) MotionManagerFormSupporter, MotionImportForm, ConfigForm)
@ -124,14 +117,14 @@ def overview(request):
for (i, motion) in enumerate(motions): for (i, motion) in enumerate(motions):
try: try:
motions[i] = { motions[i] = {
'actions' : motion.get_allowed_actions(request.user), 'actions': motion.get_allowed_actions(request.user),
'motion' : motion 'motion': motion
} }
except: except:
# todo: except what? # todo: except what?
motions[i] = { motions[i] = {
'actions' : [], 'actions': [],
'motion' : motion 'motion': motion
} }
return { return {
@ -210,12 +203,7 @@ def edit(request, motion_id=None):
managerform = None managerform = None
if valid: if valid:
del_supporters = True
if is_manager: if is_manager:
if motion: # Edit motion
original_supporters = list(motion.supporters)
else:
original_supporters = []
motion = managerform.save(commit=False) motion = managerform.save(commit=False)
elif motion_id is None: elif motion_id is None:
motion = Motion(submitter=request.user) motion = Motion(submitter=request.user)
@ -611,7 +599,7 @@ def motion_import(request):
except ValueError: except ValueError:
messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1)) messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1))
continue continue
if is_group: if is_group:
# fetch existing groups or issue an error message # fetch existing groups or issue an error message
try: try:
@ -694,7 +682,7 @@ def motion_import(request):
return redirect(reverse('motion_overview')) return redirect(reverse('motion_overview'))
except csv.Error: except csv.Error:
message.error(request, _('Import aborted because of severe errors in the input file.')) messages.error(request, _('Import aborted because of severe errors in the input file.'))
except UnicodeDecodeError: except UnicodeDecodeError:
messages.error(request, _('Import file has wrong character encoding, only UTF-8 is supported!')) messages.error(request, _('Import file has wrong character encoding, only UTF-8 is supported!'))
else: else:

View File

@ -14,15 +14,20 @@
from __future__ import with_statement from __future__ import with_statement
from random import choice from random import choice
import string
import csv import csv
from django.contrib.auth.models import User from django.contrib.auth.models import Permission
from django.db import transaction from django.db import transaction
from django.utils.translation import ugettext as _
from openslides.utils import csv_ext from openslides.utils import csv_ext
from openslides.participant.models import User from openslides.participant.models import User, Group
DEFAULT_PERMS = ['can_see_agenda', 'can_see_projector',
'can_see_motion', 'can_see_assignment',
'can_see_dashboard']
def gen_password(): def gen_password():
@ -73,7 +78,7 @@ def import_users(csv_file):
try: try:
(first_name, last_name, gender, structure_level, type, committee, comment) = line[:7] (first_name, last_name, gender, structure_level, type, committee, comment) = line[:7]
except ValueError: except ValueError:
error_messages.append(_('Ignoring malformed line %d in import file.') % line_no + 1) error_messages.append(_('Ignoring malformed line %d in import file.') % (line_no + 1))
continue continue
user = User() user = User()
user.last_name = last_name user.last_name = last_name
@ -93,3 +98,23 @@ def import_users(csv_file):
except UnicodeDecodeError: except UnicodeDecodeError:
error_messages.appen(_('Import file has wrong character encoding, only UTF-8 is supported!')) error_messages.appen(_('Import file has wrong character encoding, only UTF-8 is supported!'))
return (count_success, error_messages) return (count_success, error_messages)
def get_or_create_registered_group():
registered, created = Group.objects.get_or_create(
name__iexact='Registered', defaults={'name': 'Registered'})
if created:
registered.permissions = Permission.objects.filter(
codename__in=DEFAULT_PERMS)
registered.save()
return registered
def get_or_create_anonymous_group():
anonymous, created = Group.objects.get_or_create(
name__iexact='Anonymous', defaults={'name': 'Anonymous'})
if created:
anonymous.permissions = Permission.objects.filter(
codename__in=DEFAULT_PERMS)
anonymous.save()
return anonymous

View File

@ -18,6 +18,7 @@ from openslides.utils.forms import (
CssClassMixin, LocalizedModelMultipleChoiceField) CssClassMixin, LocalizedModelMultipleChoiceField)
from openslides.participant.models import User, Group from openslides.participant.models import User, Group
from openslides.participant.api import get_or_create_registered_group
class UserCreateForm(forms.ModelForm, CssClassMixin): class UserCreateForm(forms.ModelForm, CssClassMixin):
@ -25,6 +26,13 @@ class UserCreateForm(forms.ModelForm, CssClassMixin):
queryset=Group.objects.exclude(name__iexact='anonymous'), queryset=Group.objects.exclude(name__iexact='anonymous'),
label=_("Groups"), required=False) label=_("Groups"), required=False)
def __init__(self, *args, **kwargs):
if kwargs.get('instance', None) is None:
initial = kwargs.setdefault('initial', {})
registered = get_or_create_registered_group()
initial['groups'] = [registered.pk]
super(UserCreateForm, self).__init__(*args, **kwargs)
class Meta: class Meta:
model = User model = User
fields = ('first_name', 'last_name', 'is_active', 'groups', 'structure_level', fields = ('first_name', 'last_name', 'is_active', 'groups', 'structure_level',
@ -58,12 +66,13 @@ class GroupForm(forms.ModelForm, CssClassMixin):
instance = forms.ModelForm.save(self, False) instance = forms.ModelForm.save(self, False)
old_save_m2m = self.save_m2m old_save_m2m = self.save_m2m
def save_m2m():
old_save_m2m()
instance.user_set.clear() def save_m2m():
for user in self.cleaned_data['users']: old_save_m2m()
instance.user_set.add(user)
instance.user_set.clear()
for user in self.cleaned_data['users']:
instance.user_set.add(user)
self.save_m2m = save_m2m self.save_m2m = save_m2m
if commit: if commit:
@ -76,13 +85,13 @@ class GroupForm(forms.ModelForm, CssClassMixin):
# Do not allow to change the name "anonymous" or give another group # Do not allow to change the name "anonymous" or give another group
# this name # this name
data = self.cleaned_data['name'] data = self.cleaned_data['name']
if self.instance.name.lower() == 'anonymous': if self.instance.name.lower() in ['anonymous', 'registered']:
# Editing the anonymous-user # Editing the anonymous-user
if self.instance.name.lower() != data.lower(): if self.instance.name.lower() != data.lower():
raise forms.ValidationError( raise forms.ValidationError(
_('You can not edit the name for the anonymous user')) _('You can not edit the name for this group.'))
else: else:
if data.lower() == 'anonymous': if data.lower() in ['anonymous', 'registered']:
raise forms.ValidationError( raise forms.ValidationError(
_('Group name "%s" is reserved for internal use.') % data) _('Group name "%s" is reserved for internal use.') % data)
return data return data
@ -94,7 +103,7 @@ class GroupForm(forms.ModelForm, CssClassMixin):
class UsersettingsForm(forms.ModelForm, CssClassMixin): class UsersettingsForm(forms.ModelForm, CssClassMixin):
class Meta: class Meta:
model = User model = User
fields = ('username', 'first_name', 'last_name', 'gender', 'email', 'committee', 'about_me' ) fields = ('username', 'first_name', 'last_name', 'gender', 'email', 'committee', 'about_me')
class UserImportForm(forms.Form, CssClassMixin): class UserImportForm(forms.Form, CssClassMixin):

View File

@ -25,8 +25,9 @@ from openslides.config.signals import default_config_value
from openslides.projector.api import register_slidemodel from openslides.projector.api import register_slidemodel
from openslides.projector.projector import SlideMixin from openslides.projector.projector import SlideMixin
class User(DjangoUser, PersonMixin, Person, SlideMixin): class User(DjangoUser, PersonMixin, Person, SlideMixin):
prefix = 'user' # This is for the slides prefix = 'user' # This is for the slides
person_prefix = 'user' person_prefix = 'user'
GENDER_CHOICES = ( GENDER_CHOICES = (
('male', _('Male')), ('male', _('Male')),
@ -131,18 +132,21 @@ class User(DjangoUser, PersonMixin, Person, SlideMixin):
register_slidemodel(User) register_slidemodel(User)
class Group(DjangoGroup, PersonMixin, Person, SlideMixin): class Group(DjangoGroup, PersonMixin, Person, SlideMixin):
prefix = 'group' # This is for the slides prefix = 'group' # This is for the slides
person_prefix = 'group' person_prefix = 'group'
django_group = models.OneToOneField(DjangoGroup, editable=False, parent_link=True) django_group = models.OneToOneField(DjangoGroup, editable=False, parent_link=True)
group_as_person = models.BooleanField(default=False, verbose_name=_("Use this group as participant"), help_text=_('For example as submitter of a motion.')) 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")) description = models.TextField(blank=True, verbose_name=_("Description"))
@models.permalink @models.permalink
def get_absolute_url(self, link='view'): def get_absolute_url(self, link='view'):
""" """
Return the URL to this user. Return the URL to this user group.
link can be: link can be:
* view * view
@ -173,6 +177,7 @@ class Group(DjangoGroup, PersonMixin, Person, SlideMixin):
register_slidemodel(Group) register_slidemodel(Group)
class UsersAndGroupsToPersons(object): class UsersAndGroupsToPersons(object):
""" """
Object to send all Users and Groups or a special User or Group to Object to send all Users and Groups or a special User or Group to
@ -216,8 +221,9 @@ def receive_persons(sender, **kwargs):
""" """
Answers to the Person-API Answers to the Person-API
""" """
return UsersAndGroupsToPersons(person_prefix_filter=kwargs['person_prefix_filter'], return UsersAndGroupsToPersons(
id_filter=kwargs['id_filter']) person_prefix_filter=kwargs['person_prefix_filter'],
id_filter=kwargs['id_filter'])
@receiver(default_config_value, dispatch_uid="participant_default_config") @receiver(default_config_value, dispatch_uid="participant_default_config")
@ -234,7 +240,7 @@ def default_config(sender, key, **kwargs):
@receiver(signals.post_save, sender=DjangoUser) @receiver(signals.post_save, sender=DjangoUser)
def user_post_save(sender, instance, signal, *args, **kwargs): def djangouser_post_save(sender, instance, signal, *args, **kwargs):
try: try:
instance.user instance.user
except User.DoesNotExist: except User.DoesNotExist:
@ -242,8 +248,18 @@ def user_post_save(sender, instance, signal, *args, **kwargs):
@receiver(signals.post_save, sender=DjangoGroup) @receiver(signals.post_save, sender=DjangoGroup)
def group_post_save(sender, instance, signal, *args, **kwargs): def djangogroup_post_save(sender, instance, signal, *args, **kwargs):
try: try:
instance.group instance.group
except Group.DoesNotExist: except Group.DoesNotExist:
Group(django_group=instance).save_base(raw=True) Group(django_group=instance).save_base(raw=True)
@receiver(signals.post_save, sender=User)
def user_post_save(sender, instance, *args, **kwargs):
from openslides.participant.api import get_or_create_registered_group
if not kwargs['created']:
return
registered = get_or_create_registered_group()
instance.groups.add(registered)
instance.save()

View File

@ -66,7 +66,7 @@
</li> </li>
{# delete group #} {# delete group #}
{% if group.name != 'Anonymous' %} {% if group.name|lower != 'anonymous' and group.name|lower != 'registered' %}
<li> <li>
<a href="{% model_url group 'delete' %}"><img src="{% static 'images/icons/delete.png' %}"> {% trans 'Delete group' %}</a> <a href="{% model_url group 'delete' %}"><img src="{% static 'images/icons/delete.png' %}"> {% trans 'Delete group' %}</a>
</li> </li>

View File

@ -17,7 +17,7 @@
<tr class="{% cycle '' 'odd' %}"> <tr class="{% cycle '' 'odd' %}">
<td><a href="{% model_url group 'view' %}">{{ group.name }}</a></td> <td><a href="{% model_url group 'view' %}">{{ group.name }}</a></td>
<td><a href="{% url user_group_edit group.id %}"><img src="{% static 'images/icons/edit.png' %}" title="{% trans 'Edit group' %}"></a> <td><a href="{% url user_group_edit group.id %}"><img src="{% static 'images/icons/edit.png' %}" title="{% trans 'Edit group' %}"></a>
{% if group.name|lower != 'anonymous' %} {% if group.name|lower != 'anonymous' and group.name|lower != 'registered' %}
<a href="{% url user_group_delete group.id %}"><img src="{% static 'images/icons/delete.png' %}" title="{% trans 'Delete group' %}"></a> <a href="{% url user_group_delete group.id %}"><img src="{% static 'images/icons/delete.png' %}" title="{% trans 'Delete group' %}"></a>
{% endif %} {% endif %}
</td> </td>

View File

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

View File

@ -8,17 +8,15 @@
{% block content %} {% block content %}
<div class="item_fullscreen">{{ group }} <div class="item_fullscreen">{{ group }}
<span> <span>
<p><i>{{ group.user_set.all.count }} {% trans "participants" %}:</i></p> <p><i>{{ group.user_set.all.count }} {% trans "participants" %}</i></p>
<p> {% comment %}
{% if group.user_set.all %} TODO: print fullname (not username) of all group users [see #420]
{{ group.user_set.all|join:", " }} <p>
{% endif %} {% if group.user_set.all %}
</p> {{ group.user_set.all|join:", " }}
{% endif %}
</p>
{% endcomment %}
</span> </span>
</div> </div>
{% endblock %}
{% block scrollcontent %}
{% endblock %} {% endblock %}

View File

@ -11,7 +11,6 @@
""" """
from django.conf.urls.defaults import url, patterns from django.conf.urls.defaults import url, patterns
from django.core.urlresolvers import reverse
from openslides.participant.views import ( from openslides.participant.views import (
UserOverview, UserCreateView, UserDetailView, UserUpdateView, UserOverview, UserCreateView, UserDetailView, UserUpdateView,

View File

@ -28,7 +28,6 @@ from reportlab.platypus import (
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import PasswordChangeForm from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth.models import User
from django.contrib.auth.views import login as django_login from django.contrib.auth.views import login as django_login
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.shortcuts import redirect from django.shortcuts import redirect
@ -39,16 +38,12 @@ from openslides.utils.template import Tab
from openslides.utils.utils import ( from openslides.utils.utils import (
template, decodedict, encodedict, delete_default_permissions, html_strong) template, decodedict, encodedict, delete_default_permissions, html_strong)
from openslides.utils.views import ( from openslides.utils.views import (
FormView, PDFView, CreateView, UpdateView, DeleteView, FormView, PDFView, CreateView, UpdateView, DeleteView, PermissionMixin,
RedirectView, SingleObjectMixin, ListView, QuestionMixin) RedirectView, SingleObjectMixin, ListView, QuestionMixin, DetailView)
from openslides.config.models import config from openslides.config.models import config
from openslides.projector.projector import Widget from openslides.projector.projector import Widget
from openslides.motion.models import Motion from openslides.motion.models import Motion
from openslides.assignment.models import Assignment from openslides.assignment.models import Assignment
from openslides.participant.api import gen_username, gen_password, import_users from openslides.participant.api import gen_username, gen_password, import_users
from openslides.participant.forms import ( from openslides.participant.forms import (
UserCreateForm, UserUpdateForm, UsersettingsForm, UserCreateForm, UserUpdateForm, UsersettingsForm,
@ -125,11 +120,13 @@ class UserOverview(ListView):
percent = 0 percent = 0
# list of all existing categories # list of all existing categories
structure_levels = [p['structure_level'] for p in User.objects.values('structure_level') structure_levels = [
.exclude(structure_level='').distinct()] p['structure_level'] for p in
User.objects.values('structure_level').exclude(structure_level='').distinct()]
# list of all existing committees # list of all existing committees
committees = [p['committee'] for p in User.objects.values('committee') committees = [
.exclude(committee='').distinct()] p['committee'] for p in
User.objects.values('committee').exclude(committee='').distinct()]
# context vars # context vars
context.update({ context.update({
'allusers': all_users, 'allusers': all_users,
@ -137,13 +134,13 @@ class UserOverview(ListView):
'percent': round(percent, 1), 'percent': round(percent, 1),
'structure_levels': structure_levels, 'structure_levels': structure_levels,
'committees': committees, 'committees': committees,
'cookie': ['participant_sortfilter', urlencode(decodedict(self.sortfilter), 'cookie': [
'participant_sortfilter', urlencode(decodedict(self.sortfilter),
doseq=True)], doseq=True)],
'sortfilter': self.sortfilter}) 'sortfilter': self.sortfilter})
return context return context
from openslides.utils.views import DetailView, PermissionMixin
class UserDetailView(DetailView, PermissionMixin): class UserDetailView(DetailView, PermissionMixin):
""" """
Classed based view to show a specific user in the interface. Classed based view to show a specific user in the interface.
@ -177,8 +174,8 @@ class UserCreateView(CreateView):
apply_url = 'user_edit' apply_url = 'user_edit'
def manipulate_object(self, form): def manipulate_object(self, form):
self.object.username = gen_username(form.cleaned_data['first_name'], self.object.username = gen_username(
form.cleaned_data['last_name']) form.cleaned_data['first_name'], form.cleaned_data['last_name'])
if not self.object.default_password: if not self.object.default_password:
self.object.default_password = gen_password() self.object.default_password = gen_password()
self.object.set_password(self.object.default_password) self.object.set_password(self.object.default_password)
@ -211,6 +208,7 @@ class UserDeleteView(DeleteView):
else: else:
super(UserDeleteView, self).pre_redirect(request, *args, **kwargs) super(UserDeleteView, self).pre_redirect(request, *args, **kwargs)
class SetUserStatusView(RedirectView, SingleObjectMixin): class SetUserStatusView(RedirectView, SingleObjectMixin):
""" """
Activate or deactivate an user. Activate or deactivate an user.
@ -443,7 +441,7 @@ class GroupDeleteView(DeleteView):
url = 'user_group_overview' url = 'user_group_overview'
def pre_redirect(self, request, *args, **kwargs): def pre_redirect(self, request, *args, **kwargs):
if self.get_object().name.lower() == 'anonymous': if self.get_object().name.lower() in ['anonymous', 'registered']:
messages.error(request, _("You can not delete this Group.")) messages.error(request, _("You can not delete this Group."))
else: else:
super(GroupDeleteView, self).pre_redirect(request, *args, **kwargs) super(GroupDeleteView, self).pre_redirect(request, *args, **kwargs)
@ -547,8 +545,9 @@ def register_tab(request):
return Tab( return Tab(
title=_('Participants'), title=_('Participants'),
url=reverse('user_overview'), url=reverse('user_overview'),
permission=request.user.has_perm('participant.can_see_participant') or permission=(
request.user.has_perm('participant.can_manage_participant'), request.user.has_perm('participant.can_see_participant') or
request.user.has_perm('participant.can_manage_participant')),
selected=selected) selected=selected)
@ -569,12 +568,12 @@ def get_personal_info_widget(request):
and where you are supporter or candidate. and where you are supporter or candidate.
""" """
personal_info_context = { personal_info_context = {
'submitted_motions': Motion.objects.filter(submitter=request.user), 'submitted_motions': Motion.objects.filter(submitter=request.user),
'config_motion_min_supporters': config['motion_min_supporters'], 'config_motion_min_supporters': config['motion_min_supporters'],
'supported_motions': Motion.objects.filter(motionsupporter=request.user), 'supported_motions': Motion.objects.filter(motionsupporter=request.user),
'assignments': Assignment.objects.filter( 'assignments': Assignment.objects.filter(
assignmentcandidate__person=request.user, assignmentcandidate__person=request.user,
assignmentcandidate__blocked=False),} assignmentcandidate__blocked=False)}
return Widget( return Widget(
name='personal_info', name='personal_info',
display_name=_('My motions and elections'), display_name=_('My motions and elections'),
@ -593,7 +592,7 @@ def get_user_widget(request):
name='user', name='user',
display_name=_('Participants'), display_name=_('Participants'),
template='participant/user_widget.html', template='participant/user_widget.html',
context={'users': User.objects.all(),}, context={'users': User.objects.all()},
permission_required='projector.can_manage_projector', permission_required='projector.can_manage_projector',
default_column=1) default_column=1)
@ -607,6 +606,6 @@ def get_group_widget(request):
name='group', name='group',
display_name=_('Groups'), display_name=_('Groups'),
template='participant/group_widget.html', template='participant/group_widget.html',
context={'groups': Group.objects.all(),}, context={'groups': Group.objects.all()},
permission_required='projector.can_manage_projector', permission_required='projector.can_manage_projector',
default_column=1) default_column=1)

View File

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

View File

@ -47,7 +47,7 @@ class BaseVote(models.Model):
Subclasses have to define a option-field, which are a subclass of Subclasses have to define a option-field, which are a subclass of
BaseOption. 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) value = models.CharField(max_length=255, null=True)
def print_weight(self, raw=False): def print_weight(self, raw=False):
@ -73,7 +73,7 @@ class BaseVote(models.Model):
class CountVotesCast(models.Model): class CountVotesCast(models.Model):
votescast = MinMaxIntegerField(null=True, blank=True, min_value=-2, votescast = MinMaxIntegerField(null=True, blank=True, min_value=-2,
verbose_name=_("Votes cast")) verbose_name=_("Votes cast"))
def append_pollform_fields(self, fields): def append_pollform_fields(self, fields):
fields.append('votescast') fields.append('votescast')
@ -92,7 +92,7 @@ class CountVotesCast(models.Model):
class CountInvalid(models.Model): class CountInvalid(models.Model):
votesinvalid = MinMaxIntegerField(null=True, blank=True, min_value=-2, votesinvalid = MinMaxIntegerField(null=True, blank=True, min_value=-2,
verbose_name=_("Votes invalid")) verbose_name=_("Votes invalid"))
def append_pollform_fields(self, fields): def append_pollform_fields(self, fields):
fields.append('votesinvalid') fields.append('votesinvalid')
@ -164,7 +164,6 @@ class BasePoll(models.Model):
""" """
return self.vote_values return self.vote_values
def get_vote_class(self): def get_vote_class(self):
""" """
Return the releatet vote class. Return the releatet vote class.
@ -212,7 +211,7 @@ class BasePoll(models.Model):
""" """
from openslides.poll.forms import OptionForm from openslides.poll.forms import OptionForm
return OptionForm(extra=self.get_form_values(kwargs['formid']), return OptionForm(extra=self.get_form_values(kwargs['formid']),
**kwargs) **kwargs)
def get_vote_forms(self, **kwargs): def get_vote_forms(self, **kwargs):
""" """

View File

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

View File

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

View File

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

View File

@ -19,9 +19,6 @@ from openslides.config.signals import default_config_value
from openslides.projector.api import register_slidemodel from openslides.projector.api import register_slidemodel
from openslides.projector.projector import SlideMixin from openslides.projector.projector import SlideMixin
from openslides.config.models import config
class ProjectorSlide(models.Model, SlideMixin): class ProjectorSlide(models.Model, SlideMixin):
""" """
@ -56,8 +53,7 @@ class ProjectorSlide(models.Model, SlideMixin):
) )
register_slidemodel(ProjectorSlide, register_slidemodel(ProjectorSlide, control_template='projector/control_customslide.html')
control_template='projector/control_customslide.html')
class ProjectorOverlay(models.Model): class ProjectorOverlay(models.Model):

View File

@ -19,9 +19,9 @@ from openslides.config.models import config
from openslides.projector.signals import projector_overlays from openslides.projector.signals import projector_overlays
SLIDE = {} SLIDE = {}
class SlideMixin(object): class SlideMixin(object):
""" """
A Mixin for a Django-Model, for making the model a slide. A Mixin for a Django-Model, for making the model a slide.
@ -49,13 +49,16 @@ class SlideMixin(object):
""" """
Return True, if the the slide is the active slide. Return True, if the the slide is the active slide.
""" """
from api import get_active_slide if self.id is None:
return False
from openslides.projector.api import get_active_slide
return get_active_slide(only_sid=True) == self.sid return get_active_slide(only_sid=True) == self.sid
def set_active(self): def set_active(self):
""" """
Appoint this item as the active slide. Appoint this item as the active slide.
""" """
from openslides.projector.api import set_active_slide
set_active_slide(self.sid) set_active_slide(self.sid)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
@ -112,7 +115,7 @@ class Widget(object):
Class for a Widget for the Projector-Tab. Class for a Widget for the Projector-Tab.
""" """
def __init__(self, name, html=None, template=None, context={}, 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 self.name = name
if display_name is None: if display_name is None:
self.display_name = name.capitalize() self.display_name = name.capitalize()

View File

@ -13,34 +13,28 @@
from datetime import datetime from datetime import datetime
from time import time from time import time
from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.core.cache import cache from django.core.cache import cache
from django.core.context_processors import csrf from django.core.context_processors import csrf
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db import transaction from django.db import transaction
from django.db.models import Q from django.db.models import Q
from django.dispatch import receiver
from django.shortcuts import redirect from django.shortcuts import redirect
from django.template import RequestContext from django.template import RequestContext
from django.utils.datastructures import SortedDict
from django.utils.importlib import import_module
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from openslides.utils.template import render_block_to_string, Tab from openslides.utils.template import render_block_to_string, Tab
from openslides.utils.utils import html_strong from openslides.utils.views import (
from openslides.utils.views import (TemplateView, RedirectView, CreateView, TemplateView, RedirectView, CreateView, UpdateView, DeleteView, AjaxMixin)
UpdateView, DeleteView, AjaxMixin)
from openslides.config.models import config from openslides.config.models import config
from .api import (
from openslides.projector.api import (get_active_slide, set_active_slide, get_active_slide, set_active_slide, projector_message_set,
projector_message_set, projector_message_delete, get_slide_from_sid, projector_message_delete, get_slide_from_sid, get_all_widgets,
get_all_widgets, clear_projector_cache) clear_projector_cache)
from openslides.projector.forms import SelectWidgetsForm from .forms import SelectWidgetsForm
from openslides.projector.models import ProjectorOverlay, ProjectorSlide from .models import ProjectorOverlay, ProjectorSlide
from openslides.projector.projector import SLIDE, Widget from .projector import Widget
from openslides.projector.signals import projector_overlays from .signals import projector_overlays
class DashboardView(TemplateView, AjaxMixin): class DashboardView(TemplateView, AjaxMixin):
@ -73,7 +67,7 @@ class Projector(TemplateView, AjaxMixin):
if sid is None: if sid is None:
try: try:
data = get_active_slide() data = get_active_slide()
except AttributeError: #TODO: It has to be an Slide.DoesNotExist except AttributeError: # TODO: It has to be an Slide.DoesNotExist
data = None data = None
ajax = 'on' ajax = 'on'
active_sid = get_active_slide(True) active_sid = get_active_slide(True)
@ -92,10 +86,10 @@ class Projector(TemplateView, AjaxMixin):
# Projector Overlays # Projector Overlays
if self.kwargs['sid'] is None: if self.kwargs['sid'] is None:
active_defs = ProjectorOverlay.objects.filter(active=True) \ active_defs = ProjectorOverlay.objects.filter(active=True) \
.filter(Q(sid=active_sid) | Q(sid=None)).values_list('def_name', .filter(Q(sid=active_sid) | Q(sid=None)).values_list(
flat=True) 'def_name', flat=True)
for receiver, response in projector_overlays.send(sender=sid, for receiver, response in projector_overlays.send(
register=False, call=active_defs): sender=sid, register=False, call=active_defs):
if response is not None: if response is not None:
data['overlays'].append(response) data['overlays'].append(response)
self._data = data self._data = data
@ -124,7 +118,6 @@ class Projector(TemplateView, AjaxMixin):
'scrollcontent', self.data) 'scrollcontent', self.data)
cache.set('projector_scrollcontent', scrollcontent, 1) cache.set('projector_scrollcontent', scrollcontent, 1)
# TODO: do not call the hole data-methode, if we only need some vars # TODO: do not call the hole data-methode, if we only need some vars
data = cache.get('projector_data') data = cache.get('projector_data')
if not data: if not data:
@ -301,7 +294,6 @@ class OverlayMessageView(RedirectView):
elif 'message-clean' in request.POST: elif 'message-clean' in request.POST:
projector_message_delete() projector_message_delete()
def get_ajax_context(self, **kwargs): def get_ajax_context(self, **kwargs):
clear_projector_cache() clear_projector_cache()
return { return {
@ -309,7 +301,6 @@ class OverlayMessageView(RedirectView):
} }
class ActivateOverlay(RedirectView): class ActivateOverlay(RedirectView):
""" """
Activate or deactivate an overlay. Activate or deactivate an overlay.
@ -379,12 +370,11 @@ def register_tab(request):
""" """
Register the projector tab. Register the projector tab.
""" """
selected = True if request.path.startswith('/projector/') else False selected = request.path.startswith('/projector/')
return Tab( return Tab(
title=_('Dashboard'), title=_('Dashboard'),
url=reverse('dashboard'), url=reverse('dashboard'),
permission=request.user.has_perm('projector.can_manage_projector') or permission=request.user.has_perm('projector.can_see_dashboard'),
request.user.has_perm('projector.can_see_dashboard'),
selected=selected, selected=selected,
) )
@ -411,7 +401,7 @@ def get_widgets(request):
name='live_view', name='live_view',
display_name=_('Projector live view'), display_name=_('Projector live view'),
template='projector/live_view_widget.html', template='projector/live_view_widget.html',
context = RequestContext(request, {}), context=RequestContext(request, {}),
permission_required='projector.can_see_projector', permission_required='projector.can_see_projector',
default_column=2)) default_column=2))
@ -424,15 +414,14 @@ def get_widgets(request):
projector_overlay = ProjectorOverlay.objects.get( projector_overlay = ProjectorOverlay.objects.get(
def_name=name) def_name=name)
except ProjectorOverlay.DoesNotExist: except ProjectorOverlay.DoesNotExist:
projector_overlay = ProjectorOverlay(def_name=name, projector_overlay = ProjectorOverlay(def_name=name, active=False)
active=False)
projector_overlay.save() projector_overlay.save()
overlays.append(projector_overlay) overlays.append(projector_overlay)
context = { context = {
'overlays':overlays, 'overlays': overlays,
'countdown_time': config['countdown_time'], 'countdown_time': config['countdown_time'],
'countdown_state' : config['countdown_state']} 'countdown_state': config['countdown_state']}
context.update(csrf(request)) context.update(csrf(request))
widgets.append(Widget( widgets.append(Widget(
name='overlays', name='overlays',
@ -442,7 +431,6 @@ def get_widgets(request):
default_column=2, default_column=2,
context=context)) context=context))
# Custom slide widget # Custom slide widget
context = { context = {
'slides': ProjectorSlide.objects.all().order_by('weight'), 'slides': ProjectorSlide.objects.all().order_by('weight'),

View File

@ -12,8 +12,6 @@
from django.conf import settings from django.conf import settings
from django.conf.urls.defaults import patterns, url, include from django.conf.urls.defaults import patterns, url, include
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.shortcuts import redirect
from django.utils.importlib import import_module from django.utils.importlib import import_module
from openslides.utils.views import RedirectView from openslides.utils.views import RedirectView
@ -34,7 +32,7 @@ urlpatterns = patterns('',
) )
urlpatterns += patterns('django.contrib.staticfiles.views', urlpatterns += patterns('django.contrib.staticfiles.views',
url(r'^static/(?P<path>.*)$', 'serve', {'insecure':True}), url(r'^static/(?P<path>.*)$', 'serve', {'insecure': True}),
) )
js_info_dict = { js_info_dict = {

View File

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

View File

@ -25,10 +25,9 @@ class excel_semikolon(Dialect):
def patchup(dialect): def patchup(dialect):
if dialect: if dialect:
if dialect.delimiter in [excel_semikolon.delimiter, excel.delimiter] and \ 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 # walks like a duck and talks like a duck.. must be one
dialect.doublequote = True dialect.doublequote = True
return dialect return dialect
register_dialect("excel_semikolon", excel_semikolon) 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.fields import Field
from django.forms.util import ValidationError as FormValidationError from django.forms.util import ValidationError as FormValidationError
class JSONFormField(Field): class JSONFormField(Field):
def clean(self, value): def clean(self, value):
@ -21,6 +22,7 @@ class JSONFormField(Field):
raise FormValidationError(_("Enter valid JSON")) raise FormValidationError(_("Enter valid JSON"))
return value return value
class JSONField(models.TextField): class JSONField(models.TextField):
"""JSONField is a generic textfield that serializes/unserializes JSON objects""" """JSONField is a generic textfield that serializes/unserializes JSON objects"""

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 from django.db import models
class MinMaxIntegerField(models.IntegerField): class MinMaxIntegerField(models.IntegerField):
def __init__(self, min_value=None, max_value=None, *args, **kwargs): def __init__(self, min_value=None, max_value=None, *args, **kwargs):
self.min_value, self.max_value = min_value, max_value self.min_value, self.max_value = min_value, max_value
super(MinMaxIntegerField, self).__init__(*args, **kwargs) super(MinMaxIntegerField, self).__init__(*args, **kwargs)
def formfield(self, **kwargs): def formfield(self, **kwargs):
defaults = {'min_value': self.min_value, 'max_value' : self.max_value} defaults = {'min_value': self.min_value, 'max_value': self.max_value}
defaults.update(kwargs) defaults.update(kwargs)
return super(MinMaxIntegerField, self).formfield(**defaults) return super(MinMaxIntegerField, self).formfield(**defaults)

View File

@ -28,16 +28,16 @@ from openslides.config.models import config
# register new truetype fonts # register new truetype fonts
pdfmetrics.registerFont(TTFont('Ubuntu', path_join(settings.SITE_ROOT, pdfmetrics.registerFont(TTFont(
'static/fonts/Ubuntu-R.ttf'))) 'Ubuntu', path_join(settings.SITE_ROOT, 'static/fonts/Ubuntu-R.ttf')))
pdfmetrics.registerFont(TTFont('Ubuntu-Bold', path_join(settings.SITE_ROOT, pdfmetrics.registerFont(TTFont(
'static/fonts/Ubuntu-B.ttf'))) 'Ubuntu-Bold', path_join(settings.SITE_ROOT, 'static/fonts/Ubuntu-B.ttf')))
pdfmetrics.registerFont(TTFont('Ubuntu-Italic', path_join(settings.SITE_ROOT, pdfmetrics.registerFont(TTFont(
'static/fonts/Ubuntu-RI.ttf'))) 'Ubuntu-Italic', path_join(settings.SITE_ROOT, 'static/fonts/Ubuntu-RI.ttf')))
# set style information # set style information
PAGE_HEIGHT = defaultPageSize[1]; PAGE_HEIGHT = defaultPageSize[1]
PAGE_WIDTH = defaultPageSize[0] PAGE_WIDTH = defaultPageSize[0]
@ -105,104 +105,104 @@ stylesheet.add(ParagraphStyle(
leftIndent=0, leftIndent=0,
spaceAfter=15, spaceAfter=15,
)) ))
stylesheet.add(ParagraphStyle(name = 'Subitem', stylesheet.add(ParagraphStyle(
parent = stylesheet['Normal'], name='Subitem',
fontSize = 10, parent=stylesheet['Normal'],
leading = 10, fontSize=10,
leftIndent = 20, leading=10,
spaceAfter = 15) leftIndent=20,
) spaceAfter=15))
stylesheet.add(ParagraphStyle(name = 'Tablecell', stylesheet.add(ParagraphStyle(
parent = stylesheet['Normal'], name='Tablecell',
fontSize = 9) parent=stylesheet['Normal'],
) fontSize=9))
stylesheet.add(ParagraphStyle(name = 'Signaturefield', stylesheet.add(ParagraphStyle(name='Signaturefield',
parent = stylesheet['Normal'], parent=stylesheet['Normal'],
spaceBefore = 15) spaceBefore=15)
) )
# Ballot stylesheets # Ballot stylesheets
stylesheet.add(ParagraphStyle(name = 'Ballot_title', stylesheet.add(ParagraphStyle(name='Ballot_title',
parent = stylesheet['Bold'], parent=stylesheet['Bold'],
fontSize = 12, fontSize=12,
leading = 14, leading=14,
leftIndent = 30), leftIndent=30),
) )
stylesheet.add(ParagraphStyle(name = 'Ballot_subtitle', stylesheet.add(ParagraphStyle(name='Ballot_subtitle',
parent = stylesheet['Normal'], parent=stylesheet['Normal'],
fontSize = 10, fontSize=10,
leading = 12, leading=12,
leftIndent = 30, leftIndent=30,
rightIndent = 20, rightIndent=20,
spaceAfter = 5), spaceAfter=5),
) )
stylesheet.add(ParagraphStyle(name = 'Ballot_description', stylesheet.add(ParagraphStyle(name='Ballot_description',
parent = stylesheet['Normal'], parent=stylesheet['Normal'],
fontSize = 7, fontSize=7,
leading = 10, leading=10,
leftIndent = 30), leftIndent=30),
) )
stylesheet.add(ParagraphStyle(name = 'Ballot_option', stylesheet.add(ParagraphStyle(name='Ballot_option',
parent = stylesheet['Normal'], parent=stylesheet['Normal'],
fontSize = 12, fontSize=12,
leading = 24, leading=24,
leftIndent = 30), leftIndent=30),
) )
stylesheet.add(ParagraphStyle(name = 'Monotype', stylesheet.add(ParagraphStyle(name='Monotype',
parent = stylesheet['Normal'], parent=stylesheet['Normal'],
fontName = 'Courier', fontName='Courier',
fontSize = 12, fontSize=12,
leading = 24, leading=24,
leftIndent = 30), leftIndent=30),
) )
stylesheet.add(ParagraphStyle(name = 'Ballot_option_name', stylesheet.add(ParagraphStyle(name='Ballot_option_name',
parent = stylesheet['Normal'], parent=stylesheet['Normal'],
fontSize = 12, fontSize=12,
leading = 15, leading=15,
leftIndent = 30), leftIndent=30),
) )
stylesheet.add(ParagraphStyle(name = 'Ballot_option_group', stylesheet.add(ParagraphStyle(name='Ballot_option_group',
parent = stylesheet['Normal'], parent=stylesheet['Normal'],
fontSize = 8, fontSize=8,
leading = 15, leading=15,
leftIndent = 30), leftIndent=30),
) )
stylesheet.add(ParagraphStyle(name = 'Ballot_option_YNA', stylesheet.add(ParagraphStyle(name='Ballot_option_YNA',
parent = stylesheet['Normal'], parent=stylesheet['Normal'],
fontSize = 12, fontSize=12,
leading = 15, leading=15,
leftIndent = 49, leftIndent=49,
spaceAfter = 18), spaceAfter=18),
) )
stylesheet.add(ParagraphStyle(name = 'Ballot_option_group_right', stylesheet.add(ParagraphStyle(name='Ballot_option_group_right',
parent = stylesheet['Normal'], parent=stylesheet['Normal'],
fontSize = 8, fontSize=8,
leading = 16, leading=16,
leftIndent = 49), leftIndent=49),
) )
stylesheet.add(ParagraphStyle(name = 'Badge_title', stylesheet.add(ParagraphStyle(name='Badge_title',
parent = stylesheet['Bold'], parent=stylesheet['Bold'],
fontSize = 16, fontSize=16,
leading = 22, leading=22,
leftIndent = 30), leftIndent=30),
) )
stylesheet.add(ParagraphStyle(name = 'Badge_subtitle', stylesheet.add(ParagraphStyle(name='Badge_subtitle',
parent = stylesheet['Normal'], parent=stylesheet['Normal'],
fontSize = 12, fontSize=12,
leading = 24, leading=24,
leftIndent = 30), leftIndent=30),
) )
stylesheet.add(ParagraphStyle( stylesheet.add(ParagraphStyle(
name = 'Badge_italic', name='Badge_italic',
parent = stylesheet['Italic'], parent=stylesheet['Italic'],
fontSize = 12, fontSize=12,
leading = 24, leading=24,
leftIndent = 30, leftIndent=30,
)) ))
stylesheet.add(ParagraphStyle( stylesheet.add(ParagraphStyle(
name = 'Badge_qrcode', name='Badge_qrcode',
fontSize = 12, fontSize=12,
leftIndent = 190, leftIndent=190,
)) ))
@ -213,13 +213,13 @@ def firstPage(canvas, doc):
canvas.setFillGray(0.4) canvas.setFillGray(0.4)
title_line = u"%s | %s" % (config["event_name"], title_line = u"%s | %s" % (config["event_name"],
config["event_description"]) config["event_description"])
if len(title_line) > 75: if len(title_line) > 75:
title_line = "%s ..." % title_line[:70] title_line = "%s ..." % title_line[:70]
canvas.drawString(2.75 * cm, 28 * cm, title_line) canvas.drawString(2.75 * cm, 28 * cm, title_line)
if config["event_date"] and config["event_location"]: if config["event_date"] and config["event_location"]:
canvas.drawString(2.75 * cm, 27.6 * cm, u"%s, %s" canvas.drawString(2.75 * cm, 27.6 * cm, u"%s, %s"
% (config["event_date"], config["event_location"])) % (config["event_date"], config["event_location"]))
# time # time
canvas.setFont('Ubuntu', 7) canvas.setFont('Ubuntu', 7)

View File

@ -11,13 +11,17 @@
""" """
from openslides.utils.person.signals import receive_persons from openslides.utils.person.signals import receive_persons
from openslides.utils.person.api import (generate_person_id, get_person, from openslides.utils.person.api import (
Person, Persons) generate_person_id, get_person, Person, Persons)
from openslides.utils.person.forms import PersonFormField, MultiplePersonFormField from openslides.utils.person.forms import PersonFormField, MultiplePersonFormField
from openslides.utils.person.models import PersonField, PersonMixin from openslides.utils.person.models import PersonField, PersonMixin
__all__ = ['receive_persons', 'generate_person_id', 'get_person', 'Person',
'Persons', 'PersonFormField', 'MultiplePersonFormField',
'PersonField', 'PersonMixin', 'EmptyPerson']
class EmptyPerson(PersonMixin):
class EmptyPerson(PersonMixin, Person):
@property @property
def person_id(self): def person_id(self):
return 'empty' return 'empty'

View File

@ -61,8 +61,8 @@ class MultiplePersonFormField(PersonFormField):
widget = forms.widgets.SelectMultiple widget = forms.widgets.SelectMultiple
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(MultiplePersonFormField, self).__init__(empty_label=None, super(MultiplePersonFormField, self).__init__(
*args, **kwargs) empty_label=None, *args, **kwargs)
def to_python(self, value): def to_python(self, value):
if hasattr(value, '__iter__'): if hasattr(value, '__iter__'):

View File

@ -10,8 +10,7 @@
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
from django.http import HttpResponse from django.template import loader, Context
from django.template import loader, Context, RequestContext, TextNode
from django.template.loader_tags import BlockNode, ExtendsNode from django.template.loader_tags import BlockNode, ExtendsNode
@ -24,6 +23,10 @@ class Tab(object):
self.url = url self.url = url
## All following function are only needed to render a block from a template
## and could be removed, if the template worked with an include-statement instead.
## Its only used for ajax-request from the projector.
def get_template(template): def get_template(template):
if isinstance(template, (tuple, list)): if isinstance(template, (tuple, list)):
return loader.select_template(template) return loader.select_template(template)
@ -49,22 +52,22 @@ def render_template_block_nodelist(nodelist, block, context):
for key in ('nodelist', 'nodelist_true', 'nodelist_false'): for key in ('nodelist', 'nodelist_true', 'nodelist_false'):
if hasattr(node, key): if hasattr(node, key):
try: try:
return render_template_block_nodelist(getattr(node, key), return render_template_block_nodelist(
block, context) getattr(node, key), block, context)
except: except:
pass pass
for node in nodelist: for node in nodelist:
if isinstance(node, ExtendsNode): if isinstance(node, ExtendsNode):
try: try:
return render_template_block(node.get_parent(context), block, return render_template_block(
context) node.get_parent(context), block, context)
except BlockNotFound: except BlockNotFound:
pass pass
raise BlockNotFound raise BlockNotFound
def render_block_to_string(template_name, block, dictionary=None, def render_block_to_string(template_name, block, dictionary=None,
context_instance=None): context_instance=None):
""" """
Loads the given template_name and renders the given block with the given Loads the given template_name and renders the given block with the given
dictionary as context. Returns a string. dictionary as context. Returns a string.
@ -77,23 +80,3 @@ def render_block_to_string(template_name, block, dictionary=None,
context_instance = Context(dictionary) context_instance = Context(dictionary)
t.render(context_instance) t.render(context_instance)
return render_template_block(t, block, context_instance) return render_template_block(t, block, context_instance)
def direct_block_to_template(request, template, block, extra_context=None,
mimetype=None, **kwargs):
"""
Render a given block in a given template with any extra URL parameters in
the context as ``{{ params }}``.
"""
if extra_context is None:
extra_context = {}
dictionary = {'params': kwargs}
for key, value in extra_context.items():
if callable(value):
dictionary[key] = value()
else:
dictionary[key] = value
c = RequestContext(request, dictionary)
t = get_template(template)
t.render(c)
return HttpResponse(render_template_block(t, block, c), mimetype=mimetype)

View File

@ -10,12 +10,13 @@
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
import sys
try: try:
import json import json
except ImportError: # For python 2.5 support except ImportError: # For python 2.5 support
import simplejson as json import simplejson as json
from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
from django.core.context_processors import csrf from django.core.context_processors import csrf
@ -35,16 +36,17 @@ def gen_confirm_form(request, message, url):
Deprecated. Use Class base Views instead. Deprecated. Use Class base Views instead.
""" """
messages.warning(request, messages.warning(
""" request,
%s """
<form action="%s" method="post"> %s
<input type="hidden" value="%s" name="csrfmiddlewaretoken"> <form action="%s" method="post">
<input type="submit" value="%s"> <input type="hidden" value="%s" name="csrfmiddlewaretoken">
<input type="button" value="%s"> <input type="submit" value="%s">
</form> <input type="button" value="%s">
""" </form>
% (message, url, csrf(request)['csrf_token'], _("Yes"), _("No"))) """
% (message, url, csrf(request)['csrf_token'], _("Yes"), _("No")))
def del_confirm_form(request, object, name=None, delete_link=None): def del_confirm_form(request, object, name=None, delete_link=None):
@ -57,27 +59,28 @@ def del_confirm_form(request, object, name=None, delete_link=None):
name = object name = object
if delete_link is None: if delete_link is None:
delete_link = object.get_absolute_url('delete') delete_link = object.get_absolute_url('delete')
gen_confirm_form(request, _('Do you really want to delete %s?') gen_confirm_form(
request, _('Do you really want to delete %s?')
% html_strong(name), delete_link) % html_strong(name), delete_link)
def render_response(req, *args, **kwargs):
kwargs['context_instance'] = RequestContext(req)
return render_to_response(*args, **kwargs)
def template(template_name): def template(template_name):
"""
Decorator to set a template for a view.
Deprecated. Use class based views instead.
"""
def renderer(func): def renderer(func):
def wrapper(request, *args, **kwargs): def wrapper(request, *args, **kwargs):
output = func(request, *args, **kwargs) output = func(request, *args, **kwargs)
if not isinstance(output, dict): if not isinstance(output, dict):
return output return output
context = {} context = {}
template_manipulation.send(sender='utils_template', request=request, template_manipulation.send(
context=context) sender='utils_template', request=request, context=context)
output.update(context) output.update(context)
response = render_to_response(template_name, output, response = render_to_response(
context_instance=RequestContext(request)) template_name, output, context_instance=RequestContext(request))
if 'cookie' in output: if 'cookie' in output:
response.set_cookie(output['cookie'][0], output['cookie'][1]) response.set_cookie(output['cookie'][0], output['cookie'][1])
return response return response
@ -89,6 +92,8 @@ def permission_required(perm, login_url=None):
""" """
Decorator for views that checks whether a user has a particular permission Decorator for views that checks whether a user has a particular permission
enabled, redirecting to the log-in page if necessary. enabled, redirecting to the log-in page if necessary.
Deprecated.
""" """
def renderer(func): def renderer(func):
def wrapper(request, *args, **kw): def wrapper(request, *args, **kw):
@ -101,10 +106,12 @@ def permission_required(perm, login_url=None):
return renderer return renderer
def render_to_forbidden(request, error= def render_to_forbidden(request,
ugettext_lazy("Sorry, you have no rights to see this page.")): error=ugettext_lazy("Sorry, you have no rights to see this page.")):
return HttpResponseForbidden(render_to_string('403.html', # TODO: Integrate this function into the PermissionMixin once the
{'error': error}, context_instance=RequestContext(request))) # above function is deleted.
return HttpResponseForbidden(render_to_string(
'403.html', {'error': error}, context_instance=RequestContext(request)))
def delete_default_permissions(**kwargs): def delete_default_permissions(**kwargs):
@ -112,27 +119,27 @@ def delete_default_permissions(**kwargs):
Deletes the permissions, django creates by default for the admin. Deletes the permissions, django creates by default for the admin.
""" """
for p in Permission.objects.all(): for p in Permission.objects.all():
if p.codename.startswith('add') \ if (p.codename.startswith('add') or
or p.codename.startswith('delete') \ p.codename.startswith('delete') or
or p.codename.startswith('change'): p.codename.startswith('change')):
p.delete() p.delete()
def ajax_request(data): def ajax_request(data):
""" """
generates a HTTPResponse-Object with json-Data for a generates a HTTPResponse-Object with json-Data for a
ajax response ajax response.
Deprecated.
""" """
return HttpResponse(json.dumps(data)) return HttpResponse(json.dumps(data))
def _propper_unicode(text): def _propper_unicode(text):
res = ''
if not isinstance(text, unicode): if not isinstance(text, unicode):
res = u"%s" % text.decode('UTF-8') return u"%s" % text.decode('UTF-8')
else: else:
res = text return text
return res
def decodedict(dict): def decodedict(dict):

View File

@ -22,8 +22,7 @@ except ImportError:
# Is this exception realy necessary? # Is this exception realy necessary?
from StringIO import StringIO from StringIO import StringIO
from reportlab.platypus import (SimpleDocTemplate, Paragraph, Frame, PageBreak, from reportlab.platypus import SimpleDocTemplate, Spacer
Spacer, Table, LongTable, TableStyle, Image)
from reportlab.lib.units import cm from reportlab.lib.units import cm
from django.contrib import messages from django.contrib import messages
@ -34,9 +33,9 @@ from django.conf import settings
from django.dispatch import receiver from django.dispatch import receiver
from django.http import HttpResponseServerError, HttpResponse, HttpResponseRedirect from django.http import HttpResponseServerError, HttpResponse, HttpResponseRedirect
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _, ugettext_noop, ugettext_lazy from django.utils.translation import ugettext as _, ugettext_lazy
from django.utils.importlib import import_module from django.utils.importlib import import_module
from django.template import loader, RequestContext from django.template import RequestContext
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.views.generic import ( from django.views.generic import (
TemplateView as _TemplateView, TemplateView as _TemplateView,
@ -50,8 +49,6 @@ from django.views.generic import (
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from django.views.generic.list import TemplateResponseMixin from django.views.generic.list import TemplateResponseMixin
from openslides.config.models import config
from openslides.utils.utils import render_to_forbidden, html_strong from openslides.utils.utils import render_to_forbidden, html_strong
from openslides.utils.signals import template_manipulation from openslides.utils.signals import template_manipulation
from openslides.utils.pdf import firstPage, laterPages from openslides.utils.pdf import firstPage, laterPages
@ -64,8 +61,8 @@ View = _View
class SetCookieMixin(object): class SetCookieMixin(object):
def render_to_response(self, context, **response_kwargs): def render_to_response(self, context, **response_kwargs):
response = TemplateResponseMixin.render_to_response(self, context, response = TemplateResponseMixin.render_to_response(
**response_kwargs) self, context, **response_kwargs)
if 'cookie' in context: if 'cookie' in context:
response.set_cookie(context['cookie'][0], context['cookie'][1]) response.set_cookie(context['cookie'][0], context['cookie'][1])
return response return response
@ -90,8 +87,8 @@ class PermissionMixin(object):
if not self.has_permission(request, *args, **kwargs): if not self.has_permission(request, *args, **kwargs):
if not request.user.is_authenticated(): if not request.user.is_authenticated():
path = request.get_full_path() path = request.get_full_path()
return HttpResponseRedirect("%s?next=%s" % (settings.LOGIN_URL, return HttpResponseRedirect(
path)) "%s?next=%s" % (settings.LOGIN_URL, path))
else: else:
return render_to_forbidden(request) return render_to_forbidden(request)
return _View.dispatch(self, request, *args, **kwargs) return _View.dispatch(self, request, *args, **kwargs)
@ -130,18 +127,18 @@ class QuestionMixin(object):
option_fields = "\n".join([ option_fields = "\n".join([
'<input type="submit" name="%s" value="%s">' % (option[0], unicode(option[1])) '<input type="submit" name="%s" value="%s">' % (option[0], unicode(option[1]))
for option in self.get_answer_options()]) for option in self.get_answer_options()])
messages.warning(self.request, messages.warning(
self.request,
""" """
%(message)s %(message)s
<form action="%(url)s" method="post"> <form action="%(url)s" method="post">
<input type="hidden" value="%(csrf)s" name="csrfmiddlewaretoken"> <input type="hidden" value="%(csrf)s" name="csrfmiddlewaretoken">
%(option_fields)s %(option_fields)s
</form> </form>
""" % { """ % {'message': self.get_question(),
'message': self.get_question(), 'url': self.get_answer_url(),
'url': self.get_answer_url(), 'csrf': csrf(self.request)['csrf_token'],
'csrf': csrf(self.request)['csrf_token'], 'option_fields': option_fields})
'option_fields': option_fields})
def pre_post_redirect(self, request, *args, **kwargs): def pre_post_redirect(self, request, *args, **kwargs):
# Reacts on the response of the user in a POST-request. # Reacts on the response of the user in a POST-request.
@ -167,16 +164,16 @@ class QuestionMixin(object):
class TemplateView(PermissionMixin, _TemplateView): class TemplateView(PermissionMixin, _TemplateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(TemplateView, self).get_context_data(**kwargs) context = super(TemplateView, self).get_context_data(**kwargs)
template_manipulation.send(sender=self.__class__, request=self.request, template_manipulation.send(
context=context) sender=self.__class__, request=self.request, context=context)
return context return context
class ListView(PermissionMixin, SetCookieMixin, _ListView): class ListView(PermissionMixin, SetCookieMixin, _ListView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(ListView, self).get_context_data(**kwargs) context = super(ListView, self).get_context_data(**kwargs)
template_manipulation.send(sender=self.__class__, request=self.request, template_manipulation.send(
context=context) sender=self.__class__, request=self.request, context=context)
return context return context
@ -217,8 +214,8 @@ class FormView(PermissionMixin, _FormView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(FormView, self).get_context_data(**kwargs) context = super(FormView, self).get_context_data(**kwargs)
template_manipulation.send(sender=self.__class__, request=self.request, template_manipulation.send(
context=context) sender=self.__class__, request=self.request, context=context)
return context return context
def form_invalid(self, form): def form_invalid(self, form):
@ -235,8 +232,8 @@ class UpdateView(PermissionMixin, _UpdateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(UpdateView, self).get_context_data(**kwargs) context = super(UpdateView, self).get_context_data(**kwargs)
template_manipulation.send(sender=self.__class__, request=self.request, template_manipulation.send(
context=context) sender=self.__class__, request=self.request, context=context)
return context return context
def form_invalid(self, form): def form_invalid(self, form):
@ -256,8 +253,8 @@ class CreateView(PermissionMixin, _CreateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(CreateView, self).get_context_data(**kwargs) context = super(CreateView, self).get_context_data(**kwargs)
template_manipulation.send(sender=self.__class__, request=self.request, template_manipulation.send(
context=context) sender=self.__class__, request=self.request, context=context)
return context return context
def get_apply_url(self): def get_apply_url(self):
@ -299,7 +296,6 @@ class DeleteView(SingleObjectMixin, QuestionMixin, RedirectView):
class DetailView(TemplateView, SingleObjectMixin): class DetailView(TemplateView, SingleObjectMixin):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
context = self.get_context_data(object=self.object)
return super(DetailView, self).get(request, *args, **kwargs) return super(DetailView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -329,8 +325,8 @@ class PDFView(PermissionMixin, View):
return SimpleDocTemplate(buffer) return SimpleDocTemplate(buffer)
def build_document(self, pdf_document, story): def build_document(self, pdf_document, story):
pdf_document.build(story, onFirstPage=firstPage, pdf_document.build(
onLaterPages=laterPages) story, onFirstPage=firstPage, onLaterPages=laterPages)
def render_to_response(self, filename): def render_to_response(self, filename):
response = HttpResponse(mimetype='application/pdf') response = HttpResponse(mimetype='application/pdf')
@ -340,7 +336,7 @@ class PDFView(PermissionMixin, View):
buffer = StringIO() buffer = StringIO()
pdf_document = self.get_template(buffer) pdf_document = self.get_template(buffer)
pdf_document.title = self.get_document_title() pdf_document.title = self.get_document_title()
story = [Spacer(1, self.get_top_space()*cm)] story = [Spacer(1, self.get_top_space() * cm)]
self.append_to_pdf(story) self.append_to_pdf(story)
@ -351,9 +347,6 @@ class PDFView(PermissionMixin, View):
response.write(pdf) response.write(pdf)
return response return response
def get_filename(self):
return self.filename
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
return self.render_to_response(self.get_filename()) return self.render_to_response(self.get_filename())
@ -364,9 +357,8 @@ def server_error(request, template_name='500.html'):
Templates: `500.html` Templates: `500.html`
""" """
t = loader.get_template("500.html") return HttpResponseServerError(render_to_string(
return HttpResponseServerError(render_to_string('500.html', template_name, context_instance=RequestContext(request)))
context_instance=RequestContext(request)))
@receiver(template_manipulation, dispatch_uid="send_register_tab") @receiver(template_manipulation, dispatch_uid="send_register_tab")

5
requirements.txt Normal file
View File

@ -0,0 +1,5 @@
Django==1.4.2
django-mptt
reportlab
PIL
simplejson

View File

@ -12,15 +12,19 @@ from setuptools import find_packages
from openslides import get_version from openslides import get_version
with open('README.txt') as file:
long_description = file.read()
setup( setup(
name='openslides', name='openslides',
description='Presentation-System', description='Presentation-System',
long_description=long_description,
version=get_version(), version=get_version(),
url='http://openslides.org', url='http://openslides.org',
author='OpenSlides-Team', author='OpenSlides-Team',
author_email='support@openslides.org', author_email='support@openslides.org',
license='GPL2+', license='GPL2+',
packages=find_packages(), packages=find_packages(exclude=['tests']),
include_package_data = True, include_package_data = True,
classifiers = [ classifiers = [
# http://pypi.python.org/pypi?%3Aaction=list_classifiers # http://pypi.python.org/pypi?%3Aaction=list_classifiers

0
tests/__init__.py Normal file
View File

0
tests/agenda/__init__.py Normal file
View File

18
tests/agenda/models.py Normal file
View File

@ -0,0 +1,18 @@
from django.db import models
from openslides.projector.projector import SlideMixin
from openslides.projector.api import register_slidemodel
class ReleatedItem(SlideMixin, models.Model):
prefix = 'releateditem'
name = models.CharField(max_length='255')
def get_agenda_title(self):
return self.name
def get_agenda_title_supplement(self):
return 'test item'
register_slidemodel(ReleatedItem)

View File

@ -12,12 +12,15 @@
from django.test import TestCase from django.test import TestCase
from django.test.client import Client from django.test.client import Client
from django.contrib.auth.models import User
from django.db.models.query import EmptyQuerySet from django.db.models.query import EmptyQuerySet
from openslides.projector.api import get_active_slide from openslides.projector.api import get_active_slide
from openslides.participant.models import User
from openslides.agenda.models import Item from openslides.agenda.models import Item
from openslides.agenda.slides import agenda_show
from .models import ReleatedItem
class ItemTest(TestCase): class ItemTest(TestCase):
def setUp(self): def setUp(self):
@ -25,6 +28,8 @@ class ItemTest(TestCase):
self.item2 = Item.objects.create(title='item2') self.item2 = Item.objects.create(title='item2')
self.item3 = Item.objects.create(title='item1A', parent=self.item1) self.item3 = Item.objects.create(title='item1A', parent=self.item1)
self.item4 = Item.objects.create(title='item1Aa', parent=self.item3) self.item4 = Item.objects.create(title='item1Aa', parent=self.item3)
self.releated = ReleatedItem.objects.create(name='foo')
self.item5 = Item.objects.create(title='item5', related_sid=self.releated.sid)
def testClosed(self): def testClosed(self):
self.assertFalse(self.item1.closed) self.assertFalse(self.item1.closed)
@ -46,10 +51,6 @@ class ItemTest(TestCase):
self.assertTrue(self.item3 in self.item1.get_children()) self.assertTrue(self.item3 in self.item1.get_children())
self.assertFalse(self.item4 in self.item1.get_children()) self.assertFalse(self.item4 in self.item1.get_children())
l = Item.objects.all()
self.assertEqual(str(l),
"[<Item: item1>, <Item: item1A>, <Item: item1Aa>, <Item: item2>]")
def testForms(self): def testForms(self):
for item in Item.objects.all(): for item in Item.objects.all():
initial = item.weight_form.initial initial = item.weight_form.initial
@ -64,6 +65,36 @@ class ItemTest(TestCase):
self.item1.related_sid = 'foobar' self.item1.related_sid = 'foobar'
self.assertFalse(self.item1.get_related_slide() is None) self.assertFalse(self.item1.get_related_slide() is None)
def test_title_supplement(self):
self.assertEqual(self.item1.get_title_supplement(), '')
def test_delete_item(self):
new_item1 = Item.objects.create()
new_item2 = Item.objects.create(parent=new_item1)
new_item3 = Item.objects.create(parent=new_item2)
new_item1.delete()
self.assertTrue(new_item3 in Item.objects.all())
new_item2.delete(with_children=True)
self.assertFalse(new_item3 in Item.objects.all())
def test_absolute_url(self):
self.assertEqual(self.item1.get_absolute_url(), '/agenda/1/')
self.assertEqual(self.item1.get_absolute_url('edit'), '/agenda/1/edit/')
self.assertEqual(self.item1.get_absolute_url('delete'), '/agenda/1/del/')
def test_agenda_slide(self):
data = agenda_show()
self.assertEqual(list(data['items']), list(Item.objects.all().filter(parent=None)))
self.assertEqual(data['template'], 'projector/AgendaSummary.html')
self.assertEqual(data['title'], 'Agenda')
def test_releated_item(self):
self.assertEqual(self.item5.get_title(), self.releated.name)
self.assertEqual(self.item5.get_title_supplement(), 'test item')
self.assertEqual(self.item5.get_related_type(), 'releateditem')
self.assertEqual(self.item5.print_related_type(), 'Releateditem')
class ViewTest(TestCase): class ViewTest(TestCase):
def setUp(self): def setUp(self):
@ -71,8 +102,10 @@ class ViewTest(TestCase):
self.item2 = Item.objects.create(title='item2') self.item2 = Item.objects.create(title='item2')
self.refreshItems() self.refreshItems()
self.admin = User.objects.create_user('testadmin', '', 'default') self.admin, created = User.objects.get_or_create(username='testadmin')
self.anonym = User.objects.create_user('testanoym', '', 'default') self.anonym, created = User.objects.get_or_create(username='testanonym')
self.admin.reset_password('default')
self.anonym.reset_password('default')
self.admin.is_superuser = True self.admin.is_superuser = True
self.admin.save() self.admin.save()
@ -91,6 +124,13 @@ class ViewTest(TestCase):
def anonymClient(self): def anonymClient(self):
return Client() return Client()
def testOverview(self):
c = self.adminClient
response = c.get('/agenda/')
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.context['items']), len(Item.objects.all()))
def testActivate(self): def testActivate(self):
c = self.adminClient c = self.adminClient
@ -122,6 +162,14 @@ class ViewTest(TestCase):
self.refreshItems() self.refreshItems()
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
# Test ajax
response = c.get('/agenda/%d/close/' % self.item1.id,
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
response = c.get('/agenda/%d/open/' % self.item1.id,
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
def testEdit(self): def testEdit(self):
c = self.adminClient c = self.adminClient
@ -131,7 +179,7 @@ class ViewTest(TestCase):
response = c.get('/agenda/%d/edit/' % 1000) response = c.get('/agenda/%d/edit/' % 1000)
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
data = {'title': 'newitem1', 'text': 'item1-text', 'weight':'0'} data = {'title': 'newitem1', 'text': 'item1-text', 'weight': '0'}
response = c.post('/agenda/%d/edit/' % self.item1.id, data) response = c.post('/agenda/%d/edit/' % self.item1.id, data)
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
self.refreshItems() self.refreshItems()
@ -143,4 +191,3 @@ class ViewTest(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.refreshItems() self.refreshItems()
self.assertEqual(self.item1.title, 'newitem1') self.assertEqual(self.item1.title, 'newitem1')

23
tests/test_init.py Normal file
View File

@ -0,0 +1,23 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Unit test for OpenSlides __init__.py
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from django.test import TestCase
from openslides import get_version
class InitTest(TestCase):
def test_get_version(self):
self.assertEqual(get_version((1, 3, 0, 'beta', 2)), '1.3-beta2')
self.assertEqual(get_version((1, 0, 0, 'final', 0)), '1.0')
self.assertEqual(get_version((2, 5, 3, 'alpha', 0)), '2.5.3-alpha0')
git_version = get_version((2, 5, 0, 'dev', 0))
if 'unknown' in git_version:
self.assertEqual(len(git_version), 14)
else:
self.assertEqual(len(git_version), 47)

View File

@ -11,10 +11,10 @@
""" """
from django.test import TestCase from django.test import TestCase
from django.test.client import Client
from openslides.participant.models import User from openslides.participant.models import User
from openslides.motion.models import Motion, AVersion from openslides.motion.models import Motion
class MotionTest(TestCase): class MotionTest(TestCase):
def setUp(self): def setUp(self):
@ -39,4 +39,3 @@ class MotionTest(TestCase):
self.assertEqual(self.app1.versions.count(), 2) self.assertEqual(self.app1.versions.count(), 2)
self.assertEqual(self.app1.last_version, self.app1.versions[1]) self.assertEqual(self.app1.last_version, self.app1.versions[1])

View File

@ -11,8 +11,6 @@
""" """
from django.test import TestCase from django.test import TestCase
from django.test.client import Client
from django.contrib.auth.hashers import check_password
from openslides.utils.person import get_person, Persons from openslides.utils.person import get_person, Persons
from openslides.participant.api import gen_username, gen_password from openslides.participant.api import gen_username, gen_password
@ -38,9 +36,9 @@ class UserTest(TestCase):
self.assertEqual(unicode(self.user1), 'Max Mustermann') self.assertEqual(unicode(self.user1), 'Max Mustermann')
def test_name_suffix(self): def test_name_suffix(self):
self.user1.structure_level = 'München' self.user1.structure_level = u'München'
self.user1.save() self.user1.save()
self.assertEqual(unicode(self.user1), 'Max Mustermann (München)') self.assertEqual(unicode(self.user1), u'Max Mustermann (München)')
def test_reset_password(self): def test_reset_password(self):
self.assertIsInstance(self.user1.default_password, basestring) self.assertIsInstance(self.user1.default_password, basestring)