Insert new feature: attachments for motions.
Fixed #522. Database struture changed.
This commit is contained in:
parent
2f288f6cb1
commit
30371e964f
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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 %}
|
||||
|
@ -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()
|
||||
|
@ -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')
|
||||
|
@ -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):
|
||||
|
@ -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'
|
||||
|
Loading…
Reference in New Issue
Block a user