From 10491414836456970dc736f7a88d17d5ecdabe95 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Thu, 22 Nov 2012 18:39:54 +0100 Subject: [PATCH 01/25] Created a default group for registered users --- openslides/participant/forms.py | 16 +++++++++++++--- openslides/participant/models.py | 16 ++++++++++++++-- .../templates/participant/group_overview.html | 2 +- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/openslides/participant/forms.py b/openslides/participant/forms.py index da395680e..721cf2739 100644 --- a/openslides/participant/forms.py +++ b/openslides/participant/forms.py @@ -25,6 +25,16 @@ 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, created = Group.objects.get_or_create(name__iexact='Registered') + if created: + registered.name = 'Registered' + registered.save() + initial['groups'] = [registered.pk] + super(UserCreateForm, self).__init__(*args, **kwargs) + class Meta: model = User fields = ('first_name', 'last_name', 'is_active', 'groups', 'structure_level', @@ -76,13 +86,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 diff --git a/openslides/participant/models.py b/openslides/participant/models.py index 14758a16d..94db74084 100644 --- a/openslides/participant/models.py +++ b/openslides/participant/models.py @@ -234,7 +234,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 +242,20 @@ 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): + if not kwargs['created']: + return + registered, created = Group.objects.get_or_create(name__iexact='Registered') + if created: + registered.name = 'Registered' + registered.save() + 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 %} From fa1c384d28f97b1c9d469b7ecb08ed5daa3f5477 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Thu, 22 Nov 2012 18:47:50 +0100 Subject: [PATCH 02/25] made use of defaults in get_or_create --- openslides/participant/forms.py | 6 ++---- openslides/participant/models.py | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/openslides/participant/forms.py b/openslides/participant/forms.py index 721cf2739..a630dae26 100644 --- a/openslides/participant/forms.py +++ b/openslides/participant/forms.py @@ -28,10 +28,8 @@ class UserCreateForm(forms.ModelForm, CssClassMixin): def __init__(self, *args, **kwargs): if kwargs.get('instance', None) is None: initial = kwargs.setdefault('initial', {}) - registered, created = Group.objects.get_or_create(name__iexact='Registered') - if created: - registered.name = 'Registered' - registered.save() + registered, created = Group.objects.get_or_create( + name__iexact='Registered', defaults={'name': 'Registered'}) initial['groups'] = [registered.pk] super(UserCreateForm, self).__init__(*args, **kwargs) diff --git a/openslides/participant/models.py b/openslides/participant/models.py index 94db74084..7f2270f0f 100644 --- a/openslides/participant/models.py +++ b/openslides/participant/models.py @@ -253,9 +253,7 @@ def djangogroup_post_save(sender, instance, signal, *args, **kwargs): def user_post_save(sender, instance, *args, **kwargs): if not kwargs['created']: return - registered, created = Group.objects.get_or_create(name__iexact='Registered') - if created: - registered.name = 'Registered' - registered.save() + registered, created = Group.objects.get_or_create( + name__iexact='Registered', defaults={'name': 'Registered'}) instance.groups.add(registered) instance.save() From 9943e84a965033800e696063bb18d2bdbf7612d8 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Fri, 23 Nov 2012 09:35:41 +0100 Subject: [PATCH 03/25] default permissions for the registered group plus some cleanup for the registered and the anonymous group --- openslides/config/views.py | 19 ++----------------- openslides/participant/api.py | 29 +++++++++++++++++++++++++++-- openslides/participant/forms.py | 4 ++-- openslides/participant/models.py | 4 ++-- 4 files changed, 33 insertions(+), 23 deletions(-) diff --git a/openslides/config/views.py b/openslides/config/views.py index e93e08f18..dfeb6182e 100644 --- a/openslides/config/views.py +++ b/openslides/config/views.py @@ -12,7 +12,6 @@ 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 _ @@ -24,6 +23,7 @@ from openslides.utils.views import FormView, TemplateView from openslides.config.forms import GeneralConfigForm from openslides.config.models import config +from openslides.participant.api import get_or_create_anonymous_group class GeneralConfig(FormView): @@ -61,22 +61,7 @@ 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.')) + anonymous = get_or_create_anonymous_group() else: config['system_enable_anonymous'] = False diff --git a/openslides/participant/api.py b/openslides/participant/api.py index 938eacca6..5544af64c 100644 --- a/openslides/participant/api.py +++ b/openslides/participant/api.py @@ -17,12 +17,17 @@ 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 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(): @@ -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/openslides/participant/forms.py b/openslides/participant/forms.py index a630dae26..b1fcea525 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): @@ -28,8 +29,7 @@ class UserCreateForm(forms.ModelForm, CssClassMixin): def __init__(self, *args, **kwargs): if kwargs.get('instance', None) is None: initial = kwargs.setdefault('initial', {}) - registered, created = Group.objects.get_or_create( - name__iexact='Registered', defaults={'name': 'Registered'}) + registered = get_or_create_registered_group() initial['groups'] = [registered.pk] super(UserCreateForm, self).__init__(*args, **kwargs) diff --git a/openslides/participant/models.py b/openslides/participant/models.py index 7f2270f0f..7f047a9c4 100644 --- a/openslides/participant/models.py +++ b/openslides/participant/models.py @@ -251,9 +251,9 @@ def djangogroup_post_save(sender, instance, signal, *args, **kwargs): @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, created = Group.objects.get_or_create( - name__iexact='Registered', defaults={'name': 'Registered'}) + registered = get_or_create_registered_group() instance.groups.add(registered) instance.save() From f5a438cf9faec9a7506ed77b5bbff14eebbe53e8 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Sat, 24 Nov 2012 01:42:10 +0100 Subject: [PATCH 04/25] cleanup in utils --- openslides/utils/template.py | 24 ++++-------------------- openslides/utils/utils.py | 20 +++++++++++++------- 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/openslides/utils/template.py b/openslides/utils/template.py index c3b16b630..f87de2e09 100644 --- a/openslides/utils/template.py +++ b/openslides/utils/template.py @@ -24,6 +24,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) @@ -77,23 +81,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..8f7ff1674 100644 --- a/openslides/utils/utils.py +++ b/openslides/utils/utils.py @@ -67,12 +67,12 @@ def del_confirm_form(request, object, name=None, delete_link=None): % 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) @@ -95,6 +95,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): @@ -108,7 +110,9 @@ def permission_required(perm, login_url=None): def render_to_forbidden(request, error= - ugettext_lazy("Sorry, you have no rights to see this page.")): + 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))) @@ -127,7 +131,9 @@ def delete_default_permissions(**kwargs): def ajax_request(data): """ generates a HTTPResponse-Object with json-Data for a - ajax response + ajax response. + + Deprecated. """ return HttpResponse(json.dumps(data)) From bd32994296c62409125f2f855c56e515c4319745 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Sat, 24 Nov 2012 14:01:21 +0100 Subject: [PATCH 05/25] cleanup Removed unused imports and fixed pep8 errors --- openslides/agenda/forms.py | 4 +- openslides/agenda/models.py | 15 +- openslides/agenda/tests.py | 18 ++- openslides/agenda/views.py | 47 +++--- openslides/assignment/forms.py | 31 ++-- openslides/assignment/models.py | 33 ++-- openslides/assignment/views.py | 214 +++++++++++++------------ openslides/config/forms.py | 2 - openslides/config/models.py | 15 +- openslides/config/views.py | 12 +- openslides/global_settings.py | 21 ++- openslides/main.py | 48 +++--- openslides/motion/forms.py | 22 +-- openslides/motion/models.py | 98 ++++++----- openslides/motion/tests.py | 5 +- openslides/motion/views.py | 44 ++--- openslides/participant/api.py | 8 +- openslides/participant/forms.py | 13 +- openslides/participant/models.py | 12 +- openslides/participant/tests.py | 6 +- openslides/participant/urls.py | 1 - openslides/participant/views.py | 49 +++--- openslides/poll/forms.py | 1 - openslides/poll/models.py | 9 +- openslides/poll/views.py | 4 +- openslides/projector/api.py | 37 ++--- openslides/projector/forms.py | 1 - openslides/projector/models.py | 6 +- openslides/projector/projector.py | 7 +- openslides/projector/views.py | 56 +++---- openslides/urls.py | 4 +- openslides/utils/auth/AnonymousAuth.py | 16 +- openslides/utils/csv_ext.py | 3 +- openslides/utils/jsonfield/__init__.py | 4 +- openslides/utils/jsonfield/fields.py | 2 + openslides/utils/modelfields.py | 3 +- openslides/utils/pdf.py | 36 ++--- openslides/utils/person/__init__.py | 8 +- openslides/utils/person/forms.py | 4 +- openslides/utils/template.py | 13 +- openslides/utils/utils.py | 51 +++--- openslides/utils/views.py | 64 ++++---- 42 files changed, 486 insertions(+), 561 deletions(-) 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/tests.py b/openslides/agenda/tests.py index ca56e009d..0e270b36d 100644 --- a/openslides/agenda/tests.py +++ b/openslides/agenda/tests.py @@ -12,12 +12,12 @@ 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 .models import Item -from openslides.agenda.models import Item class ItemTest(TestCase): def setUp(self): @@ -47,8 +47,9 @@ class ItemTest(TestCase): self.assertFalse(self.item4 in self.item1.get_children()) l = Item.objects.all() - self.assertEqual(str(l), - "[, , , ]") + self.assertEqual( + str(l), + "[, , , ]") def testForms(self): for item in Item.objects.all(): @@ -71,8 +72,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() @@ -131,7 +134,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 +146,3 @@ class ViewTest(TestCase): self.assertEqual(response.status_code, 200) self.refreshItems() self.assertEqual(self.item1.title, 'newitem1') - diff --git a/openslides/agenda/views.py b/openslides/agenda/views.py index 863bd9da4..2c684bc95 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 .agenda.forms import ItemOrderForm, ItemForm class Overview(TemplateView): @@ -53,7 +49,8 @@ class Overview(TemplateView): def post(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) if not request.user.has_perm('agenda.can_manage_agenda'): - messages.error(request, + messages.error( + request, _('You are not authorized to manage the agenda.')) return self.render_to_response(context) transaction.commit() @@ -69,8 +66,8 @@ class Overview(TemplateView): Model.save(item) else: transaction.rollback() - messages.error(request, - _('Errors when reordering of the agenda')) + messages.error( + request, _('Errors when reordering of the agenda')) return self.render_to_response(context) Item.objects.rebuild() # TODO: assure, that it is a valid tree @@ -130,8 +127,8 @@ class ItemUpdate(UpdateView): apply_url = 'item_edit' def get_success_url(self): - messages.success(self.request, - _("Item %s was successfully modified.") \ + messages.success( + self.request, _("Item %s was successfully modified.") % html_strong(self.request.POST['title'])) if 'apply' in self.request.POST: return '' @@ -151,8 +148,8 @@ class ItemCreate(CreateView): apply_url = 'item_edit' def get_success_url(self): - messages.success(self.request, - _("Item %s was successfully created.") \ + messages.success( + self.request, _("Item %s was successfully created.") % html_strong(self.request.POST['title'])) if 'apply' in self.request.POST: return reverse(self.get_apply_url(), args=[self.object.id]) @@ -176,13 +173,13 @@ class ItemDelete(DeleteView): def pre_post_redirect(self, request, *args, **kwargs): if self.get_answer() == 'all': self.object.delete(with_children=True) - messages.success(request, - _("Item %s and his children were successfully deleted.") + messages.success( + request, _("Item %s and his children were successfully deleted.") % html_strong(self.object)) elif self.get_answer() == 'yes': self.object.delete(with_children=False) - messages.success(request, - _("Item %s was successfully deleted.") + messages.success( + request, _("Item %s was successfully deleted.") % html_strong(self.object)) @@ -199,7 +196,8 @@ class AgendaPDF(PDFView): ancestors = item.get_ancestors() if ancestors: space = " " * 6 * ancestors.count() - story.append(Paragraph("%s%s" % (space, item.get_title()), + story.append(Paragraph( + "%s%s" % (space, item.get_title()), stylesheet['Subitem'])) else: story.append(Paragraph(item.get_title(), stylesheet['Item'])) @@ -213,10 +211,9 @@ def register_tab(request): return Tab( title=_('Agenda'), url=reverse('item_overview'), - permission=request.user.has_perm('agenda.can_see_agenda') - or request.user.has_perm('agenda.can_manage_agenda'), - selected=selected, - ) + permission=(request.user.has_perm('agenda.can_see_agenda') or + request.user.has_perm('agenda.can_manage_agenda')), + selected=selected) def get_widgets(request): diff --git a/openslides/assignment/forms.py b/openslides/assignment/forms.py index cf4dfcba2..9adf16dbd 100644 --- a/openslides/assignment/forms.py +++ b/openslides/assignment/forms.py @@ -11,7 +11,7 @@ """ from django import forms -from django.utils.translation import ugettext_lazy as _, ugettext_noop +from django.utils.translation import ugettext_lazy as _ from openslides.utils.forms import CssClassMixin from openslides.utils.person import PersonFormField @@ -20,8 +20,8 @@ from openslides.assignment.models import Assignment class AssignmentForm(forms.ModelForm, CssClassMixin): - posts = forms.IntegerField(min_value=1, initial=1, - label=_("Number of available posts")) + posts = forms.IntegerField( + min_value=1, initial=1, label=_("Number of available posts")) class Meta: model = Assignment @@ -39,8 +39,7 @@ class ConfigForm(forms.Form, CssClassMixin): assignment_publish_winner_results_only = forms.BooleanField( required=False, label=_("Only publish voting results for selected winners " - "(Projector view only)") - ) + "(Projector view only)")) assignment_pdf_ballot_papers_selection = forms.ChoiceField( widget=forms.Select(), required=False, @@ -48,31 +47,25 @@ class ConfigForm(forms.Form, CssClassMixin): choices=( ("NUMBER_OF_DELEGATES", _("Number of all delegates")), ("NUMBER_OF_ALL_PARTICIPANTS", _("Number of all participants")), - ("CUSTOM_NUMBER", _("Use the following custom number")) - ) - ) + ("CUSTOM_NUMBER", _("Use the following custom number")))) assignment_pdf_ballot_papers_number = forms.IntegerField( - widget=forms.TextInput(attrs={'class':'small-input'}), + widget=forms.TextInput(attrs={'class': 'small-input'}), required=False, min_value=1, - label=_("Custom number of ballot papers") - ) + label=_("Custom number of ballot papers")) assignment_pdf_title = forms.CharField( widget=forms.TextInput(), required=False, - label=_("Title for PDF document (all elections)") - ) + label=_("Title for PDF document (all elections)")) assignment_pdf_preamble = forms.CharField( widget=forms.Textarea(), required=False, - label=_("Preamble text for PDF document (all elections)") - ) - assignment_poll_vote_values = forms.ChoiceField(widget=forms.Select(), + label=_("Preamble text for PDF document (all elections)")) + assignment_poll_vote_values = forms.ChoiceField( + widget=forms.Select(), required=False, label=_("Election method"), choices=( ("auto", _("Automatic assign of method.")), ("votes", _("Always one option per candidate.")), - ("yesnoabstain", _("Always Yes-No-Abstain per candidate.")), - ) - ) + ("yesnoabstain", _("Always Yes-No-Abstain per candidate.")))) diff --git a/openslides/assignment/models.py b/openslides/assignment/models.py index b70972234..fe0641934 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: candidation.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 candidation. """ - return self.assignment_candidates.filter(person=person) \ - .filter(blocked=True).exists() + return self.assignment_candidates.filter(person=person).filter(blocked=True).exists() @property def assignment_candidates(self): @@ -164,7 +156,6 @@ class Assignment(models.Model, SlideMixin): return participants #return candidates.values_list('person', flat=True) - def set_elected(self, person, value=True): candidate = self.assignment_candidates.get(person=person) candidate.elected = value @@ -212,7 +203,6 @@ class Assignment(models.Model, SlideMixin): vote_results_dict[candidate].append(votes) return vote_results_dict - def get_agenda_title(self): return self.name @@ -298,8 +288,7 @@ class AssignmentPoll(BasePoll, CountInvalid, CountVotesCast, PublishPollMixin): self.yesnoabstain = False self.save() if self.yesnoabstain: - return [ugettext_noop('Yes'), ugettext_noop('No'), - ugettext_noop('Abstain')] + return [ugettext_noop('Yes'), ugettext_noop('No'), ugettext_noop('Abstain')] else: return [ugettext_noop('Votes')] diff --git a/openslides/assignment/views.py b/openslides/assignment/views.py index 5adbf0188..f5e244732 100644 --- a/openslides/assignment/views.py +++ b/openslides/assignment/views.py @@ -13,39 +13,31 @@ import os from reportlab.lib import colors -from reportlab.platypus import (SimpleDocTemplate, PageBreak, Paragraph, - Spacer, Table, TableStyle) +from reportlab.platypus import ( + SimpleDocTemplate, PageBreak, Paragraph, Spacer, Table, TableStyle) from reportlab.lib.units import cm from django.conf import settings from django.core.urlresolvers import reverse from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.contrib.auth.models import User from django.shortcuts import redirect from django.utils.translation import ungettext, ugettext as _ from openslides.utils.pdf import stylesheet from openslides.utils.template import Tab -from openslides.utils.utils import (template, permission_required, - gen_confirm_form, del_confirm_form, ajax_request) +from openslides.utils.utils import ( + template, permission_required, gen_confirm_form, del_confirm_form, ajax_request) from openslides.utils.views import FormView, DeleteView, PDFView, RedirectView from openslides.utils.person import get_person - from openslides.config.models import config - from openslides.participant.models import User - from openslides.projector.projector import Widget - from openslides.poll.views import PollFormView - from openslides.agenda.models import Item - -from openslides.assignment.models import (Assignment, AssignmentPoll, - AssignmentOption) -from openslides.assignment.forms import (AssignmentForm, AssignmentRunForm, - ConfigForm) +from openslides.assignment.models import Assignment, AssignmentPoll +from openslides.assignment.forms import ( + AssignmentForm, AssignmentRunForm, ConfigForm) @permission_required('assignment.can_see_assignment') @@ -56,7 +48,7 @@ def get_overview(request): query = query.filter(status__iexact=request.GET['status']) try: sort = request.GET['sort'] - if sort in ['name','status']: + if sort in ['name', 'status']: query = query.order_by(sort) except KeyError: pass @@ -91,7 +83,6 @@ def view(request, assignment_id=None): if request.user.has_perm('assignment.can_nominate_other'): form = AssignmentRunForm() - polls = assignment.poll_set.all() if not request.user.has_perm('assignment.can_manage_assignment'): polls = assignment.poll_set.filter(published=True) @@ -100,7 +91,8 @@ def view(request, assignment_id=None): polls = assignment.poll_set.all() vote_results = assignment.vote_results(only_published=False) - blocked_candidates = [candidate.person for candidate in \ + blocked_candidates = [ + candidate.person for candidate in assignment.assignment_candidates.filter(blocked=True)] return { 'assignment': assignment, @@ -180,7 +172,7 @@ def run(request, assignment_id): assignment = Assignment.objects.get(pk=assignment_id) try: assignment.run(request.user, request.user) - messages.success(request, _('You have set your candidature successfully.') ) + messages.success(request, _('You have set your candidature successfully.')) except NameError, e: messages.error(request, e) return redirect(reverse('assignment_view', args=[assignment_id])) @@ -195,7 +187,8 @@ def delrun(request, assignment_id): except Exception, e: messages.error(request, e) else: - messages.success(request, + messages.success( + request, _("You have withdrawn your candidature successfully. " "You can not be nominated by other participants anymore.")) else: @@ -240,7 +233,7 @@ def set_active(request, assignment_id): @permission_required('assignment.can_manage_assignment') def gen_poll(request, assignment_id): poll = Assignment.objects.get(pk=assignment_id).gen_poll() - messages.success(request, _("New ballot was successfully created.") ) + messages.success(request, _("New ballot was successfully created.")) return redirect(reverse('assignment_poll_view', args=[poll.id])) @@ -279,9 +272,9 @@ def set_publish_status(request, poll_id): return ajax_request({'published': poll.published}) if poll.published: - messages.success(request, _("Ballot successfully published.") ) + messages.success(request, _("Ballot successfully published.")) else: - messages.success(request, _("Ballot successfully unpublished.") ) + messages.success(request, _("Ballot successfully unpublished.")) return redirect(reverse('assignment_view', args=[poll.assignment.id])) @@ -336,8 +329,9 @@ class AssignmentPDF(PDFView): try: assignment_id = self.kwargs['assignment_id'] assignment = Assignment.objects.get(id=assignment_id) - filename = u'%s-%s' % (_("Assignment"), - assignment.name.replace(' ','_')) + filename = u'%s-%s' % ( + _("Assignment"), + assignment.name.replace(' ', '_')) except: filename = _("Elections") return filename @@ -347,23 +341,24 @@ class AssignmentPDF(PDFView): assignment_id = self.kwargs['assignment_id'] except KeyError: assignment_id = None - if assignment_id is None: #print all assignments + if assignment_id is None: # print all assignments title = config["assignment_pdf_title"] story.append(Paragraph(title, stylesheet['Heading1'])) preamble = config["assignment_pdf_preamble"] if preamble: - story.append(Paragraph("%s" % preamble.replace('\r\n', '
'), + story.append(Paragraph( + "%s" % preamble.replace('\r\n', '
'), stylesheet['Paragraph'])) story.append(Spacer(0, 0.75 * cm)) assignments = Assignment.objects.all() - if not assignments: # No assignments existing - story.append(Paragraph(_("No assignments available."), - stylesheet['Heading3'])) - else: # Print all assignments + if not assignments: # No assignments existing + story.append(Paragraph( + _("No assignments available."), stylesheet['Heading3'])) + else: # Print all assignments # List of assignments for assignment in assignments: - story.append(Paragraph(assignment.name, - stylesheet['Heading3'])) + story.append(Paragraph( + assignment.name, stylesheet['Heading3'])) # Assignment details (each assignment on single page) for assignment in assignments: story.append(PageBreak()) @@ -376,28 +371,33 @@ class AssignmentPDF(PDFView): def get_assignment(self, assignment, story): # title - story.append(Paragraph(_("Election: %s") % assignment.name, - stylesheet['Heading1'])) + story.append(Paragraph( + _("Election: %s") % assignment.name, stylesheet['Heading1'])) story.append(Spacer(0, 0.5 * cm)) # posts cell1a = [] - cell1a.append(Paragraph("%s:" % + cell1a.append(Paragraph( + "%s:" % _("Number of available posts"), stylesheet['Bold'])) cell1b = [] cell1b.append(Paragraph(str(assignment.posts), stylesheet['Paragraph'])) # candidates cell2a = [] - cell2a.append(Paragraph("%s:%s:" % _("Candidates"), stylesheet['Heading4'])) cell2b = [] for candidate in assignment.candidates: - cell2b.append(Paragraph(".  %s" % candidate, + cell2b.append(Paragraph( + ".  %s" % candidate, stylesheet['Signaturefield'])) if assignment.status == "sea": for x in range(0, 2 * assignment.posts): - cell2b.append(Paragraph(".  " - "__________________________________________", - stylesheet['Signaturefield'])) + cell2b.append( + Paragraph( + ".  " + "__________________________________________", + stylesheet['Signaturefield'])) cell2b.append(Spacer(0, 0.2 * cm)) # Vote results @@ -409,15 +409,15 @@ class AssignmentPDF(PDFView): # Left side cell3a = [] - cell3a.append(Paragraph("%s:" % (_("Vote results")), - stylesheet['Heading4'])) + cell3a.append(Paragraph( + "%s:" % (_("Vote results")), stylesheet['Heading4'])) if polls.count() == 1: - cell3a.append(Paragraph("%s %s" % (polls.count(), _("ballot")), - stylesheet['Normal'])) + cell3a.append(Paragraph( + "%s %s" % (polls.count(), _("ballot")), stylesheet['Normal'])) elif polls.count() > 1: - cell3a.append(Paragraph("%s %s" % (polls.count(), _("ballots")), - stylesheet['Normal'])) + cell3a.append(Paragraph( + "%s %s" % (polls.count(), _("ballots")), stylesheet['Normal'])) # Add table head row headrow = [] @@ -426,7 +426,6 @@ class AssignmentPDF(PDFView): headrow.append("%s." % poll.get_ballot()) data_votes.append(headrow) - # Add result rows elected_candidates = list(assignment.elected) for candidate, poll_list in vote_results.iteritems(): @@ -439,12 +438,13 @@ class AssignmentPDF(PDFView): candidate_string += "\n(%s)" % candidate.name_suffix row.append(candidate_string) for vote in poll_list: - if vote == None: + if vote is None: row.append('–') elif 'Yes' in vote and 'No' in vote and 'Abstain' in vote: - row.append(_("Y: %(YES)s\nN: %(NO)s\nA: %(ABSTAIN)s") - % {'YES':vote['Yes'], 'NO': vote['No'], - 'ABSTAIN': vote['Abstain']}) + row.append( + _("Y: %(YES)s\nN: %(NO)s\nA: %(ABSTAIN)s") + % {'YES': vote['Yes'], 'NO': vote['No'], + 'ABSTAIN': vote['Abstain']}) elif 'Votes' in vote: row.append(vote['Votes']) else: @@ -465,15 +465,14 @@ class AssignmentPDF(PDFView): footrow_two.append(poll.print_votescast()) data_votes.append(footrow_two) - table_votes=Table(data_votes) - table_votes.setStyle( TableStyle([ + table_votes = Table(data_votes) + table_votes.setStyle(TableStyle([ ('GRID', (0, 0), (-1, -1), 0.5, colors.grey), - ('VALIGN',(0, 0),(-1, -1), 'TOP'), - ('LINEABOVE',(0, 0),(-1, 0), 2, colors.black), - ('LINEABOVE',(0, 1),(-1, 1), 1, colors.black), - ('LINEBELOW',(0, -1),(-1, -1), 2, colors.black), - ('ROWBACKGROUNDS', (0, 1), (-1, -1), (colors.white, (.9, .9, .9))), - ])) + ('VALIGN', (0, 0), (-1, -1), 'TOP'), + ('LINEABOVE', (0, 0), (-1, 0), 2, colors.black), + ('LINEABOVE', (0, 1), (-1, 1), 1, colors.black), + ('LINEBELOW', (0, -1), (-1, -1), 2, colors.black), + ('ROWBACKGROUNDS', (0, 1), (-1, -1), (colors.white, (.9, .9, .9)))])) # table data = [] @@ -484,17 +483,18 @@ class AssignmentPDF(PDFView): else: data.append([cell2a, cell2b]) data.append([Spacer(0, 0.2 * cm), '']) - t=Table(data) + t = Table(data) t._argW[0] = 4.5 * cm t._argW[1] = 11 * cm - t.setStyle(TableStyle([ ('BOX', (0,0), (-1, -1), 1, colors.black), - ('VALIGN', (0, 0), (-1, -1), 'TOP'), - ])) + t.setStyle(TableStyle([ + ('BOX', (0, 0), (-1, -1), 1, colors.black), + ('VALIGN', (0, 0), (-1, -1), 'TOP')])) story.append(t) story.append(Spacer(0, 1 * cm)) # text - story.append(Paragraph("%s" % assignment.description.replace('\r\n', + story.append(Paragraph( + "%s" % assignment.description.replace('\r\n', '
'), stylesheet['Paragraph'])) @@ -519,13 +519,15 @@ class AssignmentPollPDF(PDFView): return super(AssignmentPollPDF, self).get(request, *args, **kwargs) def get_filename(self): - filename = u'%s-%s_%s' % (_("Election"), self.poll.assignment.name.replace(' ', '_'), + filename = u'%s-%s_%s' % ( + _("Election"), self.poll.assignment.name.replace(' ', '_'), self.poll.get_ballot()) return filename def get_template(self, buffer): - return SimpleDocTemplate(buffer, topMargin=-6, bottomMargin=-6, - leftMargin=0, rightMargin=0, showBoundary=False) + return SimpleDocTemplate( + buffer, topMargin=-6, bottomMargin=-6, leftMargin=0, rightMargin=0, + showBoundary=False) def build_document(self, pdf_document, story): pdf_document.build(story) @@ -534,23 +536,27 @@ class AssignmentPollPDF(PDFView): imgpath = os.path.join(settings.SITE_ROOT, 'static/images/circle.png') circle = "  " % imgpath cell = [] - cell.append(Spacer(0,0.8*cm)) - cell.append(Paragraph(_("Election") + ": " + self.poll.assignment.name, + cell.append(Spacer(0, 0.8 * cm)) + cell.append(Paragraph( + _("Election") + ": " + self.poll.assignment.name, stylesheet['Ballot_title'])) - cell.append(Paragraph(self.poll.assignment.polldescription, + cell.append(Paragraph( + self.poll.assignment.polldescription, stylesheet['Ballot_subtitle'])) options = self.poll.get_options() ballot_string = _("%d. ballot") % self.poll.get_ballot() - candidate_string = ungettext("%d candidate", "%d candidates", - len(options)) % len(options) - available_posts_string = ungettext("%d available post", "%d available posts", + candidate_string = ungettext( + "%d candidate", "%d candidates", len(options)) % len(options) + available_posts_string = ungettext( + "%d available post", "%d available posts", self.poll.assignment.posts) % self.poll.assignment.posts - cell.append(Paragraph("%s, %s, %s" % (ballot_string, candidate_string, + cell.append(Paragraph( + "%s, %s, %s" % (ballot_string, candidate_string, available_posts_string), stylesheet['Ballot_description'])) cell.append(Spacer(0, 0.4 * cm)) - data= [] + data = [] # get ballot papers config values ballot_papers_selection = config["assignment_pdf_ballot_papers_selection"] ballot_papers_number = config["assignment_pdf_ballot_papers_number"] @@ -560,7 +566,7 @@ class AssignmentPollPDF(PDFView): number = User.objects.filter(type__iexact="delegate").count() elif ballot_papers_selection == "NUMBER_OF_ALL_PARTICIPANTS": number = int(User.objects.count()) - else: # ballot_papers_selection == "CUSTOM_NUMBER" + else: # ballot_papers_selection == "CUSTOM_NUMBER" number = int(ballot_papers_number) number = max(1, number) @@ -568,16 +574,18 @@ class AssignmentPollPDF(PDFView): if self.poll.yesnoabstain: for option in options: candidate = option.candidate - cell.append(Paragraph(candidate.clean_name, - stylesheet['Ballot_option_name'])) + cell.append(Paragraph( + candidate.clean_name, stylesheet['Ballot_option_name'])) if candidate.name_suffix: - cell.append(Paragraph("(%s)" % candidate.name_suffix, + cell.append(Paragraph( + "(%s)" % candidate.name_suffix, stylesheet['Ballot_option_group'])) else: - cell.append(Paragraph(" ", - stylesheet['Ballot_option_group'])) - cell.append(Paragraph(circle + _("Yes") + "  " * 3 + circle - + _("No") + "  " * 3 + circle+ _("Abstention"), + cell.append(Paragraph( + " ", stylesheet['Ballot_option_group'])) + cell.append(Paragraph( + circle + _("Yes") + "  " * 3 + circle + + _("No") + "  " * 3 + circle + _("Abstention"), stylesheet['Ballot_option_YNA'])) # print ballot papers for user in xrange(number / 2): @@ -594,14 +602,16 @@ class AssignmentPollPDF(PDFView): else: for option in options: candidate = option.candidate - cell.append(Paragraph(circle + candidate.clean_name, + cell.append(Paragraph( + circle + candidate.clean_name, stylesheet['Ballot_option_name'])) if candidate.name_suffix: - cell.append(Paragraph("(%s)" % candidate.name_suffix, + cell.append(Paragraph( + "(%s)" % candidate.name_suffix, stylesheet['Ballot_option_group_right'])) else: - cell.append(Paragraph(" ", - stylesheet['Ballot_option_group_right'])) + cell.append(Paragraph( + " ", stylesheet['Ballot_option_group_right'])) # print ballot papers for user in xrange(number / 2): data.append([cell, cell]) @@ -615,9 +625,9 @@ class AssignmentPollPDF(PDFView): else: t = Table(data, 10.5 * cm, 29.7 * cm) - t.setStyle(TableStyle([('GRID', (0, 0), (-1, -1), 0.25, colors.grey), - ('VALIGN', (0, 0), (-1, -1), 'TOP'), - ])) + t.setStyle(TableStyle([ + ('GRID', (0, 0), (-1, -1), 0.25, colors.grey), + ('VALIGN', (0, 0), (-1, -1), 'TOP')])) story.append(t) @@ -629,16 +639,15 @@ class Config(FormView): def get_initial(self): return { 'assignment_publish_winner_results_only': - config['assignment_publish_winner_results_only'], + config['assignment_publish_winner_results_only'], 'assignment_pdf_ballot_papers_selection': - config['assignment_pdf_ballot_papers_selection'], + config['assignment_pdf_ballot_papers_selection'], 'assignment_pdf_ballot_papers_number': - config['assignment_pdf_ballot_papers_number'], + config['assignment_pdf_ballot_papers_number'], 'assignment_pdf_title': config['assignment_pdf_title'], 'assignment_pdf_preamble': config['assignment_pdf_preamble'], 'assignment_poll_vote_values': - config['assignment_poll_vote_values'], - } + config['assignment_poll_vote_values']} def form_valid(self, form): if form.cleaned_data['assignment_publish_winner_results_only']: @@ -655,8 +664,8 @@ class Config(FormView): form.cleaned_data['assignment_pdf_preamble'] config['assignment_poll_vote_values'] = \ form.cleaned_data['assignment_poll_vote_values'] - messages.success(self.request, - _('Election settings successfully saved.')) + messages.success( + self.request, _('Election settings successfully saved.')) return super(Config, self).form_valid(form) @@ -665,10 +674,11 @@ def register_tab(request): return Tab( title=_('Elections'), url=reverse('assignment_overview'), - permission=request.user.has_perm('assignment.can_see_assignment') - or request.user.has_perm('assignment.can_nominate_other') - or request.user.has_perm('assignment.can_nominate_self') - or request.user.has_perm('assignment.can_manage_assignment'), + permission=( + request.user.has_perm('assignment.can_see_assignment') or + request.user.has_perm('assignment.can_nominate_other') or + request.user.has_perm('assignment.can_nominate_self') or + request.user.has_perm('assignment.can_manage_assignment')), selected=selected, ) diff --git a/openslides/config/forms.py b/openslides/config/forms.py index ef1a9b4d8..5e7f001d2 100644 --- a/openslides/config/forms.py +++ b/openslides/config/forms.py @@ -15,8 +15,6 @@ from django.utils.translation import ugettext_lazy as _ from openslides.utils.forms import CssClassMixin -from openslides.config.models import config - class GeneralConfigForm(forms.Form, CssClassMixin): event_name = forms.CharField( diff --git a/openslides/config/models.py b/openslides/config/models.py index a816be865..01ac2fd1d 100644 --- a/openslides/config/models.py +++ b/openslides/config/models.py @@ -51,7 +51,7 @@ class Config(object): pass for receiver, value in default_config_value.send(sender='config', - key=key): + key=key): if value is not None: return value if settings.DEBUG: @@ -69,7 +69,6 @@ class Config(object): def __contains__(self, item): return ConfigStore.objects.filter(key=item).exists() - config = Config() @@ -81,7 +80,7 @@ def default_config(sender, key, **kwargs): return { 'event_name': 'OpenSlides', 'event_description': - _('Presentation and assembly system'), + _('Presentation and assembly system'), 'event_date': '', 'event_location': '', 'event_organizer': '', @@ -123,11 +122,9 @@ def set_submenu(sender, request, context, **kwargs): (reverse('config_%s' % appname), _(title), selected) ) - menu_links.append ( - (reverse('config_version'), _('Version'), - request.path == reverse('config_version')) - ) + menu_links.append(( + reverse('config_version'), _('Version'), + request.path == reverse('config_version'))) context.update({ - 'menu_links': menu_links, - }) + 'menu_links': menu_links}) diff --git a/openslides/config/views.py b/openslides/config/views.py index dfeb6182e..c70fa781b 100644 --- a/openslides/config/views.py +++ b/openslides/config/views.py @@ -17,12 +17,12 @@ 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 @@ -61,12 +61,12 @@ class GeneralConfig(FormView): # system if form.cleaned_data['system_enable_anonymous']: config['system_enable_anonymous'] = True - anonymous = get_or_create_anonymous_group() + 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..fd148ebe5 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 = ( diff --git a/openslides/main.py b/openslides/main.py index 83e41395e..0156e5b95 100755 --- a/openslides/main.py +++ b/openslides/main.py @@ -65,13 +65,6 @@ 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) - - def main(argv=None, opt_defaults=None, database_path=None): if argv is None: argv = sys.argv[1:] @@ -102,7 +95,8 @@ def main(argv=None, opt_defaults=None, 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', \ + 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') @@ -142,13 +136,14 @@ 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', \ + 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') settings_content = CONFIG_TEMPLATE % dict( default_key=base64.b64encode(os.urandom(KEY_LENGTH)), - dbpath=_fs2unicode(database_path)) + dbpath=fs2unicode(database_path)) if not os.path.exists(settings_module): os.makedirs(settings_module) @@ -269,6 +264,14 @@ def start_browser(url): t = threading.Thread(target=f) t.start() + +def fs2unicode(s): + if isinstance(s, unicode): + return s + fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() + return s.decode(fs_encoding) + + def win32_portable_main(argv=None): """special entry point for the win32 portable version""" import tempfile @@ -287,17 +290,18 @@ def win32_portable_main(argv=None): 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") + 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 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.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 @@ -307,13 +311,13 @@ def win32_portable_main(argv=None): 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") + default_settings = os.path.join( + buf.value, "openslides", "openslides_personal_settings.py") + database_path = os.path.join( + buf.value, "openslides", "database.sqlite") - main(argv, opt_defaults={ "settings": default_settings }, - database_path=database_path) + main(argv, opt_defaults={"settings": default_settings}, + database_path=database_path) 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/tests.py b/openslides/motion/tests.py index 1fb387951..26c506c37 100644 --- a/openslides/motion/tests.py +++ b/openslides/motion/tests.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/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 5544af64c..fb5325628 100644 --- a/openslides/participant/api.py +++ b/openslides/participant/api.py @@ -14,11 +14,11 @@ from __future__ import with_statement from random import choice -import string import csv 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 @@ -78,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 @@ -105,7 +105,7 @@ def get_or_create_registered_group(): name__iexact='Registered', defaults={'name': 'Registered'}) if created: registered.permissions = Permission.objects.filter( - codename__in=DEFAULT_PERMS) + codename__in=DEFAULT_PERMS) registered.save() return registered @@ -115,6 +115,6 @@ def get_or_create_anonymous_group(): name__iexact='Anonymous', defaults={'name': 'Anonymous'}) if created: anonymous.permissions = Permission.objects.filter( - codename__in=DEFAULT_PERMS) + codename__in=DEFAULT_PERMS) anonymous.save() return anonymous diff --git a/openslides/participant/forms.py b/openslides/participant/forms.py index b1fcea525..8085f313f 100644 --- a/openslides/participant/forms.py +++ b/openslides/participant/forms.py @@ -66,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: @@ -102,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 7f047a9c4..0adb6219d 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,8 +132,9 @@ 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) @@ -173,6 +175,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 +219,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") diff --git a/openslides/participant/tests.py b/openslides/participant/tests.py index 10013b877..4227dc66b 100644 --- a/openslides/participant/tests.py +++ b/openslides/participant/tests.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) 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..885c4291d 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,14 @@ class SlideMixin(object): """ Return True, if the the slide is the active slide. """ - from api import get_active_slide + 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 +113,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/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..439d55744 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,17 +105,17 @@ 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='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) 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 f87de2e09..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 @@ -53,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. diff --git a/openslides/utils/utils.py b/openslides/utils/utils.py index 8f7ff1674..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,7 +65,8 @@ 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) @@ -79,11 +82,11 @@ def template(template_name): 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 @@ -109,12 +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.")): +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))) + return HttpResponseForbidden(render_to_string( + '403.html', {'error': error}, context_instance=RequestContext(request))) def delete_default_permissions(**kwargs): @@ -122,9 +125,9 @@ 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() 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") From 6b2645d11cde92274c517b64af3e0328406c015a Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Sat, 24 Nov 2012 15:00:17 +0100 Subject: [PATCH 06/25] reordered the tests moved the tests from the openslides-module to an own module configured coverage --- .coveragerc | 4 ++++ .gitignore | 4 ++++ openslides/agenda/views.py | 2 +- openslides/global_settings.py | 3 +++ tests/__init__.py | 0 openslides/agenda/tests.py => tests/test_agenda.py | 2 +- openslides/motion/tests.py => tests/test_motion.py | 0 openslides/participant/tests.py => tests/test_participant.py | 0 8 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 .coveragerc create mode 100644 tests/__init__.py rename openslides/agenda/tests.py => tests/test_agenda.py (99%) rename openslides/motion/tests.py => tests/test_motion.py (100%) rename openslides/participant/tests.py => tests/test_participant.py (100%) 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..d43bad4e2 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,7 @@ docs/_build/* build/* dist/* .DS_Store + +# Unit test / coverage reports +.coverage +htmlcov diff --git a/openslides/agenda/views.py b/openslides/agenda/views.py index 2c684bc95..55b42e204 100644 --- a/openslides/agenda/views.py +++ b/openslides/agenda/views.py @@ -27,7 +27,7 @@ from openslides.utils.utils import html_strong from openslides.projector.api import get_active_slide from openslides.projector.projector import Widget, SLIDE from .models import Item -from .agenda.forms import ItemOrderForm, ItemForm +from .forms import ItemOrderForm, ItemForm class Overview(TemplateView): diff --git a/openslides/global_settings.py b/openslides/global_settings.py index fd148ebe5..174d57d09 100644 --- a/openslides/global_settings.py +++ b/openslides/global_settings.py @@ -139,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/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/openslides/agenda/tests.py b/tests/test_agenda.py similarity index 99% rename from openslides/agenda/tests.py rename to tests/test_agenda.py index 0e270b36d..4e8a32d1d 100644 --- a/openslides/agenda/tests.py +++ b/tests/test_agenda.py @@ -16,7 +16,7 @@ from django.db.models.query import EmptyQuerySet from openslides.projector.api import get_active_slide from openslides.participant.models import User -from .models import Item +from openslides.agenda.models import Item class ItemTest(TestCase): diff --git a/openslides/motion/tests.py b/tests/test_motion.py similarity index 100% rename from openslides/motion/tests.py rename to tests/test_motion.py diff --git a/openslides/participant/tests.py b/tests/test_participant.py similarity index 100% rename from openslides/participant/tests.py rename to tests/test_participant.py From c405e9b648bcf8a16bb694a78f5496df3fafd8af Mon Sep 17 00:00:00 2001 From: Andy Kittner Date: Sat, 24 Nov 2012 14:22:02 +0100 Subject: [PATCH 07/25] Rework default settings handling (fixes #409) --- openslides/main.py | 155 ++++++++++++++++++++++++++++----------------- 1 file changed, 98 insertions(+), 57 deletions(-) diff --git a/openslides/main.py b/openslides/main.py index 83e41395e..80fe370a2 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,6 +67,10 @@ INSTALLED_APPS += INSTALLED_PLUGINS KEY_LENGTH = 30 +# sentinel used to signal that the database ought to be stored +# relative to the portable's directory +_portable_db_path = object() + _fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() def _fs2unicode(s): @@ -71,8 +78,7 @@ def _fs2unicode(s): return s return s.decode(_fs_encoding) - -def main(argv=None, opt_defaults=None, database_path=None): +def process_options(argv = None): if argv is None: argv = sys.argv[1:] @@ -90,21 +96,48 @@ def main(argv=None, opt_defaults=None, database_path=None): 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) - opts, args = parser.parse_args(argv) if args: sys.stderr.write("This command does not take arguments!\n\n") parser.print_help() sys.exit(1) + return opts + +def main(argv=None): + opts = process_options(argv) + _main(opts) + +def win32_portable_main(argv=None): + """special entry point for the win32 portable version""" + + opts = process_options(argv) + + database_path = None + + if opts.settings is None: + portable_dir = get_portable_path() + try: + fd, test_file = tempfile.mkstemp(dir=portable_dir) + except OSError: + portable_dir_writeable = False + else: + portable_dir_writeable = True + os.close(fd) + os.unlink(test_file) + + if portable_dir_writeable: + opts.settings = os.path.join(portable_dir, + "openslides", "settings.py") + database_path = _portable_db_path + + _main(opts, database_path=database_path) + +def _main(opts, database_path=None): # Find the path to the settings settings_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 +174,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) @@ -269,51 +305,56 @@ 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 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 +def get_portable_db_path(): + return get_portable_path('openslides', 'database.sqlite') - 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 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 - main(argv, opt_defaults={ "settings": default_settings }, - database_path=database_path) + 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__": From 220abe45c340563f85473aed6055554b844837e7 Mon Sep 17 00:00:00 2001 From: Andy Kittner Date: Sat, 24 Nov 2012 14:53:08 +0100 Subject: [PATCH 08/25] Copy _ctypes.pyd We need ctypes to determine the local app data directory (used for settings.py when portable dir is not writable) --- extras/win32-portable/prepare_portable.py | 1 + 1 file changed, 1 insertion(+) 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" From 4eed43f78299032256e4ef8b8b4d1b4577181a95 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Sat, 24 Nov 2012 15:26:20 +0100 Subject: [PATCH 09/25] Configured the repo for travis --- .travis.yml | 9 +++++++++ requirements.txt | 4 ++++ 2 files changed, 13 insertions(+) create mode 100644 .travis.yml create mode 100644 requirements.txt diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..c8cbbca3a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: python +python: + - "2.5" + - "2.6" + - "2.7" +install: + - pip install -r requirements.txt --use-mirrors + - pip install coverage django-discover-runner +script: coverage run ./manage.py test folivora && coverage report -m diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..6854e46d1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +Django==1.4 +django-mptt +reportlab +pil From ddd251a742f94d9b79528287b81c6d6590f38cd5 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Sat, 24 Nov 2012 15:43:17 +0100 Subject: [PATCH 10/25] fix for travis --- .travis.yml | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c8cbbca3a..3e6409fbd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,4 +6,4 @@ python: install: - pip install -r requirements.txt --use-mirrors - pip install coverage django-discover-runner -script: coverage run ./manage.py test folivora && coverage report -m +script: coverage run ./manage.py test tests && coverage report -m diff --git a/requirements.txt b/requirements.txt index 6854e46d1..9a69ad2b9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -Django==1.4 +Django==1.4.2 django-mptt reportlab pil From a7c13e3285db111382d2a31d3e1077d788e138e7 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Sat, 24 Nov 2012 16:22:48 +0100 Subject: [PATCH 11/25] added script to create a local settings --- .gitignore | 1 + .travis.yml | 1 + extras/scripts/create_local_settings.py | 15 +++++++++++++++ 3 files changed, 17 insertions(+) create mode 100644 extras/scripts/create_local_settings.py diff --git a/.gitignore b/.gitignore index d43bad4e2..d5edd3499 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ docs/_build/* build/* dist/* .DS_Store +settings.py # Unit test / coverage reports .coverage diff --git a/.travis.yml b/.travis.yml index 3e6409fbd..b65d5b1dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,4 +6,5 @@ python: install: - pip install -r requirements.txt --use-mirrors - pip install coverage django-discover-runner + - python extras/scripts/create_local_settings.py script: coverage run ./manage.py test tests && coverage report -m 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')) From 5cf27b358dcaf9dce82478d962edb83234ec5a1e Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Sat, 24 Nov 2012 16:28:47 +0100 Subject: [PATCH 12/25] added simplejson into the requirements for python 2.5 --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 9a69ad2b9..5a62061ce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ Django==1.4.2 django-mptt reportlab pil +simplejson From 65eeffe1c1cce5f4edaa015fc6757c4dc3b0f0ed Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Sat, 24 Nov 2012 16:51:21 +0100 Subject: [PATCH 13/25] Changed pil to PIL in requirements --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5a62061ce..15fe79d8d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ Django==1.4.2 django-mptt reportlab -pil +PIL simplejson From 21a51523a4f4c0b4d901f48aeba2ebe300889fd7 Mon Sep 17 00:00:00 2001 From: Andy Kittner Date: Sat, 24 Nov 2012 20:35:06 +0100 Subject: [PATCH 14/25] Make pep8.py happy --- openslides/main.py | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/openslides/main.py b/openslides/main.py index 80fe370a2..fa3ce3c98 100755 --- a/openslides/main.py +++ b/openslides/main.py @@ -73,12 +73,15 @@ _portable_db_path = object() _fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() + + def _fs2unicode(s): if isinstance(s, unicode): return s return s.decode(_fs_encoding) -def process_options(argv = None): + +def process_options(argv=None): if argv is None: argv = sys.argv[1:] @@ -104,10 +107,12 @@ def process_options(argv = None): 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""" @@ -127,12 +132,13 @@ def win32_portable_main(argv=None): os.unlink(test_file) if portable_dir_writeable: - opts.settings = os.path.join(portable_dir, - "openslides", "settings.py") + 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 @@ -305,24 +311,28 @@ def start_browser(url): t = threading.Thread(target=f) t.start() + 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')) + 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')) + 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 @@ -330,20 +340,24 @@ def get_portable_path(*args): exename = os.path.basename(sys.executable).lower() if exename != "openslides.exe": - raise Exception("Cannot determine portable path when " + raise Exception( + "Cannot determine portable path when " "not running as portable") portable_dir = _fs2unicode(os.path.dirname(os.path.abspath(sys.executable))) return os.path.join(portable_dir, *args) + def get_portable_db_path(): return get_portable_path('openslides', 'database.sqlite') + 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.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 From b3806d8cdd0231c1fb7307df859093e7320482df Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Sat, 24 Nov 2012 21:10:55 +0100 Subject: [PATCH 15/25] some tests for the agenda --- openslides/utils/jsonfield/tests.py | 108 ---------------------------- tests/test_agenda.py | 39 ++++++++++ tests/test_init.py | 19 +++++ 3 files changed, 58 insertions(+), 108 deletions(-) delete mode 100644 openslides/utils/jsonfield/tests.py create mode 100644 tests/test_init.py 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/tests/test_agenda.py b/tests/test_agenda.py index 4e8a32d1d..14fcf78e2 100644 --- a/tests/test_agenda.py +++ b/tests/test_agenda.py @@ -17,6 +17,7 @@ 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 class ItemTest(TestCase): @@ -65,6 +66,29 @@ 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') + class ViewTest(TestCase): def setUp(self): @@ -94,6 +118,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 @@ -125,6 +156,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 diff --git a/tests/test_init.py b/tests/test_init.py new file mode 100644 index 000000000..158620f87 --- /dev/null +++ b/tests/test_init.py @@ -0,0 +1,19 @@ +#!/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 testget_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') + self.assertEqual(len(get_version((2, 5, 0, 'dev', 0))), 47) From 763da9233be340203cd36e93d478bda7e13ef83d Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Sat, 24 Nov 2012 21:21:34 +0100 Subject: [PATCH 16/25] Fixed error in get_version --- tests/test_init.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_init.py b/tests/test_init.py index 158620f87..6155716f1 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -12,8 +12,12 @@ from django.test import TestCase from openslides import get_version class InitTest(TestCase): - def testget_version(self): + 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') - self.assertEqual(len(get_version((2, 5, 0, 'dev', 0))), 47) + 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) From 3f469ab9d60d06045a706d5140c106c4a85c4ada Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Sat, 24 Nov 2012 21:26:09 +0100 Subject: [PATCH 17/25] Commited the fix for get_version --- openslides/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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]) From 5d5989b67e9ab2f6b7ce6c2cd5dbb8aa1b0b93e2 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Sun, 25 Nov 2012 00:19:38 +0100 Subject: [PATCH 18/25] removed the tests package from the distribution --- .gitignore | 1 + setup.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d5edd3499..7f7a1c453 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ build/* dist/* .DS_Store settings.py +versiontools* # Unit test / coverage reports .coverage diff --git a/setup.py b/setup.py index 6b2911a46..c7da7ef4d 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ setup( 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 From 691a27655c53e3c4cd47d5fe85dba10e45ef4729 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Sun, 25 Nov 2012 00:28:41 +0100 Subject: [PATCH 19/25] Include the README.txt as long-description into the distribution --- README.txt | 17 +++++++++-------- setup.py | 4 ++++ 2 files changed, 13 insertions(+), 8 deletions(-) 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/setup.py b/setup.py index c7da7ef4d..ed1c3a018 100644 --- a/setup.py +++ b/setup.py @@ -12,9 +12,13 @@ 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', From a31a67c32e649930e75eaff6688517b1cffd55f1 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Sun, 25 Nov 2012 00:55:01 +0100 Subject: [PATCH 20/25] removed initial_data --- MANIFEST.in | 7 ++----- .../participant/fixtures/groups_de.json | 0 2 files changed, 2 insertions(+), 5 deletions(-) rename initial_data.json => openslides/participant/fixtures/groups_de.json (100%) diff --git a/MANIFEST.in b/MANIFEST.in index d028d548b..0977abf4e 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/initial_data.json recursive-include openslides/poll/templates * -recursive-include openslides/poll/static * recursive-include openslides/projector/templates * recursive-include openslides/projector/static * 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 From fddf929ab5e7350407d1b245d452c0b203369a55 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Sun, 25 Nov 2012 00:58:19 +0100 Subject: [PATCH 21/25] Load groups_de when creating the database --- MANIFEST.in | 2 +- openslides/main.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 0977abf4e..affdfa149 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -17,7 +17,7 @@ recursive-include openslides/assignment/static * recursive-include openslides/config/templates * recursive-include openslides/participant/templates * recursive-include openslides/participant/static * -include openslides/participant/initial_data.json +include openslides/participant/fixtures/groups_de.json recursive-include openslides/poll/templates * recursive-include openslides/projector/templates * recursive-include openslides/projector/static * diff --git a/openslides/main.py b/openslides/main.py index 11f543ab9..f7818b7ab 100755 --- a/openslides/main.py +++ b/openslides/main.py @@ -252,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): From abd21dd345b27eccc24c811a9c1cdea5405a7543 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Mon, 26 Nov 2012 10:05:51 +0100 Subject: [PATCH 22/25] Added more tests for the agenda --- openslides/projector/projector.py | 2 ++ tests/agenda/__init__.py | 0 tests/agenda/models.py | 18 ++++++++++++++++++ tests/{test_agenda.py => agenda/tests.py} | 16 +++++++++++----- 4 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 tests/agenda/__init__.py create mode 100644 tests/agenda/models.py rename tests/{test_agenda.py => agenda/tests.py} (92%) diff --git a/openslides/projector/projector.py b/openslides/projector/projector.py index 885c4291d..6e3129f4b 100644 --- a/openslides/projector/projector.py +++ b/openslides/projector/projector.py @@ -49,6 +49,8 @@ class SlideMixin(object): """ Return True, if the the slide is the 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 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/tests/test_agenda.py b/tests/agenda/tests.py similarity index 92% rename from tests/test_agenda.py rename to tests/agenda/tests.py index 14fcf78e2..05c582f66 100644 --- a/tests/test_agenda.py +++ b/tests/agenda/tests.py @@ -19,6 +19,8 @@ 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): @@ -26,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) @@ -47,11 +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 @@ -89,6 +88,13 @@ class ItemTest(TestCase): 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): From c276bce7802bf17f73c8c3e4d71de7ec18d0c6be Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Mon, 26 Nov 2012 10:11:36 +0100 Subject: [PATCH 23/25] Let travis test pep8 --- .travis.yml | 6 ++++-- openslides/participant/models.py | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b65d5b1dd..c2ab9f6c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,8 @@ python: - "2.7" install: - pip install -r requirements.txt --use-mirrors - - pip install coverage django-discover-runner + - pip install coverage django-discover-runner pep8 - python extras/scripts/create_local_settings.py -script: coverage run ./manage.py test tests && coverage report -m +script: + - coverage run ./manage.py test tests && coverage report -m + - pep8 --max-line-length=150 openslides | grep -v utils/pdf.py | grep -v urls.py | grep -v motion/ diff --git a/openslides/participant/models.py b/openslides/participant/models.py index 0adb6219d..bd771945d 100644 --- a/openslides/participant/models.py +++ b/openslides/participant/models.py @@ -138,7 +138,9 @@ class Group(DjangoGroup, PersonMixin, Person, SlideMixin): 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 From a0e4519b6248a48dc0b0dcb804b1f32364bd5721 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Mon, 26 Nov 2012 10:19:45 +0100 Subject: [PATCH 24/25] fixed travis pep8 line --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c2ab9f6c1..ba93efe7c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,4 +9,4 @@ install: - python extras/scripts/create_local_settings.py script: - coverage run ./manage.py test tests && coverage report -m - - pep8 --max-line-length=150 openslides | grep -v utils/pdf.py | grep -v urls.py | grep -v motion/ + - pep8 --max-line-length=150 --exclude="urls.py,motion/,pdf.py" --statistics openslides From bdfb7140139bed2e57984af0d7e3ddf8f022bd1e Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Mon, 26 Nov 2012 10:35:29 +0100 Subject: [PATCH 25/25] fixed pep8 in pdf.py --- .travis.yml | 2 +- openslides/utils/pdf.py | 144 ++++++++++++++++++++-------------------- 2 files changed, 73 insertions(+), 73 deletions(-) diff --git a/.travis.yml b/.travis.yml index ba93efe7c..bad214ce7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,4 +9,4 @@ install: - 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/,pdf.py" --statistics openslides + - pep8 --max-line-length=150 --exclude="urls.py,motion/" --statistics openslides diff --git a/openslides/utils/pdf.py b/openslides/utils/pdf.py index 439d55744..4064f109e 100755 --- a/openslides/utils/pdf.py +++ b/openslides/utils/pdf.py @@ -116,93 +116,93 @@ stylesheet.add(ParagraphStyle( name='Tablecell', parent=stylesheet['Normal'], fontSize=9)) -stylesheet.add(ParagraphStyle(name = 'Signaturefield', - parent = stylesheet['Normal'], - spaceBefore = 15) +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)