commit
63840df7b5
@ -67,7 +67,8 @@ class BaseMotionForm(CleanHtmlFormMixin, CssClassMixin, forms.ModelForm):
|
|||||||
class MotionSubmitterMixin(forms.ModelForm):
|
class MotionSubmitterMixin(forms.ModelForm):
|
||||||
"""Mixin to append the submitter field to a MotionForm."""
|
"""Mixin to append the submitter field to a MotionForm."""
|
||||||
|
|
||||||
submitter = MultiplePersonFormField(label=ugettext_lazy("Submitter"))
|
submitter = MultiplePersonFormField(label=ugettext_lazy("Submitter"),
|
||||||
|
required=False)
|
||||||
"""Submitter of the motion. Can be one or more persons."""
|
"""Submitter of the motion. Can be one or more persons."""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -93,8 +93,7 @@ class Motion(SlideMixin, models.Model):
|
|||||||
('can_support_motion', ugettext_noop('Can support motions')),
|
('can_support_motion', ugettext_noop('Can support motions')),
|
||||||
('can_manage_motion', ugettext_noop('Can manage motions')),
|
('can_manage_motion', ugettext_noop('Can manage motions')),
|
||||||
)
|
)
|
||||||
# TODO: order per default by category and identifier
|
ordering = ('identifier', )
|
||||||
# ordering = ('number',)
|
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
"""
|
"""
|
||||||
@ -103,67 +102,87 @@ class Motion(SlideMixin, models.Model):
|
|||||||
return self.active_version.title
|
return self.active_version.title
|
||||||
|
|
||||||
# TODO: Use transaction
|
# TODO: Use transaction
|
||||||
def save(self, ignore_version_data=False, *args, **kwargs):
|
def save(self, use_version=None, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Save the motion.
|
Save the motion.
|
||||||
|
|
||||||
1. Set the state of a new motion to the default state.
|
1. Set the state of a new motion to the default state.
|
||||||
2. Ensure that the identifier is not an empty string.
|
2. Ensure that the identifier is not an empty string.
|
||||||
3. Save the motion object.
|
3. Save the motion object.
|
||||||
4. Save the version data, if ignore_version_data == False.
|
4. Save the version data.
|
||||||
5. Set the active version for the motion, if ignore_version_data == False.
|
5. Set the active version for the motion if a new version object was saved.
|
||||||
|
|
||||||
|
The version data is *not* saved, if
|
||||||
|
1. the django-feature 'update_fields' is used or
|
||||||
|
2. the argument use_version is False (differ to None).
|
||||||
|
|
||||||
|
The argument use_version is choose the version object into which the
|
||||||
|
version data is saved.
|
||||||
|
* If use_version is False, no version data is saved.
|
||||||
|
* If use_version is None, the last version is used.
|
||||||
|
* Else the given version is used.
|
||||||
|
|
||||||
|
To create and use a new version object, you have to set it via the
|
||||||
|
use_version argument. You have to set the title, text and reason into
|
||||||
|
this version object before giving it to this save method. The properties
|
||||||
|
motion.title, motion.text and motion.reason will be ignored.
|
||||||
"""
|
"""
|
||||||
if not self.state:
|
if not self.state:
|
||||||
self.reset_state()
|
self.reset_state()
|
||||||
# TODO: Bad hack here to make Motion.objects.create() work
|
|
||||||
# again. We have to remove the flag to force an INSERT given
|
|
||||||
# by Django's create() method without knowing its advantages
|
|
||||||
# because of our misuse of the save() method in the
|
|
||||||
# set_identifier() method.
|
|
||||||
kwargs.pop('force_insert', None)
|
|
||||||
|
|
||||||
if not self.identifier and self.identifier is not None: # TODO: Why not >if self.identifier is '':<
|
# Solves the problem, that there can only be one motion with an empty
|
||||||
|
# string as identifier.
|
||||||
|
if self.identifier is '':
|
||||||
self.identifier = None
|
self.identifier = None
|
||||||
|
|
||||||
super(Motion, self).save(*args, **kwargs)
|
super(Motion, self).save(*args, **kwargs)
|
||||||
|
|
||||||
if not ignore_version_data:
|
if 'update_fields' in kwargs:
|
||||||
# Select version object
|
# Do not save the version data if only some motion fields are updated.
|
||||||
version = self.last_version
|
return
|
||||||
if hasattr(self, '_new_version'):
|
|
||||||
version = self.new_version
|
|
||||||
del self._new_version
|
|
||||||
version.motion = self # TODO: Test if this line is really neccessary.
|
|
||||||
|
|
||||||
# Save title, text and reason in the version object
|
if use_version is False:
|
||||||
|
# We do not need to save the version.
|
||||||
|
return
|
||||||
|
elif use_version is None:
|
||||||
|
use_version = self.get_last_version()
|
||||||
|
# Save title, text and reason into the version object.
|
||||||
for attr in ['title', 'text', 'reason']:
|
for attr in ['title', 'text', 'reason']:
|
||||||
_attr = '_%s' % attr
|
_attr = '_%s' % attr
|
||||||
try:
|
data = getattr(self, _attr, None)
|
||||||
setattr(version, attr, getattr(self, _attr))
|
if data is not None:
|
||||||
|
setattr(use_version, attr, data)
|
||||||
delattr(self, _attr)
|
delattr(self, _attr)
|
||||||
except AttributeError:
|
|
||||||
if self.versions.exists():
|
|
||||||
# If the _attr was not set, use the value from last_version
|
|
||||||
setattr(version, attr, getattr(self.last_version, attr))
|
|
||||||
|
|
||||||
# Set version_number of the new Version (if neccessary) and save it into the DB
|
# If version is not in the database, test if it has new data and set
|
||||||
if version.id is None:
|
# the version_number.
|
||||||
# TODO: auto increment the version_number in the Database
|
if use_version.id is None:
|
||||||
|
if not self.version_data_changed(use_version):
|
||||||
|
# We do not need to save the version.
|
||||||
|
return
|
||||||
version_number = self.versions.aggregate(Max('version_number'))['version_number__max'] or 0
|
version_number = self.versions.aggregate(Max('version_number'))['version_number__max'] or 0
|
||||||
version.version_number = version_number + 1
|
use_version.version_number = version_number + 1
|
||||||
version.save()
|
|
||||||
|
# Necessary line if the version was set before the motion got an id.
|
||||||
|
# This is probably a Django bug.
|
||||||
|
use_version.motion = use_version.motion
|
||||||
|
|
||||||
|
use_version.save()
|
||||||
|
|
||||||
# Set the active version of this motion. This has to be done after the
|
# Set the active version of this motion. This has to be done after the
|
||||||
# version is saved to the database
|
# version is saved in the database.
|
||||||
|
# TODO: Move parts of these last lines of code outside the save method
|
||||||
|
# when other versions than the last ones should be edited later on.
|
||||||
if self.active_version is None or not self.state.leave_old_version_active:
|
if self.active_version is None or not self.state.leave_old_version_active:
|
||||||
self.active_version = version
|
self.active_version = use_version
|
||||||
self.save(ignore_version_data=True)
|
self.save(update_fields=['active_version'])
|
||||||
|
|
||||||
def get_absolute_url(self, link='detail'):
|
def get_absolute_url(self, link='detail'):
|
||||||
"""
|
"""
|
||||||
Return an URL for this version.
|
Return an URL for this version.
|
||||||
|
|
||||||
The keyword argument 'link' can be 'detail', 'view', 'edit', 'update' or 'delete'.
|
The keyword argument 'link' can be 'detail', 'view', 'edit',
|
||||||
|
'update' or 'delete'.
|
||||||
"""
|
"""
|
||||||
if link == 'view' or link == 'detail':
|
if link == 'view' or link == 'detail':
|
||||||
return reverse('motion_detail', args=[str(self.id)])
|
return reverse('motion_detail', args=[str(self.id)])
|
||||||
@ -172,10 +191,27 @@ class Motion(SlideMixin, models.Model):
|
|||||||
if link == 'delete':
|
if link == 'delete':
|
||||||
return reverse('motion_delete', args=[str(self.id)])
|
return reverse('motion_delete', args=[str(self.id)])
|
||||||
|
|
||||||
|
def version_data_changed(self, version):
|
||||||
|
"""
|
||||||
|
Compare the version with the last version of the motion.
|
||||||
|
|
||||||
|
Returns True if the version data (title, text, reason) is different,
|
||||||
|
else returns False.
|
||||||
|
"""
|
||||||
|
if not self.versions.exists():
|
||||||
|
# If there is no version in the database, the data has always changed.
|
||||||
|
return True
|
||||||
|
|
||||||
|
last_version = self.get_last_version()
|
||||||
|
for attr in ['title', 'text', 'reason']:
|
||||||
|
if getattr(last_version, attr) != getattr(version, attr):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def set_identifier(self):
|
def set_identifier(self):
|
||||||
"""
|
"""
|
||||||
Sets the motion identifier automaticly according to the config
|
Sets the motion identifier automaticly according to the config
|
||||||
value, if it is not set yet.
|
value if it is not set yet.
|
||||||
"""
|
"""
|
||||||
if config['motion_identifier'] == 'manually' or self.identifier:
|
if config['motion_identifier'] == 'manually' or self.identifier:
|
||||||
# Do not set an identifier.
|
# Do not set an identifier.
|
||||||
@ -189,20 +225,16 @@ class Motion(SlideMixin, models.Model):
|
|||||||
if self.category is None or not self.category.prefix:
|
if self.category is None or not self.category.prefix:
|
||||||
prefix = ''
|
prefix = ''
|
||||||
else:
|
else:
|
||||||
prefix = self.category.prefix + ' '
|
prefix = '%s ' % self.category.prefix
|
||||||
|
|
||||||
# TODO: Do not use the save() method in this method, see note in
|
|
||||||
# the save() method above.
|
|
||||||
while True:
|
|
||||||
number += 1
|
number += 1
|
||||||
self.identifier = '%s%d' % (prefix, number)
|
identifier = '%s%d' % (prefix, number)
|
||||||
|
while Motion.objects.filter(identifier=identifier).exists():
|
||||||
|
number += 1
|
||||||
|
identifier = '%s%d' % (prefix, number)
|
||||||
|
|
||||||
|
self.identifier = identifier
|
||||||
self.identifier_number = number
|
self.identifier_number = number
|
||||||
try:
|
|
||||||
self.save(ignore_version_data=True)
|
|
||||||
except IntegrityError:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
def get_title(self):
|
def get_title(self):
|
||||||
"""
|
"""
|
||||||
@ -213,13 +245,13 @@ class Motion(SlideMixin, models.Model):
|
|||||||
try:
|
try:
|
||||||
return self._title
|
return self._title
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return self.version.title
|
return self.get_active_version().title
|
||||||
|
|
||||||
def set_title(self, title):
|
def set_title(self, title):
|
||||||
"""
|
"""
|
||||||
Set the titel of the motion.
|
Set the titel of the motion.
|
||||||
|
|
||||||
The title will be saved into the version object, wenn motion.save() is
|
The title will be saved in the version object, when motion.save() is
|
||||||
called.
|
called.
|
||||||
"""
|
"""
|
||||||
self._title = title
|
self._title = title
|
||||||
@ -240,7 +272,7 @@ class Motion(SlideMixin, models.Model):
|
|||||||
try:
|
try:
|
||||||
return self._text
|
return self._text
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return self.version.text
|
return self.get_active_version().text
|
||||||
|
|
||||||
def set_text(self, text):
|
def set_text(self, text):
|
||||||
"""
|
"""
|
||||||
@ -266,7 +298,7 @@ class Motion(SlideMixin, models.Model):
|
|||||||
try:
|
try:
|
||||||
return self._reason
|
return self._reason
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return self.version.reason
|
return self.get_active_version().reason
|
||||||
|
|
||||||
def set_reason(self, reason):
|
def set_reason(self, reason):
|
||||||
"""
|
"""
|
||||||
@ -283,72 +315,47 @@ class Motion(SlideMixin, models.Model):
|
|||||||
Is saved in a MotionVersion object.
|
Is saved in a MotionVersion object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@property
|
def get_new_version(self):
|
||||||
def new_version(self):
|
|
||||||
"""
|
"""
|
||||||
Return a version object, not saved in the database.
|
Return a version object, not saved in the database.
|
||||||
|
|
||||||
On the first call, it creates a new version. On any later call, it
|
The version data of the new version object is populated with the data
|
||||||
use the existing new version.
|
set via motion.title, motion.text, motion.reason. If the data is not set,
|
||||||
|
it is populated with the data from the last version object.
|
||||||
The new_version object will be deleted when it is saved into the db.
|
|
||||||
"""
|
"""
|
||||||
try:
|
new_version = MotionVersion(motion=self)
|
||||||
return self._new_version
|
if self.versions.exists():
|
||||||
except AttributeError:
|
last_version = self.get_last_version()
|
||||||
self._new_version = MotionVersion(motion=self)
|
|
||||||
return self._new_version
|
|
||||||
|
|
||||||
def get_version(self):
|
|
||||||
"""
|
|
||||||
Get the 'active' version object.
|
|
||||||
|
|
||||||
This version will be used to get the data for this motion.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return self._version
|
|
||||||
except AttributeError:
|
|
||||||
return self.last_version
|
|
||||||
|
|
||||||
def set_version(self, version):
|
|
||||||
"""
|
|
||||||
Set the 'active' version object.
|
|
||||||
|
|
||||||
The keyword argument 'version' can be a MotionVersion object or the
|
|
||||||
version_number of a version object or None.
|
|
||||||
|
|
||||||
If the argument is None, the newest version will be used.
|
|
||||||
"""
|
|
||||||
if version is None:
|
|
||||||
try:
|
|
||||||
del self._version
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
else:
|
else:
|
||||||
if type(version) is int:
|
last_version = None
|
||||||
version = self.versions.get(version_number=version)
|
for attr in ['title', 'text', 'reason']:
|
||||||
elif type(version) is not MotionVersion:
|
_attr = '_%s' % attr
|
||||||
raise ValueError('The argument \'version\' has to be int or '
|
data = getattr(self, _attr, None)
|
||||||
'MotionVersion, not %s' % type(version))
|
if data is None and last_version is not None:
|
||||||
# TODO: Test, that the version is one of this motion
|
data = getattr(last_version, attr)
|
||||||
self._version = version
|
if data is not None:
|
||||||
|
setattr(new_version, attr, data)
|
||||||
|
return new_version
|
||||||
|
|
||||||
version = property(get_version, set_version)
|
def get_active_version(self):
|
||||||
"""
|
|
||||||
The active version of this motion.
|
|
||||||
"""
|
"""
|
||||||
|
Returns the active version of the motion.
|
||||||
|
|
||||||
@property
|
If no active version is set by now, the last_version is used.
|
||||||
def last_version(self):
|
"""
|
||||||
|
if self.active_version:
|
||||||
|
return self.active_version
|
||||||
|
else:
|
||||||
|
return self.get_last_version()
|
||||||
|
|
||||||
|
def get_last_version(self):
|
||||||
"""
|
"""
|
||||||
Return the newest version of the motion.
|
Return the newest version of the motion.
|
||||||
"""
|
"""
|
||||||
# TODO: Fix the case, that the motion has no version.
|
|
||||||
# Check whether the case, that a motion has not any version, can still appear.
|
|
||||||
try:
|
try:
|
||||||
return self.versions.order_by('-version_number')[0]
|
return self.versions.order_by('-version_number')[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return self.new_version
|
return self.get_new_version()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def submitters(self):
|
def submitters(self):
|
||||||
@ -459,7 +466,7 @@ class Motion(SlideMixin, models.Model):
|
|||||||
"""
|
"""
|
||||||
Return a title for the Agenda.
|
Return a title for the Agenda.
|
||||||
"""
|
"""
|
||||||
return self.active_version.title
|
return self.title
|
||||||
|
|
||||||
def get_agenda_title_supplement(self):
|
def get_agenda_title_supplement(self):
|
||||||
"""
|
"""
|
||||||
@ -518,16 +525,6 @@ class Motion(SlideMixin, models.Model):
|
|||||||
"""
|
"""
|
||||||
MotionLog.objects.create(motion=self, message_list=message_list, person=person)
|
MotionLog.objects.create(motion=self, message_list=message_list, person=person)
|
||||||
|
|
||||||
def set_active_version(self, version):
|
|
||||||
"""
|
|
||||||
Set the active version of a motion to 'version'.
|
|
||||||
|
|
||||||
'version' can be a version object, or the version_number of a version.
|
|
||||||
"""
|
|
||||||
if type(version) is int:
|
|
||||||
version = self.versions.get(version_number=version)
|
|
||||||
self.active_version = version
|
|
||||||
|
|
||||||
|
|
||||||
class MotionVersion(models.Model):
|
class MotionVersion(models.Model):
|
||||||
"""
|
"""
|
||||||
@ -796,7 +793,7 @@ class State(models.Model):
|
|||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def save(self, **kwargs):
|
def save(self, **kwargs):
|
||||||
"""Saves a state to the database.
|
"""Saves a state in the database.
|
||||||
|
|
||||||
Used to check the integrity before saving.
|
Used to check the integrity before saving.
|
||||||
"""
|
"""
|
||||||
@ -831,7 +828,7 @@ class Workflow(models.Model):
|
|||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def save(self, **kwargs):
|
def save(self, **kwargs):
|
||||||
"""Saves a workflow to the database.
|
"""Saves a workflow in the database.
|
||||||
|
|
||||||
Used to check the integrity before saving.
|
Used to check the integrity before saving.
|
||||||
"""
|
"""
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>
|
<h1>
|
||||||
{{ motion.title }} {{ motion.category|default:'' }}
|
{{ title }} {{ motion.category|default:'' }}
|
||||||
<br>
|
<br>
|
||||||
<small>
|
<small>
|
||||||
{% if motion.identifier %}
|
{% if motion.identifier %}
|
||||||
@ -17,7 +17,7 @@
|
|||||||
<i>[{% trans "no number" %}]</i>,
|
<i>[{% trans "no number" %}]</i>,
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{# TODO: show only for workflow with versioning #}
|
{# TODO: show only for workflow with versioning #}
|
||||||
{% trans "Version" %} {{ motion.version.version_number }}
|
{% trans "Version" %} {{ version.version_number }}
|
||||||
</small>
|
</small>
|
||||||
<small class="pull-right">
|
<small class="pull-right">
|
||||||
<a href="{% url 'motion_list' %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Back to overview" %}</a>
|
<a href="{% url 'motion_list' %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Back to overview" %}</a>
|
||||||
@ -57,33 +57,31 @@
|
|||||||
<div class="row-fluid">
|
<div class="row-fluid">
|
||||||
<div class="span8">
|
<div class="span8">
|
||||||
{# TODO: show only for workflow with versioning #}
|
{# TODO: show only for workflow with versioning #}
|
||||||
{% if motion.version.version_number != motion.last_version.version_number %}
|
{% with last_version=motion.get_last_version active_version=motion.get_active_version %}
|
||||||
|
{% if version.version_number != last_version.version_number %}
|
||||||
<span class="label label-warning">
|
<span class="label label-warning">
|
||||||
<i class="icon-warning-sign icon-white"></i> {% trans "This is not the newest version." %}
|
<i class="icon-warning-sign icon-white"></i> {% trans "This is not the newest version." %}
|
||||||
</span>
|
</span>
|
||||||
<a href="{% model_url motion.last_version %}" class="btn btn-small">{% trans "Go to the newest version" %}
|
<a href="{% model_url last_version %}" class="btn btn-small">{% trans "Go to the newest version" %}
|
||||||
(# {{ motion.last_version.version_number }})</a>
|
(# {{ last_version.version_number }})</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if motion.version.version_number != motion.active_version.version_number %}
|
{% if version.version_number != active_version.version_number %}
|
||||||
<span class="label label-warning">
|
<span class="label label-warning">
|
||||||
<i class="icon-warning-sign icon-white"></i> {% trans "This version is not authorized." %}
|
<i class="icon-warning-sign icon-white"></i> {% trans "This version is not authorized." %}
|
||||||
</span>
|
</span>
|
||||||
<a href="{% model_url motion.active_version %}" class="btn btn-small">{% trans "Go to the authorized version" %}
|
<a href="{% model_url active_version %}" class="btn btn-small">{% trans "Go to the authorized version" %}
|
||||||
(# {{ motion.active_version.version_number }})</a>
|
(# {{ active_version.version_number }})</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
<!-- Text -->
|
<!-- Text -->
|
||||||
<h4>{% trans "Motion text" %}:</h4>
|
<h4>{% trans "Motion text" %}:</h4>
|
||||||
{{ motion.version.text|safe }}
|
{{ text|safe }}
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<!-- Reason -->
|
<!-- Reason -->
|
||||||
<h4>{% trans "Reason" %}:</h4>
|
<h4>{% trans "Reason" %}:</h4>
|
||||||
{% if motion.version.reason %}
|
{{ reason|safe|default:'–' }}
|
||||||
{{ motion.version.reason|safe }}
|
|
||||||
{% else %}
|
|
||||||
–
|
|
||||||
{% endif %}
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<!-- Version history -->
|
<!-- Version history -->
|
||||||
@ -102,7 +100,7 @@
|
|||||||
<th>{% trans "Actions" %}</th>
|
<th>{% trans "Actions" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<tr {% if version == motion.version %}class="offline"{%endif %}>
|
<tr {% if version == motion.use_version %}class="offline"{%endif %}>
|
||||||
<td class="nobr">
|
<td class="nobr">
|
||||||
{% if version == motion.active_version %}
|
{% if version == motion.active_version %}
|
||||||
<span class="badge badge-success" title="{% trans 'This version is authorized' %}"><i class="icon-ok icon-white"></i></span>
|
<span class="badge badge-success" title="{% trans 'This version is authorized' %}"><i class="icon-ok icon-white"></i></span>
|
||||||
@ -239,7 +237,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</h5>
|
</h5>
|
||||||
{{ motion.version.creation_time }}
|
{{ motion.use_version.creation_time }}
|
||||||
|
|
||||||
<!-- Widthdraw button -->
|
<!-- Widthdraw button -->
|
||||||
{# TODO: Check this button #}
|
{# TODO: Check this button #}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
The views are automaticly imported from openslides.motion.urls.
|
The views are automaticly imported from openslides.motion.urls.
|
||||||
|
|
||||||
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS.
|
:copyright: (c) 2011–2013 by the OpenSlides team, see AUTHORS.
|
||||||
:license: GNU GPL, see LICENSE for more details.
|
:license: GNU GPL, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ from django.db import transaction
|
|||||||
from django.db.models import Model
|
from django.db.models import Model
|
||||||
from django.utils.translation import ugettext as _, ugettext_lazy, ugettext_noop
|
from django.utils.translation import ugettext as _, ugettext_lazy, ugettext_noop
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
from django.http import Http404
|
from django.http import Http404, HttpResponseRedirect
|
||||||
|
|
||||||
from reportlab.platypus import SimpleDocTemplate
|
from reportlab.platypus import SimpleDocTemplate
|
||||||
|
|
||||||
@ -44,74 +44,72 @@ from .csv_import import import_motions
|
|||||||
|
|
||||||
|
|
||||||
class MotionListView(ListView):
|
class MotionListView(ListView):
|
||||||
"""View, to list all motions."""
|
"""
|
||||||
|
View, to list all motions.
|
||||||
|
"""
|
||||||
permission_required = 'motion.can_see_motion'
|
permission_required = 'motion.can_see_motion'
|
||||||
model = Motion
|
model = Motion
|
||||||
|
|
||||||
motion_list = MotionListView.as_view()
|
motion_list = MotionListView.as_view()
|
||||||
|
|
||||||
|
|
||||||
class GetVersionMixin(object):
|
class MotionDetailView(DetailView):
|
||||||
"""Mixin to set a specific version to a motion."""
|
"""
|
||||||
|
Show one motion.
|
||||||
def get_object(self):
|
"""
|
||||||
"""Return a Motion object. The id is taken from the url and the version
|
|
||||||
is set to the version with the 'version_number' from the URL."""
|
|
||||||
object = super(GetVersionMixin, self).get_object()
|
|
||||||
version_number = self.kwargs.get('version_number', None)
|
|
||||||
if version_number is not None:
|
|
||||||
try:
|
|
||||||
object.version = int(version_number)
|
|
||||||
except MotionVersion.DoesNotExist:
|
|
||||||
raise Http404('Version %s not found' % version_number)
|
|
||||||
else:
|
|
||||||
object.version = object.active_version
|
|
||||||
return object
|
|
||||||
|
|
||||||
|
|
||||||
class MotionDetailView(GetVersionMixin, DetailView):
|
|
||||||
"""Show one motion."""
|
|
||||||
permission_required = 'motion.can_see_motion'
|
permission_required = 'motion.can_see_motion'
|
||||||
model = Motion
|
model = Motion
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
"""Return the template context.
|
|
||||||
|
|
||||||
Append the allowed actions for the motion to the context.
|
|
||||||
"""
|
"""
|
||||||
context = super(MotionDetailView, self).get_context_data(**kwargs)
|
Return the template context.
|
||||||
context['allowed_actions'] = self.object.get_allowed_actions(self.request.user)
|
|
||||||
return context
|
Append the allowed actions for the motion, the shown version and its
|
||||||
|
data to the context.
|
||||||
|
"""
|
||||||
|
version_number = self.kwargs.get('version_number', None)
|
||||||
|
if version_number is not None:
|
||||||
|
try:
|
||||||
|
version = self.object.versions.get(version_number=int(version_number))
|
||||||
|
except MotionVersion.DoesNotExist:
|
||||||
|
raise Http404('Version %s not found' % version_number)
|
||||||
|
else:
|
||||||
|
version = self.object.get_active_version()
|
||||||
|
|
||||||
|
kwargs.update({
|
||||||
|
'allowed_actions': self.object.get_allowed_actions(self.request.user),
|
||||||
|
'version': version,
|
||||||
|
'title': version.title,
|
||||||
|
'text': version.text,
|
||||||
|
'reason': version.reason})
|
||||||
|
return super(MotionDetailView, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
motion_detail = MotionDetailView.as_view()
|
motion_detail = MotionDetailView.as_view()
|
||||||
|
|
||||||
|
|
||||||
class MotionMixin(object):
|
class MotionEditMixin(object):
|
||||||
"""
|
"""
|
||||||
Mixin for MotionViewsClasses to save the version data.
|
Mixin for motion views classes to save the version data.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def manipulate_object(self, form):
|
def form_valid(self, form):
|
||||||
"""
|
"""
|
||||||
Saves the version data into the motion object before it is saved in
|
Saves the CreateForm or UpdateForm into a motion object.
|
||||||
the Database. Does also set category, identifier and new workflow
|
|
||||||
if given.
|
|
||||||
"""
|
"""
|
||||||
super(MotionMixin, self).manipulate_object(form)
|
self.object = form.save(commit=False)
|
||||||
for attr in ['title', 'text', 'reason']:
|
|
||||||
setattr(self.object, attr, form.cleaned_data[attr])
|
|
||||||
|
|
||||||
if type(self) == MotionCreateView:
|
if type(self) == MotionUpdateView:
|
||||||
self.object.new_version
|
# Decide if a new version is saved to the database
|
||||||
|
if (self.object.state.versioning and
|
||||||
|
not form.cleaned_data.get('disable_versioning', False)):
|
||||||
|
version = self.object.get_new_version()
|
||||||
else:
|
else:
|
||||||
|
version = self.object.get_last_version()
|
||||||
|
else:
|
||||||
|
version = self.object.get_new_version()
|
||||||
|
|
||||||
for attr in ['title', 'text', 'reason']:
|
for attr in ['title', 'text', 'reason']:
|
||||||
if getattr(self.object, attr) != getattr(self.object.last_version, attr):
|
setattr(version, attr, form.cleaned_data[attr])
|
||||||
new_data = True
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
new_data = False
|
|
||||||
if new_data and self.object.state.versioning and not form.cleaned_data.get('disable_versioning', False):
|
|
||||||
self.object.new_version
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.object.category = form.cleaned_data['category']
|
self.object.category = form.cleaned_data['category']
|
||||||
@ -127,12 +125,10 @@ class MotionMixin(object):
|
|||||||
if workflow:
|
if workflow:
|
||||||
self.object.reset_state(workflow)
|
self.object.reset_state(workflow)
|
||||||
|
|
||||||
def post_save(self, form):
|
self.object.save(use_version=version)
|
||||||
"""
|
|
||||||
Save the submitter an the supporter so the motion.
|
# Save the submitter an the supporter so the motion.
|
||||||
"""
|
# TODO: Only delete and save neccessary submitters and supporters
|
||||||
super(MotionMixin, self).post_save(form)
|
|
||||||
# TODO: only delete and save neccessary submitters and supporter
|
|
||||||
if 'submitter' in form.cleaned_data:
|
if 'submitter' in form.cleaned_data:
|
||||||
self.object.submitter.all().delete()
|
self.object.submitter.all().delete()
|
||||||
MotionSubmitter.objects.bulk_create(
|
MotionSubmitter.objects.bulk_create(
|
||||||
@ -143,10 +139,11 @@ class MotionMixin(object):
|
|||||||
MotionSupporter.objects.bulk_create(
|
MotionSupporter.objects.bulk_create(
|
||||||
[MotionSupporter(motion=self.object, person=person)
|
[MotionSupporter(motion=self.object, person=person)
|
||||||
for person in form.cleaned_data['supporter']])
|
for person in form.cleaned_data['supporter']])
|
||||||
|
return HttpResponseRedirect(self.get_success_url())
|
||||||
|
|
||||||
def get_form_class(self):
|
def get_form_class(self):
|
||||||
"""
|
"""
|
||||||
Return the FormClass to Create or Update the Motion.
|
Return the FormClass to create or update the motion.
|
||||||
|
|
||||||
forms.BaseMotionForm is the base for the Class, and some FormMixins
|
forms.BaseMotionForm is the base for the Class, and some FormMixins
|
||||||
will be mixed in dependence of some config values. See motion.forms
|
will be mixed in dependence of some config values. See motion.forms
|
||||||
@ -173,8 +170,10 @@ class MotionMixin(object):
|
|||||||
return type('MotionForm', tuple(form_classes), {})
|
return type('MotionForm', tuple(form_classes), {})
|
||||||
|
|
||||||
|
|
||||||
class MotionCreateView(MotionMixin, CreateView):
|
class MotionCreateView(MotionEditMixin, CreateView):
|
||||||
"""View to create a motion."""
|
"""
|
||||||
|
View to create a motion.
|
||||||
|
"""
|
||||||
model = Motion
|
model = Motion
|
||||||
|
|
||||||
def has_permission(self, request, *args, **kwargs):
|
def has_permission(self, request, *args, **kwargs):
|
||||||
@ -190,55 +189,56 @@ class MotionCreateView(MotionMixin, CreateView):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""Write a log message, if the form is valid."""
|
"""
|
||||||
value = super(MotionCreateView, self).form_valid(form)
|
Write a log message if the form is valid.
|
||||||
|
"""
|
||||||
|
response = super(MotionCreateView, self).form_valid(form)
|
||||||
self.object.write_log([ugettext_noop('Motion created')], self.request.user)
|
self.object.write_log([ugettext_noop('Motion created')], self.request.user)
|
||||||
return value
|
|
||||||
|
|
||||||
def post_save(self, form):
|
|
||||||
super(MotionCreateView, self).post_save(form)
|
|
||||||
if not 'submitter' in form.cleaned_data:
|
if not 'submitter' in form.cleaned_data:
|
||||||
self.object.add_submitter(self.request.user)
|
self.object.add_submitter(self.request.user)
|
||||||
|
return response
|
||||||
|
|
||||||
motion_create = MotionCreateView.as_view()
|
motion_create = MotionCreateView.as_view()
|
||||||
|
|
||||||
|
|
||||||
class MotionUpdateView(MotionMixin, UpdateView):
|
class MotionUpdateView(MotionEditMixin, UpdateView):
|
||||||
"""View to update a motion."""
|
"""
|
||||||
|
View to update a motion.
|
||||||
|
"""
|
||||||
model = Motion
|
model = Motion
|
||||||
|
|
||||||
def has_permission(self, request, *args, **kwargs):
|
def has_permission(self, request, *args, **kwargs):
|
||||||
"""Check, if the request.user has the permission to edit the motion."""
|
"""
|
||||||
|
Check if the request.user has the permission to edit the motion.
|
||||||
|
"""
|
||||||
return self.get_object().get_allowed_actions(request.user)['update']
|
return self.get_object().get_allowed_actions(request.user)['update']
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""Write a log message, if the form is valid."""
|
"""
|
||||||
value = super(MotionUpdateView, self).form_valid(form)
|
Write a log message if the form is valid.
|
||||||
|
"""
|
||||||
|
response = super(MotionUpdateView, self).form_valid(form)
|
||||||
self.object.write_log([ugettext_noop('Motion updated')], self.request.user)
|
self.object.write_log([ugettext_noop('Motion updated')], self.request.user)
|
||||||
return value
|
|
||||||
|
|
||||||
def manipulate_object(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Removes the supporters if config option is True and supporting is still
|
|
||||||
available in the state.
|
|
||||||
"""
|
|
||||||
return_value = super(MotionUpdateView, self).manipulate_object(*args, **kwargs)
|
|
||||||
if (config['motion_remove_supporters'] and self.object.state.allow_support and
|
if (config['motion_remove_supporters'] and self.object.state.allow_support and
|
||||||
not self.request.user.has_perm('motion.can_manage_motion')):
|
not self.request.user.has_perm('motion.can_manage_motion')):
|
||||||
self.object.clear_supporters()
|
self.object.clear_supporters()
|
||||||
self.object.write_log([ugettext_noop('All supporters removed')], self.request.user)
|
self.object.write_log([ugettext_noop('All supporters removed')], self.request.user)
|
||||||
return return_value
|
return response
|
||||||
|
|
||||||
motion_edit = MotionUpdateView.as_view()
|
motion_edit = MotionUpdateView.as_view()
|
||||||
|
|
||||||
|
|
||||||
class MotionDeleteView(DeleteView):
|
class MotionDeleteView(DeleteView):
|
||||||
"""View to delete a motion."""
|
"""
|
||||||
|
View to delete a motion.
|
||||||
|
"""
|
||||||
model = Motion
|
model = Motion
|
||||||
success_url_name = 'motion_list'
|
success_url_name = 'motion_list'
|
||||||
|
|
||||||
def has_permission(self, request, *args, **kwargs):
|
def has_permission(self, request, *args, **kwargs):
|
||||||
"""Check if the request.user has the permission to delete the motion."""
|
"""
|
||||||
|
Check if the request.user has the permission to delete the motion.
|
||||||
|
"""
|
||||||
return self.get_object().get_allowed_actions(request.user)['delete']
|
return self.get_object().get_allowed_actions(request.user)['delete']
|
||||||
|
|
||||||
def get_success_message(self):
|
def get_success_message(self):
|
||||||
@ -247,11 +247,10 @@ class MotionDeleteView(DeleteView):
|
|||||||
motion_delete = MotionDeleteView.as_view()
|
motion_delete = MotionDeleteView.as_view()
|
||||||
|
|
||||||
|
|
||||||
class VersionPermitView(GetVersionMixin, SingleObjectMixin, QuestionMixin, RedirectView):
|
class VersionPermitView(SingleObjectMixin, QuestionMixin, RedirectView):
|
||||||
"""
|
"""
|
||||||
View to permit a version of a motion.
|
View to permit a version of a motion.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
model = Motion
|
model = Motion
|
||||||
question_url_name = 'motion_version_detail'
|
question_url_name = 'motion_version_detail'
|
||||||
success_url_name = 'motion_version_detail'
|
success_url_name = 'motion_version_detail'
|
||||||
@ -262,46 +261,56 @@ class VersionPermitView(GetVersionMixin, SingleObjectMixin, QuestionMixin, Redir
|
|||||||
Set self.object to a motion.
|
Set self.object to a motion.
|
||||||
"""
|
"""
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
|
version_number = self.kwargs.get('version_number', None)
|
||||||
|
try:
|
||||||
|
self.version = self.object.versions.get(version_number=int(version_number))
|
||||||
|
except MotionVersion.DoesNotExist:
|
||||||
|
raise Http404('Version %s not found' % version_number)
|
||||||
return super(VersionPermitView, self).get(*args, **kwargs)
|
return super(VersionPermitView, self).get(*args, **kwargs)
|
||||||
|
|
||||||
def get_url_name_args(self):
|
def get_url_name_args(self):
|
||||||
"""
|
"""
|
||||||
Return a list with arguments to create the success- and question_url.
|
Returns a list with arguments to create the success- and question_url.
|
||||||
"""
|
"""
|
||||||
return [self.object.pk, self.object.version.version_number]
|
return [self.object.pk, self.version.version_number]
|
||||||
|
|
||||||
def get_question(self):
|
def get_question(self):
|
||||||
"""
|
"""
|
||||||
Return a string, shown to the user as question to permit the version.
|
Return a string, shown to the user as question to permit the version.
|
||||||
"""
|
"""
|
||||||
return _('Are you sure you want permit Version %s?') % self.object.version.version_number
|
return _('Are you sure you want permit version %s?') % self.version.version_number
|
||||||
|
|
||||||
def case_yes(self):
|
def case_yes(self):
|
||||||
"""
|
"""
|
||||||
Activate the version, if the user chooses 'yes'.
|
Activate the version, if the user chooses 'yes'.
|
||||||
"""
|
"""
|
||||||
self.object.set_active_version(self.object.version)
|
self.object.active_version = self.version
|
||||||
self.object.save(ignore_version_data=True)
|
self.object.save(update_fields=['active_version'])
|
||||||
self.object.write_log(
|
self.object.write_log(
|
||||||
message_list=[ugettext_noop('Version %d permitted') % self.object.version.version_number],
|
message_list=[ugettext_noop('Version %d permitted')
|
||||||
|
% self.version.version_number],
|
||||||
person=self.request.user)
|
person=self.request.user)
|
||||||
|
|
||||||
version_permit = VersionPermitView.as_view()
|
version_permit = VersionPermitView.as_view()
|
||||||
|
|
||||||
|
|
||||||
class VersionDiffView(DetailView):
|
class VersionDiffView(DetailView):
|
||||||
"""Show diff between two versions of a motion."""
|
"""
|
||||||
|
Show diff between two versions of a motion.
|
||||||
|
"""
|
||||||
permission_required = 'motion.can_see_motion'
|
permission_required = 'motion.can_see_motion'
|
||||||
model = Motion
|
model = Motion
|
||||||
template_name = 'motion/motion_diff.html'
|
template_name = 'motion/motion_diff.html'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
"""Return the template context with versions and html diff strings."""
|
"""
|
||||||
|
Return the template context with versions and html diff strings.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
rev1 = int(self.request.GET['rev1'])
|
rev1 = int(self.request.GET['rev1'])
|
||||||
rev2 = int(self.request.GET['rev2'])
|
rev2 = int(self.request.GET['rev2'])
|
||||||
version_rev1 = self.object.versions.get(version_number=self.request.GET['rev1'])
|
version_rev1 = self.object.versions.get(version_number=rev1)
|
||||||
version_rev2 = self.object.versions.get(version_number=self.request.GET['rev2'])
|
version_rev2 = self.object.versions.get(version_number=rev2)
|
||||||
diff_text = htmldiff(version_rev1.text, version_rev2.text)
|
diff_text = htmldiff(version_rev1.text, version_rev2.text)
|
||||||
diff_reason = htmldiff(version_rev1.reason, version_rev2.reason)
|
diff_reason = htmldiff(version_rev1.reason, version_rev2.reason)
|
||||||
except (KeyError, ValueError, MotionVersion.DoesNotExist):
|
except (KeyError, ValueError, MotionVersion.DoesNotExist):
|
||||||
@ -323,7 +332,8 @@ version_diff = VersionDiffView.as_view()
|
|||||||
|
|
||||||
|
|
||||||
class SupportView(SingleObjectMixin, QuestionMixin, RedirectView):
|
class SupportView(SingleObjectMixin, QuestionMixin, RedirectView):
|
||||||
"""View to support or unsupport a motion.
|
"""
|
||||||
|
View to support or unsupport a motion.
|
||||||
|
|
||||||
If self.support is True, the view will append a request.user to the supporter list.
|
If self.support is True, the view will append a request.user to the supporter list.
|
||||||
|
|
||||||
@ -335,12 +345,16 @@ class SupportView(SingleObjectMixin, QuestionMixin, RedirectView):
|
|||||||
support = True
|
support = True
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
"""Set self.object to a motion."""
|
"""
|
||||||
|
Set self.object to a motion.
|
||||||
|
"""
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
return super(SupportView, self).get(request, *args, **kwargs)
|
return super(SupportView, self).get(request, *args, **kwargs)
|
||||||
|
|
||||||
def check_permission(self, request):
|
def check_permission(self, request):
|
||||||
"""Return True if the user can support or unsupport the motion. Else: False."""
|
"""
|
||||||
|
Return True if the user can support or unsupport the motion. Else: False.
|
||||||
|
"""
|
||||||
allowed_actions = self.object.get_allowed_actions(request.user)
|
allowed_actions = self.object.get_allowed_actions(request.user)
|
||||||
if self.support and not allowed_actions['support']:
|
if self.support and not allowed_actions['support']:
|
||||||
messages.error(request, _('You can not support this motion.'))
|
messages.error(request, _('You can not support this motion.'))
|
||||||
@ -352,14 +366,17 @@ class SupportView(SingleObjectMixin, QuestionMixin, RedirectView):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def get_question(self):
|
def get_question(self):
|
||||||
"""Return the question string."""
|
"""
|
||||||
|
Return the question string.
|
||||||
|
"""
|
||||||
if self.support:
|
if self.support:
|
||||||
return _('Do you really want to support this motion?')
|
return _('Do you really want to support this motion?')
|
||||||
else:
|
else:
|
||||||
return _('Do you really want to unsupport this motion?')
|
return _('Do you really want to unsupport this motion?')
|
||||||
|
|
||||||
def case_yes(self):
|
def case_yes(self):
|
||||||
"""Append or remove the request.user from the motion.
|
"""
|
||||||
|
Append or remove the request.user from the motion.
|
||||||
|
|
||||||
First the method checks the permissions, and writes a log message after
|
First the method checks the permissions, and writes a log message after
|
||||||
appending or removing the user.
|
appending or removing the user.
|
||||||
@ -374,14 +391,18 @@ class SupportView(SingleObjectMixin, QuestionMixin, RedirectView):
|
|||||||
self.object.write_log([ugettext_noop("Supporter: -%s") % user], user)
|
self.object.write_log([ugettext_noop("Supporter: -%s") % user], user)
|
||||||
|
|
||||||
def get_success_message(self):
|
def get_success_message(self):
|
||||||
"""Return the success message."""
|
"""
|
||||||
|
Return the success message.
|
||||||
|
"""
|
||||||
if self.support:
|
if self.support:
|
||||||
return _("You have supported this motion successfully.")
|
return _("You have supported this motion successfully.")
|
||||||
else:
|
else:
|
||||||
return _("You have unsupported this motion successfully.")
|
return _("You have unsupported this motion successfully.")
|
||||||
|
|
||||||
def get_redirect_url(self, **kwargs):
|
def get_redirect_url(self, **kwargs):
|
||||||
"""Return the url, the view should redirect to."""
|
"""
|
||||||
|
Return the url, the view should redirect to.
|
||||||
|
"""
|
||||||
return self.object.get_absolute_url()
|
return self.object.get_absolute_url()
|
||||||
|
|
||||||
motion_support = SupportView.as_view(support=True)
|
motion_support = SupportView.as_view(support=True)
|
||||||
@ -389,23 +410,31 @@ motion_unsupport = SupportView.as_view(support=False)
|
|||||||
|
|
||||||
|
|
||||||
class PollCreateView(SingleObjectMixin, RedirectView):
|
class PollCreateView(SingleObjectMixin, RedirectView):
|
||||||
"""View to create a poll for a motion."""
|
"""
|
||||||
|
View to create a poll for a motion.
|
||||||
|
"""
|
||||||
permission_required = 'motion.can_manage_motion'
|
permission_required = 'motion.can_manage_motion'
|
||||||
model = Motion
|
model = Motion
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
"""Set self.object to a motion."""
|
"""
|
||||||
|
Set self.object to a motion.
|
||||||
|
"""
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
return super(PollCreateView, self).get(request, *args, **kwargs)
|
return super(PollCreateView, self).get(request, *args, **kwargs)
|
||||||
|
|
||||||
def pre_redirect(self, request, *args, **kwargs):
|
def pre_redirect(self, request, *args, **kwargs):
|
||||||
"""Create the poll for the motion."""
|
"""
|
||||||
|
Create the poll for the motion.
|
||||||
|
"""
|
||||||
self.poll = self.object.create_poll()
|
self.poll = self.object.create_poll()
|
||||||
self.object.write_log([ugettext_noop("Poll created")], request.user)
|
self.object.write_log([ugettext_noop("Poll created")], request.user)
|
||||||
messages.success(request, _("New vote was successfully created."))
|
messages.success(request, _("New vote was successfully created."))
|
||||||
|
|
||||||
def get_redirect_url(self, **kwargs):
|
def get_redirect_url(self, **kwargs):
|
||||||
"""Return the URL to the EditView of the poll."""
|
"""
|
||||||
|
Return the URL to the EditView of the poll.
|
||||||
|
"""
|
||||||
return reverse('motion_poll_edit', args=[self.object.pk, self.poll.poll_number])
|
return reverse('motion_poll_edit', args=[self.object.pk, self.poll.poll_number])
|
||||||
|
|
||||||
poll_create = PollCreateView.as_view()
|
poll_create = PollCreateView.as_view()
|
||||||
@ -555,31 +584,38 @@ class MotionSetStateView(SingleObjectMixin, RedirectView):
|
|||||||
except WorkflowError, e: # TODO: Is a WorkflowError still possible here?
|
except WorkflowError, e: # TODO: Is a WorkflowError still possible here?
|
||||||
messages.error(request, e)
|
messages.error(request, e)
|
||||||
else:
|
else:
|
||||||
self.object.save(ignore_version_data=True)
|
self.object.save(update_fields=['state'])
|
||||||
self.object.write_log(
|
self.object.write_log(
|
||||||
message_list=[ugettext_noop('State changed to '), self.object.state.name],
|
message_list=[ugettext_noop('State changed to '), self.object.state.name],
|
||||||
person=self.request.user)
|
person=self.request.user)
|
||||||
messages.success(request,
|
messages.success(request,
|
||||||
_('The state of the motion was set to %s.') % html_strong(_(self.object.state.name)))
|
_('The state of the motion was set to %s.')
|
||||||
|
% html_strong(_(self.object.state.name)))
|
||||||
|
|
||||||
set_state = MotionSetStateView.as_view()
|
set_state = MotionSetStateView.as_view()
|
||||||
reset_state = MotionSetStateView.as_view(reset=True)
|
reset_state = MotionSetStateView.as_view(reset=True)
|
||||||
|
|
||||||
|
|
||||||
class CreateAgendaItemView(SingleObjectMixin, RedirectView):
|
class CreateAgendaItemView(SingleObjectMixin, RedirectView):
|
||||||
"""View to create and agenda item for a motion."""
|
"""
|
||||||
|
View to create and agenda item for a motion.
|
||||||
|
"""
|
||||||
permission_required = 'agenda.can_manage_agenda'
|
permission_required = 'agenda.can_manage_agenda'
|
||||||
model = Motion
|
model = Motion
|
||||||
url_name = 'item_overview'
|
url_name = 'item_overview'
|
||||||
url_name_args = []
|
url_name_args = []
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
"""Set self.object to a motion."""
|
"""
|
||||||
|
Set self.object to a motion.
|
||||||
|
"""
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
return super(CreateAgendaItemView, self).get(request, *args, **kwargs)
|
return super(CreateAgendaItemView, self).get(request, *args, **kwargs)
|
||||||
|
|
||||||
def pre_redirect(self, request, *args, **kwargs):
|
def pre_redirect(self, request, *args, **kwargs):
|
||||||
"""Create the agenda item."""
|
"""
|
||||||
|
Create the agenda item.
|
||||||
|
"""
|
||||||
self.item = Item.objects.create(related_sid=self.object.sid)
|
self.item = Item.objects.create(related_sid=self.object.sid)
|
||||||
self.object.write_log([ugettext_noop('Agenda item created')], self.request.user)
|
self.object.write_log([ugettext_noop('Agenda item created')], self.request.user)
|
||||||
|
|
||||||
@ -587,32 +623,40 @@ create_agenda_item = CreateAgendaItemView.as_view()
|
|||||||
|
|
||||||
|
|
||||||
class MotionPDFView(SingleObjectMixin, PDFView):
|
class MotionPDFView(SingleObjectMixin, PDFView):
|
||||||
"""Create the PDF for one, or all motions.
|
"""
|
||||||
|
Create the PDF for one, or all motions.
|
||||||
|
|
||||||
If self.print_all_motions is True, the view returns a PDF with all motions.
|
If self.print_all_motions is True, the view returns a PDF with all motions.
|
||||||
|
|
||||||
If self.print_all_motions is False, the view returns a PDF with only one
|
If self.print_all_motions is False, the view returns a PDF with only one
|
||||||
motion."""
|
motion.
|
||||||
|
"""
|
||||||
permission_required = 'motion.can_see_motion'
|
permission_required = 'motion.can_see_motion'
|
||||||
model = Motion
|
model = Motion
|
||||||
top_space = 0
|
top_space = 0
|
||||||
print_all_motions = False
|
print_all_motions = False
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
"""Set self.object to a motion."""
|
"""
|
||||||
|
Set self.object to a motion.
|
||||||
|
"""
|
||||||
if not self.print_all_motions:
|
if not self.print_all_motions:
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
return super(MotionPDFView, self).get(request, *args, **kwargs)
|
return super(MotionPDFView, self).get(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_filename(self):
|
def get_filename(self):
|
||||||
"""Return the filename for the PDF."""
|
"""
|
||||||
|
Return the filename for the PDF.
|
||||||
|
"""
|
||||||
if self.print_all_motions:
|
if self.print_all_motions:
|
||||||
return _("Motions")
|
return _("Motions")
|
||||||
else:
|
else:
|
||||||
return _("Motion: %s") % unicode(self.object)
|
return _("Motion: %s") % unicode(self.object)
|
||||||
|
|
||||||
def append_to_pdf(self, pdf):
|
def append_to_pdf(self, pdf):
|
||||||
"""Append PDF objects."""
|
"""
|
||||||
|
Append PDF objects.
|
||||||
|
"""
|
||||||
if self.print_all_motions:
|
if self.print_all_motions:
|
||||||
motions_to_pdf(pdf)
|
motions_to_pdf(pdf)
|
||||||
else:
|
else:
|
||||||
@ -694,7 +738,9 @@ motion_csv_import = MotionCSVImportView.as_view()
|
|||||||
|
|
||||||
|
|
||||||
def register_tab(request):
|
def register_tab(request):
|
||||||
"""Return the motion tab."""
|
"""
|
||||||
|
Return the motion tab.
|
||||||
|
"""
|
||||||
# TODO: Find a better way to set the selected var.
|
# TODO: Find a better way to set the selected var.
|
||||||
return Tab(
|
return Tab(
|
||||||
title=_('Motions'),
|
title=_('Motions'),
|
||||||
@ -705,7 +751,8 @@ def register_tab(request):
|
|||||||
|
|
||||||
|
|
||||||
def get_widgets(request):
|
def get_widgets(request):
|
||||||
"""Return the motion widgets for the dashboard.
|
"""
|
||||||
|
Return the motion widgets for the dashboard.
|
||||||
|
|
||||||
There is only one widget. It shows all motions.
|
There is only one widget. It shows all motions.
|
||||||
"""
|
"""
|
||||||
|
@ -19,30 +19,24 @@ class ModelTest(TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.motion = Motion.objects.create(title='v1')
|
self.motion = Motion.objects.create(title='v1')
|
||||||
self.test_user = User.objects.create(username='blub')
|
self.test_user = User.objects.create(username='blub')
|
||||||
|
# Use the simple workflow
|
||||||
self.workflow = Workflow.objects.get(pk=1)
|
self.workflow = Workflow.objects.get(pk=1)
|
||||||
|
|
||||||
def test_create_new_version(self):
|
def test_create_new_version(self):
|
||||||
motion = Motion.objects.create(title='m1')
|
motion = self.motion
|
||||||
self.assertEqual(motion.versions.count(), 1)
|
self.assertEqual(motion.versions.count(), 1)
|
||||||
|
|
||||||
motion.new_version
|
# new data, but no new version
|
||||||
motion.save()
|
|
||||||
self.assertEqual(motion.versions.count(), 2)
|
|
||||||
|
|
||||||
motion.title = 'new title'
|
motion.title = 'new title'
|
||||||
motion.save()
|
motion.save()
|
||||||
self.assertEqual(motion.versions.count(), 2)
|
self.assertEqual(motion.versions.count(), 1)
|
||||||
|
|
||||||
motion.save()
|
|
||||||
self.assertEqual(motion.versions.count(), 2)
|
|
||||||
|
|
||||||
|
# new data and new version
|
||||||
motion.text = 'new text'
|
motion.text = 'new text'
|
||||||
motion.new_version
|
motion.save(use_version=motion.get_new_version())
|
||||||
motion.save()
|
self.assertEqual(motion.versions.count(), 2)
|
||||||
self.assertEqual(motion.versions.count(), 3)
|
self.assertEqual(motion.title, 'new title')
|
||||||
|
self.assertEqual(motion.text, 'new text')
|
||||||
motion.save()
|
|
||||||
self.assertEqual(motion.versions.count(), 3)
|
|
||||||
|
|
||||||
def test_version_data(self):
|
def test_version_data(self):
|
||||||
motion = Motion()
|
motion = Motion()
|
||||||
@ -53,36 +47,24 @@ class ModelTest(TestCase):
|
|||||||
motion.title = 'title'
|
motion.title = 'title'
|
||||||
self.assertEqual(motion._title, 'title')
|
self.assertEqual(motion._title, 'title')
|
||||||
|
|
||||||
|
motion.text = 'text'
|
||||||
|
self.assertEqual(motion._text, 'text')
|
||||||
|
|
||||||
motion.reason = 'reason'
|
motion.reason = 'reason'
|
||||||
self.assertEqual(motion._reason, 'reason')
|
self.assertEqual(motion._reason, 'reason')
|
||||||
|
|
||||||
def test_version(self):
|
def test_version(self):
|
||||||
motion = Motion.objects.create(title='v1')
|
motion = self.motion
|
||||||
|
|
||||||
motion.title = 'v2'
|
motion.title = 'v2'
|
||||||
motion.new_version
|
motion.save(use_version=motion.get_new_version())
|
||||||
motion.save()
|
v2_version = motion.get_last_version()
|
||||||
v2_version = motion.version
|
|
||||||
motion.title = 'v3'
|
motion.title = 'v3'
|
||||||
motion.new_version
|
motion.save(use_version=motion.get_new_version())
|
||||||
motion.save()
|
|
||||||
with self.assertRaises(AttributeError):
|
with self.assertRaises(AttributeError):
|
||||||
self._title
|
self._title
|
||||||
self.assertEqual(motion.title, 'v3')
|
self.assertEqual(motion.title, 'v3')
|
||||||
|
|
||||||
motion.version = 1
|
|
||||||
self.assertEqual(motion.title, 'v1')
|
|
||||||
|
|
||||||
motion.version = v2_version
|
|
||||||
self.assertEqual(motion.title, 'v2')
|
|
||||||
|
|
||||||
motion.version = None
|
|
||||||
motion.version = None # Test to set a version to None, which is already None
|
|
||||||
self.assertEqual(motion.title, 'v3')
|
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
motion.version = 'wrong'
|
|
||||||
|
|
||||||
def test_absolute_url(self):
|
def test_absolute_url(self):
|
||||||
motion_id = self.motion.id
|
motion_id = self.motion.id
|
||||||
|
|
||||||
@ -147,23 +129,21 @@ class ModelTest(TestCase):
|
|||||||
motion = Motion()
|
motion = Motion()
|
||||||
motion.title = 'foo'
|
motion.title = 'foo'
|
||||||
motion.text = 'bar'
|
motion.text = 'bar'
|
||||||
first_version = motion.version
|
|
||||||
motion.save()
|
motion.save()
|
||||||
|
first_version = motion.get_last_version()
|
||||||
|
|
||||||
motion = Motion.objects.get(pk=motion.pk)
|
motion = Motion.objects.get(pk=motion.pk)
|
||||||
motion.new_version
|
|
||||||
motion.title = 'New Title'
|
motion.title = 'New Title'
|
||||||
motion.save()
|
motion.save(use_version=motion.get_new_version())
|
||||||
new_version = motion.last_version
|
new_version = motion.get_last_version()
|
||||||
self.assertEqual(motion.versions.count(), 2)
|
self.assertEqual(motion.versions.count(), 2)
|
||||||
|
|
||||||
motion.set_active_version(new_version)
|
motion.active_version = new_version
|
||||||
motion.save()
|
motion.save()
|
||||||
self.assertEqual(motion.versions.count(), 2)
|
self.assertEqual(motion.versions.count(), 2)
|
||||||
|
|
||||||
motion.set_active_version(first_version)
|
motion.active_version = first_version
|
||||||
motion.version = first_version
|
motion.save(use_version=False)
|
||||||
motion.save(ignore_version_data=True)
|
|
||||||
self.assertEqual(motion.versions.count(), 2)
|
self.assertEqual(motion.versions.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
|
@ -65,6 +65,16 @@ class TestMotionDetailView(MotionViewTestCase):
|
|||||||
self.check_url('/motion/500/', self.admin_client, 404)
|
self.check_url('/motion/500/', self.admin_client, 404)
|
||||||
|
|
||||||
|
|
||||||
|
class TestMotionDetailVersionView(MotionViewTestCase):
|
||||||
|
def test_get(self):
|
||||||
|
self.motion1.title = 'AFWEROBjwerGwer'
|
||||||
|
self.motion1.save(use_version=self.motion1.get_new_version())
|
||||||
|
self.check_url('/motion/1/version/1/', self.admin_client, 200)
|
||||||
|
response = self.check_url('/motion/1/version/2/', self.admin_client, 200)
|
||||||
|
self.assertContains(response, 'AFWEROBjwerGwer')
|
||||||
|
self.check_url('/motion/1/version/500/', self.admin_client, 404)
|
||||||
|
|
||||||
|
|
||||||
class TestMotionCreateView(MotionViewTestCase):
|
class TestMotionCreateView(MotionViewTestCase):
|
||||||
url = '/motion/new/'
|
url = '/motion/new/'
|
||||||
|
|
||||||
@ -72,7 +82,6 @@ class TestMotionCreateView(MotionViewTestCase):
|
|||||||
self.check_url(self.url, self.admin_client, 200)
|
self.check_url(self.url, self.admin_client, 200)
|
||||||
|
|
||||||
def test_admin(self):
|
def test_admin(self):
|
||||||
self.assertFalse(Motion.objects.filter(versions__title='new motion').exists())
|
|
||||||
response = self.admin_client.post(self.url, {'title': 'new motion',
|
response = self.admin_client.post(self.url, {'title': 'new motion',
|
||||||
'text': 'motion text',
|
'text': 'motion text',
|
||||||
'reason': 'motion reason',
|
'reason': 'motion reason',
|
||||||
@ -81,7 +90,6 @@ class TestMotionCreateView(MotionViewTestCase):
|
|||||||
self.assertTrue(Motion.objects.filter(versions__title='new motion').exists())
|
self.assertTrue(Motion.objects.filter(versions__title='new motion').exists())
|
||||||
|
|
||||||
def test_delegate(self):
|
def test_delegate(self):
|
||||||
self.assertFalse(Motion.objects.filter(versions__title='delegate motion').exists())
|
|
||||||
response = self.delegate_client.post(self.url, {'title': 'delegate motion',
|
response = self.delegate_client.post(self.url, {'title': 'delegate motion',
|
||||||
'text': 'motion text',
|
'text': 'motion text',
|
||||||
'reason': 'motion reason',
|
'reason': 'motion reason',
|
||||||
@ -91,7 +99,6 @@ class TestMotionCreateView(MotionViewTestCase):
|
|||||||
self.assertTrue(motion.is_submitter(self.delegate))
|
self.assertTrue(motion.is_submitter(self.delegate))
|
||||||
|
|
||||||
def test_registered(self):
|
def test_registered(self):
|
||||||
self.assertFalse(Motion.objects.filter(versions__title='registered motion').exists())
|
|
||||||
response = self.registered_client.post(self.url, {'title': 'registered motion',
|
response = self.registered_client.post(self.url, {'title': 'registered motion',
|
||||||
'text': 'motion text',
|
'text': 'motion text',
|
||||||
'reason': 'motion reason',
|
'reason': 'motion reason',
|
||||||
@ -297,24 +304,25 @@ class TestMotionDeleteView(MotionViewTestCase):
|
|||||||
class TestVersionPermitView(MotionViewTestCase):
|
class TestVersionPermitView(MotionViewTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestVersionPermitView, self).setUp()
|
super(TestVersionPermitView, self).setUp()
|
||||||
self.motion1.new_version
|
self.motion1.title = 'new'
|
||||||
self.motion1.save()
|
self.motion1.save(use_version=self.motion1.get_new_version())
|
||||||
|
|
||||||
def test_get(self):
|
def test_get(self):
|
||||||
response = self.check_url('/motion/1/version/2/permit/', self.admin_client, 302)
|
response = self.check_url('/motion/1/version/2/permit/', self.admin_client, 302)
|
||||||
self.assertRedirects(response, '/motion/1/version/2/')
|
self.assertRedirects(response, '/motion/1/version/2/')
|
||||||
|
|
||||||
def test_post(self):
|
def test_post(self):
|
||||||
new_version = self.motion1.last_version
|
new_version = self.motion1.get_last_version()
|
||||||
response = self.admin_client.post('/motion/1/version/2/permit/', {'yes': 1})
|
response = self.admin_client.post('/motion/1/version/2/permit/', {'yes': 1})
|
||||||
self.assertRedirects(response, '/motion/1/version/2/')
|
self.assertRedirects(response, '/motion/1/version/2/')
|
||||||
self.assertEqual(self.motion1.active_version, new_version)
|
self.assertEqual(self.motion1.get_active_version(), new_version)
|
||||||
|
|
||||||
def test_activate_old_version(self):
|
def test_activate_old_version(self):
|
||||||
new_version = self.motion1.last_version
|
new_version = self.motion1.get_last_version()
|
||||||
first_version = self.motion1.versions.order_by('version_number')[0]
|
first_version = self.motion1.versions.order_by('version_number')[0]
|
||||||
|
|
||||||
self.motion1.set_active_version(new_version)
|
self.motion1.active_version = new_version
|
||||||
|
self.motion1.save()
|
||||||
self.assertEqual(self.motion1.versions.count(), 2)
|
self.assertEqual(self.motion1.versions.count(), 2)
|
||||||
response = self.admin_client.post('/motion/1/version/1/permit/', {'yes': 1})
|
response = self.admin_client.post('/motion/1/version/1/permit/', {'yes': 1})
|
||||||
self.motion1 = Motion.objects.get(pk=1)
|
self.motion1 = Motion.objects.get(pk=1)
|
||||||
|
Loading…
Reference in New Issue
Block a user