motion csv import
This commit is contained in:
parent
865860a96f
commit
485518975a
@ -1,7 +1,7 @@
|
|||||||
Nummer;Titel;Text;Begründung;Antragsteller (Vorname);Antragsteller (Nachname/Gruppenname);Antragsteller ist eine Gruppe
|
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.;Volker;Versammlungsleitung;0
|
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:
|
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
|
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.;Emma;Dampf;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.",user:3
|
||||||
;Resolution;Die Versammlung möge beschließen, die Resolution zum Thema OpenSlides vom Ortsverband-Mitte zu verabschieden.;;;Vorstand;1
|
,"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.
|
58
openslides/motion/csv_import.py
Normal file
58
openslides/motion/csv_import.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
openslides.motion.csv_import
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Functions to import motions from csv.
|
||||||
|
|
||||||
|
:copyright: (c) 2011–2013 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)
|
@ -121,3 +121,8 @@ class MotionIdentifierMixin(forms.ModelForm):
|
|||||||
raise forms.ValidationError(_('The Identifier is not unique.'))
|
raise forms.ValidationError(_('The Identifier is not unique.'))
|
||||||
else:
|
else:
|
||||||
return identifier
|
return identifier
|
||||||
|
|
||||||
|
|
||||||
|
class MotionImportForm(CssClassMixin, forms.Form):
|
||||||
|
csvfile = forms.FileField(widget=forms.FileInput(attrs={'size': '50'}),
|
||||||
|
label=ugettext_lazy('CSV 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 %}
|
@ -139,4 +139,9 @@ urlpatterns = patterns('openslides.motion.views',
|
|||||||
'category_delete',
|
'category_delete',
|
||||||
name='motion_category_delete',
|
name='motion_category_delete',
|
||||||
),
|
),
|
||||||
|
|
||||||
|
url(r'^csv_import/$',
|
||||||
|
'motion_csv_import',
|
||||||
|
name='motion_csv_import',
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
@ -38,8 +38,9 @@ from .models import (Motion, MotionSubmitter, MotionSupporter, MotionPoll,
|
|||||||
MotionVersion, State, WorkflowError, Category)
|
MotionVersion, State, WorkflowError, Category)
|
||||||
from .forms import (BaseMotionForm, MotionSubmitterMixin, MotionSupporterMixin,
|
from .forms import (BaseMotionForm, MotionSubmitterMixin, MotionSupporterMixin,
|
||||||
MotionDisableVersioningMixin, MotionCategoryMixin,
|
MotionDisableVersioningMixin, MotionCategoryMixin,
|
||||||
MotionIdentifierMixin)
|
MotionIdentifierMixin, MotionImportForm)
|
||||||
from .pdf import motions_to_pdf, motion_to_pdf, motion_poll_to_pdf
|
from .pdf import motions_to_pdf, motion_to_pdf, motion_poll_to_pdf
|
||||||
|
from .csv_import import import_motions
|
||||||
|
|
||||||
|
|
||||||
class MotionListView(ListView):
|
class MotionListView(ListView):
|
||||||
@ -667,6 +668,29 @@ class CategoryDeleteView(DeleteView):
|
|||||||
category_delete = CategoryDeleteView.as_view()
|
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):
|
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.
|
||||||
|
35
tests/motion/test_csv_import.py
Normal file
35
tests/motion/test_csv_import.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Tests for openslides.motion.models
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
:copyright: 2011–2013 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)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user