Agenda DOCX export, motion log message when comment edited, motion sequential number in detail view and PDF, save pagination state to session storage (fixes #3558).

This commit is contained in:
FinnStutzenstein 2018-01-26 14:56:42 +01:00 committed by Emanuel Schütze
parent df523ce526
commit 3ba39c37c1
25 changed files with 320 additions and 114 deletions

View File

@ -14,6 +14,7 @@ Agenda:
- New permission for managing lists of speakers [#3366].
- Fixed multiple request on creation of agenda related items [#3341].
- Added possibility to mark speakers [#3570].
- New DOCX export of agenda [#3569].
Motions:
- New export dialog [#3185].
@ -56,6 +57,8 @@ Motions:
- Combined all boolean filters into one dropdown menu and added a filter
for amendments [#3501].
- Allow to delete own motions [#3516].
- Log which comment was updated [#3569].
- Save pagination sate to session storage [#3569].
Elections:
- Added pagination for list view [#3393].

View File

@ -0,0 +1,84 @@
(function () {
'use strict';
angular.module('OpenSlidesApp.agenda.docx', ['OpenSlidesApp.core.docx'])
.factory('AgendaDocxExport', [
'$http',
'gettextCatalog',
'FileSaver',
'Agenda',
'AgendaTree',
'Config',
function ($http, gettextCatalog, FileSaver, Agenda, AgendaTree, Config) {
var getData = function (items) {
// Item structure: The top layer has subitems, that are flat.
// The first layer is bold and all sublayers not. The docx
// templater cannot render items recursively, so the second
// layer are all subitems flated out. Spacing is done with tabs.
var tree = AgendaTree.getTree(items);
var subitems = []; // This will be used as a temporary variable.
var flatSubitems = function (children, parentCount) {
_.forEach(children, function (child) {
var taps = _.repeat('\t', parentCount - 1);
subitems.push({
item_number: taps + child.item.item_number,
item_title: child.item.list_view_title,
});
flatSubitems(child.children, parentCount + 1);
});
};
var twoLayerTree = _.map(tree, function (mainItem) {
subitems = [];
flatSubitems(mainItem.children, 1);
return {
item_number: mainItem.item.item_number,
item_title: mainItem.item.list_view_title,
subitems: subitems,
};
});
// header
var headerline1 = [
Config.translate(Config.get('general_event_name').value),
Config.translate(Config.get('general_event_description').value)
].filter(Boolean).join(' ');
var headerline2 = [
Config.get('general_event_location').value,
Config.get('general_event_date').value
].filter(Boolean).join(', ');
// Data structure for the docx templater.
return {
header: [headerline1, headerline2].join('\n'),
agenda_translation: gettextCatalog.getString('Agenda'),
top_list: twoLayerTree,
};
};
return {
export: function (items) {
// TODO: use filtered items.
var filename = gettextCatalog.getString('Agenda') + '.docx';
$http.get('/agenda/docxtemplate/').then(function (success) {
var content = window.atob(success.data);
var doc = new Docxgen(content);
var data = getData(items);
doc.setData(data);
doc.render();
var zip = doc.getZip();
//zip = converter.updateZipFile(zip);
var out = zip.generate({type: 'blob'});
FileSaver.saveAs(out, filename);
});
},
};
}
]);
})();

View File

@ -7,6 +7,7 @@ angular.module('OpenSlidesApp.agenda.site', [
'OpenSlidesApp.core.pdf',
'OpenSlidesApp.agenda.pdf',
'OpenSlidesApp.agenda.csv',
'OpenSlidesApp.agenda.docx',
])
.config([
@ -100,12 +101,14 @@ angular.module('OpenSlidesApp.agenda.site', [
'gettextCatalog',
'gettext',
'osTableFilter',
'osTablePagination',
'AgendaCsvExport',
'AgendaPdfExport',
'AgendaDocxExport',
'ErrorMessage',
function($scope, $filter, $http, $state, DS, operator, ngDialog, Agenda, TopicForm,
AgendaTree, Projector, ProjectionDefault, gettextCatalog, gettext, osTableFilter,
AgendaCsvExport, AgendaPdfExport, ErrorMessage) {
osTablePagination, AgendaCsvExport, AgendaPdfExport, AgendaDocxExport, ErrorMessage) {
// Bind agenda tree to the scope
$scope.$watch(function () {
return Agenda.lastModified();
@ -167,13 +170,7 @@ angular.module('OpenSlidesApp.agenda.site', [
};
// pagination
$scope.currentPage = 1;
$scope.itemsPerPage = 25;
$scope.limitBegin = 0;
$scope.pageChanged = function() {
$scope.limitBegin = ($scope.currentPage - 1) * $scope.itemsPerPage;
$scope.gotoTop();
};
$scope.pagination = osTablePagination.createInstance('AgendaTablePagination');
// parse duration for inline editing
$scope.generateDurationText = function (item) {
@ -288,6 +285,9 @@ angular.module('OpenSlidesApp.agenda.site', [
$scope.csvExport = function () {
AgendaCsvExport.export($scope.itemsFiltered);
};
$scope.docxExport = function () {
AgendaDocxExport.export($scope.itemsFiltered);
};
/** select mode functions **/
$scope.isSelectMode = false;

View File

@ -111,6 +111,12 @@
CSV
</a>
</li>
<li>
<a href="" ng-click="docxExport()">
<i class="fa fa-file-word-o"></i>
DOCX
</a>
</li>
</ul>
</div>
</div>
@ -143,9 +149,10 @@
</span>
</span>
</div>
<div class="col-md-6" ng-show="itemsFiltered.length > itemsPerPage">
<div class="col-md-6" ng-show="itemsFiltered.length > pagination.itemsPerPage">
<span class="pull-right">
<translate>Page</translate> {{ currentPage }} / {{ Math.ceil(itemsFiltered.length/itemsPerPage) }}
<translate>Page</translate> {{ pagination.currentPage }} /
{{ Math.ceil(itemsFiltered.length/pagination.itemsPerPage) }}
</span>
</div>
</div>
@ -225,7 +232,7 @@
| osFilter: filter.filterString : filter.getObjectQueryString
| filter: {closed: filter.booleanFilters.closed.value}
| filter: {is_hidden: filter.booleanFilters.is_hidden.value})
| limitTo : itemsPerPage : limitBegin">
| limitTo : pagination.itemsPerPage : pagination.limitBegin">
<!-- select column -->
<div ng-show="isSelectMode" os-perms="agenda.can_manage" class="col-xs-1 centered">
@ -368,11 +375,11 @@
</div> <!-- container -->
<ul uib-pagination
ng-show="itemsFiltered.length > itemsPerPage"
ng-show="itemsFiltered.length > pagination.itemsPerPage"
total-items="itemsFiltered.length"
items-per-page="itemsPerPage"
ng-model="currentPage"
ng-change="pageChanged()"
items-per-page="pagination.itemsPerPage"
ng-model="pagination.currentPage"
ng-change="pagination.pageChanged()"
class="pagination-sm"
direction-links="false"
boundary-links="true"

Binary file not shown.

View File

@ -0,0 +1,9 @@
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^docxtemplate/$',
views.AgendaDocxTemplateView.as_view(),
name='agenda_docx_template'),
]

View File

@ -15,6 +15,7 @@ from openslides.utils.rest_api import (
detail_route,
list_route,
)
from openslides.utils.views import BinaryTemplateView
from ..utils.auth import has_perm
from .access_permissions import ItemAccessPermissions
@ -311,3 +312,11 @@ class ItemViewSet(ListModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericV
inform_changed_data(items)
return Response({'detail': _('The agenda has been sorted.')})
# Special views
class AgendaDocxTemplateView(BinaryTemplateView):
"""
Returns the template for motions docx export
"""
template_name = 'templates/docx/agenda.docx'

View File

@ -274,11 +274,13 @@ angular.module('OpenSlidesApp.assignments.site', [
'User',
'osTableFilter',
'osTableSort',
'osTablePagination',
'gettext',
'AssignmentPhases',
'AssignmentPdfExport',
function($scope, ngDialog, AssignmentForm, Assignment, Tag, Agenda, Projector, ProjectionDefault,
gettextCatalog, User, osTableFilter, osTableSort, gettext, AssignmentPhases, AssignmentPdfExport) {
function($scope, ngDialog, AssignmentForm, Assignment, Tag, Agenda, Projector,
ProjectionDefault, gettextCatalog, User, osTableFilter, osTableSort, osTablePagination,
gettext, AssignmentPhases, AssignmentPdfExport) {
$scope.$watch(function () {
return Assignment.lastModified();
}, function () {
@ -354,13 +356,7 @@ angular.module('OpenSlidesApp.assignments.site', [
};
// Pagination
$scope.currentPage = 1;
$scope.itemsPerPage = 25;
$scope.limitBegin = 0;
$scope.pageChanged = function() {
$scope.limitBegin = ($scope.currentPage - 1) * $scope.itemsPerPage;
$scope.gotoTop();
};
$scope.pagination = osTablePagination.createInstance('AssignmentTablePagination');
// update phase
$scope.updatePhase = function (assignment, phase_id) {

View File

@ -77,9 +77,10 @@
{{ assignments.length }} {{ "elections" | translate }}<span ng-if="(assignments|filter:{selected:true}).length > 0">,
{{(assignments|filter:{selected:true}).length}} {{ "selected" | translate }}</span>
</div>
<div class="col-md-6" ng-show="assignmentsFiltered.length > itemsPerPage">
<div class="col-md-6" ng-show="assignmentsFiltered.length > pagination.itemsPerPage">
<span class="pull-right">
<translate>Page</translate> {{ currentPage }} / {{ Math.ceil(assignmentsFiltered.length/itemsPerPage) }}
<translate>Page</translate> {{ pagination.currentPage }} /
{{ Math.ceil(assignmentsFiltered.length/pagination.itemsPerPage) }}
</span>
</div>
</div>
@ -87,7 +88,7 @@
<div class="os-table container-fluid">
<div class="row header-row">
<div class="col-xs-1 centered" ng-show="isSelectMode">
<i class="fa text-danger pointer" ng-class=" selectedAll ? 'fa-check-square-o' : 'fa-square-o'"
<i class="fa text-danger pointer" ng-class="selectedAll ? 'fa-check-square-o' : 'fa-square-o'"
ng-click="checkAll()"></i>
</div>
<div class="col-xs-11 main-header">
@ -216,7 +217,7 @@
| MultiselectFilter: filter.multiselectFilters.tag : getItemId.tag
| MultiselectFilter: filter.multiselectFilters.phase : getItemId.phase
| orderByEmptyLast: sort.column : sort.reverse)
| limitTo : itemsPerPage : limitBegin">
| limitTo : pagination.itemsPerPage : pagination.limitBegin">
<!-- select column -->
<div ng-show="isSelectMode" os-perms="assignments.can_manage" class="col-xs-1 centered">
@ -332,11 +333,11 @@
</div> <!-- main table -->
<ul uib-pagination
ng-show="assignmentsFiltered.length > itemsPerPage"
ng-show="assignmentsFiltered.length > pagination.itemsPerPage"
total-items="assignmentsFiltered.length"
items-per-page="itemsPerPage"
ng-model="currentPage"
ng-change="pageChanged()"
items-per-page="pagination.itemsPerPage"
ng-model="pagination.currentPage"
ng-change="pagination.pageChanged()"
class="pagination-sm"
direction-links="false"
boundary-links="true"

View File

@ -76,6 +76,8 @@ class CoreAppConfig(AppConfig):
# Client settings
client_settings_keys = [
'MOTION_IDENTIFIER_MIN_DIGITS',
'MOTION_IDENTIFIER_WITHOUT_BLANKS',
'MOTIONS_ALLOW_AMENDMENTS_OF_AMENDMENTS'
]
client_settings_dict = {}

View File

@ -612,6 +612,47 @@ angular.module('OpenSlidesApp.core.site', [
}
])
/* Factory for pagination of the tables. Saves all settings (currentPage, ...)
* to the session storage and recovers them when the table is reloaded.
* You have to provide a 'tableName' where the settings are saved in the session
* storage. Has to be unique for obvious reasons.
* The 'itemsPerPage' is optional. If not given, it defaults to 25.
*/
.factory('osTablePagination', [
'$rootScope',
'$sessionStorage',
function ($rootScope, $sessionStorage) {
var createInstance = function (tableName, itemsPerPage) {
// Defaults
var self = {
currentPage: 1,
itemsPerPage: itemsPerPage || 25,
limitBegin: 0,
};
// Check storage; maybe recover old state.
var storage = $sessionStorage[tableName];
if (storage) {
self = storage;
}
self.save = function () {
$sessionStorage[tableName] = self;
};
self.pageChanged = function () {
self.limitBegin = (self.currentPage - 1) * self.itemsPerPage;
self.save();
$rootScope.gotoTop();
};
return self;
};
return {
createInstance: createInstance
};
}
])
/* This Factory could be used in any dialog, if the user should be warned, if another user
* also has this dialog open. Use it like in this example in any dialog controller:
var editingStoppedCallback = EditingWarning.editingStarted('editing_name' + item.id);

View File

@ -17,13 +17,14 @@ angular.module('OpenSlidesApp.mediafiles.list', [
'ngDialog',
'osTableFilter',
'osTableSort',
'osTablePagination',
'ProjectionDefault',
'Projector',
'User',
'Mediafile',
'MediafileForm',
'Logos',
function ($http, $scope, gettext, ngDialog, osTableFilter, osTableSort,
function ($http, $scope, gettext, ngDialog, osTableFilter, osTableSort, osTablePagination,
ProjectionDefault, Projector, User, Mediafile, MediafileForm, Logos) {
$scope.$watch(function () {
return Mediafile.lastModified();
@ -107,13 +108,7 @@ angular.module('OpenSlidesApp.mediafiles.list', [
];
// pagination
$scope.currentPage = 1;
$scope.itemsPerPage = 25;
$scope.limitBegin = 0;
$scope.pageChanged = function() {
$scope.limitBegin = ($scope.currentPage - 1) * $scope.itemsPerPage;
$scope.gotoTop();
};
$scope.pagination = osTablePagination.createInstance('MediafileTablePagination');
// open new/edit dialog
$scope.openDialog = function (mediafile) {

View File

@ -144,9 +144,10 @@
{{ mediafiles.length }} {{ "files" | translate }}<span ng-if="(mediafiles|filter:{selected:true}).length > 0">,
{{(mediafiles|filter:{selected:true}).length}} {{ "selected" | translate }}</span>
</div>
<div class="col-md-6" ng-show="mediafilesFiltered.length > itemsPerPage">
<div class="col-md-6" ng-show="mediafilesFiltered.length > pagination.itemsPerPage">
<span class="pull-right">
<translate>Page</translate> {{ currentPage }} / {{ Math.ceil(mediafilesFiltered.length/itemsPerPage) }}
<translate>Page</translate> {{ pagination.currentPage }} /
{{ Math.ceil(mediafilesFiltered.length/pagination.itemsPerPage) }}
</span>
</div>
</div>
@ -248,7 +249,7 @@
| filter: {filetype: (filter.booleanFilters.isPdf.value ? 'application/pdf' : (filter.booleanFilters.isPdf.value === false ? '!application/pdf' : ''))}
| filter: {hidden: filter.booleanFilters.isHidden.value}
| orderByEmptyLast: sort.column : sort.reverse)
| limitTo: itemsPerPage : limitBegin">
| limitTo: pagination.itemsPerPage : pagination.limitBegin">
<!-- select column -->
<div ng-show="isSelectMode" os-perms="mediafiles.can_manage" class="col-xs-1 centered">
@ -368,11 +369,11 @@
</div><!-- end os-table -->
<ul uib-pagination
ng-show="mediafilesFiltered.length > itemsPerPage"
ng-show="mediafilesFiltered.length > pagination.itemsPerPage"
total-items="mediafilesFiltered.length"
items-per-page="itemsPerPage"
ng-model="currentPage"
ng-change="pageChanged()"
items-per-page="pagination.itemsPerPage"
ng-model="pagination.currentPage"
ng-change="pagination.pageChanged()"
class="pagination-sm"
direction-links="false"
boundary-links="true"

View File

@ -272,6 +272,14 @@ angular.module('OpenSlidesApp.motions', [
title += this.getTitle();
return title;
},
getSequentialNumber: function () {
var id = this.id + '';
var zeros = Math.max(0, OpenSlidesSettings.MOTION_IDENTIFIER_MIN_DIGITS - id.length);
for (var i = 0; i < zeros; i++) {
id = '0' + id;
}
return id;
},
getText: function (versionId) {
return this.getVersion(versionId).text;
},

View File

@ -16,8 +16,9 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
'Config',
'Motion',
'MotionComment',
function($q, operator, gettextCatalog, PDFLayout, PdfMakeConverter, ImageConverter, HTMLValidizer,
Category, Config, Motion, MotionComment) {
'OpenSlidesSettings',
function($q, operator, gettextCatalog, PDFLayout, PdfMakeConverter, ImageConverter,
HTMLValidizer, Category, Config, Motion, MotionComment, OpenSlidesSettings) {
/**
* Provides the content as JS objects for Motions in pdfMake context
* @constructor
@ -77,7 +78,8 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
);
}
if (Config.get('motions_export_sequential_number').value) {
subtitleLines.push(gettextCatalog.getString('Sequential number') + ': ' + motion.id);
subtitleLines.push(gettextCatalog.getString('Sequential number') + ': ' +
motion.getSequentialNumber());
}
var subtitle = PDFLayout.createSubtitle(subtitleLines);

View File

@ -962,13 +962,14 @@ angular.module('OpenSlidesApp.motions.site', [
'ProjectionDefault',
'osTableFilter',
'osTableSort',
'osTablePagination',
'MotionExportForm',
'MotionPdfExport',
'PersonalNoteManager',
function($scope, $state, $http, gettext, gettextCatalog, operator, ngDialog, MotionForm, Motion,
MotionComment, Category, Config, Tag, Workflow, User, Agenda, MotionBlock, Projector,
ProjectionDefault, osTableFilter, osTableSort, MotionExportForm, MotionPdfExport,
PersonalNoteManager) {
ProjectionDefault, osTableFilter, osTableSort, osTablePagination, MotionExportForm,
MotionPdfExport, PersonalNoteManager) {
Category.bindAll({}, $scope, 'categories');
MotionBlock.bindAll({}, $scope, 'motionBlocks');
Tag.bindAll({}, $scope, 'tags');
@ -1177,14 +1178,7 @@ angular.module('OpenSlidesApp.motions.site', [
];
// pagination
$scope.currentPage = 1;
$scope.itemsPerPage = 25;
$scope.limitBegin = 0;
$scope.pageChanged = function() {
$scope.limitBegin = ($scope.currentPage - 1) * $scope.itemsPerPage;
$scope.gotoTop();
};
$scope.pagination = osTablePagination.createInstance('MotionTablePagination');
// update state
$scope.updateState = function (motion, state_id) {
@ -1497,6 +1491,7 @@ angular.module('OpenSlidesApp.motions.site', [
var thisIndex = _.findIndex(motions, function (motion) {
return motion.id === $scope.motion.id;
});
this.count = motions.length;
this.nextMotion = thisIndex < motions.length-1 ? motions[thisIndex+1] : _.head(motions);
this.previousMotion = thisIndex > 0 ? motions[thisIndex-1] : _.last(motions);
},

View File

@ -78,10 +78,13 @@
<i class="fa fa-exclamation-triangle"></i>
<translate>This version is not permitted.</translate>
</span>
<small>
<translate>Sequential number</translate> {{ motion.getSequentialNumber() }}
</small>
</h2>
</div>
<div class="col-sm-6">
<span class="pull-right">
<span class="pull-right" ng-if="navigation.count > 2">
<a ui-sref="motions.motion.detail({id: navigation.previousMotion.id})"
class="btn btn-default btn-sm" ng-disabled="!navigation.previousMotion">
<i class="fa fa-angle-double-left"></i>

View File

@ -126,9 +126,10 @@
{{ motions.length }} {{ "motions" | translate }}<span ng-if="(motions|filter:{selected:true}).length > 0">,
{{(motions|filter:{selected:true}).length}} {{ "selected" | translate }}</span>
</div>
<div class="col-md-6" ng-show="motionsFiltered.length > itemsPerPage">
<div class="col-md-6" ng-show="motionsFiltered.length > pagination.itemsPerPage">
<span class="pull-right">
<translate>Page</translate> {{ currentPage }} / {{ Math.ceil(motionsFiltered.length/itemsPerPage) }}
<translate>Page</translate> {{ pagination.currentPage }} /
{{ Math.ceil(motionsFiltered.length/pagination.itemsPerPage) }}
</span>
</div>
</div>
@ -532,7 +533,7 @@
| filter: {isAmendment: filter.booleanFilters.isAmendment.value}
| toArray
| orderByEmptyLast: sort.column : sort.reverse)
| limitTo : itemsPerPage : limitBegin">
| limitTo : pagination.itemsPerPage : pagination.limitBegin">
<!-- select column -->
<div ng-show="isSelectMode" os-perms="motions.can_manage" class="col-xs-1 centered">
@ -788,11 +789,11 @@
</div> <!-- data row -->
<ul uib-pagination
ng-show="motionsFiltered.length > itemsPerPage"
ng-show="motionsFiltered.length > pagination.itemsPerPage"
total-items="motionsFiltered.length"
items-per-page="itemsPerPage"
ng-model="currentPage"
ng-change="pageChanged()"
items-per-page="pagination.itemsPerPage"
ng-model="pagination.currentPage"
ng-change="pagination.pageChanged()"
class="pagination-sm"
direction-links="false"
boundary-links="true"

View File

@ -1,9 +1,7 @@
import base64
import re
from typing import Optional # noqa
from django.conf import settings
from django.contrib.staticfiles import finders
from django.core.exceptions import ValidationError as DjangoValidationError
from django.db import IntegrityError, transaction
from django.http import Http404
@ -24,7 +22,7 @@ from ..utils.rest_api import (
ValidationError,
detail_route,
)
from ..utils.views import APIView
from ..utils.views import BinaryTemplateView
from .access_permissions import (
CategoryAccessPermissions,
MotionAccessPermissions,
@ -206,6 +204,16 @@ class MotionViewSet(ModelViewSet):
# No comments here. Just do nothing.
pass
# get changed comment fields
changed_comment_fields = []
comments = request.data.get('comments', {})
for id, value in comments.items():
if not motion.comments or motion.comments.get(id) != value:
field = config['motions_comments'].get(id)
if field:
name = field['name']
changed_comment_fields.append(name)
# Validate data and update motion.
serializer = self.get_serializer(
motion,
@ -222,6 +230,11 @@ class MotionViewSet(ModelViewSet):
updated_motion.supporters.clear()
updated_motion.write_log([ugettext_noop('All supporters removed')], request.user)
if len(changed_comment_fields) > 0:
updated_motion.write_log(
[ugettext_noop('Comment {} updated').format(', '.join(changed_comment_fields))],
request.user)
# Send new submitters and supporters via autoupdate because users
# without permission to see users may not have them but can get it now.
new_users = list(updated_motion.submitters.all())
@ -707,15 +720,10 @@ class WorkflowViewSet(ModelViewSet):
return result
# Special API views
# Special views
class MotionDocxTemplateView(APIView):
class MotionDocxTemplateView(BinaryTemplateView):
"""
Returns the template for motions docx export
"""
http_method_names = ['get']
def get_context_data(self, **context):
with open(finders.find('templates/docx/motions.docx'), "rb") as file:
response = base64.b64encode(file.read())
return response
template_name = 'templates/docx/motions.docx'

View File

@ -12,6 +12,7 @@ urlpatterns += [
url(r'^%s(?P<path>.*)$' % settings.MEDIA_URL.lstrip('/'), protected_serve, {'document_root': settings.MEDIA_ROOT}),
url(r'^(?P<url>.*[^/])$', RedirectView.as_view(url='/%(url)s/', permanent=True)),
url(r'^rest/', include(router.urls)),
url(r'^agenda/', include('openslides.agenda.urls')),
url(r'^motions/', include('openslides.motions.urls')),
url(r'^users/', include('openslides.users.urls')),

View File

@ -551,12 +551,13 @@ angular.module('OpenSlidesApp.users.site', [
'UserCsvExport',
'osTableFilter',
'osTableSort',
'osTablePagination',
'gettext',
'UserPdfExport',
'ErrorMessage',
function($scope, $state, $http, $q, ngDialog, UserForm, User, Group, PasswordGenerator,
Projector, ProjectionDefault, Config, gettextCatalog, UserCsvExport, osTableFilter,
osTableSort, gettext, UserPdfExport, ErrorMessage) {
osTableSort, osTablePagination, gettext, UserPdfExport, ErrorMessage) {
$scope.$watch(function () {
return User.lastModified();
}, function () {
@ -649,13 +650,7 @@ angular.module('OpenSlidesApp.users.site', [
];
// pagination
$scope.currentPage = 1;
$scope.itemsPerPage = 25;
$scope.limitBegin = 0;
$scope.pageChanged = function() {
$scope.limitBegin = ($scope.currentPage - 1) * $scope.itemsPerPage;
$scope.gotoTop();
};
$scope.pagination = osTablePagination.createInstance('UserTablePagination');
// Toggle group from user
$scope.toggleGroup = function (user, group) {
@ -1121,15 +1116,13 @@ angular.module('OpenSlidesApp.users.site', [
'User',
'Group',
'UserCsvExport',
'osTablePagination',
'ErrorMessage',
function($scope, $http, $q, gettext, gettextCatalog, User, Group, UserCsvExport, ErrorMessage) {
function($scope, $http, $q, gettext, gettextCatalog, User, Group, UserCsvExport,
osTablePagination, ErrorMessage) {
// import from textarea
$scope.importByLine = function () {
var usernames = $scope.userlist[0].split("\n");
// Ignore empty lines.
/*usernames = _.filter(usernames, function (name) {
return name !== '';
});*/
var users = _.map(usernames, function (name) {
// Split each full name in first and last name.
// The last word is set as last name, rest is the first name(s).
@ -1157,12 +1150,9 @@ angular.module('OpenSlidesApp.users.site', [
};
// pagination
$scope.currentPage = 1;
$scope.itemsPerPage = 100;
$scope.limitBegin = 0;
$scope.pageChanged = function() {
$scope.limitBegin = ($scope.currentPage - 1) * $scope.itemsPerPage;
};
$scope.pagination = osTablePagination.createInstance('UserImportTablePagination', 100);
// Duplicates
$scope.duplicateActions = [
gettext('keep original'),
gettext('override new'),

View File

@ -68,6 +68,12 @@
<div ng-show="users.length">
<h3 translate>Preview</h3>
<div class="clearfix" ng-if="users.length > pagination.itemsPerPage">
<span class="pull-right">
<translate>Page</translate> {{ pagination.currentPage }} /
{{ Math.ceil(users.length/pagination.itemsPerPage) }}
</span>
</div>
<div class="scroll-x-container">
<table class="table table-striped table-bordered table-condensed">
<thead>
@ -106,7 +112,7 @@
</div>
</th>
<tbody>
<tr ng-repeat="user in users | limitTo : itemsPerPage : limitBegin">
<tr ng-repeat="user in users | limitTo : pagination.itemsPerPage : pagination.limitBegin">
<td class="minimum"
ng-class="{ 'text-danger': (user.importerror || user.duplicateAction == duplicateActions[0]), 'text-success': user.imported }">
<span ng-if="user.importerror || user.duplicateAction == duplicateActions[0]">
@ -120,7 +126,7 @@
<i class="fa fa-check-circle fa-lg"></i>
</span>
<td class="nobr">
{{ (currentPage - 1) * itemsPerPage + $index + 1 }}
{{ (pagination.currentPage - 1) * pagination.itemsPerPage + $index + 1 }}
<td>
{{ user.title }}
<td ng-class="{ 'text-danger': user.name_error }">
@ -180,11 +186,11 @@
</table>
</div>
<ul uib-pagination
ng-show="users.length > itemsPerPage"
ng-show="users.length > pagination.itemsPerPage"
total-items="users.length"
items-per-page="itemsPerPage"
ng-model="currentPage"
ng-change="pageChanged()"
items-per-page="pagination.itemsPerPage"
ng-model="pagination.currentPage"
ng-change="pagination.pageChanged()"
class="pagination-sm"
direction-links="false"
boundary-links="true"

View File

@ -147,9 +147,10 @@
{{ users.length }} {{ "participants" | translate }}<span ng-if="(users|filter:{selected:true}).length > 0">,
{{(users|filter:{selected:true}).length}} {{ "selected" | translate }}</span>
</div>
<div class="col-md-6" ng-show="usersFiltered.length > itemsPerPage">
<div class="col-md-6" ng-show="usersFiltered.length > pagination.itemsPerPage">
<span class="pull-right">
<translate>Page</translate> {{ currentPage }} / {{ Math.ceil(usersFiltered.length/itemsPerPage) }}
<translate>Page</translate> {{ pagination.currentPage }} /
{{ Math.ceil(usersFiltered.length/pagination.itemsPerPage) }}
</span>
</div>
</div>
@ -304,7 +305,7 @@
ng-mouseleave="user.hover=false"
ng-class="{'projected': user.isProjected().length}"
ng-repeat="user in usersFiltered
| limitTo : itemsPerPage : limitBegin">
| limitTo : pagination.itemsPerPage : pagination.limitBegin">
<!-- select column -->
<div ng-show="isSelectMode" os-perms="users.can_manage" class="col-xs-1 centered">
@ -451,11 +452,11 @@
</div><!-- end os-table -->
<ul uib-pagination
ng-show="usersFiltered.length > itemsPerPage"
ng-show="usersFiltered.length > pagination.itemsPerPage"
total-items="usersFiltered.length"
items-per-page="itemsPerPage"
ng-model="currentPage"
ng-change="pageChanged()"
items-per-page="pagination.itemsPerPage"
ng-model="pagination.currentPage"
ng-change="pagination.pageChanged()"
class="pagination-sm"
direction-links="false"
boundary-links="true"

View File

@ -1,3 +1,4 @@
import base64
from typing import Any, Dict, List # noqa
from django.contrib.staticfiles import finders
@ -67,8 +68,20 @@ class TemplateView(View):
raise ImproperlyConfigured("'template_name' is not provided.")
if self.template_name not in self.state:
with open(finders.find(self.template_name)) as template:
self.state[self.template_name] = template.read()
self.state[self.template_name] = self.load_template()
def load_template(self) -> str:
with open(finders.find(self.template_name)) as template:
return template.read()
def get(self, *args: Any, **kwargs: Any) -> HttpResponse:
return HttpResponse(self.state[self.template_name])
class BinaryTemplateView(TemplateView):
"""
Loads the specified binary template and encode it with base64.
"""
def load_template(self):
with open(finders.find(self.template_name), 'rb') as template:
return base64.b64encode(template.read())

View File

@ -8,11 +8,12 @@ from rest_framework import status
from rest_framework.test import APIClient
from openslides.core.config import config
from openslides.core.models import Tag
from openslides.core.models import ConfigStore, Tag
from openslides.motions.models import (
Category,
Motion,
MotionBlock,
MotionLog,
State,
Workflow,
)
@ -604,6 +605,35 @@ class UpdateMotion(TestCase):
motion = Motion.objects.get()
self.assertEqual(motion.versions.count(), 1)
def test_update_comment_creates_log_entry(self):
field_name = 'comment_field_name_texl2i7%sookqerpl29a'
config['motions_comments'] = {
'1': {
'name': field_name,
'public': False
}
}
# Update Config cache
CollectionElement.from_instance(
ConfigStore.objects.get(key='motions_comments')
)
response = self.client.patch(
reverse('motion-detail', args=[self.motion.pk]),
{'title': 'title_test_sfdAaufd56HR7sd5FDq7av',
'text': 'text_test_fiuhefF86()ew1Ef346AF6W',
'comments': {'1': 'comment1_sdpoiuffo3%7dwDwW)'}
},
format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
motion_logs = MotionLog.objects.filter(motion=self.motion)
self.assertEqual(motion_logs.count(), 2)
motion_log = motion_logs.order_by('-time').first()
self.assertTrue(field_name in motion_log.message_list[0])
class DeleteMotion(TestCase):
"""