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/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..e6449d35b 100644 --- a/README.txt +++ b/README.txt @@ -1,6 +1,6 @@ - ================================== - English README file for OpenSlides - ================================== +================================== +English README file for OpenSlides +================================== This is OpenSlides, version 1.3-beta2 (2012-11-09). @@ -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..6554a4939 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" diff --git a/openslides/__init__.py b/openslides/__init__.py index a9fc81a12..93ec6dc61 100644 --- a/openslides/__init__.py +++ b/openslides/__init__.py @@ -29,12 +29,11 @@ def get_version(version=None): if version[3] != 'final': if version[3] == 'dev': try: + import os git_head_path = '.git/' + open('.git/HEAD', 'r').read()[5:].rstrip() + git_commit_id = open(os.path.abspath(git_head_path), 'r').read().rstrip() except IOError: git_commit_id = 'unknown' - else: - import os - git_commit_id = open(os.path.abspath(git_head_path), 'r').read().rstrip() sub = '-%s%s' % (version[3], git_commit_id) else: sub = '-' + version[3] + str(version[4]) 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 cdb9832bf..bd5d64622 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() @@ -116,14 +111,12 @@ class Assignment(models.Model, SlideMixin): else: candidature.delete() - def is_candidate(self, person): """ return True, if person is a candidate. """ try: - return self.assignment_candidates.filter(person=person) \ - .exclude(blocked=True).exists() + return self.assignment_candidates.filter(person=person).exclude(blocked=True).exists() except AttributeError: return False @@ -131,8 +124,7 @@ class Assignment(models.Model, SlideMixin): """ 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): @@ -167,7 +159,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 @@ -215,7 +206,6 @@ class Assignment(models.Model, SlideMixin): vote_results_dict[candidate].append(votes) return vote_results_dict - def get_agenda_title(self): return self.name @@ -301,8 +291,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 e93e08f18..c70fa781b 100644 --- a/openslides/config/views.py +++ b/openslides/config/views.py @@ -12,18 +12,18 @@ from django.conf import settings from django.contrib import messages -from django.contrib.auth.models import Group, Permission from django.core.urlresolvers import reverse from django.utils.importlib import import_module from django.utils.translation import ugettext as _ from openslides import get_version - from openslides.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 0186a3219..174d57d09 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 = ( @@ -142,3 +139,6 @@ CACHES = { 'LOCATION': 'openslidecache' } } + +TEST_RUNNER = 'discover_runner.DiscoverRunner' +TEST_DISCOVER_TOP_LEVEL = os.path.dirname(os.path.dirname(__file__)) 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/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 3868849be..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,12 +132,15 @@ 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 @@ -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/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/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..c0fa8d6c5 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. @@ -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..e262022b4 100644 --- a/openslides/utils/person/__init__.py +++ b/openslides/utils/person/__init__.py @@ -11,11 +11,15 @@ """ 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): @property 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 41aaf7544..d8f054eb0 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 @@ -41,16 +42,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): @@ -63,27 +65,28 @@ def del_confirm_form(request, object, name=None, delete_link=None): name = object if delete_link is None: delete_link = object.get_absolute_url('delete') - gen_confirm_form(request, _('Do you really want to delete %s?') + gen_confirm_form( + request, _('Do you really want to delete %s?') % html_strong(name), delete_link) -def render_response(req, *args, **kwargs): - kwargs['context_instance'] = RequestContext(req) - return render_to_response(*args, **kwargs) - - def template(template_name): + """ + Decorator to set a template for a view. + + Deprecated. Use class based views instead. + """ def renderer(func): def wrapper(request, *args, **kwargs): output = func(request, *args, **kwargs) if not isinstance(output, dict): return output context = {} - template_manipulation.send(sender='utils_template', request=request, - context=context) + template_manipulation.send( + sender='utils_template', request=request, context=context) output.update(context) - response = render_to_response(template_name, output, - context_instance=RequestContext(request)) + response = render_to_response( + template_name, output, context_instance=RequestContext(request)) if 'cookie' in output: response.set_cookie(output['cookie'][0], output['cookie'][1]) return response @@ -95,6 +98,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): @@ -107,10 +112,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): @@ -118,16 +125,18 @@ 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)) 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)