diff --git a/CHANGELOG b/CHANGELOG index a03480ed2..ce2243963 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -36,6 +36,8 @@ Core: - Add a version of has_perm that can work with cached users. - Cache the group with there permissions. - Removed our db-session backend and added possibility to use any django session backend. +- New csv import layout. +- Replaced angular-csv-import through Papa Parse for csv parsing. Motions: - Added adjustable line numbering mode (outside, inside, none) for each @@ -57,7 +59,6 @@ Motions: - New PDF layout. - Added DOCX export with docxtemplater. - Changed label of former state "commited a bill" to "refered to committee". -- New csv import layout and using Papa Parse for parsing the csv. - Number of ballots printed can now be set in config. - Add new personal settings to remove all whitespaces from motion identifier. - Add new personal settings to allow amendments of amendments. diff --git a/README.rst b/README.rst index 77450342a..e41c21c0b 100644 --- a/README.rst +++ b/README.rst @@ -192,7 +192,6 @@ OpenSlides uses the following projects or parts of them: * `angular-bootstrap-colorpicker `_, License: MIT * `angular-chosen-localytics `_, License: MIT * `angular-ckeditor `_, License: MIT - * `angular-csv-import `_, License: MIT * `angular-formly `_, License: MIT * `angular-formly-templates-bootstrap `_, License: MIT * `angular-gettext `_, License: MIT diff --git a/bower.json b/bower.json index a3b4ab996..6d94e75f1 100644 --- a/bower.json +++ b/bower.json @@ -9,7 +9,6 @@ "angular-bootstrap-colorpicker": "~3.0.25", "angular-chosen-localytics": "~1.5.0", "angular-ckeditor": "~1.0.3", - "angular-csv-import": "0.0.36", "angular-file-saver": "~1.1.2", "angular-formly": "~8.4.0", "angular-formly-templates-bootstrap": "~6.2.0", diff --git a/openslides/agenda/static/js/agenda/csv.js b/openslides/agenda/static/js/agenda/csv.js index 807c0dd67..4aeef07c1 100644 --- a/openslides/agenda/static/js/agenda/csv.js +++ b/openslides/agenda/static/js/agenda/csv.js @@ -6,28 +6,37 @@ angular.module('OpenSlidesApp.agenda.csv', []) .factory('AgendaCsvExport', [ 'HumanTimeConverter', - function (HumanTimeConverter) { - return function (element, agenda) { - var csvRows = [ - ['title', 'text', 'duration', 'comment', 'is_hidden'], - ]; - _.forEach(agenda, function (item) { - var row = []; - var duration = item.duration ? HumanTimeConverter.secondsToHumanTime(item.duration*60, - { seconds: 'disabled', - hours: 'enabled' }) : ''; - row.push('"' + (item.title || '') + '"'); - row.push('"' + (item.text || '') + '"'); - row.push('"' + duration + '"'); - row.push('"' + (item.comment || '') + '"'); - row.push('"' + (item.is_hidden ? '1' : '') + '"'); - csvRows.push(row); + 'gettextCatalog', + function (HumanTimeConverter, gettextCatalog) { + var makeHeaderline = function () { + var headerline = ['Title', 'Text', 'Duration', 'Comment', 'Internal item']; + return _.map(headerline, function (entry) { + return gettextCatalog.getString(entry); }); + }; + return { + export: function (element, agenda) { + var csvRows = [ + makeHeaderline() + ]; + _.forEach(agenda, function (item) { + var row = []; + var duration = item.duration ? HumanTimeConverter.secondsToHumanTime(item.duration*60, + { seconds: 'disabled', + hours: 'enabled' }) : ''; + row.push('"' + (item.title || '') + '"'); + row.push('"' + (item.text || '') + '"'); + row.push('"' + duration + '"'); + row.push('"' + (item.comment || '') + '"'); + row.push('"' + (item.is_hidden ? '1' : '') + '"'); + csvRows.push(row); + }); - var csvString = csvRows.join("%0A"); - element.href = 'data:text/csv;charset=utf-8,' + csvString; - element.download = 'agenda-export.csv'; - element.target = '_blank'; + var csvString = csvRows.join("%0A"); + element.href = 'data:text/csv;charset=utf-8,' + csvString; + element.download = 'agenda-export.csv'; + element.target = '_blank'; + }, }; } ]); diff --git a/openslides/agenda/static/js/agenda/site.js b/openslides/agenda/static/js/agenda/site.js index 0421eabda..79803a907 100644 --- a/openslides/agenda/static/js/agenda/site.js +++ b/openslides/agenda/static/js/agenda/site.js @@ -305,7 +305,7 @@ angular.module('OpenSlidesApp.agenda.site', [ }; $scope.csvExport = function () { var element = document.getElementById('downloadLinkCSV'); - AgendaCsvExport(element, $scope.itemsFiltered); + AgendaCsvExport.export(element, $scope.itemsFiltered); }; /** select mode functions **/ diff --git a/openslides/core/static/js/core/site.js b/openslides/core/static/js/core/site.js index ee3276337..f28fdba4f 100644 --- a/openslides/core/static/js/core/site.js +++ b/openslides/core/static/js/core/site.js @@ -16,7 +16,6 @@ angular.module('OpenSlidesApp.core.site', [ 'ngDialog', 'ngFileSaver', 'ngMessages', - 'ngCsvImport', 'ngStorage', 'ckeditor', 'luegg.directives', diff --git a/openslides/motions/static/js/motions/csv.js b/openslides/motions/static/js/motions/csv.js index 5e621f958..910c7e1c4 100644 --- a/openslides/motions/static/js/motions/csv.js +++ b/openslides/motions/static/js/motions/csv.js @@ -5,29 +5,50 @@ angular.module('OpenSlidesApp.motions.csv', []) .factory('MotionCsvExport', [ - function () { - return function (element, motions) { - var csvRows = [ - ['identifier', 'title', 'text', 'reason', 'submitter', 'category', 'origin'], - ]; - _.forEach(motions, function (motion) { - var row = []; - row.push('"' + motion.identifier + '"'); - row.push('"' + motion.getTitle() + '"'); - row.push('"' + motion.getText() + '"'); - row.push('"' + motion.getReason() + '"'); - var submitter = motion.submitters[0] ? motion.submitters[0].get_full_name() : ''; - row.push('"' + submitter + '"'); - var category = motion.category ? motion.category.name : ''; - row.push('"' + category + '"'); - row.push('"' + motion.origin + '"'); - csvRows.push(row); + 'gettextCatalog', + function (gettextCatalog) { + var makeHeaderline = function () { + var headerline = ['Identifier', 'Title', 'Text', 'Reason', 'Submitter', 'Category', 'Origin']; + return _.map(headerline, function (entry) { + return gettextCatalog.getString(entry); }); + }; + return { + export: function (element, motions) { + var csvRows = [ + makeHeaderline() + ]; + _.forEach(motions, function (motion) { + var row = []; + row.push('"' + motion.identifier + '"'); + row.push('"' + motion.getTitle() + '"'); + row.push('"' + motion.getText() + '"'); + row.push('"' + motion.getReason() + '"'); + var submitter = motion.submitters[0] ? motion.submitters[0].get_full_name() : ''; + row.push('"' + submitter + '"'); + var category = motion.category ? motion.category.name : ''; + row.push('"' + category + '"'); + row.push('"' + motion.origin + '"'); + csvRows.push(row); + }); - var csvString = csvRows.join("%0A"); - element.href = 'data:text/csv;charset=utf-8,' + csvString; - element.download = 'motions-export.csv'; - element.target = '_blank'; + var csvString = csvRows.join("%0A"); + element.href = 'data:text/csv;charset=utf-8,' + csvString; + element.download = 'motions-export.csv'; + element.target = '_blank'; + }, + downloadExample: function (element) { + var csvRows = [makeHeaderline(), + // example entries + ['A1', 'Title 1', 'Text 1', 'Reason 1', 'Submitter A', 'Category A', 'Last Year Conference A'], + ['B1', 'Title 2', 'Text 2', 'Reason 2', 'Submitter B', 'Category B', '' ], + ['' , 'Title 3', 'Text 3', '' , '' , '' , '' ], + ]; + var csvString = csvRows.join("%0A"); + element.href = 'data:text/csv;charset=utf-8,' + csvString; + element.download = 'motions-example.csv'; + element.target = '_blank'; + }, }; } ]); diff --git a/openslides/motions/static/js/motions/site.js b/openslides/motions/static/js/motions/site.js index 1887c83f0..139dfa4e1 100644 --- a/openslides/motions/static/js/motions/site.js +++ b/openslides/motions/static/js/motions/site.js @@ -1017,7 +1017,7 @@ angular.module('OpenSlidesApp.motions.site', [ // Export as a csv file $scope.csvExport = function () { var element = document.getElementById('downloadLinkCSV'); - MotionCsvExport(element, $scope.motionsFiltered); + MotionCsvExport.export(element, $scope.motionsFiltered); }; // Export as docx file $scope.docxExport = function () { @@ -1656,8 +1656,8 @@ angular.module('OpenSlidesApp.motions.site', [ 'Category', 'Motion', 'User', - 'gettextCatalog', - function($scope, $q, gettext, Category, Motion, User, gettextCatalog) { + 'MotionCsvExport', + function($scope, $q, gettext, Category, Motion, User, MotionCsvExport) { // set initial data for csv import $scope.motions = []; @@ -1683,6 +1683,7 @@ angular.module('OpenSlidesApp.motions.site', [ }); _.forEach(motions, function (motion) { + motion.selected = true; // identifier if (motion.identifier !== '') { // All motion objects are already loaded via the resolve statement from ui-router. @@ -1740,6 +1741,20 @@ angular.module('OpenSlidesApp.motions.site', [ $scope.motions.push(motion); }); + $scope.calcStats(); + }; + + $scope.calcStats = function () { + $scope.motionsWillNotBeImported = 0; + $scope.motionsWillBeImported = 0; + + $scope.motions.forEach(function(motion) { + if (!motion.importerror && motion.selected) { + $scope.motionsWillBeImported++; + } else { + $scope.motionsWillNotBeImported++; + } + }); }; // Counter for creations @@ -1758,7 +1773,7 @@ angular.module('OpenSlidesApp.motions.site', [ var importedCategories = []; // collect users and categories angular.forEach($scope.motions, function (motion) { - if (!motion.importerror) { + if (motion.selected && !motion.importerror) { // collect user if not exists if (!motion.submitters_id && motion.submitter) { var index = motion.submitter.indexOf(' '); @@ -1813,7 +1828,7 @@ angular.module('OpenSlidesApp.motions.site', [ // wait for users and categories to create $q.all(createPromises).then( function() { angular.forEach($scope.motions, function (motion) { - if (!motion.importerror) { + if (motion.selected && !motion.importerror) { // now, add user if (!motion.submitters_id && motion.submitter) { var index = motion.submitter.indexOf(' '); @@ -1856,21 +1871,8 @@ angular.module('OpenSlidesApp.motions.site', [ }; // download CSV example file $scope.downloadCSVExample = function () { - var headerline = ['Identifier', 'Title', 'Text', 'Reason', 'Submitter', 'Category', 'Origin']; - headerline = _.map(headerline, function (entry) { - return gettextCatalog.getString(entry); - }); var element = document.getElementById('downloadLink'); - var csvRows = [headerline, - // example entries - ['A1', 'Title 1', 'Text 1', 'Reason 1', 'Submitter A', 'Category A', 'Last Year Conference A'], - ['B1', 'Title 2', 'Text 2', 'Reason 2', 'Submitter B', 'Category B', '' ], - ['' , 'Title 3', 'Text 3', '' , '' , '' , '' ], - ]; - var csvString = csvRows.join("%0A"); - element.href = 'data:text/csv;charset=utf-8,' + csvString; - element.download = 'motions-example.csv'; - element.target = '_blank'; + MotionCsvExport.downloadExample(element); }; } ]) diff --git a/openslides/motions/static/templates/motions/motion-import.html b/openslides/motions/static/templates/motions/motion-import.html index a6a646acd..125944cc5 100644 --- a/openslides/motions/static/templates/motions/motion-import.html +++ b/openslides/motions/static/templates/motions/motion-import.html @@ -34,72 +34,73 @@

Preview

- - - - - - -
- # - Identifier - Title - Text - Reason - Submitter - Category - Origin
- - - - - - - - - - - {{ $index + 1 }} - - - - - {{ motion.identifier }} - - - - - {{ motion.title }} - - - - - {{ motion.text | limitTo:80 }}{{ motion.text.length > 80 ? '...' : '' }} - {{ motion.reason | limitTo:80 }}{{ motion.reason.length > 80 ? '...' : '' }} - - - - - {{ motion.submitter }} - - - - - {{ motion.category }} - {{ motion.origin | limitTo:30 }}{{ motion.origin.length > 30 ? '...' : '' }} -
+
+ + + + + + +
+ # + Identifier + Title + Text + Reason + Submitter + Category + Origin
+ + + + + + + + + + + + {{ $index + 1 }} + + + + + {{ motion.identifier }} + + + + + {{ motion.title }} + + + + + {{ motion.text | limitTo:80 }}{{ motion.text.length > 80 ? '...' : '' }} + {{ motion.reason | limitTo:80 }}{{ motion.reason.length > 80 ? '...' : '' }} + + + + + {{ motion.submitter }} + + + + + {{ motion.category }} + {{ motion.origin | limitTo:30 }}{{ motion.origin.length > 30 ? '...' : '' }} +
+
-
- {{ motionsFailed.length }} + {{ motionsWillNotBeImported }} motions will be not imported.
-
- {{ motions.length - motionsFailed.length }} + {{ motionsWillBeImported }} motions will be imported.
@@ -116,8 +117,8 @@ -
diff --git a/openslides/topics/static/js/topics/csv.js b/openslides/topics/static/js/topics/csv.js new file mode 100644 index 000000000..01b9389e4 --- /dev/null +++ b/openslides/topics/static/js/topics/csv.js @@ -0,0 +1,35 @@ +(function () { + +'use strict'; + +angular.module('OpenSlidesApp.topics.csv', []) + +.factory('TopicsCsvExample', [ + 'gettextCatalog', + function (gettextCatalog) { + var makeHeaderline = function () { + var headerline = ['Title', 'Text', 'Duration', 'Comment', 'Internal item']; + return _.map(headerline, function (entry) { + return gettextCatalog.getString(entry); + }); + }; + return { + downloadExample: function (element) { + var csvRows = [makeHeaderline(), + // example entries + ['Demo 1', 'Demo text 1', '1:00', 'test comment', ''], + ['Break', '', '0:10', '', '1'], + ['Demo 2', 'Demo text 2', '1:30', '', ''] + + ]; + var csvString = csvRows.join("%0A"); + element.href = 'data:text/csv;charset=utf-8,' + csvString; + element.download = 'agenda-example.csv'; + element.target = '_blank'; + + }, + }; + } +]); + +}()); diff --git a/openslides/topics/static/js/topics/site.js b/openslides/topics/static/js/topics/site.js index c04f27d88..f5ab4cd17 100644 --- a/openslides/topics/static/js/topics/site.js +++ b/openslides/topics/static/js/topics/site.js @@ -2,7 +2,7 @@ 'use strict'; -angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics']) +angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics', 'OpenSlidesApp.topics.csv']) .config([ '$stateProvider', @@ -272,7 +272,8 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics']) 'Agenda', 'Topic', 'HumanTimeConverter', - function($scope, gettext, Agenda, Topic, HumanTimeConverter) { + 'TopicsCsvExample', + function($scope, gettext, Agenda, Topic, HumanTimeConverter, TopicsCsvExample) { // Big TODO: Change wording from "item" to "topic". // import from textarea $scope.importByLine = function () { @@ -299,53 +300,36 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics']) }; // *** CSV import *** - // set initial data for csv import + $scope.csvConfig = { + accept: '.csv, .txt', + encodingOptions: ['UTF-8', 'ISO-8859-1'], + parseConfig: { + skipEmptyLines: true, + }, + }; + var FIELDS = ['title', 'text', 'duration', 'comment', 'is_hidden']; $scope.items = []; - $scope.separator = ','; - $scope.encoding = 'UTF-8'; - $scope.encodingOptions = ['UTF-8', 'ISO-8859-1']; - $scope.accept = '.csv, .txt'; - $scope.csv = { - content: null, - header: true, - headerVisible: false, - separator: $scope.separator, - separatorVisible: false, - encoding: $scope.encoding, - encodingVisible: false, - accept: $scope.accept, - result: null - }; - // set csv file encoding - $scope.setEncoding = function () { - $scope.csv.encoding = $scope.encoding; - }; - // set csv file encoding - $scope.setSeparator = function () { - $scope.csv.separator = $scope.separator; - }; - // detect if csv file is loaded - $scope.$watch('csv.result', function () { + $scope.onCsvChange = function (csv) { $scope.items = []; - var quotionRe = /^"(.*)"$/; - angular.forEach($scope.csv.result, function (item, index) { - // title - if (item.title) { - item.title = item.title.replace(quotionRe, '$1'); + + var items = []; + _.forEach(csv.data, function (row) { + if (row.length > 1) { + var filledRow = _.zipObject(FIELDS, row); + items.push(filledRow); } + }); + + _.forEach(items, function (item, index) { + item.selected = true; + if (!item.title) { item.importerror = true; item.title_error = gettext('Error: Title is required.'); } - // text - if (item.text) { - item.text = item.text.replace(quotionRe, '$1'); - } // duration if (item.duration) { - var time = item.duration.replace(quotionRe, '$1'); - time = HumanTimeConverter.humanTimeToSeconds(time, {hours: true})/60; - + var time = HumanTimeConverter.humanTimeToSeconds(item.duration, {hours: true})/60; if (time <= 0) { // null instead of 0 or negative duration time = null; } @@ -353,13 +337,8 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics']) } else { item.duration = null; } - // comment - if (item.comment) { - item.comment = item.comment.replace(quotionRe, '$1'); - } // is_hidden if (item.is_hidden) { - item.is_hidden = item.is_hidden.replace(quotionRe, '$1'); if (item.is_hidden == '1') { item.type = 2; } else { @@ -374,13 +353,27 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics']) item.weight = 1000 + index; $scope.items.push(item); }); - }); + $scope.calcStats(); + }; + + $scope.calcStats = function () { + $scope.itemsWillNotBeImported = 0; + $scope.itemsWillBeImported = 0; + + $scope.items.forEach(function(item) { + if (item.selected && !item.importerror) { + $scope.itemsWillBeImported++; + } else { + $scope.itemsWillNotBeImported++; + } + }); + }; // import from csv file $scope.import = function () { $scope.csvImporting = true; angular.forEach($scope.items, function (item) { - if (!item.importerror) { + if (item.selected && !item.importerror) { Topic.create(item).then( function(success) { item.imported = true; @@ -399,24 +392,12 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics']) $scope.csvimported = true; }; $scope.clear = function () { - $scope.csv.result = null; + $scope.items = null; }; // download CSV example file $scope.downloadCSVExample = function () { var element = document.getElementById('downloadLink'); - var csvRows = [ - // column header line - ['title', 'text', 'duration', 'comment', 'is_hidden'], - // example entries - ['Demo 1', 'Demo text 1', '1:00', 'test comment', ''], - ['Break', '', '0:10', '', '1'], - ['Demo 2', 'Demo text 2', '1:30', '', ''] - - ]; - var csvString = csvRows.join("%0A"); - element.href = 'data:text/csv;charset=utf-8,' + csvString; - element.download = 'agenda-example.csv'; - element.target = '_blank'; + TopicsCsvExample.downloadExample(element); }; } ]); diff --git a/openslides/topics/static/templates/topics/topic-import.html b/openslides/topics/static/templates/topics/topic-import.html index 4195a0c29..1b6d29a56 100644 --- a/openslides/topics/static/templates/topics/topic-import.html +++ b/openslides/topics/static/templates/topics/topic-import.html @@ -39,86 +39,73 @@

Import by CSV file

-
-
-

Select a CSV file -

-
- - -
- - - -
-
+ +

Select a CSV file

+

Please note:

  • Required comma or semicolon separated values with these column header names in the first row:
    - title, text, duration, comment, is_hidden + + Title, + Text, + Duration, + Comment, + Internal item +
  • Title is required. All other fields are optional and may be empty.
  • Only double quotes are accepted as text delimiter (no single quotes).
  • Download CSV example file
-
+

Preview

- - - - - - -
- # - Title - Text - Duration - Comment - Is hidden
- - - - - - - - - - {{ $index + 1 }} - - - - - {{ item.title }} - {{ item.text | limitTo:80 }}{{ item.text.length > 80 ? '...' : '' }} - {{ item.duration | osMinutesToTime }} - {{ item.comment }} - {{ item.is_hidden }} -
+
+ + + + + + +
+ # + Title + Text + Duration + Comment + Internal item
+ + + + + + + + + + + {{ $index + 1 }} + + + + + {{ item.title }} + {{ item.text | limitTo:80 }}{{ item.text.length > 80 ? '...' : '' }} + {{ item.duration | osMinutesToTime }} + {{ item.comment }} + {{ item.is_hidden }} +
+
-
- {{ itemsFailed.length }} + {{ itemsWillNMotBeImported }} topics will be not imported.
-
- {{ items.length - itemsFailed.length }} + {{ itemsWillBeImported }} topics will be imported.
@@ -133,8 +120,8 @@ -
diff --git a/openslides/users/static/js/users/csv.js b/openslides/users/static/js/users/csv.js index 3a2f5c393..725acafde 100644 --- a/openslides/users/static/js/users/csv.js +++ b/openslides/users/static/js/users/csv.js @@ -8,10 +8,17 @@ angular.module('OpenSlidesApp.users.csv', []) 'Group', 'gettextCatalog', function (Group, gettextCatalog) { + var makeHeaderline = function () { + var headerline = ['Title', 'Given name', 'Surname', 'Structure level', 'Participant number', 'Groups', + 'Comment', 'Is active', 'Is present', 'Is a committee']; + return _.map(headerline, function (entry) { + return gettextCatalog.getString(entry); + }); + }; return { export: function (element, users) { var csvRows = [ - ['title', 'first_name', 'last_name', 'structure_level', 'number', 'groups', 'comment', 'is_active', 'is_present', 'is_committee'], + makeHeaderline() ]; _.forEach(users, function (user) { var row = []; @@ -46,9 +53,8 @@ angular.module('OpenSlidesApp.users.csv', []) if (groups.length >= 2) { csvGroup = gettextCatalog.getString(groups[groups.length - 1].name); // take last group } - var csvRows = [ - // column header line - ['title', 'first_name', 'last_name', 'structure_level', 'number', 'groups', 'comment', 'is_active', 'is_present', 'is_committee'], + + var csvRows = [makeHeaderline(), // example entries ['Dr.', 'Max', 'Mustermann', 'Berlin','1234567890', csvGroups, 'xyz', '1', '1', ''], ['', 'John', 'Doe', 'Washington','75/99/8-2', csvGroup, 'abc', '1', '1', ''], diff --git a/openslides/users/static/js/users/site.js b/openslides/users/static/js/users/site.js index 4ffdb19ed..62499a761 100644 --- a/openslides/users/static/js/users/site.js +++ b/openslides/users/static/js/users/site.js @@ -917,32 +917,6 @@ angular.module('OpenSlidesApp.users.site', [ }); }; - // *** csv import *** - // set initial data for csv import - $scope.users = []; - $scope.separator = ','; - $scope.encoding = 'UTF-8'; - $scope.encodingOptions = ['UTF-8', 'ISO-8859-1']; - $scope.accept = '.csv, .txt'; - $scope.csv = { - content: null, - header: true, - headerVisible: false, - separator: $scope.separator, - separatorVisible: false, - encoding: $scope.encoding, - encodingVisible: false, - accept: $scope.accept, - result: null - }; - // set csv file encoding - $scope.setEncoding = function () { - $scope.csv.encoding = $scope.encoding; - }; - // set csv file encoding - $scope.setSeparator = function () { - $scope.csv.separator = $scope.separator; - }; // pagination $scope.currentPage = 1; $scope.itemsPerPage = 100; @@ -955,45 +929,46 @@ angular.module('OpenSlidesApp.users.site', [ gettext('override new'), gettext('create duplicate') ]; - // detect if csv file is loaded - $scope.$watch('csv.result', function () { + + // *** csv import *** + $scope.csvConfig = { + accept: '.csv, .txt', + encodingOptions: ['UTF-8', 'ISO-8859-1'], + parseConfig: { + skipEmptyLines: true, + }, + }; + + var FIELDS = ['title', 'first_name', 'last_name', 'structure_level', 'number', + 'groups', 'comment', 'is_active', 'is_present', 'is_committee']; + $scope.users = []; + $scope.onCsvChange = function (csv) { // All user objects are already loaded via the resolve statement from ui-router. var users = User.getAll(); $scope.users = []; + + var csvUsers = []; + _.forEach(csv.data, function (row) { + if (row.length >= 2) { + var filledRow = _.zipObject(FIELDS, row); + csvUsers.push(filledRow); + } + }); $scope.duplicates = 0; - var quotionRe = /^"(.*)"$/; - angular.forEach($scope.csv.result, function (user) { + _.forEach(csvUsers, function (user) { user.selected = true; - // title - if (user.title) { - user.title = user.title.replace(quotionRe, '$1'); - } - // first name - if (user.first_name) { - user.first_name = user.first_name.replace(quotionRe, '$1'); - } - // last name - if (user.last_name) { - user.last_name = user.last_name.replace(quotionRe, '$1'); - } if (!user.first_name && !user.last_name) { user.importerror = true; user.name_error = gettext('Error: Given name or surname is required.'); } - // structure level - if (user.structure_level) { - user.structure_level = user.structure_level.replace(quotionRe, '$1'); - } // number - if (user.number) { - user.number = user.number.replace(quotionRe, '$1'); - } else { + if (!user.number) { user.number = ""; } // groups user.groups_id = []; // will be overwritten if there are groups if (user.groups) { - user.groups = user.groups.replace(quotionRe, '$1').split(','); + user.groups = user.groups.split(','); user.groups = _.map(user.groups, function (group) { return _.trim(group); // remove whitespaces on start or end }); @@ -1015,43 +990,9 @@ angular.module('OpenSlidesApp.users.site', [ // for template: user.groupsNotToCreate = _.difference(user.groups, user.groupsToCreate); } - // comment - if (user.comment) { - user.comment = user.comment.replace(quotionRe, '$1'); - } - // is active - if (user.is_active) { - user.is_active = user.is_active.replace(quotionRe, '$1'); - if (user.is_active == '1') { - user.is_active = true; - } else { - user.is_active = false; - } - } else { - user.is_active = false; - } - // is present - if (user.is_present) { - user.is_present = user.is_present.replace(quotionRe, '$1'); - if (user.is_present == '1') { - user.is_present = true; - } else { - user.is_present = false; - } - } else { - user.is_present = false; - } - // is committee - if (user.is_committee) { - user.is_committee = user.is_committee.replace(quotionRe, '$1'); - if (user.is_committee == '1') { - user.is_committee = true; - } else { - user.is_committee = false; - } - } else { - user.is_committee = false; - } + user.is_active = (user.is_active !== undefined && user.is_active === '1'); + user.is_present = (user.is_present !== undefined && user.is_present === '1'); + user.is_committee = (user.is_committee !== undefined && user.is_committee === '1'); // Check for duplicates user.duplicate = false; @@ -1089,7 +1030,7 @@ angular.module('OpenSlidesApp.users.site', [ $scope.users.push(user); }); $scope.calcStats(); - }); + }; // Stats $scope.calcStats = function() { @@ -1193,7 +1134,7 @@ angular.module('OpenSlidesApp.users.site', [ }); }; $scope.clear = function () { - $scope.csv.result = null; + $scope.users = null; }; // download CSV example file $scope.downloadCSVExample = function () { diff --git a/openslides/users/static/templates/users/user-import.html b/openslides/users/static/templates/users/user-import.html index d1cef57f1..eb2929bd8 100644 --- a/openslides/users/static/templates/users/user-import.html +++ b/openslides/users/static/templates/users/user-import.html @@ -41,41 +41,31 @@

Import by CSV file

-
-
-

Select a CSV file -

-
- - -
- - - -
-
+

Select a CSV file

+

Please note:

  • Required comma or semicolon separated values with these column header names in the first row:
    - title, first_name, last_name, structure_level, number, groups, comment, is_active, is_present, is_committee + + Title, + Given name, + Surname, + Structure level, + Participant number, + Groups, + Comment, + Is active, + Is present, + Is committee +
  • At least given name or surname have to be filled in. All other fields are optional and may be empty.
  • Only double quotes are accepted as text delimiter (no single quotes).
  • Download CSV example file
-
+
Preview