Merge branch 'master' into Version_String

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

4
.coveragerc Normal file
View File

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

6
.gitignore vendored
View File

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

12
.travis.yml Normal file
View File

@ -0,0 +1,12 @@
language: python
python:
- "2.5"
- "2.6"
- "2.7"
install:
- pip install -r requirements.txt --use-mirrors
- pip install coverage django-discover-runner pep8
- python extras/scripts/create_local_settings.py
script:
- coverage run ./manage.py test tests && coverage report -m
- pep8 --max-line-length=150 --exclude="urls.py,motion/" --statistics openslides

View File

@ -5,3 +5,5 @@ Authors of OpenSlides in chronological order of first contribution:
Norman Jäckel <mail@normanjaeckel.de>
René Köcher <shirk@bitspin.org>
Andy Kittner <andkit@gmx.net>
Moira Brülisauer <moira.bruelisauer@piratenpartei.ch> (French translation)
Alexis Roussel <alexis.roussel@partipirate.ch> (French translation)

View File

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

View File

@ -3,15 +3,56 @@ Installation Instructions for OpenSlides 1.3
Content
-------
I. Installation on Windows (32/64bit)
II. Installation on GNU/Linux and MacOSX
I. Installation on GNU/Linux and MacOSX
II. Installation on Windows (32/64bit)
If you need help ask on OpenSlides users mailing list.
See http://openslides.org for more information.
I. Installation on Windows (32/64bit)
-------------------------------------
I. Installation on GNU/Linux and MacOSX
----------------------------------------
Make sure that you have installed Python (>= 2.5) on your system.
You can setup a virtualenv environment to install OpenSlides as non-root user.
Run before you start install OpenSlides:
$ virtualenv .venv
$ source .venv/bin/activate
1. Install OpenSlides:
$ pip install openslides
OpenSlides will installed the following required python packages:
+ Django
+ django-mptt
+ ReportLab Toolkit
+ Python Imaging Library (PIL)
2. Start OpenSlides server and open URL in your default browser:
$ openslides
If you run this command the first time a new database and the
admin account are created. Please change the password after
first login!
Username: admin
Password: admin
Use 'openslides --help' to show all start options.
II. Installation on Windows (32/64bit)
--------------------------------------
NOTE: There is a portable version of OpenSlides for Windows which does not
required any install steps! If there is a reason that you can not use the
portable version you should run the following install steps.
1. Install requirements:
@ -76,73 +117,3 @@ I. Installation on Windows (32/64bit)
Use 'python start.py --help' to show all start options.
II. Installation on GNU/Linux and MacOSX
----------------------------------------
1. Install requirements:
OpenSlides requires following programs, which should be
installed first:
+ Python Programming Language 2 (>= 2.5)
+ virtualenv (>= 1.4.1)
+ ReportLab Toolkit
+ Python Imaging Library (PIL)
E.g. for ubuntu run:
$ sudo apt-get install python python-virtualenv python-reportlab python-imaging
2. Get OpenSlides:
a) Download latest OpenSlides release from http://openslides.org.
OR
b) Clone development version from OpenSlides' github repository
https://github.com/OpenSlides/OpenSlides.
This requires Git, see http://git-scm.com/.
Open command line (cmd) and run:
git clone git://github.com/OpenSlides/OpenSlides.git
3. Setup your virtual environment with virtualenv:
Go to the (extracted/cloned) root directory of OpenSlides
and create virtualenv environment:
$ virtualenv .venv
For virtualenv >= 1.7 use instead:
$ virtualenv --system-site-packages .venv
4. Activate the virtual environment:
$ source .venv/bin/activate
5. Install the required python-packages:
$ pip install django django-mptt
If you use python < 2.6 you also have to install simplejson:
$ pip install simplejson
If requirements reportlab or PIL still missing (see 1.):
$ pip install reportlab pil
6. Start OpenSlides server and open URL in your default browser:
$ python start.py
If you run this script the first time a new database and the
admin account are created. Please change the password after
first login!
Username: admin
Password: admin
Use 'python start.py --help' to show all start options.
7. Restart OpenSlides:
To restart OpenSlides after closing the terminal activate the
virtual environment (see 4.) before starting the server (see 6.).

