Insert new feature: attachments for motions.

Fixed #522. Database struture changed.
This commit is contained in:
Norman Jäckel 2013-10-16 23:57:29 +02:00
parent 2f288f6cb1
commit 30371e964f
7 changed files with 74 additions and 8 deletions

View File

@ -14,6 +14,7 @@ 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,
LocalizedModelChoiceField)
from openslides.utils.person import MultiplePersonFormField, PersonFormField
@ -48,13 +49,22 @@ class BaseMotionForm(CleanHtmlFormMixin, CssClassMixin, forms.ModelForm):
Reason of the motion. will be saved in a MotionVersion object.
"""
attachments = forms.ModelMultipleChoiceField(
queryset=Mediafile.objects.all(),
required=False,
label=ugettext_lazy('Attachments'))
"""
Attachments of the motion.
"""
class Meta:
model = Motion
fields = ()
def __init__(self, *args, **kwargs):
"""
Fill the FormFields releated to the version data with initial data.
Fill the FormFields related to the version data with initial data.
Fill also the initial data for attachments.
"""
self.motion = kwargs.get('instance', None)
self.initial = kwargs.setdefault('initial', {})
@ -63,6 +73,7 @@ class BaseMotionForm(CleanHtmlFormMixin, CssClassMixin, forms.ModelForm):
self.initial['title'] = last_version.title
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)

View File

@ -21,6 +21,7 @@ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy, ugettext_noop
from openslides.config.api import config
from openslides.mediafile.models import Mediafile
from openslides.poll.models import (BaseOption, BasePoll, BaseVote,
CountInvalid, CountVotesCast)
from openslides.projector.models import RelatedModelMixin, SlideMixin
@ -78,6 +79,11 @@ class Motion(SlideMixin, models.Model):
ForeignKey to one category of motions.
"""
attachments = models.ManyToManyField(Mediafile)
"""
Many to many relation to mediafile objects.
"""
# TODO: proposal
#master = models.ForeignKey('self', null=True, blank=True)

View File

@ -89,6 +89,17 @@
{{ reason|safe|default:'' }}
<br>
<!-- Attachments -->
{% with attachments=motion.attachments.all %}
{% if attachments %}
<h4>{% trans "Attachments" %}:</h4>
{% for attachment in attachments %}
<p><a href="{{ attachment.mediafile.url }}">{{ attachment }}</a></p>
{% endfor %}
<br>
{% endif %}
{% endwith %}
<!-- Version history -->
{% with versions=motion.versions.all %}
{% if versions|length > 1 %}

View File

@ -126,6 +126,10 @@ class MotionEditMixin(object):
[MotionSupporter(motion=self.object, person=person)
for person in form.cleaned_data['supporter']])
# Save the attachments
self.object.attachments.clear()
self.object.attachments.add(*form.cleaned_data['attachments'])
# Update the projector if the motion is on it. This can not be done in
# the model, because bulk_create does not call the save method.
active_slide = get_active_slide()

View File

