motion csv import

This commit is contained in:
Oskar Hahn 2013-05-08 18:07:09 +02:00 committed by Norman Jäckel
parent 865860a96f
commit 485518975a
7 changed files with 180 additions and 7 deletions

View File

@ -1,7 +1,7 @@
Nummer;Titel;Text;Begründung;Antragsteller (Vorname);Antragsteller (Nachname/Gruppenname);Antragsteller ist eine Gruppe
1;Entlastung des Vorstandes;Die Versammlung möge beschließen, den Vorstand für seine letzte Legislaturperiode zu entlasten.;Bericht erfolgt mündlich.;Volker;Versammlungsleitung;0
2;Satzungsänderung §2, Abs.3;"Die Versammlung möge beschließen, die Satzung in § 2 Abs. 3 wie folgt zu ändern:
Identifier,Titel,Text,Begründung,Antragsteller ID
1,Entlastung des Vorstandes,"Die Versammlung möge beschließen, den Vorstand für seine letzte Legislaturperiode zu entlasten.","Bericht erfolgt mündlich.",user:5
2,"Satzungsänderung §2, Abs.3","Die Versammlung möge beschließen, die Satzung in § 2 Abs. 3 wie folgt zu ändern:
Es wird nach dem Wort ""Zweck"" der Satz ""..."" eingefügt.";Die Änderung der Satzung ist aufgrund der letzten Erfahrungen eine sinnvolle Maßnahme, weil ...;David;Delegierter;0
3;Einführung von elektronischen Abstimmungen mit OpenSlides;"Die Versammlung möge beschließen, öffentliche Abstimmungen künftig elektronisch mit dem OpenSlides Plugin ""VoteCollector"" durchzuführen.";Elektronische Abstimmungen beschleunigen den Ablauf. OpenSlides wird bereits bei uns eingesetzt und bietet ein zusätzliches Plugin, um mit Keypads für jeden Teilnehmer elektronisch abzustimmen. Die Ergebnisse werden direkt in OpenSlides gespeichert. Details gibts über den professional Support auf openslides.org.;Emma;Dampf;0
;Resolution;Die Versammlung möge beschließen, die Resolution zum Thema OpenSlides vom Ortsverband-Mitte zu verabschieden.;;;Vorstand;1
Es wird nach dem Wort ""Zweck"" der Satz ""..."" eingefügt.","Die Änderung der Satzung ist aufgrund der letzten Erfahrungen eine sinnvolle Maßnahme, weil ...",user:2
3,"Einführung von elektronischen Abstimmungen mit OpenSlides","Die Versammlung möge beschließen, öffentliche Abstimmungen künftig elektronisch mit dem OpenSlides Plugin ""VoteCollector"" durchzuführen.", Elektronische Abstimmungen beschleunigen den Ablauf. OpenSlides wird bereits bei uns eingesetzt und bietet ein zusätzliches Plugin, um mit Keypads für jeden Teilnehmer elektronisch abzustimmen. Die Ergebnisse werden direkt in OpenSlides gespeichert. Details gibts über den professional Support auf openslides.org.",user:3
,"Resolution","Die Versammlung möge beschließen, die Resolution zum Thema OpenSlides vom Ortsverband-Mitte zu verabschieden.",,user:3

Can't render this file because it contains an unexpected character in line 6 and column 527.

View File

@ -0,0 +1,58 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.motion.csv_import
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Functions to import motions from csv.
:copyright: (c) 20112013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
# TODO: Rename the file to 'csv.py' when we drop python2 support. At the moment
# the name csv has a conflict with the core-module. See:
# http://docs.python.org/2/tutorial/modules.html#intra-package-references
import csv
from django.db import transaction
from django.utils.translation import ugettext as _
from openslides.utils import csv_ext
from .models import Motion
def import_motions(csv_file):
error_messages = []
count_success = 0
csv_file.read().decode('utf-8')
csv_file.seek(0)
with transaction.commit_on_success():
for (line_no, line) in enumerate(csv.reader(csv_file)):
if line_no < 1:
# Do not read the header line
continue
# TODO: test for wrong format
try:
(identifier, title, text, reason, person_id) = line[:5]
except ValueError:
error_messages.append(_('Ignoring malformed line %d in import file.') % (line_no + 1))
continue
if identifier:
try:
motion = Motion.objects.get(identifier=identifier)
except Motion.DoesNotExist:
motion = Motion()
else:
motion = Motion()
motion.title = title
motion.text = text
motion.reason = reason
motion.save()
# TODO: person does not exist
motion.add_submitter(person_id)
count_success += 1
return (count_success, error_messages)

