Start to rewrite the motion app

This commit is contained in:
Oskar Hahn 2013-01-06 12:07:37 +01:00
parent ae8641bcad
commit 0231fe37f5
17 changed files with 368 additions and 2332 deletions

View File

@ -11,50 +11,39 @@
""" """
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext as _
from openslides.utils.forms import CssClassMixin from openslides.utils.forms import CssClassMixin
from openslides.utils.person import PersonFormField, MultiplePersonFormField from openslides.utils.person import PersonFormField, MultiplePersonFormField
from openslides.motion.models import Motion from .models import Motion
class MotionForm(forms.Form, CssClassMixin): class BaseMotionForm(forms.ModelForm, CssClassMixin):
class Meta:
model = Motion
fields = ()
def __init__(self, *args, **kwargs):
motion = kwargs.get('instance', None)
if motion is not None:
initial = kwargs.setdefault('initial', {})
initial['title'] = motion.title
initial['text'] = motion.text
initial['reason'] = motion.reason
super(BaseMotionForm, self).__init__(*args, **kwargs)
title = forms.CharField(widget=forms.TextInput(), label=_("Title")) title = forms.CharField(widget=forms.TextInput(), label=_("Title"))
text = forms.CharField(widget=forms.Textarea(), label=_("Text")) text = forms.CharField(widget=forms.Textarea(), label=_("Text"))
reason = forms.CharField( reason = forms.CharField(
widget=forms.Textarea(), required=False, label=_("Reason")) widget=forms.Textarea(), required=False, label=_("Reason"))
class MotionFormTrivialChanges(MotionForm): class MotionCreateForm(BaseMotionForm):
trivial_change = forms.BooleanField( pass
required=False, label=_("Trivial change"),
help_text=_("Trivial changes don't create a new version."))
class MotionManagerForm(forms.ModelForm, CssClassMixin): class MotionUpdateForm(BaseMotionForm):
submitter = PersonFormField(label=_("Submitter")) pass
class Meta:
model = Motion
exclude = ('number', 'status', 'permitted', 'log', 'supporter')
class MotionManagerFormSupporter(MotionManagerForm):
# TODO: Do not show the submitter in the user-list
supporter = MultiplePersonFormField(required=False, label=_("Supporters"))
class MotionImportForm(forms.Form, CssClassMixin):
csvfile = forms.FileField(
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"'),
)
class ConfigForm(forms.Form, CssClassMixin): class ConfigForm(forms.Form, CssClassMixin):

View File

