Merge pull request #2825 from FinnStutzenstein/CsvImports
New csv import for users and topics
This commit is contained in:
commit
8bb0918372
@ -36,6 +36,8 @@ Core:
|
|||||||
- Add a version of has_perm that can work with cached users.
|
- Add a version of has_perm that can work with cached users.
|
||||||
- Cache the group with there permissions.
|
- Cache the group with there permissions.
|
||||||
- Removed our db-session backend and added possibility to use any django session backend.
|
- 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:
|
Motions:
|
||||||
- Added adjustable line numbering mode (outside, inside, none) for each
|
- Added adjustable line numbering mode (outside, inside, none) for each
|
||||||
@ -57,7 +59,6 @@ Motions:
|
|||||||
- New PDF layout.
|
- New PDF layout.
|
||||||
- Added DOCX export with docxtemplater.
|
- Added DOCX export with docxtemplater.
|
||||||
- Changed label of former state "commited a bill" to "refered to committee".
|
- 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.
|
- 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 remove all whitespaces from motion identifier.
|
||||||
- Add new personal settings to allow amendments of amendments.
|
- Add new personal settings to allow amendments of amendments.
|
||||||
|
@ -192,7 +192,6 @@ OpenSlides uses the following projects or parts of them:
|
|||||||
* `angular-bootstrap-colorpicker <https://github.com/buberdds/angular-bootstrap-colorpicker>`_, License: MIT
|
* `angular-bootstrap-colorpicker <https://github.com/buberdds/angular-bootstrap-colorpicker>`_, License: MIT
|
||||||
* `angular-chosen-localytics <http://github.com/leocaseiro/angular-chosen>`_, License: MIT
|
* `angular-chosen-localytics <http://github.com/leocaseiro/angular-chosen>`_, License: MIT
|
||||||
* `angular-ckeditor <https://github.com/lemonde/angular-ckeditor/>`_, License: MIT
|
* `angular-ckeditor <https://github.com/lemonde/angular-ckeditor/>`_, License: MIT
|
||||||
* `angular-csv-import <https://github.com/bahaaldine/angular-csv-import>`_, License: MIT
|
|
||||||
* `angular-formly <http://formly-js.github.io/angular-formly/>`_, License: MIT
|
* `angular-formly <http://formly-js.github.io/angular-formly/>`_, License: MIT
|
||||||
* `angular-formly-templates-bootstrap <https://github.com/formly-js/angular-formly-templates-bootstrap>`_, License: MIT
|
* `angular-formly-templates-bootstrap <https://github.com/formly-js/angular-formly-templates-bootstrap>`_, License: MIT
|
||||||
* `angular-gettext <http://angular-gettext.rocketeer.be/>`_, License: MIT
|
* `angular-gettext <http://angular-gettext.rocketeer.be/>`_, License: MIT
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
"angular-bootstrap-colorpicker": "~3.0.25",
|
"angular-bootstrap-colorpicker": "~3.0.25",
|
||||||
"angular-chosen-localytics": "~1.5.0",
|
"angular-chosen-localytics": "~1.5.0",
|
||||||
"angular-ckeditor": "~1.0.3",
|
"angular-ckeditor": "~1.0.3",
|
||||||
"angular-csv-import": "0.0.36",
|
|
||||||
"angular-file-saver": "~1.1.2",
|
"angular-file-saver": "~1.1.2",
|
||||||
"angular-formly": "~8.4.0",
|
"angular-formly": "~8.4.0",
|
||||||
"angular-formly-templates-bootstrap": "~6.2.0",
|
"angular-formly-templates-bootstrap": "~6.2.0",
|
||||||
|
@ -6,28 +6,37 @@ angular.module('OpenSlidesApp.agenda.csv', [])
|
|||||||
|
|
||||||
.factory('AgendaCsvExport', [
|
.factory('AgendaCsvExport', [
|
||||||
'HumanTimeConverter',
|
'HumanTimeConverter',
|
||||||
function (HumanTimeConverter) {
|
'gettextCatalog',
|
||||||
return function (element, agenda) {
|
function (HumanTimeConverter, gettextCatalog) {
|
||||||
var csvRows = [
|
var makeHeaderline = function () {
|
||||||
['title', 'text', 'duration', 'comment', 'is_hidden'],
|
var headerline = ['Title', 'Text', 'Duration', 'Comment', 'Internal item'];
|
||||||
];
|
return _.map(headerline, function (entry) {
|
||||||
_.forEach(agenda, function (item) {
|
return gettextCatalog.getString(entry);
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
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");
|
var csvString = csvRows.join("%0A");
|
||||||
element.href = 'data:text/csv;charset=utf-8,' + csvString;
|
element.href = 'data:text/csv;charset=utf-8,' + csvString;
|
||||||
element.download = 'agenda-export.csv';
|
element.download = 'agenda-export.csv';
|
||||||
element.target = '_blank';
|
element.target = '_blank';
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
@ -306,7 +306,7 @@ angular.module('OpenSlidesApp.agenda.site', [
|
|||||||
};
|
};
|
||||||
$scope.csvExport = function () {
|
$scope.csvExport = function () {
|
||||||
var element = document.getElementById('downloadLinkCSV');
|
var element = document.getElementById('downloadLinkCSV');
|
||||||
AgendaCsvExport(element, $scope.itemsFiltered);
|
AgendaCsvExport.export(element, $scope.itemsFiltered);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** select mode functions **/
|
/** select mode functions **/
|
||||||
|
@ -16,7 +16,6 @@ angular.module('OpenSlidesApp.core.site', [
|
|||||||
'ngDialog',
|
'ngDialog',
|
||||||
'ngFileSaver',
|
'ngFileSaver',
|
||||||
'ngMessages',
|
'ngMessages',
|
||||||
'ngCsvImport',
|
|
||||||
'ngStorage',
|
'ngStorage',
|
||||||
'ckeditor',
|
'ckeditor',
|
||||||
'luegg.directives',
|
'luegg.directives',
|
||||||
|
@ -5,29 +5,50 @@
|
|||||||
angular.module('OpenSlidesApp.motions.csv', [])
|
angular.module('OpenSlidesApp.motions.csv', [])
|
||||||
|
|
||||||
.factory('MotionCsvExport', [
|
.factory('MotionCsvExport', [
|
||||||
function () {
|
'gettextCatalog',
|
||||||
return function (element, motions) {
|
function (gettextCatalog) {
|
||||||
var csvRows = [
|
var makeHeaderline = function () {
|
||||||
['identifier', 'title', 'text', 'reason', 'submitter', 'category', 'origin'],
|
var headerline = ['Identifier', 'Title', 'Text', 'Reason', 'Submitter', 'Category', 'Origin'];
|
||||||
];
|
return _.map(headerline, function (entry) {
|
||||||
_.forEach(motions, function (motion) {
|
return gettextCatalog.getString(entry);
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
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");
|
var csvString = csvRows.join("%0A");
|
||||||
element.href = 'data:text/csv;charset=utf-8,' + csvString;
|
element.href = 'data:text/csv;charset=utf-8,' + csvString;
|
||||||
element.download = 'motions-export.csv';
|
element.download = 'motions-export.csv';
|
||||||
element.target = '_blank';
|
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';
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
@ -1017,7 +1017,7 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
// Export as a csv file
|
// Export as a csv file
|
||||||
$scope.csvExport = function () {
|
$scope.csvExport = function () {
|
||||||
var element = document.getElementById('downloadLinkCSV');
|
var element = document.getElementById('downloadLinkCSV');
|
||||||
MotionCsvExport(element, $scope.motionsFiltered);
|
MotionCsvExport.export(element, $scope.motionsFiltered);
|
||||||
};
|
};
|
||||||
// Export as docx file
|
// Export as docx file
|
||||||
$scope.docxExport = function () {
|
$scope.docxExport = function () {
|
||||||
@ -1665,8 +1665,8 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
'Category',
|
'Category',
|
||||||
'Motion',
|
'Motion',
|
||||||
'User',
|
'User',
|
||||||
'gettextCatalog',
|
'MotionCsvExport',
|
||||||
function($scope, $q, gettext, Category, Motion, User, gettextCatalog) {
|
function($scope, $q, gettext, Category, Motion, User, MotionCsvExport) {
|
||||||
// set initial data for csv import
|
// set initial data for csv import
|
||||||
$scope.motions = [];
|
$scope.motions = [];
|
||||||
|
|
||||||
@ -1692,6 +1692,7 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
});
|
});
|
||||||
|
|
||||||
_.forEach(motions, function (motion) {
|
_.forEach(motions, function (motion) {
|
||||||
|
motion.selected = true;
|
||||||
// identifier
|
// identifier
|
||||||
if (motion.identifier !== '') {
|
if (motion.identifier !== '') {
|
||||||
// All motion objects are already loaded via the resolve statement from ui-router.
|
// All motion objects are already loaded via the resolve statement from ui-router.
|
||||||
@ -1749,6 +1750,20 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
|
|
||||||
$scope.motions.push(motion);
|
$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
|
// Counter for creations
|
||||||
@ -1767,7 +1782,7 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
var importedCategories = [];
|
var importedCategories = [];
|
||||||
// collect users and categories
|
// collect users and categories
|
||||||
angular.forEach($scope.motions, function (motion) {
|
angular.forEach($scope.motions, function (motion) {
|
||||||
if (!motion.importerror) {
|
if (motion.selected && !motion.importerror) {
|
||||||
// collect user if not exists
|
// collect user if not exists
|
||||||
if (!motion.submitters_id && motion.submitter) {
|
if (!motion.submitters_id && motion.submitter) {
|
||||||
var index = motion.submitter.indexOf(' ');
|
var index = motion.submitter.indexOf(' ');
|
||||||
@ -1822,7 +1837,7 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
// wait for users and categories to create
|
// wait for users and categories to create
|
||||||
$q.all(createPromises).then( function() {
|
$q.all(createPromises).then( function() {
|
||||||
angular.forEach($scope.motions, function (motion) {
|
angular.forEach($scope.motions, function (motion) {
|
||||||
if (!motion.importerror) {
|
if (motion.selected && !motion.importerror) {
|
||||||
// now, add user
|
// now, add user
|
||||||
if (!motion.submitters_id && motion.submitter) {
|
if (!motion.submitters_id && motion.submitter) {
|
||||||
var index = motion.submitter.indexOf(' ');
|
var index = motion.submitter.indexOf(' ');
|
||||||
@ -1865,21 +1880,8 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
};
|
};
|
||||||
// download CSV example file
|
// download CSV example file
|
||||||
$scope.downloadCSVExample = function () {
|
$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 element = document.getElementById('downloadLink');
|
||||||
var csvRows = [headerline,
|
MotionCsvExport.downloadExample(element);
|
||||||
// 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';
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
@ -34,72 +34,73 @@
|
|||||||
|
|
||||||
<div ng-if="motions.length">
|
<div ng-if="motions.length">
|
||||||
<h3 translate>Preview</h3>
|
<h3 translate>Preview</h3>
|
||||||
<table class="table table-striped table-bordered table-condensed">
|
<div class="scroll-x-container">
|
||||||
<thead>
|
<table class="table table-striped table-bordered table-condensed">
|
||||||
<tr>
|
<thead>
|
||||||
<th>
|
<tr>
|
||||||
<th>#
|
<th>
|
||||||
<th translate>Identifier
|
<th>#
|
||||||
<th translate>Title
|
<th translate>Identifier
|
||||||
<th translate>Text
|
<th translate>Title
|
||||||
<th translate>Reason
|
<th translate>Text
|
||||||
<th translate>Submitter
|
<th translate>Reason
|
||||||
<th translate>Category
|
<th translate>Submitter
|
||||||
<th translate>Origin</th>
|
<th translate>Category
|
||||||
<tbody ng-repeat="motion in motions">
|
<th translate>Origin</th>
|
||||||
<tr>
|
<tbody ng-repeat="motion in motions">
|
||||||
<td class="minimum"
|
<tr>
|
||||||
ng-class="{ 'text-danger': motion.importerror, 'text-success': motion.imported }">
|
<td class="minimum"
|
||||||
<span ng-if="motion.importerror">
|
ng-class="{ 'text-danger': motion.importerror, 'text-success': motion.imported }">
|
||||||
<i class="fa fa-exclamation-triangle fa-lg"></i>
|
<span ng-if="motion.importerror">
|
||||||
</span>
|
<i class="fa fa-exclamation-triangle fa-lg"></i>
|
||||||
<span ng-if="!motion.importerror && !motion.imported">
|
</span>
|
||||||
<i class="fa fa-check-circle-o fa-lg"></i>
|
<span ng-if="!motion.importerror && !motion.imported" class="pointer">
|
||||||
</span>
|
<i class="fa fa-check-circle-o fa-lg" ng-if="motion.selected" ng-click="motion.selected=false; calcStats();"></i>
|
||||||
<span ng-if="motion.imported">
|
<i class="fa fa-circle-o fa-lg" ng-if="!motion.selected" ng-click="motion.selected=true; calcStats();"></i>
|
||||||
<i class="fa fa-check-circle fa-lg"></i>
|
</span>
|
||||||
</span>
|
<span ng-if="motion.imported">
|
||||||
<td>
|
<i class="fa fa-check-circle fa-lg"></i>
|
||||||
{{ $index + 1 }}
|
</span>
|
||||||
<td ng-class="{ 'text-danger': motion.identifier_error }">
|
<td>
|
||||||
<span ng-if="motion.identifier_error" title="{{ motion.identifier_error | translate }}">
|
{{ $index + 1 }}
|
||||||
<i class="fa fa-exclamation-triangle pointer"></i>
|
<td ng-class="{ 'text-danger': motion.identifier_error }">
|
||||||
</span>
|
<span ng-if="motion.identifier_error" title="{{ motion.identifier_error | translate }}">
|
||||||
{{ motion.identifier }}
|
<i class="fa fa-exclamation-triangle pointer"></i>
|
||||||
<td ng-class="{ 'text-danger': motion.title_error }">
|
</span>
|
||||||
<span ng-if="motion.title_error" title="{{ motion.title_error | translate }}">
|
{{ motion.identifier }}
|
||||||
<i class="fa fa-exclamation-triangle"></i>
|
<td ng-class="{ 'text-danger': motion.title_error }">
|
||||||
</span>
|
<span ng-if="motion.title_error" title="{{ motion.title_error | translate }}">
|
||||||
{{ motion.title }}
|
<i class="fa fa-exclamation-triangle"></i>
|
||||||
<td ng-class="{ 'text-danger': motion.text_error }">
|
</span>
|
||||||
<span ng-if="motion.text_error" title="{{ motion.text_error | translate }}">
|
{{ motion.title }}
|
||||||
<i class="fa fa-exclamation-triangle"></i>
|
<td ng-class="{ 'text-danger': motion.text_error }">
|
||||||
</span>
|
<span ng-if="motion.text_error" title="{{ motion.text_error | translate }}">
|
||||||
{{ motion.text | limitTo:80 }}{{ motion.text.length > 80 ? '...' : '' }}
|
<i class="fa fa-exclamation-triangle"></i>
|
||||||
<td>{{ motion.reason | limitTo:80 }}{{ motion.reason.length > 80 ? '...' : '' }}
|
</span>
|
||||||
<td ng-class="{ 'text-warning': motion.submitter_create }">
|
{{ motion.text | limitTo:80 }}{{ motion.text.length > 80 ? '...' : '' }}
|
||||||
<span ng-if="motion.submitter_create" title="{{ motion.submitter_create | translate }}">
|
<td>{{ motion.reason | limitTo:80 }}{{ motion.reason.length > 80 ? '...' : '' }}
|
||||||
<i class="fa fa-plus-circle"></i>
|
<td ng-class="{ 'text-warning': motion.submitter_create }">
|
||||||
</span>
|
<span ng-if="motion.submitter_create" title="{{ motion.submitter_create | translate }}">
|
||||||
{{ motion.submitter }}
|
<i class="fa fa-plus-circle"></i>
|
||||||
<td ng-class="{ 'text-warning': motion.category_create }">
|
</span>
|
||||||
<span ng-if="motion.category_create" title="{{ motion.category_create | translate }}">
|
{{ motion.submitter }}
|
||||||
<i class="fa fa-plus-circle"></i>
|
<td ng-class="{ 'text-warning': motion.category_create }">
|
||||||
</span>
|
<span ng-if="motion.category_create" title="{{ motion.category_create | translate }}">
|
||||||
{{ motion.category }}
|
<i class="fa fa-plus-circle"></i>
|
||||||
<td>{{ motion.origin | limitTo:30 }}{{ motion.origin.length > 30 ? '...' : '' }}
|
</span>
|
||||||
</table>
|
{{ motion.category }}
|
||||||
|
<td>{{ motion.origin | limitTo:30 }}{{ motion.origin.length > 30 ? '...' : '' }}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="text-danger">
|
<div class="text-danger">
|
||||||
<div ng-repeat="motion in motionsFailed = (motions | filter:{importerror:true})"></div>
|
|
||||||
<i class="fa fa-exclamation-triangle"></i>
|
<i class="fa fa-exclamation-triangle"></i>
|
||||||
{{ motionsFailed.length }}
|
{{ motionsWillNotBeImported }}
|
||||||
<translate>motions will be not imported.</translate>
|
<translate>motions will be not imported.</translate>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div ng-repeat="motion in motionsPassed = (motions | filter:{importerror:false})"></div>
|
|
||||||
<i class="fa fa-check-circle-o fa-lg"></i>
|
<i class="fa fa-check-circle-o fa-lg"></i>
|
||||||
{{ motions.length - motionsFailed.length }}
|
{{ motionsWillBeImported }}
|
||||||
<translate>motions will be imported.</translate>
|
<translate>motions will be imported.</translate>
|
||||||
</div>
|
</div>
|
||||||
<div ng-repeat="motion in motionsImported = (motions | filter:{imported:true})"></div>
|
<div ng-repeat="motion in motionsImported = (motions | filter:{imported:true})"></div>
|
||||||
@ -116,8 +117,8 @@
|
|||||||
<button ng-click="clear()" class="btn btn-default" translate>
|
<button ng-click="clear()" class="btn btn-default" translate>
|
||||||
Clear preview
|
Clear preview
|
||||||
</button>
|
</button>
|
||||||
<button ng-if="!csvImporting && (motions.length - motionsFailed.length) > 0" ng-click="import()" class="btn btn-primary" translate>
|
<button ng-if="!csvImporting && motionsWillBeImported > 0" ng-click="import()" class="btn btn-primary" translate>
|
||||||
Import {{ motions.length - motionsFailed.length }} motions
|
Import {{ motionsWillBeImported }} motions
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="spacer">
|
<div class="spacer">
|
||||||
|
35
openslides/topics/static/js/topics/csv.js
Normal file
35
openslides/topics/static/js/topics/csv.js
Normal file
@ -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';
|
||||||
|
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
}());
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics'])
|
angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics', 'OpenSlidesApp.topics.csv'])
|
||||||
|
|
||||||
.config([
|
.config([
|
||||||
'$stateProvider',
|
'$stateProvider',
|
||||||
@ -272,7 +272,8 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics'])
|
|||||||
'Agenda',
|
'Agenda',
|
||||||
'Topic',
|
'Topic',
|
||||||
'HumanTimeConverter',
|
'HumanTimeConverter',
|
||||||
function($scope, gettext, Agenda, Topic, HumanTimeConverter) {
|
'TopicsCsvExample',
|
||||||
|
function($scope, gettext, Agenda, Topic, HumanTimeConverter, TopicsCsvExample) {
|
||||||
// Big TODO: Change wording from "item" to "topic".
|
// Big TODO: Change wording from "item" to "topic".
|
||||||
// import from textarea
|
// import from textarea
|
||||||
$scope.importByLine = function () {
|
$scope.importByLine = function () {
|
||||||
@ -299,53 +300,36 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics'])
|
|||||||
};
|
};
|
||||||
|
|
||||||
// *** CSV import ***
|
// *** 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.items = [];
|
||||||
$scope.separator = ',';
|
$scope.onCsvChange = function (csv) {
|
||||||
$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.items = [];
|
$scope.items = [];
|
||||||
var quotionRe = /^"(.*)"$/;
|
|
||||||
angular.forEach($scope.csv.result, function (item, index) {
|
var items = [];
|
||||||
// title
|
_.forEach(csv.data, function (row) {
|
||||||
if (item.title) {
|
if (row.length > 1) {
|
||||||
item.title = item.title.replace(quotionRe, '$1');
|
var filledRow = _.zipObject(FIELDS, row);
|
||||||
|
items.push(filledRow);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_.forEach(items, function (item, index) {
|
||||||
|
item.selected = true;
|
||||||
|
|
||||||
if (!item.title) {
|
if (!item.title) {
|
||||||
item.importerror = true;
|
item.importerror = true;
|
||||||
item.title_error = gettext('Error: Title is required.');
|
item.title_error = gettext('Error: Title is required.');
|
||||||
}
|
}
|
||||||
// text
|
|
||||||
if (item.text) {
|
|
||||||
item.text = item.text.replace(quotionRe, '$1');
|
|
||||||
}
|
|
||||||
// duration
|
// duration
|
||||||
if (item.duration) {
|
if (item.duration) {
|
||||||
var time = item.duration.replace(quotionRe, '$1');
|
var time = HumanTimeConverter.humanTimeToSeconds(item.duration, {hours: true})/60;
|
||||||
time = HumanTimeConverter.humanTimeToSeconds(time, {hours: true})/60;
|
|
||||||
|
|
||||||
if (time <= 0) { // null instead of 0 or negative duration
|
if (time <= 0) { // null instead of 0 or negative duration
|
||||||
time = null;
|
time = null;
|
||||||
}
|
}
|
||||||
@ -353,13 +337,8 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics'])
|
|||||||
} else {
|
} else {
|
||||||
item.duration = null;
|
item.duration = null;
|
||||||
}
|
}
|
||||||
// comment
|
|
||||||
if (item.comment) {
|
|
||||||
item.comment = item.comment.replace(quotionRe, '$1');
|
|
||||||
}
|
|
||||||
// is_hidden
|
// is_hidden
|
||||||
if (item.is_hidden) {
|
if (item.is_hidden) {
|
||||||
item.is_hidden = item.is_hidden.replace(quotionRe, '$1');
|
|
||||||
if (item.is_hidden == '1') {
|
if (item.is_hidden == '1') {
|
||||||
item.type = 2;
|
item.type = 2;
|
||||||
} else {
|
} else {
|
||||||
@ -374,13 +353,27 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics'])
|
|||||||
item.weight = 1000 + index;
|
item.weight = 1000 + index;
|
||||||
$scope.items.push(item);
|
$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
|
// import from csv file
|
||||||
$scope.import = function () {
|
$scope.import = function () {
|
||||||
$scope.csvImporting = true;
|
$scope.csvImporting = true;
|
||||||
angular.forEach($scope.items, function (item) {
|
angular.forEach($scope.items, function (item) {
|
||||||
if (!item.importerror) {
|
if (item.selected && !item.importerror) {
|
||||||
Topic.create(item).then(
|
Topic.create(item).then(
|
||||||
function(success) {
|
function(success) {
|
||||||
item.imported = true;
|
item.imported = true;
|
||||||
@ -399,24 +392,12 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics'])
|
|||||||
$scope.csvimported = true;
|
$scope.csvimported = true;
|
||||||
};
|
};
|
||||||
$scope.clear = function () {
|
$scope.clear = function () {
|
||||||
$scope.csv.result = null;
|
$scope.items = null;
|
||||||
};
|
};
|
||||||
// download CSV example file
|
// download CSV example file
|
||||||
$scope.downloadCSVExample = function () {
|
$scope.downloadCSVExample = function () {
|
||||||
var element = document.getElementById('downloadLink');
|
var element = document.getElementById('downloadLink');
|
||||||
var csvRows = [
|
TopicsCsvExample.downloadExample(element);
|
||||||
// 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';
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
@ -39,86 +39,73 @@
|
|||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<h2 translate>Import by CSV file</h2>
|
<h2 translate>Import by CSV file</h2>
|
||||||
<div class="block row">
|
|
||||||
<div class="title">
|
<h3 translate>Select a CSV file</h3>
|
||||||
<h3 translate>Select a CSV file
|
<csv-import change="onCsvChange(csv)" config="csvConfig"></csv-import>
|
||||||
</div>
|
|
||||||
<div class="block right import">
|
|
||||||
<label class="label" for="inputSeparator" translate>Separator</label>
|
|
||||||
<input type="text" ng-model="separator" ng-change="setSeparator()" ng-init="separator=separator" id="inputSeparator">
|
|
||||||
<br>
|
|
||||||
<label class="label" for="selectEncoding" translate>Encoding</label>
|
|
||||||
<select ng-model="encoding" ng-options="enc as enc for enc in encodingOptions"
|
|
||||||
ng-selected="setEncoding()" ng-init="encoding=encoding" id="selectEncoding"></select>
|
|
||||||
<ng-csv-import
|
|
||||||
content="csv.content"
|
|
||||||
header="csv.header"
|
|
||||||
header-visible="csv.headerVisible"
|
|
||||||
separator="csv.separator"
|
|
||||||
separator-visible="csv.separatorVisible"
|
|
||||||
result="csv.result"
|
|
||||||
encoding="csv.encoding"
|
|
||||||
accept="csv.accept"
|
|
||||||
encoding-visible="csv.encodingVisible"></ng-csv-import>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h4 translate>Please note:</h4>
|
<h4 translate>Please note:</h4>
|
||||||
<ul class="indentation">
|
<ul class="indentation">
|
||||||
<li><translate>Required comma or semicolon separated values with these column header names in the first row</translate>:<br>
|
<li><translate>Required comma or semicolon separated values with these column header names in the first row</translate>:<br>
|
||||||
<code>title, text, duration, comment, is_hidden</code>
|
<code>
|
||||||
|
<translate>Title</translate>,
|
||||||
|
<translate>Text</translate>,
|
||||||
|
<translate>Duration</translate>,
|
||||||
|
<translate>Comment</translate>,
|
||||||
|
<translate>Internal item</translate>
|
||||||
|
</code>
|
||||||
<li translate>Title is required. All other fields are optional and may be empty.
|
<li translate>Title is required. All other fields are optional and may be empty.
|
||||||
<li translate>Only double quotes are accepted as text delimiter (no single quotes).
|
<li translate>Only double quotes are accepted as text delimiter (no single quotes).
|
||||||
<li><a id="downloadLink" href="" ng-click="downloadCSVExample()" translate>Download CSV example file</a>
|
<li><a id="downloadLink" href="" ng-click="downloadCSVExample()" translate>Download CSV example file</a>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div ng-if="csv.result">
|
<div ng-if="items.length">
|
||||||
<h3 translate>Preview</h3>
|
<h3 translate>Preview</h3>
|
||||||
<table ng-if="!csvImporting" class="table table-striped table-bordered table-condensed">
|
<div class="scroll-x-container">
|
||||||
<thead>
|
<table ng-if="!csvImporting" class="table table-striped table-bordered table-condensed">
|
||||||
<tr>
|
<thead>
|
||||||
<th>
|
<tr>
|
||||||
<th>#
|
<th>
|
||||||
<th translate>Title
|
<th>#
|
||||||
<th translate>Text
|
<th translate>Title
|
||||||
<th translate>Duration
|
<th translate>Text
|
||||||
<th translate>Comment
|
<th translate>Duration
|
||||||
<th translate>Is hidden</th>
|
<th translate>Comment
|
||||||
<tbody ng-repeat="item in items">
|
<th translate>Internal item</th>
|
||||||
<tr>
|
<tbody ng-repeat="item in items">
|
||||||
<td class="minimum"
|
<tr>
|
||||||
ng-class="{ 'text-danger': item.importerror, 'text-success': item.imported }">
|
<td class="minimum"
|
||||||
<span ng-if="item.importerror">
|
ng-class="{ 'text-danger': item.importerror, 'text-success': item.imported }">
|
||||||
<i class="fa fa-exclamation-triangle fa-lg"></i>
|
<span ng-if="item.importerror">
|
||||||
</span>
|
<i class="fa fa-exclamation-triangle fa-lg"></i>
|
||||||
<span ng-if="!item.importerror && !item.imported">
|
</span>
|
||||||
<i class="fa fa-check-circle-o fa-lg"></i>
|
<span ng-if="!item.importerror && !item.imported" class="pointer">
|
||||||
</span>
|
<i class="fa fa-check-circle-o fa-lg" ng-if="item.selected" ng-click="item.selected=false; calcStats();"></i>
|
||||||
<span ng-if="item.imported">
|
<i class="fa fa-circle-o fa-lg" ng-if="!item.selected" ng-click="item.selected=true; calcStats();"></i>
|
||||||
<i class="fa fa-check-circle fa-lg"></i>
|
</span>
|
||||||
</span>
|
<span ng-if="item.imported">
|
||||||
<td class="minimum">{{ $index + 1 }}
|
<i class="fa fa-check-circle fa-lg"></i>
|
||||||
<td ng-class="{ 'text-danger': item.title_error }">
|
</span>
|
||||||
<span ng-if="item.title_error" title="{{ item.title_error | translate }}">
|
<td class="minimum">{{ $index + 1 }}
|
||||||
<i class="fa fa-exclamation-triangle"></i>
|
<td ng-class="{ 'text-danger': item.title_error }">
|
||||||
</span>
|
<span ng-if="item.title_error" title="{{ item.title_error | translate }}">
|
||||||
{{ item.title }}
|
<i class="fa fa-exclamation-triangle"></i>
|
||||||
<td>{{ item.text | limitTo:80 }}{{ item.text.length > 80 ? '...' : '' }}
|
</span>
|
||||||
<td>{{ item.duration | osMinutesToTime }}
|
{{ item.title }}
|
||||||
<td>{{ item.comment }}
|
<td>{{ item.text | limitTo:80 }}{{ item.text.length > 80 ? '...' : '' }}
|
||||||
<td>{{ item.is_hidden }}
|
<td>{{ item.duration | osMinutesToTime }}
|
||||||
</table>
|
<td>{{ item.comment }}
|
||||||
|
<td>{{ item.is_hidden }}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="text-danger">
|
<div class="text-danger">
|
||||||
<div ng-repeat="item in itemsFailed = (items | filter:{importerror:true})"></div>
|
|
||||||
<i class="fa fa-exclamation-triangle"></i>
|
<i class="fa fa-exclamation-triangle"></i>
|
||||||
{{ itemsFailed.length }}
|
{{ itemsWillNMotBeImported }}
|
||||||
<translate>topics will be not imported.</translate>
|
<translate>topics will be not imported.</translate>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div ng-repeat="item in itemsPassed = (items | filter:{importerror:false})"></div>
|
|
||||||
<i class="fa fa-check-circle-o fa-lg"></i>
|
<i class="fa fa-check-circle-o fa-lg"></i>
|
||||||
{{ items.length - itemsFailed.length }}
|
{{ itemsWillBeImported }}
|
||||||
<translate>topics will be imported.</translate>
|
<translate>topics will be imported.</translate>
|
||||||
</div>
|
</div>
|
||||||
<div ng-repeat="item in itemsImported = (items | filter:{imported:true})"></div>
|
<div ng-repeat="item in itemsImported = (items | filter:{imported:true})"></div>
|
||||||
@ -133,8 +120,8 @@
|
|||||||
<button ng-click="clear()" class="btn btn-default" translate>
|
<button ng-click="clear()" class="btn btn-default" translate>
|
||||||
Clear preview
|
Clear preview
|
||||||
</button>
|
</button>
|
||||||
<button ng-if="!csvImporting && (items.length - itemdFailed.length) > 0" ng-click="import()" class="btn btn-primary" translate>
|
<button ng-if="!csvImporting && itemsWillBeImported > 0" ng-click="import()" class="btn btn-primary" translate>
|
||||||
Import {{ items.length - itemsFailed.length }} topics
|
Import {{ itemsWillBeImported }} topics
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="spacer">
|
<div class="spacer">
|
||||||
|
@ -8,10 +8,17 @@ angular.module('OpenSlidesApp.users.csv', [])
|
|||||||
'Group',
|
'Group',
|
||||||
'gettextCatalog',
|
'gettextCatalog',
|
||||||
function (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 {
|
return {
|
||||||
export: function (element, users) {
|
export: function (element, users) {
|
||||||
var csvRows = [
|
var csvRows = [
|
||||||
['title', 'first_name', 'last_name', 'structure_level', 'number', 'groups', 'comment', 'is_active', 'is_present', 'is_committee'],
|
makeHeaderline()
|
||||||
];
|
];
|
||||||
_.forEach(users, function (user) {
|
_.forEach(users, function (user) {
|
||||||
var row = [];
|
var row = [];
|
||||||
@ -46,9 +53,8 @@ angular.module('OpenSlidesApp.users.csv', [])
|
|||||||
if (groups.length >= 2) {
|
if (groups.length >= 2) {
|
||||||
csvGroup = gettextCatalog.getString(groups[groups.length - 1].name); // take last group
|
csvGroup = gettextCatalog.getString(groups[groups.length - 1].name); // take last group
|
||||||
}
|
}
|
||||||
var csvRows = [
|
|
||||||
// column header line
|
var csvRows = [makeHeaderline(),
|
||||||
['title', 'first_name', 'last_name', 'structure_level', 'number', 'groups', 'comment', 'is_active', 'is_present', 'is_committee'],
|
|
||||||
// example entries
|
// example entries
|
||||||
['Dr.', 'Max', 'Mustermann', 'Berlin','1234567890', csvGroups, 'xyz', '1', '1', ''],
|
['Dr.', 'Max', 'Mustermann', 'Berlin','1234567890', csvGroups, 'xyz', '1', '1', ''],
|
||||||
['', 'John', 'Doe', 'Washington','75/99/8-2', csvGroup, 'abc', '1', '1', ''],
|
['', 'John', 'Doe', 'Washington','75/99/8-2', csvGroup, 'abc', '1', '1', ''],
|
||||||
|
@ -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
|
// pagination
|
||||||
$scope.currentPage = 1;
|
$scope.currentPage = 1;
|
||||||
$scope.itemsPerPage = 100;
|
$scope.itemsPerPage = 100;
|
||||||
@ -955,45 +929,46 @@ angular.module('OpenSlidesApp.users.site', [
|
|||||||
gettext('override new'),
|
gettext('override new'),
|
||||||
gettext('create duplicate')
|
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.
|
// All user objects are already loaded via the resolve statement from ui-router.
|
||||||
var users = User.getAll();
|
var users = User.getAll();
|
||||||
$scope.users = [];
|
$scope.users = [];
|
||||||
|
|
||||||
|
var csvUsers = [];
|
||||||
|
_.forEach(csv.data, function (row) {
|
||||||
|
if (row.length >= 2) {
|
||||||
|
var filledRow = _.zipObject(FIELDS, row);
|
||||||
|
csvUsers.push(filledRow);
|
||||||
|
}
|
||||||
|
});
|
||||||
$scope.duplicates = 0;
|
$scope.duplicates = 0;
|
||||||
var quotionRe = /^"(.*)"$/;
|
_.forEach(csvUsers, function (user) {
|
||||||
angular.forEach($scope.csv.result, function (user) {
|
|
||||||
user.selected = true;
|
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) {
|
if (!user.first_name && !user.last_name) {
|
||||||
user.importerror = true;
|
user.importerror = true;
|
||||||
user.name_error = gettext('Error: Given name or surname is required.');
|
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
|
// number
|
||||||
if (user.number) {
|
if (!user.number) {
|
||||||
user.number = user.number.replace(quotionRe, '$1');
|
|
||||||
} else {
|
|
||||||
user.number = "";
|
user.number = "";
|
||||||
}
|
}
|
||||||
// groups
|
// groups
|
||||||
user.groups_id = []; // will be overwritten if there are groups
|
user.groups_id = []; // will be overwritten if there are groups
|
||||||
if (user.groups) {
|
if (user.groups) {
|
||||||
user.groups = user.groups.replace(quotionRe, '$1').split(',');
|
user.groups = user.groups.split(',');
|
||||||
user.groups = _.map(user.groups, function (group) {
|
user.groups = _.map(user.groups, function (group) {
|
||||||
return _.trim(group); // remove whitespaces on start or end
|
return _.trim(group); // remove whitespaces on start or end
|
||||||
});
|
});
|
||||||
@ -1015,43 +990,9 @@ angular.module('OpenSlidesApp.users.site', [
|
|||||||
// for template:
|
// for template:
|
||||||
user.groupsNotToCreate = _.difference(user.groups, user.groupsToCreate);
|
user.groupsNotToCreate = _.difference(user.groups, user.groupsToCreate);
|
||||||
}
|
}
|
||||||
// comment
|
user.is_active = (user.is_active !== undefined && user.is_active === '1');
|
||||||
if (user.comment) {
|
user.is_present = (user.is_present !== undefined && user.is_present === '1');
|
||||||
user.comment = user.comment.replace(quotionRe, '$1');
|
user.is_committee = (user.is_committee !== undefined && user.is_committee === '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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for duplicates
|
// Check for duplicates
|
||||||
user.duplicate = false;
|
user.duplicate = false;
|
||||||
@ -1089,7 +1030,7 @@ angular.module('OpenSlidesApp.users.site', [
|
|||||||
$scope.users.push(user);
|
$scope.users.push(user);
|
||||||
});
|
});
|
||||||
$scope.calcStats();
|
$scope.calcStats();
|
||||||
});
|
};
|
||||||
|
|
||||||
// Stats
|
// Stats
|
||||||
$scope.calcStats = function() {
|
$scope.calcStats = function() {
|
||||||
@ -1193,7 +1134,7 @@ angular.module('OpenSlidesApp.users.site', [
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
$scope.clear = function () {
|
$scope.clear = function () {
|
||||||
$scope.csv.result = null;
|
$scope.users = null;
|
||||||
};
|
};
|
||||||
// download CSV example file
|
// download CSV example file
|
||||||
$scope.downloadCSVExample = function () {
|
$scope.downloadCSVExample = function () {
|
||||||
|
@ -41,41 +41,31 @@
|
|||||||
|
|
||||||
<h2 translate>Import by CSV file</h2>
|
<h2 translate>Import by CSV file</h2>
|
||||||
|
|
||||||
<div class="block row">
|
<h3 translate>Select a CSV file</h3>
|
||||||
<div class="title">
|
<csv-import change="onCsvChange(csv)" config="csvConfig"></csv-import>
|
||||||
<h3 translate>Select a CSV file
|
|
||||||
</div>
|
|
||||||
<div class="block right import">
|
|
||||||
<label class="label" for="inputSeparator" translate>Separator</label>
|
|
||||||
<input type="text" ng-model="separator" ng-change="setSeparator()" ng-init="separator=separator" id="inputSeparator">
|
|
||||||
<br>
|
|
||||||
<label class="label" for="selectEncoding" translate>Encoding</label>
|
|
||||||
<select ng-model="encoding" ng-options="enc as enc for enc in encodingOptions"
|
|
||||||
ng-selected="setEncoding()" ng-init="encoding=encoding" id="selectEncoding"></select>
|
|
||||||
<ng-csv-import
|
|
||||||
content="csv.content"
|
|
||||||
header="csv.header"
|
|
||||||
header-visible="csv.headerVisible"
|
|
||||||
separator="csv.separator"
|
|
||||||
separator-visible="csv.separatorVisible"
|
|
||||||
result="csv.result"
|
|
||||||
accept="csv.accept"
|
|
||||||
encoding="csv.encoding"
|
|
||||||
encoding-visible="csv.encodingVisible"></ng-csv-import>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h4 translate>Please note:</h4>
|
<h4 translate>Please note:</h4>
|
||||||
<ul class="indentation">
|
<ul class="indentation">
|
||||||
<li><translate>Required comma or semicolon separated values with these column header names in the first row</translate>:<br>
|
<li><translate>Required comma or semicolon separated values with these column header names in the first row</translate>:<br>
|
||||||
<code>title, first_name, last_name, structure_level, number, groups, comment, is_active, is_present, is_committee</code>
|
<code>
|
||||||
|
<translate>Title</translate>,
|
||||||
|
<translate>Given name</translate>,
|
||||||
|
<translate>Surname</translate>,
|
||||||
|
<translate>Structure level</translate>,
|
||||||
|
<translate>Participant number</translate>,
|
||||||
|
<translate>Groups</translate>,
|
||||||
|
<translate>Comment</translate>,
|
||||||
|
<translate>Is active</translate>,
|
||||||
|
<translate>Is present</translate>,
|
||||||
|
<translate>Is committee</translate>
|
||||||
|
</code>
|
||||||
<li translate>At least given name or surname have to be filled in. All
|
<li translate>At least given name or surname have to be filled in. All
|
||||||
other fields are optional and may be empty.
|
other fields are optional and may be empty.
|
||||||
<li translate>Only double quotes are accepted as text delimiter (no single quotes).
|
<li translate>Only double quotes are accepted as text delimiter (no single quotes).
|
||||||
<li><a id="downloadLink" href="" ng-click="downloadCSVExample()" translate>Download CSV example file</a>
|
<li><a id="downloadLink" href="" ng-click="downloadCSVExample()" translate>Download CSV example file</a>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div ng-show="csv.result">
|
<div ng-show="users.length"
|
||||||
<h3 translate>Preview</h3>
|
<h3 translate>Preview</h3>
|
||||||
<div class="scroll-x-container">
|
<div class="scroll-x-container">
|
||||||
<table class="table table-striped table-bordered table-condensed">
|
<table class="table table-striped table-bordered table-condensed">
|
||||||
|
Loading…
Reference in New Issue
Block a user