@ -43,9 +43,8 @@ class MediafileTest(TestCase):
self.normal_user.reset_password('default')
# Setup a mediafile object
self.tmp_dir = os.path.realpath(os.path.dirname(__file__))
settings.MEDIA_ROOT = self.tmp_dir
tmpfile_no, mediafile_path = tempfile.mkstemp(prefix='tmp_openslides_test', dir=self.tmp_dir)
self.tmp_dir = settings.MEDIA_ROOT
tmpfile_no, mediafile_path = tempfile.mkstemp(prefix='tmp_openslides_test_', dir=self.tmp_dir)
self.object = Mediafile.objects.create(title='Title File 1', mediafile=mediafile_path, uploader=self.vip_user)
os.close(tmpfile_no)
@ -155,7 +154,7 @@ class MediafileTest(TestCase):
def test_edit_mediafile_post_request(self):
# Test only one user
tmpfile_no, mediafile_2_path = tempfile.mkstemp(prefix='tmp_openslides_test', dir=self.tmp_dir)
tmpfile_no, mediafile_2_path = tempfile.mkstemp(prefix='tmp_openslides_test_', dir=self.tmp_dir)
os.close(tmpfile_no)
object_2 = Mediafile.objects.create(title='Title File 2', mediafile=mediafile_2_path, uploader=self.vip_user)
client_1 = self.login_clients()['client_manager']
@ -185,7 +184,7 @@ class MediafileTest(TestCase):
self.assertRedirects(response, expected_url='/login/?next=/mediafile/2/del/', status_code=302, target_status_code=200)
def test_delete_mediafile_post_request(self):
tmpfile_no, mediafile_3_path = tempfile.mkstemp(prefix='tmp_openslides_test', dir=self.tmp_dir)
tmpfile_no, mediafile_3_path = tempfile.mkstemp(prefix='tmp_openslides_test_', dir=self.tmp_dir)
os.close(tmpfile_no)
object_3 = Mediafile.objects.create(title='Title File 3', mediafile=mediafile_3_path)
client_1 = self.login_clients()['client_manager']
@ -194,7 +193,7 @@ class MediafileTest(TestCase):
self.assertFalse(os.path.exists(object_3.mediafile.path))
def test_filesize(self):
tmpfile_no, mediafile_4_path = tempfile.mkstemp(prefix='tmp_openslides_test', dir=self.tmp_dir)
tmpfile_no, mediafile_4_path = tempfile.mkstemp(prefix='tmp_openslides_test_', dir=self.tmp_dir)
os.close(tmpfile_no)
object_4 = Mediafile.objects.create(title='Title File 4', mediafile=mediafile_4_path)
self.assertEqual(object_4.get_filesize(), '< 1 kB')

View File

@ -8,9 +8,14 @@
:license: GNU GPL, see LICENSE for more details.
"""
import os
import tempfile
from django.conf import settings
from django.test.client import Client
from openslides.config.api import config
from openslides.mediafile.models import Mediafile
from openslides.motion.models import Category, Motion, MotionLog, State
from openslides.participant.models import Group, User
from openslides.utils.test import TestCase
@ -64,6 +69,25 @@ class TestMotionDetailView(MotionViewTestCase):
self.check_url('/motion/2/', self.admin_client, 200)
self.check_url('/motion/500/', self.admin_client, 404)
def test_attachment(self):
# Preparation
tmpfile_no, attachment_path = tempfile.mkstemp(prefix='tmp_openslides_test_', dir=settings.MEDIA_ROOT)
os.close(tmpfile_no)
attachment = Mediafile.objects.create(title='TestFile_Neiri4xai4ueseGohzid', mediafile=attachment_path)
self.motion1.attachments.add(attachment)
# Test appearance
response = self.registered_client.get('/motion/1/')
self.assertContains(response, '<h4>Attachments:</h4>')
self.assertContains(response, 'TestFile_Neiri4xai4ueseGohzid')
# Test disappearance
attachment.mediafile.delete()
attachment.delete()
response = self.registered_client.get('/motion/1/')
self.assertNotContains(response, '<h4>Attachments:</h4>')
self.assertNotContains(response, 'TestFile_Neiri4xai4ueseGohzid')
class TestMotionDetailVersionView(MotionViewTestCase):
def test_get(self):
@ -363,6 +387,15 @@ class TestMotionUpdateView(MotionViewTestCase):
'workflow': 2})
self.assertEqual(MotionLog.objects.get(pk=5).message_list, ['Motion version', ' 2 ', 'updated'])
def test_attachment_initial(self):
attachment = Mediafile.objects.create(title='test_title_iech1maatahShiecohca')
self.motion1.attachments.add(attachment)
response = self.admin_client.get(self.url)
self.assertContains(response, '<option value="1" selected="selected">%s</option>' % attachment.title)
self.motion1.attachments.clear()
response = self.admin_client.get(self.url)
self.assertNotContains(response, '<option value="1" selected="selected">%s</option>' % attachment.title)
class TestMotionDeleteView(MotionViewTestCase):
def test_get(self):

View File

@ -1,6 +1,8 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
from openslides.global_settings import * # noqa
# Use 'DEBUG = True' to get more details for server errors
@ -36,7 +38,7 @@ INSTALLED_APPS += INSTALLED_PLUGINS
# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"
MEDIA_ROOT = ''
MEDIA_ROOT = os.path.realpath(os.path.dirname(__file__))
# Use RAM storage for whoosh index
HAYSTACK_CONNECTIONS['default']['STORAGE'] = 'ram'