@ -31,576 +31,217 @@ from openslides.projector.models import SlideMixin
from openslides.agenda.models import Item from openslides.agenda.models import Item
class MotionSupporter(models.Model): RELATION = (
motion = models.ForeignKey("Motion") (1, _('Submitter')),
person = PersonField() (2, _('Supporter')))
class Motion(models.Model, SlideMixin): class RelatedPersonsManager(models.Manager):
prefix = "motion" def __init__(self, relation, *args, **kwargs):
STATUS = ( super(RelatedPersonsManager, self).__init__(*args, **kwargs)
('pub', _('Published')), for key, value in RELATION:
('per', _('Permitted')), if key == relation:
('acc', _('Accepted')), self.relation = key
('rej', _('Rejected')),
('wit', _('Withdrawed')),
('adj', _('Adjourned')),
('noc', _('Not Concerned')),
('com', _('Commited a bill')),
('nop', _('Rejected (not authorized)')),
('rev', _('Needs Review')), # Where is this status used?
#additional actions:
# edit
# delete
# setnumber
# support
# unsupport
# createitem
# activateitem
# genpoll
)
submitter = PersonField(verbose_name=_("Submitter"))
number = models.PositiveSmallIntegerField(blank=True, null=True,
unique=True)
status = models.CharField(max_length=3, choices=STATUS, default='pub')
permitted = models.ForeignKey('AVersion', related_name='permitted',
null=True, blank=True)
log = models.TextField(blank=True, null=True)
@property
def last_version(self):
"""
Return last version of the motion.
"""
try:
return AVersion.objects.filter(motion=self).order_by('id') \
.reverse()[0]
except IndexError:
return None
@property
def public_version(self):
"""
Return permitted, if the motion was permitted, else last_version
"""
if self.permitted is not None:
return self.permitted
else:
return self.last_version
def accept_version(self, version, user=None):
"""
accept a Version
"""
self.permitted = version
self.save(nonewversion=True)
version.rejected = False
version.save()
self.writelog(_("Version %d authorized") % version.aid, user)
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)
return True
return False
@property
def versions(self):
"""
Return a list of all versions of the motion.
"""
return AVersion.objects.filter(motion=self)
@property
def creation_time(self):
"""
Return the time of the creation of the motion.
"""
try:
return self.versions[0].time
except IndexError:
return None
@property
def notes(self):
"""
Return some information of the motion.
"""
note = []
if self.status == "pub" and not self.enough_supporters:
note.append(ugettext("Searching for supporters."))
if self.status == "pub" and self.permitted is None:
note.append(ugettext("Not yet authorized."))
elif self.unpermitted_changes and self.permitted:
note.append(ugettext("Not yet authorized changes."))
return note
@property
def unpermitted_changes(self):
"""
Return True if the motion has unpermitted changes.
The motion has unpermitted changes, if the permitted-version
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):
return True
else:
return False
@property
def supporters(self):
return sorted([object.person for object in self.motionsupporter_set.all()],
key=lambda person: person.sort_name)
def is_supporter(self, person):
try:
return self.motionsupporter_set.filter(person=person).exists()
except AttributeError:
return False
@property
def enough_supporters(self):
"""
Return True, if the motion has enough supporters
"""
min_supporters = int(config['motion_min_supporters'])
if self.status == "pub":
return self.count_supporters() >= min_supporters
else:
return True
def count_supporters(self):
return self.motionsupporter_set.count()
@property
def missing_supporters(self):
"""
Return number of missing supporters
"""
min_supporters = int(config['motion_min_supporters'])
delta = min_supporters - self.count_supporters()
if delta > 0:
return delta
else:
return 0
def save(self, user=None, nonewversion=False, trivial_change=False):
"""
Save the Motion, and create a new AVersion if necessary
"""
super(Motion, self).save()
if nonewversion:
return
last_version = self.last_version
fields = ["text", "title", "reason"]
if last_version is not None:
changed_fields = [
f for f in fields
if getattr(last_version, f) != getattr(self, f)]
if not changed_fields:
return # No changes
try:
if trivial_change and last_version is not None:
last_version.text = self.text
last_version.title = self.title
last_version.reason = self.reason
last_version.save()
meta = AVersion._meta
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
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')
except AttributeError:
is_manager = False
supporters = self.motionsupporter_set.all()
if (self.status == "pub" and
supporters and not is_manager):
supporters.delete()
self.writelog(_("Supporters removed"), user)
def reset(self, user):
"""
Reset the motion.
"""
self.status = "pub"
self.permitted = None
self.save()
self.writelog(_("Status reseted to: %s") % (self.get_status_display()), user)
def support(self, person):
"""
Add a Supporter to the list of supporters of the motion.
"""
if person == self.submitter:
# TODO: Use own Exception
raise NameError('Supporter can not be the submitter of a '
'motion.')
if not self.is_supporter(person):
MotionSupporter(motion=self, person=person).save()
self.writelog(_("Supporter: +%s") % (person))
# TODO: Raise a precise exception for the view in else-clause
def unsupport(self, person):
"""
remove a supporter from the list of supporters of the motion
"""
try:
self.motionsupporter_set.get(person=person).delete()
except MotionSupporter.DoesNotExist:
# TODO: Don't do nothing but raise a precise exception for the view
pass
else:
self.writelog(_("Supporter: -%s") % (person))
def set_number(self, number=None, user=None):
"""
Set a number for ths motion.
"""
if self.number is not None:
# TODO: Use own Exception
raise NameError('This motion has already a number.')
if number is None:
try:
number = Motion.objects.aggregate(Max('number'))['number__max'] + 1
except TypeError:
number = 1
self.number = number
self.save()
self.writelog(_("Number set: %s") % (self.number), user)
return self.number
def permit(self, user=None):
"""
Change the status of this motion to permit.
"""
self.set_status(user, "per")
aversion = self.last_version
if self.number is None:
self.set_number()
self.permitted = aversion
self.save()
self.writelog(_("Version %s authorized") % (aversion.aid), user)
return self.permitted
def notpermit(self, user=None):
"""
Change the status of this motion to 'not permitted (rejected)'.
"""
self.set_status(user, "nop")
#TODO: reject last version
if self.number is None:
self.set_number()
self.save()
self.writelog(_("Version %s not authorized") % (self.last_version.aid), user)
def set_status(self, user, status, force=False):
"""
Set the status of the motion.
"""
error = True
for a, b in Motion.STATUS:
if status == a:
error = False
break break
if 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.\'')
% self.status)
actions = []
actions = self.get_allowed_actions(user)
if status not in actions and not force:
#TODO: Use the Right Error
raise NameError(_(
'The motion status is: \'%(currentstatus)s\'. '
'You can not set the status to \'%(newstatus)s\'.') % {
'currentstatus': self.status,
'newstatus': status})
oldstatus = self.get_status_display()
self.status = status
self.save()
self.writelog(_("Status modified") + ": %s -> %s"
% (oldstatus, self.get_status_display()), user)
def get_allowed_actions(self, user):
"""
Return a list of all the allowed status.
"""
actions = []
# check if user allowed to withdraw an motion
if ((self.status == "pub"
and self.number
and user == self.submitter)
or (self.status == "pub"
and self.number
and user.has_perm("motion.can_manage_motion"))
or (self.status == "per"
and user == self.submitter)
or (self.status == "per"
and user.has_perm("motion.can_manage_motion"))):
actions.append("wit")
#Check if the user can review the motion
if (self.status == "rev"
and (self.submitter == user
or user.has_perm("motion.can_manage_motion"))):
actions.append("pub")
# Check if the user can support and unspoort the motion
if (self.status == "pub"
and user != self.submitter
and not self.is_supporter(user)):
actions.append("support")
if self.status == "pub" and self.is_supporter(user):
actions.append("unsupport")
#Check if the user can edit the motion
if (user == self.submitter \
and (self.status in ('pub', 'per'))) \
or user.has_perm("motion.can_manage_motion"):
actions.append("edit")
# Check if the user can delete the motion (admin, manager, owner)
# reworked as requiered in #100
if (user.has_perm("motion.can_delete_all_motions") or
(user.has_perm("motion.can_manage_motion") and
self.number is None) or
(self.submitter == user and self.number is None)):
actions.append("delete")
#For the rest, all actions need the manage permission
if not user.has_perm("motion.can_manage_motion"):
return actions
if self.status == "pub":
actions.append("nop")
actions.append("per")
if self.number == None:
actions.append("setnumber")
if self.status == "per":
actions.append("acc")
actions.append("rej")
actions.append("adj")
actions.append("noc")
actions.append("com")
actions.append("genpoll")
if self.unpermitted_changes:
actions.append("permitversion")
actions.append("rejectversion")
return actions
def delete(self, force=False):
"""
Delete the motion. It is not possible, if the motion has
allready a number
"""
if self.number and not force:
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()
def writelog(self, text, user=None):
if not self.log:
self.log = ""
self.log += u"%s | %s" % (datetime.now().strftime("%d.%m.%Y %H:%M:%S"), _propper_unicode(text))
if user is not None:
if isinstance(user, User):
self.log += u" (%s %s)" % (_("by"), _propper_unicode(user.username))
else: else:
self.log += u" (%s %s)" % (_("by"), _propper_unicode(str(user))) raise ValueError('Unknown relation with id %d' % relation)
self.log += "\n"
self.save()
def get_agenda_title(self): def get_query_set(self):
return self.public_version.title return (super(RelatedPersonsManager, self).get_query_set()
.filter(relation=self.relation))
def get_agenda_title_supplement(self):
number = self.number or '<i>[%s]</i>' % ugettext('no number')
return '(%s %s)' % (ugettext('motion'), number)
def __getattr__(self, name): class MotionRelatedPersons(models.Model):
""" submitter = RelatedPersonsManager(relation=1)
if name is title, text, reason or time, supporter = RelatedPersonsManager(relation=2)
Return this attribute from the newest version of the motion objects = models.Manager()
"""
if name in ('title', 'text', 'reason', 'time', 'aid'): person = PersonField()
relation = models.IntegerField(default=1, choices=RELATION)
motion = models.ForeignKey('Motion', related_name="persons")
class Motion(SlideMixin, models.Model):
prefix = "motion" # Rename this in the slide-system
# TODO: Use this attribute for the default_version, if the permission system
# is deactivated. Maybe it has to be renamed.
permitted_version = models.ForeignKey(
'MotionVersion', null=True, blank=True, related_name="permitted")
# TODO: Define status
status = models.CharField(max_length=3)
# Log (Translatable)
identifier = models.CharField(max_length=255, null=True, blank=True,
unique=True)
category = models.ForeignKey('Category', null=True, blank=True)
# TODO proposal
# Maybe rename to master_copy
master = models.ForeignKey('self', null=True, blank=True)
class Meta:
permissions = (
('can_see_motion', ugettext_noop('Can see motions')),
('can_create_motion', ugettext_noop('Can create motions')),
('can_support_motion', ugettext_noop('Can support motions')),
('can_manage_motion', ugettext_noop('Can manage motions')),
)
# TODO: order per default by category and identifier
# ordering = ('number',)
def __unicode__(self):
return self.get_title()
# TODO: Use transaction
def save(self, *args, **kwargs):
super(Motion, self).save(*args, **kwargs)
new_data = False
for attr in ['_title', '_text', '_reason']:
if hasattr(self, attr):
new_data = True
break
need_new_version = True # TODO: Do we need a new version (look in config)
if hasattr(self, '_version') or (new_data and need_new_version):
version = self.new_version
del self._new_version
version.motion = self # Test if this line is realy neccessary.
elif new_data and not need_new_version:
# TODO: choose an explicit version
version = self.last_version
else:
# We do not need to save the motion version
return
for attr in ['title', 'text', 'reason']:
_attr = '_%s' % attr
try: try:
if name == 'aid': setattr(version, attr, getattr(self, _attr))
return self.last_version.aid
return self.last_version.__dict__[name]
except TypeError:
raise AttributeError(name)
except AttributeError: except AttributeError:
raise AttributeError(name) setattr(version, attr, getattr(self.last_version, attr))
raise AttributeError(name) version.save()
def gen_poll(self, user=None): def get_absolute_url(self, link='detail'):
""" if link == 'view' or link == 'detail':
Generates a poll object for the motion return reverse('motion_detail', args=[str(self.id)])
"""
poll = MotionPoll(motion=self)
poll.save()
poll.set_options()
self.writelog(_("Poll created"), user)
return poll
@property
def polls(self):
return self.motionpoll_set.all()
@property
def results(self):
return self.get_poll_results()
def get_poll_results(self):
"""
Return a list of voting results
"""
results = []
for poll in self.polls:
for option in poll.get_options():
if option.get_votes().exists():
results.append((
option['Yes'], option['No'],
option['Abstain'], poll.print_votesinvalid(),
poll.print_votescast()))
return results
def slide(self):
"""
return the slide dict
"""
data = super(Motion, self).slide()
data['motion'] = self
data['title'] = self.title
data['template'] = 'projector/Motion.html'
return data
def get_absolute_url(self, link='view'):
if link == 'view':
return reverse('motion_view', args=[str(self.id)])
if link == 'edit': if link == 'edit':
return reverse('motion_edit', args=[str(self.id)]) return reverse('motion_edit', args=[str(self.id)])
if link == 'delete': if link == 'delete':
return reverse('motion_delete', args=[str(self.id)]) return reverse('motion_delete', args=[str(self.id)])
def __unicode__(self): def get_title(self):
try: try:
return self.last_version.title return self._title
except AttributeError: except AttributeError:
return "no title jet" return self.default_version.title
class Meta: def set_title(self, title):
permissions = ( self._title = title
('can_see_motion', ugettext_noop("Can see motions")),
('can_create_motion', ugettext_noop("Can create motions")),
('can_support_motion', ugettext_noop("Can support motions")),
('can_manage_motion', ugettext_noop("Can manage motions")),
)
ordering = ('number',)
title = property(get_title, set_title)
class AVersion(models.Model): def get_text(self):
title = models.CharField(max_length=100, verbose_name=_("Title")) try:
text = models.TextField(verbose_name=_("Text")) return self._text
reason = models.TextField(null=True, blank=True, verbose_name=_("Reason")) except AttributeError:
rejected = models.BooleanField() # = Not Permitted return self.default_version.text
time = models.DateTimeField(auto_now=True)
motion = models.ForeignKey(Motion)
def __unicode__(self): def set_text(self, text):
return "%s %s" % (self.id, self.title) self._text = text
text = property(get_text, set_text)
def get_reason(self):
try:
return self._reason
except AttributeError:
return self.default_version.reason
def set_reason(self, reason):
self._reason = reason
reason = property(get_reason, set_reason)
@property @property
def aid(self): def new_version(self):
try: try:
return self._aid return self._new_version
except AttributeError: except AttributeError:
self._aid = AVersion.objects \ self._new_version = MotionVersion(motion=self)
.filter(motion=self.motion) \ return self._new_version
.filter(id__lte=self.id).count()
return self._aid
register_slidemodel(Motion) @property
def submitter(self):
return MotionRelatedPersons.submitter.filter(motion=self)
@property
def supporter(self):
return MotionRelatedPersons.supporter.filter(motion=self)
def get_version(self, version_id):
# TODO: Check case, if version_id is not one of this motion
return self.versions.get(pk=version_id)
class MotionVote(BaseVote): def get_default_version(self):
option = models.ForeignKey('MotionOption') try:
return self._default_version
except AttributeError:
# TODO: choose right version via config
return self.last_version
def set_default_version(self, version):
if version is None:
try:
del self._default_version
except AttributeError:
pass
else:
if type(version) is int:
version = self.versions.all()[version]
elif type(version) is not MotionVersion:
raise ValueError('The argument \'version\' has to be int or '
'MotionVersion, not %s' % type(version))
self._default_version = version
default_version = property(get_default_version, set_default_version)
@property
def last_version(self):
# TODO: Fix the case, that the motion has no Version
try:
return self.versions.order_by('id').reverse()[0]
except IndexError:
return self.new_version
class MotionOption(BaseOption): class MotionVersion(models.Model):
poll = models.ForeignKey('MotionPoll') title = models.CharField(max_length=255, verbose_name=_("Title"))
vote_class = MotionVote text = models.TextField(verbose_name=_("Text"))
reason = models.TextField(null=True, blank=True, verbose_name=_("Reason"))
rejected = models.BooleanField(default=False)
creation_time = models.DateTimeField(auto_now=True)
motion = models.ForeignKey(Motion, related_name='versions')
identifier = models.CharField(max_length=255, verbose_name=_("Version identifier"))
note = models.TextField(null=True, blank=True)
def __unicode__(self):
return "%s Version %s" % (self.motion, self.get_version_number())
def get_version_number(self):
if self.pk is None:
return 'new'
return (MotionVersion.objects.filter(motion=self.motion)
.filter(id__lte=self.pk).count())
class MotionPoll(BasePoll, CountInvalid, CountVotesCast): class Category(models.Model):
option_class = MotionOption name = models.CharField(max_length=255, verbose_name=_("Category name"))
vote_values = [ prefix = models.CharField(max_length=32, verbose_name=_("Category prefix"))
ugettext_noop('Yes'), ugettext_noop('No'), ugettext_noop('Abstain')]
motion = models.ForeignKey(Motion) def __unicode__(self):
return self.name
def get_motion(self):
return self.motion
def set_options(self):
#TODO: maybe it is possible with .create() to call this without poll=self
self.get_option_class()(poll=self).save()
def append_pollform_fields(self, fields):
CountInvalid.append_pollform_fields(self, fields)
CountVotesCast.append_pollform_fields(self, fields)
def get_absolute_url(self):
return reverse('motion_poll_view', args=[self.id])
def get_ballot(self):
return self.motion.motionpoll_set.filter(id__lte=self.id).count()
@receiver(default_config_value, dispatch_uid="motion_default_config") class Comment(models.Model):
def default_config(sender, key, **kwargs): motion_version = models.ForeignKey(MotionVersion)
return { text = models.TextField()
'motion_min_supporters': 0, author = PersonField()
'motion_preamble': _('The assembly may decide,'), creation_time = models.DateTimeField(auto_now=True)
'motion_pdf_ballot_papers_selection': 'CUSTOM_NUMBER',
'motion_pdf_ballot_papers_number': '8',
'motion_pdf_title': _('Motions'),
'motion_pdf_preamble': '',
'motion_allow_trivial_change': False,
}.get(key)

View File

@ -1,66 +0,0 @@
{% extends "base.html" %}
{% load tags %}
{% load i18n %}
{% load staticfiles %}
{% block submenu %}
{% url 'motion_overview' as url_motionoverview %}
<h4>{% trans "Motions" %}</h4>
<ul>
<li class="{% if request.path == url_motionoverview %}selected{% endif %}"><a href="{% url 'motion_overview' %}">{% trans "All motions" %}</a></li>
{% if perms.motion.can_create_motion or perms.motion.can_manage_motion %}
<li class="{% active request '/motion/new' %}"><a href="{% url 'motion_new' %}">{% trans "New motion" %}</a></li>
{% endif %}
{% if perms.motion.can_manage_motion %}
<li class="{% active request '/motion/import' %}"><a href="{% url 'motion_import' %}">{% trans 'Import motions' %}</a></li>
{% endif %}
<li><a href="{% url 'print_motions' %}"><img src="{% static 'images/icons/pdf.png' %}"> {% trans 'All motions as PDF' %}</a></li>
</ul>
{# second submenu #}
{% if motion %}
<br>
<h3>{% trans "Motion No." %}
{% if motion.number != None %}
{{ motion.number }}
{% else %}
<i>[-]</i>
{% endif %}
</h3>
<ul>
{# view motion #}
{% url 'motion_view' motion.id as url_motionview %}
<li class="{% if request.path == url_motionview %}selected{% endif %}"><a href="{% url 'motion_view' motion.id %}">{% trans 'View motion' %}</a></li>
{# edit motion #}
{% if "edit" in actions %}
{% url 'motion_edit' motion.id as url_motionedit %}
<li class="{% if request.path == url_motionedit %}selected{% endif %}"><a href="{% url 'motion_edit' motion.id %}"><img src="{% static 'images/icons/edit.png' %}"> {% trans 'Edit motion' %}</a></li>
{% endif %}
{# delete motion #}
{% if "delete" in actions %}
<li><a href="{% url 'motion_delete' motion.id %}"><img src="{% static 'images/icons/delete.png' %}"> {% trans 'Delete motion' %}</a></li>
{% endif %}
{# PDF #}
<li><a href="{% url 'print_motion' motion.id %}"><img src="{% static 'images/icons/pdf.png' %}"> {% trans 'Motion as PDF' %}</a></li>
{# activate and polls #}
{% if perms.projector.can_manage_projector %}
<li>
<a class="activate_link {% if item.active %}active{% endif %}" href="{% url 'projector_activate_slide' motion.sid %}"><img src="{% static 'images/icons/projector.png' %}"> {% trans 'Show Motion' %}</a>
</li>
{% endif %}
{% if perms.motion.can_manage_motion %}
{% for poll in motion.polls %}
{% url 'motion_poll_view' poll.id as url_motionpollview %}
<li class="{% if request.path == url_motionpollview %}selected{% endif %}"><a href="{% url 'motion_poll_view' poll.id %}"><img src="{% static 'images/icons/edit.png' %}"> {{ forloop.counter }}. {% trans "Vote" %}</a></li>
{% endfor %}
{% endif %}
{# Agenda Item #}
{% if perms.agenda.can_manage_agenda %}
<li>
<a href="{% url 'motion_create_agenda' motion.id %}">{% trans 'New agenda item' %}</a>
</li>
{% endif %}
</ul>
{% endif %}
{% endblock %}

View File

@ -1,22 +0,0 @@
{% extends "config/base_config.html" %}
{% load i18n %}
{% block title %}{{ block.super }} {% trans "Motion settings" %}{% endblock %}
{% block content %}
<h1>{% trans "Motion settings" %}</h1>
<form action="" method="post">{% csrf_token %}
{{ form.as_p }}
<p>
<button class="button" type="submit">
<span class="icon ok">{% trans 'Save' %}</span>
</button>
<a href="{% url 'config_motion' %}">
<button class="button" type="button" onclick="window.location='{% url 'config_motion' %}'">
<span class="icon cancel">{% trans 'Cancel' %}</span>
</button>
</a>
</p>
</form>
{% endblock %}

View File

@ -1,40 +0,0 @@
{% extends "motion/base_motion.html" %}
{% load i18n %}
{% block title %}
{{ block.super }}
{% if motion %}
{% trans "Edit motion" %}
{% else %}
{% trans "New motion" %}
{% endif %}
{% endblock %}
{% block content %}
{% if motion %}
<h1>{% trans "Edit motion" %}</h1>
{% else %}
<h1>{% trans "New motion" %}</h1>
{% endif %}
<form action="" method="post">{% csrf_token %}
{{ form.as_p }}
{{ managerform.as_p }}
<p>
<button class="button" type="submit">
<span class="icon ok">{% trans 'Save' %}</span>
</button>
<button class="button" type="submit" name="apply">
<span class="icon apply">{% trans 'Apply' %}</span>
</button>
<a href="{% url 'motion_overview' %}">
<button class="button" type="button" onclick="window.location='{% url 'motion_overview' %}'">
<span class="icon cancel">{% trans 'Cancel' %}</span>
</button>
</a>
</p>
<small>* {% trans "required" %}</small>
</form>
{% endblock %}

View File

@ -1,36 +0,0 @@
{% extends "motion/base_motion.html" %}
{% load i18n %}
{% block title %}{{ block.super }} {% trans "Import motions" %} {% endblock %}
{% block content %}
<h1>{% trans "Import motions" %}</h1>
<p>{% trans 'Select a CSV file to import motions!' %}</p>
<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>
<p><a href="http://dev.openslides.org/wiki/CSVImport" target="_blank">{% trans 'A CSV example file is available in OpenSlides Wiki.' %}</a>
</p>
<form enctype="multipart/form-data" action="" method="post">{% csrf_token %}
{{ form.as_p }}
<p>
<button class="button" type="submit">
<span class="icon import">{% trans 'Import' %}</span>
</button>
<a href="{% url 'motion_overview' %}">
<button class="button" type="button" onclick="window.location='{% url 'motion_overview' %}'">
<span class="icon cancel">{% trans 'Cancel' %}</span>
</button>
</a>
</p>
<small>* {% trans "required" %}</small>
</form>
{% endblock %}

View File

@ -0,0 +1,12 @@
{% extends "base.html" %}
{% load tags %}
{% load i18n %}
{% load staticfiles %}
{% block title %}{{ block.super }} {% trans "Motion" %} "{{ version.title }}"{% endblock %}
{% block content %}
<p>Titel: {{ object.title }} </p>
<p>Text: {{ object.text }}</p>
{% endblock %}

View File

@ -0,0 +1,26 @@
{% extends "base.html" %}
{% load tags %}
{% load i18n %}
{% load staticfiles %}
{% block title %}{{ block.super }} {% trans "Motion Form" %}{% endblock %}
{% block content %}
<h1>{% trans "Motions Forms" %}</h1>
<form action="" method="post">{% csrf_token %}
{{ form.as_p }}
<button class="button" type="submit">
<span class="icon ok">{% trans 'Save' %}</span>
</button>
<button class="button" type="submit" name="apply">
<span class="icon apply">{% trans 'Apply' %}</span>
</button>
<a href="{% url 'motion_list' %}">
<button class="button" type="button" onclick="window.location='{% url 'motion_list' %}'">
<span class="icon cancel">{% trans 'Cancel' %}</span>
</button>
</a>
<small>* {% trans "required" %}</small>
</form>
{% endblock %}

View File

@ -0,0 +1,16 @@
{% extends "base.html" %}
{% load tags %}
{% load i18n %}
{% load staticfiles %}
{% block title %}{{ block.super }} {% trans "Motions" %}{% endblock %}
{% block content %}
<h1>{% trans "Motions" %}</h1>
<ul>
{% for motion in motion_list %}
<li>{{ motion }}</li>
{% endfor %}
</ul>
{% endblock %}

View File

@ -1,88 +0,0 @@
{% extends "motion/base_motion.html" %}
{% load tags %}
{% load i18n %}
{% load staticfiles %}
{% block title %}{{ block.super }} {% trans "Motions" %}{% endblock %}
{% block content %}
<h1>{% trans "Motions" %}</h1>
<p><form action="{{ request.url }}" name="filter" method="get">
{% trans "Filter" %}:
{% if min_supporters > 0 %}
<input type="checkbox" name="needsup" onchange="document.forms['filter'].submit()"
{% if 'on' in request.GET.needsup %}checked{% endif %}> {% trans "Need supporters" %} &nbsp;
{% endif %}
<input type="checkbox" name="number" onchange="document.forms['filter'].submit()"
{% if 'on' in request.GET.number %}checked{% endif %}> {% trans "Without number" %} &nbsp;
<input type="checkbox" name="status" onchange="document.forms['filter'].submit()"
{% if 'on' in request.GET.status %}checked{% endif %}> {% trans "Status" %}:
<select class="default-input" name="statusvalue" onchange="{% if 'on' in request.GET.status %}document.forms['filter'].submit(){% endif %}">
<option value="pub" {% if 'pub' in request.GET.statusvalue %}selected{% endif %}>{% trans "Not yet authorized" %}</option>
<option value="per" {% if 'on' in request.GET.status and 'per' in request.GET.statusvalue %}selected{% endif %}>{% trans "Authorized" %}</option>
<option value="acc" {% if 'on' in request.GET.status and 'acc' in request.GET.statusvalue %}selected{% endif %}>{% trans "Accepted" %}</option>
<option value="rej" {% if 'on' in request.GET.status and 'rej' in request.GET.statusvalue %}selected{% endif %}>{% trans "Rejected" %}</option>
<option value="wit" {% if 'on' in request.GET.status and 'wit' in request.GET.statusvalue %}selected{% endif %}>{% trans "Withdrawen (by submitter)" %}</option>
<option value="rev" {% if 'rev' in request.GET.statusvalue %}selected{% endif %}>{% trans "Needs Review" %}</option>
</select>
</form>
</p>
{{ motions|length }}
{% blocktrans count counter=motions|length context "number of motions"%}motion{% plural %}motions{% endblocktrans %}
<table>
<tr>
<th><a href="?sort=number{% if 'number' in request.GET.sort and 'reverse' not in request.GET %}&reverse{%endif%}">{% trans "Number" %}</a></th>
<th><a href="?sort=title{% if 'title' in request.GET.sort and 'reverse' not in request.GET %}&reverse{%endif%}">{% trans "Motion title" %}</a></th>
{% if min_supporters > 0 %}
<th><a href="?sort=supporter{% if 'supporter' in request.GET.sort and 'reverse' not in request.GET %}&reverse{%endif%}">{% trans "Number of supporters" %}</a></th>
{% endif %}
<th><a href="?sort=status{% if 'status' in request.GET.sort and 'reverse' not in request.GET %}&reverse{%endif%}">{% trans "Status" %}</a></th>
<th><a href="?sort=submitter{% if 'submitter' in request.GET.sort and 'reverse' not in request.GET %}&reverse{%endif%}">{% trans "Submitter" %}</a></th>
<th><a href="?sort=time{% if 'time' in request.GET.sort and 'reverse' not in request.GET %}&reverse{%endif%}">{% trans "Creation Time" %}<a></th>
<th style="width: 1px;">{% trans "Actions" %}</th>
</tr>
{% for app_info in motions %}
{% with motion=app_info.motion useractions=app_info.actions %}
<tr class="{% cycle '' 'odd' %}
{% if motion.active %}activeline{% endif %}">
<td>{% if motion.number %}{{ motion.number }}{% else %}-{% endif %}</td>
<td><a href="{% url 'motion_view' motion.id %}">{{ motion.public_version.title }}</a></td>
{% if min_supporters > 0 %}
<td>{{ motion.count_supporters }}</td>
{% endif %}
<td>{% if motion.status != "pub" %}
{{ motion.get_status_display }}<br>
{% endif %}
{% for note in motion.notes %}
{{ note }}
{% if not forloop.last %}<br>{%endif%}
{% endfor %}
</td>
<td>{{ motion.submitter }}</td>
<td>{{ motion.creation_time }}</td>
<td>
<span style="width: 1px; white-space: nowrap;">
{% if perms.projector.can_manage_projector %}
<a class="activate_link {% if motion.active %}active{% endif %}" href="{% url 'projector_activate_slide' motion.sid %}" title="{% trans 'Activate motion' %}">
<span></span>
</a>
{% endif %}
{% if perms.motion.can_manage_motion %}
<a href="{% url 'motion_edit' motion.id %}"><img src="{% static 'images/icons/edit.png' %}" title="{% trans 'Edit motion' %}"></a>
{% if "delete" in useractions %}
<a href="{% url 'motion_delete' motion.id %}"><img src="{% static 'images/icons/delete.png' %}" title="{% trans 'Delete motion' %}"></a>
{% endif %}
{% endif %}
<a href="{% url 'print_motion' motion.id %}" title="{% trans 'Motion as PDF' %}"><img src="{% static 'images/icons/pdf.png' %}"></a>
</span>
</td>
</tr>
{% endwith %}
{% empty %}
<tr>
<td colspan="7"><i>{% trans "No motions available." %}</i></td>
</tr>
{% endfor %}
</table>
{% endblock %}

View File

@ -1,64 +0,0 @@
{% extends 'motion/base_motion.html' %}
{% load i18n %}
{% load staticfiles %}
{% block title %}
{{ block.super }} {% trans "Motion" %} "{{ motion.public_version.title }}"
{{ ballot }}. {% trans "Vote" %}
{% endblock %}
{% block content %}
<h1>{{ motion.public_version.title }} ({% trans "Motion" %}
{{ motion.number }}) {{ ballot }}. {% trans "Vote" %}</h1>
<i class="helptext">{% trans "Special values" %}: -1 = {% trans 'majority' %}; -2 = {% trans 'undocumented' %}</i>
<form action="" method="post" class="small-form">{% csrf_token %}
{{ pre_form }}
<span id="poll_id" style="display:none">{{ poll.id }}</span>
<table class="table" style="width: auto;">
<tr>
<th>{% trans "Option" %}</th>
<th>{% trans "Votes" %}</th>
</tr>
{% for value in forms.0 %}
<tr>
<td>{{ value.label }}</td>
<td>{{ value.errors }}{{ value }}</td>
</tr>
{% endfor %}
<tr class="total">
<td>{% trans "Invalid votes" %}</td>
<td>{{ pollform.votesinvalid.errors }}{{ pollform.votesinvalid }}</td>
</tr>
<tr class="total">
<td>{% trans "Votes cast" %}</td>
<td>{{ pollform.votescast.errors }}{{ pollform.votescast }}</td>
</tr>
</table>
{{ post_form }}
<p>
<a href="{% url 'print_motion_poll' poll.id %}">
<button class="button" type="button" onclick="window.location='{% url 'print_motion_poll' poll.id %}'">
<span class="icon pdf">{% trans 'Ballot paper as PDF' %}</span>
</button>
</a>
</p>
<p>
<button class="button" type="submit">
<span class="icon ok">{% trans 'Save' %}</span>
</button>
<button class="button" type="submit" name="apply">
<span class="icon apply">{% trans 'Apply' %}</span>
</button>
<a href="{% url 'motion_view' motion.id %}">
<button class="button" type="button" onclick="window.location='{% url 'motion_view' motion.id %}'">
<span class="icon cancel">{% trans 'Cancel' %}</span>
</button>
</a>
</p>
</form>
{% endblock %}

View File

@ -1,303 +0,0 @@
{% extends "motion/base_motion.html" %}
{% load tags %}
{% load i18n %}
{% load staticfiles %}
{% block title %}{{ block.super }} {% trans "Motion" %} "{{ version.title }}"{% endblock %}
{% block submenu %}
{{ block.super }}
{% endblock %}
{% block content %}
<div id="sidebar">
<div class="box">
<h4>{% trans "Submitter" %}:</h4>
{{ motion.submitter }}
{% if min_supporters > 0 %}
<h4>{% trans "Supporters" %}: *</h4>
{% if not motion.supporters %}
-
{% else %}
<ol>
{% for supporter in motion.supporters %}
<li> {{ supporter }}</li>
{% endfor %}
</ol>
{% endif %}
{% endif %}
<h4>{% trans "Status" %}:</h4>
{% if motion.status != "pub" %}
{% trans motion.get_status_display %}
<br>
{% endif %}
{% for note in motion.notes %}
{{ note }}
{% if not forloop.last %}<br>{% endif %}
{% endfor %}
<h4>{% trans "Vote results" %}:</h4>
{% with motion.polls as polls %}
{% if not polls.exists %}
{% if perms.motion.can_manage_motion %}
{% if "genpoll" in actions %}
<a href="{% url 'motion_gen_poll' motion.id %}">
<span class="button">
<span class="icon statistics">{% trans 'New vote' %}</span>
</span>
</a>
{% else %}
-
{% endif %}
{% else %}
-
{% endif %}
{% endif %}
<ul class="results">
{% for poll in polls %}
{% if perms.motion.can_manage_motion or poll.has_votes %}
<li>
{% if perms.motion.can_manage_motion %}
<strong>{{ forloop.counter }}. {% trans "Vote" %} </strong>
<a class="icon edit" href="{% url 'motion_poll_view' poll.id %}" title="{% trans 'Edit Vote' %}">
<span></span>
</a>
<a class="icon delete" href="{% url 'motion_poll_delete' poll.id %}" title="{% trans 'Delete Vote' %}">
<span></span>
</a>
{% elif poll.has_votes %}
<strong>{{ forloop.counter }}. {% trans "Vote" %}:</strong>
{% endif %}
<br>
{% if poll.has_votes %}
{% with poll.get_options.0 as option %}
<img src="{% static 'images/icons/voting-yes.png' %}" title="{% trans 'Yes' %}"> {{ option.Yes }}<br>
<img src="{% static 'images/icons/voting-no.png' %}" title="{% trans 'No' %}"> {{ option.No }}<br>
<img src="{% static 'images/icons/voting-abstention.png' %}" title="{% trans 'Abstention' %}"> {{ option.Abstain }}<br>
<img src="{% static 'images/icons/voting-invalid.png' %}" title="{% trans 'Invalid' %}"> {{ poll.print_votesinvalid }}<br>
<div style="border-top: 1px solid; padding-top: 5px; margin: 5px 0; width: 10em;">
<img src="{% static 'images/icons/voting-total.png' %}" title="{% trans 'Votes cast' %}"> {{ poll.print_votescast }}
</div>
{% endwith %}
{% if perms.motion.can_manage_motion %}
{% if forloop.last %}
{% if "genpoll" in actions %}
<a href="{% url 'motion_gen_poll' motion.id %}">
<span class="button"><span class="icon statistics">{% trans 'New vote' %}</span></span>
</a>
{% endif %}
{% endif %}
{% endif %}
{% else %}
{% if perms.motion.can_manage_motion %}
<a href="{% url 'motion_poll_view' poll.id %}">
<span class="button"><span class="icon statistics">{% trans 'Enter result' %}</span></span>
</a>
{% endif %}
{% endif %}
</li>
{% endif %}
{% endfor %}
</ul>
{% endwith %}
<h4>{% trans "Creation Time" %}:</h4>
{{ motion.creation_time }}
<p></p>
{% if "wit" in actions and user == motion.submitter.user %}
<p></p>
<a href="{% url 'motion_set_status' motion.id 'wit' %}">
<span class="button"><span class="icon revert">{% trans 'Withdraw' %}</span></span>
</a>
{% endif %}
{% if perms.motion.can_support_motion and min_supporters > 0 %}
{% if "unsupport" in actions %}
<p></p>
<a href="{% url 'motion_unsupport' motion.id %}">
<span class="button"><span class="icon remove">{% trans 'Unsupport' %}</span></span>
</a>
{% endif %}
{% if "support" in actions %}
<p></p>
<a href="{% url 'motion_support' motion.id %}">
<span class="button"><span class="icon add">{% trans 'Support' %}</span></span>
</a>
{% endif %}
{% endif %}
</div>
{% if min_supporters > 0 %}
<small>* {% trans "minimum required supporters" %}: {{ min_supporters }}</small>
{% endif %}
<br><br>
{% if perms.motion.can_manage_motion %}
<div class="box">
<h4><b>{% trans "Manage motion" %}</b></h4>
{% if "pub" in actions or "per" in actions or "nop" in actions or "setnumber" in actions %}
<h4>{% trans "Formal validation" %}:</h4>
{% if "pub" in actions %}
<a href="{% url 'motion_set_status' motion.id 'pub' %}"><span class="button"><span class="icon ok-blue">{% trans 'Publish' %}</span></span></a>
{% endif %}
{% if "per" in actions %}
<a href="{% url 'motion_permit' motion.id %}"><span class="button"><span class="icon ok-blue">{% trans 'Permit' %}</span></span></a>
{% endif %}
{% if "nop" in actions %}
<a href="{% url 'motion_notpermit' motion.id %}"><span class="button"><span class="icon reject">{% trans 'Not permit (reject)' %}</span></span></a>
{% endif %}
{% if "setnumber" in actions %}
<a href="{% url 'motion_set_number' motion.id %}"><span class="button"><span class="icon number">{% trans 'Set number' %}</span></span></a>
{% endif %}
</p>
{% endif %}
<h4></h4>
{% if "acc" in actions or "rej" in actions %}
<h4>{% trans "Result after vote" %}:</h4>
{% if "acc" in actions %}
<a href="{% url 'motion_set_status' motion.id 'acc' %}">
<span class="button"><span class="icon done">{% trans 'Accepted' %}</span></span>
</a>
{% endif %}
{% if "rej" in actions %}
<a href="{% url 'motion_set_status' motion.id 'rej' %}">
<span class="button"><span class="icon reject">{% trans 'Rejected' %}</span></span>
</a>
{% endif %}
{% endif %}
{% if "adj" in actions or "noc" in actions or "com" in actions or "wit" in actions %}
<h4>{% trans 'Result after debate' %}:</h4>
{% if "adj" in actions %}
<a href="{% url 'motion_set_status' motion.id 'adj' %}"><span class="button">{% trans 'Adjourned' %}</span></a><br>
{% endif %}
{% if "noc" in actions %}
<a href="{% url 'motion_set_status' motion.id 'noc' %}'><span class="button">{% trans 'Not Concerned' %}</span></a><br>
{% endif %}
{% if "com" in actions %}
<a href="{% url 'motion_set_status' motion.id 'com' %}'><span class="button">{% trans 'Commited a bill' %}</span></a><br>
{% endif %}
{% if "wit" in actions %}
<a href="{% url 'motion_set_status' motion.id 'wit' %}'><span class="button">{% trans 'Withdrawed by submitter' %}</span></a>
{% endif %}
{% endif %}
<p></p>
<hr>
<h4>{% trans "For Administration only:" %}</h4>
<a href="{% url 'motion_reset' motion.id %}">
<span class="button"><span class="icon undo">{% trans 'Reset' %}</span></span>
</a>
</div>
{% endif %} {# end perms.motion.can_support_motion #}
</div> <!-- end sidebar -->
<div id="main">
<h1>
{{ version.title }}
({% trans "Motion" %}
{% if motion.number != None %}
{{ motion.number }})
{% else %}
<i>[{% trans "no number" %}]</i>)
{% endif %}
</h1>
{% trans "Version" %} {{ version.aid }}
{% if motion.public_version != motion.last_version %}
&#8901;
{% if version == motion.public_version %}
{% trans "This is not the newest version." %} <a href="{% url 'motion_view_newest' motion.id %}">{% trans "Go to version" %} {{ motion.last_version.aid }}.</a>
{% else %}
{% trans "This is not the authorized version." %} <a href="{% url 'motion_view' motion.id %}">{% trans "Go to version" %} {{ motion.public_version.aid }}.</a>
{% endif %}
{% endif %}
<h2>{% trans "Motion" %}:</h2>
{{ version.text|linebreaks }}
<h2>{% trans "Reason" %}:</h2>
{% if version.reason %}
{{ version.reason|linebreaks }}
{% else %}
{% endif %}
{% if motion.versions|length > 1 %}
<h2>{% trans "Version History" %}:</h2>
<table class="table valigntop" style="width: auto;">
<tr>
<th></th>
<th>{% trans "Version" %}</th>
<th>{% trans "Time" %}</th>
<th>{% trans "Title" %}</th>
<th>{% trans "Text" %}</th>
<th>{% trans "Reason" %}</th>
</tr>
{% for revision in motion.versions %}
<tr class="{% cycle 'odd' '' %}">
<td style="white-space:nowrap;">
{% if motion.status != "pub" %}
{% if revision == motion.permitted %}
<img title="{% trans 'Version authorized' %}" src="{% static 'images/icons/accept.png' %}">
{% else %}
{% if perms.motion.can_manage_motion %}
<a href="{% url 'motion_version_permit' revision.id %}"><img title="{% trans 'Permit Version' %}" src="{% static 'images/icons/accept-grey.png' %}"></a>
{% endif %}
{% if not revision.rejected and revision.id > motion.permitted.id and perms.motion.can_manage_motion %}
<a href="{% url 'motion_version_reject' revision.id %}"><img title="{% trans 'Reject Version' %}" src="{% static 'images/icons/reject-grey.png' %}"></a>
{% endif %}
{% endif %}
{% if revision.rejected %}
<img title="{% trans 'Version rejected' %}" src="{% static 'images/icons/reject.png' %}">
{% endif %}
{% endif %}
</td>
<td>{{ revision.aid }}</td>
<td><i>{{ revision.time }}</i></td>
<td>
{% ifchanged %}
<b>{{ revision.title }}</b>
{% else %}
<i>[{% trans "unchanged" %}]</i>
{% endifchanged %}
</td>
<td>
{% ifchanged %}
{{ revision.text|linebreaks }}
{% else %}
<i>[{% trans "unchanged" %}]</i>
{% endifchanged %}
</td>
<td>
{% ifchanged %}
{{ revision.reason|linebreaks }}
{% else %}
<i>[{% trans "unchanged" %}]</i>
{% endifchanged %}
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% if perms.motion.can_manage_motion %}
<h2>{% trans "Log" %}:</h2>
{{ motion.log|linebreaks }}
{% endif %}
</div>
{% endblock %}

View File

@ -1,34 +0,0 @@
{% load staticfiles %}
{% load i18n %}
{% load tags %}
<ul style="line-height: 180%">
{% for motion in motions %}
<li class="{% if motion.active %}activeline{% endif %}">
<a href="{% url 'projector_activate_slide' motion.sid %}" class="activate_link {% if motion.active %}active{% endif %}">
<div></div>
</a>
<a href="{% model_url motion 'delete' %}" title="{% trans 'Delete' %}" class="icon delete right">
<span></span>
</a>
<a href="{% model_url motion 'edit' %}" title="{% trans 'Edit' %}" class="icon edit right">
<span></span>
</a>
<a href="{% url 'projctor_preview_slide' motion.sid %}" title="{% trans 'Preview' %}" class="icon preview right">
<span></span>
</a>
<a href="{% model_url motion 'view' %}">
{{ motion.public_version.title }}
</a>
({% trans "motion" %}
{% if motion.number %}
{{ motion.number }})
{% else %}
<i>[{% trans "no number" %}]</i>)
{% endif %}
</li>
{% empty %}
<li>{% trans 'No motion available.' %}</li>
{% endfor %}
</ul>

View File

@ -12,130 +12,29 @@
from django.conf.urls import url, patterns from django.conf.urls import url, patterns
from openslides.motion.views import (MotionDelete, ViewPoll,
MotionPDF, MotionPollPDF, CreateAgendaItem, SupportView)
urlpatterns = patterns('openslides.motion.views', urlpatterns = patterns('openslides.motion.views',
url(r'^$', url(r'^$',
'overview', 'motion_list',
name='motion_overview', name='motion_list',
), ),
url(r'^(?P<motion_id>\d+)/$', url(r'^create/$',
'view', 'motion_create',
name='motion_view',
),
url(r'^(?P<motion_id>\d+)/agenda/$',
CreateAgendaItem.as_view(),
name='motion_create_agenda',
),
url(r'^(?P<motion_id>\d+)/newest/$',
'view',
{'newest': True},
name='motion_view_newest',
),
url(r'^new/$',
'edit',
name='motion_new', name='motion_new',
), ),
url(r'^import/$', url(r'^(?P<pk>\d+)/$',
'motion_import', 'motion_detail',
name='motion_import', name='motion_detail',
), ),
url(r'^(?P<motion_id>\d+)/edit/$', url(r'^(?P<pk>\d+)/edit/$',
'edit', 'motion_edit',
name='motion_edit', name='motion_edit',
), ),
url(r'^(?P<motion_id>\d+)/del/$', url(r'^(?P<pk>\d+)/version/(?P<version_id>\d+)/$',
MotionDelete.as_view(), 'motion_detail',
name='motion_delete', name='motion_version_detail',
),
url(r'^del/$',
MotionDelete.as_view(),
{ 'motion_id' : None , 'motion_ids' : None },
name='motion_delete',
),
url(r'^(?P<motion_id>\d+)/setnumber/$',
'set_number',
name='motion_set_number',
),
url(r'^(?P<motion_id>\d+)/setstatus/(?P<status>[a-z]{3})/$',
'set_status',
name='motion_set_status',
),
url(r'^(?P<motion_id>\d+)/permit/$',
'permit',
name='motion_permit',
),
url(r'^version/(?P<aversion_id>\d+)/permit/$',
'permit_version',
name='motion_version_permit',
),
url(r'^version/(?P<aversion_id>\d+)/reject/$',
'reject_version',
name='motion_version_reject',
),
url(r'^(?P<motion_id>\d+)/notpermit/$',
'notpermit',
name='motion_notpermit',
),
url(r'^(?P<motion_id>\d+)/reset/$',
'reset',
name='motion_reset',
),
url(r'^(?P<motion_id>\d+)/support/$',
SupportView.as_view(support=True),
name='motion_support',
),
url(r'^(?P<motion_id>\d+)/unsupport/$',
SupportView.as_view(support=False),
name='motion_unsupport',
),
url(r'^(?P<motion_id>\d+)/gen_poll/$',
'gen_poll',
name='motion_gen_poll',
),
url(r'^print/$',
MotionPDF.as_view(),
{'motion_id': None},
name='print_motions',
),
url(r'^(?P<motion_id>\d+)/print/$',
MotionPDF.as_view(),
name='print_motion',
),
url(r'^poll/(?P<poll_id>\d+)/print/$',
MotionPollPDF.as_view(),
name='print_motion_poll',
),
url(r'^poll/(?P<poll_id>\d+)/$',
ViewPoll.as_view(),
name='motion_poll_view',
),
url(r'^poll/(?P<poll_id>\d+)/del/$',
'delete_poll',
name='motion_poll_delete',
), ),
) )

View File

@ -2,950 +2,75 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
openslides.motion.views openslides.motion.views
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~
Views for the motion app. Views for the motion app.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS. :copyright: 2011, 2012 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
from reportlab.platypus import Paragraph
import csv
import os
from urlparse 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 django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib import messages
from django.db import transaction from django.db import transaction
from django.shortcuts import redirect from django.db.models import Model
from django.utils.translation import ugettext as _, ungettext from django.utils.translation import ugettext as _, ugettext_lazy
from django.views.generic.detail import SingleObjectMixin
from openslides.utils import csv_ext
from openslides.utils.pdf import stylesheet 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 ( from openslides.utils.views import (
PDFView, RedirectView, DeleteView, FormView, SingleObjectMixin, TemplateView, RedirectView, UpdateView, CreateView, DeleteView, PDFView,
QuestionMixin) DetailView, ListView)
from openslides.utils.person import get_person from openslides.utils.template import Tab
from openslides.config.models import config from openslides.utils.utils import html_strong
from openslides.projector.projector import Widget from openslides.projector.api import get_active_slide
from openslides.poll.views import PollFormView from openslides.projector.projector import Widget, SLIDE
from openslides.participant.api import gen_username, gen_password from .models import Motion
from openslides.participant.models import User, Group from .forms import MotionCreateForm, MotionUpdateForm
from openslides.agenda.models import Item
from openslides.motion.models import Motion, AVersion, MotionPoll
from openslides.motion.forms import (
MotionForm, MotionFormTrivialChanges, MotionManagerForm,
MotionManagerFormSupporter, MotionImportForm, ConfigForm)
@permission_required('motion.can_see_motion') from django.views.generic.edit import ModelFormMixin
@template('motion/overview.html')
def overview(request):
"""
View all motions
"""
try:
sortfilter = parse_qs(request.COOKIES['votecollector_sortfilter'])
for value in sortfilter:
sortfilter[value] = sortfilter[value][0]
except KeyError:
sortfilter = {}
for value in [u'sort', u'reverse', u'number', u'status', u'needsup', u'statusvalue']:
if value in request.REQUEST:
if request.REQUEST[value] == '0':
try:
del sortfilter[value]
except KeyError:
pass
else:
sortfilter[value] = request.REQUEST[value]
query = Motion.objects.all()
if 'number' in sortfilter:
query = query.filter(number=None)
if 'status' in sortfilter:
if 'statusvalue' in sortfilter and 'on' in sortfilter['status']:
query = query.filter(status__iexact=sortfilter['statusvalue'])
if 'sort' in sortfilter:
if sortfilter['sort'] == 'title':
sort = 'aversion__title'
elif sortfilter['sort'] == 'time':
sort = 'aversion__time'
else:
sort = sortfilter['sort']
query = query.order_by(sort)
if sort.startswith('aversion_'):
# limit result to last version of an motion
query = query.filter(aversion__id__in=[x.last_version.id for x in Motion.objects.all()])
if 'reverse' in sortfilter:
query = query.reverse()
# todo: rewrite this with a .filter()
if 'needsup' in sortfilter:
motions = []
for motion in query.all():
if not motion.enough_supporters:
motions.append(motion)
else:
motions = query
if type(motions) is not list:
motions = list(query.all())
# not the most efficient way to do this but 'get_allowed_actions'
# is not callable from within djangos templates..
for (i, motion) in enumerate(motions):
try:
motions[i] = {
'actions': motion.get_allowed_actions(request.user),
'motion': motion
}
except:
# todo: except what?
motions[i] = {
'actions': [],
'motion': motion
}
return {
'motions': motions,
'min_supporters': int(config['motion_min_supporters']),
}
@permission_required('motion.can_see_motion') class MotionListView(ListView):
@template('motion/view.html') permission_required = 'motion.can_see_motion'
def view(request, motion_id, newest=False):
"""
View one motion.
"""
motion = Motion.objects.get(pk=motion_id)
if newest:
version = motion.last_version
else:
version = motion.public_version
revisions = motion.versions
actions = motion.get_allowed_actions(user=request.user)
return {
'motion': motion,
'revisions': revisions,
'actions': actions,
'min_supporters': int(config['motion_min_supporters']),
'version': version,
#'results': motion.results
}
@login_required
@template('motion/edit.html')
def edit(request, motion_id=None):
"""
View a form to edit or create a motion.
"""
if request.user.has_perm('motion.can_manage_motion'):
is_manager = True
else:
is_manager = False
if not is_manager \
and not request.user.has_perm('motion.can_create_motion'):
messages.error(request, _("You have not the necessary rights to create or edit motions."))
return redirect(reverse('motion_overview'))
if motion_id is not None:
motion = Motion.objects.get(id=motion_id)
if not 'edit' in motion.get_allowed_actions(request.user):
messages.error(request, _("You can not edit this motion."))
return redirect(reverse('motion_view', args=[motion.id]))
actions = motion.get_allowed_actions(user=request.user)
else:
motion = None
actions = None
formclass = MotionFormTrivialChanges \
if config['motion_allow_trivial_change'] and motion_id \
else MotionForm
managerformclass = MotionManagerFormSupporter \
if config['motion_min_supporters'] \
else MotionManagerForm
if request.method == 'POST':
dataform = formclass(request.POST, prefix="data")
valid = dataform.is_valid()
if is_manager:
managerform = managerformclass(request.POST,
instance=motion,
prefix="manager")
valid = valid and managerform.is_valid()
else:
managerform = None
if valid:
if is_manager:
motion = managerform.save(commit=False)
elif motion_id is None:
motion = Motion(submitter=request.user)
motion.title = dataform.cleaned_data['title']
motion.text = dataform.cleaned_data['text']
motion.reason = dataform.cleaned_data['reason']
try:
trivial_change = config['motion_allow_trivial_change'] \
and dataform.cleaned_data['trivial_change']
except KeyError:
trivial_change = False
motion.save(request.user, trivial_change=trivial_change)
if is_manager:
try:
new_supporters = set(managerform.cleaned_data['supporter'])
except KeyError:
# The managerform has no field for the supporters
pass
else:
old_supporters = set(motion.supporters)
# add new supporters
for supporter in new_supporters.difference(old_supporters):
motion.support(supporter)
# remove old supporters
for supporter in old_supporters.difference(new_supporters):
motion.unsupport(supporter)
if motion_id is None:
messages.success(request, _('New motion was successfully created.'))
else:
messages.success(request, _('Motion was successfully modified.'))
if not 'apply' in request.POST:
return redirect(reverse('motion_view', args=[motion.id]))
if motion_id is None:
return redirect(reverse('motion_edit', args=[motion.id]))
else:
messages.error(request, _('Please check the form for errors.'))
else:
if motion_id is None:
initial = {'text': config['motion_preamble']}
else:
if motion.status == "pub" and motion.supporters:
if request.user.has_perm('motion.can_manage_motion'):
messages.warning(request, _("Attention: Do you really want to edit this motion? The supporters will <b>not</b> be removed automatically because you can manage motions. Please check if the supports are valid after your changing!"))
else:
messages.warning(request, _("Attention: Do you really want to edit this motion? All <b>%s</b> supporters will be removed! Try to convince the supporters again.") % motion.count_supporters() )
initial = {'title': motion.title,
'text': motion.text,
'reason': motion.reason}
dataform = formclass(initial=initial, prefix="data")
if is_manager:
if motion_id is None:
initial = {'submitter': request.user.person_id}
else:
initial = {'submitter': motion.submitter.person_id,
'supporter': [supporter.person_id for supporter in motion.supporters]}
managerform = managerformclass(initial=initial,
instance=motion, prefix="manager")
else:
managerform = None
return {
'form': dataform,
'managerform': managerform,
'motion': motion,
'actions': actions,
}
@permission_required('motion.can_manage_motion')
@template('motion/view.html')
def set_number(request, motion_id):
"""
set a number for an motion.
"""
try:
Motion.objects.get(pk=motion_id).set_number(user=request.user)
messages.success(request, _("Motion number was successfully set."))
except Motion.DoesNotExist:
pass
except NameError:
pass
return redirect(reverse('motion_view', args=[motion_id]))
@permission_required('motion.can_manage_motion')
@template('motion/view.html')
def permit(request, motion_id):
"""
permit an motion.
"""
try:
Motion.objects.get(pk=motion_id).permit(user=request.user)
messages.success(request, _("Motion was successfully authorized."))
except Motion.DoesNotExist:
pass
except NameError, e:
messages.error(request, e)
return redirect(reverse('motion_view', args=[motion_id]))
@permission_required('motion.can_manage_motion')
@template('motion/view.html')
def notpermit(request, motion_id):
"""
reject (not permit) an motion.
"""
try:
Motion.objects.get(pk=motion_id).notpermit(user=request.user)
messages.success(request, _("Motion was successfully rejected."))
except Motion.DoesNotExist:
pass
except NameError, e:
messages.error(request, e)
return redirect(reverse('motion_view', args=[motion_id]))
@template('motion/view.html')
def set_status(request, motion_id=None, status=None):
"""
set a status of an motion.
"""
try:
if status is not None:
motion = Motion.objects.get(pk=motion_id)
motion.set_status(user=request.user, status=status)
messages.success(request, _("Motion status was set to: <b>%s</b>.") % motion.get_status_display())
except Motion.DoesNotExist:
pass
except NameError, e:
messages.error(request, e)
return redirect(reverse('motion_view', args=[motion_id]))
@permission_required('motion.can_manage_motion')
@template('motion/view.html')
def reset(request, motion_id):
"""
reset an motion.
"""
try:
Motion.objects.get(pk=motion_id).reset(user=request.user)
messages.success(request, _("Motion status was reset.") )
except Motion.DoesNotExist:
pass
return redirect(reverse('motion_view', args=[motion_id]))
class SupportView(SingleObjectMixin, QuestionMixin, RedirectView):
"""
Classed based view to support or unsupport a motion. Use
support=True or support=False in urls.py
"""
permission_required = 'motion.can_support_motion'
model = Motion model = Motion
pk_url_kwarg = 'motion_id'
support = True
def get(self, request, *args, **kwargs): motion_list = MotionListView.as_view()
self.object = self.get_object()
return super(SupportView, self).get(request, *args, **kwargs)
def check_allowed_actions(self, request):
"""
Checks whether request.user can support or unsupport the motion.
Returns True or False.
"""
allowed_actions = self.object.get_allowed_actions(request.user)
if self.support and not 'support' in allowed_actions:
messages.error(request, _('You can not support this motion.'))
return False
elif not self.support and not 'unsupport' in allowed_actions:
messages.error(request, _('You can not unsupport this motion.'))
return False
else:
return True
def pre_redirect(self, request, *args, **kwargs):
if self.check_allowed_actions(request):
super(SupportView, self).pre_redirect(request, *args, **kwargs)
def get_question(self):
if self.support:
return _('Do you really want to support this motion?')
else:
return _('Do you really want to unsupport this motion?')
def case_yes(self):
if self.check_allowed_actions(self.request):
if self.support:
self.object.support(person=self.request.user)
else:
self.object.unsupport(person=self.request.user)
def get_success_message(self):
if self.support:
return _("You have supported this motion successfully.")
else:
return _("You have unsupported this motion successfully.")
def get_redirect_url(self, **kwargs):
return reverse('motion_view', args=[kwargs[self.pk_url_kwarg]])
@permission_required('motion.can_manage_motion') class MotionDetailView(DetailView):
@template('motion/view.html') permission_required = 'motion.can_see_motion'
def gen_poll(request, motion_id):
"""
gen a poll for this motion.
"""
try:
poll = Motion.objects.get(pk=motion_id).gen_poll(user=request.user)
messages.success(request, _("New vote was successfully created.") )
except Motion.DoesNotExist:
pass # TODO: do not call poll after this excaption
return redirect(reverse('motion_poll_view', args=[poll.id]))
@permission_required('motion.can_manage_motion')
def delete_poll(request, poll_id):
"""
delete a poll from this motion
"""
poll = MotionPoll.objects.get(pk=poll_id)
motion = poll.motion
count = motion.polls.filter(id__lte=poll_id).count()
if request.method == 'POST':
poll.delete()
motion.writelog(_("Poll deleted"), request.user)
messages.success(request, _('Poll was successfully deleted.'))
else:
del_confirm_form(request, poll, name=_("the %s. poll") % count, delete_link=reverse('motion_poll_delete', args=[poll_id]))
return redirect(reverse('motion_view', args=[motion.id]))
class MotionDelete(DeleteView):
"""
Delete one or more Motions.
"""
model = Motion model = Motion
url = 'motion_overview' template_name = 'motion/motion_detail.html'
def has_permission(self, request, *args, **kwargs):
self.kwargs = kwargs
return self.get_object().get_allowed_actions(request.user)
def get_object(self): def get_object(self):
self.motions = [] object = super(MotionDetailView, self).get_object()
version_id = self.kwargs.get('version_id', None)
if version_id is not None:
object.default_version = int(version_id) -1
return object
if self.kwargs.get('motion_id', None): motion_detail = MotionDetailView.as_view()
try:
return Motion.objects.get(id=int(self.kwargs['motion_id']))
except Motion.DoesNotExist:
return None
if self.kwargs.get('motion_ids', []):
for appid in self.kwargs['motion_ids']:
try:
self.motions.append(Motion.objects.get(id=int(appid)))
except Motion.DoesNotExist:
pass
if self.motions: class MotionMixin(object):
return self.motions[0] def manipulate_object(self, form):
return None for attr in ['title', 'text', 'reason']:
setattr(self.object, attr, form.cleaned_data[attr])
def pre_post_redirect(self, request, *args, **kwargs):
self.object = self.get_object()
if len(self.motions): class MotionCreateView(MotionMixin, CreateView):
for motion in self.motions: permission_required = 'motion.can_create_motion'
if not 'delete' in motion.get_allowed_actions(user=request.user): model = Motion
messages.error(request, _("You can not delete motion <b>%s</b>.") % motion) form_class = MotionCreateForm
continue
title = motion.title motion_create = MotionCreateView.as_view()
motion.delete(force=True)
messages.success(request, _("Motion <b>%s</b> was successfully deleted.") % title)
elif self.object:
if not 'delete' in self.object.get_allowed_actions(user=request.user):
messages.error(request, _("You can not delete motion <b>%s</b>.") % self.object)
elif self.get_answer() == 'yes':
title = self.object.title
self.object.delete(force=True)
messages.success(request, _("Motion <b>%s</b> was successfully deleted.") % title)
else:
messages.error(request, _("Invalid request"))
class MotionUpdateView(MotionMixin, UpdateView):
model = Motion
form_class = MotionUpdateForm
class ViewPoll(PollFormView): motion_edit = MotionUpdateView.as_view()
permission_required = 'motion.can_manage_motion'
poll_class = MotionPoll
template_name = 'motion/poll_view.html'
def get_context_data(self, **kwargs):
context = super(ViewPoll, self).get_context_data(**kwargs)
self.motion = self.poll.get_motion()
context['motion'] = self.motion
context['ballot'] = self.poll.get_ballot()
context['actions'] = self.motion.get_allowed_actions(user=self.request.user)
return context
def get_modelform_class(self):
cls = super(ViewPoll, self).get_modelform_class()
user = self.request.user
class ViewPollFormClass(cls):
def save(self, commit = True):
instance = super(ViewPollFormClass, self).save(commit)
motion = instance.motion
motion.writelog(_("Poll was updated"), user)
return instance
return ViewPollFormClass
def get_success_url(self):
if not 'apply' in self.request.POST:
return reverse('motion_view', args=[self.poll.motion.id])
return ''
@permission_required('motion.can_manage_motion')
def permit_version(request, aversion_id):
aversion = AVersion.objects.get(pk=aversion_id)
motion = aversion.motion
if request.method == 'POST':
motion.accept_version(aversion, user=request.user)
messages.success(request, _("Version <b>%s</b> accepted.") % (aversion.aid))
else:
gen_confirm_form(request, _('Do you really want to authorize version <b>%s</b>?') % aversion.aid, reverse('motion_version_permit', args=[aversion.id]))
return redirect(reverse('motion_view', args=[motion.id]))
@permission_required('motion.can_manage_motion')
def reject_version(request, aversion_id):
aversion = AVersion.objects.get(pk=aversion_id)
motion = aversion.motion
if request.method == 'POST':
if motion.reject_version(aversion, user=request.user):
messages.success(request, _("Version <b>%s</b> rejected.") % (aversion.aid))
else:
messages.error(request, _("ERROR by rejecting the version.") )
else:
gen_confirm_form(request, _('Do you really want to reject version <b>%s</b>?') % aversion.aid, reverse('motion_version_reject', args=[aversion.id]))
return redirect(reverse('motion_view', args=[motion.id]))
@permission_required('motion.can_manage_motion')
@template('motion/import.html')
def motion_import(request):
if request.method == 'POST':
form = MotionImportForm(request.POST, request.FILES)
if form.is_valid():
import_permitted = form.cleaned_data['import_permitted']
try:
# check for valid encoding (will raise UnicodeDecodeError if not)
request.FILES['csvfile'].read().decode('utf-8')
request.FILES['csvfile'].seek(0)
users_generated = 0
motions_generated = 0
motions_modified = 0
groups_assigned = 0
groups_generated = 0
with transaction.commit_on_success():
dialect = csv.Sniffer().sniff(request.FILES['csvfile'].readline())
dialect = csv_ext.patchup(dialect)
request.FILES['csvfile'].seek(0)
for (lno, line) in enumerate(csv.reader(request.FILES['csvfile'], dialect=dialect)):
# basic input verification
if lno < 1:
continue
try:
(number, title, text, reason, first_name, last_name, is_group) = line[:7]
if is_group.strip().lower() in ['y', 'j', 't', 'yes', 'ja', 'true', '1', 1]:
is_group = True
else:
is_group = False
except ValueError:
messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1))
continue
form = MotionForm({'title': title, 'text': text, 'reason': reason})
if not form.is_valid():
messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1))
continue
if number:
try:
number = abs(long(number))
if number < 1:
messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1))
continue
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:
user = Group.objects.get(name=last_name)
if user.group_as_person == False:
messages.error(request, _('Ignoring line %d because the assigned group may not act as a person.') % (lno + 1))
continue
else:
user = get_person(user.person_id)
groups_assigned += 1
except Group.DoesNotExist:
group = Group()
group.group_as_person = True
group.description = _('Created by motion import.')
group.name = last_name
group.save()
groups_generated += 1
user = get_person(group.person_id)
else:
# fetch existing users or create new users as needed
try:
user = User.objects.get(first_name=first_name, last_name=last_name)
except User.DoesNotExist:
user = None
if user is None:
if not first_name or not last_name:
messages.error(request, _('Ignoring line %d because it contains an incomplete first / last name pair.') % (lno + 1))
continue
user = User()
user.last_name = last_name
user.first_name = first_name
user.username = gen_username(first_name, last_name)
user.structure_level = ''
user.committee = ''
user.gender = ''
user.type = ''
user.default_password = gen_password()
user.save()
user.reset_password()
users_generated += 1
# create / modify the motion
motion = None
if number:
try:
motion = Motion.objects.get(number=number)
motions_modified += 1
except Motion.DoesNotExist:
motion = None
if motion is None:
motion = Motion(submitter=user)
if number:
motion.number = number
motions_generated += 1
motion.title = form.cleaned_data['title']
motion.text = form.cleaned_data['text']
motion.reason = form.cleaned_data['reason']
if import_permitted:
motion.status = 'per'
motion.save(user, trivial_change=True)
if motions_generated:
messages.success(request, ungettext('%d motion was successfully imported.',
'%d motions were successfully imported.', motions_generated) % motions_generated)
if motions_modified:
messages.success(request, ungettext('%d motion was successfully modified.',
'%d motions were successfully modified.', motions_modified) % motions_modified)
if users_generated:
messages.success(request, ungettext('%d new user was added.', '%d new users were added.', users_generated) % users_generated)
if groups_generated:
messages.success(request, ungettext('%d new group was added.', '%d new groups were added.', groups_generated) % groups_generated)
if groups_assigned:
messages.success(request, ungettext('%d group assigned to motions.', '%d groups assigned to motions.', groups_assigned) % groups_assigned)
return redirect(reverse('motion_overview'))
except csv.Error:
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:
messages.error(request, _('Please check the form for errors.'))
else:
messages.warning(request, _("Attention: Existing motions will be modified if you import new motions with the same number."))
messages.warning(request, _("Attention: Importing an motions without a number multiple times will create duplicates."))
form = MotionImportForm()
return {
'form': form,
}
class CreateAgendaItem(RedirectView):
permission_required = 'agenda.can_manage_agenda'
def pre_redirect(self, request, *args, **kwargs):
self.motion = Motion.objects.get(pk=kwargs['motion_id'])
self.item = Item(related_sid=self.motion.sid)
self.item.save()
def get_redirect_url(self, **kwargs):
return reverse('item_overview')
class MotionPDF(PDFView):
permission_required = 'motion.can_see_motion'
top_space = 0
def get_filename(self):
motion_id = self.kwargs['motion_id']
if motion_id is None:
filename = _("Motions")
else:
motion = Motion.objects.get(id=motion_id)
if motion.number:
number = motion.number
else:
number = ""
filename = u'%s%s' % (_("Motion"), str(number))
return filename
def append_to_pdf(self, story):
motion_id = self.kwargs['motion_id']
if motion_id is None: #print all motions
title = config["motion_pdf_title"]
story.append(Paragraph(title, stylesheet['Heading1']))
preamble = config["motion_pdf_preamble"]
if preamble:
story.append(Paragraph("%s" % preamble.replace('\r\n','<br/>'), stylesheet['Paragraph']))
story.append(Spacer(0,0.75*cm))
motions = Motion.objects.all()
if not motions: # No motions existing
story.append(Paragraph(_("No motions available."), stylesheet['Heading3']))
else: # Print all Motions
# List of motions
for motion in motions:
if motion.number:
story.append(Paragraph(_("Motion No.")+" %s: %s" % (motion.number, motion.title), stylesheet['Heading3']))
else:
story.append(Paragraph(_("Motion No.")+"&nbsp;&nbsp;&nbsp;: %s" % (motion.title), stylesheet['Heading3']))
# Motions details (each motion on single page)
for motion in motions:
story.append(PageBreak())
story = self.get_motion(motion, story)
else: # print selected motion
motion = Motion.objects.get(id=motion_id)
story = self.get_motion(motion, story)
def get_motion(self, motion, story):
# Preparing Table
data = []
# motion number
if motion.number:
story.append(Paragraph(_("Motion No.")+" %s" % motion.number, stylesheet['Heading1']))
else:
story.append(Paragraph(_("Motion No."), stylesheet['Heading1']))
# submitter
cell1a = []
cell1a.append(Spacer(0, 0.2 * cm))
cell1a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Submitter"), stylesheet['Heading4']))
cell1b = []
cell1b.append(Spacer(0, 0.2 * cm))
cell1b.append(Paragraph("%s" % motion.submitter, stylesheet['Normal']))
data.append([cell1a, cell1b])
if motion.status == "pub":
# Cell for the signature
cell2a = []
cell2b = []
cell2a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Signature"), stylesheet['Heading4']))
cell2b.append(Paragraph("__________________________________________", stylesheet['Signaturefield']))
cell2b.append(Spacer(0, 0.1 * cm))
cell2b.append(Spacer(0,0.2*cm))
data.append([cell2a, cell2b])
# supporters
if config['motion_min_supporters']:
cell3a = []
cell3b = []
cell3a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font><seqreset id='counter'>" % _("Supporters"), stylesheet['Heading4']))
for supporter in motion.supporters:
cell3b.append(Paragraph("<seq id='counter'/>.&nbsp; %s" % supporter, stylesheet['Signaturefield']))
if motion.status == "pub":
for x in range(motion.missing_supporters):
cell3b.append(Paragraph("<seq id='counter'/>.&nbsp; __________________________________________",stylesheet['Signaturefield']))
cell3b.append(Spacer(0, 0.2 * cm))
data.append([cell3a, cell3b])
# status
cell4a = []
cell4b = []
note = " ".join(motion.notes)
cell4a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Status"), stylesheet['Heading4']))
if note != "":
if motion.status == "pub":
cell4b.append(Paragraph(note, stylesheet['Normal']))
else:
cell4b.append(Paragraph("%s | %s" % (motion.get_status_display(), note), stylesheet['Normal']))
else:
cell4b.append(Paragraph("%s" % motion.get_status_display(), stylesheet['Normal']))
data.append([cell4a, cell4b])
# Version number (aid)
if motion.public_version.aid > 1:
cell5a = []
cell5b = []
cell5a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Version"), stylesheet['Heading4']))
cell5b.append(Paragraph("%s" % motion.public_version.aid, stylesheet['Normal']))
data.append([cell5a, cell5b])
# voting results
poll_results = motion.get_poll_results()
if poll_results:
cell6a = []
cell6a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Vote results"), stylesheet['Heading4']))
cell6b = []
ballotcounter = 0
for result in poll_results:
ballotcounter += 1
if len(poll_results) > 1:
cell6b.append(Paragraph("%s. %s" % (ballotcounter, _("Vote")), stylesheet['Bold']))
cell6b.append(Paragraph("%s: %s <br/> %s: %s <br/> %s: %s <br/> %s: %s <br/> %s: %s" % (_("Yes"), result[0], _("No"), result[1], _("Abstention"), result[2], _("Invalid"), result[3], _("Votes cast"), result[4]), stylesheet['Normal']))
cell6b.append(Spacer(0, 0.2*cm))
data.append([cell6a, cell6b])
# Creating Table
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')]))
story.append(t)
story.append(Spacer(0, 1 * cm))
# title
story.append(Paragraph(motion.public_version.title, stylesheet['Heading3']))
# text
story.append(Paragraph("%s" % motion.public_version.text.replace('\r\n','<br/>'), stylesheet['Paragraph']))
# reason
if motion.public_version.reason:
story.append(Paragraph(_("Reason")+":", stylesheet['Heading3']))
story.append(Paragraph("%s" % motion.public_version.reason.replace('\r\n','<br/>'), stylesheet['Paragraph']))
return story
class MotionPollPDF(PDFView):
permission_required = 'motion.can_manage_motion'
top_space = 0
def get(self, request, *args, **kwargs):
self.poll = MotionPoll.objects.get(id=self.kwargs['poll_id'])
return super(MotionPollPDF, self).get(request, *args, **kwargs)
def get_filename(self):
filename = u'%s%s_%s' % (_("Motion"), str(self.poll.motion.number), _("Poll"))
return filename
def get_template(self, buffer):
return SimpleDocTemplate(buffer, topMargin=-6, bottomMargin=-6, leftMargin=0, rightMargin=0, showBoundary=False)
def build_document(self, pdf_document, story):
pdf_document.build(story)
def append_to_pdf(self, story):
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(_("Motion No. %s") % self.poll.motion.number, stylesheet['Ballot_title']))
cell.append(Paragraph(self.poll.motion.title, stylesheet['Ballot_subtitle']))
cell.append(Paragraph(_("%d. Vote") % self.poll.get_ballot(), stylesheet['Ballot_description']))
cell.append(Spacer(0,0.5*cm))
cell.append(Paragraph(circle + unicode(_("Yes")), stylesheet['Ballot_option']))
cell.append(Paragraph(circle + unicode(_("No")), stylesheet['Ballot_option']))
cell.append(Paragraph(circle + unicode(_("Abstention")), stylesheet['Ballot_option']))
data= []
# get ballot papers config values
ballot_papers_selection = config["motion_pdf_ballot_papers_selection"]
ballot_papers_number = config["motion_pdf_ballot_papers_number"]
# set number of ballot papers
if ballot_papers_selection == "NUMBER_OF_DELEGATES":
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"
number = int(ballot_papers_number)
number = max(1, number)
# print ballot papers
if number > 0:
for user in xrange(number / 2):
data.append([cell, cell])
rest = number % 2
if rest:
data.append([cell, ''])
t=Table(data, 10.5 * cm, 7.42 * cm)
t.setStyle(TableStyle([('GRID', (0, 0), (-1, -1), 0.25, colors.grey),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
]))
story.append(t)
class Config(FormView):
permission_required = 'config.can_manage_config'
form_class = ConfigForm
template_name = 'motion/config.html'
def get_initial(self):
return {
'motion_min_supporters': config['motion_min_supporters'],
'motion_preamble': config['motion_preamble'],
'motion_pdf_ballot_papers_selection': config['motion_pdf_ballot_papers_selection'],
'motion_pdf_ballot_papers_number': config['motion_pdf_ballot_papers_number'],
'motion_pdf_title': config['motion_pdf_title'],
'motion_pdf_preamble': config['motion_pdf_preamble'],
'motion_allow_trivial_change': config['motion_allow_trivial_change'],
}
def form_valid(self, form):
config['motion_min_supporters'] = form.cleaned_data['motion_min_supporters']
config['motion_preamble'] = form.cleaned_data['motion_preamble']
config['motion_pdf_ballot_papers_selection'] = form.cleaned_data['motion_pdf_ballot_papers_selection']
config['motion_pdf_ballot_papers_number'] = form.cleaned_data['motion_pdf_ballot_papers_number']
config['motion_pdf_title'] = form.cleaned_data['motion_pdf_title']
config['motion_pdf_preamble'] = form.cleaned_data['motion_pdf_preamble']
config['motion_allow_trivial_change'] = form.cleaned_data['motion_allow_trivial_change']
messages.success(self.request, _('Motion settings successfully saved.'))
return super(Config, self).form_valid(form)
def register_tab(request):
selected = True if request.path.startswith('/motion/') else False
return Tab(
title=_('Motions'),
url=reverse('motion_overview'),
permission=request.user.has_perm('motion.can_see_motion') or request.user.has_perm('motion.can_support_motion') or request.user.has_perm('motion.can_support_motion') or request.user.has_perm('motion.can_manage_motion'),
selected=selected,
)
def get_widgets(request):
return [
Widget(
name='motions',
display_name=_('Motions'),
template='motion/widget.html',
context={'motions': Motion.objects.all()},
permission_required='projector.can_manage_projector')]

View File

@ -554,30 +554,30 @@ def get_widgets(request):
group_widget and a personal_info_widget. group_widget and a personal_info_widget.
""" """
return [ return [
get_personal_info_widget(request), #get_personal_info_widget(request),
get_user_widget(request), get_user_widget(request),
get_group_widget(request)] get_group_widget(request)]
def get_personal_info_widget(request): ## def get_personal_info_widget(request):
""" ## """
Provides a widget for personal info. It shows your submitted motions ## Provides a widget for personal info. It shows your submitted motions
and where you are supporter or candidate. ## and where you are supporter or candidate.
""" ## """
personal_info_context = { ## personal_info_context = {
'submitted_motions': Motion.objects.filter(submitter=request.user), ## 'submitted_motions': Motion.objects.filter(submitter=request.user),
'config_motion_min_supporters': config['motion_min_supporters'], ## 'config_motion_min_supporters': config['motion_min_supporters'],
'supported_motions': Motion.objects.filter(motionsupporter=request.user), ## 'supported_motions': Motion.objects.filter(motionsupporter=request.user),
'assignments': Assignment.objects.filter( ## 'assignments': Assignment.objects.filter(
assignmentcandidate__person=request.user, ## assignmentcandidate__person=request.user,
assignmentcandidate__blocked=False)} ## assignmentcandidate__blocked=False)}
return Widget( ## return Widget(
name='personal_info', ## name='personal_info',
display_name=_('My motions and elections'), ## display_name=_('My motions and elections'),
template='participant/personal_info_widget.html', ## template='participant/personal_info_widget.html',
context=personal_info_context, ## context=personal_info_context,
permission_required=None, ## permission_required=None,
default_column=1) ## default_column=1)
def get_user_widget(request): def get_user_widget(request):