View File

@ -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 *

View File

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

View File

@ -0,0 +1,15 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
script_path = os.path.realpath(os.path.dirname(__file__))
sys.path.append(os.path.join(script_path, '..', '..'))
from openslides.main import create_settings
if __name__ == "__main__":
cwd = os.getcwd()
create_settings(os.path.join(cwd, 'settings.py'),
os.path.join(cwd, 'database.sqlite'))

View File

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

View File

@ -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

View File

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

View File

@ -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 = "&nbsp;" * 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):

View File

@ -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."))))

View File

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

View File

@ -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']))
else: # Print all assignments
if not assignments: # No assignments existing
story.append(Paragraph(
_("No assignments available."), stylesheet['Heading3']))
else: # Print all assignments
# List of assignments
for assignment in assignments:
story.append(Paragraph(assignment.name,
stylesheet['Heading3']))
story.append(Paragraph(
assignment.name, stylesheet['Heading3']))
# Assignment details (each assignment on single page)
for assignment in assignments:
story.append(PageBreak())
@ -376,28 +371,33 @@ class AssignmentPDF(PDFView):
def get_assignment(self, assignment, story):
# title
story.append(Paragraph(_("Election: %s") % assignment.name,
stylesheet['Heading1']))
story.append(Paragraph(
_("Election: %s") % assignment.name, stylesheet['Heading1']))
story.append(Spacer(0, 0.5 * cm))
# posts
cell1a = []
cell1a.append(Paragraph("<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'/>.&nbsp; %s" % candidate,
cell2b.append(Paragraph(
"<seq id='counter'/>.&nbsp; %s" % candidate,
stylesheet['Signaturefield']))
if assignment.status == "sea":
for x in range(0, 2 * assignment.posts):
cell2b.append(Paragraph("<seq id='counter'/>.&nbsp; "
"__________________________________________",
stylesheet['Signaturefield']))
cell2b.append(
Paragraph(
"<seq id='counter'/>.&nbsp; "
"__________________________________________",
stylesheet['Signaturefield']))
cell2b.append(Spacer(0, 0.2 * cm))
# Vote results
@ -409,15 +409,15 @@ class AssignmentPDF(PDFView):
# Left side
cell3a = []
cell3a.append(Paragraph("%s:" % (_("Vote results")),
stylesheet['Heading4']))
cell3a.append(Paragraph(
"%s:" % (_("Vote results")), stylesheet['Heading4']))
if polls.count() == 1:
cell3a.append(Paragraph("%s %s" % (polls.count(), _("ballot")),
stylesheet['Normal']))
cell3a.append(Paragraph(
"%s %s" % (polls.count(), _("ballot")), stylesheet['Normal']))
elif polls.count() > 1:
cell3a.append(Paragraph("%s %s" % (polls.count(), _("ballots")),
stylesheet['Normal']))
cell3a.append(Paragraph(
"%s %s" % (polls.count(), _("ballots")), stylesheet['Normal']))
# Add table head row
headrow = []
@ -426,7 +426,6 @@ class AssignmentPDF(PDFView):
headrow.append("%s." % poll.get_ballot())
data_votes.append(headrow)
# Add result rows
elected_candidates = list(assignment.elected)
for candidate, poll_list in vote_results.iteritems():
@ -439,12 +438,13 @@ class AssignmentPDF(PDFView):
candidate_string += "\n(%s)" % candidate.name_suffix
row.append(candidate_string)
for vote in poll_list:
if vote == None:
if vote is None:
row.append('')
elif 'Yes' in vote and 'No' in vote and 'Abstain' in vote:
row.append(_("Y: %(YES)s\nN: %(NO)s\nA: %(ABSTAIN)s")
% {'YES':vote['Yes'], 'NO': vote['No'],
'ABSTAIN': vote['Abstain']})
row.append(
_("Y: %(YES)s\nN: %(NO)s\nA: %(ABSTAIN)s")
% {'YES': vote['Yes'], 'NO': vote['No'],
'ABSTAIN': vote['Abstain']})
elif 'Votes' in vote:
row.append(vote['Votes'])
else:
@ -465,15 +465,14 @@ class AssignmentPDF(PDFView):
footrow_two.append(poll.print_votescast())
data_votes.append(footrow_two)
table_votes=Table(data_votes)
table_votes.setStyle( TableStyle([
table_votes = Table(data_votes)
table_votes.setStyle(TableStyle([
('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
('VALIGN',(0, 0),(-1, -1), 'TOP'),
('LINEABOVE',(0, 0),(-1, 0), 2, colors.black),
('LINEABOVE',(0, 1),(-1, 1), 1, colors.black),
('LINEBELOW',(0, -1),(-1, -1), 2, colors.black),
('ROWBACKGROUNDS', (0, 1), (-1, -1), (colors.white, (.9, .9, .9))),
]))
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('LINEABOVE', (0, 0), (-1, 0), 2, colors.black),
('LINEABOVE', (0, 1), (-1, 1), 1, colors.black),
('LINEBELOW', (0, -1), (-1, -1), 2, colors.black),
('ROWBACKGROUNDS', (0, 1), (-1, -1), (colors.white, (.9, .9, .9)))]))
# table
data = []
@ -484,17 +483,18 @@ class AssignmentPDF(PDFView):
else:
data.append([cell2a, cell2b])
data.append([Spacer(0, 0.2 * cm), ''])
t=Table(data)
t = Table(data)
t._argW[0] = 4.5 * cm
t._argW[1] = 11 * cm
t.setStyle(TableStyle([ ('BOX', (0,0), (-1, -1), 1, colors.black),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
]))
t.setStyle(TableStyle([
('BOX', (0, 0), (-1, -1), 1, colors.black),
('VALIGN', (0, 0), (-1, -1), 'TOP')]))
story.append(t)
story.append(Spacer(0, 1 * cm))
# text
story.append(Paragraph("%s" % assignment.description.replace('\r\n',
story.append(Paragraph(
"%s" % assignment.description.replace('\r\n',
'<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'/>&nbsp;&nbsp;" % imgpath
cell = []
cell.append(Spacer(0,0.8*cm))
cell.append(Paragraph(_("Election") + ": " + self.poll.assignment.name,
cell.append(Spacer(0, 0.8 * cm))
cell.append(Paragraph(
_("Election") + ": " + self.poll.assignment.name,
stylesheet['Ballot_title']))
cell.append(Paragraph(self.poll.assignment.polldescription,
cell.append(Paragraph(
self.poll.assignment.polldescription,
stylesheet['Ballot_subtitle']))
options = self.poll.get_options()
ballot_string = _("%d. ballot") % self.poll.get_ballot()
candidate_string = ungettext("%d candidate", "%d candidates",
len(options)) % len(options)
available_posts_string = ungettext("%d available post", "%d available posts",
candidate_string = ungettext(
"%d candidate", "%d candidates", len(options)) % len(options)
available_posts_string = ungettext(
"%d available post", "%d available posts",
self.poll.assignment.posts) % self.poll.assignment.posts
cell.append(Paragraph("%s, %s, %s" % (ballot_string, candidate_string,
cell.append(Paragraph(
"%s, %s, %s" % (ballot_string, candidate_string,
available_posts_string), stylesheet['Ballot_description']))
cell.append(Spacer(0, 0.4 * cm))
data= []
data = []
# get ballot papers config values
ballot_papers_selection = config["assignment_pdf_ballot_papers_selection"]
ballot_papers_number = config["assignment_pdf_ballot_papers_number"]
@ -560,7 +566,7 @@ class AssignmentPollPDF(PDFView):
number = User.objects.filter(type__iexact="delegate").count()
elif ballot_papers_selection == "NUMBER_OF_ALL_PARTICIPANTS":
number = int(User.objects.count())
else: # ballot_papers_selection == "CUSTOM_NUMBER"
else: # ballot_papers_selection == "CUSTOM_NUMBER"
number = int(ballot_papers_number)
number = max(1, number)
@ -568,16 +574,18 @@ class AssignmentPollPDF(PDFView):
if self.poll.yesnoabstain:
for option in options:
candidate = option.candidate
cell.append(Paragraph(candidate.clean_name,
stylesheet['Ballot_option_name']))
cell.append(Paragraph(
candidate.clean_name, stylesheet['Ballot_option_name']))
if candidate.name_suffix:
cell.append(Paragraph("(%s)" % candidate.name_suffix,
cell.append(Paragraph(
"(%s)" % candidate.name_suffix,
stylesheet['Ballot_option_group']))
else:
cell.append(Paragraph("&nbsp;",
stylesheet['Ballot_option_group']))
cell.append(Paragraph(circle + _("Yes") + "&nbsp; " * 3 + circle
+ _("No") + "&nbsp; " * 3 + circle+ _("Abstention"),
cell.append(Paragraph(
"&nbsp;", stylesheet['Ballot_option_group']))
cell.append(Paragraph(
circle + _("Yes") + "&nbsp; " * 3 + circle
+ _("No") + "&nbsp; " * 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("&nbsp;",
stylesheet['Ballot_option_group_right']))
cell.append(Paragraph(
"&nbsp;", stylesheet['Ballot_option_group_right']))
# print ballot papers
for user in xrange(number / 2):
data.append([cell, cell])
@ -615,9 +625,9 @@ class AssignmentPollPDF(PDFView):
else:
t = Table(data, 10.5 * cm, 29.7 * cm)
t.setStyle(TableStyle([('GRID', (0, 0), (-1, -1), 0.25, colors.grey),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
]))
t.setStyle(TableStyle([
('GRID', (0, 0), (-1, -1), 0.25, colors.grey),
('VALIGN', (0, 0), (-1, -1), 'TOP')]))
story.append(t)
@ -629,16 +639,15 @@ class Config(FormView):
def get_initial(self):
return {
'assignment_publish_winner_results_only':
config['assignment_publish_winner_results_only'],
config['assignment_publish_winner_results_only'],
'assignment_pdf_ballot_papers_selection':
config['assignment_pdf_ballot_papers_selection'],
config['assignment_pdf_ballot_papers_selection'],
'assignment_pdf_ballot_papers_number':
config['assignment_pdf_ballot_papers_number'],
config['assignment_pdf_ballot_papers_number'],
'assignment_pdf_title': config['assignment_pdf_title'],
'assignment_pdf_preamble': config['assignment_pdf_preamble'],
'assignment_poll_vote_values':
config['assignment_poll_vote_values'],
}
config['assignment_poll_vote_values']}
def form_valid(self, form):
if form.cleaned_data['assignment_publish_winner_results_only']:
@ -655,8 +664,8 @@ class Config(FormView):
form.cleaned_data['assignment_pdf_preamble']
config['assignment_poll_vote_values'] = \
form.cleaned_data['assignment_poll_vote_values']
messages.success(self.request,
_('Election settings successfully saved.'))
messages.success(
self.request, _('Election settings successfully saved.'))
return super(Config, self).form_valid(form)
@ -665,10 +674,11 @@ def register_tab(request):
return Tab(
title=_('Elections'),
url=reverse('assignment_overview'),
permission=request.user.has_perm('assignment.can_see_assignment')
or request.user.has_perm('assignment.can_nominate_other')
or request.user.has_perm('assignment.can_nominate_self')
or request.user.has_perm('assignment.can_manage_assignment'),
permission=(
request.user.has_perm('assignment.can_see_assignment') or
request.user.has_perm('assignment.can_nominate_other') or
request.user.has_perm('assignment.can_nominate_self') or
request.user.has_perm('assignment.can_manage_assignment')),
selected=selected,
)

View File

@ -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(

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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

View File

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

View File

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

View File

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

View File

@ -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>

View File

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

View File

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

View File

@ -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,

View File

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

View File

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

View File

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

View File

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

View File

@ -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:

View File

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

View File

@ -19,9 +19,6 @@ from openslides.config.signals import default_config_value
from openslides.projector.api import register_slidemodel
from openslides.projector.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):

View File

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

View File

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

View File

@ -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 = {

View File

@ -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']}

View File

@ -25,10 +25,9 @@ class excel_semikolon(Dialect):
def patchup(dialect):
if dialect:
if dialect.delimiter in [excel_semikolon.delimiter, excel.delimiter] and \
dialect.quotechar == excel_semikolon.quotechar:
dialect.quotechar == excel_semikolon.quotechar:
# walks like a duck and talks like a duck.. must be one
dialect.doublequote = True
return dialect
register_dialect("excel_semikolon", excel_semikolon)

View File

@ -1 +1,3 @@
from fields import JSONField
from fields import JSONField
__all__ = ['JSONField']

View File

@ -6,6 +6,7 @@ from django.utils.translation import ugettext_lazy as _
from django.forms.fields import Field
from django.forms.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"""

View File

@ -1,108 +0,0 @@
from django.db import models
from django.test import TestCase
from django.utils import simplejson as json
from fields import JSONField
class JsonModel(models.Model):
json = JSONField()
class ComplexEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, complex):
return {
'__complex__': True,
'real': obj.real,
'imag': obj.imag,
}
return json.JSONEncoder.default(self, obj)
def as_complex(dct):
if '__complex__' in dct:
return complex(dct['real'], dct['imag'])
return dct
class JSONModelCustomEncoders(models.Model):
# A JSON field that can store complex numbers
json = JSONField(
dump_kwargs={'cls': ComplexEncoder},
load_kwargs={'object_hook': as_complex},
)
class JSONFieldTest(TestCase):
"""JSONField Wrapper Tests"""
def test_json_field_create(self):
"""Test saving a JSON object in our JSONField"""
json_obj = {
"item_1": "this is a json blah",
"blergh": "hey, hey, hey"}
obj = JsonModel.objects.create(json=json_obj)
new_obj = JsonModel.objects.get(id=obj.id)
self.failUnlessEqual(new_obj.json, json_obj)
def test_json_field_modify(self):
"""Test modifying a JSON object in our JSONField"""
json_obj_1 = {'a': 1, 'b': 2}
json_obj_2 = {'a': 3, 'b': 4}
obj = JsonModel.objects.create(json=json_obj_1)
self.failUnlessEqual(obj.json, json_obj_1)
obj.json = json_obj_2
self.failUnlessEqual(obj.json, json_obj_2)
obj.save()
self.failUnlessEqual(obj.json, json_obj_2)
self.assert_(obj)
def test_json_field_load(self):
"""Test loading a JSON object from the DB"""
json_obj_1 = {'a': 1, 'b': 2}
obj = JsonModel.objects.create(json=json_obj_1)
new_obj = JsonModel.objects.get(id=obj.id)
self.failUnlessEqual(new_obj.json, json_obj_1)
def test_json_list(self):
"""Test storing a JSON list"""
json_obj = ["my", "list", "of", 1, "objs", {"hello": "there"}]
obj = JsonModel.objects.create(json=json_obj)
new_obj = JsonModel.objects.get(id=obj.id)
self.failUnlessEqual(new_obj.json, json_obj)
def test_empty_objects(self):
"""Test storing empty objects"""
for json_obj in [{}, [], 0, '', False]:
obj = JsonModel.objects.create(json=json_obj)
new_obj = JsonModel.objects.get(id=obj.id)
self.failUnlessEqual(json_obj, obj.json)
self.failUnlessEqual(json_obj, new_obj.json)
def test_custom_encoder(self):
"""Test encoder_cls and object_hook"""
value = 1 + 3j # A complex number
obj = JSONModelCustomEncoders.objects.create(json=value)
new_obj = JSONModelCustomEncoders.objects.get(pk=obj.pk)
self.failUnlessEqual(value, new_obj.json)

View File

@ -12,12 +12,13 @@
from django.db import models
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)

View File

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

View File

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

View File

@ -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__'):

View File

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

View File

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

View File

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

5
requirements.txt Normal file
View File

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

View File

@ -12,15 +12,19 @@ from setuptools import find_packages
from openslides import get_version
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
View File

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

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

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

View File

@ -12,12 +12,15 @@
from django.test import TestCase
from django.test.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
View File

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

View File

@ -11,10 +11,10 @@
"""
from django.test import TestCase
from django.test.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])

View File

@ -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)