diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..8655e4714 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,4 @@ +[run] +source=openslides +[report] +exclude_lines = def __(unicode|repr)__ diff --git a/.gitignore b/.gitignore index 6fa3cff77..7f7a1c453 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,9 @@ docs/_build/* build/* dist/* .DS_Store +settings.py +versiontools* + +# Unit test / coverage reports +.coverage +htmlcov diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..bad214ce7 --- /dev/null +++ b/.travis.yml @@ -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 diff --git a/AUTHORS b/AUTHORS index 72ee9b2ef..f4eeb7de7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -5,3 +5,5 @@ Authors of OpenSlides in chronological order of first contribution: Norman Jäckel René Köcher Andy Kittner + Moira Brülisauer (French translation) + Alexis Roussel (French translation) diff --git a/CHANGELOG b/CHANGELOG index bb83ba535..37c507c76 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,80 @@ CHANGELOG of OpenSlides 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: . (#326) +- New user and group slides. (#176) +- Don't allow to deactivate the administrator or themself. +- Don't allow to delete themself. +- Renamed participant field 'groups' to 'structure level' (German: Gliederungsebene). +- Rewrote participant views as class based views. +- Made OpenSlides user a child model of Django user model. +- Appended tests. +- Fixed error to allow admins to delete anonymous group + +Other: +- Added French translation (Thanks to Moira). +- Updated setup.py to make an openslides python package. +- Removed frontpage (welcome widget contains it's content) and redirect '/' to dashboard url. + +- Added LOCALE_PATHS to openslides_settings to avoid deprication in Django 1.5. +- Redesigned the DeleteView (append QuestionMixin to send question via the django message API). +- Fixed encoding error in settings.py. (#349) +- Renamed openslides_settings.py to openslides_global_settings.py. +- New default path to database file (XDG_DATA_HOME, e.g. ~/.local/share/openslides/). +- New default path to settings file (XDG_CONFIG_HOME, e.g. ~/.config/openslides/). +- Added special handling to determine location of database and settings file in portable version. +- Don't use similar characters in generated passwords (no 'Il10oO'). +- Localised the datetime in PDF header. (#296) +- Used specific session cookie name. (#332) +- Moved code repository from hg to git (incl. some required updates, e.g. version string function). +- Updated German translations. +- Several code optimizations. +- Several minor and medium issues and errors were fixed. + + Version 1.2 (2012-07-25) ======================== [http://dev.openslides.org/milestone/1.2] diff --git a/INSTALL.txt b/INSTALL.txt index 68ef8416a..512fae243 100644 --- a/INSTALL.txt +++ b/INSTALL.txt @@ -3,15 +3,56 @@ Installation Instructions for OpenSlides 1.3 Content ------- -I. Installation on Windows (32/64bit) -II. Installation on GNU/Linux and MacOSX +I. Installation on GNU/Linux and MacOSX +II. Installation on Windows (32/64bit) If you need help ask on OpenSlides users mailing list. See http://openslides.org for more information. -I. Installation on Windows (32/64bit) -------------------------------------- +I. Installation on GNU/Linux and MacOSX +---------------------------------------- + +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: @@ -76,73 +117,3 @@ I. Installation on Windows (32/64bit) 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.). diff --git a/MANIFEST.in b/MANIFEST.in index d028d548b..affdfa149 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,5 @@ include AUTHORS include CHANGELOG -include initial_data.json include INSTALL.txt include LICENSE include manage.py @@ -12,16 +11,14 @@ recursive-include openslides/templates * recursive-include openslides/agenda/templates * recursive-include openslides/agenda/static * -recursive-include openslides/application/templates * -recursive-include openslides/application/static * +recursive-include openslides/motion/templates * recursive-include openslides/assignment/templates * recursive-include openslides/assignment/static * recursive-include openslides/config/templates * -recursive-include openslides/config/static * recursive-include openslides/participant/templates * recursive-include openslides/participant/static * +include openslides/participant/fixtures/groups_de.json recursive-include openslides/poll/templates * -recursive-include openslides/poll/static * recursive-include openslides/projector/templates * recursive-include openslides/projector/static * diff --git a/README.txt b/README.txt index 92ca78025..6a3f12f91 100644 --- a/README.txt +++ b/README.txt @@ -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? @@ -10,7 +10,7 @@ What is OpenSlides? OpenSlides is a free, web-based presentation system for displaying and 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 @@ -18,14 +18,15 @@ Getting started Install and start OpenSlides as described in the INSTALL.txt. If you need help please contact the OpenSlides team on public mailing -list or read the OpenSlides manual. See http://www.openslides.org. +list or read the OpenSlides manual. See http://openslides.org. The start script of OpenSlides ============================== -Simply running - openslides.exe (on Windows) or - python start.py (on Linux/MacOS) +Simply running + openslides.exe (on Windows) or + python start.py (on Linux/MacOS) + will start OpenSlides using djangos development server. It will also try to open OpenSlides in your default webbrowser. diff --git a/extras/scripts/create_local_settings.py b/extras/scripts/create_local_settings.py new file mode 100644 index 000000000..1702992e0 --- /dev/null +++ b/extras/scripts/create_local_settings.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import sys + +script_path = os.path.realpath(os.path.dirname(__file__)) +sys.path.append(os.path.join(script_path, '..', '..')) + +from openslides.main import create_settings + +if __name__ == "__main__": + cwd = os.getcwd() + create_settings(os.path.join(cwd, 'settings.py'), + os.path.join(cwd, 'database.sqlite')) diff --git a/extras/win32-portable/prepare_portable.py b/extras/win32-portable/prepare_portable.py index 9418b8e10..dbdb15da9 100644 --- a/extras/win32-portable/prepare_portable.py +++ b/extras/win32-portable/prepare_portable.py @@ -87,6 +87,7 @@ PY_DLLS = [ "_sqlite3.pyd", "_socket.pyd", "select.pyd", + "_ctypes.pyd", ] MSVCR_PUBLIC_KEY = "1fc8b3b9a1e18e3b" @@ -306,8 +307,8 @@ def main(): shutil.copyfile("extras/win32-portable/openslides.exe", os.path.join(odir, "openslides.exe")) - shutil.copyfile("initial_data.json", - os.path.join(odir, "initial_data.json")) + shutil.copyfile("openslides/participant/fixtures/groups_de.json", + os.path.join(odir, "groups_de.json")) copy_dlls(odir) copy_msvcr(odir) diff --git a/openslides/agenda/forms.py b/openslides/agenda/forms.py index bfaa30af6..66a4ee7a7 100644 --- a/openslides/agenda/forms.py +++ b/openslides/agenda/forms.py @@ -24,8 +24,8 @@ class ItemForm(forms.ModelForm, CssClassMixin): """ Form to create of update an item. """ - parent = TreeNodeChoiceField(queryset=Item.objects.all(), - label=_("Parent item"), required=False) + parent = TreeNodeChoiceField( + queryset=Item.objects.all(), label=_("Parent item"), required=False) class Meta: model = Item diff --git a/openslides/agenda/models.py b/openslides/agenda/models.py index d887d553c..0356607dd 100644 --- a/openslides/agenda/models.py +++ b/openslides/agenda/models.py @@ -10,12 +10,6 @@ :license: GNU GPL, see LICENSE for more details. """ -try: - import json -except ImportError: - # for python 2.5 support - import simplejson as json - from django.db import models from django.core.urlresolvers import reverse from django.utils.translation import ugettext_lazy as _, ugettext_noop, ugettext @@ -23,11 +17,9 @@ from django.utils.translation import ugettext_lazy as _, ugettext_noop, ugettext from mptt.models import MPTTModel, TreeForeignKey from openslides.config.models import config - from openslides.projector.projector import SlideMixin -from openslides.projector.api import (register_slidemodel, get_slide_from_sid, - register_slidefunc, split_sid) - +from openslides.projector.api import ( + register_slidemodel, get_slide_from_sid, register_slidefunc) from openslides.agenda.slides import agenda_show @@ -45,7 +37,7 @@ class Item(MPTTModel, SlideMixin): closed = models.BooleanField(default=False, verbose_name=_("Closed")) weight = models.IntegerField(default=0, verbose_name=_("Weight")) parent = TreeForeignKey('self', null=True, blank=True, - related_name='children') + related_name='children') related_sid = models.CharField(null=True, blank=True, max_length=63) def get_related_slide(self): @@ -84,7 +76,6 @@ class Item(MPTTModel, SlideMixin): return self.title return self.get_related_slide().get_agenda_title() - def get_title_supplement(self): """ return a supplement for the title. diff --git a/openslides/agenda/views.py b/openslides/agenda/views.py index 863bd9da4..55b42e204 100644 --- a/openslides/agenda/views.py +++ b/openslides/agenda/views.py @@ -11,7 +11,6 @@ """ from reportlab.platypus import Paragraph -from django.core.context_processors import csrf from django.core.urlresolvers import reverse from django.contrib import messages from django.db import transaction @@ -20,18 +19,15 @@ from django.utils.translation import ugettext as _, ugettext_lazy from django.views.generic.detail import SingleObjectMixin from openslides.utils.pdf import stylesheet -from openslides.utils.views import (TemplateView, RedirectView, UpdateView, - CreateView, DeleteView, PDFView, DetailView) +from openslides.utils.views import ( + TemplateView, RedirectView, UpdateView, CreateView, DeleteView, PDFView, + DetailView) from openslides.utils.template import Tab from openslides.utils.utils import html_strong - -from openslides.config.models import config - from openslides.projector.api import get_active_slide from openslides.projector.projector import Widget, SLIDE - -from openslides.agenda.models import Item -from openslides.agenda.forms import ItemOrderForm, ItemForm +from .models import Item +from .forms import ItemOrderForm, ItemForm class Overview(TemplateView): @@ -53,7 +49,8 @@ class Overview(TemplateView): def post(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) if not request.user.has_perm('agenda.can_manage_agenda'): - messages.error(request, + messages.error( + request, _('You are not authorized to manage the agenda.')) return self.render_to_response(context) transaction.commit() @@ -69,8 +66,8 @@ class Overview(TemplateView): Model.save(item) else: transaction.rollback() - messages.error(request, - _('Errors when reordering of the agenda')) + messages.error( + request, _('Errors when reordering of the agenda')) return self.render_to_response(context) Item.objects.rebuild() # TODO: assure, that it is a valid tree @@ -130,8 +127,8 @@ class ItemUpdate(UpdateView): apply_url = 'item_edit' def get_success_url(self): - messages.success(self.request, - _("Item %s was successfully modified.") \ + messages.success( + self.request, _("Item %s was successfully modified.") % html_strong(self.request.POST['title'])) if 'apply' in self.request.POST: return '' @@ -151,8 +148,8 @@ class ItemCreate(CreateView): apply_url = 'item_edit' def get_success_url(self): - messages.success(self.request, - _("Item %s was successfully created.") \ + messages.success( + self.request, _("Item %s was successfully created.") % html_strong(self.request.POST['title'])) if 'apply' in self.request.POST: return reverse(self.get_apply_url(), args=[self.object.id]) @@ -176,13 +173,13 @@ class ItemDelete(DeleteView): def pre_post_redirect(self, request, *args, **kwargs): if self.get_answer() == 'all': self.object.delete(with_children=True) - messages.success(request, - _("Item %s and his children were successfully deleted.") + messages.success( + request, _("Item %s and his children were successfully deleted.") % html_strong(self.object)) elif self.get_answer() == 'yes': self.object.delete(with_children=False) - messages.success(request, - _("Item %s was successfully deleted.") + messages.success( + request, _("Item %s was successfully deleted.") % html_strong(self.object)) @@ -199,7 +196,8 @@ class AgendaPDF(PDFView): ancestors = item.get_ancestors() if ancestors: space = " " * 6 * ancestors.count() - story.append(Paragraph("%s%s" % (space, item.get_title()), + story.append(Paragraph( + "%s%s" % (space, item.get_title()), stylesheet['Subitem'])) else: story.append(Paragraph(item.get_title(), stylesheet['Item'])) @@ -213,10 +211,9 @@ def register_tab(request): return Tab( title=_('Agenda'), url=reverse('item_overview'), - permission=request.user.has_perm('agenda.can_see_agenda') - or request.user.has_perm('agenda.can_manage_agenda'), - selected=selected, - ) + permission=(request.user.has_perm('agenda.can_see_agenda') or + request.user.has_perm('agenda.can_manage_agenda')), + selected=selected) def get_widgets(request): diff --git a/openslides/assignment/forms.py b/openslides/assignment/forms.py index cf4dfcba2..9adf16dbd 100644 --- a/openslides/assignment/forms.py +++ b/openslides/assignment/forms.py @@ -11,7 +11,7 @@ """ from django import forms -from django.utils.translation import ugettext_lazy as _, ugettext_noop +from django.utils.translation import ugettext_lazy as _ from openslides.utils.forms import CssClassMixin from openslides.utils.person import PersonFormField @@ -20,8 +20,8 @@ from openslides.assignment.models import Assignment class AssignmentForm(forms.ModelForm, CssClassMixin): - posts = forms.IntegerField(min_value=1, initial=1, - label=_("Number of available posts")) + posts = forms.IntegerField( + min_value=1, initial=1, label=_("Number of available posts")) class Meta: model = Assignment @@ -39,8 +39,7 @@ class ConfigForm(forms.Form, CssClassMixin): assignment_publish_winner_results_only = forms.BooleanField( required=False, label=_("Only publish voting results for selected winners " - "(Projector view only)") - ) + "(Projector view only)")) assignment_pdf_ballot_papers_selection = forms.ChoiceField( widget=forms.Select(), required=False, @@ -48,31 +47,25 @@ class ConfigForm(forms.Form, CssClassMixin): choices=( ("NUMBER_OF_DELEGATES", _("Number of all delegates")), ("NUMBER_OF_ALL_PARTICIPANTS", _("Number of all participants")), - ("CUSTOM_NUMBER", _("Use the following custom number")) - ) - ) + ("CUSTOM_NUMBER", _("Use the following custom number")))) assignment_pdf_ballot_papers_number = forms.IntegerField( - widget=forms.TextInput(attrs={'class':'small-input'}), + widget=forms.TextInput(attrs={'class': 'small-input'}), required=False, min_value=1, - label=_("Custom number of ballot papers") - ) + label=_("Custom number of ballot papers")) assignment_pdf_title = forms.CharField( widget=forms.TextInput(), required=False, - label=_("Title for PDF document (all elections)") - ) + label=_("Title for PDF document (all elections)")) assignment_pdf_preamble = forms.CharField( widget=forms.Textarea(), required=False, - label=_("Preamble text for PDF document (all elections)") - ) - assignment_poll_vote_values = forms.ChoiceField(widget=forms.Select(), + label=_("Preamble text for PDF document (all elections)")) + assignment_poll_vote_values = forms.ChoiceField( + widget=forms.Select(), required=False, label=_("Election method"), choices=( ("auto", _("Automatic assign of method.")), ("votes", _("Always one option per candidate.")), - ("yesnoabstain", _("Always Yes-No-Abstain per candidate.")), - ) - ) + ("yesnoabstain", _("Always Yes-No-Abstain per candidate.")))) diff --git a/openslides/assignment/models.py b/openslides/assignment/models.py index b70972234..56269b601 100644 --- a/openslides/assignment/models.py +++ b/openslides/assignment/models.py @@ -16,16 +16,12 @@ from django.dispatch import receiver from django.utils.translation import ugettext_lazy as _, ugettext_noop from openslides.utils.person import PersonField - from openslides.config.models import config from openslides.config.signals import default_config_value - from openslides.projector.api import register_slidemodel from openslides.projector.projector import SlideMixin - -from openslides.poll.models import (BasePoll, CountInvalid, CountVotesCast, - BaseOption, PublishPollMixin, BaseVote) - +from openslides.poll.models import ( + BasePoll, CountInvalid, CountVotesCast, BaseOption, PublishPollMixin, BaseVote) from openslides.agenda.models import Item @@ -51,11 +47,10 @@ class Assignment(models.Model, SlideMixin): ) name = models.CharField(max_length=100, verbose_name=_("Name")) - description = models.TextField(null=True, blank=True, - verbose_name=_("Description")) - posts = models.PositiveSmallIntegerField( - verbose_name=_("Number of available posts")) - polldescription = models.CharField(max_length=100, null=True, blank=True, + description = models.TextField(null=True, blank=True, verbose_name=_("Description")) + posts = models.PositiveSmallIntegerField(verbose_name=_("Number of available posts")) + polldescription = models.CharField( + max_length=100, null=True, blank=True, verbose_name=_("Comment on the ballot paper")) status = models.CharField(max_length=3, choices=STATUS, default='sea') @@ -68,8 +63,8 @@ class Assignment(models.Model, SlideMixin): if error: raise NameError(_('%s is not a valid status.') % status) if self.status == status: - raise NameError(_('The assignment status is already %s.') - % self.status) + raise NameError( + _('The assignment status is already %s.') % self.status) self.status = status self.save() @@ -85,16 +80,16 @@ class Assignment(models.Model, SlideMixin): raise NameError(_('%s is already a candidate.') % candidate) if not person.has_perm("assignment.can_manage_assignment") and self.status != 'sea': raise NameError(_('The candidate list is already closed.')) - candidation = self.assignment_candidates.filter(person=candidate) - if candidation and candidate != person and \ + candidature = self.assignment_candidates.filter(person=candidate) + if candidature and candidate != person and \ 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 raise NameError( _('%s does not want to be a candidate.') % candidate) - elif candidation: - candidation[0].blocked = False - candidation[0].save() + elif candidature: + candidature[0].blocked = False + candidature[0].save() else: AssignmentCandidate(assignment=self, person=candidate).save() @@ -103,36 +98,33 @@ class Assignment(models.Model, SlideMixin): stop running for a vote """ try: - candidation = self.assignment_candidates.get(person=candidate) + candidature = self.assignment_candidates.get(person=candidate) except AssignmentCandidate.DoesNotExist: raise Exception(_('%s is no candidate') % candidate) - if not candidation.blocked: + if not candidature.blocked: if blocked: - candidation.blocked = True - candidation.save() + candidature.blocked = True + candidature.save() else: - candidation.delete() + candidature.delete() else: - candidation.delete() - + candidature.delete() def is_candidate(self, person): """ return True, if person is a candidate. """ try: - return self.assignment_candidates.filter(person=person) \ - .exclude(blocked=True).exists() + return self.assignment_candidates.filter(person=person).exclude(blocked=True).exists() except AttributeError: return False def is_blocked(self, person): """ - return True, if the person is blockt for candidation. + return True, if the person is blockt for candidature. """ - return self.assignment_candidates.filter(person=person) \ - .filter(blocked=True).exists() + return self.assignment_candidates.filter(person=person).filter(blocked=True).exists() @property def assignment_candidates(self): @@ -164,7 +156,6 @@ class Assignment(models.Model, SlideMixin): return participants #return candidates.values_list('person', flat=True) - def set_elected(self, person, value=True): candidate = self.assignment_candidates.get(person=person) candidate.elected = value @@ -212,7 +203,6 @@ class Assignment(models.Model, SlideMixin): vote_results_dict[candidate].append(votes) return vote_results_dict - def get_agenda_title(self): return self.name @@ -298,8 +288,7 @@ class AssignmentPoll(BasePoll, CountInvalid, CountVotesCast, PublishPollMixin): self.yesnoabstain = False self.save() if self.yesnoabstain: - return [ugettext_noop('Yes'), ugettext_noop('No'), - ugettext_noop('Abstain')] + return [ugettext_noop('Yes'), ugettext_noop('No'), ugettext_noop('Abstain')] else: return [ugettext_noop('Votes')] diff --git a/openslides/assignment/views.py b/openslides/assignment/views.py index 5adbf0188..f5e244732 100644 --- a/openslides/assignment/views.py +++ b/openslides/assignment/views.py @@ -13,39 +13,31 @@ import os from reportlab.lib import colors -from reportlab.platypus import (SimpleDocTemplate, PageBreak, Paragraph, - Spacer, Table, TableStyle) +from reportlab.platypus import ( + SimpleDocTemplate, PageBreak, Paragraph, Spacer, Table, TableStyle) from reportlab.lib.units import cm from django.conf import settings from django.core.urlresolvers import reverse from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.contrib.auth.models import User from django.shortcuts import redirect from django.utils.translation import ungettext, ugettext as _ from openslides.utils.pdf import stylesheet from openslides.utils.template import Tab -from openslides.utils.utils import (template, permission_required, - gen_confirm_form, del_confirm_form, ajax_request) +from openslides.utils.utils import ( + template, permission_required, gen_confirm_form, del_confirm_form, ajax_request) from openslides.utils.views import FormView, DeleteView, PDFView, RedirectView from openslides.utils.person import get_person - from openslides.config.models import config - from openslides.participant.models import User - from openslides.projector.projector import Widget - from openslides.poll.views import PollFormView - from openslides.agenda.models import Item - -from openslides.assignment.models import (Assignment, AssignmentPoll, - AssignmentOption) -from openslides.assignment.forms import (AssignmentForm, AssignmentRunForm, - ConfigForm) +from openslides.assignment.models import Assignment, AssignmentPoll +from openslides.assignment.forms import ( + AssignmentForm, AssignmentRunForm, ConfigForm) @permission_required('assignment.can_see_assignment') @@ -56,7 +48,7 @@ def get_overview(request): query = query.filter(status__iexact=request.GET['status']) try: sort = request.GET['sort'] - if sort in ['name','status']: + if sort in ['name', 'status']: query = query.order_by(sort) except KeyError: pass @@ -91,7 +83,6 @@ def view(request, assignment_id=None): if request.user.has_perm('assignment.can_nominate_other'): form = AssignmentRunForm() - polls = assignment.poll_set.all() if not request.user.has_perm('assignment.can_manage_assignment'): polls = assignment.poll_set.filter(published=True) @@ -100,7 +91,8 @@ def view(request, assignment_id=None): polls = assignment.poll_set.all() vote_results = assignment.vote_results(only_published=False) - blocked_candidates = [candidate.person for candidate in \ + blocked_candidates = [ + candidate.person for candidate in assignment.assignment_candidates.filter(blocked=True)] return { 'assignment': assignment, @@ -180,7 +172,7 @@ def run(request, assignment_id): assignment = Assignment.objects.get(pk=assignment_id) try: assignment.run(request.user, request.user) - messages.success(request, _('You have set your candidature successfully.') ) + messages.success(request, _('You have set your candidature successfully.')) except NameError, e: messages.error(request, e) return redirect(reverse('assignment_view', args=[assignment_id])) @@ -195,7 +187,8 @@ def delrun(request, assignment_id): except Exception, e: messages.error(request, e) else: - messages.success(request, + messages.success( + request, _("You have withdrawn your candidature successfully. " "You can not be nominated by other participants anymore.")) else: @@ -240,7 +233,7 @@ def set_active(request, assignment_id): @permission_required('assignment.can_manage_assignment') def gen_poll(request, assignment_id): poll = Assignment.objects.get(pk=assignment_id).gen_poll() - messages.success(request, _("New ballot was successfully created.") ) + messages.success(request, _("New ballot was successfully created.")) return redirect(reverse('assignment_poll_view', args=[poll.id])) @@ -279,9 +272,9 @@ def set_publish_status(request, poll_id): return ajax_request({'published': poll.published}) if poll.published: - messages.success(request, _("Ballot successfully published.") ) + messages.success(request, _("Ballot successfully published.")) else: - messages.success(request, _("Ballot successfully unpublished.") ) + messages.success(request, _("Ballot successfully unpublished.")) return redirect(reverse('assignment_view', args=[poll.assignment.id])) @@ -336,8 +329,9 @@ class AssignmentPDF(PDFView): try: assignment_id = self.kwargs['assignment_id'] assignment = Assignment.objects.get(id=assignment_id) - filename = u'%s-%s' % (_("Assignment"), - assignment.name.replace(' ','_')) + filename = u'%s-%s' % ( + _("Assignment"), + assignment.name.replace(' ', '_')) except: filename = _("Elections") return filename @@ -347,23 +341,24 @@ class AssignmentPDF(PDFView): assignment_id = self.kwargs['assignment_id'] except KeyError: assignment_id = None - if assignment_id is None: #print all assignments + if assignment_id is None: # print all assignments title = config["assignment_pdf_title"] story.append(Paragraph(title, stylesheet['Heading1'])) preamble = config["assignment_pdf_preamble"] if preamble: - story.append(Paragraph("%s" % preamble.replace('\r\n', '
'), + story.append(Paragraph( + "%s" % preamble.replace('\r\n', '
'), stylesheet['Paragraph'])) story.append(Spacer(0, 0.75 * cm)) assignments = Assignment.objects.all() - if not assignments: # No assignments existing - story.append(Paragraph(_("No assignments available."), - stylesheet['Heading3'])) - else: # Print all assignments + if not assignments: # No assignments existing + story.append(Paragraph( + _("No assignments available."), stylesheet['Heading3'])) + else: # Print all assignments # List of assignments for assignment in assignments: - story.append(Paragraph(assignment.name, - stylesheet['Heading3'])) + story.append(Paragraph( + assignment.name, stylesheet['Heading3'])) # Assignment details (each assignment on single page) for assignment in assignments: story.append(PageBreak()) @@ -376,28 +371,33 @@ class AssignmentPDF(PDFView): def get_assignment(self, assignment, story): # title - story.append(Paragraph(_("Election: %s") % assignment.name, - stylesheet['Heading1'])) + story.append(Paragraph( + _("Election: %s") % assignment.name, stylesheet['Heading1'])) story.append(Spacer(0, 0.5 * cm)) # posts cell1a = [] - cell1a.append(Paragraph("%s:" % + cell1a.append(Paragraph( + "%s:" % _("Number of available posts"), stylesheet['Bold'])) cell1b = [] cell1b.append(Paragraph(str(assignment.posts), stylesheet['Paragraph'])) # candidates cell2a = [] - cell2a.append(Paragraph("%s:%s:" % _("Candidates"), stylesheet['Heading4'])) cell2b = [] for candidate in assignment.candidates: - cell2b.append(Paragraph(".  %s" % candidate, + cell2b.append(Paragraph( + ".  %s" % candidate, stylesheet['Signaturefield'])) if assignment.status == "sea": for x in range(0, 2 * assignment.posts): - cell2b.append(Paragraph(".  " - "__________________________________________", - stylesheet['Signaturefield'])) + cell2b.append( + Paragraph( + ".  " + "__________________________________________", + stylesheet['Signaturefield'])) cell2b.append(Spacer(0, 0.2 * cm)) # Vote results @@ -409,15 +409,15 @@ class AssignmentPDF(PDFView): # Left side cell3a = [] - cell3a.append(Paragraph("%s:" % (_("Vote results")), - stylesheet['Heading4'])) + cell3a.append(Paragraph( + "%s:" % (_("Vote results")), stylesheet['Heading4'])) if polls.count() == 1: - cell3a.append(Paragraph("%s %s" % (polls.count(), _("ballot")), - stylesheet['Normal'])) + cell3a.append(Paragraph( + "%s %s" % (polls.count(), _("ballot")), stylesheet['Normal'])) elif polls.count() > 1: - cell3a.append(Paragraph("%s %s" % (polls.count(), _("ballots")), - stylesheet['Normal'])) + cell3a.append(Paragraph( + "%s %s" % (polls.count(), _("ballots")), stylesheet['Normal'])) # Add table head row headrow = [] @@ -426,7 +426,6 @@ class AssignmentPDF(PDFView): headrow.append("%s." % poll.get_ballot()) data_votes.append(headrow) - # Add result rows elected_candidates = list(assignment.elected) for candidate, poll_list in vote_results.iteritems(): @@ -439,12 +438,13 @@ class AssignmentPDF(PDFView): candidate_string += "\n(%s)" % candidate.name_suffix row.append(candidate_string) for vote in poll_list: - if vote == None: + if vote is None: row.append('–') elif 'Yes' in vote and 'No' in vote and 'Abstain' in vote: - row.append(_("Y: %(YES)s\nN: %(NO)s\nA: %(ABSTAIN)s") - % {'YES':vote['Yes'], 'NO': vote['No'], - 'ABSTAIN': vote['Abstain']}) + row.append( + _("Y: %(YES)s\nN: %(NO)s\nA: %(ABSTAIN)s") + % {'YES': vote['Yes'], 'NO': vote['No'], + 'ABSTAIN': vote['Abstain']}) elif 'Votes' in vote: row.append(vote['Votes']) else: @@ -465,15 +465,14 @@ class AssignmentPDF(PDFView): footrow_two.append(poll.print_votescast()) data_votes.append(footrow_two) - table_votes=Table(data_votes) - table_votes.setStyle( TableStyle([ + table_votes = Table(data_votes) + table_votes.setStyle(TableStyle([ ('GRID', (0, 0), (-1, -1), 0.5, colors.grey), - ('VALIGN',(0, 0),(-1, -1), 'TOP'), - ('LINEABOVE',(0, 0),(-1, 0), 2, colors.black), - ('LINEABOVE',(0, 1),(-1, 1), 1, colors.black), - ('LINEBELOW',(0, -1),(-1, -1), 2, colors.black), - ('ROWBACKGROUNDS', (0, 1), (-1, -1), (colors.white, (.9, .9, .9))), - ])) + ('VALIGN', (0, 0), (-1, -1), 'TOP'), + ('LINEABOVE', (0, 0), (-1, 0), 2, colors.black), + ('LINEABOVE', (0, 1), (-1, 1), 1, colors.black), + ('LINEBELOW', (0, -1), (-1, -1), 2, colors.black), + ('ROWBACKGROUNDS', (0, 1), (-1, -1), (colors.white, (.9, .9, .9)))])) # table data = [] @@ -484,17 +483,18 @@ class AssignmentPDF(PDFView): else: data.append([cell2a, cell2b]) data.append([Spacer(0, 0.2 * cm), '']) - t=Table(data) + t = Table(data) t._argW[0] = 4.5 * cm t._argW[1] = 11 * cm - t.setStyle(TableStyle([ ('BOX', (0,0), (-1, -1), 1, colors.black), - ('VALIGN', (0, 0), (-1, -1), 'TOP'), - ])) + t.setStyle(TableStyle([ + ('BOX', (0, 0), (-1, -1), 1, colors.black), + ('VALIGN', (0, 0), (-1, -1), 'TOP')])) story.append(t) story.append(Spacer(0, 1 * cm)) # text - story.append(Paragraph("%s" % assignment.description.replace('\r\n', + story.append(Paragraph( + "%s" % assignment.description.replace('\r\n', '
'), stylesheet['Paragraph'])) @@ -519,13 +519,15 @@ class AssignmentPollPDF(PDFView): return super(AssignmentPollPDF, self).get(request, *args, **kwargs) def get_filename(self): - filename = u'%s-%s_%s' % (_("Election"), self.poll.assignment.name.replace(' ', '_'), + filename = u'%s-%s_%s' % ( + _("Election"), self.poll.assignment.name.replace(' ', '_'), self.poll.get_ballot()) return filename def get_template(self, buffer): - return SimpleDocTemplate(buffer, topMargin=-6, bottomMargin=-6, - leftMargin=0, rightMargin=0, showBoundary=False) + return SimpleDocTemplate( + buffer, topMargin=-6, bottomMargin=-6, leftMargin=0, rightMargin=0, + showBoundary=False) def build_document(self, pdf_document, story): pdf_document.build(story) @@ -534,23 +536,27 @@ class AssignmentPollPDF(PDFView): imgpath = os.path.join(settings.SITE_ROOT, 'static/images/circle.png') circle = "  " % imgpath cell = [] - cell.append(Spacer(0,0.8*cm)) - cell.append(Paragraph(_("Election") + ": " + self.poll.assignment.name, + cell.append(Spacer(0, 0.8 * cm)) + cell.append(Paragraph( + _("Election") + ": " + self.poll.assignment.name, stylesheet['Ballot_title'])) - cell.append(Paragraph(self.poll.assignment.polldescription, + cell.append(Paragraph( + self.poll.assignment.polldescription, stylesheet['Ballot_subtitle'])) options = self.poll.get_options() ballot_string = _("%d. ballot") % self.poll.get_ballot() - candidate_string = ungettext("%d candidate", "%d candidates", - len(options)) % len(options) - available_posts_string = ungettext("%d available post", "%d available posts", + candidate_string = ungettext( + "%d candidate", "%d candidates", len(options)) % len(options) + available_posts_string = ungettext( + "%d available post", "%d available posts", self.poll.assignment.posts) % self.poll.assignment.posts - cell.append(Paragraph("%s, %s, %s" % (ballot_string, candidate_string, + cell.append(Paragraph( + "%s, %s, %s" % (ballot_string, candidate_string, available_posts_string), stylesheet['Ballot_description'])) cell.append(Spacer(0, 0.4 * cm)) - data= [] + data = [] # get ballot papers config values ballot_papers_selection = config["assignment_pdf_ballot_papers_selection"] ballot_papers_number = config["assignment_pdf_ballot_papers_number"] @@ -560,7 +566,7 @@ class AssignmentPollPDF(PDFView): number = User.objects.filter(type__iexact="delegate").count() elif ballot_papers_selection == "NUMBER_OF_ALL_PARTICIPANTS": number = int(User.objects.count()) - else: # ballot_papers_selection == "CUSTOM_NUMBER" + else: # ballot_papers_selection == "CUSTOM_NUMBER" number = int(ballot_papers_number) number = max(1, number) @@ -568,16 +574,18 @@ class AssignmentPollPDF(PDFView): if self.poll.yesnoabstain: for option in options: candidate = option.candidate - cell.append(Paragraph(candidate.clean_name, - stylesheet['Ballot_option_name'])) + cell.append(Paragraph( + candidate.clean_name, stylesheet['Ballot_option_name'])) if candidate.name_suffix: - cell.append(Paragraph("(%s)" % candidate.name_suffix, + cell.append(Paragraph( + "(%s)" % candidate.name_suffix, stylesheet['Ballot_option_group'])) else: - cell.append(Paragraph(" ", - stylesheet['Ballot_option_group'])) - cell.append(Paragraph(circle + _("Yes") + "  " * 3 + circle - + _("No") + "  " * 3 + circle+ _("Abstention"), + cell.append(Paragraph( + " ", stylesheet['Ballot_option_group'])) + cell.append(Paragraph( + circle + _("Yes") + "  " * 3 + circle + + _("No") + "  " * 3 + circle + _("Abstention"), stylesheet['Ballot_option_YNA'])) # print ballot papers for user in xrange(number / 2): @@ -594,14 +602,16 @@ class AssignmentPollPDF(PDFView): else: for option in options: candidate = option.candidate - cell.append(Paragraph(circle + candidate.clean_name, + cell.append(Paragraph( + circle + candidate.clean_name, stylesheet['Ballot_option_name'])) if candidate.name_suffix: - cell.append(Paragraph("(%s)" % candidate.name_suffix, + cell.append(Paragraph( + "(%s)" % candidate.name_suffix, stylesheet['Ballot_option_group_right'])) else: - cell.append(Paragraph(" ", - stylesheet['Ballot_option_group_right'])) + cell.append(Paragraph( + " ", stylesheet['Ballot_option_group_right'])) # print ballot papers for user in xrange(number / 2): data.append([cell, cell]) @@ -615,9 +625,9 @@ class AssignmentPollPDF(PDFView): else: t = Table(data, 10.5 * cm, 29.7 * cm) - t.setStyle(TableStyle([('GRID', (0, 0), (-1, -1), 0.25, colors.grey), - ('VALIGN', (0, 0), (-1, -1), 'TOP'), - ])) + t.setStyle(TableStyle([ + ('GRID', (0, 0), (-1, -1), 0.25, colors.grey), + ('VALIGN', (0, 0), (-1, -1), 'TOP')])) story.append(t) @@ -629,16 +639,15 @@ class Config(FormView): def get_initial(self): return { 'assignment_publish_winner_results_only': - config['assignment_publish_winner_results_only'], + config['assignment_publish_winner_results_only'], 'assignment_pdf_ballot_papers_selection': - config['assignment_pdf_ballot_papers_selection'], + config['assignment_pdf_ballot_papers_selection'], 'assignment_pdf_ballot_papers_number': - config['assignment_pdf_ballot_papers_number'], + config['assignment_pdf_ballot_papers_number'], 'assignment_pdf_title': config['assignment_pdf_title'], 'assignment_pdf_preamble': config['assignment_pdf_preamble'], 'assignment_poll_vote_values': - config['assignment_poll_vote_values'], - } + config['assignment_poll_vote_values']} def form_valid(self, form): if form.cleaned_data['assignment_publish_winner_results_only']: @@ -655,8 +664,8 @@ class Config(FormView): form.cleaned_data['assignment_pdf_preamble'] config['assignment_poll_vote_values'] = \ form.cleaned_data['assignment_poll_vote_values'] - messages.success(self.request, - _('Election settings successfully saved.')) + messages.success( + self.request, _('Election settings successfully saved.')) return super(Config, self).form_valid(form) @@ -665,10 +674,11 @@ def register_tab(request): return Tab( title=_('Elections'), url=reverse('assignment_overview'), - permission=request.user.has_perm('assignment.can_see_assignment') - or request.user.has_perm('assignment.can_nominate_other') - or request.user.has_perm('assignment.can_nominate_self') - or request.user.has_perm('assignment.can_manage_assignment'), + permission=( + request.user.has_perm('assignment.can_see_assignment') or + request.user.has_perm('assignment.can_nominate_other') or + request.user.has_perm('assignment.can_nominate_self') or + request.user.has_perm('assignment.can_manage_assignment')), selected=selected, ) diff --git a/openslides/config/forms.py b/openslides/config/forms.py index ef1a9b4d8..5e7f001d2 100644 --- a/openslides/config/forms.py +++ b/openslides/config/forms.py @@ -15,8 +15,6 @@ from django.utils.translation import ugettext_lazy as _ from openslides.utils.forms import CssClassMixin -from openslides.config.models import config - class GeneralConfigForm(forms.Form, CssClassMixin): event_name = forms.CharField( diff --git a/openslides/config/models.py b/openslides/config/models.py index a816be865..01ac2fd1d 100644 --- a/openslides/config/models.py +++ b/openslides/config/models.py @@ -51,7 +51,7 @@ class Config(object): pass for receiver, value in default_config_value.send(sender='config', - key=key): + key=key): if value is not None: return value if settings.DEBUG: @@ -69,7 +69,6 @@ class Config(object): def __contains__(self, item): return ConfigStore.objects.filter(key=item).exists() - config = Config() @@ -81,7 +80,7 @@ def default_config(sender, key, **kwargs): return { 'event_name': 'OpenSlides', 'event_description': - _('Presentation and assembly system'), + _('Presentation and assembly system'), 'event_date': '', 'event_location': '', 'event_organizer': '', @@ -123,11 +122,9 @@ def set_submenu(sender, request, context, **kwargs): (reverse('config_%s' % appname), _(title), selected) ) - menu_links.append ( - (reverse('config_version'), _('Version'), - request.path == reverse('config_version')) - ) + menu_links.append(( + reverse('config_version'), _('Version'), + request.path == reverse('config_version'))) context.update({ - 'menu_links': menu_links, - }) + 'menu_links': menu_links}) diff --git a/openslides/config/views.py b/openslides/config/views.py index a84567c43..448b3d758 100644 --- a/openslides/config/views.py +++ b/openslides/config/views.py @@ -12,18 +12,18 @@ from django.conf import settings from django.contrib import messages -from django.contrib.auth.models import Group, Permission from django.core.urlresolvers import reverse from django.utils.importlib import import_module from django.utils.translation import ugettext as _ from openslides import get_version, get_git_commit_id, RELEASE - from openslides.utils.template import Tab from openslides.utils.views import FormView, TemplateView +from .forms import GeneralConfigForm +from .models import config -from openslides.config.forms import GeneralConfigForm -from openslides.config.models import config +# TODO: Do not import the participant module in config +from openslides.participant.api import get_or_create_anonymous_group class GeneralConfig(FormView): @@ -61,27 +61,12 @@ class GeneralConfig(FormView): # system if form.cleaned_data['system_enable_anonymous']: config['system_enable_anonymous'] = True - # check for Anonymous group and (re)create it as needed - try: - anonymous = Group.objects.get(name='Anonymous') - except Group.DoesNotExist: - default_perms = ['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.')) + get_or_create_anonymous_group() else: config['system_enable_anonymous'] = False - messages.success(self.request, - _('General settings successfully saved.')) + messages.success( + self.request, _('General settings successfully saved.')) return super(GeneralConfig, self).form_valid(form) diff --git a/openslides/global_settings.py b/openslides/global_settings.py index 24d40aefa..adb51aea7 100644 --- a/openslides/global_settings.py +++ b/openslides/global_settings.py @@ -13,15 +13,12 @@ import os import sys -_fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() -def _fs2unicode(s): - if isinstance(s, unicode): - return s - return s.decode(_fs_encoding) +from openslides.main import fs2unicode SITE_ROOT = os.path.realpath(os.path.dirname(__file__)) -AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend', +AUTHENTICATION_BACKENDS = ( + 'django.contrib.auth.backends.ModelBackend', 'openslides.utils.auth.AnonymousAuth',) LOGIN_URL = '/login/' @@ -48,12 +45,12 @@ USE_I18N = True USE_L10N = True LOCALE_PATHS = ( - _fs2unicode(os.path.join(SITE_ROOT, 'locale')), + fs2unicode(os.path.join(SITE_ROOT, 'locale')), ) # Absolute path to the directory that holds media. # Example: "/home/media/media.lawrence.com/" -MEDIA_ROOT = _fs2unicode(os.path.join(SITE_ROOT, './static/')) +MEDIA_ROOT = fs2unicode(os.path.join(SITE_ROOT, './static/')) # URL that handles the media served from MEDIA_ROOT. Make sure to use a # trailing slash if there is a path component (optional in other cases). @@ -62,17 +59,17 @@ MEDIA_URL = '' # Absolute path to the directory that holds static media from ``collectstatic`` # Example: "/home/media/static.lawrence.com/" -STATIC_ROOT = _fs2unicode(os.path.join(SITE_ROOT, '../site-static')) +STATIC_ROOT = fs2unicode(os.path.join(SITE_ROOT, '../site-static')) # URL that handles the media served from STATIC_ROOT. Make sure to use a # trailing slash if there is a path component (optional in other cases). # Examples: "http://static.lawrence.com", "http://example.com/static/" -STATIC_URL = '/static/' +STATIC_URL = '/static/' # Additional directories containing static files (not application specific) # Examples: "/home/media/lawrence.com/extra-static/" STATICFILES_DIRS = ( - _fs2unicode(os.path.join(SITE_ROOT, 'static')), + fs2unicode(os.path.join(SITE_ROOT, 'static')), ) #XXX: Note this setting (as well as our workaround finder) @@ -106,7 +103,7 @@ TEMPLATE_DIRS = ( # "C:/www/django/templates". # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. - _fs2unicode(os.path.join(SITE_ROOT, 'templates')), + fs2unicode(os.path.join(SITE_ROOT, 'templates')), ) INSTALLED_APPS = ( @@ -141,3 +138,6 @@ CACHES = { 'LOCATION': 'openslidecache' } } + +TEST_RUNNER = 'discover_runner.DiscoverRunner' +TEST_DISCOVER_TOP_LEVEL = os.path.dirname(os.path.dirname(__file__)) diff --git a/openslides/locale/de/LC_MESSAGES/django.mo b/openslides/locale/de/LC_MESSAGES/django.mo index e3d56e464..2662a31e2 100644 Binary files a/openslides/locale/de/LC_MESSAGES/django.mo and b/openslides/locale/de/LC_MESSAGES/django.mo differ diff --git a/openslides/locale/de/LC_MESSAGES/django.po b/openslides/locale/de/LC_MESSAGES/django.po index cebb45241..50db5277a 100644 --- a/openslides/locale/de/LC_MESSAGES/django.po +++ b/openslides/locale/de/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: OpenSlides 1.x\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-11-22 10:22+0100\n" +"POT-Creation-Date: 2012-11-27 21:23+0100\n" "PO-Revision-Date: 2012-07-28 11:07+0200\n" "Last-Translator: Emanuel Schuetze \n" "Language-Team: support@openslides.de\n" @@ -17,15 +17,15 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: global_settings.py:36 +#: global_settings.py:33 msgid "German" msgstr "Deutsch" -#: global_settings.py:37 +#: global_settings.py:34 msgid "English" msgstr "Englisch" -#: global_settings.py:38 +#: global_settings.py:35 msgid "French" msgstr "Französisch" @@ -33,43 +33,43 @@ msgstr "Französisch" msgid "Parent item" msgstr "Elternelement" -#: agenda/models.py:42 config/forms.py:61 motion/forms.py:22 -#: motion/models.py:545 motion/templates/motion/view.html:246 -#: projector/models.py:32 +#: agenda/models.py:34 config/forms.py:59 motion/forms.py:22 +#: motion/models.py:539 motion/templates/motion/view.html:246 +#: projector/models.py:29 msgid "Title" msgstr "Titel" -#: agenda/models.py:43 motion/forms.py:23 motion/models.py:546 -#: motion/templates/motion/view.html:247 projector/models.py:33 +#: agenda/models.py:35 motion/forms.py:23 motion/models.py:540 +#: motion/templates/motion/view.html:247 projector/models.py:30 msgid "Text" msgstr "Text" -#: agenda/models.py:44 agenda/templates/agenda/overview.html:65 -#: agenda/templates/agenda/view.html:13 participant/models.py:59 +#: agenda/models.py:36 agenda/templates/agenda/overview.html:65 +#: agenda/templates/agenda/view.html:13 participant/models.py:60 #: participant/templates/participant/overview.html:72 #: participant/templates/participant/user_detail.html:45 msgid "Comment" msgstr "Kommentar" -#: agenda/models.py:45 +#: agenda/models.py:37 msgid "Closed" msgstr "Abgeschlossen" -#: agenda/models.py:46 agenda/templates/agenda/overview.html:71 -#: projector/models.py:34 +#: agenda/models.py:38 agenda/templates/agenda/overview.html:71 +#: projector/models.py:31 msgid "Weight" msgstr "Gewichtung" -#: agenda/models.py:180 +#: agenda/models.py:171 msgid "Can see agenda" msgstr "Darf die Tagesordnung sehen" -#: agenda/models.py:181 +#: agenda/models.py:172 msgid "Can manage agenda" msgstr "Darf die Tagesordung verwalten" -#: agenda/models.py:189 agenda/slides.py:20 agenda/views.py:194 -#: agenda/views.py:195 agenda/views.py:214 agenda/views.py:229 +#: agenda/models.py:180 agenda/slides.py:20 agenda/views.py:191 +#: agenda/views.py:192 agenda/views.py:212 agenda/views.py:226 #: agenda/templates/agenda/base_agenda.html:10 #: agenda/templates/agenda/overview.html:8 #: agenda/templates/agenda/overview.html:52 @@ -79,34 +79,34 @@ msgstr "Darf die Tagesordung verwalten" msgid "Agenda" msgstr "Tagesordnung" -#: agenda/views.py:57 +#: agenda/views.py:54 msgid "You are not authorized to manage the agenda." msgstr "Sie sind nicht berechtigt die Tagesordnung zu ändern." -#: agenda/views.py:73 +#: agenda/views.py:70 msgid "Errors when reordering of the agenda" msgstr "Fehler beim Neusortieren der Tagesordnung" -#: agenda/views.py:134 +#: agenda/views.py:131 #, python-format msgid "Item %s was successfully modified." msgstr "Eintrag %s wurde erfolgreich bearbeitet." -#: agenda/views.py:155 +#: agenda/views.py:152 #, python-format msgid "Item %s was successfully created." msgstr "Eintrag %s wurde erfolgreich angelegt." -#: agenda/views.py:172 +#: agenda/views.py:169 msgid "Yes, with all child items." msgstr "Ja, mit allen Kindelementen." -#: agenda/views.py:180 +#: agenda/views.py:177 #, python-format msgid "Item %s and his children were successfully deleted." msgstr "Eintrag %s und seine Kindelemente wurde erfolgreich gelöscht." -#: agenda/views.py:185 +#: agenda/views.py:182 #, python-format msgid "Item %s was successfully deleted." msgstr "Eintrag %s wurde erfolgreich gelöscht." @@ -170,12 +170,12 @@ msgstr "Speichern" #: assignment/templates/assignment/poll_view.html:73 #: config/templates/config/general.html:60 #: motion/templates/motion/config.html:17 motion/templates/motion/edit.html:33 -#: motion/templates/motion/import.html:27 +#: motion/templates/motion/import.html:30 #: motion/templates/motion/poll_view.html:59 #: participant/templates/participant/config.html:17 #: participant/templates/participant/edit.html:38 #: participant/templates/participant/group_edit.html:32 -#: participant/templates/participant/import.html:27 +#: participant/templates/participant/import.html:28 #: participant/templates/participant/password_change.html:26 #: participant/templates/participant/settings.html:26 #: projector/templates/projector/new.html:20 @@ -197,10 +197,10 @@ msgstr "Übernehmen" #: agenda/templates/agenda/edit.html:35 #: assignment/templates/assignment/edit.html:37 -#: motion/templates/motion/edit.html:37 motion/templates/motion/import.html:31 +#: motion/templates/motion/edit.html:37 motion/templates/motion/import.html:34 #: participant/templates/participant/edit.html:42 #: participant/templates/participant/group_edit.html:36 -#: participant/templates/participant/import.html:31 +#: participant/templates/participant/import.html:32 msgid "required" msgstr "erforderlich" @@ -226,25 +226,25 @@ msgstr "Zusammenfassung für diesen Eintrag projizieren" msgid "Do you want to save the changed order of agenda items?" msgstr "Möchten Sie die geänderte Reihenfolge der Einträge speichern?" -#: agenda/templates/agenda/overview.html:46 assignment/models.py:301 -#: assignment/views.py:579 assignment/templates/assignment/view.html:168 +#: agenda/templates/agenda/overview.html:46 assignment/models.py:294 +#: assignment/views.py:587 assignment/templates/assignment/view.html:168 #: assignment/templates/assignment/view.html:172 #: assignment/templates/projector/Assignment.html:78 -#: assignment/templates/projector/Assignment.html:82 motion/models.py:579 -#: motion/views.py:842 motion/views.py:893 +#: assignment/templates/projector/Assignment.html:82 motion/models.py:574 +#: motion/views.py:830 motion/views.py:881 #: motion/templates/motion/view.html:79 -#: motion/templates/projector/Motion.html:37 utils/utils.py:53 -#: utils/views.py:111 +#: motion/templates/projector/Motion.html:37 utils/utils.py:55 +#: utils/views.py:108 msgid "Yes" msgstr "Ja" -#: agenda/templates/agenda/overview.html:47 assignment/models.py:301 -#: assignment/views.py:580 assignment/templates/assignment/view.html:169 -#: assignment/templates/projector/Assignment.html:79 motion/models.py:579 -#: motion/views.py:842 motion/views.py:894 +#: agenda/templates/agenda/overview.html:47 assignment/models.py:294 +#: assignment/views.py:588 assignment/templates/assignment/view.html:169 +#: assignment/templates/projector/Assignment.html:79 motion/models.py:574 +#: motion/views.py:830 motion/views.py:882 #: motion/templates/motion/view.html:80 -#: motion/templates/projector/Motion.html:38 utils/utils.py:53 -#: utils/views.py:111 +#: motion/templates/projector/Motion.html:38 utils/utils.py:55 +#: utils/views.py:108 msgid "No" msgstr "Nein" @@ -318,7 +318,7 @@ msgstr "Löschen" msgid "Edit" msgstr "Bearbeiten" -#: assignment/forms.py:24 assignment/models.py:57 assignment/views.py:385 +#: assignment/forms.py:24 assignment/models.py:51 assignment/views.py:381 #: assignment/templates/assignment/view.html:13 #: assignment/templates/projector/Assignment.html:21 msgid "Number of available posts" @@ -333,177 +333,177 @@ msgid "Only publish voting results for selected winners (Projector view only)" msgstr "" "Wahlergebnisse der nicht gewählten Kandidaten auf dem Projektor verbergen" -#: assignment/forms.py:47 motion/forms.py:77 +#: assignment/forms.py:46 motion/forms.py:77 msgid "Number of ballot papers (selection)" msgstr "Anzahl der Stimmzettel (Vorauswahl)" -#: assignment/forms.py:49 motion/forms.py:79 +#: assignment/forms.py:48 motion/forms.py:79 msgid "Number of all delegates" msgstr "Anzahl aller Delegierten" -#: assignment/forms.py:50 motion/forms.py:80 +#: assignment/forms.py:49 motion/forms.py:80 msgid "Number of all participants" msgstr "Anzahl aller Teilnehmer/innen" -#: assignment/forms.py:51 motion/forms.py:81 +#: assignment/forms.py:50 motion/forms.py:81 msgid "Use the following custom number" msgstr "Verwende die folgende benutzerdefinierte Anzahl" -#: assignment/forms.py:58 motion/forms.py:88 +#: assignment/forms.py:55 motion/forms.py:88 msgid "Custom number of ballot papers" msgstr "Benutzerdefinierte Anzahl von Stimmzetteln" -#: assignment/forms.py:63 +#: assignment/forms.py:59 msgid "Title for PDF document (all elections)" msgstr "Titel für PDF-Dokument (alle Wahlen)" -#: assignment/forms.py:68 +#: assignment/forms.py:63 msgid "Preamble text for PDF document (all elections)" msgstr "Einleitungstext für PDF-Dokument (alle Wahlen) " -#: assignment/forms.py:72 +#: assignment/forms.py:67 msgid "Election method" msgstr "Wahlmethode" -#: assignment/forms.py:74 +#: assignment/forms.py:69 msgid "Automatic assign of method." msgstr "Automatische Zuordnung der Methode." -#: assignment/forms.py:75 +#: assignment/forms.py:70 msgid "Always one option per candidate." msgstr "Eine Stimme pro Kandidat/in." -#: assignment/forms.py:76 +#: assignment/forms.py:71 msgid "Always Yes-No-Abstain per candidate." msgstr "Ja, Nein, Enthaltung pro Kandidat/in." -#: assignment/models.py:48 assignment/templates/assignment/overview.html:15 +#: assignment/models.py:44 assignment/templates/assignment/overview.html:15 #: assignment/templates/assignment/view.html:23 msgid "Searching for candidates" msgstr "Auf Kandidatensuche" -#: assignment/models.py:49 assignment/templates/assignment/overview.html:16 +#: assignment/models.py:45 assignment/templates/assignment/overview.html:16 #: assignment/templates/assignment/view.html:25 msgid "Voting" msgstr "Im Wahlvorgang" -#: assignment/models.py:50 assignment/templates/assignment/overview.html:17 +#: assignment/models.py:46 assignment/templates/assignment/overview.html:17 #: assignment/templates/assignment/view.html:27 msgid "Finished" msgstr "Abgeschlossen" -#: assignment/models.py:53 +#: assignment/models.py:49 msgid "Name" msgstr "Name" -#: assignment/models.py:55 participant/models.py:134 +#: assignment/models.py:50 participant/models.py:144 msgid "Description" msgstr "Beschreibung" -#: assignment/models.py:59 +#: assignment/models.py:54 msgid "Comment on the ballot paper" msgstr "Kommentar für den Stimmzettel" -#: assignment/models.py:69 motion/models.py:337 +#: assignment/models.py:64 motion/models.py:332 #, python-format msgid "%s is not a valid status." msgstr "%s ist kein gültiger Status." -#: assignment/models.py:71 +#: assignment/models.py:67 #, python-format msgid "The assignment status is already %s." msgstr "Der Wahlstatus ist bereits %s." -#: assignment/models.py:85 +#: assignment/models.py:80 #, python-format msgid "%s is already a candidate." msgstr "%s ist bereits ein/e Kandidat/in." -#: assignment/models.py:87 assignment/views.py:202 +#: assignment/models.py:82 assignment/views.py:195 msgid "The candidate list is already closed." msgstr "Die Kandidatenliste ist bereits geschlossen." -#: assignment/models.py:94 +#: assignment/models.py:89 #, python-format msgid "%s does not want to be a candidate." msgstr "%s möchte nicht kandidieren." -#: assignment/models.py:108 +#: assignment/models.py:103 #, python-format msgid "%s is no candidate" msgstr "%s ist kein/e Kandidat/in" -#: assignment/models.py:254 +#: assignment/models.py:247 msgid "Can see assignment" msgstr "Darf Wahlen sehen" -#: assignment/models.py:256 +#: assignment/models.py:249 msgid "Can nominate another person" msgstr "Darf andere Personen für Wahlen vorschlagen" -#: assignment/models.py:257 +#: assignment/models.py:250 msgid "Can nominate themselves" msgstr "Darf selbst für Wahlen kandidieren" -#: assignment/models.py:258 +#: assignment/models.py:251 msgid "Can manage assignment" msgstr "Darf Wahlen verwalten" -#: assignment/models.py:302 motion/models.py:580 +#: assignment/models.py:294 motion/models.py:574 msgid "Abstain" msgstr "Enthaltung" -#: assignment/models.py:304 motion/templates/motion/poll_view.html:22 +#: assignment/models.py:296 motion/templates/motion/poll_view.html:22 msgid "Votes" msgstr "Stimmen" -#: assignment/models.py:321 +#: assignment/models.py:313 #, python-format msgid "Ballot %d" msgstr "Wahlgang %d" -#: assignment/models.py:330 assignment/views.py:342 assignment/views.py:666 -#: assignment/views.py:680 +#: assignment/models.py:322 assignment/views.py:336 assignment/views.py:675 +#: assignment/views.py:690 #: assignment/templates/assignment/base_assignment.html:14 #: assignment/templates/assignment/overview.html:6 #: assignment/templates/assignment/overview.html:9 msgid "Elections" msgstr "Wahlen" -#: assignment/views.py:88 +#: assignment/views.py:80 #, python-format msgid "Candidate %s was nominated successfully." msgstr "Kandidat/in %s wurde erfolgreich vorgeschlagen." -#: assignment/views.py:130 +#: assignment/views.py:122 msgid "New election was successfully created." msgstr "Neue Wahl wurde erfolgreich angelegt." -#: assignment/views.py:132 +#: assignment/views.py:124 msgid "Election was successfully modified." msgstr "Wahl wurde erfolgreich geändert." -#: assignment/views.py:138 motion/views.py:257 motion/views.py:701 -#: participant/views.py:506 participant/views.py:529 utils/views.py:225 -#: utils/views.py:243 utils/views.py:267 +#: assignment/views.py:130 motion/views.py:245 motion/views.py:689 +#: participant/views.py:508 participant/views.py:531 utils/views.py:222 +#: utils/views.py:240 utils/views.py:264 msgid "Please check the form for errors." msgstr "Bitte kontrollieren Sie das Formular nach Fehlern." -#: assignment/views.py:157 +#: assignment/views.py:149 #, python-format msgid "Election %s was successfully deleted." msgstr "Wahl %s wurde erfolgreich gelöscht." -#: assignment/views.py:170 +#: assignment/views.py:162 #, python-format msgid "Election status was set to: %s." msgstr "Wahlstatus wurde gesetzt auf: %s." -#: assignment/views.py:183 +#: assignment/views.py:175 msgid "You have set your candidature successfully." msgstr "Sie haben Ihre Kandidatur erfolgreich gesetzt." -#: assignment/views.py:199 +#: assignment/views.py:192 msgid "" "You have withdrawn your candidature successfully. You can not be nominated " "by other participants anymore." @@ -511,71 +511,71 @@ msgstr "" "Sie haben Ihre Kandidatur erfolgreich zurückgezogen. Sie können nun von " "anderen Teilnehmer/innen nicht mehr vorgeschlagen werden." -#: assignment/views.py:220 +#: assignment/views.py:213 #, python-format msgid "Candidate %s was withdrawn successfully." msgstr "Die Kandidatur von %s wurde erfolgreich zurückgezogen." -#: assignment/views.py:222 +#: assignment/views.py:215 #, python-format msgid "%s was unblocked successfully." msgstr "%s wurde erfolgreich freigegeben." -#: assignment/views.py:226 +#: assignment/views.py:219 #, python-format msgid "Do you really want to withdraw %s from the election?" msgstr "Soll %s wirklich von der Wahl zurückgezogen werden?" -#: assignment/views.py:228 +#: assignment/views.py:221 #, python-format msgid "Do you really want to unblock %s for the election?" msgstr "Soll %s wirklich für die Wahl freigegeben werden?" -#: assignment/views.py:243 +#: assignment/views.py:236 msgid "New ballot was successfully created." msgstr "Neuer Wahlgang erfolgreich angelegt." -#: assignment/views.py:275 +#: assignment/views.py:268 #, python-format msgid "Ballot ID %d does not exist." msgstr "Wahlgang-ID %d existiert nicht." -#: assignment/views.py:282 +#: assignment/views.py:275 msgid "Ballot successfully published." msgstr "Wahlgang wurde erfolgreich veröffentlicht." -#: assignment/views.py:284 +#: assignment/views.py:277 msgid "Ballot successfully unpublished." msgstr "Wahlgang wurde erfolgreich unveröffentlicht." -#: assignment/views.py:297 +#: assignment/views.py:290 msgid "not elected" msgstr "nicht gewählt" -#: assignment/views.py:300 assignment/views.py:483 +#: assignment/views.py:293 assignment/views.py:482 #: assignment/templates/assignment/view.html:48 msgid "elected" msgstr "gewählt" -#: assignment/views.py:328 +#: assignment/views.py:321 msgid "Ballot was successfully deleted." msgstr "Abstimmung wurde erfolgreich gelöscht." -#: assignment/views.py:339 +#: assignment/views.py:333 msgid "Assignment" msgstr "Wahl" -#: assignment/views.py:360 assignment/templates/assignment/overview.html:59 +#: assignment/views.py:356 assignment/templates/assignment/overview.html:59 #: assignment/templates/assignment/widget.html:23 msgid "No assignments available." msgstr "Keine Wahlen vorhanden." -#: assignment/views.py:379 +#: assignment/views.py:375 #, python-format msgid "Election: %s" msgstr "Wahlen: %s" -#: assignment/views.py:391 assignment/views.py:424 +#: assignment/views.py:388 assignment/views.py:424 #: assignment/templates/assignment/overview.html:26 #: assignment/templates/assignment/poll_view.html:18 #: assignment/templates/assignment/view.html:37 @@ -585,12 +585,12 @@ msgstr "Wahlen: %s" msgid "Candidates" msgstr "Kandidaten/innen" -#: assignment/views.py:412 motion/views.py:835 +#: assignment/views.py:413 motion/views.py:823 #: motion/templates/motion/view.html:44 msgid "Vote results" msgstr "Abstimmungsergebnis" -#: assignment/views.py:416 +#: assignment/views.py:417 #: assignment/templates/assignment/base_assignment.html:71 #: assignment/templates/assignment/poll_view.html:5 #: assignment/templates/assignment/poll_view.html:8 @@ -599,7 +599,7 @@ msgstr "Abstimmungsergebnis" msgid "ballot" msgstr "Wahlgang" -#: assignment/views.py:419 +#: assignment/views.py:420 msgid "ballots" msgstr "Wahlgänge" @@ -625,14 +625,14 @@ msgstr "Ungültige Stimmen" #: assignment/templates/assignment/view.html:202 #: assignment/templates/assignment/view.html:207 #: assignment/templates/projector/Assignment.html:109 -#: assignment/templates/projector/Assignment.html:115 motion/views.py:842 +#: assignment/templates/projector/Assignment.html:115 motion/views.py:830 #: motion/templates/motion/poll_view.html:35 #: motion/templates/motion/view.html:84 #: motion/templates/projector/Motion.html:42 poll/models.py:76 msgid "Votes cast" msgstr "Abgegebene Stimmen" -#: assignment/views.py:522 assignment/views.py:538 +#: assignment/views.py:523 assignment/views.py:541 #: assignment/templates/assignment/overview.html:25 #: assignment/templates/assignment/poll_view.html:5 #: assignment/templates/assignment/view.html:6 @@ -640,33 +640,33 @@ msgstr "Abgegebene Stimmen" msgid "Election" msgstr "Wahl" -#: assignment/views.py:544 +#: assignment/views.py:548 #, python-format msgid "%d. ballot" msgstr "%d. Wahlgang" -#: assignment/views.py:545 +#: assignment/views.py:550 #, python-format msgid "%d candidate" msgid_plural "%d candidates" msgstr[0] "%d Kandidat/in" msgstr[1] "%d Kandidaten/innen" -#: assignment/views.py:547 +#: assignment/views.py:552 #, python-format msgid "%d available post" msgid_plural "%d available posts" msgstr[0] "%d verfügbare Posten" msgstr[1] "%d verfügbare Posten" -#: assignment/views.py:580 assignment/templates/assignment/view.html:170 -#: assignment/templates/projector/Assignment.html:80 motion/views.py:842 -#: motion/views.py:895 motion/templates/motion/view.html:81 +#: assignment/views.py:588 assignment/templates/assignment/view.html:170 +#: assignment/templates/projector/Assignment.html:80 motion/views.py:830 +#: motion/views.py:883 motion/templates/motion/view.html:81 #: motion/templates/projector/Motion.html:39 msgid "Abstention" msgstr "Enthaltung" -#: assignment/views.py:659 +#: assignment/views.py:668 msgid "Election settings successfully saved." msgstr "Wahl-Einstellungen wurden erfolgreich gespeichert." @@ -722,7 +722,7 @@ msgstr "Wahl-Einstellungen" #: assignment/templates/assignment/overview.html:14 #: assignment/templates/assignment/overview.html:27 #: assignment/templates/assignment/view.html:11 -#: assignment/templates/projector/Assignment.html:18 motion/views.py:813 +#: assignment/templates/projector/Assignment.html:18 motion/views.py:801 #: motion/templates/motion/overview.html:20 #: motion/templates/motion/overview.html:40 #: motion/templates/motion/view.html:34 @@ -766,13 +766,13 @@ msgid "Special values" msgstr "Spezielle Werte" #: assignment/templates/assignment/poll_view.html:12 -#: motion/templates/motion/poll_view.html:14 poll/models.py:235 +#: motion/templates/motion/poll_view.html:14 poll/models.py:234 msgid "majority" msgstr "Mehrheit" #: assignment/templates/assignment/poll_view.html:12 -#: motion/templates/motion/poll_view.html:14 poll/models.py:237 -#: poll/models.py:239 +#: motion/templates/motion/poll_view.html:14 poll/models.py:236 +#: poll/models.py:238 msgid "undocumented" msgstr "nicht erfasst" @@ -844,7 +844,7 @@ msgid "was not a
candidate" msgstr "war kein Kandidat" #: assignment/templates/assignment/view.html:191 -#: assignment/templates/projector/Assignment.html:100 motion/views.py:842 +#: assignment/templates/projector/Assignment.html:100 motion/views.py:830 #: motion/templates/motion/view.html:82 #: motion/templates/projector/Motion.html:40 msgid "Invalid" @@ -854,31 +854,31 @@ msgstr "Ungültig" msgid "No results available." msgstr "Keine Ergebnisse vorhanden." -#: config/forms.py:24 +#: config/forms.py:22 msgid "Event name" msgstr "Veranstaltungsname" -#: config/forms.py:30 +#: config/forms.py:28 msgid "Short description of event" msgstr "Kurzbeschreibung der Veranstaltung" -#: config/forms.py:38 +#: config/forms.py:36 msgid "Event date" msgstr "Veranstaltungszeitraum" -#: config/forms.py:44 +#: config/forms.py:42 msgid "Event location" msgstr "Veranstaltungsort" -#: config/forms.py:50 +#: config/forms.py:48 msgid "Event organizer" msgstr "Veranstalter" -#: config/forms.py:55 +#: config/forms.py:53 msgid "Allow access for anonymous guest users" msgstr "Erlaube Zugriff für anonyme Gast-Nutzer" -#: config/forms.py:67 participant/forms.py:114 +#: config/forms.py:65 participant/forms.py:123 msgid "Welcome text" msgstr "Willkommenstext" @@ -886,42 +886,34 @@ msgstr "Willkommenstext" msgid "Can manage configuration" msgstr "Darf die Konfiguration verwalten" -#: config/models.py:84 +#: config/models.py:83 msgid "Presentation and assembly system" msgstr "Präsentations- und Versammlungssystem" -#: config/models.py:89 +#: config/models.py:88 msgid "Welcome to OpenSlides" msgstr "Willkommen bei OpenSlides" -#: config/models.py:90 +#: config/models.py:89 msgid "[Place for your welcome text.]" msgstr "[Platz für Ihren Begrüßungstext.]" -#: config/models.py:103 +#: config/models.py:102 msgid "General" msgstr "Allgemein" -#: config/models.py:127 config/templates/config/version.html:5 +#: config/models.py:126 config/templates/config/version.html:5 #: config/templates/config/version.html:8 -#: config/templates/config/version.html:11 motion/views.py:827 +#: config/templates/config/version.html:11 motion/views.py:815 #: motion/templates/motion/view.html:214 motion/templates/motion/view.html:244 msgid "Version" msgstr "Version" -#: config/views.py:78 -msgid "" -"Anonymous access enabled. Please modify the \"Anonymous\" group to fit your " -"required permissions." -msgstr "" -"Anonymer Zugriff aktiviert. Bitte setzen Sie die Rechte der Gruppe " -"\"Anonymous\" passend zum gewünschten Zugriffslevel." - -#: config/views.py:84 +#: config/views.py:69 msgid "General settings successfully saved." msgstr "Allgemeine Einstellungen erfolgreich gespeichert." -#: config/views.py:119 config/templates/config/base_config.html:7 +#: config/views.py:104 config/templates/config/base_config.html:7 msgid "Configuration" msgstr "Konfiguration" @@ -942,7 +934,7 @@ msgstr "Willkommens-Widget" msgid "System" msgstr "System" -#: motion/forms.py:25 motion/models.py:547 motion/views.py:861 +#: motion/forms.py:25 motion/models.py:541 motion/views.py:849 #: motion/templates/motion/view.html:229 motion/templates/motion/view.html:249 #: motion/templates/projector/Motion.html:77 msgid "Reason" @@ -956,18 +948,18 @@ msgstr "Triviale Änderung" msgid "Trivial changes don't create a new version." msgstr "Triviale Änderungen erzeugen keine neue Version." -#: motion/forms.py:35 motion/models.py:68 motion/views.py:780 +#: motion/forms.py:35 motion/models.py:63 motion/views.py:768 #: motion/templates/motion/overview.html:41 #: motion/templates/motion/view.html:18 #: motion/templates/projector/Motion.html:55 msgid "Submitter" msgstr "Antragsteller/in" -#: motion/forms.py:44 motion/views.py:800 motion/templates/motion/view.html:22 +#: motion/forms.py:44 motion/views.py:788 motion/templates/motion/view.html:22 msgid "Supporters" msgstr "Unterstützer/innen" -#: motion/forms.py:50 participant/forms.py:102 +#: motion/forms.py:50 participant/forms.py:111 msgid "CSV File" msgstr "CSV-Datei" @@ -1008,74 +1000,74 @@ msgid "Warning: Trivial changes undermine the motions autorisation system." msgstr "" "Warnung: Triviale Änderungen unterlaufen das Zulassungssystem von Anträgen." -#: motion/models.py:47 +#: motion/models.py:42 msgid "Published" msgstr "Veröffentlicht" -#: motion/models.py:48 +#: motion/models.py:43 msgid "Permitted" msgstr "Zugelassen" -#: motion/models.py:49 motion/templates/motion/overview.html:24 +#: motion/models.py:44 motion/templates/motion/overview.html:24 #: motion/templates/motion/view.html:167 msgid "Accepted" msgstr "Angenommen" -#: motion/models.py:50 motion/templates/motion/overview.html:25 +#: motion/models.py:45 motion/templates/motion/overview.html:25 #: motion/templates/motion/view.html:172 msgid "Rejected" msgstr "Abgelehnt" -#: motion/models.py:51 +#: motion/models.py:46 msgid "Withdrawed" msgstr "Zurückgezogen" -#: motion/models.py:52 motion/templates/motion/view.html:180 +#: motion/models.py:47 motion/templates/motion/view.html:180 msgid "Adjourned" msgstr "Vertagt" # please check! -#: motion/models.py:53 motion/templates/motion/view.html:183 +#: motion/models.py:48 motion/templates/motion/view.html:183 msgid "Not Concerned" msgstr "Nicht befasst" # please check! -#: motion/models.py:54 motion/templates/motion/view.html:186 +#: motion/models.py:49 motion/templates/motion/view.html:186 msgid "Commited a bill" msgstr "Verwiesen (in Ausschuss)" -#: motion/models.py:55 +#: motion/models.py:50 msgid "Rejected (not authorized)" msgstr "Verworfen (nicht zulässig)" -#: motion/models.py:56 motion/templates/motion/overview.html:27 +#: motion/models.py:51 motion/templates/motion/overview.html:27 msgid "Needs Review" msgstr "Benötigt Review" -#: motion/models.py:105 +#: motion/models.py:100 #, python-format msgid "Version %d authorized" msgstr "Version %d zugelassen" -#: motion/models.py:112 +#: motion/models.py:107 #, python-format msgctxt "Rejected means not authorized" msgid "Version %d rejected" msgstr "Version %d verworfen" -#: motion/models.py:141 +#: motion/models.py:136 msgid "Searching for supporters." msgstr "Auf Unterstützersuche." -#: motion/models.py:143 +#: motion/models.py:138 msgid "Not yet authorized." msgstr "Noch nicht zugelassen." -#: motion/models.py:145 +#: motion/models.py:140 msgid "Not yet authorized changes." msgstr "Noch nicht zugelassene Änderungen." -#: motion/models.py:225 +#: motion/models.py:223 #, python-format msgid "" "Trivial changes to version %(version)d; changed fields: %(changed_fields)s" @@ -1083,51 +1075,51 @@ msgstr "" "Triviale Änderung an Version %(version)d; Geänderte Felder: " "%(changed_fields)s" -#: motion/models.py:236 +#: motion/models.py:235 #, python-format msgid "Version %s created" msgstr "Version %s erstellt" -#: motion/models.py:246 +#: motion/models.py:244 msgid "Supporters removed" msgstr "Unterstützer/innen gelöscht" -#: motion/models.py:255 +#: motion/models.py:253 #, python-format msgid "Status reseted to: %s" msgstr "Status zurückgesetzt auf: %s" -#: motion/models.py:267 +#: motion/models.py:265 #, python-format msgid "Supporter: +%s" msgstr "Unterstützer/in: +%s" -#: motion/models.py:280 +#: motion/models.py:278 #, python-format msgid "Supporter: -%s" msgstr "Unterstützer/in: -%s" -#: motion/models.py:297 +#: motion/models.py:294 #, python-format msgid "Number set: %s" msgstr "Nummer gesetzt: %s" -#: motion/models.py:310 +#: motion/models.py:307 #, python-format msgid "Version %s authorized" msgstr "Version %s zugelassen" -#: motion/models.py:324 +#: motion/models.py:319 #, python-format msgid "Version %s not authorized" msgstr "Version %s nicht zugelassen" -#: motion/models.py:340 +#: motion/models.py:335 #, python-format msgid "The motion status is already '%s.'" msgstr "Der Antragsstatus ist bereits '%s'." -#: motion/models.py:348 +#: motion/models.py:343 #, python-format msgid "" "The motion status is: '%(currentstatus)s'. You can not set the status to " @@ -1136,15 +1128,15 @@ msgstr "" "Der Antragsstatus ist: '%(currentstatus)s'. Sie können den Status nicht auf " "'%(newstatus)s' setzen." -#: motion/models.py:356 +#: motion/models.py:351 msgid "Status modified" msgstr "Status geändert" -#: motion/models.py:449 motion/models.py:451 +#: motion/models.py:443 motion/models.py:445 msgid "by" msgstr "von" -#: motion/models.py:459 motion/templates/motion/view.html:210 +#: motion/models.py:453 motion/templates/motion/view.html:210 #: motion/templates/motion/widget.html:27 #: motion/templates/projector/Motion.html:65 #: participant/templates/participant/personal_info_widget.html:13 @@ -1152,62 +1144,62 @@ msgstr "von" msgid "no number" msgstr "ohne Nummer" -#: motion/models.py:460 motion/templates/motion/widget.html:23 +#: motion/models.py:454 motion/templates/motion/widget.html:23 #: participant/templates/participant/personal_info_widget.html:9 #: participant/templates/participant/personal_info_widget.html:28 msgid "motion" msgstr "Antrag" -#: motion/models.py:485 +#: motion/models.py:479 msgid "Poll created" msgstr "Abstimmung erstellt" -#: motion/models.py:536 +#: motion/models.py:530 msgid "Can see motions" msgstr "Darf Anträge sehen" -#: motion/models.py:537 +#: motion/models.py:531 msgid "Can create motions" msgstr "Darf Anträge erstellen" -#: motion/models.py:538 +#: motion/models.py:532 msgid "Can support motions" msgstr "Darf Anträge unterstützen" -#: motion/models.py:539 +#: motion/models.py:533 msgid "Can manage motions" msgstr "Darf Anträge verwalten" -#: motion/models.py:606 +#: motion/models.py:600 msgid "The assembly may decide," msgstr "Die Versammlung möge beschließen," -#: motion/models.py:609 motion/views.py:730 motion/views.py:955 -#: motion/views.py:966 motion/templates/motion/base_motion.html:9 +#: motion/models.py:603 motion/views.py:718 motion/views.py:943 +#: motion/views.py:954 motion/templates/motion/base_motion.html:9 #: motion/templates/motion/overview.html:7 #: motion/templates/motion/overview.html:10 msgid "Motions" msgstr "Anträge" -#: motion/views.py:180 +#: motion/views.py:173 msgid "You have not the necessary rights to create or edit motions." msgstr "" "Sie haben nicht die nötigen Rechte, um Anträge zu erstellen oder zu " "bearbeiten." -#: motion/views.py:185 +#: motion/views.py:178 msgid "You can not edit this motion." msgstr "Sie dürfen diesen Antrag nicht bearbeiten." -#: motion/views.py:248 +#: motion/views.py:236 msgid "New motion was successfully created." msgstr "Neuer Antrag wurde erfolgreich angelegt." -#: motion/views.py:250 +#: motion/views.py:238 msgid "Motion was successfully modified." msgstr "Antrag wurde erfolgreich geändert." -#: motion/views.py:264 +#: motion/views.py:252 msgid "" "Attention: Do you really want to edit this motion? The supporters will " "not be removed automatically because you can manage motions. Please " @@ -1217,7 +1209,7 @@ msgstr "" "werden nicht automatisch entfernt, da Sie Anträge verwalten dürfen. " "Prüfen Sie, ob die Unterstützungen noch gültig sind." -#: motion/views.py:266 +#: motion/views.py:254 #, python-format msgid "" "Attention: Do you really want to edit this motion? All %s supporters " @@ -1226,128 +1218,128 @@ msgstr "" "Wollen Sie den Antrag wirklich ändern? Alle %s Unterstützer/innen " "werden dann automatisch entfernt. Versuchen Sie diese erneut zu gewinnen." -#: motion/views.py:298 +#: motion/views.py:286 msgid "Motion number was successfully set." msgstr "Antragsnummer wurde erfolgreich gesetzt." -#: motion/views.py:314 +#: motion/views.py:302 msgid "Motion was successfully authorized." msgstr "Antrag wurde erfolgreich zugelassen." -#: motion/views.py:329 +#: motion/views.py:317 msgid "Motion was successfully rejected." msgstr "Antrag wurde erfolgreich verworfen." -#: motion/views.py:345 +#: motion/views.py:333 #, python-format msgid "Motion status was set to: %s." msgstr "Antragsstatus wurde gesetzt auf: %s." -#: motion/views.py:361 +#: motion/views.py:349 msgid "Motion status was reset." msgstr "Antragsstatus wurde zurückgesetzt." -#: motion/views.py:388 +#: motion/views.py:376 msgid "You can not support this motion." msgstr "Sie dürfen diesen Antrag nicht unterstützen." -#: motion/views.py:391 +#: motion/views.py:379 msgid "You can not unsupport this motion." msgstr "Sie dürfen Ihre Unterstützung für diesen Antrag nicht entziehen." -#: motion/views.py:402 +#: motion/views.py:390 msgid "Do you really want to support this motion?" msgstr "Wollen Sie wirklich diesen Antrag unterstützen?" -#: motion/views.py:404 +#: motion/views.py:392 msgid "Do you really want to unsupport this motion?" msgstr "Wollen Sie wirklich Ihre Unterstützung für diesen Antrag entziehen?" -#: motion/views.py:415 +#: motion/views.py:403 msgid "You have supported this motion successfully." msgstr "Sie haben den Antrag erfolgreich unterstützt." -#: motion/views.py:417 +#: motion/views.py:405 msgid "You have unsupported this motion successfully." msgstr "Sie haben dem Antrag erfolgreich Ihre Unterstützung entzogen." -#: motion/views.py:431 +#: motion/views.py:419 msgid "New vote was successfully created." msgstr "Neue Abstimmung erfolgreich angelegt." -#: motion/views.py:447 +#: motion/views.py:435 msgid "Poll deleted" msgstr "Abstimmung gelöscht" -#: motion/views.py:448 +#: motion/views.py:436 msgid "Poll was successfully deleted." msgstr "Abstimmung wurde erfolgreich gelöscht." -#: motion/views.py:450 +#: motion/views.py:438 #, python-format msgid "the %s. poll" msgstr "die %s. Abstimmung" -#: motion/views.py:491 motion/views.py:500 +#: motion/views.py:479 motion/views.py:488 #, python-format msgid "You can not delete motion %s." msgstr "Sie können Antrag %s nicht löschen." -#: motion/views.py:496 motion/views.py:504 +#: motion/views.py:484 motion/views.py:492 #, python-format msgid "Motion %s was successfully deleted." msgstr "Antrag %s wurde erfolgreich gelöscht." -#: motion/views.py:506 +#: motion/views.py:494 msgid "Invalid request" msgstr "Ungültige Anfrage" -#: motion/views.py:530 +#: motion/views.py:518 msgid "Poll was updated" msgstr "Abstimmung wurde aktualisiert" -#: motion/views.py:547 +#: motion/views.py:535 #, python-format msgid "Version %s accepted." msgstr "Version %s akzeptiert." -#: motion/views.py:549 +#: motion/views.py:537 #, python-format msgid "Do you really want to authorize version %s?" msgstr "Soll Version %s wirklich zugelassen werden?" -#: motion/views.py:559 +#: motion/views.py:547 #, python-format msgid "Version %s rejected." msgstr "Version %s zurückgewiesen." -#: motion/views.py:561 +#: motion/views.py:549 msgid "ERROR by rejecting the version." msgstr "FEHLER beim Zurückweisen der Version." -#: motion/views.py:563 +#: motion/views.py:551 #, python-format msgid "Do you really want to reject version %s?" msgstr "Soll Version %s wirklich zurückgewiesen werden?" -#: motion/views.py:599 motion/views.py:603 motion/views.py:609 -#: motion/views.py:612 participant/api.py:76 +#: motion/views.py:587 motion/views.py:591 motion/views.py:597 +#: motion/views.py:600 participant/api.py:81 #, python-format msgid "Ignoring malformed line %d in import file." msgstr "Fehlerhafte Zeile %d der Quelldatei wurde ignoriert." -#: motion/views.py:620 +#: motion/views.py:608 #, python-format msgid "Ignoring line %d because the assigned group may not act as a person." msgstr "" "Fehlerhafte Zeile %d der Quelldatei wurde ignoriert da die verwendete Gruppe " "nicht als Person auftreten darf." -#: motion/views.py:629 +#: motion/views.py:617 msgid "Created by motion import." msgstr "Erstellt durch Antragsimport." -#: motion/views.py:643 +#: motion/views.py:631 #, python-format msgid "" "Ignoring line %d because it contains an incomplete first / last name pair." @@ -1355,52 +1347,52 @@ msgstr "" "Fehlerhafte Zeile %d der Quelldatei wurde ignoriert, da Vor- bzw. Nachname " "Leerstrings enthalten." -#: motion/views.py:681 +#: motion/views.py:669 #, python-format msgid "%d motion was successfully imported." msgid_plural "%d motions were successfully imported." msgstr[0] "%d Antrag wurde erfolgreich importiert." msgstr[1] "%d Anträge wurden erfolgreich importiert." -#: motion/views.py:684 +#: motion/views.py:672 #, python-format msgid "%d motion was successfully modified." msgid_plural "%d motions were successfully modified." msgstr[0] "%d Antrag wurde erfolgreich geändert." msgstr[1] "%d Anträge wurden erfolgreich geändert." -#: motion/views.py:687 +#: motion/views.py:675 #, python-format msgid "%d new user was added." msgid_plural "%d new users were added." msgstr[0] "%d neuer Nutzer wurde erstellt." msgstr[1] "%d neue Nutzer wurden erstellt." -#: motion/views.py:690 +#: motion/views.py:678 #, python-format msgid "%d new group was added." msgid_plural "%d new groups were added." msgstr[0] "%d neue Gruppe wurde erstellt." msgstr[1] "%d neue Gruppen wurden erstellt." -#: motion/views.py:693 +#: motion/views.py:681 #, python-format msgid "%d group assigned to motions." msgid_plural "%d groups assigned to motions." msgstr[0] "%d Gruppe wurde zugewiesen." msgstr[1] "%d Gruppen wurden zugewiesen." -#: motion/views.py:697 participant/api.py:92 +#: motion/views.py:685 participant/api.py:97 msgid "Import aborted because of severe errors in the input file." msgstr "Import auf Grund von schweren Fehlern in der Quelldatei abgebrochen." -#: motion/views.py:699 participant/api.py:94 +#: motion/views.py:687 participant/api.py:99 msgid "Import file has wrong character encoding, only UTF-8 is supported!" msgstr "" "Die Quelldatei benutzt eine ungültige Zeichenkodierung, es wird nur UTF-8 " "wird unterstützt!" -#: motion/views.py:703 +#: motion/views.py:691 msgid "" "Attention: Existing motions will be modified if you import new motions with " "the same number." @@ -1408,7 +1400,7 @@ msgstr "" "Achtung: Existierende Anträge werden geändert wenn Sie neue Anträge mit " "identischer Nummer importieren." -#: motion/views.py:704 +#: motion/views.py:692 msgid "" "Attention: Importing an motions without a number multiple times will create " "duplicates." @@ -1416,7 +1408,7 @@ msgstr "" "Achtung: Bei mehrfachem Import eines Antrags ohne Nummer können Duplikate " "entstehen." -#: motion/views.py:737 motion/views.py:875 +#: motion/views.py:725 motion/views.py:863 #: motion/templates/motion/poll_view.html:7 #: motion/templates/motion/poll_view.html:12 #: motion/templates/motion/view.html:7 motion/templates/motion/view.html:206 @@ -1426,21 +1418,21 @@ msgstr "" msgid "Motion" msgstr "Antrag" -#: motion/views.py:751 motion/templates/motion/overview.html:84 +#: motion/views.py:739 motion/templates/motion/overview.html:84 msgid "No motions available." msgstr "Keine Anträge vorhanden." -#: motion/views.py:756 motion/views.py:758 motion/views.py:773 -#: motion/views.py:775 motion/templates/motion/base_motion.html:24 +#: motion/views.py:744 motion/views.py:746 motion/views.py:761 +#: motion/views.py:763 motion/templates/motion/base_motion.html:24 #: motion/templates/projector/Motion.html:63 msgid "Motion No." msgstr "Antrag Nr." -#: motion/views.py:790 +#: motion/views.py:778 msgid "Signature" msgstr "Unterschrift" -#: motion/views.py:841 motion/templates/motion/base_motion.html:55 +#: motion/views.py:829 motion/templates/motion/base_motion.html:55 #: motion/templates/motion/poll_view.html:8 #: motion/templates/motion/poll_view.html:13 #: motion/templates/motion/view.html:66 motion/templates/motion/view.html:74 @@ -1448,21 +1440,21 @@ msgstr "Unterschrift" msgid "Vote" msgstr "Abstimmung" -#: motion/views.py:875 +#: motion/views.py:863 msgid "Poll" msgstr "Abstimmung" -#: motion/views.py:889 +#: motion/views.py:877 #, python-format msgid "Motion No. %s" msgstr "Antrag Nr. %s" -#: motion/views.py:891 +#: motion/views.py:879 #, python-format msgid "%d. Vote" msgstr "%d. Abstimmung" -#: motion/views.py:948 +#: motion/views.py:936 msgid "Motion settings successfully saved." msgstr "Antrags-Einstellungen wurden erfolgreich gespeichert." @@ -1517,28 +1509,34 @@ msgid "Select a CSV file to import motions!" msgstr "Wählen Sie eine CSV-Datei zum Importieren von Anträgen aus!" #: motion/templates/motion/import.html:11 -msgid "" -"Required comma separated values: {number, title, text, reason, " -"first_name, last_name, is_group} (number, reason and is_group are optional and may be empty)" -msgstr "" -"Erforderliche kommaseparierte Werte: {Nummer, Titel, Text, Begründung, " -"Vorname, Nachname, Gruppenantrag} (Nummer, " -"Begründung und Gruppenantrag sind optional und " -"können auch leer sein)" +#: participant/templates/participant/import.html:11 +msgid "Required comma separated values" +msgstr "Erforderliche kommaseparierte Werte" -#: motion/templates/motion/import.html:13 -#: participant/templates/participant/import.html:13 +#: motion/templates/motion/import.html:12 +msgid "number, title, text, reason, first_name, last_name, is_group" +msgstr "Nummer, Titel, Text, Begründung, Vorname, Nachname, Gruppenantrag" + +#: motion/templates/motion/import.html:14 +msgid "" +"number, reason and is_group are " +"optional and may be empty" +msgstr "" +"Nummer, Begründung und Gruppenantrag " +"sind optional und können auch leer sein." + +#: motion/templates/motion/import.html:16 +#: participant/templates/participant/import.html:14 msgid "Required CSV file encoding: UTF-8 (Unicode)." msgstr "Erforderliches CSV-Datei-Encoding: UTF-8 (Unicode)." -#: motion/templates/motion/import.html:16 -#: participant/templates/participant/import.html:16 +#: motion/templates/motion/import.html:19 +#: participant/templates/participant/import.html:17 msgid "A CSV example file is available in OpenSlides Wiki." msgstr "Eine CSV-Beispiel-Datei gibt es im OpenSlides Wiki." -#: motion/templates/motion/import.html:23 -#: participant/templates/participant/import.html:23 +#: motion/templates/motion/import.html:26 +#: participant/templates/participant/import.html:24 msgid "Import" msgstr "Importieren" @@ -1731,18 +1729,18 @@ msgstr "Keine Abstimmungsergebnisse vorhanden." msgid "Participant" msgstr "Teilnehmer" -#: participant/forms.py:26 participant/views.py:604 +#: participant/forms.py:27 participant/views.py:607 #: participant/templates/participant/group_overview.html:7 #: participant/templates/participant/group_overview.html:10 #: participant/templates/participant/user_detail.html:14 msgid "Groups" msgstr "Gruppen" -#: participant/forms.py:44 +#: participant/forms.py:52 msgid "Permissions" msgstr "Rechte" -#: participant/forms.py:47 participant/views.py:544 participant/views.py:590 +#: participant/forms.py:55 participant/views.py:546 participant/views.py:593 #: participant/templates/participant/base_participant.html:12 #: participant/templates/participant/overview.html:7 #: participant/templates/participant/overview.html:18 @@ -1750,211 +1748,211 @@ msgstr "Rechte" msgid "Participants" msgstr "Teilnehmer/innen" -#: participant/forms.py:83 -msgid "You can not edit the name for the anonymous user" -msgstr "Sie dürfen den Namen für Anonymous nicht bearbeiten." +#: participant/forms.py:92 +msgid "You can not edit the name for this group." +msgstr "Sie dürfen den Namen dieser Gruppe nicht bearbeiten." -#: participant/forms.py:87 +#: participant/forms.py:96 #, python-format msgid "Group name \"%s\" is reserved for internal use." msgstr "Der Gruppenname \"%s\" ist für interne Verwendung reserviert." -#: participant/forms.py:109 +#: participant/forms.py:118 msgid "System URL" msgstr "System URL" -#: participant/forms.py:110 participant/forms.py:115 +#: participant/forms.py:119 participant/forms.py:124 msgid "Printed in PDF of first time passwords only." msgstr "Erscheint nur im PDF der Erst-Passwörter" -#: participant/forms.py:118 +#: participant/forms.py:127 msgid "Sort participants by first name" msgstr "Teilnehmer/innen nach Vornamen sortieren" -#: participant/forms.py:119 +#: participant/forms.py:128 msgid "Disable for sorting by last name" msgstr "Deaktivieren für Sortierung nach Nachnamen" -#: participant/models.py:32 participant/templates/participant/overview.html:25 +#: participant/models.py:33 participant/templates/participant/overview.html:25 msgid "Male" msgstr "Männlich" -#: participant/models.py:33 participant/templates/participant/overview.html:26 +#: participant/models.py:34 participant/templates/participant/overview.html:26 msgid "Female" msgstr "Weiblich" -#: participant/models.py:36 participant/templates/participant/overview.html:38 +#: participant/models.py:37 participant/templates/participant/overview.html:38 msgid "Delegate" msgstr "Delegierter" -#: participant/models.py:37 participant/templates/participant/overview.html:39 +#: participant/models.py:38 participant/templates/participant/overview.html:39 msgid "Observer" msgstr "Beobachter" -#: participant/models.py:38 participant/templates/participant/overview.html:40 +#: participant/models.py:39 participant/templates/participant/overview.html:40 msgid "Staff" msgstr "Mitarbeiter" -#: participant/models.py:39 participant/templates/participant/overview.html:41 +#: participant/models.py:40 participant/templates/participant/overview.html:41 msgid "Guest" msgstr "Gast" -#: participant/models.py:44 participant/templates/participant/overview.html:30 +#: participant/models.py:45 participant/templates/participant/overview.html:30 #: participant/templates/participant/overview.html:68 msgid "Structure level" msgstr "Gliederungsebene" -#: participant/models.py:45 +#: participant/models.py:46 msgid "Will be shown after the name." msgstr "Wird nach dem Namen angezeigt." -#: participant/models.py:48 participant/templates/participant/overview.html:24 +#: participant/models.py:49 participant/templates/participant/overview.html:24 #: participant/templates/participant/user_detail.html:24 msgid "Gender" msgstr "Geschlecht" -#: participant/models.py:48 participant/models.py:51 participant/models.py:54 +#: participant/models.py:49 participant/models.py:52 participant/models.py:55 msgid "Only for filtering the participant list." msgstr "Nur zum Filtern der Teilnehmerliste." -#: participant/models.py:51 +#: participant/models.py:52 msgid "Typ" msgstr "Typ" -#: participant/models.py:53 participant/views.py:259 +#: participant/models.py:54 participant/views.py:255 #: participant/templates/participant/overview.html:45 #: participant/templates/participant/overview.html:70 #: participant/templates/participant/user_detail.html:34 msgid "Committee" msgstr "Amt" -#: participant/models.py:56 +#: participant/models.py:57 #: participant/templates/participant/user_detail.html:39 msgid "About me" msgstr "Über mich" -#: participant/models.py:57 +#: participant/models.py:58 msgid "Your profile text" msgstr "Ihr Profiltext" -#: participant/models.py:60 +#: participant/models.py:61 msgid "Only for notes." msgstr "Nur für Notizen." -#: participant/models.py:63 +#: participant/models.py:64 msgid "Default password" msgstr "Vorgegebenes Passwort" -#: participant/models.py:111 +#: participant/models.py:118 msgid "Can see participant" msgstr "Darf die Teilnehmer/inen sehen" -#: participant/models.py:113 +#: participant/models.py:120 msgid "Can manage participant" msgstr "Darf die Teilnehmer/inen verwalten" -#: participant/models.py:133 +#: participant/models.py:142 msgid "Use this group as participant" msgstr "Verwende diese Gruppe als Teilnehmer/in" -#: participant/models.py:133 +#: participant/models.py:143 msgid "For example as submitter of a motion." msgstr "Zum Beispiel als Antragsteller." -#: participant/models.py:225 +#: participant/models.py:237 msgid "Welcome to OpenSlides!" msgstr "Willkommen bei OpenSlides!" -#: participant/views.py:210 +#: participant/views.py:207 msgid "You can not delete yourself." msgstr "Sie dürfen sich nicht selbst löschen." -#: participant/views.py:212 -msgid "You can not delete the administrator." -msgstr "Sie dürfen den Administrator nicht löschen." - -#: participant/views.py:232 +#: participant/views.py:228 msgid "You can not deactivate yourself." msgstr "Sie dürfen sich nicht selbst deaktivieren." -#: participant/views.py:235 +#: participant/views.py:231 msgid "You can not deactivate the administrator." msgstr "Sie dürfen den Administrator nicht deaktivieren." -#: participant/views.py:254 +#: participant/views.py:250 msgid "Participant-list" msgstr "Teilnehmerliste" -#: participant/views.py:255 +#: participant/views.py:251 msgid "List of Participants" msgstr "Teilnehmerliste" -#: participant/views.py:258 participant/templates/participant/overview.html:67 +#: participant/views.py:254 participant/templates/participant/overview.html:67 msgid "Last Name" msgstr "Nachname" -#: participant/views.py:258 participant/templates/participant/overview.html:66 +#: participant/views.py:254 participant/templates/participant/overview.html:66 msgid "First Name" msgstr "Vorname" -#: participant/views.py:258 +#: participant/views.py:254 #: participant/templates/participant/group_overview.html:13 msgid "Group" msgstr "Gruppe" -#: participant/views.py:258 participant/templates/participant/overview.html:37 +#: participant/views.py:254 participant/templates/participant/overview.html:37 #: participant/templates/participant/overview.html:69 #: participant/templates/participant/user_detail.html:29 msgid "Type" msgstr "Typ" -#: participant/views.py:290 +#: participant/views.py:286 msgid "Participant-passwords" msgstr "Teilnehmer-Passwoerter" -#: participant/views.py:312 +#: participant/views.py:308 msgid "Account for OpenSlides" msgstr "Zugang für OpenSlides" -#: participant/views.py:314 +#: participant/views.py:310 #, python-format msgid "for %s" msgstr "für %s" -#: participant/views.py:317 +#: participant/views.py:313 #, python-format msgid "User: %s" msgstr "Nutzername: %s" -#: participant/views.py:321 +#: participant/views.py:317 #, python-format msgid "Password: %s" msgstr "Passwort: %s" -#: participant/views.py:326 +#: participant/views.py:322 #, python-format msgid "URL: %s" msgstr "URL: %s" -#: participant/views.py:368 +#: participant/views.py:364 #, python-format msgid "%d new participants were successfully imported." msgstr "%d neue Teilnehmer/innen wurden erfolgreich importiert." -#: participant/views.py:379 +#: participant/views.py:375 msgid "Do you really want to reset the password?" msgstr "Soll das Passwort wirklich zurückgesetzt werden?" -#: participant/views.py:392 +#: participant/views.py:388 #, python-format msgid "The Password for %s was successfully reset." msgstr "Das Passwort für %s wurde erfolgreich zurückgesetzt." -#: participant/views.py:471 +#: participant/views.py:445 +msgid "You can not delete this Group." +msgstr "Sie dürfen diese Gruppe nicht löschen." + +#: participant/views.py:473 msgid "Participants settings successfully saved." msgstr "Teilnehmer/innen-Einstellungen wurden erfolgreich gespeichert." -#: participant/views.py:481 +#: participant/views.py:483 #, python-format msgid "" "Installation was successfully! Use %(user)s (password: %(password)s) for " @@ -1967,15 +1965,15 @@ msgstr "" "Sie das Passwort nach der ersten Anmeldung! Anderenfalls erscheint diese " "Meldung weiterhin für alle und ist ein Sicherheitsrisiko." -#: participant/views.py:504 +#: participant/views.py:506 msgid "User settings successfully saved." msgstr "Nutzereinstellungen wurden erfolgreich gespeichert." -#: participant/views.py:526 +#: participant/views.py:528 msgid "Password successfully changed." msgstr "Passwort wurde erfolgreich geändert." -#: participant/views.py:576 +#: participant/views.py:579 msgid "My motions and elections" msgstr "Meine Anträge und Wahlen" @@ -2055,12 +2053,10 @@ msgid "Reset to First Password" msgstr "Auf Erst-Passwort zurücksetzen" #: participant/templates/participant/group_detail.html:14 -#: participant/templates/projector/GroupSlide.html:13 msgid "Members" msgstr "Mitglieder" #: participant/templates/participant/group_detail.html:19 -#: participant/templates/projector/GroupSlide.html:22 msgid "No members available." msgstr "Keine Mitglieder vorhanden." @@ -2073,13 +2069,11 @@ msgstr "Keine Gruppen vorhanden." msgid "Select a CSV file to import participants!" msgstr "Wählen Sie eine CSV-Datei zum Importieren von Teilnehmer/innen aus!" -#: participant/templates/participant/import.html:11 +#: participant/templates/participant/import.html:12 msgid "" -"Required comma separated values: {first_name, last_name, gender, " -"group, type, committee, comment}" +"first_name, last_name, gender, structure level, type, committee, comment" msgstr "" -"Erforderliche kommaseparierte Werte: {Vorname, Nachname, Geschlecht, " -"Gruppe, Typ, Amt, Kommentar}" +"Vorname, Nachname, Geschlecht, Gliederungsebene, Typ, Amt, Kommentar" #: participant/templates/participant/login.html:8 #: participant/templates/participant/login.html:16 @@ -2108,7 +2102,7 @@ msgstr "Weiter als Gast" msgid "Not specified" msgstr "Nicht angegeben" -#: participant/templates/participant/overview.html:53 projector/models.py:67 +#: participant/templates/participant/overview.html:53 projector/models.py:63 msgid "Active" msgstr "Aktiv" @@ -2131,15 +2125,15 @@ msgstr "von" msgid "Last Login" msgstr "Letzer Login" -#: participant/templates/participant/overview.html:100 +#: participant/templates/participant/overview.html:102 msgid "Change status to inactive" msgstr "Status ändern auf inaktiv" -#: participant/templates/participant/overview.html:103 +#: participant/templates/participant/overview.html:105 msgid "Change status to active" msgstr "Status ändern auf aktiv" -#: participant/templates/participant/overview.html:113 +#: participant/templates/participant/overview.html:115 #: participant/templates/participant/user_widget.html:22 msgid "No participants available." msgstr "Keine Teilnehmer/innen vorhanden." @@ -2201,37 +2195,37 @@ msgstr "Ungültige Stimmen" msgid "votes" msgstr "Stimmen" -#: projector/models.py:53 +#: projector/models.py:50 msgid "Can manage the projector" msgstr "Darf den Projektor steuern" -#: projector/models.py:54 +#: projector/models.py:51 msgid "Can see the projector" msgstr "Darf den Projektor sehen" -#: projector/models.py:55 +#: projector/models.py:52 msgid "Can see the dashboard" msgstr "Darf das Dashboard sehen" -#: projector/views.py:206 +#: projector/views.py:199 msgid "Errors in the form" msgstr "Fehler im Formular" -#: projector/views.py:384 projector/templates/projector/base_projector.html:7 +#: projector/views.py:375 projector/templates/projector/base_projector.html:7 #: projector/templates/projector/base_projector.html:12 #: projector/templates/projector/dashboard.html:17 msgid "Dashboard" msgstr "Dashboard" -#: projector/views.py:412 +#: projector/views.py:402 msgid "Projector live view" msgstr "Projektor-Live-Ansicht" -#: projector/views.py:439 +#: projector/views.py:428 msgid "Overlays" msgstr "Einblendungen" -#: projector/views.py:452 +#: projector/views.py:440 msgid "Custom Slides" msgstr "Benutzerdefinierte Folien" @@ -2350,53 +2344,43 @@ msgstr "Stand: %s" msgid "Page %s" msgstr "Seite %s" -#: utils/utils.py:66 utils/views.py:290 +#: utils/utils.py:69 utils/views.py:287 #, python-format msgid "Do you really want to delete %s?" msgstr "Soll %s wirklich gelöscht werden?" -#: utils/utils.py:111 +#: utils/utils.py:116 msgid "Sorry, you have no rights to see this page." msgstr "Bedaure, Sie haben keine Berechtigung diese Seite zu sehen." -#: utils/views.py:109 +#: utils/views.py:106 msgid "Are you sure?" msgstr "Sind Sie sicher?" -#: utils/views.py:110 +#: utils/views.py:107 msgid "Thank you for your answer" msgstr "Danke für Ihre Antwort" -#: utils/views.py:247 +#: utils/views.py:244 #, python-format msgid "%s was successfully modified." msgstr "%s wurde erfolgreich bearbeitet." -#: utils/views.py:278 +#: utils/views.py:275 #, python-format msgid "%s was successfully created." msgstr "%s wurde erfolgreich angelegt." -#: utils/views.py:296 +#: utils/views.py:293 #, python-format msgid "%s was successfully deleted." msgstr "%s wurde erfolgreich gelöscht." -#: utils/views.py:312 +#: utils/views.py:308 msgid "undefined-filename" msgstr "undefinierter-dateiname" -#: utils/jsonfield/fields.py:21 +#: utils/jsonfield/fields.py:22 msgid "Enter valid JSON" msgstr "Gebe valides JSON ein" -#~ msgid "Ignoring line %d because the assigned group does not exist." -#~ msgstr "" -#~ "Fehlerhafte Zeile %d der Quelldatei wurde ignoriert da die verwendete " -#~ "Gruppe nicht existiert." - -#~ msgid "posts" -#~ msgstr "Posten" - -#~ msgid "candidates" -#~ msgstr "Kandidaten/innen" diff --git a/openslides/locale/fr/LC_MESSAGES/django.mo b/openslides/locale/fr/LC_MESSAGES/django.mo index 50f308e0a..c8595d553 100644 Binary files a/openslides/locale/fr/LC_MESSAGES/django.mo and b/openslides/locale/fr/LC_MESSAGES/django.mo differ diff --git a/openslides/locale/fr/LC_MESSAGES/django.po b/openslides/locale/fr/LC_MESSAGES/django.po index 5cec74118..4b3ee0738 100644 --- a/openslides/locale/fr/LC_MESSAGES/django.po +++ b/openslides/locale/fr/LC_MESSAGES/django.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: OpenSlides 1.2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-11-07 22:35+0100\n" +"POT-Creation-Date: 2012-11-27 21:23+0100\n" "PO-Revision-Date: 2012-07-28 21:36+0200\n" "Last-Translator: Moira Brülisauer \n" "Language-Team: Französisch <>\n" @@ -19,15 +19,15 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n!=1);\n" -#: global_settings.py:36 +#: global_settings.py:33 msgid "German" msgstr "Allemand" -#: global_settings.py:37 +#: global_settings.py:34 msgid "English" msgstr "Anglais" -#: global_settings.py:38 +#: global_settings.py:35 msgid "French" msgstr "" @@ -35,43 +35,43 @@ msgstr "" msgid "Parent item" msgstr "Elément parent" -#: agenda/models.py:42 config/forms.py:61 motion/forms.py:22 -#: motion/models.py:540 motion/templates/motion/view.html:246 -#: projector/models.py:32 +#: agenda/models.py:34 config/forms.py:59 motion/forms.py:22 +#: motion/models.py:539 motion/templates/motion/view.html:246 +#: projector/models.py:29 msgid "Title" msgstr "Titre" -#: agenda/models.py:43 motion/forms.py:23 motion/models.py:541 -#: motion/templates/motion/view.html:247 projector/models.py:33 +#: agenda/models.py:35 motion/forms.py:23 motion/models.py:540 +#: motion/templates/motion/view.html:247 projector/models.py:30 msgid "Text" msgstr "Texte" -#: agenda/models.py:44 agenda/templates/agenda/overview.html:65 -#: agenda/templates/agenda/view.html:13 participant/models.py:56 +#: agenda/models.py:36 agenda/templates/agenda/overview.html:65 +#: agenda/templates/agenda/view.html:13 participant/models.py:60 #: participant/templates/participant/overview.html:72 #: participant/templates/participant/user_detail.html:45 msgid "Comment" msgstr "Commentaire" -#: agenda/models.py:45 +#: agenda/models.py:37 msgid "Closed" msgstr "Fermé" -#: agenda/models.py:46 agenda/templates/agenda/overview.html:71 -#: projector/models.py:34 +#: agenda/models.py:38 agenda/templates/agenda/overview.html:71 +#: projector/models.py:31 msgid "Weight" msgstr "Pondération" -#: agenda/models.py:180 +#: agenda/models.py:171 msgid "Can see agenda" msgstr "Peut voir l'ordre du jour" -#: agenda/models.py:181 +#: agenda/models.py:172 msgid "Can manage agenda" msgstr "Peut gérer l'ordre du jour" -#: agenda/models.py:189 agenda/slides.py:20 agenda/views.py:194 -#: agenda/views.py:195 agenda/views.py:214 +#: agenda/models.py:180 agenda/slides.py:20 agenda/views.py:191 +#: agenda/views.py:192 agenda/views.py:212 agenda/views.py:226 #: agenda/templates/agenda/base_agenda.html:10 #: agenda/templates/agenda/overview.html:8 #: agenda/templates/agenda/overview.html:52 @@ -81,34 +81,34 @@ msgstr "Peut gérer l'ordre du jour" msgid "Agenda" msgstr "Ordre du jour" -#: agenda/views.py:57 +#: agenda/views.py:54 msgid "You are not authorized to manage the agenda." msgstr "Vous n'êtes pas autorisé à gérer l'ordre du jour." -#: agenda/views.py:73 +#: agenda/views.py:70 msgid "Errors when reordering of the agenda" msgstr "Erreurs en réorganisant de l'ordre du jour" -#: agenda/views.py:134 +#: agenda/views.py:131 #, python-format msgid "Item %s was successfully modified." msgstr "Elément %s a été modifié avec succès." -#: agenda/views.py:155 +#: agenda/views.py:152 #, python-format msgid "Item %s was successfully created." msgstr "Elément %s a été créé avec succès." -#: agenda/views.py:172 +#: agenda/views.py:169 msgid "Yes, with all child items." msgstr "Oui, avec tous les sous-éléments." -#: agenda/views.py:180 +#: agenda/views.py:177 #, python-format msgid "Item %s and his children were successfully deleted." msgstr "Elément %s et ses sous-éléments ont été créé avec succès." -#: agenda/views.py:185 +#: agenda/views.py:182 #, python-format msgid "Item %s was successfully deleted." msgstr "Elément %s a été supprimé avec succès." @@ -172,12 +172,12 @@ msgstr "Enregistrer" #: assignment/templates/assignment/poll_view.html:73 #: config/templates/config/general.html:60 #: motion/templates/motion/config.html:17 motion/templates/motion/edit.html:33 -#: motion/templates/motion/import.html:27 +#: motion/templates/motion/import.html:30 #: motion/templates/motion/poll_view.html:59 #: participant/templates/participant/config.html:17 #: participant/templates/participant/edit.html:38 #: participant/templates/participant/group_edit.html:32 -#: participant/templates/participant/import.html:27 +#: participant/templates/participant/import.html:28 #: participant/templates/participant/password_change.html:26 #: participant/templates/participant/settings.html:26 #: projector/templates/projector/new.html:20 @@ -187,7 +187,7 @@ msgstr "Annuler" #: agenda/templates/agenda/edit.html:27 #: assignment/templates/assignment/edit.html:29 #: assignment/templates/assignment/poll_view.html:69 -#: assignment/templates/assignment/view.html:83 +#: assignment/templates/assignment/view.html:92 #: motion/templates/motion/edit.html:29 #: motion/templates/motion/poll_view.html:55 #: participant/templates/participant/edit.html:34 @@ -199,10 +199,10 @@ msgstr "Appliquer" #: agenda/templates/agenda/edit.html:35 #: assignment/templates/assignment/edit.html:37 -#: motion/templates/motion/edit.html:37 motion/templates/motion/import.html:31 +#: motion/templates/motion/edit.html:37 motion/templates/motion/import.html:34 #: participant/templates/participant/edit.html:42 #: participant/templates/participant/group_edit.html:36 -#: participant/templates/participant/import.html:31 +#: participant/templates/participant/import.html:32 msgid "required" msgstr "requis" @@ -228,25 +228,25 @@ msgstr "Activez le résumé pour cet élément" msgid "Do you want to save the changed order of agenda items?" msgstr "Voulez-vous enregistrer les modifications de l'ordre du jour?" -#: agenda/templates/agenda/overview.html:46 assignment/models.py:302 -#: assignment/views.py:577 assignment/templates/assignment/view.html:184 -#: assignment/templates/assignment/view.html:188 +#: agenda/templates/agenda/overview.html:46 assignment/models.py:294 +#: assignment/views.py:587 assignment/templates/assignment/view.html:168 +#: assignment/templates/assignment/view.html:172 #: assignment/templates/projector/Assignment.html:78 #: assignment/templates/projector/Assignment.html:82 motion/models.py:574 -#: motion/views.py:804 motion/views.py:855 +#: motion/views.py:830 motion/views.py:881 #: motion/templates/motion/view.html:79 -#: motion/templates/projector/Motion.html:37 utils/utils.py:53 -#: utils/views.py:111 +#: motion/templates/projector/Motion.html:37 utils/utils.py:55 +#: utils/views.py:108 msgid "Yes" msgstr "Oui" -#: agenda/templates/agenda/overview.html:47 assignment/models.py:302 -#: assignment/views.py:578 assignment/templates/assignment/view.html:185 +#: agenda/templates/agenda/overview.html:47 assignment/models.py:294 +#: assignment/views.py:588 assignment/templates/assignment/view.html:169 #: assignment/templates/projector/Assignment.html:79 motion/models.py:574 -#: motion/views.py:804 motion/views.py:856 +#: motion/views.py:830 motion/views.py:882 #: motion/templates/motion/view.html:80 -#: motion/templates/projector/Motion.html:38 utils/utils.py:53 -#: utils/views.py:111 +#: motion/templates/projector/Motion.html:38 utils/utils.py:55 +#: utils/views.py:108 msgid "No" msgstr "Non" @@ -301,7 +301,7 @@ msgid "Preview" msgstr "Aperçu" #: agenda/templates/agenda/widget.html:23 -#: assignment/templates/assignment/view.html:148 +#: assignment/templates/assignment/view.html:132 #: assignment/templates/assignment/widget.html:11 #: motion/templates/motion/widget.html:11 #: participant/templates/participant/group_widget.html:11 @@ -311,7 +311,7 @@ msgid "Delete" msgstr "Supprimer" #: agenda/templates/agenda/widget.html:26 -#: assignment/templates/assignment/view.html:147 +#: assignment/templates/assignment/view.html:131 #: assignment/templates/assignment/widget.html:14 #: motion/templates/motion/widget.html:14 #: participant/templates/participant/group_widget.html:14 @@ -320,7 +320,7 @@ msgstr "Supprimer" msgid "Edit" msgstr "Modifier" -#: assignment/forms.py:24 assignment/models.py:57 assignment/views.py:383 +#: assignment/forms.py:24 assignment/models.py:51 assignment/views.py:381 #: assignment/templates/assignment/view.html:13 #: assignment/templates/projector/Assignment.html:21 msgid "Number of available posts" @@ -336,176 +336,177 @@ msgstr "" "Publier seulement les résultat du candidat qui a gagné l'election " "(Uniquement sur la vue projecteur)" -#: assignment/forms.py:47 motion/forms.py:77 +#: assignment/forms.py:46 motion/forms.py:77 msgid "Number of ballot papers (selection)" msgstr "Nombre de bulletins de vote (sélection)" -#: assignment/forms.py:49 motion/forms.py:79 +#: assignment/forms.py:48 motion/forms.py:79 msgid "Number of all delegates" msgstr "Nombre de délégués" -#: assignment/forms.py:50 motion/forms.py:80 +#: assignment/forms.py:49 motion/forms.py:80 msgid "Number of all participants" msgstr "Nombre de participants" -#: assignment/forms.py:51 motion/forms.py:81 +#: assignment/forms.py:50 motion/forms.py:81 msgid "Use the following custom number" msgstr "Utilisez le nombre personnalisé suivant" -#: assignment/forms.py:58 motion/forms.py:88 +#: assignment/forms.py:55 motion/forms.py:88 msgid "Custom number of ballot papers" msgstr "Nombre personnalisé de bulletins de vote" -#: assignment/forms.py:63 +#: assignment/forms.py:59 msgid "Title for PDF document (all elections)" msgstr "Titre du fichier PDF (toutes les élections)" -#: assignment/forms.py:68 +#: assignment/forms.py:63 msgid "Preamble text for PDF document (all elections)" msgstr "Texte de préambule pour le fichier PDF (toutes les élections)" -#: assignment/forms.py:72 +#: assignment/forms.py:67 msgid "Election method" msgstr "Mode d'élection" -#: assignment/forms.py:74 +#: assignment/forms.py:69 msgid "Automatic assign of method." msgstr "Assignation automatique de la méthode." -#: assignment/forms.py:75 +#: assignment/forms.py:70 msgid "Always one option per candidate." msgstr "Toujours une option par candidat" -#: assignment/forms.py:76 +#: assignment/forms.py:71 msgid "Always Yes-No-Abstain per candidate." msgstr "Toujours Oui-Non-Abstention par candidat." -#: assignment/models.py:48 assignment/templates/assignment/overview.html:15 +#: assignment/models.py:44 assignment/templates/assignment/overview.html:15 #: assignment/templates/assignment/view.html:23 msgid "Searching for candidates" msgstr "Recherche de candidats" -#: assignment/models.py:49 assignment/templates/assignment/overview.html:16 +#: assignment/models.py:45 assignment/templates/assignment/overview.html:16 #: assignment/templates/assignment/view.html:25 msgid "Voting" msgstr "Vote" -#: assignment/models.py:50 assignment/templates/assignment/overview.html:17 +#: assignment/models.py:46 assignment/templates/assignment/overview.html:17 #: assignment/templates/assignment/view.html:27 msgid "Finished" msgstr "Terminé" -#: assignment/models.py:53 +#: assignment/models.py:49 msgid "Name" msgstr "Nom" -#: assignment/models.py:55 participant/models.py:120 +#: assignment/models.py:50 participant/models.py:144 msgid "Description" msgstr "Description" -#: assignment/models.py:59 +#: assignment/models.py:54 msgid "Comment on the ballot paper" msgstr "Commentaire sur le bulletin de vote" -#: assignment/models.py:69 motion/models.py:335 +#: assignment/models.py:64 motion/models.py:332 #, python-format msgid "%s is not a valid status." msgstr "%s est un statut invalide." -#: assignment/models.py:71 +#: assignment/models.py:67 #, python-format msgid "The assignment status is already %s." msgstr "Le statut d'assignation est déjà sur %s" -#: assignment/models.py:85 +#: assignment/models.py:80 #, python-format msgid "%s is already a candidate." msgstr "%s est déja un candidat." -#: assignment/models.py:87 assignment/views.py:200 +#: assignment/models.py:82 assignment/views.py:195 msgid "The candidate list is already closed." msgstr "La liste des candidats est déjà fermée" -#: assignment/models.py:94 +#: assignment/models.py:89 #, python-format msgid "%s does not want to be a candidate." msgstr "%s ne veut pas etre un candidat" -#: assignment/models.py:109 +#: assignment/models.py:103 #, python-format msgid "%s is no candidate" msgstr "%s n'est pas un candidat" -#: assignment/models.py:255 +#: assignment/models.py:247 msgid "Can see assignment" msgstr "Peut voir l'assignation" -#: assignment/models.py:257 +#: assignment/models.py:249 msgid "Can nominate another person" msgstr "Peut nonimer une autre personne" -#: assignment/models.py:258 +#: assignment/models.py:250 msgid "Can nominate themselves" msgstr "Peut se nominer soi-même" -#: assignment/models.py:259 +#: assignment/models.py:251 msgid "Can manage assignment" msgstr "Peut gérer l'assignation" -#: assignment/models.py:303 motion/models.py:575 +#: assignment/models.py:294 motion/models.py:574 msgid "Abstain" msgstr "Abstention" -#: assignment/models.py:305 motion/templates/motion/poll_view.html:22 +#: assignment/models.py:296 motion/templates/motion/poll_view.html:22 msgid "Votes" msgstr "Votes" -#: assignment/models.py:322 +#: assignment/models.py:313 #, python-format msgid "Ballot %d" msgstr "Vote %d" -#: assignment/models.py:331 assignment/views.py:340 assignment/views.py:664 +#: assignment/models.py:322 assignment/views.py:336 assignment/views.py:675 +#: assignment/views.py:690 #: assignment/templates/assignment/base_assignment.html:14 #: assignment/templates/assignment/overview.html:6 #: assignment/templates/assignment/overview.html:9 msgid "Elections" msgstr "Elections" -#: assignment/views.py:88 +#: assignment/views.py:80 #, python-format msgid "Candidate %s was nominated successfully." msgstr "Le candidat %s a été nominé avec succès. " -#: assignment/views.py:130 +#: assignment/views.py:122 msgid "New election was successfully created." msgstr "La nouvelle élection a été créée avec succès." -#: assignment/views.py:132 +#: assignment/views.py:124 msgid "Election was successfully modified." msgstr "L'élection a été modifiée avec succès." -#: assignment/views.py:138 motion/views.py:257 motion/views.py:663 -#: participant/views.py:492 participant/views.py:515 utils/views.py:225 -#: utils/views.py:243 utils/views.py:267 +#: assignment/views.py:130 motion/views.py:245 motion/views.py:689 +#: participant/views.py:508 participant/views.py:531 utils/views.py:222 +#: utils/views.py:240 utils/views.py:264 msgid "Please check the form for errors." msgstr "S'il vous plaît vérifier si il a des erreurs dans le formulaire." -#: assignment/views.py:157 +#: assignment/views.py:149 #, python-format msgid "Election %s was successfully deleted." msgstr "L'election %s a été supprimée avec succès." -#: assignment/views.py:170 +#: assignment/views.py:162 #, python-format msgid "Election status was set to: %s." msgstr "Le statut de l'élection a été changé sur %s." -#: assignment/views.py:181 +#: assignment/views.py:175 msgid "You have set your candidature successfully." msgstr "Vous avez inséré votre candidature avec succès." -#: assignment/views.py:197 +#: assignment/views.py:192 msgid "" "You have withdrawn your candidature successfully. You can not be nominated " "by other participants anymore." @@ -513,101 +514,100 @@ msgstr "" "Vous avez retiré votre candidature avec succès. On ne peut plus vous " "nominer comme candidat" -#: assignment/views.py:218 +#: assignment/views.py:213 #, python-format msgid "Candidate %s was withdrawn successfully." msgstr "Le candidat %s a été rejeté avec succès." -#: assignment/views.py:220 +#: assignment/views.py:215 #, python-format msgid "%s was unblocked successfully." msgstr "Le candidat %s a été nominé avec succès." -#: assignment/views.py:224 +#: assignment/views.py:219 #, python-format msgid "Do you really want to withdraw %s from the election?" msgstr "Voulez-vous vraiment exclure %s de cette élection?" -#: assignment/views.py:226 +#: assignment/views.py:221 #, python-format msgid "Do you really want to unblock %s for the election?" msgstr "" "Voulez-vous vraiment repermettre a participer %s à cette élection?" -#: assignment/views.py:241 +#: assignment/views.py:236 msgid "New ballot was successfully created." msgstr "Le nouveau vote a été créé avec succès" -#: assignment/views.py:273 +#: assignment/views.py:268 #, python-format msgid "Ballot ID %d does not exist." msgstr "L'identifiant %d de vote n'existe pas." -#: assignment/views.py:280 +#: assignment/views.py:275 msgid "Ballot successfully published." msgstr "Le vote a été publié avec succès." -#: assignment/views.py:282 +#: assignment/views.py:277 msgid "Ballot successfully unpublished." msgstr "Le vote a été retiré avec succès." -#: assignment/views.py:295 +#: assignment/views.py:290 msgid "not elected" msgstr "non élu" -#: assignment/views.py:298 assignment/views.py:481 -#: assignment/templates/assignment/overview.html:35 +#: assignment/views.py:293 assignment/views.py:482 +#: assignment/templates/assignment/view.html:48 msgid "elected" msgstr "élu" -#: assignment/views.py:326 +#: assignment/views.py:321 msgid "Ballot was successfully deleted." msgstr "Le vote a été supprimé avec succès." -#: assignment/views.py:337 +#: assignment/views.py:333 msgid "Assignment" msgstr "Assignation" -#: assignment/views.py:358 assignment/templates/assignment/overview.html:58 +#: assignment/views.py:356 assignment/templates/assignment/overview.html:59 #: assignment/templates/assignment/widget.html:23 msgid "No assignments available." msgstr "Aucune assignation disponible." -#: assignment/views.py:377 +#: assignment/views.py:375 #, python-format msgid "Election: %s" msgstr "Election: %s" -#: assignment/views.py:389 assignment/views.py:422 +#: assignment/views.py:388 assignment/views.py:424 #: assignment/templates/assignment/overview.html:26 #: assignment/templates/assignment/poll_view.html:18 -#: assignment/templates/assignment/view.html:36 -#: assignment/templates/assignment/view.html:136 +#: assignment/templates/assignment/view.html:37 +#: assignment/templates/assignment/view.html:120 #: assignment/templates/projector/Assignment.html:38 #: assignment/templates/projector/Assignment.html:56 msgid "Candidates" msgstr "Candidats" -#: assignment/views.py:410 motion/views.py:797 +#: assignment/views.py:413 motion/views.py:823 #: motion/templates/motion/view.html:44 msgid "Vote results" msgstr "Résultat du vote" -#: assignment/views.py:414 +#: assignment/views.py:417 #: assignment/templates/assignment/base_assignment.html:71 #: assignment/templates/assignment/poll_view.html:5 #: assignment/templates/assignment/poll_view.html:8 -#: assignment/templates/assignment/view.html:130 -#: assignment/templates/assignment/view.html:139 +#: assignment/templates/assignment/view.html:123 #: assignment/templates/projector/Assignment.html:59 msgid "ballot" msgstr "vote" -#: assignment/views.py:417 +#: assignment/views.py:420 msgid "ballots" msgstr "votes" -#: assignment/views.py:443 +#: assignment/views.py:445 #, python-format msgid "" "Y: %(YES)s\n" @@ -619,26 +619,26 @@ msgstr "" "A: %(ABSTAIN)s" # assignment/templates/assignment/poll_view.html:35 -#: assignment/views.py:454 assignment/templates/assignment/poll_view.html:35 -#: assignment/templates/assignment/view.html:203 -#: assignment/templates/projector/Assignment.html:97 +#: assignment/views.py:456 assignment/templates/assignment/poll_view.html:35 +#: assignment/templates/assignment/view.html:186 +#: assignment/templates/projector/Assignment.html:96 #: motion/templates/motion/poll_view.html:31 msgid "Invalid votes" msgstr "Votes invalides" # assignment/templates/assignment/poll_view.html:45 -#: assignment/views.py:461 assignment/templates/assignment/poll_view.html:45 -#: assignment/templates/assignment/view.html:220 -#: assignment/templates/assignment/view.html:225 -#: assignment/templates/projector/Assignment.html:111 -#: assignment/templates/projector/Assignment.html:117 motion/views.py:804 +#: assignment/views.py:463 assignment/templates/assignment/poll_view.html:45 +#: assignment/templates/assignment/view.html:202 +#: assignment/templates/assignment/view.html:207 +#: assignment/templates/projector/Assignment.html:109 +#: assignment/templates/projector/Assignment.html:115 motion/views.py:830 #: motion/templates/motion/poll_view.html:35 #: motion/templates/motion/view.html:84 #: motion/templates/projector/Motion.html:42 poll/models.py:76 msgid "Votes cast" msgstr "Nombre de votants" -#: assignment/views.py:520 assignment/views.py:536 +#: assignment/views.py:523 assignment/views.py:541 #: assignment/templates/assignment/overview.html:25 #: assignment/templates/assignment/poll_view.html:5 #: assignment/templates/assignment/view.html:6 @@ -646,19 +646,19 @@ msgstr "Nombre de votants" msgid "Election" msgstr "Election" -#: assignment/views.py:542 +#: assignment/views.py:548 #, python-format msgid "%d. ballot" msgstr "%d. vote" -#: assignment/views.py:543 +#: assignment/views.py:550 #, python-format msgid "%d candidate" msgid_plural "%d candidates" msgstr[0] "%d candidat" msgstr[1] "%d candidats" -#: assignment/views.py:545 +#: assignment/views.py:552 #, python-format msgid "%d available post" msgid_plural "%d available posts" @@ -666,21 +666,17 @@ msgstr[0] "%d postes disponibles" msgstr[1] "%d postes disponibles" # assignment/templates/assignment/view.html:160 -#: assignment/views.py:578 assignment/templates/assignment/view.html:186 -#: assignment/templates/projector/Assignment.html:80 motion/views.py:804 -#: motion/views.py:857 motion/templates/motion/view.html:81 +#: assignment/views.py:588 assignment/templates/assignment/view.html:170 +#: assignment/templates/projector/Assignment.html:80 motion/views.py:830 +#: motion/views.py:883 motion/templates/motion/view.html:81 #: motion/templates/projector/Motion.html:39 msgid "Abstention" msgstr "Abstention" -#: assignment/views.py:657 +#: assignment/views.py:668 msgid "Election settings successfully saved." msgstr "Les paramètres de l'élection ont été enregistrés avec succès." -#: assignment/views.py:677 -msgid "Assignments" -msgstr "Assignements" - #: assignment/templates/assignment/base_assignment.html:16 msgid "All elections" msgstr "Toutes les élections" @@ -702,17 +698,17 @@ msgstr "Afficher l'élection" #: assignment/templates/assignment/base_assignment.html:39 #: assignment/templates/assignment/edit.html:8 #: assignment/templates/assignment/edit.html:17 -#: assignment/templates/assignment/overview.html:49 +#: assignment/templates/assignment/overview.html:50 msgid "Edit election" msgstr "Modifier l'élection" #: assignment/templates/assignment/base_assignment.html:44 -#: assignment/templates/assignment/overview.html:50 +#: assignment/templates/assignment/overview.html:51 msgid "Delete election" msgstr "Supprimer l'élection" #: assignment/templates/assignment/base_assignment.html:50 -#: assignment/templates/assignment/overview.html:52 +#: assignment/templates/assignment/overview.html:53 msgid "Election as PDF" msgstr "L'élection en PDF" @@ -733,7 +729,7 @@ msgstr "Paramètres de l'élection" #: assignment/templates/assignment/overview.html:14 #: assignment/templates/assignment/overview.html:27 #: assignment/templates/assignment/view.html:11 -#: assignment/templates/projector/Assignment.html:18 motion/views.py:775 +#: assignment/templates/projector/Assignment.html:18 motion/views.py:801 #: motion/templates/motion/overview.html:20 #: motion/templates/motion/overview.html:40 #: motion/templates/motion/view.html:34 @@ -749,14 +745,21 @@ msgstr[0] "élection" msgstr[1] "élections" #: assignment/templates/assignment/overview.html:35 -msgid "posts" -msgstr "postes" +#, python-format +msgid "posts: %(posts)s" +msgstr "" #: assignment/templates/assignment/overview.html:37 -msgid "candidates" -msgstr "Candidats" +#, fuzzy, python-format +msgid "candidates: %(candidates)s" +msgstr "Aucun candidat n'est disponible" -#: assignment/templates/assignment/overview.html:44 +#: assignment/templates/assignment/overview.html:39 +#, fuzzy, python-format +msgid "elected: %(elected)s" +msgstr "Supprimer l'élection" + +#: assignment/templates/assignment/overview.html:45 msgid "Activate election" msgstr "Activer l'élection" @@ -770,13 +773,13 @@ msgid "Special values" msgstr "Valeurs spéciales" #: assignment/templates/assignment/poll_view.html:12 -#: motion/templates/motion/poll_view.html:14 poll/models.py:235 +#: motion/templates/motion/poll_view.html:14 poll/models.py:234 msgid "majority" msgstr "majorité" #: assignment/templates/assignment/poll_view.html:12 -#: motion/templates/motion/poll_view.html:14 poll/models.py:237 -#: poll/models.py:239 +#: motion/templates/motion/poll_view.html:14 poll/models.py:236 +#: poll/models.py:238 msgid "undocumented" msgstr "non documenté" @@ -789,110 +792,102 @@ msgstr "bulletin de vote en PDF" msgid "Change status" msgstr "Modifier le statut" -#: assignment/templates/assignment/view.html:43 -#: assignment/templates/assignment/view.html:99 -#: assignment/templates/assignment/view.html:113 +#: assignment/templates/assignment/view.html:44 +#: assignment/templates/assignment/view.html:106 msgid "Remove candidate" msgstr "Enlever le candidat" -#: assignment/templates/assignment/view.html:48 +#: assignment/templates/assignment/view.html:51 +#, fuzzy +msgid "Mark candidate as not elected" +msgstr "Le candidat est élu" + +#: assignment/templates/assignment/view.html:57 #: assignment/templates/projector/Assignment.html:44 msgid "No candidates available." msgstr "Aucun candidat n'est disponible" -#: assignment/templates/assignment/view.html:60 +#: assignment/templates/assignment/view.html:69 msgid "Withdraw self candidature" msgstr "Retirer sa propre candidature" -#: assignment/templates/assignment/view.html:66 +#: assignment/templates/assignment/view.html:75 msgid "Self candidature" msgstr "Se proposer comme candidat" -#: assignment/templates/assignment/view.html:77 +#: assignment/templates/assignment/view.html:86 msgid "Add new participant" msgstr "Ajouter un nouveau participant" -#: assignment/templates/assignment/view.html:92 -msgid "Elected Candidates" -msgstr "Candidats bloqué" - -#: assignment/templates/assignment/view.html:104 -msgid "No elected candidates available." -msgstr "Aucun candidat n'est disponible" - -#: assignment/templates/assignment/view.html:109 +#: assignment/templates/assignment/view.html:102 msgid "Blocked Candidates" msgstr "Candidats bloqué" -#: assignment/templates/assignment/view.html:116 +#: assignment/templates/assignment/view.html:109 msgid "No blocked candidates available." msgstr "Aucun candidat n'est disponible" -#: assignment/templates/assignment/view.html:121 +#: assignment/templates/assignment/view.html:115 #: assignment/templates/projector/Assignment.html:52 msgid "Election results" msgstr "Résultat des élections" -#: assignment/templates/assignment/view.html:144 +#: assignment/templates/assignment/view.html:128 msgid "Publish/unpublish results" msgstr "Publier/dépublier les résultats" -#: assignment/templates/assignment/view.html:156 -#: assignment/templates/assignment/view.html:244 +#: assignment/templates/assignment/view.html:140 +#: assignment/templates/assignment/view.html:223 msgid "New ballot" msgstr "Nouveau vote" -#: assignment/templates/assignment/view.html:171 +#: assignment/templates/assignment/view.html:155 #: assignment/templates/projector/Assignment.html:69 msgid "Candidate is elected" msgstr "Le candidat est élu" -#: assignment/templates/assignment/view.html:190 +#: assignment/templates/assignment/view.html:174 #: assignment/templates/projector/Assignment.html:84 msgid "was not a
candidate" msgstr "n'était pas un
candidat" -#: assignment/templates/assignment/view.html:208 -#: assignment/templates/projector/Assignment.html:101 motion/views.py:804 +#: assignment/templates/assignment/view.html:191 +#: assignment/templates/projector/Assignment.html:100 motion/views.py:830 #: motion/templates/motion/view.html:82 #: motion/templates/projector/Motion.html:40 msgid "Invalid" msgstr "Invalide" -#: assignment/templates/assignment/view.html:239 -#: assignment/templates/projector/Assignment.html:128 -msgid "No ballots available." -msgstr "Aucun vote disponible." +#: assignment/templates/assignment/view.html:219 +#, fuzzy +msgid "No results available." +msgstr "Aucun résultat de sondage disponible" -#: assignment/templates/projector/Assignment.html:126 -msgid "Vote results are not published yet." -msgstr "" - -#: config/forms.py:24 +#: config/forms.py:22 msgid "Event name" msgstr "Nom de l'événement" -#: config/forms.py:30 +#: config/forms.py:28 msgid "Short description of event" msgstr "Une courte description de l'événement" -#: config/forms.py:38 +#: config/forms.py:36 msgid "Event date" msgstr "Date de l'événement" -#: config/forms.py:44 +#: config/forms.py:42 msgid "Event location" msgstr "Lieu de l'événement" -#: config/forms.py:50 +#: config/forms.py:48 msgid "Event organizer" msgstr "Organisateur de l'événement" -#: config/forms.py:55 +#: config/forms.py:53 msgid "Allow access for anonymous guest users" msgstr "Permettre l'accès anomyme" -#: config/forms.py:67 participant/forms.py:114 +#: config/forms.py:65 participant/forms.py:123 msgid "Welcome text" msgstr "Texte de bienvenue" @@ -900,47 +895,34 @@ msgstr "Texte de bienvenue" msgid "Can manage configuration" msgstr "Peut gérer la configuration" -#: config/models.py:84 +#: config/models.py:83 msgid "Presentation and assembly system" msgstr "" -#: config/models.py:89 +#: config/models.py:88 msgid "Welcome to OpenSlides" msgstr "Bienvenue sur OpenSlides!" -#: config/models.py:90 +#: config/models.py:89 msgid "[Place for your welcome text.]" msgstr "" -#: config/models.py:92 -#, python-format -msgid "Get professional support for OpenSlides on %s." -msgstr "Obtenez de l'aide professionnelle pour OpenSlides sur %s." - -#: config/models.py:107 +#: config/models.py:102 msgid "General" msgstr "Général" -#: config/models.py:131 config/templates/config/version.html:5 +#: config/models.py:126 config/templates/config/version.html:5 #: config/templates/config/version.html:8 -#: config/templates/config/version.html:11 motion/views.py:789 +#: config/templates/config/version.html:11 motion/views.py:815 #: motion/templates/motion/view.html:214 motion/templates/motion/view.html:244 msgid "Version" msgstr "Version" -#: config/views.py:79 -msgid "" -"Anonymous access enabled. Please modify the \"Anonymous\" group to fit your " -"required permissions." -msgstr "" -"L'accès anonyme a été activé. S'il vous plait, modifiez le groupe \"Anonymous" -"\" pour lui donner les droits nécéssaires" - -#: config/views.py:85 +#: config/views.py:69 msgid "General settings successfully saved." msgstr "Les paramètres généraux ont été enregistrés avec succès." -#: config/views.py:120 config/templates/config/base_config.html:7 +#: config/views.py:104 config/templates/config/base_config.html:7 msgid "Configuration" msgstr "Configuration" @@ -954,14 +936,15 @@ msgid "Event" msgstr "Evénement" #: config/templates/config/general.html:26 -msgid "Frontpage" -msgstr "Page d'accueil" +#, fuzzy +msgid "Welcome Widget" +msgstr "Texte de bienvenue" #: config/templates/config/general.html:41 msgid "System" msgstr "Système" -#: motion/forms.py:25 motion/models.py:542 motion/views.py:823 +#: motion/forms.py:25 motion/models.py:541 motion/views.py:849 #: motion/templates/motion/view.html:229 motion/templates/motion/view.html:249 #: motion/templates/projector/Motion.html:77 msgid "Reason" @@ -975,18 +958,18 @@ msgstr "Changement trivial" msgid "Trivial changes don't create a new version." msgstr "Des changement triviaux ne créent pas une nouvelle version." -#: motion/forms.py:35 motion/models.py:66 motion/views.py:742 +#: motion/forms.py:35 motion/models.py:63 motion/views.py:768 #: motion/templates/motion/overview.html:41 #: motion/templates/motion/view.html:18 #: motion/templates/projector/Motion.html:55 msgid "Submitter" msgstr "Requérant" -#: motion/forms.py:44 motion/views.py:762 motion/templates/motion/view.html:22 +#: motion/forms.py:44 motion/views.py:788 motion/templates/motion/view.html:22 msgid "Supporters" msgstr "Partisants" -#: motion/forms.py:50 participant/forms.py:102 +#: motion/forms.py:50 participant/forms.py:111 msgid "CSV File" msgstr "Fichier CSV" @@ -1028,68 +1011,68 @@ msgstr "" "Attention: Les changements triviaux perturbent le système d'autorisation de " "motion." -#: motion/models.py:45 +#: motion/models.py:42 msgid "Published" msgstr "Publié" -#: motion/models.py:46 +#: motion/models.py:43 msgid "Permitted" msgstr "Permis" -#: motion/models.py:47 motion/templates/motion/overview.html:24 +#: motion/models.py:44 motion/templates/motion/overview.html:24 #: motion/templates/motion/view.html:167 msgid "Accepted" msgstr "Accepté" -#: motion/models.py:48 motion/templates/motion/overview.html:25 +#: motion/models.py:45 motion/templates/motion/overview.html:25 #: motion/templates/motion/view.html:172 msgid "Rejected" msgstr "Rejeté" -#: motion/models.py:49 +#: motion/models.py:46 msgid "Withdrawed" msgstr "Retiré" -#: motion/models.py:50 motion/templates/motion/view.html:180 +#: motion/models.py:47 motion/templates/motion/view.html:180 msgid "Adjourned" msgstr "Ajourné" -#: motion/models.py:51 motion/templates/motion/view.html:183 +#: motion/models.py:48 motion/templates/motion/view.html:183 msgid "Not Concerned" msgstr "Non concerné" -#: motion/models.py:52 motion/templates/motion/view.html:186 +#: motion/models.py:49 motion/templates/motion/view.html:186 msgid "Commited a bill" msgstr "Transféré à (une commission)" -#: motion/models.py:53 +#: motion/models.py:50 msgid "Rejected (not authorized)" msgstr "Rejeté (non autorisé)" -#: motion/models.py:54 motion/templates/motion/overview.html:27 +#: motion/models.py:51 motion/templates/motion/overview.html:27 msgid "Needs Review" msgstr "Doit être revu" -#: motion/models.py:103 +#: motion/models.py:100 #, python-format msgid "Version %d authorized" msgstr "Version %d autorisée" -#: motion/models.py:110 +#: motion/models.py:107 #, python-format msgctxt "Rejected means not authorized" msgid "Version %d rejected" msgstr "Version %d rejetée" -#: motion/models.py:139 +#: motion/models.py:136 msgid "Searching for supporters." msgstr "Recherche de soutien" -#: motion/models.py:141 +#: motion/models.py:138 msgid "Not yet authorized." msgstr "N'est pas encore autorisé" -#: motion/models.py:143 +#: motion/models.py:140 msgid "Not yet authorized changes." msgstr "Les changements ne sont pas encore approuvés." @@ -1101,7 +1084,7 @@ msgstr "" "Changements triviaux à la version %(version)d; les champs modifiés: " "%(changed_fields)s" -#: motion/models.py:234 +#: motion/models.py:235 #, python-format msgid "Version %s created" msgstr "Version %s créée" @@ -1125,27 +1108,27 @@ msgstr "Partisants: +%s" msgid "Supporter: -%s" msgstr "Partisants: -%s" -#: motion/models.py:295 +#: motion/models.py:294 #, python-format msgid "Number set: %s" msgstr "Numéro inséré: %s" -#: motion/models.py:308 +#: motion/models.py:307 #, python-format msgid "Version %s authorized" msgstr "Version %s autorisée" -#: motion/models.py:322 +#: motion/models.py:319 #, python-format msgid "Version %s not authorized" msgstr "Version %s non autorisée" -#: motion/models.py:338 +#: motion/models.py:335 #, python-format msgid "The motion status is already '%s.'" msgstr "La motion a déja le statut '%s'." -#: motion/models.py:346 +#: motion/models.py:343 #, python-format msgid "" "The motion status is: '%(currentstatus)s'. You can not set the status to " @@ -1154,15 +1137,15 @@ msgstr "" "Le statut de la motion est: '%(currentstatus)s'. Vous ne pouvez pas le " "changer le statut sur '%(newstatus)s'." -#: motion/models.py:354 +#: motion/models.py:351 msgid "Status modified" msgstr "Statut modifié" -#: motion/models.py:446 +#: motion/models.py:443 motion/models.py:445 msgid "by" msgstr "par" -#: motion/models.py:454 motion/templates/motion/view.html:210 +#: motion/models.py:453 motion/templates/motion/view.html:210 #: motion/templates/motion/widget.html:27 #: motion/templates/projector/Motion.html:65 #: participant/templates/participant/personal_info_widget.html:13 @@ -1170,60 +1153,60 @@ msgstr "par" msgid "no number" msgstr "pas de nombre" -#: motion/models.py:455 motion/templates/motion/widget.html:23 +#: motion/models.py:454 motion/templates/motion/widget.html:23 #: participant/templates/participant/personal_info_widget.html:9 #: participant/templates/participant/personal_info_widget.html:28 msgid "motion" msgstr "motion" -#: motion/models.py:480 +#: motion/models.py:479 msgid "Poll created" msgstr "Sondage créé" -#: motion/models.py:531 +#: motion/models.py:530 msgid "Can see motions" msgstr "Peut voir les motions" -#: motion/models.py:532 +#: motion/models.py:531 msgid "Can create motions" msgstr "Peut créer des motions" -#: motion/models.py:533 +#: motion/models.py:532 msgid "Can support motions" msgstr "Peut soutenir les motions" -#: motion/models.py:534 +#: motion/models.py:533 msgid "Can manage motions" msgstr "Peut gérer les motions" -#: motion/models.py:601 +#: motion/models.py:600 msgid "The assembly may decide," msgstr "Je demande a l'Assemblée de décider sur..." -#: motion/models.py:604 motion/views.py:692 motion/views.py:917 -#: motion/templates/motion/base_motion.html:9 +#: motion/models.py:603 motion/views.py:718 motion/views.py:943 +#: motion/views.py:954 motion/templates/motion/base_motion.html:9 #: motion/templates/motion/overview.html:7 #: motion/templates/motion/overview.html:10 msgid "Motions" msgstr "Motions" -#: motion/views.py:180 +#: motion/views.py:173 msgid "You have not the necessary rights to create or edit motions." msgstr "Vous n'avez pas l'autorisation de créer ou modifier des motions." -#: motion/views.py:185 +#: motion/views.py:178 msgid "You can not edit this motion." msgstr "Vous ne pouvez pas modifier cette motion." -#: motion/views.py:248 +#: motion/views.py:236 msgid "New motion was successfully created." msgstr "La nouvelle motion a été créée avec succès." -#: motion/views.py:250 +#: motion/views.py:238 msgid "Motion was successfully modified." msgstr "La motion a été modifiée avec succès." -#: motion/views.py:264 +#: motion/views.py:252 msgid "" "Attention: Do you really want to edit this motion? The supporters will " "not be removed automatically because you can manage motions. Please " @@ -1234,7 +1217,7 @@ msgstr "" "gérer les motions. S'il vous plait vérifiez si les partisans sont valables " "après votre modification!" -#: motion/views.py:266 +#: motion/views.py:254 #, python-format msgid "" "Attention: Do you really want to edit this motion? All %s supporters " @@ -1243,151 +1226,181 @@ msgstr "" "Attention: Voulez-vous vraiment modifier cette motion? Tout les %s " "partisans seront supprimés! Essayez de convaincre les partisans à nouveau." -#: motion/views.py:298 +#: motion/views.py:286 msgid "Motion number was successfully set." msgstr "Le numéro de la motion a été mis avec succès." -#: motion/views.py:314 +#: motion/views.py:302 msgid "Motion was successfully authorized." msgstr "La motion a été autorisée avec succès." -#: motion/views.py:329 +#: motion/views.py:317 msgid "Motion was successfully rejected." msgstr "La motion a été rejetée avec succès. " -#: motion/views.py:345 +#: motion/views.py:333 #, python-format msgid "Motion status was set to: %s." msgstr "Le statut de la motion a été changé sur: b>%s" -#: motion/views.py:361 +#: motion/views.py:349 msgid "Motion status was reset." msgstr "Statut de la motion a été remis à zéro." -#: motion/views.py:388 +#: motion/views.py:376 msgid "You can not support this motion." msgstr "Vous ne pouvez pas supporter cette motion. " -#: motion/views.py:391 +#: motion/views.py:379 msgid "You can not unsupport this motion." msgstr "Vous ne pouvez pas retirer votre support pour cette motion. " -#: motion/views.py:402 +#: motion/views.py:390 msgid "Do you really want to support this motion?" msgstr "Voulez-vous vraiment supporter cette motion?" -#: motion/views.py:404 +#: motion/views.py:392 msgid "Do you really want to unsupport this motion?" msgstr "Voulez-vous vraiment retirer votre support pour cette motion?" -#: motion/views.py:415 +#: motion/views.py:403 msgid "You have supported this motion successfully." msgstr "" "Vous supportez maintenant cette motion, vous avez vous ajouté avec succès." -#: motion/views.py:417 +#: motion/views.py:405 msgid "You have unsupported this motion successfully." msgstr "Vous avez retiré votre support pour cette motion avec succès. " -#: motion/views.py:431 +#: motion/views.py:419 msgid "New vote was successfully created." msgstr "Nouveau vote a été créé avec succès." -#: motion/views.py:447 +#: motion/views.py:435 msgid "Poll deleted" msgstr "Sondage supprimé" -#: motion/views.py:448 +#: motion/views.py:436 msgid "Poll was successfully deleted." msgstr "Le sondage a été supprimé avec succès." -#: motion/views.py:450 +#: motion/views.py:438 #, python-format msgid "the %s. poll" msgstr "le %s. sondage" -#: motion/views.py:491 motion/views.py:500 +#: motion/views.py:479 motion/views.py:488 #, python-format msgid "You can not delete motion %s." msgstr "Vous ne pouvez pas supprimer la motion %s." -#: motion/views.py:496 motion/views.py:504 +#: motion/views.py:484 motion/views.py:492 #, python-format msgid "Motion %s was successfully deleted." msgstr "Motion %s a été supprimé avec succès." -#: motion/views.py:506 +#: motion/views.py:494 msgid "Invalid request" msgstr "Demande invalide" -#: motion/views.py:530 +#: motion/views.py:518 msgid "Poll was updated" msgstr "Le sondage a été actualisé" -#: motion/views.py:547 +#: motion/views.py:535 #, python-format msgid "Version %s accepted." msgstr "Version %s acceptée." -#: motion/views.py:549 +#: motion/views.py:537 #, python-format msgid "Do you really want to authorize version %s?" msgstr "Voulez-vous vraiment autoriser cette version %s" -#: motion/views.py:559 +#: motion/views.py:547 #, python-format msgid "Version %s rejected." msgstr "Version %s rejetée." -#: motion/views.py:561 +#: motion/views.py:549 msgid "ERROR by rejecting the version." msgstr "ERREUR en rejetant la version." -#: motion/views.py:563 +#: motion/views.py:551 #, python-format msgid "Do you really want to reject version %s?" msgstr "Voulez vous vraiment rejeter la version %s?" -#: motion/views.py:593 motion/views.py:597 motion/views.py:603 -#: motion/views.py:606 participant/api.py:76 +#: motion/views.py:587 motion/views.py:591 motion/views.py:597 +#: motion/views.py:600 participant/api.py:81 #, python-format msgid "Ignoring malformed line %d in import file." msgstr "La ligne %d mal formée dans le fichier d'entrée a été ignorée" -#: motion/views.py:649 +#: motion/views.py:608 +#, python-format +msgid "Ignoring line %d because the assigned group may not act as a person." +msgstr "" + +#: motion/views.py:617 +#, fuzzy +msgid "Created by motion import." +msgstr "Peut créer des motions" + +#: motion/views.py:631 +#, python-format +msgid "" +"Ignoring line %d because it contains an incomplete first / last name pair." +msgstr "" + +#: motion/views.py:669 #, python-format msgid "%d motion was successfully imported." msgid_plural "%d motions were successfully imported." msgstr[0] "%d la motion a été importée avec succès." msgstr[1] "%d les motions ont été importées avec succès." -#: motion/views.py:652 +#: motion/views.py:672 #, python-format msgid "%d motion was successfully modified." msgid_plural "%d motions were successfully modified." msgstr[0] "%d la motion a été modifiée avec succès." msgstr[1] "%d les motions ont été modifiées avec succès." -#: motion/views.py:655 +#: motion/views.py:675 #, python-format msgid "%d new user was added." msgid_plural "%d new users were added." msgstr[0] "%d nouvel utilisateur a été créé." msgstr[1] "%d nouveaux utilisateurs ont été créés." -#: motion/views.py:659 participant/api.py:92 +#: motion/views.py:678 +#, fuzzy, python-format +msgid "%d new group was added." +msgid_plural "%d new groups were added." +msgstr[0] "%d nouvel utilisateur a été créé." +msgstr[1] "%d nouveaux utilisateurs ont été créés." + +#: motion/views.py:681 +#, python-format +msgid "%d group assigned to motions." +msgid_plural "%d groups assigned to motions." +msgstr[0] "" +msgstr[1] "" + +#: motion/views.py:685 participant/api.py:97 msgid "Import aborted because of severe errors in the input file." msgstr "" "l'importation a été interrompu en raison d'erreurs graves dans le fichier " "d'entrée" -#: motion/views.py:661 participant/api.py:94 +#: motion/views.py:687 participant/api.py:99 msgid "Import file has wrong character encoding, only UTF-8 is supported!" msgstr "" "Le fichier d'entrée a un mauvais encodage des caractères , seul UTF-8 est " "pris en charge" -#: motion/views.py:665 +#: motion/views.py:691 msgid "" "Attention: Existing motions will be modified if you import new motions with " "the same number." @@ -1395,7 +1408,7 @@ msgstr "" "Attention: Les motions existantes seront modifiées si vous importez de " "nouvelles motions avec le même numéro" -#: motion/views.py:666 +#: motion/views.py:692 msgid "" "Attention: Importing an motions without a number multiple times will create " "duplicates." @@ -1403,7 +1416,7 @@ msgstr "" "Attention: L'importation d'une motion sans numéro à plusieurs reprises " "créera des doublons." -#: motion/views.py:699 motion/views.py:837 +#: motion/views.py:725 motion/views.py:863 #: motion/templates/motion/poll_view.html:7 #: motion/templates/motion/poll_view.html:12 #: motion/templates/motion/view.html:7 motion/templates/motion/view.html:206 @@ -1413,21 +1426,21 @@ msgstr "" msgid "Motion" msgstr "Motion" -#: motion/views.py:713 motion/templates/motion/overview.html:84 +#: motion/views.py:739 motion/templates/motion/overview.html:84 msgid "No motions available." msgstr "Aucune motion disponible" -#: motion/views.py:718 motion/views.py:720 motion/views.py:735 -#: motion/views.py:737 motion/templates/motion/base_motion.html:24 +#: motion/views.py:744 motion/views.py:746 motion/views.py:761 +#: motion/views.py:763 motion/templates/motion/base_motion.html:24 #: motion/templates/projector/Motion.html:63 msgid "Motion No." msgstr "Motion No." -#: motion/views.py:752 +#: motion/views.py:778 msgid "Signature" msgstr "Signature" -#: motion/views.py:803 motion/templates/motion/base_motion.html:55 +#: motion/views.py:829 motion/templates/motion/base_motion.html:55 #: motion/templates/motion/poll_view.html:8 #: motion/templates/motion/poll_view.html:13 #: motion/templates/motion/view.html:66 motion/templates/motion/view.html:74 @@ -1435,21 +1448,21 @@ msgstr "Signature" msgid "Vote" msgstr "Vote" -#: motion/views.py:837 +#: motion/views.py:863 msgid "Poll" msgstr "Sondage" -#: motion/views.py:851 +#: motion/views.py:877 #, python-format msgid "Motion No. %s" msgstr "Motion No %s" -#: motion/views.py:853 +#: motion/views.py:879 #, python-format msgid "%d. Vote" msgstr "%d. Vote" -#: motion/views.py:910 +#: motion/views.py:936 msgid "Motion settings successfully saved." msgstr "Les paramètres des motions ont été enregistrés avec succès" @@ -1504,27 +1517,36 @@ msgid "Select a CSV file to import motions!" msgstr "Selectionnez un fichier CSV pour importer les motions!" #: motion/templates/motion/import.html:11 +#: participant/templates/participant/import.html:11 +msgid "Required comma separated values" +msgstr "" + +#: motion/templates/motion/import.html:12 +msgid "number, title, text, reason, first_name, last_name, is_group" +msgstr "" + +#: motion/templates/motion/import.html:14 +#, fuzzy msgid "" -"Required comma separated values: {number, title, text, reason, " -"first_name, last_name} (number and reason " -"are optional and may be empty)" +"number, reason and is_group are " +"optional and may be empty" msgstr "" "Les valeurs obligatoires, séparées par des virgules: {nombre, titre, " "texte, motivation, prénom, nom} (nombre et " "motivation sont facultatives et peuvent être laissées vides.)" -#: motion/templates/motion/import.html:13 -#: participant/templates/participant/import.html:13 +#: motion/templates/motion/import.html:16 +#: participant/templates/participant/import.html:14 msgid "Required CSV file encoding: UTF-8 (Unicode)." msgstr "Le fichier CSV requièrt un encodage de caractères UTF-8 (Unicode)" -#: motion/templates/motion/import.html:16 -#: participant/templates/participant/import.html:16 +#: motion/templates/motion/import.html:19 +#: participant/templates/participant/import.html:17 msgid "A CSV example file is available in OpenSlides Wiki." msgstr "Un exemple de fichier CSV est disponible sur le Wiki de Openslides. " -#: motion/templates/motion/import.html:23 -#: participant/templates/participant/import.html:23 +#: motion/templates/motion/import.html:26 +#: participant/templates/participant/import.html:24 msgid "Import" msgstr "Importation" @@ -1717,18 +1739,18 @@ msgstr "Aucun résultat de sondage disponible" msgid "Participant" msgstr "Participant" -#: participant/forms.py:26 participant/views.py:590 +#: participant/forms.py:27 participant/views.py:607 #: participant/templates/participant/group_overview.html:7 #: participant/templates/participant/group_overview.html:10 #: participant/templates/participant/user_detail.html:14 msgid "Groups" msgstr "Groupe" -#: participant/forms.py:44 +#: participant/forms.py:52 msgid "Permissions" msgstr "Permissions" -#: participant/forms.py:47 participant/views.py:530 +#: participant/forms.py:55 participant/views.py:546 participant/views.py:593 #: participant/templates/participant/base_participant.html:12 #: participant/templates/participant/overview.html:7 #: participant/templates/participant/overview.html:18 @@ -1736,198 +1758,218 @@ msgstr "Permissions" msgid "Participants" msgstr "Participants" -#: participant/forms.py:83 -msgid "You can not edit the name for the anonymous user" +#: participant/forms.py:92 +#, fuzzy +msgid "You can not edit the name for this group." msgstr "Vous ne pouvez pas modifier le de l'utilisateur anomyme." -#: participant/forms.py:87 +#: participant/forms.py:96 #, python-format msgid "Group name \"%s\" is reserved for internal use." msgstr "Ce nom de groupe \"%s\" est réservé pour une utilisation interne." -#: participant/forms.py:109 +#: participant/forms.py:118 msgid "System URL" msgstr "URL du système" -#: participant/forms.py:110 participant/forms.py:115 +#: participant/forms.py:119 participant/forms.py:124 msgid "Printed in PDF of first time passwords only." msgstr "" "Imprimé dans le PDF avec la liste des premiers mots de passe seulement." -#: participant/forms.py:118 +#: participant/forms.py:127 msgid "Sort participants by first name" msgstr "" -#: participant/forms.py:119 +#: participant/forms.py:128 msgid "Disable for sorting by last name" msgstr "" -#: participant/models.py:29 participant/templates/participant/overview.html:25 +#: participant/models.py:33 participant/templates/participant/overview.html:25 msgid "Male" msgstr "Masculin" -#: participant/models.py:30 participant/templates/participant/overview.html:26 +#: participant/models.py:34 participant/templates/participant/overview.html:26 msgid "Female" msgstr "Féminin" -#: participant/models.py:33 participant/templates/participant/overview.html:38 +#: participant/models.py:37 participant/templates/participant/overview.html:38 msgid "Delegate" msgstr "Délégué" -#: participant/models.py:34 participant/templates/participant/overview.html:39 +#: participant/models.py:38 participant/templates/participant/overview.html:39 msgid "Observer" msgstr "Observateur" -#: participant/models.py:35 participant/templates/participant/overview.html:40 +#: participant/models.py:39 participant/templates/participant/overview.html:40 msgid "Staff" msgstr "Personnel" -#: participant/models.py:36 participant/templates/participant/overview.html:41 +#: participant/models.py:40 participant/templates/participant/overview.html:41 msgid "Guest" msgstr "Invité" -#: participant/models.py:41 participant/templates/participant/overview.html:30 +#: participant/models.py:45 participant/templates/participant/overview.html:30 #: participant/templates/participant/overview.html:68 -msgid "Detail" +msgid "Structure level" msgstr "" -#: participant/models.py:42 +#: participant/models.py:46 msgid "Will be shown after the name." msgstr "Apparait après le nom." -#: participant/models.py:45 participant/templates/participant/overview.html:24 +#: participant/models.py:49 participant/templates/participant/overview.html:24 #: participant/templates/participant/user_detail.html:24 msgid "Gender" msgstr "Sexe" -#: participant/models.py:45 participant/models.py:48 participant/models.py:51 +#: participant/models.py:49 participant/models.py:52 participant/models.py:55 msgid "Only for filtering the participant list." msgstr "Seulement pour filtrer la liste des utilisateurs." -#: participant/models.py:48 +#: participant/models.py:52 msgid "Typ" msgstr "Type" -#: participant/models.py:50 participant/views.py:245 +#: participant/models.py:54 participant/views.py:255 #: participant/templates/participant/overview.html:45 #: participant/templates/participant/overview.html:70 #: participant/templates/participant/user_detail.html:34 msgid "Committee" msgstr "Comité" -#: participant/models.py:53 +#: participant/models.py:57 #: participant/templates/participant/user_detail.html:39 msgid "About me" msgstr "" -#: participant/models.py:54 +#: participant/models.py:58 msgid "Your profile text" msgstr "" -#: participant/models.py:57 +#: participant/models.py:61 msgid "Only for notes." msgstr "Seulement pour des notes." -#: participant/models.py:60 +#: participant/models.py:64 msgid "Default password" msgstr "Premier mot de passe" -#: participant/models.py:108 +#: participant/models.py:118 msgid "Can see participant" msgstr "Peut voir les participants" -#: participant/models.py:110 +#: participant/models.py:120 msgid "Can manage participant" msgstr "Peut gérer des participants" -#: participant/models.py:119 +#: participant/models.py:142 msgid "Use this group as participant" msgstr "" -#: participant/models.py:119 +#: participant/models.py:143 msgid "For example as submitter of a motion." msgstr "" -#: participant/models.py:201 +#: participant/models.py:237 msgid "Welcome to OpenSlides!" msgstr "Bienvenue sur OpenSlides!" -#: participant/views.py:240 +#: participant/views.py:207 +#, fuzzy +msgid "You can not delete yourself." +msgstr "Vous ne pouvez pas supprimer la motion %s." + +#: participant/views.py:228 +msgid "You can not deactivate yourself." +msgstr "" + +#: participant/views.py:231 +#, fuzzy +msgid "You can not deactivate the administrator." +msgstr "Vous ne pouvez pas modifier cette motion." + +#: participant/views.py:250 msgid "Participant-list" msgstr "Liste des participants" -#: participant/views.py:241 +#: participant/views.py:251 msgid "List of Participants" msgstr "La liste des participants" -#: participant/views.py:244 participant/templates/participant/overview.html:67 +#: participant/views.py:254 participant/templates/participant/overview.html:67 msgid "Last Name" msgstr "Nom" -#: participant/views.py:244 participant/templates/participant/overview.html:66 +#: participant/views.py:254 participant/templates/participant/overview.html:66 msgid "First Name" msgstr "Prénom" -#: participant/views.py:244 +#: participant/views.py:254 #: participant/templates/participant/group_overview.html:13 msgid "Group" msgstr "Groupe" -#: participant/views.py:244 participant/templates/participant/overview.html:37 +#: participant/views.py:254 participant/templates/participant/overview.html:37 #: participant/templates/participant/overview.html:69 #: participant/templates/participant/user_detail.html:29 msgid "Type" msgstr "Type" -#: participant/views.py:276 +#: participant/views.py:286 msgid "Participant-passwords" msgstr "Mot de passe du participant" -#: participant/views.py:298 +#: participant/views.py:308 msgid "Account for OpenSlides" msgstr "Compte pour OpenSlides" -#: participant/views.py:300 +#: participant/views.py:310 #, python-format msgid "for %s" msgstr "pour %s" -#: participant/views.py:303 +#: participant/views.py:313 #, python-format msgid "User: %s" msgstr "Utilisateur: %s" -#: participant/views.py:307 +#: participant/views.py:317 #, python-format msgid "Password: %s" msgstr "Mot de passe: %s" -#: participant/views.py:312 +#: participant/views.py:322 #, python-format msgid "URL: %s" msgstr "URL: %s" -#: participant/views.py:354 +#: participant/views.py:364 #, python-format msgid "%d new participants were successfully imported." msgstr "%d nouveaux participants ont été importés avec succès. " -#: participant/views.py:365 +#: participant/views.py:375 msgid "Do you really want to reset the password?" msgstr "Voulez-vous vraiment reinitialser le mot de passe?" -#: participant/views.py:378 +#: participant/views.py:388 #, python-format msgid "The Password for %s was successfully reset." msgstr "Le mot de passe de %s a été initialisé avec succès" -#: participant/views.py:457 +#: participant/views.py:445 +#, fuzzy +msgid "You can not delete this Group." +msgstr "Vous ne pouvez pas modifier cette motion." + +#: participant/views.py:473 msgid "Participants settings successfully saved." msgstr "" "Les modifications des paramètres des participants ont été appliquées avec " "succès" -#: participant/views.py:467 +#: participant/views.py:483 #, python-format msgid "" "Installation was successfully! Use %(user)s (password: %(password)s) for " @@ -1941,22 +1983,18 @@ msgstr "" "ce message apparaîtra toujours pour tout le monde et pourrait représenter un " "risque de sécurité." -#: participant/views.py:490 +#: participant/views.py:506 msgid "User settings successfully saved." msgstr "Les paramètres d'utilisateurs ont été enregistrés avec succès." -#: participant/views.py:512 +#: participant/views.py:528 msgid "Password successfully changed." msgstr "Le mot de passe a été changé avec succès." -#: participant/views.py:562 +#: participant/views.py:579 msgid "My motions and elections" msgstr "" -#: participant/views.py:576 -msgid "Users" -msgstr "Utilisateurs" - #: participant/templates/participant/base_participant.html:15 msgid "All participants" msgstr "Tout les participants" @@ -2003,7 +2041,7 @@ msgid "Edit participant" msgstr "Modifier le participant" #: participant/templates/participant/base_participant.html:50 -#: participant/templates/participant/overview.html:97 +#: participant/templates/participant/overview.html:98 msgid "Delete participant" msgstr "Supprimer le participant" @@ -2049,10 +2087,10 @@ msgstr "Aucun groupe n'est disponible" msgid "Select a CSV file to import participants!" msgstr "Selectionnez un fichier CSV pour importer des participants" -#: participant/templates/participant/import.html:11 +#: participant/templates/participant/import.html:12 +#, fuzzy msgid "" -"Required comma separated values: {first_name, last_name, gender, " -"group, type, committee, comment}" +"first_name, last_name, gender, structure level, type, committee, comment" msgstr "" "Valeurs obligatoires, séparées par des virgules: {prénom, nom, sexe, " "groupe, type, committé, commentaire}" @@ -2084,7 +2122,7 @@ msgstr "Continuer en tant qu'invité" msgid "Not specified" msgstr "Non spécifié" -#: participant/templates/participant/overview.html:53 projector/models.py:67 +#: participant/templates/participant/overview.html:53 projector/models.py:63 msgid "Active" msgstr "Actif" @@ -2107,15 +2145,15 @@ msgstr "de" msgid "Last Login" msgstr "Dernière connexion" -#: participant/templates/participant/overview.html:99 +#: participant/templates/participant/overview.html:102 msgid "Change status to inactive" msgstr "Changer le statut sur inactif" -#: participant/templates/participant/overview.html:102 +#: participant/templates/participant/overview.html:105 msgid "Change status to active" msgstr "Changer le statut sur actif" -#: participant/templates/participant/overview.html:111 +#: participant/templates/participant/overview.html:115 #: participant/templates/participant/user_widget.html:22 msgid "No participants available." msgstr "Aucun participant disponible" @@ -2165,6 +2203,11 @@ msgstr "" msgid "The participant has not logged in yet." msgstr "" +#: participant/templates/projector/GroupSlide.html:11 +#, fuzzy +msgid "participants" +msgstr "participant" + #: poll/models.py:95 msgid "Votes invalid" msgstr "Votes invalides" @@ -2173,43 +2216,40 @@ msgstr "Votes invalides" msgid "votes" msgstr "votes" -#: projector/models.py:53 +#: projector/models.py:50 msgid "Can manage the projector" msgstr "Peut gérer le projecteur" -#: projector/models.py:54 +#: projector/models.py:51 msgid "Can see the projector" msgstr "Peut voir le projecteur" -#: projector/models.py:55 +#: projector/models.py:52 msgid "Can see the dashboard" msgstr "Peut voir la vue d'ensemble" -#: projector/views.py:204 +#: projector/views.py:199 msgid "Errors in the form" msgstr "Erreurs dans le formulaire" -#: projector/views.py:383 projector/templates/projector/dashboard.html:17 +#: projector/views.py:375 projector/templates/projector/base_projector.html:7 +#: projector/templates/projector/base_projector.html:12 +#: projector/templates/projector/dashboard.html:17 msgid "Dashboard" msgstr "Vue d'ensemble" -#: projector/views.py:411 +#: projector/views.py:402 msgid "Projector live view" msgstr "Vue projecteur live" -#: projector/views.py:437 +#: projector/views.py:428 msgid "Overlays" msgstr "Superpositions" -#: projector/views.py:450 +#: projector/views.py:440 msgid "Custom Slides" msgstr "Diapositives personnalisées" -#: projector/templates/projector/base_projector.html:7 -#: projector/templates/projector/base_projector.html:12 -msgid "Projector" -msgstr "Projecteur" - #: projector/templates/projector/base_projector.html:15 msgid "Overview" msgstr "Vue d'ensemble" @@ -2221,7 +2261,6 @@ msgid "Select widgets" msgstr "" #: projector/templates/projector/base_projector.html:22 -#: templates/front_page.html:25 msgid "Projector view" msgstr "Vue projecteur" @@ -2259,27 +2298,23 @@ msgstr "Page d'accueil" msgid "New slide" msgstr "nouvelle diapositive" -#: projector/templates/projector/dashboard.html:21 -msgid "Adjust projector view" -msgstr "Régler la vue projecteur" - -#: projector/templates/projector/dashboard.html:22 +#: projector/templates/projector/live_view_widget.html:10 msgid "Zoom in" msgstr "Zoom avant" -#: projector/templates/projector/dashboard.html:25 +#: projector/templates/projector/live_view_widget.html:13 msgid "Zoom out" msgstr "Zoom arrière" -#: projector/templates/projector/dashboard.html:28 +#: projector/templates/projector/live_view_widget.html:18 msgid "Scroll text up" msgstr "Faire défiler le texte vers le haut" -#: projector/templates/projector/dashboard.html:31 +#: projector/templates/projector/live_view_widget.html:21 msgid "Scroll text down" msgstr "Faire défiler le texte vers le bas" -#: projector/templates/projector/dashboard.html:34 +#: projector/templates/projector/live_view_widget.html:26 msgid "Reset projector view" msgstr "Remettre la vue projecteur à zéro" @@ -2300,8 +2335,7 @@ msgstr "N'a pas pu trouver la page." msgid "Server Error" msgstr "Erreur du serveur" -#: templates/base.html:21 templates/front_page.html:6 -#: templates/front_page.html.py:22 +#: templates/base.html:21 msgid "Home" msgstr "Accueil" @@ -2313,60 +2347,107 @@ msgstr "Déconnecter" msgid "Welcome" msgstr "Bienvenue" -#: templates/front_page.html:12 -msgid "You have access to the following pages:" -msgstr "Vous avez accès aux pages suivantes:" +#: templates/base.html:79 +msgid "" +"Get professional " +"support for OpenSlides." +msgstr "" -#: utils/pdf.py:225 -msgid "%Y-%m-%d %H:%Mh" -msgstr "%d-%m-%Y %H:%Mh" - -#: utils/pdf.py:226 +#: utils/pdf.py:227 #, python-format -msgid "Printed: %s" -msgstr "Imprimé: %s" +msgid "As of: %s" +msgstr "" -#: utils/pdf.py:237 utils/pdf.py:246 +#: utils/pdf.py:238 utils/pdf.py:247 #, python-format msgid "Page %s" msgstr "Page %s" -#: utils/utils.py:66 utils/views.py:290 +#: utils/utils.py:69 utils/views.py:287 #, python-format msgid "Do you really want to delete %s?" msgstr "Voulez-vous vraiment supprimer %s?" -#: utils/utils.py:111 +#: utils/utils.py:116 msgid "Sorry, you have no rights to see this page." msgstr "Désolé, vous n'avez pas le droit de voir cette page" -#: utils/views.py:109 +#: utils/views.py:106 msgid "Are you sure?" msgstr "" -#: utils/views.py:110 +#: utils/views.py:107 msgid "Thank you for your answer" msgstr "" -#: utils/views.py:247 +#: utils/views.py:244 #, python-format msgid "%s was successfully modified." msgstr "%s a été modifié avec succès." -#: utils/views.py:278 +#: utils/views.py:275 #, python-format msgid "%s was successfully created." msgstr "%s a été créé avec succès." -#: utils/views.py:296 +#: utils/views.py:293 #, python-format msgid "%s was successfully deleted." msgstr "%s a été supprimé avec succès" -#: utils/views.py:312 +#: utils/views.py:308 msgid "undefined-filename" msgstr "nom de fichier non-déterminé" -#: utils/jsonfield/fields.py:21 +#: utils/jsonfield/fields.py:22 msgid "Enter valid JSON" msgstr "Entre une JSON valide" + +#~ msgid "Assignments" +#~ msgstr "Assignements" + +#~ msgid "posts" +#~ msgstr "postes" + +#~ msgid "candidates" +#~ msgstr "Candidats" + +#~ msgid "Elected Candidates" +#~ msgstr "Candidats bloqué" + +#~ msgid "No elected candidates available." +#~ msgstr "Aucun candidat n'est disponible" + +#~ msgid "No ballots available." +#~ msgstr "Aucun vote disponible." + +#~ msgid "Get professional support for OpenSlides on %s." +#~ msgstr "Obtenez de l'aide professionnelle pour OpenSlides sur %s." + +#~ msgid "" +#~ "Anonymous access enabled. Please modify the \"Anonymous\" group to fit " +#~ "your required permissions." +#~ msgstr "" +#~ "L'accès anonyme a été activé. S'il vous plait, modifiez le groupe " +#~ "\"Anonymous\" pour lui donner les droits nécéssaires" + +#~ msgid "Frontpage" +#~ msgstr "Page d'accueil" + +#~ msgid "Users" +#~ msgstr "Utilisateurs" + +#~ msgid "Projector" +#~ msgstr "Projecteur" + +#~ msgid "Adjust projector view" +#~ msgstr "Régler la vue projecteur" + +#~ msgid "You have access to the following pages:" +#~ msgstr "Vous avez accès aux pages suivantes:" + +#~ msgid "%Y-%m-%d %H:%Mh" +#~ msgstr "%d-%m-%Y %H:%Mh" + +#~ msgid "Printed: %s" +#~ msgstr "Imprimé: %s" diff --git a/openslides/main.py b/openslides/main.py index 83e41395e..f7818b7ab 100755 --- a/openslides/main.py +++ b/openslides/main.py @@ -13,13 +13,15 @@ # for python 2.5 support from __future__ import with_statement -import os -import sys -import optparse -import socket -import time -import threading import base64 +import ctypes +import optparse +import os +import socket +import sys +import tempfile +import threading +import time import webbrowser import django.conf @@ -28,14 +30,15 @@ from django.core.management import execute_from_command_line CONFIG_TEMPLATE = """#!/usr/bin/env python # -*- coding: utf-8 -*- +import openslides.main from openslides.global_settings import * # Use 'DEBUG = True' to get more details for server errors -# (Default for relaeses: 'False') +# (Default for releases: 'False') DEBUG = False TEMPLATE_DEBUG = DEBUG -DBPATH = %(dbpath)r +DBPATH = %(dbpath)s DATABASES = { 'default': { @@ -64,15 +67,12 @@ INSTALLED_APPS += INSTALLED_PLUGINS KEY_LENGTH = 30 - -_fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() -def _fs2unicode(s): - if isinstance(s, unicode): - return s - return s.decode(_fs_encoding) +# sentinel used to signal that the database ought to be stored +# relative to the portable's directory +_portable_db_path = object() -def main(argv=None, opt_defaults=None, database_path=None): +def process_options(argv=None): if argv is None: argv = sys.argv[1:] @@ -86,12 +86,11 @@ def main(argv=None, opt_defaults=None, database_path=None): parser.add_option( "--reset-admin", action="store_true", help="Make sure the user 'admin' exists and uses 'admin' as password") - parser.add_option("-s", "--settings", help="Path to the openslides configuration.") parser.add_option( - "--no-reload", action="store_true", help="Do not reload the development server") - - if not opt_defaults is None: - parser.set_defaults(**opt_defaults) + "-s", "--settings", help="Path to the openslides configuration.") + parser.add_option( + "--no-reload", action="store_true", + help="Do not reload the development server") opts, args = parser.parse_args(argv) if args: @@ -99,12 +98,45 @@ def main(argv=None, opt_defaults=None, database_path=None): parser.print_help() sys.exit(1) + return opts + + +def main(argv=None): + opts = process_options(argv) + _main(opts) + + +def win32_portable_main(argv=None): + """special entry point for the win32 portable version""" + + opts = process_options(argv) + + database_path = None + + if opts.settings is None: + portable_dir = get_portable_path() + try: + fd, test_file = tempfile.mkstemp(dir=portable_dir) + except OSError: + portable_dir_writeable = False + else: + portable_dir_writeable = True + os.close(fd) + os.unlink(test_file) + + if portable_dir_writeable: + opts.settings = os.path.join( + portable_dir, "openslides", "settings.py") + database_path = _portable_db_path + + _main(opts, database_path=database_path) + + +def _main(opts, database_path=None): # Find the path to the settings settings_path = opts.settings if settings_path is None: - config_home = os.environ.get('XDG_CONFIG_HOME', \ - os.path.join(os.path.expanduser('~'), '.config')) - settings_path = os.path.join(config_home, 'openslides', 'settings.py') + settings_path = get_user_config_path('openslides', 'settings.py') # Create settings if necessary 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): settings_module = os.path.dirname(settings_path) - if database_path is None: - data_home = os.environ.get('XDG_DATA_HOME', \ - os.path.join(os.path.expanduser('~'), '.local', 'share')) - database_path = os.path.join(data_home, 'openslides', 'database.sqlite') + if database_path is _portable_db_path: + database_path = get_portable_db_path() + dbpath_value = 'openslides.main.get_portable_db_path()' + else: + if database_path is None: + database_path = get_user_data_path('openslides', 'database.sqlite') + dbpath_value = repr(fs2unicode(database_path)) settings_content = CONFIG_TEMPLATE % dict( default_key=base64.b64encode(os.urandom(KEY_LENGTH)), - dbpath=_fs2unicode(database_path)) + dbpath=dbpath_value) if not os.path.exists(settings_module): os.makedirs(settings_module) @@ -217,6 +252,7 @@ def run_syncdb(): # now initialize the database argv = ["", "syncdb", "--noinput"] execute_from_command_line(argv) + execute_from_command_line(["", "loaddata", "groups_de"]) def set_system_url(url): @@ -269,51 +305,71 @@ def start_browser(url): t = threading.Thread(target=f) t.start() -def win32_portable_main(argv=None): - """special entry point for the win32 portable version""" - import tempfile +def fs2unicode(s): + if isinstance(s, unicode): + return s + fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() + return s.decode(fs_encoding) + + +def get_user_config_path(*args): + if sys.platform == "win32": + return win32_get_app_data_path(*args) + + config_home = os.environ.get( + 'XDG_CONFIG_HOME', os.path.join(os.path.expanduser('~'), '.config')) + + return os.path.join(fs2unicode(config_home), *args) + + +def get_user_data_path(*args): + if sys.platform == "win32": + return win32_get_app_data_path(*args) + + data_home = os.environ.get( + 'XDG_DATA_HOME', os.path.join( + os.path.expanduser('~'), '.local', 'share')) + + return os.path.join(fs2unicode(data_home), *args) + + +def get_portable_path(*args): # NOTE: sys.executable will be the path to openslides.exe # since it is essentially a small wrapper that embeds the # python interpreter - portable_dir = os.path.dirname(os.path.abspath(sys.executable)) - try: - fd, test_file = tempfile.mkstemp(dir=portable_dir) - except OSError: - portable_dir_writeable = False - else: - portable_dir_writeable = True - os.close(fd) - os.unlink(test_file) - if portable_dir_writeable: - default_settings = os.path.join(portable_dir, "openslides", - "openslides_personal_settings.py") - database_path = os.path.join(portable_dir, "openslides", - "database.sqlite") - else: - import ctypes + exename = os.path.basename(sys.executable).lower() + if exename != "openslides.exe": + raise Exception( + "Cannot determine portable path when " + "not running as portable") - shell32 = ctypes.WinDLL("shell32.dll") - SHGetFolderPath = shell32.SHGetFolderPathW - SHGetFolderPath.argtypes = (ctypes.c_void_p, ctypes.c_int, - ctypes.c_void_p, ctypes.c_uint32, ctypes.c_wchar_p) - SHGetFolderPath.restype = ctypes.c_uint32 + portable_dir = fs2unicode(os.path.dirname(os.path.abspath(sys.executable))) + return os.path.join(portable_dir, *args) - CSIDL_LOCAL_APPDATA = 0x001c - MAX_PATH = 260 - buf = ctypes.create_unicode_buffer(MAX_PATH) - res = SHGetFolderPath(0, CSIDL_LOCAL_APPDATA, 0, 0, buf) - if res != 0: - raise Exception("Could not deterime APPDATA path") - default_settings = os.path.join(buf.value, "openslides", - "openslides_personal_settings.py") - database_path = os.path.join(buf.value, "openslides", - "database.sqlite") +def get_portable_db_path(): + return get_portable_path('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__": diff --git a/openslides/motion/forms.py b/openslides/motion/forms.py index 64f949bf4..6fbfc9cff 100644 --- a/openslides/motion/forms.py +++ b/openslides/motion/forms.py @@ -11,7 +11,7 @@ """ from django import forms -from django.utils.translation import ugettext_lazy as _, ugettext_noop +from django.utils.translation import ugettext_lazy as _ from openslides.utils.forms import CssClassMixin from openslides.utils.person import PersonFormField, MultiplePersonFormField @@ -21,18 +21,18 @@ from openslides.motion.models import Motion class MotionForm(forms.Form, CssClassMixin): title = forms.CharField(widget=forms.TextInput(), label=_("Title")) text = forms.CharField(widget=forms.Textarea(), label=_("Text")) - reason = forms.CharField(widget=forms.Textarea(), required=False, - label=_("Reason")) + reason = forms.CharField( + widget=forms.Textarea(), required=False, label=_("Reason")) class MotionFormTrivialChanges(MotionForm): - trivial_change = forms.BooleanField(required=False, - label=_("Trivial change"), + trivial_change = forms.BooleanField( + required=False, label=_("Trivial change"), help_text=_("Trivial changes don't create a new version.")) class MotionManagerForm(forms.ModelForm, CssClassMixin): - submitter = PersonFormField(label = _("Submitter")) + submitter = PersonFormField(label=_("Submitter")) class Meta: model = Motion @@ -46,20 +46,20 @@ class MotionManagerFormSupporter(MotionManagerForm): class MotionImportForm(forms.Form, CssClassMixin): csvfile = forms.FileField( - widget=forms.FileInput(attrs={'size':'50'}), + widget=forms.FileInput(attrs={'size': '50'}), label=_("CSV File"), ) import_permitted = forms.BooleanField( required=False, label=_("Import motions with status \"authorized\""), help_text=_('Set the initial status for each motion to ' - '"authorized"'), + '"authorized"'), ) class ConfigForm(forms.Form, CssClassMixin): 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"), initial=4, min_value=0, @@ -82,7 +82,7 @@ class ConfigForm(forms.Form, CssClassMixin): ] ) motion_pdf_ballot_papers_number = forms.IntegerField( - widget=forms.TextInput(attrs={'class':'small-input'}), + widget=forms.TextInput(attrs={'class': 'small-input'}), required=False, min_value=1, label=_("Custom number of ballot papers") @@ -101,6 +101,6 @@ class ConfigForm(forms.Form, CssClassMixin): motion_allow_trivial_change = forms.BooleanField( label=_("Allow trivial changes"), help_text=_('Warning: Trivial changes undermine the motions ' - 'autorisation system.'), + 'autorisation system.'), required=False, ) diff --git a/openslides/motion/models.py b/openslides/motion/models.py index 380b7de3a..7127e521c 100644 --- a/openslides/motion/models.py +++ b/openslides/motion/models.py @@ -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.person import PersonField - from openslides.config.models import config from openslides.config.signals import default_config_value - -from openslides.poll.models import (BaseOption, BasePoll, CountVotesCast, - CountInvalid, BaseVote) - -from openslides.participant.models import User, Group - +from openslides.poll.models import ( + BaseOption, BasePoll, CountVotesCast, CountInvalid, BaseVote) +from openslides.participant.models import User from openslides.projector.api import register_slidemodel from openslides.projector.models import SlideMixin - from openslides.agenda.models import Item @@ -53,7 +48,7 @@ class Motion(models.Model, SlideMixin): ('noc', _('Not Concerned')), ('com', _('Commited a bill')), ('nop', _('Rejected (not authorized)')), - ('rev', _('Needs Review')), # Where is this status used? + ('rev', _('Needs Review')), # Where is this status used? #additional actions: # edit # delete @@ -67,9 +62,9 @@ class Motion(models.Model, SlideMixin): submitter = PersonField(verbose_name=_("Submitter")) number = models.PositiveSmallIntegerField(blank=True, null=True, - unique=True) + unique=True) status = models.CharField(max_length=3, choices=STATUS, default='pub') - permitted = models.ForeignKey('AVersion', related_name='permitted', \ + permitted = models.ForeignKey('AVersion', related_name='permitted', null=True, blank=True) log = models.TextField(blank=True, null=True) @@ -94,7 +89,7 @@ class Motion(models.Model, SlideMixin): else: return self.last_version - def accept_version(self, version, user = None): + def accept_version(self, version, user=None): """ accept a Version """ @@ -102,15 +97,15 @@ class Motion(models.Model, SlideMixin): self.save(nonewversion=True) version.rejected = False version.save() - self.writelog(_("Version %d authorized") % (version.aid, ), - user) + self.writelog(_("Version %d authorized") % version.aid, user) - def reject_version(self, version, user = None): + def reject_version(self, version, user=None): if version.id > self.permitted.id: version.rejected = True version.save() - self.writelog(pgettext("Rejected means not authorized", "Version %d rejected") - % (version.aid, ), user) + self.writelog(pgettext( + "Rejected means not authorized", "Version %d rejected") + % version.aid, user) return True return False @@ -154,8 +149,8 @@ class Motion(models.Model, SlideMixin): is not the lastone and the lastone is not rejected. TODO: rename the property in unchecked__changes """ - if (self.last_version != self.permitted - and not self.last_version.rejected): + if (self.last_version != self.permitted and + not self.last_version.rejected): return True else: return False @@ -207,7 +202,8 @@ class Motion(models.Model, SlideMixin): last_version = self.last_version fields = ["text", "title", "reason"] if last_version is not None: - changed_fields = [f for f in fields + changed_fields = [ + f for f in fields if getattr(last_version, f) != getattr(self, f)] if not changed_fields: return # No changes @@ -219,19 +215,22 @@ class Motion(models.Model, SlideMixin): last_version.save() meta = AVersion._meta - field_names = [unicode(meta.get_field(f).verbose_name) + field_names = [ + unicode(meta.get_field(f).verbose_name) for f in changed_fields] - self.writelog(_("Trivial changes to version %(version)d; " - "changed fields: %(changed_fields)s") - % dict(version = last_version.aid, - changed_fields = ", ".join(field_names))) - return # Done + self.writelog( + _("Trivial changes to version %(version)d; " + "changed fields: %(changed_fields)s") + % dict(version=last_version.aid, + changed_fields=", ".join(field_names))) + return # Done - version = AVersion(title=getattr(self, 'title', ''), - text=getattr(self, 'text', ''), - reason=getattr(self, 'reason', ''), - motion=self) + version = AVersion( + title=getattr(self, 'title', ''), + text=getattr(self, 'text', ''), + reason=getattr(self, 'reason', ''), + motion=self) version.save() self.writelog(_("Version %s created") % version.aid, user) is_manager = user.has_perm('motion.can_manage_motion') @@ -239,9 +238,8 @@ class Motion(models.Model, SlideMixin): is_manager = False supporters = self.motionsupporter_set.all() - if (self.status == "pub" - and supporters - and not is_manager): + if (self.status == "pub" and + supporters and not is_manager): supporters.delete() 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 """ try: - object = self.motionsupporter_set.get(person=person).delete() + self.motionsupporter_set.get(person=person).delete() except MotionSupporter.DoesNotExist: # TODO: Don't do nothing but raise a precise exception for the view pass @@ -288,8 +286,7 @@ class Motion(models.Model, SlideMixin): raise NameError('This motion has already a number.') if number is None: try: - number = Motion.objects.aggregate(Max('number')) \ - ['number__max'] + 1 + number = Motion.objects.aggregate(Max('number'))['number__max'] + 1 except TypeError: number = 1 self.number = number @@ -316,8 +313,6 @@ class Motion(models.Model, SlideMixin): """ self.set_status(user, "nop") #TODO: reject last version - aversion = self.last_version - #self.permitted = aversion if self.number is None: self.set_number() self.save() @@ -333,11 +328,11 @@ class Motion(models.Model, SlideMixin): error = False break if error: - #TODO: Use the Right Error + # TODO: Use the Right Error raise NameError(_('%s is not a valid status.') % status) if self.status == status: - #TODO: Use the Right Error - raise NameError(_('The motion status is already \'%s.\'') \ + # TODO: Use the Right Error + raise NameError(_('The motion status is already \'%s.\'') % self.status) actions = [] @@ -353,7 +348,7 @@ class Motion(models.Model, SlideMixin): oldstatus = self.get_status_display() self.status = status self.save() - self.writelog(_("Status modified")+": %s -> %s" \ + self.writelog(_("Status modified") + ": %s -> %s" % (oldstatus, self.get_status_display()), user) def get_allowed_actions(self, user): @@ -432,10 +427,9 @@ class Motion(models.Model, SlideMixin): allready a number """ 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.') - for item in Item.objects.filter(related_sid=self.sid): item.delete() super(Motion, self).delete() @@ -501,12 +495,12 @@ class Motion(models.Model, SlideMixin): for poll in self.polls: for option in poll.get_options(): if option.get_votes().exists(): - results.append((option['Yes'], option['No'], + results.append(( + option['Yes'], option['No'], option['Abstain'], poll.print_votesinvalid(), poll.print_votescast())) return results - def slide(self): """ return the slide dict @@ -542,10 +536,10 @@ class Motion(models.Model, SlideMixin): class AVersion(models.Model): - title = models.CharField(max_length=100, verbose_name = _("Title")) - text = models.TextField(verbose_name = _("Text")) - reason = models.TextField(null=True, blank=True, verbose_name = _("Reason")) - rejected = models.BooleanField() # = Not Permitted + title = models.CharField(max_length=100, verbose_name=_("Title")) + text = models.TextField(verbose_name=_("Text")) + reason = models.TextField(null=True, blank=True, verbose_name=_("Reason")) + rejected = models.BooleanField() # = Not Permitted time = models.DateTimeField(auto_now=True) motion = models.ForeignKey(Motion) @@ -576,8 +570,8 @@ class MotionOption(BaseOption): class MotionPoll(BasePoll, CountInvalid, CountVotesCast): option_class = MotionOption - vote_values = [ugettext_noop('Yes'), ugettext_noop('No'), - ugettext_noop('Abstain')] + vote_values = [ + ugettext_noop('Yes'), ugettext_noop('No'), ugettext_noop('Abstain')] motion = models.ForeignKey(Motion) diff --git a/openslides/motion/templates/motion/import.html b/openslides/motion/templates/motion/import.html index aa36852a7..1c4607693 100644 --- a/openslides/motion/templates/motion/import.html +++ b/openslides/motion/templates/motion/import.html @@ -8,7 +8,10 @@

{% trans "Import motions" %}

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

-

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

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

diff --git a/openslides/motion/views.py b/openslides/motion/views.py index 15dbf4913..76b2ba4c3 100644 --- a/openslides/motion/views.py +++ b/openslides/motion/views.py @@ -18,19 +18,17 @@ import os try: from urlparse import parse_qs -except ImportError: # python <= 2.5 +except ImportError: # python <= 2.5 from cgi import parse_qs from reportlab.lib import colors from reportlab.lib.units import cm -from reportlab.platypus import (SimpleDocTemplate, PageBreak, Paragraph, Spacer, - Table, TableStyle) +from reportlab.platypus import ( + SimpleDocTemplate, PageBreak, Paragraph, Spacer, Table, TableStyle) from django.conf import settings from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.contrib.auth.models import User -from django.core.context_processors import csrf from django.core.urlresolvers import reverse from django.db import transaction from django.shortcuts import redirect @@ -39,26 +37,21 @@ from django.utils.translation import ugettext as _, ungettext from openslides.utils import csv_ext from openslides.utils.pdf import stylesheet from openslides.utils.template import Tab -from openslides.utils.utils import (template, permission_required, - del_confirm_form, gen_confirm_form) -from openslides.utils.views import (PDFView, RedirectView, DeleteView, - FormView, SingleObjectMixin, QuestionMixin) +from openslides.utils.utils import ( + template, permission_required, del_confirm_form, gen_confirm_form) +from openslides.utils.views import ( + PDFView, RedirectView, DeleteView, FormView, SingleObjectMixin, + QuestionMixin) from openslides.utils.person import get_person - from openslides.config.models import config - from openslides.projector.projector import Widget - from openslides.poll.views import PollFormView - from openslides.participant.api import gen_username, gen_password from openslides.participant.models import User, Group - from openslides.agenda.models import Item - from openslides.motion.models import Motion, AVersion, MotionPoll -from openslides.motion.forms import (MotionForm, - MotionFormTrivialChanges, MotionManagerForm, +from openslides.motion.forms import ( + MotionForm, MotionFormTrivialChanges, MotionManagerForm, MotionManagerFormSupporter, MotionImportForm, ConfigForm) @@ -124,14 +117,14 @@ def overview(request): for (i, motion) in enumerate(motions): try: motions[i] = { - 'actions' : motion.get_allowed_actions(request.user), - 'motion' : motion + 'actions': motion.get_allowed_actions(request.user), + 'motion': motion } except: # todo: except what? motions[i] = { - 'actions' : [], - 'motion' : motion + 'actions': [], + 'motion': motion } return { @@ -210,12 +203,7 @@ def edit(request, motion_id=None): managerform = None if valid: - del_supporters = True if is_manager: - if motion: # Edit motion - original_supporters = list(motion.supporters) - else: - original_supporters = [] motion = managerform.save(commit=False) elif motion_id is None: motion = Motion(submitter=request.user) @@ -611,7 +599,7 @@ def motion_import(request): except ValueError: messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1)) continue - + if is_group: # fetch existing groups or issue an error message try: @@ -694,7 +682,7 @@ def motion_import(request): return redirect(reverse('motion_overview')) 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: messages.error(request, _('Import file has wrong character encoding, only UTF-8 is supported!')) else: diff --git a/openslides/participant/api.py b/openslides/participant/api.py index 938eacca6..fb5325628 100644 --- a/openslides/participant/api.py +++ b/openslides/participant/api.py @@ -14,15 +14,20 @@ from __future__ import with_statement from random import choice -import string import csv -from django.contrib.auth.models import User +from django.contrib.auth.models import Permission from django.db import transaction +from django.utils.translation import ugettext as _ from openslides.utils import csv_ext -from openslides.participant.models import User +from openslides.participant.models import User, Group + + +DEFAULT_PERMS = ['can_see_agenda', 'can_see_projector', + 'can_see_motion', 'can_see_assignment', + 'can_see_dashboard'] def gen_password(): @@ -73,7 +78,7 @@ def import_users(csv_file): try: (first_name, last_name, gender, structure_level, type, committee, comment) = line[:7] except ValueError: - error_messages.append(_('Ignoring malformed line %d in import file.') % line_no + 1) + error_messages.append(_('Ignoring malformed line %d in import file.') % (line_no + 1)) continue user = User() user.last_name = last_name @@ -93,3 +98,23 @@ def import_users(csv_file): except UnicodeDecodeError: error_messages.appen(_('Import file has wrong character encoding, only UTF-8 is supported!')) return (count_success, error_messages) + + +def get_or_create_registered_group(): + registered, created = Group.objects.get_or_create( + name__iexact='Registered', defaults={'name': 'Registered'}) + if created: + registered.permissions = Permission.objects.filter( + codename__in=DEFAULT_PERMS) + registered.save() + return registered + + +def get_or_create_anonymous_group(): + anonymous, created = Group.objects.get_or_create( + name__iexact='Anonymous', defaults={'name': 'Anonymous'}) + if created: + anonymous.permissions = Permission.objects.filter( + codename__in=DEFAULT_PERMS) + anonymous.save() + return anonymous diff --git a/initial_data.json b/openslides/participant/fixtures/groups_de.json similarity index 100% rename from initial_data.json rename to openslides/participant/fixtures/groups_de.json diff --git a/openslides/participant/forms.py b/openslides/participant/forms.py index da395680e..8085f313f 100644 --- a/openslides/participant/forms.py +++ b/openslides/participant/forms.py @@ -18,6 +18,7 @@ from openslides.utils.forms import ( CssClassMixin, LocalizedModelMultipleChoiceField) from openslides.participant.models import User, Group +from openslides.participant.api import get_or_create_registered_group class UserCreateForm(forms.ModelForm, CssClassMixin): @@ -25,6 +26,13 @@ class UserCreateForm(forms.ModelForm, CssClassMixin): queryset=Group.objects.exclude(name__iexact='anonymous'), label=_("Groups"), required=False) + def __init__(self, *args, **kwargs): + if kwargs.get('instance', None) is None: + initial = kwargs.setdefault('initial', {}) + registered = get_or_create_registered_group() + initial['groups'] = [registered.pk] + super(UserCreateForm, self).__init__(*args, **kwargs) + class Meta: model = User fields = ('first_name', 'last_name', 'is_active', 'groups', 'structure_level', @@ -58,12 +66,13 @@ class GroupForm(forms.ModelForm, CssClassMixin): instance = forms.ModelForm.save(self, False) old_save_m2m = self.save_m2m - def save_m2m(): - old_save_m2m() - instance.user_set.clear() - for user in self.cleaned_data['users']: - instance.user_set.add(user) + def save_m2m(): + old_save_m2m() + + instance.user_set.clear() + for user in self.cleaned_data['users']: + instance.user_set.add(user) self.save_m2m = save_m2m if commit: @@ -76,13 +85,13 @@ class GroupForm(forms.ModelForm, CssClassMixin): # Do not allow to change the name "anonymous" or give another group # this name data = self.cleaned_data['name'] - if self.instance.name.lower() == 'anonymous': + if self.instance.name.lower() in ['anonymous', 'registered']: # Editing the anonymous-user if self.instance.name.lower() != data.lower(): raise forms.ValidationError( - _('You can not edit the name for the anonymous user')) + _('You can not edit the name for this group.')) else: - if data.lower() == 'anonymous': + if data.lower() in ['anonymous', 'registered']: raise forms.ValidationError( _('Group name "%s" is reserved for internal use.') % data) return data @@ -94,7 +103,7 @@ class GroupForm(forms.ModelForm, CssClassMixin): class UsersettingsForm(forms.ModelForm, CssClassMixin): class Meta: model = User - fields = ('username', 'first_name', 'last_name', 'gender', 'email', 'committee', 'about_me' ) + fields = ('username', 'first_name', 'last_name', 'gender', 'email', 'committee', 'about_me') class UserImportForm(forms.Form, CssClassMixin): diff --git a/openslides/participant/models.py b/openslides/participant/models.py index 14758a16d..6cad7364f 100644 --- a/openslides/participant/models.py +++ b/openslides/participant/models.py @@ -25,8 +25,9 @@ from openslides.config.signals import default_config_value from openslides.projector.api import register_slidemodel from openslides.projector.projector import SlideMixin + class User(DjangoUser, PersonMixin, Person, SlideMixin): - prefix = 'user' # This is for the slides + prefix = 'user' # This is for the slides person_prefix = 'user' GENDER_CHOICES = ( ('male', _('Male')), @@ -131,18 +132,21 @@ class User(DjangoUser, PersonMixin, Person, SlideMixin): register_slidemodel(User) + class Group(DjangoGroup, PersonMixin, Person, SlideMixin): - prefix = 'group' # This is for the slides + prefix = 'group' # This is for the slides person_prefix = 'group' django_group = models.OneToOneField(DjangoGroup, editable=False, parent_link=True) - group_as_person = models.BooleanField(default=False, 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")) @models.permalink def get_absolute_url(self, link='view'): """ - Return the URL to this user. + Return the URL to this user group. link can be: * view @@ -173,6 +177,7 @@ class Group(DjangoGroup, PersonMixin, Person, SlideMixin): register_slidemodel(Group) + class UsersAndGroupsToPersons(object): """ 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 """ - return UsersAndGroupsToPersons(person_prefix_filter=kwargs['person_prefix_filter'], - id_filter=kwargs['id_filter']) + return UsersAndGroupsToPersons( + person_prefix_filter=kwargs['person_prefix_filter'], + id_filter=kwargs['id_filter']) @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) -def user_post_save(sender, instance, signal, *args, **kwargs): +def djangouser_post_save(sender, instance, signal, *args, **kwargs): try: instance.user except User.DoesNotExist: @@ -242,8 +248,18 @@ def user_post_save(sender, instance, signal, *args, **kwargs): @receiver(signals.post_save, sender=DjangoGroup) -def group_post_save(sender, instance, signal, *args, **kwargs): +def djangogroup_post_save(sender, instance, signal, *args, **kwargs): try: instance.group except Group.DoesNotExist: Group(django_group=instance).save_base(raw=True) + + +@receiver(signals.post_save, sender=User) +def user_post_save(sender, instance, *args, **kwargs): + from openslides.participant.api import get_or_create_registered_group + if not kwargs['created']: + return + registered = get_or_create_registered_group() + instance.groups.add(registered) + instance.save() diff --git a/openslides/participant/templates/participant/base_participant.html b/openslides/participant/templates/participant/base_participant.html index a0f318179..751c02a3a 100644 --- a/openslides/participant/templates/participant/base_participant.html +++ b/openslides/participant/templates/participant/base_participant.html @@ -66,7 +66,7 @@ {# delete group #} - {% if group.name != 'Anonymous' %} + {% if group.name|lower != 'anonymous' and group.name|lower != 'registered' %}
  • {% trans 'Delete group' %}
  • diff --git a/openslides/participant/templates/participant/group_overview.html b/openslides/participant/templates/participant/group_overview.html index 8bcbf46c1..550fe2f56 100644 --- a/openslides/participant/templates/participant/group_overview.html +++ b/openslides/participant/templates/participant/group_overview.html @@ -17,7 +17,7 @@ {{ group.name }} - {% if group.name|lower != 'anonymous' %} + {% if group.name|lower != 'anonymous' and group.name|lower != 'registered' %} {% endif %} diff --git a/openslides/participant/templates/participant/import.html b/openslides/participant/templates/participant/import.html index d01a76e39..4b1777332 100644 --- a/openslides/participant/templates/participant/import.html +++ b/openslides/participant/templates/participant/import.html @@ -8,7 +8,8 @@

    {% trans 'Import participants' %}

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

    -

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

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

    diff --git a/openslides/participant/templates/projector/GroupSlide.html b/openslides/participant/templates/projector/GroupSlide.html index 90f738743..3de117e95 100644 --- a/openslides/participant/templates/projector/GroupSlide.html +++ b/openslides/participant/templates/projector/GroupSlide.html @@ -8,17 +8,15 @@ {% block content %}
    {{ group }} -

    {{ group.user_set.all.count }} {% trans "participants" %}:

    -

    - {% if group.user_set.all %} - {{ group.user_set.all|join:", " }} - {% endif %} -

    +

    {{ group.user_set.all.count }} {% trans "participants" %}

    + {% comment %} + TODO: print fullname (not username) of all group users [see #420] +

    + {% if group.user_set.all %} + {{ group.user_set.all|join:", " }} + {% endif %} +

    + {% endcomment %}
    - -{% endblock %} - -{% block scrollcontent %} - {% endblock %} diff --git a/openslides/participant/urls.py b/openslides/participant/urls.py index e3ac2e553..569f8b85f 100644 --- a/openslides/participant/urls.py +++ b/openslides/participant/urls.py @@ -11,7 +11,6 @@ """ from django.conf.urls.defaults import url, patterns -from django.core.urlresolvers import reverse from openslides.participant.views import ( UserOverview, UserCreateView, UserDetailView, UserUpdateView, diff --git a/openslides/participant/views.py b/openslides/participant/views.py index b4be8b998..34cb032a0 100644 --- a/openslides/participant/views.py +++ b/openslides/participant/views.py @@ -28,7 +28,6 @@ from reportlab.platypus import ( from django.contrib import messages from django.contrib.auth.decorators import login_required from django.contrib.auth.forms import PasswordChangeForm -from django.contrib.auth.models import User from django.contrib.auth.views import login as django_login from django.core.urlresolvers import reverse from django.shortcuts import redirect @@ -39,16 +38,12 @@ from openslides.utils.template import Tab from openslides.utils.utils import ( template, decodedict, encodedict, delete_default_permissions, html_strong) from openslides.utils.views import ( - FormView, PDFView, CreateView, UpdateView, DeleteView, - RedirectView, SingleObjectMixin, ListView, QuestionMixin) - + FormView, PDFView, CreateView, UpdateView, DeleteView, PermissionMixin, + RedirectView, SingleObjectMixin, ListView, QuestionMixin, DetailView) from openslides.config.models import config - from openslides.projector.projector import Widget - from openslides.motion.models import Motion from openslides.assignment.models import Assignment - from openslides.participant.api import gen_username, gen_password, import_users from openslides.participant.forms import ( UserCreateForm, UserUpdateForm, UsersettingsForm, @@ -125,11 +120,13 @@ class UserOverview(ListView): percent = 0 # list of all existing categories - structure_levels = [p['structure_level'] for p in User.objects.values('structure_level') - .exclude(structure_level='').distinct()] + structure_levels = [ + p['structure_level'] for p in + User.objects.values('structure_level').exclude(structure_level='').distinct()] # list of all existing committees - committees = [p['committee'] for p in User.objects.values('committee') - .exclude(committee='').distinct()] + committees = [ + p['committee'] for p in + User.objects.values('committee').exclude(committee='').distinct()] # context vars context.update({ 'allusers': all_users, @@ -137,13 +134,13 @@ class UserOverview(ListView): 'percent': round(percent, 1), 'structure_levels': structure_levels, 'committees': committees, - 'cookie': ['participant_sortfilter', urlencode(decodedict(self.sortfilter), + 'cookie': [ + 'participant_sortfilter', urlencode(decodedict(self.sortfilter), doseq=True)], 'sortfilter': self.sortfilter}) return context -from openslides.utils.views import DetailView, PermissionMixin class UserDetailView(DetailView, PermissionMixin): """ Classed based view to show a specific user in the interface. @@ -177,8 +174,8 @@ class UserCreateView(CreateView): apply_url = 'user_edit' def manipulate_object(self, form): - self.object.username = gen_username(form.cleaned_data['first_name'], - form.cleaned_data['last_name']) + self.object.username = gen_username( + form.cleaned_data['first_name'], form.cleaned_data['last_name']) if not self.object.default_password: self.object.default_password = gen_password() self.object.set_password(self.object.default_password) @@ -211,6 +208,7 @@ class UserDeleteView(DeleteView): else: super(UserDeleteView, self).pre_redirect(request, *args, **kwargs) + class SetUserStatusView(RedirectView, SingleObjectMixin): """ Activate or deactivate an user. @@ -443,7 +441,7 @@ class GroupDeleteView(DeleteView): url = 'user_group_overview' 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.")) else: super(GroupDeleteView, self).pre_redirect(request, *args, **kwargs) @@ -547,8 +545,9 @@ def register_tab(request): return Tab( title=_('Participants'), url=reverse('user_overview'), - permission=request.user.has_perm('participant.can_see_participant') or - request.user.has_perm('participant.can_manage_participant'), + permission=( + request.user.has_perm('participant.can_see_participant') or + request.user.has_perm('participant.can_manage_participant')), selected=selected) @@ -569,12 +568,12 @@ def get_personal_info_widget(request): and where you are supporter or candidate. """ personal_info_context = { - 'submitted_motions': Motion.objects.filter(submitter=request.user), - 'config_motion_min_supporters': config['motion_min_supporters'], - 'supported_motions': Motion.objects.filter(motionsupporter=request.user), - 'assignments': Assignment.objects.filter( - assignmentcandidate__person=request.user, - assignmentcandidate__blocked=False),} + 'submitted_motions': Motion.objects.filter(submitter=request.user), + 'config_motion_min_supporters': config['motion_min_supporters'], + 'supported_motions': Motion.objects.filter(motionsupporter=request.user), + 'assignments': Assignment.objects.filter( + assignmentcandidate__person=request.user, + assignmentcandidate__blocked=False)} return Widget( name='personal_info', display_name=_('My motions and elections'), @@ -593,7 +592,7 @@ def get_user_widget(request): name='user', display_name=_('Participants'), template='participant/user_widget.html', - context={'users': User.objects.all(),}, + context={'users': User.objects.all()}, permission_required='projector.can_manage_projector', default_column=1) @@ -607,6 +606,6 @@ def get_group_widget(request): name='group', display_name=_('Groups'), template='participant/group_widget.html', - context={'groups': Group.objects.all(),}, + context={'groups': Group.objects.all()}, permission_required='projector.can_manage_projector', default_column=1) diff --git a/openslides/poll/forms.py b/openslides/poll/forms.py index 2fd893b11..7d4db9822 100644 --- a/openslides/poll/forms.py +++ b/openslides/poll/forms.py @@ -11,7 +11,6 @@ """ from django import forms -from django.utils.translation import ugettext_lazy as _ from openslides.utils.forms import CssClassMixin diff --git a/openslides/poll/models.py b/openslides/poll/models.py index a38514619..19821b3d1 100644 --- a/openslides/poll/models.py +++ b/openslides/poll/models.py @@ -47,7 +47,7 @@ class BaseVote(models.Model): Subclasses have to define a option-field, which are a subclass of BaseOption. """ - weight = models.IntegerField(default=1, null=True) # Use MinMaxIntegerField + weight = models.IntegerField(default=1, null=True) # Use MinMaxIntegerField value = models.CharField(max_length=255, null=True) def print_weight(self, raw=False): @@ -73,7 +73,7 @@ class BaseVote(models.Model): class CountVotesCast(models.Model): votescast = MinMaxIntegerField(null=True, blank=True, min_value=-2, - verbose_name=_("Votes cast")) + verbose_name=_("Votes cast")) def append_pollform_fields(self, fields): fields.append('votescast') @@ -92,7 +92,7 @@ class CountVotesCast(models.Model): class CountInvalid(models.Model): votesinvalid = MinMaxIntegerField(null=True, blank=True, min_value=-2, - verbose_name=_("Votes invalid")) + verbose_name=_("Votes invalid")) def append_pollform_fields(self, fields): fields.append('votesinvalid') @@ -164,7 +164,6 @@ class BasePoll(models.Model): """ return self.vote_values - def get_vote_class(self): """ Return the releatet vote class. @@ -212,7 +211,7 @@ class BasePoll(models.Model): """ from openslides.poll.forms import OptionForm return OptionForm(extra=self.get_form_values(kwargs['formid']), - **kwargs) + **kwargs) def get_vote_forms(self, **kwargs): """ diff --git a/openslides/poll/views.py b/openslides/poll/views.py index ef58218d9..70c48e7cc 100644 --- a/openslides/poll/views.py +++ b/openslides/poll/views.py @@ -35,7 +35,7 @@ class PollFormView(TemplateView): context['forms'] = self.poll.get_vote_forms() FormClass = self.get_modelform_class() context['pollform'] = FormClass(instance=self.poll, - prefix='pollform') + prefix='pollform') return context def get_success_url(self): @@ -52,7 +52,7 @@ class PollFormView(TemplateView): FormClass = self.get_modelform_class() pollform = FormClass(data=self.request.POST, instance=self.poll, - prefix='pollform') + prefix='pollform') error = False for form in option_forms: diff --git a/openslides/projector/api.py b/openslides/projector/api.py index 6bcc04772..7cbd5da02 100644 --- a/openslides/projector/api.py +++ b/openslides/projector/api.py @@ -12,12 +12,11 @@ from django.conf import settings from django.core.cache import cache -from django.template.loader import render_to_string from django.utils.datastructures import SortedDict from django.utils.importlib import import_module from openslides.config.models import config -from openslides.projector.projector import SLIDE, Slide, Widget +from openslides.projector.projector import SLIDE, Slide def split_sid(sid): @@ -95,27 +94,18 @@ def clear_projector_cache(): cache.delete('projector_data') -def register_slidemodel(model, model_name=None, control_template=None, - weight=0): +def register_slidemodel(model, model_name=None, control_template=None, weight=0): """ Register a Model as a slide. """ + # TODO: control_template should never be None if model_name is None: model_name = model.prefix - if control_template is None: - control_template = 'projector/default_control_slidemodel.html' - category = model.__module__.split('.')[0] - SLIDE[model_name] = Slide( - model_slide=True, - model=model, - category=category, - key=model.prefix, - model_name=model_name, - control_template=control_template, - weight=weight, - ) + SLIDE[model_name] = Slide(model_slide=True, model=model, category=category, + key=model.prefix, model_name=model_name, + control_template=control_template, weight=weight) def register_slidefunc(key, func, control_template=None, weight=0, name=''): @@ -125,15 +115,9 @@ def register_slidefunc(key, func, control_template=None, weight=0, name=''): if control_template is None: control_template = 'projector/default_control_slidefunc.html' category = func.__module__.split('.')[0] - SLIDE[key] = Slide( - model_slide=False, - func=func, - category=category, - key=key, - control_template=control_template, - weight=weight, - name=name, - ) + SLIDE[key] = Slide(model_slide=False, func=func, category=category, + key=key, control_template=control_template, weight=weight, + name=name,) def projector_message_set(message, sid=None): @@ -147,7 +131,7 @@ def projector_message_set(message, sid=None): overlay = ProjectorOverlay.objects.get(def_name='Message') except ProjectorOverlay.DoesNotExist: overlay = ProjectorOverlay(def_name='Message', active=False) - overlay.sid=sid + overlay.sid = sid overlay.save() @@ -166,7 +150,6 @@ def get_all_widgets(request, session=False): mod = import_module(app + '.views') except ImportError: continue - appname = mod.__name__.split('.')[0] try: modul_widgets = mod.get_widgets(request) except AttributeError: diff --git a/openslides/projector/forms.py b/openslides/projector/forms.py index 3c8d24498..59d69ef9c 100644 --- a/openslides/projector/forms.py +++ b/openslides/projector/forms.py @@ -11,7 +11,6 @@ """ from django import forms -from django.utils.translation import ugettext_lazy as _ from openslides.utils.forms import CssClassMixin diff --git a/openslides/projector/models.py b/openslides/projector/models.py index 299b41afb..f403b6395 100644 --- a/openslides/projector/models.py +++ b/openslides/projector/models.py @@ -19,9 +19,6 @@ from openslides.config.signals import default_config_value from openslides.projector.api import register_slidemodel from openslides.projector.projector import SlideMixin -from openslides.config.models import config - - class ProjectorSlide(models.Model, SlideMixin): """ @@ -56,8 +53,7 @@ class ProjectorSlide(models.Model, SlideMixin): ) -register_slidemodel(ProjectorSlide, - control_template='projector/control_customslide.html') +register_slidemodel(ProjectorSlide, control_template='projector/control_customslide.html') class ProjectorOverlay(models.Model): diff --git a/openslides/projector/projector.py b/openslides/projector/projector.py index a35e0517b..6e3129f4b 100644 --- a/openslides/projector/projector.py +++ b/openslides/projector/projector.py @@ -19,9 +19,9 @@ from openslides.config.models import config from openslides.projector.signals import projector_overlays - SLIDE = {} + class SlideMixin(object): """ A Mixin for a Django-Model, for making the model a slide. @@ -49,13 +49,16 @@ class SlideMixin(object): """ Return True, if the the slide is the active slide. """ - from api import get_active_slide + if self.id is None: + return False + from openslides.projector.api import get_active_slide return get_active_slide(only_sid=True) == self.sid def set_active(self): """ Appoint this item as the active slide. """ + from openslides.projector.api import set_active_slide set_active_slide(self.sid) def save(self, *args, **kwargs): @@ -112,7 +115,7 @@ class Widget(object): Class for a Widget for the Projector-Tab. """ def __init__(self, name, html=None, template=None, context={}, - permission_required=None, display_name=None, default_column=1): + permission_required=None, display_name=None, default_column=1): self.name = name if display_name is None: self.display_name = name.capitalize() diff --git a/openslides/projector/views.py b/openslides/projector/views.py index e9ab1b3ec..997e89cf2 100644 --- a/openslides/projector/views.py +++ b/openslides/projector/views.py @@ -13,34 +13,28 @@ from datetime import datetime from time import time -from django.conf import settings from django.contrib import messages from django.core.cache import cache from django.core.context_processors import csrf from django.core.urlresolvers import reverse from django.db import transaction from django.db.models import Q -from django.dispatch import receiver from django.shortcuts import redirect 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 openslides.utils.template import render_block_to_string, Tab -from openslides.utils.utils import html_strong -from openslides.utils.views import (TemplateView, RedirectView, CreateView, - UpdateView, DeleteView, AjaxMixin) - +from openslides.utils.views import ( + TemplateView, RedirectView, CreateView, UpdateView, DeleteView, AjaxMixin) from openslides.config.models import config - -from openslides.projector.api import (get_active_slide, set_active_slide, - projector_message_set, projector_message_delete, get_slide_from_sid, - get_all_widgets, clear_projector_cache) -from openslides.projector.forms import SelectWidgetsForm -from openslides.projector.models import ProjectorOverlay, ProjectorSlide -from openslides.projector.projector import SLIDE, Widget -from openslides.projector.signals import projector_overlays +from .api import ( + get_active_slide, set_active_slide, projector_message_set, + projector_message_delete, get_slide_from_sid, get_all_widgets, + clear_projector_cache) +from .forms import SelectWidgetsForm +from .models import ProjectorOverlay, ProjectorSlide +from .projector import Widget +from .signals import projector_overlays class DashboardView(TemplateView, AjaxMixin): @@ -73,7 +67,7 @@ class Projector(TemplateView, AjaxMixin): if sid is None: try: data = get_active_slide() - except AttributeError: #TODO: It has to be an Slide.DoesNotExist + except AttributeError: # TODO: It has to be an Slide.DoesNotExist data = None ajax = 'on' active_sid = get_active_slide(True) @@ -92,10 +86,10 @@ class Projector(TemplateView, AjaxMixin): # Projector Overlays if self.kwargs['sid'] is None: active_defs = ProjectorOverlay.objects.filter(active=True) \ - .filter(Q(sid=active_sid) | Q(sid=None)).values_list('def_name', - flat=True) - for receiver, response in projector_overlays.send(sender=sid, - register=False, call=active_defs): + .filter(Q(sid=active_sid) | Q(sid=None)).values_list( + 'def_name', flat=True) + for receiver, response in projector_overlays.send( + sender=sid, register=False, call=active_defs): if response is not None: data['overlays'].append(response) self._data = data @@ -124,7 +118,6 @@ class Projector(TemplateView, AjaxMixin): 'scrollcontent', self.data) cache.set('projector_scrollcontent', scrollcontent, 1) - # TODO: do not call the hole data-methode, if we only need some vars data = cache.get('projector_data') if not data: @@ -301,7 +294,6 @@ class OverlayMessageView(RedirectView): elif 'message-clean' in request.POST: projector_message_delete() - def get_ajax_context(self, **kwargs): clear_projector_cache() return { @@ -309,7 +301,6 @@ class OverlayMessageView(RedirectView): } - class ActivateOverlay(RedirectView): """ Activate or deactivate an overlay. @@ -379,12 +370,11 @@ def register_tab(request): """ Register the projector tab. """ - selected = True if request.path.startswith('/projector/') else False + selected = request.path.startswith('/projector/') return Tab( title=_('Dashboard'), url=reverse('dashboard'), - permission=request.user.has_perm('projector.can_manage_projector') or - request.user.has_perm('projector.can_see_dashboard'), + permission=request.user.has_perm('projector.can_see_dashboard'), selected=selected, ) @@ -411,7 +401,7 @@ def get_widgets(request): name='live_view', display_name=_('Projector live view'), template='projector/live_view_widget.html', - context = RequestContext(request, {}), + context=RequestContext(request, {}), permission_required='projector.can_see_projector', default_column=2)) @@ -424,15 +414,14 @@ def get_widgets(request): projector_overlay = ProjectorOverlay.objects.get( def_name=name) except ProjectorOverlay.DoesNotExist: - projector_overlay = ProjectorOverlay(def_name=name, - active=False) + projector_overlay = ProjectorOverlay(def_name=name, active=False) projector_overlay.save() overlays.append(projector_overlay) context = { - 'overlays':overlays, - 'countdown_time': config['countdown_time'], - 'countdown_state' : config['countdown_state']} + 'overlays': overlays, + 'countdown_time': config['countdown_time'], + 'countdown_state': config['countdown_state']} context.update(csrf(request)) widgets.append(Widget( name='overlays', @@ -442,7 +431,6 @@ def get_widgets(request): default_column=2, context=context)) - # Custom slide widget context = { 'slides': ProjectorSlide.objects.all().order_by('weight'), diff --git a/openslides/urls.py b/openslides/urls.py index 509fea41c..5edb948d0 100644 --- a/openslides/urls.py +++ b/openslides/urls.py @@ -12,8 +12,6 @@ from django.conf import settings from django.conf.urls.defaults import patterns, url, include -from django.contrib.staticfiles.urls import staticfiles_urlpatterns -from django.shortcuts import redirect from django.utils.importlib import import_module from openslides.utils.views import RedirectView @@ -34,7 +32,7 @@ urlpatterns = patterns('', ) urlpatterns += patterns('django.contrib.staticfiles.views', - url(r'^static/(?P.*)$', 'serve', {'insecure':True}), + url(r'^static/(?P.*)$', 'serve', {'insecure': True}), ) js_info_dict = { diff --git a/openslides/utils/auth/AnonymousAuth.py b/openslides/utils/auth/AnonymousAuth.py index 8d21cc1e0..e6c4b27fe 100644 --- a/openslides/utils/auth/AnonymousAuth.py +++ b/openslides/utils/auth/AnonymousAuth.py @@ -37,8 +37,8 @@ class AnonymousAuth(object): - try to return the permissions for the 'Anonymous' group """ - if not user_obj.is_anonymous() or obj is not None or \ - not config['system_enable_anonymous']: + if (not user_obj.is_anonymous() or obj is not None or + not config['system_enable_anonymous']): return set() perms = Permission.objects.filter(group__name='Anonymous') @@ -60,8 +60,8 @@ class AnonymousAuth(object): """ Check if the user as a specific permission """ - if not user_obj.is_anonymous() or obj is not None or \ - not config['system_enable_anonymous']: + if (not user_obj.is_anonymous() or obj is not None or + not config['system_enable_anonymous']): return False return (perm in self.get_all_permissions(user_obj)) @@ -70,8 +70,8 @@ class AnonymousAuth(object): """ Check if the user has permissions on the module app_label """ - if not user_obj.is_anonymous() or \ - not config['system_enable_anonymous']: + if (not user_obj.is_anonymous() or + not config['system_enable_anonymous']): return False for perm in self.get_all_permissions(user_obj): @@ -87,10 +87,10 @@ class AnonymousAuth(object): """ return None + def anonymous_context_additions(RequestContext): """ Add a variable to the request context that will indicate if anonymous login is possible at all. """ - return {'os_enable_anonymous_login' : config['system_enable_anonymous']} - + return {'os_enable_anonymous_login': config['system_enable_anonymous']} diff --git a/openslides/utils/csv_ext.py b/openslides/utils/csv_ext.py index 864de5cf4..4f2fa8c4a 100644 --- a/openslides/utils/csv_ext.py +++ b/openslides/utils/csv_ext.py @@ -25,10 +25,9 @@ class excel_semikolon(Dialect): def patchup(dialect): if dialect: if dialect.delimiter in [excel_semikolon.delimiter, excel.delimiter] and \ - dialect.quotechar == excel_semikolon.quotechar: + dialect.quotechar == excel_semikolon.quotechar: # walks like a duck and talks like a duck.. must be one dialect.doublequote = True return dialect register_dialect("excel_semikolon", excel_semikolon) - diff --git a/openslides/utils/jsonfield/__init__.py b/openslides/utils/jsonfield/__init__.py index b8d87c831..6dde401ce 100644 --- a/openslides/utils/jsonfield/__init__.py +++ b/openslides/utils/jsonfield/__init__.py @@ -1 +1,3 @@ -from fields import JSONField \ No newline at end of file +from fields import JSONField + +__all__ = ['JSONField'] diff --git a/openslides/utils/jsonfield/fields.py b/openslides/utils/jsonfield/fields.py index 029ca74db..45c66aee4 100644 --- a/openslides/utils/jsonfield/fields.py +++ b/openslides/utils/jsonfield/fields.py @@ -6,6 +6,7 @@ from django.utils.translation import ugettext_lazy as _ from django.forms.fields import Field from django.forms.util import ValidationError as FormValidationError + class JSONFormField(Field): def clean(self, value): @@ -21,6 +22,7 @@ class JSONFormField(Field): raise FormValidationError(_("Enter valid JSON")) return value + class JSONField(models.TextField): """JSONField is a generic textfield that serializes/unserializes JSON objects""" diff --git a/openslides/utils/jsonfield/tests.py b/openslides/utils/jsonfield/tests.py deleted file mode 100644 index 2eb87ec37..000000000 --- a/openslides/utils/jsonfield/tests.py +++ /dev/null @@ -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) diff --git a/openslides/utils/modelfields.py b/openslides/utils/modelfields.py index 72f63b51d..e3ccd1ef4 100644 --- a/openslides/utils/modelfields.py +++ b/openslides/utils/modelfields.py @@ -12,12 +12,13 @@ from django.db import models + class MinMaxIntegerField(models.IntegerField): def __init__(self, min_value=None, max_value=None, *args, **kwargs): self.min_value, self.max_value = min_value, max_value super(MinMaxIntegerField, self).__init__(*args, **kwargs) def formfield(self, **kwargs): - defaults = {'min_value': self.min_value, 'max_value' : self.max_value} + defaults = {'min_value': self.min_value, 'max_value': self.max_value} defaults.update(kwargs) return super(MinMaxIntegerField, self).formfield(**defaults) diff --git a/openslides/utils/pdf.py b/openslides/utils/pdf.py index a50dfdcd4..4064f109e 100755 --- a/openslides/utils/pdf.py +++ b/openslides/utils/pdf.py @@ -28,16 +28,16 @@ from openslides.config.models import config # register new truetype fonts -pdfmetrics.registerFont(TTFont('Ubuntu', path_join(settings.SITE_ROOT, - 'static/fonts/Ubuntu-R.ttf'))) -pdfmetrics.registerFont(TTFont('Ubuntu-Bold', path_join(settings.SITE_ROOT, - 'static/fonts/Ubuntu-B.ttf'))) -pdfmetrics.registerFont(TTFont('Ubuntu-Italic', path_join(settings.SITE_ROOT, - 'static/fonts/Ubuntu-RI.ttf'))) +pdfmetrics.registerFont(TTFont( + 'Ubuntu', path_join(settings.SITE_ROOT, 'static/fonts/Ubuntu-R.ttf'))) +pdfmetrics.registerFont(TTFont( + 'Ubuntu-Bold', path_join(settings.SITE_ROOT, 'static/fonts/Ubuntu-B.ttf'))) +pdfmetrics.registerFont(TTFont( + 'Ubuntu-Italic', path_join(settings.SITE_ROOT, 'static/fonts/Ubuntu-RI.ttf'))) # set style information -PAGE_HEIGHT = defaultPageSize[1]; +PAGE_HEIGHT = defaultPageSize[1] PAGE_WIDTH = defaultPageSize[0] @@ -105,104 +105,104 @@ stylesheet.add(ParagraphStyle( leftIndent=0, spaceAfter=15, )) -stylesheet.add(ParagraphStyle(name = 'Subitem', - parent = stylesheet['Normal'], - fontSize = 10, - leading = 10, - leftIndent = 20, - spaceAfter = 15) - ) -stylesheet.add(ParagraphStyle(name = 'Tablecell', - parent = stylesheet['Normal'], - fontSize = 9) - ) -stylesheet.add(ParagraphStyle(name = 'Signaturefield', - parent = stylesheet['Normal'], - spaceBefore = 15) +stylesheet.add(ParagraphStyle( + name='Subitem', + parent=stylesheet['Normal'], + fontSize=10, + leading=10, + leftIndent=20, + spaceAfter=15)) +stylesheet.add(ParagraphStyle( + name='Tablecell', + parent=stylesheet['Normal'], + fontSize=9)) +stylesheet.add(ParagraphStyle(name='Signaturefield', + parent=stylesheet['Normal'], + spaceBefore=15) ) # Ballot stylesheets -stylesheet.add(ParagraphStyle(name = 'Ballot_title', - parent = stylesheet['Bold'], - fontSize = 12, - leading = 14, - leftIndent = 30), +stylesheet.add(ParagraphStyle(name='Ballot_title', + parent=stylesheet['Bold'], + fontSize=12, + leading=14, + leftIndent=30), ) -stylesheet.add(ParagraphStyle(name = 'Ballot_subtitle', - parent = stylesheet['Normal'], - fontSize = 10, - leading = 12, - leftIndent = 30, - rightIndent = 20, - spaceAfter = 5), +stylesheet.add(ParagraphStyle(name='Ballot_subtitle', + parent=stylesheet['Normal'], + fontSize=10, + leading=12, + leftIndent=30, + rightIndent=20, + spaceAfter=5), ) -stylesheet.add(ParagraphStyle(name = 'Ballot_description', - parent = stylesheet['Normal'], - fontSize = 7, - leading = 10, - leftIndent = 30), +stylesheet.add(ParagraphStyle(name='Ballot_description', + parent=stylesheet['Normal'], + fontSize=7, + leading=10, + leftIndent=30), ) -stylesheet.add(ParagraphStyle(name = 'Ballot_option', - parent = stylesheet['Normal'], - fontSize = 12, - leading = 24, - leftIndent = 30), +stylesheet.add(ParagraphStyle(name='Ballot_option', + parent=stylesheet['Normal'], + fontSize=12, + leading=24, + leftIndent=30), ) -stylesheet.add(ParagraphStyle(name = 'Monotype', - parent = stylesheet['Normal'], - fontName = 'Courier', - fontSize = 12, - leading = 24, - leftIndent = 30), +stylesheet.add(ParagraphStyle(name='Monotype', + parent=stylesheet['Normal'], + fontName='Courier', + fontSize=12, + leading=24, + leftIndent=30), ) -stylesheet.add(ParagraphStyle(name = 'Ballot_option_name', - parent = stylesheet['Normal'], - fontSize = 12, - leading = 15, - leftIndent = 30), +stylesheet.add(ParagraphStyle(name='Ballot_option_name', + parent=stylesheet['Normal'], + fontSize=12, + leading=15, + leftIndent=30), ) -stylesheet.add(ParagraphStyle(name = 'Ballot_option_group', - parent = stylesheet['Normal'], - fontSize = 8, - leading = 15, - leftIndent = 30), +stylesheet.add(ParagraphStyle(name='Ballot_option_group', + parent=stylesheet['Normal'], + fontSize=8, + leading=15, + leftIndent=30), ) -stylesheet.add(ParagraphStyle(name = 'Ballot_option_YNA', - parent = stylesheet['Normal'], - fontSize = 12, - leading = 15, - leftIndent = 49, - spaceAfter = 18), +stylesheet.add(ParagraphStyle(name='Ballot_option_YNA', + parent=stylesheet['Normal'], + fontSize=12, + leading=15, + leftIndent=49, + spaceAfter=18), ) -stylesheet.add(ParagraphStyle(name = 'Ballot_option_group_right', - parent = stylesheet['Normal'], - fontSize = 8, - leading = 16, - leftIndent = 49), +stylesheet.add(ParagraphStyle(name='Ballot_option_group_right', + parent=stylesheet['Normal'], + fontSize=8, + leading=16, + leftIndent=49), ) -stylesheet.add(ParagraphStyle(name = 'Badge_title', - parent = stylesheet['Bold'], - fontSize = 16, - leading = 22, - leftIndent = 30), +stylesheet.add(ParagraphStyle(name='Badge_title', + parent=stylesheet['Bold'], + fontSize=16, + leading=22, + leftIndent=30), ) -stylesheet.add(ParagraphStyle(name = 'Badge_subtitle', - parent = stylesheet['Normal'], - fontSize = 12, - leading = 24, - leftIndent = 30), +stylesheet.add(ParagraphStyle(name='Badge_subtitle', + parent=stylesheet['Normal'], + fontSize=12, + leading=24, + leftIndent=30), ) stylesheet.add(ParagraphStyle( - name = 'Badge_italic', - parent = stylesheet['Italic'], - fontSize = 12, - leading = 24, - leftIndent = 30, + name='Badge_italic', + parent=stylesheet['Italic'], + fontSize=12, + leading=24, + leftIndent=30, )) stylesheet.add(ParagraphStyle( - name = 'Badge_qrcode', - fontSize = 12, - leftIndent = 190, + name='Badge_qrcode', + fontSize=12, + leftIndent=190, )) @@ -213,13 +213,13 @@ def firstPage(canvas, doc): canvas.setFillGray(0.4) title_line = u"%s | %s" % (config["event_name"], - config["event_description"]) + config["event_description"]) if len(title_line) > 75: title_line = "%s ..." % title_line[:70] canvas.drawString(2.75 * cm, 28 * cm, title_line) if config["event_date"] and config["event_location"]: canvas.drawString(2.75 * cm, 27.6 * cm, u"%s, %s" - % (config["event_date"], config["event_location"])) + % (config["event_date"], config["event_location"])) # time canvas.setFont('Ubuntu', 7) diff --git a/openslides/utils/person/__init__.py b/openslides/utils/person/__init__.py index 7a69e8f25..430f04c91 100644 --- a/openslides/utils/person/__init__.py +++ b/openslides/utils/person/__init__.py @@ -11,13 +11,17 @@ """ from openslides.utils.person.signals import receive_persons -from openslides.utils.person.api import (generate_person_id, get_person, - Person, Persons) +from openslides.utils.person.api import ( + generate_person_id, get_person, Person, Persons) from openslides.utils.person.forms import PersonFormField, MultiplePersonFormField from openslides.utils.person.models import PersonField, PersonMixin +__all__ = ['receive_persons', 'generate_person_id', 'get_person', 'Person', + 'Persons', 'PersonFormField', 'MultiplePersonFormField', + 'PersonField', 'PersonMixin', 'EmptyPerson'] -class EmptyPerson(PersonMixin): + +class EmptyPerson(PersonMixin, Person): @property def person_id(self): return 'empty' diff --git a/openslides/utils/person/forms.py b/openslides/utils/person/forms.py index cee080bce..6cb19f3ab 100644 --- a/openslides/utils/person/forms.py +++ b/openslides/utils/person/forms.py @@ -61,8 +61,8 @@ class MultiplePersonFormField(PersonFormField): widget = forms.widgets.SelectMultiple def __init__(self, *args, **kwargs): - super(MultiplePersonFormField, self).__init__(empty_label=None, - *args, **kwargs) + super(MultiplePersonFormField, self).__init__( + empty_label=None, *args, **kwargs) def to_python(self, value): if hasattr(value, '__iter__'): diff --git a/openslides/utils/template.py b/openslides/utils/template.py index c3b16b630..5ed5eb778 100644 --- a/openslides/utils/template.py +++ b/openslides/utils/template.py @@ -10,8 +10,7 @@ :license: GNU GPL, see LICENSE for more details. """ -from django.http import HttpResponse -from django.template import loader, Context, RequestContext, TextNode +from django.template import loader, Context from django.template.loader_tags import BlockNode, ExtendsNode @@ -24,6 +23,10 @@ class Tab(object): 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): if isinstance(template, (tuple, list)): 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'): if hasattr(node, key): try: - return render_template_block_nodelist(getattr(node, key), - block, context) + return render_template_block_nodelist( + getattr(node, key), block, context) except: pass for node in nodelist: if isinstance(node, ExtendsNode): try: - return render_template_block(node.get_parent(context), block, - context) + return render_template_block( + node.get_parent(context), block, context) except BlockNotFound: pass raise BlockNotFound 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 dictionary as context. Returns a string. @@ -77,23 +80,3 @@ def render_block_to_string(template_name, block, dictionary=None, context_instance = Context(dictionary) t.render(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) diff --git a/openslides/utils/utils.py b/openslides/utils/utils.py index d2ed32e5f..fa5f96d5e 100644 --- a/openslides/utils/utils.py +++ b/openslides/utils/utils.py @@ -10,12 +10,13 @@ :license: GNU GPL, see LICENSE for more details. """ +import sys + try: import json -except ImportError: # For python 2.5 support +except ImportError: # For python 2.5 support import simplejson as json -from django.conf import settings from django.contrib import messages from django.contrib.auth.models import Permission from django.core.context_processors import csrf @@ -35,16 +36,17 @@ def gen_confirm_form(request, message, url): Deprecated. Use Class base Views instead. """ - messages.warning(request, - """ - %s -
    - - - -
    - """ - % (message, url, csrf(request)['csrf_token'], _("Yes"), _("No"))) + messages.warning( + request, + """ + %s +
    + + + +
    + """ + % (message, url, csrf(request)['csrf_token'], _("Yes"), _("No"))) def del_confirm_form(request, object, name=None, delete_link=None): @@ -57,27 +59,28 @@ def del_confirm_form(request, object, name=None, delete_link=None): name = object if delete_link is None: delete_link = object.get_absolute_url('delete') - gen_confirm_form(request, _('Do you really want to delete %s?') + gen_confirm_form( + request, _('Do you really want to delete %s?') % html_strong(name), delete_link) -def render_response(req, *args, **kwargs): - kwargs['context_instance'] = RequestContext(req) - return render_to_response(*args, **kwargs) - - def template(template_name): + """ + Decorator to set a template for a view. + + Deprecated. Use class based views instead. + """ def renderer(func): def wrapper(request, *args, **kwargs): output = func(request, *args, **kwargs) if not isinstance(output, dict): return output context = {} - template_manipulation.send(sender='utils_template', request=request, - context=context) + template_manipulation.send( + sender='utils_template', request=request, context=context) output.update(context) - response = render_to_response(template_name, output, - context_instance=RequestContext(request)) + response = render_to_response( + template_name, output, context_instance=RequestContext(request)) if 'cookie' in output: response.set_cookie(output['cookie'][0], output['cookie'][1]) return response @@ -89,6 +92,8 @@ def permission_required(perm, login_url=None): """ Decorator for views that checks whether a user has a particular permission enabled, redirecting to the log-in page if necessary. + + Deprecated. """ def renderer(func): def wrapper(request, *args, **kw): @@ -101,10 +106,12 @@ def permission_required(perm, login_url=None): return renderer -def render_to_forbidden(request, error= - ugettext_lazy("Sorry, you have no rights to see this page.")): - return HttpResponseForbidden(render_to_string('403.html', - {'error': error}, context_instance=RequestContext(request))) +def render_to_forbidden(request, + error=ugettext_lazy("Sorry, you have no rights to see this page.")): + # TODO: Integrate this function into the PermissionMixin once the + # above function is deleted. + return HttpResponseForbidden(render_to_string( + '403.html', {'error': error}, context_instance=RequestContext(request))) def delete_default_permissions(**kwargs): @@ -112,27 +119,27 @@ def delete_default_permissions(**kwargs): Deletes the permissions, django creates by default for the admin. """ for p in Permission.objects.all(): - if p.codename.startswith('add') \ - or p.codename.startswith('delete') \ - or p.codename.startswith('change'): + if (p.codename.startswith('add') or + p.codename.startswith('delete') or + p.codename.startswith('change')): p.delete() def ajax_request(data): """ generates a HTTPResponse-Object with json-Data for a - ajax response + ajax response. + + Deprecated. """ return HttpResponse(json.dumps(data)) def _propper_unicode(text): - res = '' if not isinstance(text, unicode): - res = u"%s" % text.decode('UTF-8') + return u"%s" % text.decode('UTF-8') else: - res = text - return res + return text def decodedict(dict): diff --git a/openslides/utils/views.py b/openslides/utils/views.py index 5e814bf9d..b9d1fc4b9 100644 --- a/openslides/utils/views.py +++ b/openslides/utils/views.py @@ -22,8 +22,7 @@ except ImportError: # Is this exception realy necessary? from StringIO import StringIO -from reportlab.platypus import (SimpleDocTemplate, Paragraph, Frame, PageBreak, - Spacer, Table, LongTable, TableStyle, Image) +from reportlab.platypus import SimpleDocTemplate, Spacer from reportlab.lib.units import cm from django.contrib import messages @@ -34,9 +33,9 @@ from django.conf import settings from django.dispatch import receiver from django.http import HttpResponseServerError, HttpResponse, HttpResponseRedirect from django.utils.decorators import method_decorator -from django.utils.translation import ugettext as _, ugettext_noop, ugettext_lazy +from django.utils.translation import ugettext as _, ugettext_lazy from django.utils.importlib import import_module -from django.template import loader, RequestContext +from django.template import RequestContext from django.template.loader import render_to_string from django.views.generic import ( TemplateView as _TemplateView, @@ -50,8 +49,6 @@ from django.views.generic import ( from django.views.generic.detail import SingleObjectMixin from django.views.generic.list import TemplateResponseMixin -from openslides.config.models import config - from openslides.utils.utils import render_to_forbidden, html_strong from openslides.utils.signals import template_manipulation from openslides.utils.pdf import firstPage, laterPages @@ -64,8 +61,8 @@ View = _View class SetCookieMixin(object): def render_to_response(self, context, **response_kwargs): - response = TemplateResponseMixin.render_to_response(self, context, - **response_kwargs) + response = TemplateResponseMixin.render_to_response( + self, context, **response_kwargs) if 'cookie' in context: response.set_cookie(context['cookie'][0], context['cookie'][1]) return response @@ -90,8 +87,8 @@ class PermissionMixin(object): if not self.has_permission(request, *args, **kwargs): if not request.user.is_authenticated(): path = request.get_full_path() - return HttpResponseRedirect("%s?next=%s" % (settings.LOGIN_URL, - path)) + return HttpResponseRedirect( + "%s?next=%s" % (settings.LOGIN_URL, path)) else: return render_to_forbidden(request) return _View.dispatch(self, request, *args, **kwargs) @@ -130,18 +127,18 @@ class QuestionMixin(object): option_fields = "\n".join([ '' % (option[0], unicode(option[1])) for option in self.get_answer_options()]) - messages.warning(self.request, + messages.warning( + self.request, """ %(message)s
    %(option_fields)s
    - """ % { - 'message': self.get_question(), - 'url': self.get_answer_url(), - 'csrf': csrf(self.request)['csrf_token'], - 'option_fields': option_fields}) + """ % {'message': self.get_question(), + 'url': self.get_answer_url(), + 'csrf': csrf(self.request)['csrf_token'], + 'option_fields': option_fields}) def pre_post_redirect(self, request, *args, **kwargs): # Reacts on the response of the user in a POST-request. @@ -167,16 +164,16 @@ class QuestionMixin(object): class TemplateView(PermissionMixin, _TemplateView): def get_context_data(self, **kwargs): context = super(TemplateView, self).get_context_data(**kwargs) - template_manipulation.send(sender=self.__class__, request=self.request, - context=context) + template_manipulation.send( + sender=self.__class__, request=self.request, context=context) return context class ListView(PermissionMixin, SetCookieMixin, _ListView): def get_context_data(self, **kwargs): context = super(ListView, self).get_context_data(**kwargs) - template_manipulation.send(sender=self.__class__, request=self.request, - context=context) + template_manipulation.send( + sender=self.__class__, request=self.request, context=context) return context @@ -217,8 +214,8 @@ class FormView(PermissionMixin, _FormView): def get_context_data(self, **kwargs): context = super(FormView, self).get_context_data(**kwargs) - template_manipulation.send(sender=self.__class__, request=self.request, - context=context) + template_manipulation.send( + sender=self.__class__, request=self.request, context=context) return context def form_invalid(self, form): @@ -235,8 +232,8 @@ class UpdateView(PermissionMixin, _UpdateView): def get_context_data(self, **kwargs): context = super(UpdateView, self).get_context_data(**kwargs) - template_manipulation.send(sender=self.__class__, request=self.request, - context=context) + template_manipulation.send( + sender=self.__class__, request=self.request, context=context) return context def form_invalid(self, form): @@ -256,8 +253,8 @@ class CreateView(PermissionMixin, _CreateView): def get_context_data(self, **kwargs): context = super(CreateView, self).get_context_data(**kwargs) - template_manipulation.send(sender=self.__class__, request=self.request, - context=context) + template_manipulation.send( + sender=self.__class__, request=self.request, context=context) return context def get_apply_url(self): @@ -299,7 +296,6 @@ class DeleteView(SingleObjectMixin, QuestionMixin, RedirectView): class DetailView(TemplateView, SingleObjectMixin): def get(self, request, *args, **kwargs): self.object = self.get_object() - context = self.get_context_data(object=self.object) return super(DetailView, self).get(request, *args, **kwargs) def get_context_data(self, **kwargs): @@ -329,8 +325,8 @@ class PDFView(PermissionMixin, View): return SimpleDocTemplate(buffer) def build_document(self, pdf_document, story): - pdf_document.build(story, onFirstPage=firstPage, - onLaterPages=laterPages) + pdf_document.build( + story, onFirstPage=firstPage, onLaterPages=laterPages) def render_to_response(self, filename): response = HttpResponse(mimetype='application/pdf') @@ -340,7 +336,7 @@ class PDFView(PermissionMixin, View): buffer = StringIO() pdf_document = self.get_template(buffer) pdf_document.title = self.get_document_title() - story = [Spacer(1, self.get_top_space()*cm)] + story = [Spacer(1, self.get_top_space() * cm)] self.append_to_pdf(story) @@ -351,9 +347,6 @@ class PDFView(PermissionMixin, View): response.write(pdf) return response - def get_filename(self): - return self.filename - def get(self, request, *args, **kwargs): return self.render_to_response(self.get_filename()) @@ -364,9 +357,8 @@ def server_error(request, template_name='500.html'): Templates: `500.html` """ - t = loader.get_template("500.html") - return HttpResponseServerError(render_to_string('500.html', - context_instance=RequestContext(request))) + return HttpResponseServerError(render_to_string( + template_name, context_instance=RequestContext(request))) @receiver(template_manipulation, dispatch_uid="send_register_tab") diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..15fe79d8d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +Django==1.4.2 +django-mptt +reportlab +PIL +simplejson diff --git a/setup.py b/setup.py index 6b2911a46..ed1c3a018 100644 --- a/setup.py +++ b/setup.py @@ -12,15 +12,19 @@ from setuptools import find_packages from openslides import get_version +with open('README.txt') as file: + long_description = file.read() + setup( name='openslides', description='Presentation-System', + long_description=long_description, version=get_version(), url='http://openslides.org', author='OpenSlides-Team', author_email='support@openslides.org', license='GPL2+', - packages=find_packages(), + packages=find_packages(exclude=['tests']), include_package_data = True, classifiers = [ # http://pypi.python.org/pypi?%3Aaction=list_classifiers diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/agenda/__init__.py b/tests/agenda/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/agenda/models.py b/tests/agenda/models.py new file mode 100644 index 000000000..f61694356 --- /dev/null +++ b/tests/agenda/models.py @@ -0,0 +1,18 @@ +from django.db import models + +from openslides.projector.projector import SlideMixin +from openslides.projector.api import register_slidemodel + + +class ReleatedItem(SlideMixin, models.Model): + prefix = 'releateditem' + + name = models.CharField(max_length='255') + + def get_agenda_title(self): + return self.name + + def get_agenda_title_supplement(self): + return 'test item' + +register_slidemodel(ReleatedItem) diff --git a/openslides/agenda/tests.py b/tests/agenda/tests.py similarity index 65% rename from openslides/agenda/tests.py rename to tests/agenda/tests.py index ca56e009d..05c582f66 100644 --- a/openslides/agenda/tests.py +++ b/tests/agenda/tests.py @@ -12,12 +12,15 @@ from django.test import TestCase from django.test.client import Client -from django.contrib.auth.models import User from django.db.models.query import EmptyQuerySet from openslides.projector.api import get_active_slide - +from openslides.participant.models import User from openslides.agenda.models import Item +from openslides.agenda.slides import agenda_show + +from .models import ReleatedItem + class ItemTest(TestCase): def setUp(self): @@ -25,6 +28,8 @@ class ItemTest(TestCase): self.item2 = Item.objects.create(title='item2') self.item3 = Item.objects.create(title='item1A', parent=self.item1) self.item4 = Item.objects.create(title='item1Aa', parent=self.item3) + self.releated = ReleatedItem.objects.create(name='foo') + self.item5 = Item.objects.create(title='item5', related_sid=self.releated.sid) def testClosed(self): self.assertFalse(self.item1.closed) @@ -46,10 +51,6 @@ class ItemTest(TestCase): self.assertTrue(self.item3 in self.item1.get_children()) self.assertFalse(self.item4 in self.item1.get_children()) - l = Item.objects.all() - self.assertEqual(str(l), - "[, , , ]") - def testForms(self): for item in Item.objects.all(): initial = item.weight_form.initial @@ -64,6 +65,36 @@ class ItemTest(TestCase): self.item1.related_sid = 'foobar' self.assertFalse(self.item1.get_related_slide() is None) + def test_title_supplement(self): + self.assertEqual(self.item1.get_title_supplement(), '') + + def test_delete_item(self): + new_item1 = Item.objects.create() + new_item2 = Item.objects.create(parent=new_item1) + new_item3 = Item.objects.create(parent=new_item2) + new_item1.delete() + self.assertTrue(new_item3 in Item.objects.all()) + new_item2.delete(with_children=True) + self.assertFalse(new_item3 in Item.objects.all()) + + def test_absolute_url(self): + self.assertEqual(self.item1.get_absolute_url(), '/agenda/1/') + self.assertEqual(self.item1.get_absolute_url('edit'), '/agenda/1/edit/') + self.assertEqual(self.item1.get_absolute_url('delete'), '/agenda/1/del/') + + def test_agenda_slide(self): + data = agenda_show() + self.assertEqual(list(data['items']), list(Item.objects.all().filter(parent=None))) + self.assertEqual(data['template'], 'projector/AgendaSummary.html') + self.assertEqual(data['title'], 'Agenda') + + def test_releated_item(self): + self.assertEqual(self.item5.get_title(), self.releated.name) + self.assertEqual(self.item5.get_title_supplement(), 'test item') + self.assertEqual(self.item5.get_related_type(), 'releateditem') + self.assertEqual(self.item5.print_related_type(), 'Releateditem') + + class ViewTest(TestCase): def setUp(self): @@ -71,8 +102,10 @@ class ViewTest(TestCase): self.item2 = Item.objects.create(title='item2') self.refreshItems() - self.admin = User.objects.create_user('testadmin', '', 'default') - self.anonym = User.objects.create_user('testanoym', '', 'default') + self.admin, created = User.objects.get_or_create(username='testadmin') + self.anonym, created = User.objects.get_or_create(username='testanonym') + self.admin.reset_password('default') + self.anonym.reset_password('default') self.admin.is_superuser = True self.admin.save() @@ -91,6 +124,13 @@ class ViewTest(TestCase): def anonymClient(self): return Client() + def testOverview(self): + c = self.adminClient + + response = c.get('/agenda/') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context['items']), len(Item.objects.all())) + def testActivate(self): c = self.adminClient @@ -122,6 +162,14 @@ class ViewTest(TestCase): self.refreshItems() self.assertEqual(response.status_code, 404) + # Test ajax + response = c.get('/agenda/%d/close/' % self.item1.id, + HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response.status_code, 200) + response = c.get('/agenda/%d/open/' % self.item1.id, + HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response.status_code, 200) + def testEdit(self): c = self.adminClient @@ -131,7 +179,7 @@ class ViewTest(TestCase): response = c.get('/agenda/%d/edit/' % 1000) self.assertEqual(response.status_code, 404) - data = {'title': 'newitem1', 'text': 'item1-text', 'weight':'0'} + data = {'title': 'newitem1', 'text': 'item1-text', 'weight': '0'} response = c.post('/agenda/%d/edit/' % self.item1.id, data) self.assertEqual(response.status_code, 302) self.refreshItems() @@ -143,4 +191,3 @@ class ViewTest(TestCase): self.assertEqual(response.status_code, 200) self.refreshItems() self.assertEqual(self.item1.title, 'newitem1') - diff --git a/tests/test_init.py b/tests/test_init.py new file mode 100644 index 000000000..6155716f1 --- /dev/null +++ b/tests/test_init.py @@ -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) diff --git a/openslides/motion/tests.py b/tests/test_motion.py similarity index 92% rename from openslides/motion/tests.py rename to tests/test_motion.py index 1fb387951..26c506c37 100644 --- a/openslides/motion/tests.py +++ b/tests/test_motion.py @@ -11,10 +11,10 @@ """ from django.test import TestCase -from django.test.client import Client from openslides.participant.models import User -from openslides.motion.models import Motion, AVersion +from openslides.motion.models import Motion + class MotionTest(TestCase): def setUp(self): @@ -39,4 +39,3 @@ class MotionTest(TestCase): self.assertEqual(self.app1.versions.count(), 2) self.assertEqual(self.app1.last_version, self.app1.versions[1]) - diff --git a/openslides/participant/tests.py b/tests/test_participant.py similarity index 92% rename from openslides/participant/tests.py rename to tests/test_participant.py index 10013b877..4227dc66b 100644 --- a/openslides/participant/tests.py +++ b/tests/test_participant.py @@ -11,8 +11,6 @@ """ from django.test import TestCase -from django.test.client import Client -from django.contrib.auth.hashers import check_password from openslides.utils.person import get_person, Persons from openslides.participant.api import gen_username, gen_password @@ -38,9 +36,9 @@ class UserTest(TestCase): self.assertEqual(unicode(self.user1), 'Max Mustermann') def test_name_suffix(self): - self.user1.structure_level = 'München' + self.user1.structure_level = u'München' 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): self.assertIsInstance(self.user1.default_password, basestring)