View File

@ -41,6 +41,7 @@ from django.views.generic import (
View as _View, View as _View,
FormView as _FormView, FormView as _FormView,
ListView as _ListView, ListView as _ListView,
DetailView as _DetailView,
) )
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from django.views.generic.list import TemplateResponseMixin from django.views.generic.list import TemplateResponseMixin
@ -98,6 +99,31 @@ class AjaxMixin(object):
return HttpResponse(json.dumps(self.get_ajax_context(**kwargs))) return HttpResponse(json.dumps(self.get_ajax_context(**kwargs)))
class ExtraContextMixin(object):
def get_context_data(self, **kwargs):
context = super(ExtraContextMixin, self).get_context_data(**kwargs)
template_manipulation.send(
sender=self.__class__, request=self.request, context=context)
return context
class SuccessUrlMixin(object):
def get_success_url(self):
messages.success(self.request, self.get_success_message())
if 'apply' in self.request.POST:
return reverse(self.get_apply_url(), args=[self.object.id])
if self.success_url:
url = reverse(success_url)
else:
try:
url = self.object.get_absolute_url()
except AttributeError:
raise ImproperlyConfigured(
"No URL to redirect to. Either provide a url or define"
" a get_absolute_url method on the Model.")
return url
class QuestionMixin(object): class QuestionMixin(object):
question = ugettext_lazy('Are you sure?') question = ugettext_lazy('Are you sure?')
success_message = ugettext_lazy('Thank you for your answer') success_message = ugettext_lazy('Thank you for your answer')
@ -157,21 +183,12 @@ class QuestionMixin(object):
return self.success_message return self.success_message
class TemplateView(PermissionMixin, _TemplateView): class TemplateView(PermissionMixin, ExtraContextMixin, _TemplateView):
def get_context_data(self, **kwargs): pass
context = super(TemplateView, self).get_context_data(**kwargs)
template_manipulation.send(
sender=self.__class__, request=self.request, context=context)
return context
class ListView(PermissionMixin, SetCookieMixin, _ListView): class ListView(PermissionMixin, SetCookieMixin, ExtraContextMixin, _ListView):
def get_context_data(self, **kwargs): pass
context = super(ListView, self).get_context_data(**kwargs)
template_manipulation.send(
sender=self.__class__, request=self.request, context=context)
return context
class AjaxView(PermissionMixin, AjaxMixin, View): class AjaxView(PermissionMixin, AjaxMixin, View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
@ -202,36 +219,26 @@ class RedirectView(PermissionMixin, AjaxMixin, _RedirectView):
return reverse(super(RedirectView, self).get_redirect_url(**kwargs)) return reverse(super(RedirectView, self).get_redirect_url(**kwargs))
class FormView(PermissionMixin, _FormView): class FormView(PermissionMixin, ExtraContextMixin, SuccessUrlMixin, _FormView):
def get_success_url(self):
if not self.success_url:
return ''
return reverse(super(FormView, self).get_success_url())
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)
return context
def form_invalid(self, form): def form_invalid(self, form):
messages.error(self.request, _('Please check the form for errors.')) messages.error(self.request, _('Please check the form for errors.'))
return super(FormView, self).form_invalid(form) return super(FormView, self).form_invalid(form)
class UpdateView(PermissionMixin, _UpdateView): class ModelFormMixin(object):
def get_success_url(self): def form_valid(self, form):
messages.success(self.request, self.get_success_message()) self.object = form.save(commit=False)
if 'apply' in self.request.POST: self.manipulate_object(form)
return '' self.object.save()
return reverse(super(UpdateView, self).get_success_url()) form.save_m2m()
return HttpResponseRedirect(self.get_success_url())
def get_context_data(self, **kwargs): def manipulate_object(self, form):
context = super(UpdateView, self).get_context_data(**kwargs) pass
template_manipulation.send(
sender=self.__class__, request=self.request, context=context)
return context
class UpdateView(PermissionMixin, SuccessUrlMixin, ExtraContextMixin,
ModelFormMixin, _UpdateView):
def form_invalid(self, form): def form_invalid(self, form):
messages.error(self.request, _('Please check the form for errors.')) messages.error(self.request, _('Please check the form for errors.'))
return super(UpdateView, self).form_invalid(form) return super(UpdateView, self).form_invalid(form)
@ -240,46 +247,25 @@ class UpdateView(PermissionMixin, _UpdateView):
return _('%s was successfully modified.') % html_strong(self.object) return _('%s was successfully modified.') % html_strong(self.object)
class CreateView(PermissionMixin, _CreateView): class CreateView(PermissionMixin, SuccessUrlMixin, ExtraContextMixin,
ModelFormMixin, _CreateView):
apply_url = None apply_url = None
success_url = None
def get_success_url(self):
messages.success(self.request, self.get_success_message())
if 'apply' in self.request.POST:
return reverse(self.get_apply_url(), args=[self.object.id])
return reverse(super(CreateView, self).get_success_url())
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)
return context
def get_apply_url(self): def get_apply_url(self):
if self apply_url: if self.apply_url:
return self.apply_url return self.apply_url
else: else:
raise ImproperlyConfigured( raise ImproperlyConfigured(
"No URL to redirect to. Provide a apply_url.") "No URL to redirect to. Provide a apply_url.")
def form_invalid(self, form): def form_invalid(self, form):
messages.error(self.request, _('Please check the form for errors.')) messages.error(self.request, _('Please check the form for errors.'))
return super(CreateView, self).form_invalid(form) return super(CreateView, self).form_invalid(form)
def form_valid(self, form):
self.object = form.save(commit=False)
self.manipulate_object(form)
self.object.save()
form.save_m2m()
return HttpResponseRedirect(self.get_success_url())
def get_success_message(self): def get_success_message(self):
return _('%s was successfully created.') % html_strong(self.object) return _('%s was successfully created.') % html_strong(self.object)
def manipulate_object(self, form):
pass
class DeleteView(SingleObjectMixin, QuestionMixin, RedirectView): class DeleteView(SingleObjectMixin, QuestionMixin, RedirectView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
@ -296,16 +282,11 @@ class DeleteView(SingleObjectMixin, QuestionMixin, RedirectView):
return _('%s was successfully deleted.') % html_strong(self.object) return _('%s was successfully deleted.') % html_strong(self.object)
class DetailView(TemplateView, SingleObjectMixin): class DetailView(PermissionMixin, ExtraContextMixin, _DetailView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
return super(DetailView, self).get(request, *args, **kwargs) return super(DetailView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
context.update(SingleObjectMixin.get_context_data(self, **kwargs))
return context
class PDFView(PermissionMixin, View): class PDFView(PermissionMixin, View):
filename = _('undefined-filename') filename = _('undefined-filename')