Feature: amendments
This commit is contained in:
parent
a93e5aa785
commit
2951b4b38c
12
CHANGELOG
12
CHANGELOG
@ -4,8 +4,16 @@
|
||||
|
||||
http://openslides.org
|
||||
|
||||
Version 1.6.2 (unreleased)
|
||||
==========================
|
||||
Version 1.7 (unreleased)
|
||||
========================
|
||||
[https://github.com/OpenSlides/OpenSlides/milestones/1.7]
|
||||
|
||||
motion:
|
||||
- New Feature to create amendments, which are related to a parent motion.
|
||||
|
||||
|
||||
Version 1.6.2
|
||||
=============
|
||||
[https://github.com/OpenSlides/OpenSlides/milestones/1.6.2]
|
||||
Motions:
|
||||
- Added possibility to hide motions from non staff users in some states.
|
||||
|
@ -3,7 +3,6 @@
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy
|
||||
|
||||
from openslides.config.api import config
|
||||
from openslides.mediafile.models import Mediafile
|
||||
from openslides.utils.forms import (CleanHtmlFormMixin, CssClassMixin,
|
||||
CSVImportForm, LocalizedModelChoiceField)
|
||||
@ -66,8 +65,6 @@ class BaseMotionForm(CleanHtmlFormMixin, CssClassMixin, forms.ModelForm):
|
||||
self.initial['text'] = last_version.text
|
||||
self.initial['reason'] = last_version.reason
|
||||
self.initial['attachments'] = self.motion.attachments.all()
|
||||
else:
|
||||
self.initial['text'] = config['motion_preamble']
|
||||
super(BaseMotionForm, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
|
@ -71,8 +71,12 @@ class Motion(SlideMixin, AbsoluteUrlMixin, models.Model):
|
||||
Many to many relation to mediafile objects.
|
||||
"""
|
||||
|
||||
# TODO: proposal
|
||||
# master = models.ForeignKey('self', null=True, blank=True)
|
||||
parent = models.ForeignKey('self', null=True, blank=True, related_name='amendments')
|
||||
"""
|
||||
Field for amendments to reference to the motion that should be altered.
|
||||
|
||||
Null if the motion is not an amendment.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
@ -206,19 +210,31 @@ class Motion(SlideMixin, AbsoluteUrlMixin, models.Model):
|
||||
|
||||
def set_identifier(self):
|
||||
"""
|
||||
Sets the motion identifier automaticly according to the config
|
||||
value if it is not set yet.
|
||||
Sets the motion identifier automaticly according to the config value if
|
||||
it is not set yet.
|
||||
"""
|
||||
# The identifier is already set or should be set manually
|
||||
if config['motion_identifier'] == 'manually' or self.identifier:
|
||||
# Do not set an identifier.
|
||||
return
|
||||
|
||||
# The motion is an amendment
|
||||
elif self.is_amendment():
|
||||
motions = self.parent.amendments.all()
|
||||
|
||||
# The motions should be counted per category
|
||||
elif config['motion_identifier'] == 'per_category':
|
||||
motions = Motion.objects.filter(category=self.category)
|
||||
else: # That means: config['motion_identifier'] == 'serially_numbered'
|
||||
|
||||
# The motions should be counted over all.
|
||||
else:
|
||||
motions = Motion.objects.all()
|
||||
|
||||
number = motions.aggregate(Max('identifier_number'))['identifier_number__max'] or 0
|
||||
if self.category is None or not self.category.prefix:
|
||||
if self.is_amendment():
|
||||
parent_identifier = self.parent.identifier or ''
|
||||
prefix = '%s %s ' % (parent_identifier, config['motion_amendments_prefix'])
|
||||
elif self.category is None or not self.category.prefix:
|
||||
prefix = ''
|
||||
else:
|
||||
prefix = '%s ' % self.category.prefix
|
||||
@ -524,6 +540,15 @@ class Motion(SlideMixin, AbsoluteUrlMixin, models.Model):
|
||||
"""
|
||||
MotionLog.objects.create(motion=self, message_list=message_list, person=person)
|
||||
|
||||
def is_amendment(self):
|
||||
"""
|
||||
Returns True if the motion is an amendment.
|
||||
|
||||
A motion is a amendment if amendments are activated in the config and
|
||||
the motion has a parent.
|
||||
"""
|
||||
return config['motion_amendments_enabled'] and self.parent is not None
|
||||
|
||||
|
||||
class MotionVersion(AbsoluteUrlMixin, models.Model):
|
||||
"""
|
||||
@ -819,10 +844,12 @@ class State(models.Model):
|
||||
"""If true, new versions are not automaticly set active."""
|
||||
|
||||
dont_set_identifier = models.BooleanField(default=False)
|
||||
"""Decides if the motion gets an identifier.
|
||||
"""
|
||||
Decides if the motion gets an identifier.
|
||||
|
||||
If true, the motion does not get an identifier if the state change to
|
||||
this one, else it does."""
|
||||
this one, else it does.
|
||||
"""
|
||||
|
||||
def __unicode__(self):
|
||||
"""Returns the name of the state."""
|
||||
|
@ -3,7 +3,7 @@
|
||||
from django import forms
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||
from django.utils.translation import ugettext_lazy, ugettext_noop, pgettext
|
||||
|
||||
from openslides.config.api import ConfigGroup, ConfigGroupedCollection, ConfigVariable
|
||||
from openslides.config.signals import config_signal
|
||||
@ -67,6 +67,25 @@ def setup_motion_config(sender, **kwargs):
|
||||
motion_stop_submitting,
|
||||
motion_allow_disable_versioning))
|
||||
|
||||
# Amendments
|
||||
motion_amendments_enabled = ConfigVariable(
|
||||
name='motion_amendments_enabled',
|
||||
default_value=False,
|
||||
form_field=forms.BooleanField(
|
||||
label=ugettext_lazy('Activate amendments'),
|
||||
required=False))
|
||||
|
||||
motion_amendments_prefix = ConfigVariable(
|
||||
name='motion_amendments_prefix',
|
||||
default_value=pgettext('Prefix for amendment', 'A'),
|
||||
form_field=forms.CharField(
|
||||
required=False,
|
||||
label=ugettext_lazy('Prefix for the identifier for amendments')))
|
||||
|
||||
group_amendments = ConfigGroup(
|
||||
title=ugettext_lazy('Amendments'),
|
||||
variables=(motion_amendments_enabled, motion_amendments_prefix))
|
||||
|
||||
# Supporters
|
||||
motion_min_supporters = ConfigVariable(
|
||||
name='motion_min_supporters',
|
||||
@ -148,7 +167,8 @@ def setup_motion_config(sender, **kwargs):
|
||||
title=ugettext_noop('Motion'),
|
||||
url='motion',
|
||||
weight=30,
|
||||
groups=(group_general, group_supporters, group_ballot_papers, group_pdf))
|
||||
groups=(group_general, group_amendments, group_supporters,
|
||||
group_ballot_papers, group_pdf))
|
||||
|
||||
|
||||
@receiver(post_database_setup, dispatch_uid='motion_create_builtin_workflows')
|
||||
|
@ -29,6 +29,9 @@
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if motion.is_amendment %}
|
||||
(Amendment of <a href="{{ motion.parent|absolute_url }}">{{ motion.parent.identifier|default:motion.parent }}</a>)
|
||||
{% endif %}
|
||||
</small>
|
||||
<small class="pull-right">
|
||||
<a href="{% url 'motion_list' %}" class="btn btn-mini">
|
||||
@ -280,6 +283,23 @@
|
||||
</h5>
|
||||
{{ version.creation_time }}
|
||||
|
||||
{% if 'motion_amendments_enabled'|get_config %}
|
||||
<h5>{% trans 'Amendments' %}:</h5>
|
||||
{% with amendments=motion.amendments.all %}
|
||||
{% if amendments %}
|
||||
<ul>
|
||||
{% for amendment in amendments %}
|
||||
<li><a href="{{ amendment|absolute_url }}">{{ amendment }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<a class="btn btn-mini btn-primary" href="{% url 'motion_create_amendment' motion.pk %}">
|
||||
<i class="icon-plus icon-white"></i>
|
||||
{% trans 'New amendment' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<!-- Support/Unsupport button -->
|
||||
{% if perms.motion.can_support_motion and 'motion_min_supporters'|get_config > 0 %}
|
||||
{% if allowed_actions.unsupport %}
|
||||
|
@ -33,7 +33,7 @@
|
||||
<small class="pull-right">
|
||||
{% if perms.motion.can_create_motion %}
|
||||
{% if not 'motion_stop_submitting'|get_config or perms.motion.can_manage_motion %}
|
||||
<a href="{% url 'motion_new' %}" class="btn btn-mini btn-primary" rel="tooltip" data-original-title="{% trans 'New motion' %}"><i class="icon-plus icon-white"></i> {% trans 'New' %}</a>
|
||||
<a href="{% url 'motion_create' %}" class="btn btn-mini btn-primary" rel="tooltip" data-original-title="{% trans 'New motion' %}"><i class="icon-plus icon-white"></i> {% trans 'New' %}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if perms.motion.can_manage_motion %}
|
||||
@ -64,7 +64,14 @@
|
||||
{% for motion in motion_list %}
|
||||
<tr class="{% if motion.is_active_slide %}activeline{% endif %}">
|
||||
<td class="nobr">{{ motion.identifier|default:'' }}</td>
|
||||
<td><a href="{{ motion|absolute_url }}">{{ motion.title }}</a></td>
|
||||
<td>
|
||||
<a href="{{ motion|absolute_url }}">{{ motion.title }}</a>
|
||||
{% if motion.is_amendment %}
|
||||
<a class="label label-success" data-original-title="Amendment" rel="tooltip">
|
||||
{{ 'motion_amendments_prefix'|get_config }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="optional">{% if motion.category %}{{ motion.category }}{% else %}–{% endif %}</td>
|
||||
<td class="optional-small"><span class="label label-info">{% trans motion.state.name %}</span></td>
|
||||
<td class="optional">
|
||||
|
@ -77,6 +77,9 @@
|
||||
{{ motion.active_version.title }}
|
||||
<small>
|
||||
{% trans "Motion" %} {{ motion.identifier|default:'' }}
|
||||
{% if motion.is_amendment %}
|
||||
(Amendment of {{ motion.parent.identifier|default:motion.parent }})
|
||||
{% endif %}
|
||||
{% if motion.get_active_version.version_number > 1 %} | {% trans 'Version' %} {{ motion.active_version.version_number }}{% endif %}
|
||||
</small>
|
||||
</h1>
|
||||
|
@ -2,107 +2,112 @@
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
from . import views
|
||||
|
||||
# TODO: define the Views inhere
|
||||
urlpatterns = patterns(
|
||||
'openslides.motion.views',
|
||||
url(r'^$',
|
||||
'motion_list',
|
||||
views.MotionListView.as_view(),
|
||||
name='motion_list'),
|
||||
|
||||
url(r'^new/$',
|
||||
'motion_create',
|
||||
# TODO: rename to motion_create
|
||||
name='motion_new'),
|
||||
views.MotionCreateView.as_view(),
|
||||
name='motion_create'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/$',
|
||||
'motion_detail',
|
||||
views.MotionDetailView.as_view(),
|
||||
name='motion_detail'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/edit/$',
|
||||
'motion_update',
|
||||
views.MotionUpdateView.as_view(),
|
||||
name='motion_update'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/del/$',
|
||||
'motion_delete',
|
||||
views.MotionDeleteView.as_view(),
|
||||
name='motion_delete'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/new_amendment/$',
|
||||
views.MotionCreateAmendmentView.as_view(),
|
||||
name='motion_create_amendment'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/version/(?P<version_number>\d+)/$',
|
||||
'motion_detail',
|
||||
views.MotionDetailView.as_view(),
|
||||
name='motion_version_detail'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/version/(?P<version_number>\d+)/permit/$',
|
||||
'version_permit',
|
||||
views.VersionPermitView.as_view(),
|
||||
name='motion_version_permit'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/version/(?P<version_number>\d+)/del/$',
|
||||
'version_delete',
|
||||
views.VersionDeleteView.as_view(),
|
||||
name='motion_version_delete'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/diff/$',
|
||||
'version_diff',
|
||||
views.VersionDiffView.as_view(),
|
||||
name='motion_version_diff'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/support/$',
|
||||
'motion_support',
|
||||
views.SupportView.as_view(support=True),
|
||||
name='motion_support'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/unsupport/$',
|
||||
'motion_unsupport',
|
||||
views.SupportView.as_view(support=False),
|
||||
name='motion_unsupport'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/create_poll/$',
|
||||
'poll_create',
|
||||
views.PollCreateView.as_view(),
|
||||
name='motionpoll_create'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/poll/(?P<poll_number>\d+)/edit/$',
|
||||
'poll_update',
|
||||
views.PollUpdateView.as_view(),
|
||||
name='motionpoll_update'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/poll/(?P<poll_number>\d+)/del/$',
|
||||
'poll_delete',
|
||||
views.PollDeleteView.as_view(),
|
||||
name='motionpoll_delete'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/poll/(?P<poll_number>\d+)/pdf/$',
|
||||
'poll_pdf',
|
||||
views.PollPDFView.as_view(),
|
||||
name='motionpoll_pdf'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/set_state/(?P<state>\d+)/$',
|
||||
'set_state',
|
||||
views.MotionSetStateView.as_view(),
|
||||
name='motion_set_state'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/reset_state/$',
|
||||
'reset_state',
|
||||
views.MotionSetStateView.as_view(reset=True),
|
||||
name='motion_reset_state'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/agenda/$',
|
||||
'create_agenda_item',
|
||||
views.CreateRelatedAgendaItemView.as_view(),
|
||||
name='motion_create_agenda'),
|
||||
|
||||
url(r'^pdf/$',
|
||||
'motion_list_pdf',
|
||||
views.MotionPDFView.as_view(print_all_motions=True),
|
||||
name='motion_list_pdf'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/pdf/$',
|
||||
'motion_detail_pdf',
|
||||
views.MotionPDFView.as_view(print_all_motions=False),
|
||||
name='motion_detail_pdf'),
|
||||
|
||||
url(r'^category/$',
|
||||
'category_list',
|
||||
views.CategoryListView.as_view(),
|
||||
name='motion_category_list'),
|
||||
|
||||
url(r'^category/new/$',
|
||||
'category_create',
|
||||
views.CategoryCreateView.as_view(),
|
||||
name='motion_category_create'),
|
||||
|
||||
url(r'^category/(?P<pk>\d+)/edit/$',
|
||||
'category_update',
|
||||
views.CategoryUpdateView.as_view(),
|
||||
name='motion_category_update'),
|
||||
|
||||
url(r'^category/(?P<pk>\d+)/del/$',
|
||||
'category_delete',
|
||||
views.CategoryDeleteView.as_view(),
|
||||
name='motion_category_delete'),
|
||||
|
||||
url(r'^csv_import/$',
|
||||
'motion_csv_import',
|
||||
views.MotionCSVImportView.as_view(),
|
||||
name='motion_csv_import'),
|
||||
)
|
||||
|
@ -57,8 +57,6 @@ class MotionListView(ListView):
|
||||
motions.append(motion)
|
||||
return motions
|
||||
|
||||
motion_list = MotionListView.as_view()
|
||||
|
||||
|
||||
class MotionDetailView(DetailView):
|
||||
"""
|
||||
@ -96,8 +94,6 @@ class MotionDetailView(DetailView):
|
||||
'reason': version.reason})
|
||||
return super(MotionDetailView, self).get_context_data(**kwargs)
|
||||
|
||||
motion_detail = MotionDetailView.as_view()
|
||||
|
||||
|
||||
class MotionEditMixin(object):
|
||||
"""
|
||||
@ -205,17 +201,26 @@ class MotionCreateView(MotionEditMixin, CreateView):
|
||||
|
||||
def form_valid(self, form):
|
||||
"""
|
||||
Write a log message if the form is valid.
|
||||
Write a log message and set the submitter if necessary.
|
||||
"""
|
||||
# First, validate and process the form and create the motion
|
||||
response = super(MotionCreateView, self).form_valid(form)
|
||||
|
||||
# Write the log message
|
||||
self.object.write_log([ugettext_noop('Motion created')], self.request.user)
|
||||
|
||||
# Set submitter to request.user if no submitter is set yet
|
||||
if ('submitter' not in form.cleaned_data or
|
||||
not form.cleaned_data['submitter']):
|
||||
self.object.add_submitter(self.request.user)
|
||||
return response
|
||||
|
||||
def get_initial(self):
|
||||
"""
|
||||
Sets the initial data for the MotionCreateForm.
|
||||
"""
|
||||
initial = super(MotionCreateView, self).get_initial()
|
||||
initial['text'] = config['motion_preamble']
|
||||
if self.request.user.has_perm('motion.can_manage_motion'):
|
||||
initial['workflow'] = config['motion_workflow']
|
||||
return initial
|
||||
@ -229,7 +234,53 @@ class MotionCreateView(MotionEditMixin, CreateView):
|
||||
self.object.reset_state(workflow)
|
||||
self.version = self.object.get_new_version()
|
||||
|
||||
motion_create = MotionCreateView.as_view()
|
||||
|
||||
class MotionCreateAmendmentView(MotionCreateView):
|
||||
"""
|
||||
Create an amendment.
|
||||
"""
|
||||
|
||||
def dispatch(self, *args, **kwargs):
|
||||
if not config['motion_amendments_enabled']:
|
||||
raise Http404('Amendments are disabled in the config.')
|
||||
return super(MotionCreateAmendmentView, self).dispatch(*args, **kwargs)
|
||||
|
||||
def get_parent_motion(self):
|
||||
"""
|
||||
Gets the parent motion from the url.
|
||||
|
||||
Caches the value.
|
||||
"""
|
||||
try:
|
||||
parent = self._object_parent
|
||||
except AttributeError:
|
||||
# self.get_object() is the django method, which does not cache the
|
||||
# object. For now this is not a problem, because get_object() is only
|
||||
# called once.
|
||||
parent = self._object_parent = self.get_object()
|
||||
return parent
|
||||
|
||||
def manipulate_object(self, form):
|
||||
"""
|
||||
Sets the parent to the motion to which this amendment refers.
|
||||
"""
|
||||
self.object.parent = self.get_parent_motion()
|
||||
super(MotionCreateAmendmentView, self).manipulate_object(form)
|
||||
|
||||
def get_initial(self):
|
||||
"""
|
||||
Sets the initial values to the form.
|
||||
|
||||
This are the values for title, text and reason which are set to the
|
||||
values from the parent motion.
|
||||
"""
|
||||
initial = super(MotionCreateAmendmentView, self).get_initial()
|
||||
parent = self.get_parent_motion()
|
||||
initial['title'] = parent.title
|
||||
initial['text'] = parent.text
|
||||
initial['reason'] = parent.reason
|
||||
initial['category'] = parent.category
|
||||
return initial
|
||||
|
||||
|
||||
class MotionUpdateView(MotionEditMixin, UpdateView):
|
||||
@ -297,8 +348,6 @@ class MotionUpdateView(MotionEditMixin, UpdateView):
|
||||
self.version = self.object.get_last_version()
|
||||
self.used_new_version = False
|
||||
|
||||
motion_update = MotionUpdateView.as_view()
|
||||
|
||||
|
||||
class MotionDeleteView(DeleteView):
|
||||
"""
|
||||
@ -316,8 +365,6 @@ class MotionDeleteView(DeleteView):
|
||||
def get_final_message(self):
|
||||
return _('%s was successfully deleted.') % _('Motion')
|
||||
|
||||
motion_delete = MotionDeleteView.as_view()
|
||||
|
||||
|
||||
class VersionDeleteView(DeleteView):
|
||||
"""
|
||||
@ -349,8 +396,6 @@ class VersionDeleteView(DeleteView):
|
||||
def get_url_name_args(self):
|
||||
return (self.get_object().motion_id, )
|
||||
|
||||
version_delete = VersionDeleteView.as_view()
|
||||
|
||||
|
||||
class VersionPermitView(SingleObjectMixin, QuestionView):
|
||||
"""
|
||||
@ -396,8 +441,6 @@ class VersionPermitView(SingleObjectMixin, QuestionView):
|
||||
ugettext_noop('permitted')],
|
||||
person=self.request.user)
|
||||
|
||||
version_permit = VersionPermitView.as_view()
|
||||
|
||||
|
||||
class VersionDiffView(DetailView):
|
||||
"""
|
||||
@ -438,8 +481,6 @@ class VersionDiffView(DetailView):
|
||||
})
|
||||
return context
|
||||
|
||||
version_diff = VersionDiffView.as_view()
|
||||
|
||||
|
||||
class SupportView(SingleObjectMixin, QuestionView):
|
||||
"""
|
||||
@ -502,9 +543,6 @@ class SupportView(SingleObjectMixin, QuestionView):
|
||||
else:
|
||||
return _("You have unsupported this motion successfully.")
|
||||
|
||||
motion_support = SupportView.as_view(support=True)
|
||||
motion_unsupport = SupportView.as_view(support=False)
|
||||
|
||||
|
||||
class PollCreateView(SingleObjectMixin, RedirectView):
|
||||
"""
|
||||
@ -528,8 +566,6 @@ class PollCreateView(SingleObjectMixin, RedirectView):
|
||||
"""
|
||||
return reverse('motionpoll_update', args=[self.get_object().pk, self.poll.poll_number])
|
||||
|
||||
poll_create = PollCreateView.as_view()
|
||||
|
||||
|
||||
class PollMixin(object):
|
||||
"""
|
||||
@ -595,8 +631,6 @@ class PollUpdateView(PollMixin, PollFormView):
|
||||
self.get_object().write_log([ugettext_noop('Poll updated')], self.request.user)
|
||||
return value
|
||||
|
||||
poll_update = PollUpdateView.as_view()
|
||||
|
||||
|
||||
class PollDeleteView(PollMixin, DeleteView):
|
||||
"""
|
||||
@ -618,8 +652,6 @@ class PollDeleteView(PollMixin, DeleteView):
|
||||
"""
|
||||
return reverse('motion_detail', args=[self.get_object().motion.pk])
|
||||
|
||||
poll_delete = PollDeleteView.as_view()
|
||||
|
||||
|
||||
class PollPDFView(PollMixin, PDFView):
|
||||
"""
|
||||
@ -649,8 +681,6 @@ class PollPDFView(PollMixin, PDFView):
|
||||
"""
|
||||
motion_poll_to_pdf(pdf, self.get_object())
|
||||
|
||||
poll_pdf = PollPDFView.as_view()
|
||||
|
||||
|
||||
class MotionSetStateView(SingleObjectMixin, RedirectView):
|
||||
"""
|
||||
@ -688,9 +718,6 @@ class MotionSetStateView(SingleObjectMixin, RedirectView):
|
||||
_('The state of the motion was set to %s.')
|
||||
% html_strong(_(self.get_object().state.name)))
|
||||
|
||||
set_state = MotionSetStateView.as_view()
|
||||
reset_state = MotionSetStateView.as_view(reset=True)
|
||||
|
||||
|
||||
class CreateRelatedAgendaItemView(_CreateRelatedAgendaItemView):
|
||||
"""
|
||||
@ -705,8 +732,6 @@ class CreateRelatedAgendaItemView(_CreateRelatedAgendaItemView):
|
||||
super(CreateRelatedAgendaItemView, self).pre_redirect(request, *args, **kwargs)
|
||||
self.get_object().write_log([ugettext_noop('Agenda item created')], self.request.user)
|
||||
|
||||
create_agenda_item = CreateRelatedAgendaItemView.as_view()
|
||||
|
||||
|
||||
class MotionPDFView(SingleObjectMixin, PDFView):
|
||||
"""
|
||||
@ -767,16 +792,11 @@ class MotionPDFView(SingleObjectMixin, PDFView):
|
||||
else:
|
||||
motion_to_pdf(pdf, self.get_object())
|
||||
|
||||
motion_list_pdf = MotionPDFView.as_view(print_all_motions=True)
|
||||
motion_detail_pdf = MotionPDFView.as_view(print_all_motions=False)
|
||||
|
||||
|
||||
class CategoryListView(ListView):
|
||||
required_permission = 'motion.can_manage_motion'
|
||||
model = Category
|
||||
|
||||
category_list = CategoryListView.as_view()
|
||||
|
||||
|
||||
class CategoryCreateView(CreateView):
|
||||
required_permission = 'motion.can_manage_motion'
|
||||
@ -784,8 +804,6 @@ class CategoryCreateView(CreateView):
|
||||
success_url_name = 'motion_category_list'
|
||||
url_name_args = []
|
||||
|
||||
category_create = CategoryCreateView.as_view()
|
||||
|
||||
|
||||
class CategoryUpdateView(UpdateView):
|
||||
required_permission = 'motion.can_manage_motion'
|
||||
@ -793,8 +811,6 @@ class CategoryUpdateView(UpdateView):
|
||||
success_url_name = 'motion_category_list'
|
||||
url_name_args = []
|
||||
|
||||
category_update = CategoryUpdateView.as_view()
|
||||
|
||||
|
||||
class CategoryDeleteView(DeleteView):
|
||||
required_permission = 'motion.can_manage_motion'
|
||||
@ -803,8 +819,6 @@ class CategoryDeleteView(DeleteView):
|
||||
url_name_args = []
|
||||
success_url_name = 'motion_category_list'
|
||||
|
||||
category_delete = CategoryDeleteView.as_view()
|
||||
|
||||
|
||||
class MotionCSVImportView(CSVImportView):
|
||||
"""
|
||||
@ -830,5 +844,3 @@ class MotionCSVImportView(CSVImportView):
|
||||
messages.error(self.request, error)
|
||||
# Overleap method of CSVImportView
|
||||
return super(CSVImportView, self).form_valid(form)
|
||||
|
||||
motion_csv_import = MotionCSVImportView.as_view()
|
||||
|
@ -147,6 +147,64 @@ class ModelTest(TestCase):
|
||||
# motion.__unicode__() raised an AttributeError
|
||||
self.assertEqual(str(motion), 'test_identifier_VohT1hu9uhiSh6ooVBFS | test_title_Koowoh1ISheemeey1air')
|
||||
|
||||
def test_is_amendment(self):
|
||||
config['motion_amendments_enabled'] = True
|
||||
amendment = Motion.objects.create(title='amendment', parent=self.motion)
|
||||
|
||||
self.assertTrue(amendment.is_amendment())
|
||||
self.assertFalse(self.motion.is_amendment())
|
||||
|
||||
def test_set_identifier_allready_set(self):
|
||||
"""
|
||||
If the motion already has a identifier, the method does nothing.
|
||||
"""
|
||||
motion = Motion(identifier='My test identifier')
|
||||
|
||||
motion.set_identifier()
|
||||
|
||||
self.assertEqual(motion.identifier, 'My test identifier')
|
||||
|
||||
def test_set_identifier_manually(self):
|
||||
"""
|
||||
If the config is set to manually, the method does nothing.
|
||||
"""
|
||||
config['motion_identifier'] = 'manually'
|
||||
motion = Motion()
|
||||
|
||||
motion.set_identifier()
|
||||
|
||||
# If the identifier should be set manually, the method does nothing
|
||||
self.assertIsNone(motion.identifier)
|
||||
|
||||
def test_set_identifier_amendment(self):
|
||||
"""
|
||||
If the motion is an amendment, the identifier is the identifier from the
|
||||
parent + a suffix.
|
||||
"""
|
||||
config['motion_amendments_enabled'] = True
|
||||
self.motion.identifier = 'Parent identifier'
|
||||
self.motion.save()
|
||||
motion = Motion(parent=self.motion)
|
||||
|
||||
motion.set_identifier()
|
||||
|
||||
self.assertEqual(motion.identifier, 'Parent identifier A 1')
|
||||
|
||||
def test_set_identifier_second_amendment(self):
|
||||
"""
|
||||
If a motion has already an amendment, the second motion gets another
|
||||
identifier.
|
||||
"""
|
||||
config['motion_amendments_enabled'] = True
|
||||
self.motion.identifier = 'Parent identifier'
|
||||
self.motion.save()
|
||||
Motion.objects.create(title='Amendment1', parent=self.motion)
|
||||
motion = Motion(parent=self.motion)
|
||||
|
||||
motion.set_identifier()
|
||||
|
||||
self.assertEqual(motion.identifier, 'Parent identifier A 2')
|
||||
|
||||
|
||||
class ConfigTest(TestCase):
|
||||
def test_stop_submitting(self):
|
||||
|
@ -2,12 +2,15 @@
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from mock import MagicMock
|
||||
|
||||
from django.conf import settings
|
||||
from django.test import RequestFactory
|
||||
from django.test.client import Client
|
||||
|
||||
from openslides.config.api import config
|
||||
from openslides.mediafile.models import Mediafile
|
||||
from openslides.motion import views
|
||||
from openslides.motion.models import Category, Motion, MotionLog, State
|
||||
from openslides.participant.models import Group, User
|
||||
from openslides.utils.test import TestCase
|
||||
@ -244,6 +247,67 @@ class TestMotionCreateView(MotionViewTestCase):
|
||||
self.assertEqual(MotionLog.objects.get(pk=1).message_list, ['Motion created'])
|
||||
|
||||
|
||||
class TestMotionCreateAmendmentView(MotionViewTestCase):
|
||||
url = '/motion/1/new_amendment/'
|
||||
|
||||
def test_get_amendment_active(self):
|
||||
config['motion_amendments_enabled'] = True
|
||||
self.check_url(self.url, self.admin_client, 200)
|
||||
|
||||
def test_get_amendment_inactive(self):
|
||||
config['motion_amendments_enabled'] = False
|
||||
self.check_url(self.url, self.admin_client, 404)
|
||||
|
||||
def test_get_parent_motion(self):
|
||||
motion = Motion.objects.create(title='Test Motion')
|
||||
view = views.MotionCreateAmendmentView()
|
||||
view.request = RequestFactory().get(self.url)
|
||||
view.kwargs = {'pk': motion.pk}
|
||||
|
||||
self.assertEqual(view.get_parent_motion(), motion)
|
||||
|
||||
def test_manipulate_object(self):
|
||||
motion = Motion.objects.create(title='Test Motion')
|
||||
view = views.MotionCreateAmendmentView()
|
||||
view.request = RequestFactory().get(self.url)
|
||||
view.kwargs = {'pk': motion.pk}
|
||||
view.object = MagicMock()
|
||||
|
||||
view.manipulate_object(MagicMock())
|
||||
|
||||
self.assertEqual(view.object.parent, motion)
|
||||
|
||||
def test_get_initial(self):
|
||||
motion = Motion.objects.create(
|
||||
title='Test Motion', text='Parent Motion text', reason='test reason')
|
||||
view = views.MotionCreateAmendmentView()
|
||||
view.request = MagicMock()
|
||||
view.kwargs = {'pk': motion.pk}
|
||||
|
||||
self.assertEqual(view.get_initial(), {
|
||||
'reason': u'test reason',
|
||||
'text': u'Parent Motion text',
|
||||
'title': u'Test Motion',
|
||||
'category': None,
|
||||
'workflow': '1'})
|
||||
|
||||
def test_get_initial_with_category(self):
|
||||
category = Category.objects.create(name='test category')
|
||||
motion = Motion.objects.create(
|
||||
title='Test Motion', text='Parent Motion text', reason='test reason',
|
||||
category=category)
|
||||
view = views.MotionCreateAmendmentView()
|
||||
view.request = MagicMock()
|
||||
view.kwargs = {'pk': motion.pk}
|
||||
|
||||
self.assertEqual(view.get_initial(), {
|
||||
'reason': u'test reason',
|
||||
'text': u'Parent Motion text',
|
||||
'title': u'Test Motion',
|
||||
'category': category,
|
||||
'workflow': '1'})
|
||||
|
||||
|
||||
class TestMotionUpdateView(MotionViewTestCase):
|
||||
url = '/motion/1/edit/'
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user