Merge remote branch 'upstream/master'
This commit is contained in:
commit
40984a6508
4
.coveragerc
Normal file
4
.coveragerc
Normal file
@ -0,0 +1,4 @@
|
||||
[run]
|
||||
source=openslides
|
||||
[report]
|
||||
exclude_lines = def __(unicode|repr)__
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -12,3 +12,9 @@ docs/_build/*
|
||||
build/*
|
||||
dist/*
|
||||
.DS_Store
|
||||
settings.py
|
||||
versiontools*
|
||||
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
htmlcov
|
||||
|
12
.travis.yml
Normal file
12
.travis.yml
Normal file
@ -0,0 +1,12 @@
|
||||
language: python
|
||||
python:
|
||||
- "2.5"
|
||||
- "2.6"
|
||||
- "2.7"
|
||||
install:
|
||||
- pip install -r requirements.txt --use-mirrors
|
||||
- pip install coverage django-discover-runner pep8
|
||||
- python extras/scripts/create_local_settings.py
|
||||
script:
|
||||
- coverage run ./manage.py test tests && coverage report -m
|
||||
- pep8 --max-line-length=150 --exclude="urls.py,motion/" --statistics openslides
|
@ -1,6 +1,5 @@
|
||||
include AUTHORS
|
||||
include CHANGELOG
|
||||
include initial_data.json
|
||||
include INSTALL.txt
|
||||
include LICENSE
|
||||
include manage.py
|
||||
@ -12,16 +11,14 @@ recursive-include openslides/templates *
|
||||
|
||||
recursive-include openslides/agenda/templates *
|
||||
recursive-include openslides/agenda/static *
|
||||
recursive-include openslides/application/templates *
|
||||
recursive-include openslides/application/static *
|
||||
recursive-include openslides/motion/templates *
|
||||
recursive-include openslides/assignment/templates *
|
||||
recursive-include openslides/assignment/static *
|
||||
recursive-include openslides/config/templates *
|
||||
recursive-include openslides/config/static *
|
||||
recursive-include openslides/participant/templates *
|
||||
recursive-include openslides/participant/static *
|
||||
include openslides/participant/fixtures/groups_de.json
|
||||
recursive-include openslides/poll/templates *
|
||||
recursive-include openslides/poll/static *
|
||||
recursive-include openslides/projector/templates *
|
||||
recursive-include openslides/projector/static *
|
||||
|
||||
|
11
README.txt
11
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,7 +18,7 @@ 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
|
||||
@ -26,6 +26,7 @@ The start script of OpenSlides
|
||||
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.
|
||||
|
||||
|
15
extras/scripts/create_local_settings.py
Normal file
15
extras/scripts/create_local_settings.py
Normal file
@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
script_path = os.path.realpath(os.path.dirname(__file__))
|
||||
sys.path.append(os.path.join(script_path, '..', '..'))
|
||||
|
||||
from openslides.main import create_settings
|
||||
|
||||
if __name__ == "__main__":
|
||||
cwd = os.getcwd()
|
||||
create_settings(os.path.join(cwd, 'settings.py'),
|
||||
os.path.join(cwd, 'database.sqlite'))
|
@ -87,6 +87,7 @@ PY_DLLS = [
|
||||
"_sqlite3.pyd",
|
||||
"_socket.pyd",
|
||||
"select.pyd",
|
||||
"_ctypes.pyd",
|
||||
]
|
||||
|
||||
MSVCR_PUBLIC_KEY = "1fc8b3b9a1e18e3b"
|
||||
|
@ -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])
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
@ -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.
|
||||
|
@ -11,7 +11,6 @@
|
||||
"""
|
||||
from reportlab.platypus import Paragraph
|
||||
|
||||
from django.core.context_processors import csrf
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib import messages
|
||||
from django.db import transaction
|
||||
@ -20,18 +19,15 @@ from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
|
||||
from openslides.utils.pdf import stylesheet
|
||||
from openslides.utils.views import (TemplateView, RedirectView, UpdateView,
|
||||
CreateView, DeleteView, PDFView, DetailView)
|
||||
from openslides.utils.views import (
|
||||
TemplateView, RedirectView, UpdateView, CreateView, DeleteView, PDFView,
|
||||
DetailView)
|
||||
from openslides.utils.template import Tab
|
||||
from openslides.utils.utils import html_strong
|
||||
|
||||
from openslides.config.models import config
|
||||
|
||||
from openslides.projector.api import get_active_slide
|
||||
from openslides.projector.projector import Widget, SLIDE
|
||||
|
||||
from openslides.agenda.models import Item
|
||||
from openslides.agenda.forms import ItemOrderForm, ItemForm
|
||||
from .models import Item
|
||||
from .forms import ItemOrderForm, ItemForm
|
||||
|
||||
|
||||
class Overview(TemplateView):
|
||||
@ -53,7 +49,8 @@ class Overview(TemplateView):
|
||||
def post(self, request, *args, **kwargs):
|
||||
context = self.get_context_data(**kwargs)
|
||||
if not request.user.has_perm('agenda.can_manage_agenda'):
|
||||
messages.error(request,
|
||||
messages.error(
|
||||
request,
|
||||
_('You are not authorized to manage the agenda.'))
|
||||
return self.render_to_response(context)
|
||||
transaction.commit()
|
||||
@ -69,8 +66,8 @@ class Overview(TemplateView):
|
||||
Model.save(item)
|
||||
else:
|
||||
transaction.rollback()
|
||||
messages.error(request,
|
||||
_('Errors when reordering of the agenda'))
|
||||
messages.error(
|
||||
request, _('Errors when reordering of the agenda'))
|
||||
return self.render_to_response(context)
|
||||
Item.objects.rebuild()
|
||||
# TODO: assure, that it is a valid tree
|
||||
@ -130,8 +127,8 @@ class ItemUpdate(UpdateView):
|
||||
apply_url = 'item_edit'
|
||||
|
||||
def get_success_url(self):
|
||||
messages.success(self.request,
|
||||
_("Item %s was successfully modified.") \
|
||||
messages.success(
|
||||
self.request, _("Item %s was successfully modified.")
|
||||
% html_strong(self.request.POST['title']))
|
||||
if 'apply' in self.request.POST:
|
||||
return ''
|
||||
@ -151,8 +148,8 @@ class ItemCreate(CreateView):
|
||||
apply_url = 'item_edit'
|
||||
|
||||
def get_success_url(self):
|
||||
messages.success(self.request,
|
||||
_("Item %s was successfully created.") \
|
||||
messages.success(
|
||||
self.request, _("Item %s was successfully created.")
|
||||
% html_strong(self.request.POST['title']))
|
||||
if 'apply' in self.request.POST:
|
||||
return reverse(self.get_apply_url(), args=[self.object.id])
|
||||
@ -176,13 +173,13 @@ class ItemDelete(DeleteView):
|
||||
def pre_post_redirect(self, request, *args, **kwargs):
|
||||
if self.get_answer() == 'all':
|
||||
self.object.delete(with_children=True)
|
||||
messages.success(request,
|
||||
_("Item %s and his children were successfully deleted.")
|
||||
messages.success(
|
||||
request, _("Item %s and his children were successfully deleted.")
|
||||
% html_strong(self.object))
|
||||
elif self.get_answer() == 'yes':
|
||||
self.object.delete(with_children=False)
|
||||
messages.success(request,
|
||||
_("Item %s was successfully deleted.")
|
||||
messages.success(
|
||||
request, _("Item %s was successfully deleted.")
|
||||
% html_strong(self.object))
|
||||
|
||||
|
||||
@ -199,7 +196,8 @@ class AgendaPDF(PDFView):
|
||||
ancestors = item.get_ancestors()
|
||||
if ancestors:
|
||||
space = " " * 6 * ancestors.count()
|
||||
story.append(Paragraph("%s%s" % (space, item.get_title()),
|
||||
story.append(Paragraph(
|
||||
"%s%s" % (space, item.get_title()),
|
||||
stylesheet['Subitem']))
|
||||
else:
|
||||
story.append(Paragraph(item.get_title(), stylesheet['Item']))
|
||||
@ -213,10 +211,9 @@ def register_tab(request):
|
||||
return Tab(
|
||||
title=_('Agenda'),
|
||||
url=reverse('item_overview'),
|
||||
permission=request.user.has_perm('agenda.can_see_agenda')
|
||||
or request.user.has_perm('agenda.can_manage_agenda'),
|
||||
selected=selected,
|
||||
)
|
||||
permission=(request.user.has_perm('agenda.can_see_agenda') or
|
||||
request.user.has_perm('agenda.can_manage_agenda')),
|
||||
selected=selected)
|
||||
|
||||
|
||||
def get_widgets(request):
|
||||
|
@ -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."))))
|
||||
|
@ -16,16 +16,12 @@ from django.dispatch import receiver
|
||||
from django.utils.translation import ugettext_lazy as _, ugettext_noop
|
||||
|
||||
from openslides.utils.person import PersonField
|
||||
|
||||
from openslides.config.models import config
|
||||
from openslides.config.signals import default_config_value
|
||||
|
||||
from openslides.projector.api import register_slidemodel
|
||||
from openslides.projector.projector import SlideMixin
|
||||
|
||||
from openslides.poll.models import (BasePoll, CountInvalid, CountVotesCast,
|
||||
BaseOption, PublishPollMixin, BaseVote)
|
||||
|
||||
from openslides.poll.models import (
|
||||
BasePoll, CountInvalid, CountVotesCast, BaseOption, PublishPollMixin, BaseVote)
|
||||
from openslides.agenda.models import Item
|
||||
|
||||
|
||||
@ -51,11 +47,10 @@ class Assignment(models.Model, SlideMixin):
|
||||
)
|
||||
|
||||
name = models.CharField(max_length=100, verbose_name=_("Name"))
|
||||
description = models.TextField(null=True, blank=True,
|
||||
verbose_name=_("Description"))
|
||||
posts = models.PositiveSmallIntegerField(
|
||||
verbose_name=_("Number of available posts"))
|
||||
polldescription = models.CharField(max_length=100, null=True, blank=True,
|
||||
description = models.TextField(null=True, blank=True, verbose_name=_("Description"))
|
||||
posts = models.PositiveSmallIntegerField(verbose_name=_("Number of available posts"))
|
||||
polldescription = models.CharField(
|
||||
max_length=100, null=True, blank=True,
|
||||
verbose_name=_("Comment on the ballot paper"))
|
||||
status = models.CharField(max_length=3, choices=STATUS, default='sea')
|
||||
|
||||
@ -68,8 +63,8 @@ class Assignment(models.Model, SlideMixin):
|
||||
if error:
|
||||
raise NameError(_('%s is not a valid status.') % status)
|
||||
if self.status == status:
|
||||
raise NameError(_('The assignment status is already %s.')
|
||||
% self.status)
|
||||
raise NameError(
|
||||
_('The assignment status is already %s.') % self.status)
|
||||
self.status = status
|
||||
self.save()
|
||||
|
||||
@ -116,14 +111,12 @@ class Assignment(models.Model, SlideMixin):
|
||||
else:
|
||||
candidature.delete()
|
||||
|
||||
|
||||
def is_candidate(self, person):
|
||||
"""
|
||||
return True, if person is a candidate.
|
||||
"""
|
||||
try:
|
||||
return self.assignment_candidates.filter(person=person) \
|
||||
.exclude(blocked=True).exists()
|
||||
return self.assignment_candidates.filter(person=person).exclude(blocked=True).exists()
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
@ -131,8 +124,7 @@ class Assignment(models.Model, SlideMixin):
|
||||
"""
|
||||
return True, if the person is blockt for candidature.
|
||||
"""
|
||||
return self.assignment_candidates.filter(person=person) \
|
||||
.filter(blocked=True).exists()
|
||||
return self.assignment_candidates.filter(person=person).filter(blocked=True).exists()
|
||||
|
||||
@property
|
||||
def assignment_candidates(self):
|
||||
@ -167,7 +159,6 @@ class Assignment(models.Model, SlideMixin):
|
||||
return participants
|
||||
#return candidates.values_list('person', flat=True)
|
||||
|
||||
|
||||
def set_elected(self, person, value=True):
|
||||
candidate = self.assignment_candidates.get(person=person)
|
||||
candidate.elected = value
|
||||
@ -215,7 +206,6 @@ class Assignment(models.Model, SlideMixin):
|
||||
vote_results_dict[candidate].append(votes)
|
||||
return vote_results_dict
|
||||
|
||||
|
||||
def get_agenda_title(self):
|
||||
return self.name
|
||||
|
||||
@ -301,8 +291,7 @@ class AssignmentPoll(BasePoll, CountInvalid, CountVotesCast, PublishPollMixin):
|
||||
self.yesnoabstain = False
|
||||
self.save()
|
||||
if self.yesnoabstain:
|
||||
return [ugettext_noop('Yes'), ugettext_noop('No'),
|
||||
ugettext_noop('Abstain')]
|
||||
return [ugettext_noop('Yes'), ugettext_noop('No'), ugettext_noop('Abstain')]
|
||||
else:
|
||||
return [ugettext_noop('Votes')]
|
||||
|
||||
|
@ -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', '<br/>'),
|
||||
story.append(Paragraph(
|
||||
"%s" % preamble.replace('\r\n', '<br/>'),
|
||||
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']))
|
||||
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,26 +371,31 @@ class AssignmentPDF(PDFView):
|
||||
|
||||
def get_assignment(self, assignment, story):
|
||||
# title
|
||||
story.append(Paragraph(_("Election: %s") % assignment.name,
|
||||
stylesheet['Heading1']))
|
||||
story.append(Paragraph(
|
||||
_("Election: %s") % assignment.name, stylesheet['Heading1']))
|
||||
story.append(Spacer(0, 0.5 * cm))
|
||||
# posts
|
||||
cell1a = []
|
||||
cell1a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" %
|
||||
cell1a.append(Paragraph(
|
||||
"<font name='Ubuntu-Bold'>%s:</font>" %
|
||||
_("Number of available posts"), stylesheet['Bold']))
|
||||
cell1b = []
|
||||
cell1b.append(Paragraph(str(assignment.posts), stylesheet['Paragraph']))
|
||||
# candidates
|
||||
cell2a = []
|
||||
cell2a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font><seqreset" \
|
||||
cell2a.append(Paragraph(
|
||||
"<font name='Ubuntu-Bold'>%s:</font><seqreset"
|
||||
" id='counter'>" % _("Candidates"), stylesheet['Heading4']))
|
||||
cell2b = []
|
||||
for candidate in assignment.candidates:
|
||||
cell2b.append(Paragraph("<seq id='counter'/>. %s" % candidate,
|
||||
cell2b.append(Paragraph(
|
||||
"<seq id='counter'/>. %s" % candidate,
|
||||
stylesheet['Signaturefield']))
|
||||
if assignment.status == "sea":
|
||||
for x in range(0, 2 * assignment.posts):
|
||||
cell2b.append(Paragraph("<seq id='counter'/>. "
|
||||
cell2b.append(
|
||||
Paragraph(
|
||||
"<seq id='counter'/>. "
|
||||
"__________________________________________",
|
||||
stylesheet['Signaturefield']))
|
||||
cell2b.append(Spacer(0, 0.2 * cm))
|
||||
@ -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,11 +438,12 @@ 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'],
|
||||
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'])
|
||||
@ -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',
|
||||
'<br/>'), 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 = "<img src='%s' width='15' height='15'/> " % 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"]
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -637,8 +647,7 @@ class Config(FormView):
|
||||
'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,
|
||||
)
|
||||
|
||||
|
@ -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(
|
||||
|
@ -69,7 +69,6 @@ class Config(object):
|
||||
def __contains__(self, item):
|
||||
return ConfigStore.objects.filter(key=item).exists()
|
||||
|
||||
|
||||
config = Config()
|
||||
|
||||
|
||||
@ -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})
|
||||
|
@ -12,18 +12,18 @@
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.models import Group, Permission
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.importlib import import_module
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from openslides import get_version
|
||||
|
||||
from openslides.utils.template import Tab
|
||||
from openslides.utils.views import FormView, TemplateView
|
||||
from .forms import GeneralConfigForm
|
||||
from .models import config
|
||||
|
||||
from openslides.config.forms import GeneralConfigForm
|
||||
from openslides.config.models import config
|
||||
# TODO: Do not import the participant module in config
|
||||
from openslides.participant.api import get_or_create_anonymous_group
|
||||
|
||||
|
||||
class GeneralConfig(FormView):
|
||||
@ -61,27 +61,12 @@ class GeneralConfig(FormView):
|
||||
# system
|
||||
if form.cleaned_data['system_enable_anonymous']:
|
||||
config['system_enable_anonymous'] = True
|
||||
# check for Anonymous group and (re)create it as needed
|
||||
try:
|
||||
anonymous = Group.objects.get(name='Anonymous')
|
||||
except Group.DoesNotExist:
|
||||
default_perms = ['can_see_agenda', 'can_see_projector',
|
||||
'can_see_motion', 'can_see_assignment',
|
||||
'can_see_dashboard']
|
||||
anonymous = Group()
|
||||
anonymous.name = 'Anonymous'
|
||||
anonymous.save()
|
||||
anonymous.permissions = Permission.objects.filter(
|
||||
codename__in=default_perms)
|
||||
anonymous.save()
|
||||
messages.success(self.request,
|
||||
_('Anonymous access enabled. Please modify the "Anonymous" ' \
|
||||
'group to fit your required permissions.'))
|
||||
get_or_create_anonymous_group()
|
||||
else:
|
||||
config['system_enable_anonymous'] = False
|
||||
|
||||
messages.success(self.request,
|
||||
_('General settings successfully saved.'))
|
||||
messages.success(
|
||||
self.request, _('General settings successfully saved.'))
|
||||
return super(GeneralConfig, self).form_valid(form)
|
||||
|
||||
|
||||
|
@ -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,7 +59,7 @@ 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).
|
||||
@ -72,7 +69,7 @@ STATIC_URL = '/static/'
|
||||
# Additional directories containing static files (not application specific)
|
||||
# Examples: "/home/media/lawrence.com/extra-static/"
|
||||
STATICFILES_DIRS = (
|
||||
_fs2unicode(os.path.join(SITE_ROOT, 'static')),
|
||||
fs2unicode(os.path.join(SITE_ROOT, 'static')),
|
||||
)
|
||||
|
||||
#XXX: Note this setting (as well as our workaround finder)
|
||||
@ -106,7 +103,7 @@ TEMPLATE_DIRS = (
|
||||
# "C:/www/django/templates".
|
||||
# Always use forward slashes, even on Windows.
|
||||
# Don't forget to use absolute paths, not relative paths.
|
||||
_fs2unicode(os.path.join(SITE_ROOT, 'templates')),
|
||||
fs2unicode(os.path.join(SITE_ROOT, 'templates')),
|
||||
)
|
||||
|
||||
INSTALLED_APPS = (
|
||||
@ -142,3 +139,6 @@ CACHES = {
|
||||
'LOCATION': 'openslidecache'
|
||||
}
|
||||
}
|
||||
|
||||
TEST_RUNNER = 'discover_runner.DiscoverRunner'
|
||||
TEST_DISCOVER_TOP_LEVEL = os.path.dirname(os.path.dirname(__file__))
|
||||
|
@ -13,13 +13,15 @@
|
||||
# for python 2.5 support
|
||||
from __future__ import with_statement
|
||||
|
||||
import os
|
||||
import sys
|
||||
import optparse
|
||||
import socket
|
||||
import time
|
||||
import threading
|
||||
import base64
|
||||
import ctypes
|
||||
import optparse
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
import webbrowser
|
||||
|
||||
import django.conf
|
||||
@ -28,14 +30,15 @@ from django.core.management import execute_from_command_line
|
||||
CONFIG_TEMPLATE = """#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import openslides.main
|
||||
from openslides.global_settings import *
|
||||
|
||||
# Use 'DEBUG = True' to get more details for server errors
|
||||
# (Default for relaeses: 'False')
|
||||
# (Default for releases: 'False')
|
||||
DEBUG = False
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
|
||||
DBPATH = %(dbpath)r
|
||||
DBPATH = %(dbpath)s
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
@ -64,15 +67,12 @@ INSTALLED_APPS += INSTALLED_PLUGINS
|
||||
|
||||
KEY_LENGTH = 30
|
||||
|
||||
|
||||
_fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
|
||||
def _fs2unicode(s):
|
||||
if isinstance(s, unicode):
|
||||
return s
|
||||
return s.decode(_fs_encoding)
|
||||
# sentinel used to signal that the database ought to be stored
|
||||
# relative to the portable's directory
|
||||
_portable_db_path = object()
|
||||
|
||||
|
||||
def main(argv=None, opt_defaults=None, database_path=None):
|
||||
def process_options(argv=None):
|
||||
if argv is None:
|
||||
argv = sys.argv[1:]
|
||||
|
||||
@ -86,12 +86,11 @@ def main(argv=None, opt_defaults=None, database_path=None):
|
||||
parser.add_option(
|
||||
"--reset-admin", action="store_true",
|
||||
help="Make sure the user 'admin' exists and uses 'admin' as password")
|
||||
parser.add_option("-s", "--settings", help="Path to the openslides configuration.")
|
||||
parser.add_option(
|
||||
"--no-reload", action="store_true", help="Do not reload the development server")
|
||||
|
||||
if not opt_defaults is None:
|
||||
parser.set_defaults(**opt_defaults)
|
||||
"-s", "--settings", help="Path to the openslides configuration.")
|
||||
parser.add_option(
|
||||
"--no-reload", action="store_true",
|
||||
help="Do not reload the development server")
|
||||
|
||||
opts, args = parser.parse_args(argv)
|
||||
if args:
|
||||
@ -99,12 +98,45 @@ def main(argv=None, opt_defaults=None, database_path=None):
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
return opts
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
opts = process_options(argv)
|
||||
_main(opts)
|
||||
|
||||
|
||||
def win32_portable_main(argv=None):
|
||||
"""special entry point for the win32 portable version"""
|
||||
|
||||
opts = process_options(argv)
|
||||
|
||||
database_path = None
|
||||
|
||||
if opts.settings is None:
|
||||
portable_dir = get_portable_path()
|
||||
try:
|
||||
fd, test_file = tempfile.mkstemp(dir=portable_dir)
|
||||
except OSError:
|
||||
portable_dir_writeable = False
|
||||
else:
|
||||
portable_dir_writeable = True
|
||||
os.close(fd)
|
||||
os.unlink(test_file)
|
||||
|
||||
if portable_dir_writeable:
|
||||
opts.settings = os.path.join(
|
||||
portable_dir, "openslides", "settings.py")
|
||||
database_path = _portable_db_path
|
||||
|
||||
_main(opts, database_path=database_path)
|
||||
|
||||
|
||||
def _main(opts, database_path=None):
|
||||
# Find the path to the settings
|
||||
settings_path = opts.settings
|
||||
if settings_path is None:
|
||||
config_home = os.environ.get('XDG_CONFIG_HOME', \
|
||||
os.path.join(os.path.expanduser('~'), '.config'))
|
||||
settings_path = os.path.join(config_home, 'openslides', 'settings.py')
|
||||
settings_path = get_user_config_path('openslides', 'settings.py')
|
||||
|
||||
# Create settings if necessary
|
||||
if not os.path.exists(settings_path):
|
||||
@ -141,14 +173,17 @@ def main(argv=None, opt_defaults=None, database_path=None):
|
||||
def create_settings(settings_path, database_path=None):
|
||||
settings_module = os.path.dirname(settings_path)
|
||||
|
||||
if database_path is _portable_db_path:
|
||||
database_path = get_portable_db_path()
|
||||
dbpath_value = 'openslides.main.get_portable_db_path()'
|
||||
else:
|
||||
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')
|
||||
database_path = get_user_data_path('openslides', 'database.sqlite')
|
||||
dbpath_value = repr(fs2unicode(database_path))
|
||||
|
||||
settings_content = CONFIG_TEMPLATE % dict(
|
||||
default_key=base64.b64encode(os.urandom(KEY_LENGTH)),
|
||||
dbpath=_fs2unicode(database_path))
|
||||
dbpath=dbpath_value)
|
||||
|
||||
if not os.path.exists(settings_module):
|
||||
os.makedirs(settings_module)
|
||||
@ -217,6 +252,7 @@ def run_syncdb():
|
||||
# now initialize the database
|
||||
argv = ["", "syncdb", "--noinput"]
|
||||
execute_from_command_line(argv)
|
||||
execute_from_command_line(["", "loaddata", "groups_de"])
|
||||
|
||||
|
||||
def set_system_url(url):
|
||||
@ -269,35 +305,60 @@ def start_browser(url):
|
||||
t = threading.Thread(target=f)
|
||||
t.start()
|
||||
|
||||
def win32_portable_main(argv=None):
|
||||
"""special entry point for the win32 portable version"""
|
||||
import tempfile
|
||||
|
||||
def fs2unicode(s):
|
||||
if isinstance(s, unicode):
|
||||
return s
|
||||
fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
|
||||
return s.decode(fs_encoding)
|
||||
|
||||
|
||||
def get_user_config_path(*args):
|
||||
if sys.platform == "win32":
|
||||
return win32_get_app_data_path(*args)
|
||||
|
||||
config_home = os.environ.get(
|
||||
'XDG_CONFIG_HOME', os.path.join(os.path.expanduser('~'), '.config'))
|
||||
|
||||
return os.path.join(fs2unicode(config_home), *args)
|
||||
|
||||
|
||||
def get_user_data_path(*args):
|
||||
if sys.platform == "win32":
|
||||
return win32_get_app_data_path(*args)
|
||||
|
||||
data_home = os.environ.get(
|
||||
'XDG_DATA_HOME', os.path.join(
|
||||
os.path.expanduser('~'), '.local', 'share'))
|
||||
|
||||
return os.path.join(fs2unicode(data_home), *args)
|
||||
|
||||
|
||||
def get_portable_path(*args):
|
||||
# NOTE: sys.executable will be the path to openslides.exe
|
||||
# since it is essentially a small wrapper that embeds the
|
||||
# python interpreter
|
||||
portable_dir = os.path.dirname(os.path.abspath(sys.executable))
|
||||
try:
|
||||
fd, test_file = tempfile.mkstemp(dir=portable_dir)
|
||||
except OSError:
|
||||
portable_dir_writeable = False
|
||||
else:
|
||||
portable_dir_writeable = True
|
||||
os.close(fd)
|
||||
os.unlink(test_file)
|
||||
|
||||
if portable_dir_writeable:
|
||||
default_settings = os.path.join(portable_dir, "openslides",
|
||||
"openslides_personal_settings.py")
|
||||
database_path = os.path.join(portable_dir, "openslides",
|
||||
"database.sqlite")
|
||||
else:
|
||||
import ctypes
|
||||
exename = os.path.basename(sys.executable).lower()
|
||||
if exename != "openslides.exe":
|
||||
raise Exception(
|
||||
"Cannot determine portable path when "
|
||||
"not running as portable")
|
||||
|
||||
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
|
||||
@ -307,13 +368,8 @@ 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")
|
||||
|
||||
main(argv, opt_defaults={ "settings": default_settings },
|
||||
database_path=database_path)
|
||||
return os.path.join(buf.value, *args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -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,7 +46,7 @@ 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(
|
||||
@ -59,7 +59,7 @@ class MotionImportForm(forms.Form, CssClassMixin):
|
||||
|
||||
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")
|
||||
|
@ -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
|
||||
|
||||
|
||||
@ -69,7 +64,7 @@ class Motion(models.Model, SlideMixin):
|
||||
number = models.PositiveSmallIntegerField(blank=True, null=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,16 +215,19 @@ 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; "
|
||||
self.writelog(
|
||||
_("Trivial changes to version %(version)d; "
|
||||
"changed fields: %(changed_fields)s")
|
||||
% dict(version = last_version.aid,
|
||||
changed_fields = ", ".join(field_names)))
|
||||
% dict(version=last_version.aid,
|
||||
changed_fields=", ".join(field_names)))
|
||||
return # Done
|
||||
|
||||
version = AVersion(title=getattr(self, 'title', ''),
|
||||
version = AVersion(
|
||||
title=getattr(self, 'title', ''),
|
||||
text=getattr(self, 'text', ''),
|
||||
reason=getattr(self, 'reason', ''),
|
||||
motion=self)
|
||||
@ -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,9 +536,9 @@ 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"))
|
||||
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)
|
||||
|
||||
|
@ -23,14 +23,12 @@ except ImportError: # python <= 2.5
|
||||
|
||||
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)
|
||||
@ -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:
|
||||
|
@ -14,15 +14,20 @@
|
||||
from __future__ import with_statement
|
||||
|
||||
from random import choice
|
||||
import string
|
||||
import csv
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.db import transaction
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from openslides.utils import csv_ext
|
||||
|
||||
from openslides.participant.models import User
|
||||
from openslides.participant.models import User, Group
|
||||
|
||||
|
||||
DEFAULT_PERMS = ['can_see_agenda', 'can_see_projector',
|
||||
'can_see_motion', 'can_see_assignment',
|
||||
'can_see_dashboard']
|
||||
|
||||
|
||||
def gen_password():
|
||||
@ -73,7 +78,7 @@ def import_users(csv_file):
|
||||
try:
|
||||
(first_name, last_name, gender, structure_level, type, committee, comment) = line[:7]
|
||||
except ValueError:
|
||||
error_messages.append(_('Ignoring malformed line %d in import file.') % line_no + 1)
|
||||
error_messages.append(_('Ignoring malformed line %d in import file.') % (line_no + 1))
|
||||
continue
|
||||
user = User()
|
||||
user.last_name = last_name
|
||||
@ -93,3 +98,23 @@ def import_users(csv_file):
|
||||
except UnicodeDecodeError:
|
||||
error_messages.appen(_('Import file has wrong character encoding, only UTF-8 is supported!'))
|
||||
return (count_success, error_messages)
|
||||
|
||||
|
||||
def get_or_create_registered_group():
|
||||
registered, created = Group.objects.get_or_create(
|
||||
name__iexact='Registered', defaults={'name': 'Registered'})
|
||||
if created:
|
||||
registered.permissions = Permission.objects.filter(
|
||||
codename__in=DEFAULT_PERMS)
|
||||
registered.save()
|
||||
return registered
|
||||
|
||||
|
||||
def get_or_create_anonymous_group():
|
||||
anonymous, created = Group.objects.get_or_create(
|
||||
name__iexact='Anonymous', defaults={'name': 'Anonymous'})
|
||||
if created:
|
||||
anonymous.permissions = Permission.objects.filter(
|
||||
codename__in=DEFAULT_PERMS)
|
||||
anonymous.save()
|
||||
return anonymous
|
||||
|
@ -18,6 +18,7 @@ from openslides.utils.forms import (
|
||||
CssClassMixin, LocalizedModelMultipleChoiceField)
|
||||
|
||||
from openslides.participant.models import User, Group
|
||||
from openslides.participant.api import get_or_create_registered_group
|
||||
|
||||
|
||||
class UserCreateForm(forms.ModelForm, CssClassMixin):
|
||||
@ -25,6 +26,13 @@ class UserCreateForm(forms.ModelForm, CssClassMixin):
|
||||
queryset=Group.objects.exclude(name__iexact='anonymous'),
|
||||
label=_("Groups"), required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if kwargs.get('instance', None) is None:
|
||||
initial = kwargs.setdefault('initial', {})
|
||||
registered = get_or_create_registered_group()
|
||||
initial['groups'] = [registered.pk]
|
||||
super(UserCreateForm, self).__init__(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('first_name', 'last_name', 'is_active', 'groups', 'structure_level',
|
||||
@ -58,6 +66,7 @@ class GroupForm(forms.ModelForm, CssClassMixin):
|
||||
instance = forms.ModelForm.save(self, False)
|
||||
|
||||
old_save_m2m = self.save_m2m
|
||||
|
||||
def save_m2m():
|
||||
old_save_m2m()
|
||||
|
||||
@ -76,13 +85,13 @@ class GroupForm(forms.ModelForm, CssClassMixin):
|
||||
# Do not allow to change the name "anonymous" or give another group
|
||||
# this name
|
||||
data = self.cleaned_data['name']
|
||||
if self.instance.name.lower() == 'anonymous':
|
||||
if self.instance.name.lower() in ['anonymous', 'registered']:
|
||||
# Editing the anonymous-user
|
||||
if self.instance.name.lower() != data.lower():
|
||||
raise forms.ValidationError(
|
||||
_('You can not edit the name for the anonymous user'))
|
||||
_('You can not edit the name for this group.'))
|
||||
else:
|
||||
if data.lower() == 'anonymous':
|
||||
if data.lower() in ['anonymous', 'registered']:
|
||||
raise forms.ValidationError(
|
||||
_('Group name "%s" is reserved for internal use.') % data)
|
||||
return data
|
||||
@ -94,7 +103,7 @@ class GroupForm(forms.ModelForm, CssClassMixin):
|
||||
class UsersettingsForm(forms.ModelForm, CssClassMixin):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('username', 'first_name', 'last_name', 'gender', 'email', 'committee', 'about_me' )
|
||||
fields = ('username', 'first_name', 'last_name', 'gender', 'email', 'committee', 'about_me')
|
||||
|
||||
|
||||
class UserImportForm(forms.Form, CssClassMixin):
|
||||
|
@ -25,6 +25,7 @@ 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
|
||||
person_prefix = 'user'
|
||||
@ -131,12 +132,15 @@ class User(DjangoUser, PersonMixin, Person, SlideMixin):
|
||||
|
||||
register_slidemodel(User)
|
||||
|
||||
|
||||
class Group(DjangoGroup, PersonMixin, Person, SlideMixin):
|
||||
prefix = 'group' # This is for the slides
|
||||
person_prefix = 'group'
|
||||
|
||||
django_group = models.OneToOneField(DjangoGroup, editable=False, parent_link=True)
|
||||
group_as_person = models.BooleanField(default=False, verbose_name=_("Use this group as participant"), help_text=_('For example as submitter of a motion.'))
|
||||
group_as_person = models.BooleanField(
|
||||
default=False, verbose_name=_("Use this group as participant"),
|
||||
help_text=_('For example as submitter of a motion.'))
|
||||
description = models.TextField(blank=True, verbose_name=_("Description"))
|
||||
|
||||
@models.permalink
|
||||
@ -173,6 +177,7 @@ class Group(DjangoGroup, PersonMixin, Person, SlideMixin):
|
||||
|
||||
register_slidemodel(Group)
|
||||
|
||||
|
||||
class UsersAndGroupsToPersons(object):
|
||||
"""
|
||||
Object to send all Users and Groups or a special User or Group to
|
||||
@ -216,7 +221,8 @@ def receive_persons(sender, **kwargs):
|
||||
"""
|
||||
Answers to the Person-API
|
||||
"""
|
||||
return UsersAndGroupsToPersons(person_prefix_filter=kwargs['person_prefix_filter'],
|
||||
return UsersAndGroupsToPersons(
|
||||
person_prefix_filter=kwargs['person_prefix_filter'],
|
||||
id_filter=kwargs['id_filter'])
|
||||
|
||||
|
||||
@ -234,7 +240,7 @@ def default_config(sender, key, **kwargs):
|
||||
|
||||
|
||||
@receiver(signals.post_save, sender=DjangoUser)
|
||||
def user_post_save(sender, instance, signal, *args, **kwargs):
|
||||
def djangouser_post_save(sender, instance, signal, *args, **kwargs):
|
||||
try:
|
||||
instance.user
|
||||
except User.DoesNotExist:
|
||||
@ -242,8 +248,18 @@ def user_post_save(sender, instance, signal, *args, **kwargs):
|
||||
|
||||
|
||||
@receiver(signals.post_save, sender=DjangoGroup)
|
||||
def group_post_save(sender, instance, signal, *args, **kwargs):
|
||||
def djangogroup_post_save(sender, instance, signal, *args, **kwargs):
|
||||
try:
|
||||
instance.group
|
||||
except Group.DoesNotExist:
|
||||
Group(django_group=instance).save_base(raw=True)
|
||||
|
||||
|
||||
@receiver(signals.post_save, sender=User)
|
||||
def user_post_save(sender, instance, *args, **kwargs):
|
||||
from openslides.participant.api import get_or_create_registered_group
|
||||
if not kwargs['created']:
|
||||
return
|
||||
registered = get_or_create_registered_group()
|
||||
instance.groups.add(registered)
|
||||
instance.save()
|
||||
|
@ -17,7 +17,7 @@
|
||||
<tr class="{% cycle '' 'odd' %}">
|
||||
<td><a href="{% model_url group 'view' %}">{{ group.name }}</a></td>
|
||||
<td><a href="{% url user_group_edit group.id %}"><img src="{% static 'images/icons/edit.png' %}" title="{% trans 'Edit group' %}"></a>
|
||||
{% if group.name|lower != 'anonymous' %}
|
||||
{% if group.name|lower != 'anonymous' and group.name|lower != 'registered' %}
|
||||
<a href="{% url user_group_delete group.id %}"><img src="{% static 'images/icons/delete.png' %}" title="{% trans 'Delete group' %}"></a>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
||||
@ -574,7 +573,7 @@ def get_personal_info_widget(request):
|
||||
'supported_motions': Motion.objects.filter(motionsupporter=request.user),
|
||||
'assignments': Assignment.objects.filter(
|
||||
assignmentcandidate__person=request.user,
|
||||
assignmentcandidate__blocked=False),}
|
||||
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)
|
||||
|
@ -11,7 +11,6 @@
|
||||
"""
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from openslides.utils.forms import CssClassMixin
|
||||
|
||||
|
@ -164,7 +164,6 @@ class BasePoll(models.Model):
|
||||
"""
|
||||
return self.vote_values
|
||||
|
||||
|
||||
def get_vote_class(self):
|
||||
"""
|
||||
Return the releatet vote class.
|
||||
|
@ -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:
|
||||
|
@ -11,7 +11,6 @@
|
||||
"""
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from openslides.utils.forms import CssClassMixin
|
||||
|
||||
|
@ -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):
|
||||
|
@ -19,9 +19,9 @@ from openslides.config.models import config
|
||||
|
||||
from openslides.projector.signals import projector_overlays
|
||||
|
||||
|
||||
SLIDE = {}
|
||||
|
||||
|
||||
class SlideMixin(object):
|
||||
"""
|
||||
A Mixin for a Django-Model, for making the model a slide.
|
||||
@ -49,13 +49,16 @@ class SlideMixin(object):
|
||||
"""
|
||||
Return True, if the the slide is the active slide.
|
||||
"""
|
||||
from api import get_active_slide
|
||||
if self.id is None:
|
||||
return False
|
||||
from openslides.projector.api import get_active_slide
|
||||
return get_active_slide(only_sid=True) == self.sid
|
||||
|
||||
def set_active(self):
|
||||
"""
|
||||
Appoint this item as the active slide.
|
||||
"""
|
||||
from openslides.projector.api import set_active_slide
|
||||
set_active_slide(self.sid)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
@ -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,
|
||||
'overlays': overlays,
|
||||
'countdown_time': config['countdown_time'],
|
||||
'countdown_state' : config['countdown_state']}
|
||||
'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'),
|
||||
|
@ -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<path>.*)$', 'serve', {'insecure':True}),
|
||||
url(r'^static/(?P<path>.*)$', 'serve', {'insecure': True}),
|
||||
)
|
||||
|
||||
js_info_dict = {
|
||||
|
@ -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']}
|
||||
|
@ -31,4 +31,3 @@ def patchup(dialect):
|
||||
return dialect
|
||||
|
||||
register_dialect("excel_semikolon", excel_semikolon)
|
||||
|
||||
|
@ -1 +1,3 @@
|
||||
from fields import JSONField
|
||||
|
||||
__all__ = ['JSONField']
|
||||
|
@ -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"""
|
||||
|
||||
|
@ -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)
|
@ -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)
|
||||
|
@ -28,16 +28,16 @@ from openslides.config.models import config
|
||||
|
||||
|
||||
# register new truetype fonts
|
||||
pdfmetrics.registerFont(TTFont('Ubuntu', path_join(settings.SITE_ROOT,
|
||||
'static/fonts/Ubuntu-R.ttf')))
|
||||
pdfmetrics.registerFont(TTFont('Ubuntu-Bold', path_join(settings.SITE_ROOT,
|
||||
'static/fonts/Ubuntu-B.ttf')))
|
||||
pdfmetrics.registerFont(TTFont('Ubuntu-Italic', path_join(settings.SITE_ROOT,
|
||||
'static/fonts/Ubuntu-RI.ttf')))
|
||||
pdfmetrics.registerFont(TTFont(
|
||||
'Ubuntu', path_join(settings.SITE_ROOT, 'static/fonts/Ubuntu-R.ttf')))
|
||||
pdfmetrics.registerFont(TTFont(
|
||||
'Ubuntu-Bold', path_join(settings.SITE_ROOT, 'static/fonts/Ubuntu-B.ttf')))
|
||||
pdfmetrics.registerFont(TTFont(
|
||||
'Ubuntu-Italic', path_join(settings.SITE_ROOT, 'static/fonts/Ubuntu-RI.ttf')))
|
||||
|
||||
|
||||
# set style information
|
||||
PAGE_HEIGHT = defaultPageSize[1];
|
||||
PAGE_HEIGHT = defaultPageSize[1]
|
||||
PAGE_WIDTH = defaultPageSize[0]
|
||||
|
||||
|
||||
@ -105,104 +105,104 @@ stylesheet.add(ParagraphStyle(
|
||||
leftIndent=0,
|
||||
spaceAfter=15,
|
||||
))
|
||||
stylesheet.add(ParagraphStyle(name = 'Subitem',
|
||||
parent = stylesheet['Normal'],
|
||||
fontSize = 10,
|
||||
leading = 10,
|
||||
leftIndent = 20,
|
||||
spaceAfter = 15)
|
||||
)
|
||||
stylesheet.add(ParagraphStyle(name = 'Tablecell',
|
||||
parent = stylesheet['Normal'],
|
||||
fontSize = 9)
|
||||
)
|
||||
stylesheet.add(ParagraphStyle(name = 'Signaturefield',
|
||||
parent = stylesheet['Normal'],
|
||||
spaceBefore = 15)
|
||||
stylesheet.add(ParagraphStyle(
|
||||
name='Subitem',
|
||||
parent=stylesheet['Normal'],
|
||||
fontSize=10,
|
||||
leading=10,
|
||||
leftIndent=20,
|
||||
spaceAfter=15))
|
||||
stylesheet.add(ParagraphStyle(
|
||||
name='Tablecell',
|
||||
parent=stylesheet['Normal'],
|
||||
fontSize=9))
|
||||
stylesheet.add(ParagraphStyle(name='Signaturefield',
|
||||
parent=stylesheet['Normal'],
|
||||
spaceBefore=15)
|
||||
)
|
||||
|
||||
# Ballot stylesheets
|
||||
stylesheet.add(ParagraphStyle(name = 'Ballot_title',
|
||||
parent = stylesheet['Bold'],
|
||||
fontSize = 12,
|
||||
leading = 14,
|
||||
leftIndent = 30),
|
||||
stylesheet.add(ParagraphStyle(name='Ballot_title',
|
||||
parent=stylesheet['Bold'],
|
||||
fontSize=12,
|
||||
leading=14,
|
||||
leftIndent=30),
|
||||
)
|
||||
stylesheet.add(ParagraphStyle(name = 'Ballot_subtitle',
|
||||
parent = stylesheet['Normal'],
|
||||
fontSize = 10,
|
||||
leading = 12,
|
||||
leftIndent = 30,
|
||||
rightIndent = 20,
|
||||
spaceAfter = 5),
|
||||
stylesheet.add(ParagraphStyle(name='Ballot_subtitle',
|
||||
parent=stylesheet['Normal'],
|
||||
fontSize=10,
|
||||
leading=12,
|
||||
leftIndent=30,
|
||||
rightIndent=20,
|
||||
spaceAfter=5),
|
||||
)
|
||||
stylesheet.add(ParagraphStyle(name = 'Ballot_description',
|
||||
parent = stylesheet['Normal'],
|
||||
fontSize = 7,
|
||||
leading = 10,
|
||||
leftIndent = 30),
|
||||
stylesheet.add(ParagraphStyle(name='Ballot_description',
|
||||
parent=stylesheet['Normal'],
|
||||
fontSize=7,
|
||||
leading=10,
|
||||
leftIndent=30),
|
||||
)
|
||||
stylesheet.add(ParagraphStyle(name = 'Ballot_option',
|
||||
parent = stylesheet['Normal'],
|
||||
fontSize = 12,
|
||||
leading = 24,
|
||||
leftIndent = 30),
|
||||
stylesheet.add(ParagraphStyle(name='Ballot_option',
|
||||
parent=stylesheet['Normal'],
|
||||
fontSize=12,
|
||||
leading=24,
|
||||
leftIndent=30),
|
||||
)
|
||||
stylesheet.add(ParagraphStyle(name = 'Monotype',
|
||||
parent = stylesheet['Normal'],
|
||||
fontName = 'Courier',
|
||||
fontSize = 12,
|
||||
leading = 24,
|
||||
leftIndent = 30),
|
||||
stylesheet.add(ParagraphStyle(name='Monotype',
|
||||
parent=stylesheet['Normal'],
|
||||
fontName='Courier',
|
||||
fontSize=12,
|
||||
leading=24,
|
||||
leftIndent=30),
|
||||
)
|
||||
stylesheet.add(ParagraphStyle(name = 'Ballot_option_name',
|
||||
parent = stylesheet['Normal'],
|
||||
fontSize = 12,
|
||||
leading = 15,
|
||||
leftIndent = 30),
|
||||
stylesheet.add(ParagraphStyle(name='Ballot_option_name',
|
||||
parent=stylesheet['Normal'],
|
||||
fontSize=12,
|
||||
leading=15,
|
||||
leftIndent=30),
|
||||
)
|
||||
stylesheet.add(ParagraphStyle(name = 'Ballot_option_group',
|
||||
parent = stylesheet['Normal'],
|
||||
fontSize = 8,
|
||||
leading = 15,
|
||||
leftIndent = 30),
|
||||
stylesheet.add(ParagraphStyle(name='Ballot_option_group',
|
||||
parent=stylesheet['Normal'],
|
||||
fontSize=8,
|
||||
leading=15,
|
||||
leftIndent=30),
|
||||
)
|
||||
stylesheet.add(ParagraphStyle(name = 'Ballot_option_YNA',
|
||||
parent = stylesheet['Normal'],
|
||||
fontSize = 12,
|
||||
leading = 15,
|
||||
leftIndent = 49,
|
||||
spaceAfter = 18),
|
||||
stylesheet.add(ParagraphStyle(name='Ballot_option_YNA',
|
||||
parent=stylesheet['Normal'],
|
||||
fontSize=12,
|
||||
leading=15,
|
||||
leftIndent=49,
|
||||
spaceAfter=18),
|
||||
)
|
||||
stylesheet.add(ParagraphStyle(name = 'Ballot_option_group_right',
|
||||
parent = stylesheet['Normal'],
|
||||
fontSize = 8,
|
||||
leading = 16,
|
||||
leftIndent = 49),
|
||||
stylesheet.add(ParagraphStyle(name='Ballot_option_group_right',
|
||||
parent=stylesheet['Normal'],
|
||||
fontSize=8,
|
||||
leading=16,
|
||||
leftIndent=49),
|
||||
)
|
||||
stylesheet.add(ParagraphStyle(name = 'Badge_title',
|
||||
parent = stylesheet['Bold'],
|
||||
fontSize = 16,
|
||||
leading = 22,
|
||||
leftIndent = 30),
|
||||
stylesheet.add(ParagraphStyle(name='Badge_title',
|
||||
parent=stylesheet['Bold'],
|
||||
fontSize=16,
|
||||
leading=22,
|
||||
leftIndent=30),
|
||||
)
|
||||
stylesheet.add(ParagraphStyle(name = 'Badge_subtitle',
|
||||
parent = stylesheet['Normal'],
|
||||
fontSize = 12,
|
||||
leading = 24,
|
||||
leftIndent = 30),
|
||||
stylesheet.add(ParagraphStyle(name='Badge_subtitle',
|
||||
parent=stylesheet['Normal'],
|
||||
fontSize=12,
|
||||
leading=24,
|
||||
leftIndent=30),
|
||||
)
|
||||
stylesheet.add(ParagraphStyle(
|
||||
name = 'Badge_italic',
|
||||
parent = stylesheet['Italic'],
|
||||
fontSize = 12,
|
||||
leading = 24,
|
||||
leftIndent = 30,
|
||||
name='Badge_italic',
|
||||
parent=stylesheet['Italic'],
|
||||
fontSize=12,
|
||||
leading=24,
|
||||
leftIndent=30,
|
||||
))
|
||||
stylesheet.add(ParagraphStyle(
|
||||
name = 'Badge_qrcode',
|
||||
fontSize = 12,
|
||||
leftIndent = 190,
|
||||
name='Badge_qrcode',
|
||||
fontSize=12,
|
||||
leftIndent=190,
|
||||
))
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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__'):
|
||||
|
@ -10,8 +10,7 @@
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.template import loader, Context, RequestContext, TextNode
|
||||
from django.template import loader, Context
|
||||
from django.template.loader_tags import BlockNode, ExtendsNode
|
||||
|
||||
|
||||
@ -24,6 +23,10 @@ class Tab(object):
|
||||
self.url = url
|
||||
|
||||
|
||||
## All following function are only needed to render a block from a template
|
||||
## and could be removed, if the template worked with an include-statement instead.
|
||||
## Its only used for ajax-request from the projector.
|
||||
|
||||
def get_template(template):
|
||||
if isinstance(template, (tuple, list)):
|
||||
return loader.select_template(template)
|
||||
@ -49,15 +52,15 @@ 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
|
||||
@ -77,23 +80,3 @@ def render_block_to_string(template_name, block, dictionary=None,
|
||||
context_instance = Context(dictionary)
|
||||
t.render(context_instance)
|
||||
return render_template_block(t, block, context_instance)
|
||||
|
||||
|
||||
def direct_block_to_template(request, template, block, extra_context=None,
|
||||
mimetype=None, **kwargs):
|
||||
"""
|
||||
Render a given block in a given template with any extra URL parameters in
|
||||
the context as ``{{ params }}``.
|
||||
"""
|
||||
if extra_context is None:
|
||||
extra_context = {}
|
||||
dictionary = {'params': kwargs}
|
||||
for key, value in extra_context.items():
|
||||
if callable(value):
|
||||
dictionary[key] = value()
|
||||
else:
|
||||
dictionary[key] = value
|
||||
c = RequestContext(request, dictionary)
|
||||
t = get_template(template)
|
||||
t.render(c)
|
||||
return HttpResponse(render_template_block(t, block, c), mimetype=mimetype)
|
||||
|
@ -10,12 +10,13 @@
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
try:
|
||||
import json
|
||||
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,7 +42,8 @@ def gen_confirm_form(request, message, url):
|
||||
|
||||
Deprecated. Use Class base Views instead.
|
||||
"""
|
||||
messages.warning(request,
|
||||
messages.warning(
|
||||
request,
|
||||
"""
|
||||
%s
|
||||
<form action="%s" method="post">
|
||||
@ -63,27 +65,28 @@ def del_confirm_form(request, object, name=None, delete_link=None):
|
||||
name = object
|
||||
if delete_link is None:
|
||||
delete_link = object.get_absolute_url('delete')
|
||||
gen_confirm_form(request, _('Do you really want to delete %s?')
|
||||
gen_confirm_form(
|
||||
request, _('Do you really want to delete %s?')
|
||||
% html_strong(name), delete_link)
|
||||
|
||||
|
||||
def render_response(req, *args, **kwargs):
|
||||
kwargs['context_instance'] = RequestContext(req)
|
||||
return render_to_response(*args, **kwargs)
|
||||
|
||||
|
||||
def template(template_name):
|
||||
"""
|
||||
Decorator to set a template for a view.
|
||||
|
||||
Deprecated. Use class based views instead.
|
||||
"""
|
||||
def renderer(func):
|
||||
def wrapper(request, *args, **kwargs):
|
||||
output = func(request, *args, **kwargs)
|
||||
if not isinstance(output, dict):
|
||||
return output
|
||||
context = {}
|
||||
template_manipulation.send(sender='utils_template', request=request,
|
||||
context=context)
|
||||
template_manipulation.send(
|
||||
sender='utils_template', request=request, context=context)
|
||||
output.update(context)
|
||||
response = render_to_response(template_name, output,
|
||||
context_instance=RequestContext(request))
|
||||
response = render_to_response(
|
||||
template_name, output, context_instance=RequestContext(request))
|
||||
if 'cookie' in output:
|
||||
response.set_cookie(output['cookie'][0], output['cookie'][1])
|
||||
return response
|
||||
@ -95,6 +98,8 @@ def permission_required(perm, login_url=None):
|
||||
"""
|
||||
Decorator for views that checks whether a user has a particular permission
|
||||
enabled, redirecting to the log-in page if necessary.
|
||||
|
||||
Deprecated.
|
||||
"""
|
||||
def renderer(func):
|
||||
def wrapper(request, *args, **kw):
|
||||
@ -107,10 +112,12 @@ def permission_required(perm, login_url=None):
|
||||
return renderer
|
||||
|
||||
|
||||
def render_to_forbidden(request, error=
|
||||
ugettext_lazy("Sorry, you have no rights to see this page.")):
|
||||
return HttpResponseForbidden(render_to_string('403.html',
|
||||
{'error': error}, context_instance=RequestContext(request)))
|
||||
def render_to_forbidden(request,
|
||||
error=ugettext_lazy("Sorry, you have no rights to see this page.")):
|
||||
# TODO: Integrate this function into the PermissionMixin once the
|
||||
# above function is deleted.
|
||||
return HttpResponseForbidden(render_to_string(
|
||||
'403.html', {'error': error}, context_instance=RequestContext(request)))
|
||||
|
||||
|
||||
def delete_default_permissions(**kwargs):
|
||||
@ -118,16 +125,18 @@ def delete_default_permissions(**kwargs):
|
||||
Deletes the permissions, django creates by default for the admin.
|
||||
"""
|
||||
for p in Permission.objects.all():
|
||||
if p.codename.startswith('add') \
|
||||
or p.codename.startswith('delete') \
|
||||
or p.codename.startswith('change'):
|
||||
if (p.codename.startswith('add') or
|
||||
p.codename.startswith('delete') or
|
||||
p.codename.startswith('change')):
|
||||
p.delete()
|
||||
|
||||
|
||||
def ajax_request(data):
|
||||
"""
|
||||
generates a HTTPResponse-Object with json-Data for a
|
||||
ajax response
|
||||
ajax response.
|
||||
|
||||
Deprecated.
|
||||
"""
|
||||
return HttpResponse(json.dumps(data))
|
||||
|
||||
|
@ -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,15 +127,15 @@ class QuestionMixin(object):
|
||||
option_fields = "\n".join([
|
||||
'<input type="submit" name="%s" value="%s">' % (option[0], unicode(option[1]))
|
||||
for option in self.get_answer_options()])
|
||||
messages.warning(self.request,
|
||||
messages.warning(
|
||||
self.request,
|
||||
"""
|
||||
%(message)s
|
||||
<form action="%(url)s" method="post">
|
||||
<input type="hidden" value="%(csrf)s" name="csrfmiddlewaretoken">
|
||||
%(option_fields)s
|
||||
</form>
|
||||
""" % {
|
||||
'message': self.get_question(),
|
||||
""" % {'message': self.get_question(),
|
||||
'url': self.get_answer_url(),
|
||||
'csrf': csrf(self.request)['csrf_token'],
|
||||
'option_fields': option_fields})
|
||||
@ -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")
|
||||
|
5
requirements.txt
Normal file
5
requirements.txt
Normal file
@ -0,0 +1,5 @@
|
||||
Django==1.4.2
|
||||
django-mptt
|
||||
reportlab
|
||||
PIL
|
||||
simplejson
|
6
setup.py
6
setup.py
@ -12,15 +12,19 @@ from setuptools import find_packages
|
||||
from openslides import get_version
|
||||
|
||||
|
||||
with open('README.txt') as file:
|
||||
long_description = file.read()
|
||||
|
||||
setup(
|
||||
name='openslides',
|
||||
description='Presentation-System',
|
||||
long_description=long_description,
|
||||
version=get_version(),
|
||||
url='http://openslides.org',
|
||||
author='OpenSlides-Team',
|
||||
author_email='support@openslides.org',
|
||||
license='GPL2+',
|
||||
packages=find_packages(),
|
||||
packages=find_packages(exclude=['tests']),
|
||||
include_package_data = True,
|
||||
classifiers = [
|
||||
# http://pypi.python.org/pypi?%3Aaction=list_classifiers
|
||||
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
0
tests/agenda/__init__.py
Normal file
0
tests/agenda/__init__.py
Normal file
18
tests/agenda/models.py
Normal file
18
tests/agenda/models.py
Normal file
@ -0,0 +1,18 @@
|
||||
from django.db import models
|
||||
|
||||
from openslides.projector.projector import SlideMixin
|
||||
from openslides.projector.api import register_slidemodel
|
||||
|
||||
|
||||
class ReleatedItem(SlideMixin, models.Model):
|
||||
prefix = 'releateditem'
|
||||
|
||||
name = models.CharField(max_length='255')
|
||||
|
||||
def get_agenda_title(self):
|
||||
return self.name
|
||||
|
||||
def get_agenda_title_supplement(self):
|
||||
return 'test item'
|
||||
|
||||
register_slidemodel(ReleatedItem)
|
@ -12,12 +12,15 @@
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test.client import Client
|
||||
from django.contrib.auth.models import User
|
||||
from django.db.models.query import EmptyQuerySet
|
||||
|
||||
from openslides.projector.api import get_active_slide
|
||||
|
||||
from openslides.participant.models import User
|
||||
from openslides.agenda.models import Item
|
||||
from openslides.agenda.slides import agenda_show
|
||||
|
||||
from .models import ReleatedItem
|
||||
|
||||
|
||||
class ItemTest(TestCase):
|
||||
def setUp(self):
|
||||
@ -25,6 +28,8 @@ class ItemTest(TestCase):
|
||||
self.item2 = Item.objects.create(title='item2')
|
||||
self.item3 = Item.objects.create(title='item1A', parent=self.item1)
|
||||
self.item4 = Item.objects.create(title='item1Aa', parent=self.item3)
|
||||
self.releated = ReleatedItem.objects.create(name='foo')
|
||||
self.item5 = Item.objects.create(title='item5', related_sid=self.releated.sid)
|
||||
|
||||
def testClosed(self):
|
||||
self.assertFalse(self.item1.closed)
|
||||
@ -46,10 +51,6 @@ class ItemTest(TestCase):
|
||||
self.assertTrue(self.item3 in self.item1.get_children())
|
||||
self.assertFalse(self.item4 in self.item1.get_children())
|
||||
|
||||
l = Item.objects.all()
|
||||
self.assertEqual(str(l),
|
||||
"[<Item: item1>, <Item: item1A>, <Item: item1Aa>, <Item: item2>]")
|
||||
|
||||
def testForms(self):
|
||||
for item in Item.objects.all():
|
||||
initial = item.weight_form.initial
|
||||
@ -64,6 +65,36 @@ class ItemTest(TestCase):
|
||||
self.item1.related_sid = 'foobar'
|
||||
self.assertFalse(self.item1.get_related_slide() is None)
|
||||
|
||||
def test_title_supplement(self):
|
||||
self.assertEqual(self.item1.get_title_supplement(), '')
|
||||
|
||||
def test_delete_item(self):
|
||||
new_item1 = Item.objects.create()
|
||||
new_item2 = Item.objects.create(parent=new_item1)
|
||||
new_item3 = Item.objects.create(parent=new_item2)
|
||||
new_item1.delete()
|
||||
self.assertTrue(new_item3 in Item.objects.all())
|
||||
new_item2.delete(with_children=True)
|
||||
self.assertFalse(new_item3 in Item.objects.all())
|
||||
|
||||
def test_absolute_url(self):
|
||||
self.assertEqual(self.item1.get_absolute_url(), '/agenda/1/')
|
||||
self.assertEqual(self.item1.get_absolute_url('edit'), '/agenda/1/edit/')
|
||||
self.assertEqual(self.item1.get_absolute_url('delete'), '/agenda/1/del/')
|
||||
|
||||
def test_agenda_slide(self):
|
||||
data = agenda_show()
|
||||
self.assertEqual(list(data['items']), list(Item.objects.all().filter(parent=None)))
|
||||
self.assertEqual(data['template'], 'projector/AgendaSummary.html')
|
||||
self.assertEqual(data['title'], 'Agenda')
|
||||
|
||||
def test_releated_item(self):
|
||||
self.assertEqual(self.item5.get_title(), self.releated.name)
|
||||
self.assertEqual(self.item5.get_title_supplement(), 'test item')
|
||||
self.assertEqual(self.item5.get_related_type(), 'releateditem')
|
||||
self.assertEqual(self.item5.print_related_type(), 'Releateditem')
|
||||
|
||||
|
||||
|
||||
class ViewTest(TestCase):
|
||||
def setUp(self):
|
||||
@ -71,8 +102,10 @@ class ViewTest(TestCase):
|
||||
self.item2 = Item.objects.create(title='item2')
|
||||
self.refreshItems()
|
||||
|
||||
self.admin = User.objects.create_user('testadmin', '', 'default')
|
||||
self.anonym = User.objects.create_user('testanoym', '', 'default')
|
||||
self.admin, created = User.objects.get_or_create(username='testadmin')
|
||||
self.anonym, created = User.objects.get_or_create(username='testanonym')
|
||||
self.admin.reset_password('default')
|
||||
self.anonym.reset_password('default')
|
||||
|
||||
self.admin.is_superuser = True
|
||||
self.admin.save()
|
||||
@ -91,6 +124,13 @@ class ViewTest(TestCase):
|
||||
def anonymClient(self):
|
||||
return Client()
|
||||
|
||||
def testOverview(self):
|
||||
c = self.adminClient
|
||||
|
||||
response = c.get('/agenda/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(len(response.context['items']), len(Item.objects.all()))
|
||||
|
||||
def testActivate(self):
|
||||
c = self.adminClient
|
||||
|
||||
@ -122,6 +162,14 @@ class ViewTest(TestCase):
|
||||
self.refreshItems()
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
# Test ajax
|
||||
response = c.get('/agenda/%d/close/' % self.item1.id,
|
||||
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = c.get('/agenda/%d/open/' % self.item1.id,
|
||||
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def testEdit(self):
|
||||
c = self.adminClient
|
||||
|
||||
@ -131,7 +179,7 @@ class ViewTest(TestCase):
|
||||
response = c.get('/agenda/%d/edit/' % 1000)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
data = {'title': 'newitem1', 'text': 'item1-text', 'weight':'0'}
|
||||
data = {'title': 'newitem1', 'text': 'item1-text', 'weight': '0'}
|
||||
response = c.post('/agenda/%d/edit/' % self.item1.id, data)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.refreshItems()
|
||||
@ -143,4 +191,3 @@ class ViewTest(TestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.refreshItems()
|
||||
self.assertEqual(self.item1.title, 'newitem1')
|
||||
|
23
tests/test_init.py
Normal file
23
tests/test_init.py
Normal file
@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Unit test for OpenSlides __init__.py
|
||||
|
||||
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from openslides import get_version
|
||||
|
||||
class InitTest(TestCase):
|
||||
def test_get_version(self):
|
||||
self.assertEqual(get_version((1, 3, 0, 'beta', 2)), '1.3-beta2')
|
||||
self.assertEqual(get_version((1, 0, 0, 'final', 0)), '1.0')
|
||||
self.assertEqual(get_version((2, 5, 3, 'alpha', 0)), '2.5.3-alpha0')
|
||||
git_version = get_version((2, 5, 0, 'dev', 0))
|
||||
if 'unknown' in git_version:
|
||||
self.assertEqual(len(git_version), 14)
|
||||
else:
|
||||
self.assertEqual(len(git_version), 47)
|
@ -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])
|
||||
|
@ -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)
|
Loading…
Reference in New Issue
Block a user