View File

@ -121,3 +121,8 @@ class MotionIdentifierMixin(forms.ModelForm):
raise forms.ValidationError(_('The Identifier is not unique.'))
else:
return identifier
class MotionImportForm(CssClassMixin, forms.Form):
csvfile = forms.FileField(widget=forms.FileInput(attrs={'size': '50'}),
label=ugettext_lazy('CSV File'))

View File

@ -0,0 +1,46 @@
{% extends "base.html" %}
{% load i18n %}
{% block title %}{{ block.super }} {% trans "Import motions" %} {% endblock %}
{% block content %}
<h1>
{% trans 'Import motions' %}
<small class="pull-right">
<a href="{% url 'motion_list' %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Back to overview" %}</a>
</small>
</h1>
<p>{% trans 'Select a CSV file to import motions!' %}</p>
<p>{% trans 'Please note' %}:</p>
<ul>
<li>
{% trans 'Required comma separated values' %}:<br>
<code>({% trans 'identifier, title, text, reason, submitter_id' %})</code>
</li>
<li>
{% trans 'identifier and reason are optional and may be empty' %}.
</li>
<li>
{% trans 'Required CSV file encoding: UTF-8 (Unicode).' %}
</li>
<li>
<a href="https://github.com/OpenSlides/OpenSlides/wiki/CSV-Import">{% trans 'Use the CSV example file from OpenSlides Wiki.' %}</a>
</li>
</ul>
<form enctype="multipart/form-data" action="" method="post">{% csrf_token %}
{% include "form.html" %}
<p>
<button class="btn btn-primary" type="submit">
<span class="icon import">{% trans 'Import' %}</span>
</button>
<a href="{% url 'motion_list' %}" class="btn">
{% trans 'Cancel' %}
</a>
</p>
<small>* {% trans "required" %}</small>
</form>
{% endblock %}

View File

@ -139,4 +139,9 @@ urlpatterns = patterns('openslides.motion.views',
'category_delete',
name='motion_category_delete',
),
url(r'^csv_import/$',
'motion_csv_import',
name='motion_csv_import',
),
)

View File

@ -38,8 +38,9 @@ from .models import (Motion, MotionSubmitter, MotionSupporter, MotionPoll,
MotionVersion, State, WorkflowError, Category)
from .forms import (BaseMotionForm, MotionSubmitterMixin, MotionSupporterMixin,
MotionDisableVersioningMixin, MotionCategoryMixin,
MotionIdentifierMixin)
MotionIdentifierMixin, MotionImportForm)
from .pdf import motions_to_pdf, motion_to_pdf, motion_poll_to_pdf
from .csv_import import import_motions
class MotionListView(ListView):
@ -667,6 +668,29 @@ class CategoryDeleteView(DeleteView):
category_delete = CategoryDeleteView.as_view()
class MotionCSVImportView(FormView):
"""
Import motions via csv.
"""
permission_required = 'motions.can_manage_participant'
template_name = 'motion/motion_form_csv_import.html'
form_class = MotionImportForm
success_url_name = 'motion_list'
def form_valid(self, form):
# check for valid encoding (will raise UnicodeDecodeError if not)
count_success, error_messages = import_motions(self.request.FILES['csvfile'])
for message in error_messages:
messages.error(self.request, message)
if count_success:
messages.success(
self.request,
_('%d motions were successfully imported.') % count_success)
return super(MotionCSVImportView, self).form_valid(form)
motion_csv_import = MotionCSVImportView.as_view()
def register_tab(request):
"""Return the motion tab."""
# TODO: Find a better way to set the selected var.

View File

@ -0,0 +1,35 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Tests for openslides.motion.models
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
import os
from django.test.client import Client
from openslides.config.api import config
from openslides.utils.test import TestCase
from openslides.participant.models import User
from openslides.motion.models import Motion
from openslides.motion.csv_import import import_motions
class CSVImport(TestCase):
def setUp(self):
# Admin
self.admin = User.objects.create_superuser('admin', 'admin@admin.admin', 'admin')
self.admin_client = Client()
self.admin_client.login(username='admin', password='admin')
def test_example_file(self):
csv_dir = os.path.join(os.path.dirname(__file__), '..', '..', 'extras', 'csv-examples')
with open(csv_dir + '/motions-demo_de.csv') as f:
import_motions(f)
self.assertEqual(Motion.objects.count(), 4)