Merge pull request #3569 from FinnStutzenstein/dgb-1
Agenda DOCX export, motion log message when comment edited, motion se…
This commit is contained in:
commit
2a1b0a645a
@ -14,6 +14,7 @@ Agenda:
|
|||||||
- New permission for managing lists of speakers [#3366].
|
- New permission for managing lists of speakers [#3366].
|
||||||
- Fixed multiple request on creation of agenda related items [#3341].
|
- Fixed multiple request on creation of agenda related items [#3341].
|
||||||
- Added possibility to mark speakers [#3570].
|
- Added possibility to mark speakers [#3570].
|
||||||
|
- New DOCX export of agenda [#3569].
|
||||||
|
|
||||||
Motions:
|
Motions:
|
||||||
- New export dialog [#3185].
|
- New export dialog [#3185].
|
||||||
@ -56,6 +57,8 @@ Motions:
|
|||||||
- Combined all boolean filters into one dropdown menu and added a filter
|
- Combined all boolean filters into one dropdown menu and added a filter
|
||||||
for amendments [#3501].
|
for amendments [#3501].
|
||||||
- Allow to delete own motions [#3516].
|
- Allow to delete own motions [#3516].
|
||||||
|
- Log which comment was updated [#3569].
|
||||||
|
- Save pagination sate to session storage [#3569].
|
||||||
|
|
||||||
Elections:
|
Elections:
|
||||||
- Added pagination for list view [#3393].
|
- Added pagination for list view [#3393].
|
||||||
|
84
openslides/agenda/static/js/agenda/docx.js
Normal file
84
openslides/agenda/static/js/agenda/docx.js
Normal 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);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
})();
|
@ -7,6 +7,7 @@ angular.module('OpenSlidesApp.agenda.site', [
|
|||||||
'OpenSlidesApp.core.pdf',
|
'OpenSlidesApp.core.pdf',
|
||||||
'OpenSlidesApp.agenda.pdf',
|
'OpenSlidesApp.agenda.pdf',
|
||||||
'OpenSlidesApp.agenda.csv',
|
'OpenSlidesApp.agenda.csv',
|
||||||
|
'OpenSlidesApp.agenda.docx',
|
||||||
])
|
])
|
||||||
|
|
||||||
.config([
|
.config([
|
||||||
@ -100,12 +101,14 @@ angular.module('OpenSlidesApp.agenda.site', [
|
|||||||
'gettextCatalog',
|
'gettextCatalog',
|
||||||
'gettext',
|
'gettext',
|
||||||
'osTableFilter',
|
'osTableFilter',
|
||||||
|
'osTablePagination',
|
||||||
'AgendaCsvExport',
|
'AgendaCsvExport',
|
||||||
'AgendaPdfExport',
|
'AgendaPdfExport',
|
||||||
|
'AgendaDocxExport',
|
||||||
'ErrorMessage',
|
'ErrorMessage',
|
||||||
function($scope, $filter, $http, $state, DS, operator, ngDialog, Agenda, TopicForm,
|
function($scope, $filter, $http, $state, DS, operator, ngDialog, Agenda, TopicForm,
|
||||||
AgendaTree, Projector, ProjectionDefault, gettextCatalog, gettext, osTableFilter,
|
AgendaTree, Projector, ProjectionDefault, gettextCatalog, gettext, osTableFilter,
|
||||||
AgendaCsvExport, AgendaPdfExport, ErrorMessage) {
|
osTablePagination, AgendaCsvExport, AgendaPdfExport, AgendaDocxExport, ErrorMessage) {
|
||||||
// Bind agenda tree to the scope
|
// Bind agenda tree to the scope
|
||||||
$scope.$watch(function () {
|
$scope.$watch(function () {
|
||||||
return Agenda.lastModified();
|
return Agenda.lastModified();
|
||||||
@ -167,13 +170,7 @@ angular.module('OpenSlidesApp.agenda.site', [
|
|||||||
};
|
};
|
||||||
|
|
||||||
// pagination
|
// pagination
|
||||||
$scope.currentPage = 1;
|
$scope.pagination = osTablePagination.createInstance('AgendaTablePagination');
|
||||||
$scope.itemsPerPage = 25;
|
|
||||||
$scope.limitBegin = 0;
|
|
||||||
$scope.pageChanged = function() {
|
|
||||||
$scope.limitBegin = ($scope.currentPage - 1) * $scope.itemsPerPage;
|
|
||||||
$scope.gotoTop();
|
|
||||||
};
|
|
||||||
|
|
||||||
// parse duration for inline editing
|
// parse duration for inline editing
|
||||||
$scope.generateDurationText = function (item) {
|
$scope.generateDurationText = function (item) {
|
||||||
@ -288,6 +285,9 @@ angular.module('OpenSlidesApp.agenda.site', [
|
|||||||
$scope.csvExport = function () {
|
$scope.csvExport = function () {
|
||||||
AgendaCsvExport.export($scope.itemsFiltered);
|
AgendaCsvExport.export($scope.itemsFiltered);
|
||||||
};
|
};
|
||||||
|
$scope.docxExport = function () {
|
||||||
|
AgendaDocxExport.export($scope.itemsFiltered);
|
||||||
|
};
|
||||||
|
|
||||||
/** select mode functions **/
|
/** select mode functions **/
|
||||||
$scope.isSelectMode = false;
|
$scope.isSelectMode = false;
|
||||||
|
@ -111,6 +111,12 @@
|
|||||||
CSV
|
CSV
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="" ng-click="docxExport()">
|
||||||
|
<i class="fa fa-file-word-o"></i>
|
||||||
|
DOCX
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -143,9 +149,10 @@
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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">
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -225,7 +232,7 @@
|
|||||||
| osFilter: filter.filterString : filter.getObjectQueryString
|
| osFilter: filter.filterString : filter.getObjectQueryString
|
||||||
| filter: {closed: filter.booleanFilters.closed.value}
|
| filter: {closed: filter.booleanFilters.closed.value}
|
||||||
| filter: {is_hidden: filter.booleanFilters.is_hidden.value})
|
| filter: {is_hidden: filter.booleanFilters.is_hidden.value})
|
||||||
| limitTo : itemsPerPage : limitBegin">
|
| limitTo : pagination.itemsPerPage : pagination.limitBegin">
|
||||||
|
|
||||||
<!-- select column -->
|
<!-- select column -->
|
||||||
<div ng-show="isSelectMode" os-perms="agenda.can_manage" class="col-xs-1 centered">
|
<div ng-show="isSelectMode" os-perms="agenda.can_manage" class="col-xs-1 centered">
|
||||||
@ -368,11 +375,11 @@
|
|||||||
</div> <!-- container -->
|
</div> <!-- container -->
|
||||||
|
|
||||||
<ul uib-pagination
|
<ul uib-pagination
|
||||||
ng-show="itemsFiltered.length > itemsPerPage"
|
ng-show="itemsFiltered.length > pagination.itemsPerPage"
|
||||||
total-items="itemsFiltered.length"
|
total-items="itemsFiltered.length"
|
||||||
items-per-page="itemsPerPage"
|
items-per-page="pagination.itemsPerPage"
|
||||||
ng-model="currentPage"
|
ng-model="pagination.currentPage"
|
||||||
ng-change="pageChanged()"
|
ng-change="pagination.pageChanged()"
|
||||||
class="pagination-sm"
|
class="pagination-sm"
|
||||||
direction-links="false"
|
direction-links="false"
|
||||||
boundary-links="true"
|
boundary-links="true"
|
||||||
|
BIN
openslides/agenda/static/templates/docx/agenda.docx
Normal file
BIN
openslides/agenda/static/templates/docx/agenda.docx
Normal file
Binary file not shown.
9
openslides/agenda/urls.py
Normal file
9
openslides/agenda/urls.py
Normal 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'),
|
||||||
|
]
|
@ -15,6 +15,7 @@ from openslides.utils.rest_api import (
|
|||||||
detail_route,
|
detail_route,
|
||||||
list_route,
|
list_route,
|
||||||
)
|
)
|
||||||
|
from openslides.utils.views import BinaryTemplateView
|
||||||
|
|
||||||
from ..utils.auth import has_perm
|
from ..utils.auth import has_perm
|
||||||
from .access_permissions import ItemAccessPermissions
|
from .access_permissions import ItemAccessPermissions
|
||||||
@ -311,3 +312,11 @@ class ItemViewSet(ListModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericV
|
|||||||
|
|
||||||
inform_changed_data(items)
|
inform_changed_data(items)
|
||||||
return Response({'detail': _('The agenda has been sorted.')})
|
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'
|
||||||
|
@ -274,11 +274,13 @@ angular.module('OpenSlidesApp.assignments.site', [
|
|||||||
'User',
|
'User',
|
||||||
'osTableFilter',
|
'osTableFilter',
|
||||||
'osTableSort',
|
'osTableSort',
|
||||||
|
'osTablePagination',
|
||||||
'gettext',
|
'gettext',
|
||||||
'AssignmentPhases',
|
'AssignmentPhases',
|
||||||
'AssignmentPdfExport',
|
'AssignmentPdfExport',
|
||||||
function($scope, ngDialog, AssignmentForm, Assignment, Tag, Agenda, Projector, ProjectionDefault,
|
function($scope, ngDialog, AssignmentForm, Assignment, Tag, Agenda, Projector,
|
||||||
gettextCatalog, User, osTableFilter, osTableSort, gettext, AssignmentPhases, AssignmentPdfExport) {
|
ProjectionDefault, gettextCatalog, User, osTableFilter, osTableSort, osTablePagination,
|
||||||
|
gettext, AssignmentPhases, AssignmentPdfExport) {
|
||||||
$scope.$watch(function () {
|
$scope.$watch(function () {
|
||||||
return Assignment.lastModified();
|
return Assignment.lastModified();
|
||||||
}, function () {
|
}, function () {
|
||||||
@ -354,13 +356,7 @@ angular.module('OpenSlidesApp.assignments.site', [
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Pagination
|
// Pagination
|
||||||
$scope.currentPage = 1;
|
$scope.pagination = osTablePagination.createInstance('AssignmentTablePagination');
|
||||||
$scope.itemsPerPage = 25;
|
|
||||||
$scope.limitBegin = 0;
|
|
||||||
$scope.pageChanged = function() {
|
|
||||||
$scope.limitBegin = ($scope.currentPage - 1) * $scope.itemsPerPage;
|
|
||||||
$scope.gotoTop();
|
|
||||||
};
|
|
||||||
|
|
||||||
// update phase
|
// update phase
|
||||||
$scope.updatePhase = function (assignment, phase_id) {
|
$scope.updatePhase = function (assignment, phase_id) {
|
||||||
|
@ -77,9 +77,10 @@
|
|||||||
{{ assignments.length }} {{ "elections" | translate }}<span ng-if="(assignments|filter:{selected:true}).length > 0">,
|
{{ assignments.length }} {{ "elections" | translate }}<span ng-if="(assignments|filter:{selected:true}).length > 0">,
|
||||||
{{(assignments|filter:{selected:true}).length}} {{ "selected" | translate }}</span>
|
{{(assignments|filter:{selected:true}).length}} {{ "selected" | translate }}</span>
|
||||||
</div>
|
</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">
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -216,7 +217,7 @@
|
|||||||
| MultiselectFilter: filter.multiselectFilters.tag : getItemId.tag
|
| MultiselectFilter: filter.multiselectFilters.tag : getItemId.tag
|
||||||
| MultiselectFilter: filter.multiselectFilters.phase : getItemId.phase
|
| MultiselectFilter: filter.multiselectFilters.phase : getItemId.phase
|
||||||
| orderByEmptyLast: sort.column : sort.reverse)
|
| orderByEmptyLast: sort.column : sort.reverse)
|
||||||
| limitTo : itemsPerPage : limitBegin">
|
| limitTo : pagination.itemsPerPage : pagination.limitBegin">
|
||||||
|
|
||||||
<!-- select column -->
|
<!-- select column -->
|
||||||
<div ng-show="isSelectMode" os-perms="assignments.can_manage" class="col-xs-1 centered">
|
<div ng-show="isSelectMode" os-perms="assignments.can_manage" class="col-xs-1 centered">
|
||||||
@ -332,11 +333,11 @@
|
|||||||
</div> <!-- main table -->
|
</div> <!-- main table -->
|
||||||
|
|
||||||
<ul uib-pagination
|
<ul uib-pagination
|
||||||
ng-show="assignmentsFiltered.length > itemsPerPage"
|
ng-show="assignmentsFiltered.length > pagination.itemsPerPage"
|
||||||
total-items="assignmentsFiltered.length"
|
total-items="assignmentsFiltered.length"
|
||||||
items-per-page="itemsPerPage"
|
items-per-page="pagination.itemsPerPage"
|
||||||
ng-model="currentPage"
|
ng-model="pagination.currentPage"
|
||||||
ng-change="pageChanged()"
|
ng-change="pagination.pageChanged()"
|
||||||
class="pagination-sm"
|
class="pagination-sm"
|
||||||
direction-links="false"
|
direction-links="false"
|
||||||
boundary-links="true"
|
boundary-links="true"
|
||||||
|
@ -76,6 +76,8 @@ class CoreAppConfig(AppConfig):
|
|||||||
|
|
||||||
# Client settings
|
# Client settings
|
||||||
client_settings_keys = [
|
client_settings_keys = [
|
||||||
|
'MOTION_IDENTIFIER_MIN_DIGITS',
|
||||||
|
'MOTION_IDENTIFIER_WITHOUT_BLANKS',
|
||||||
'MOTIONS_ALLOW_AMENDMENTS_OF_AMENDMENTS'
|
'MOTIONS_ALLOW_AMENDMENTS_OF_AMENDMENTS'
|
||||||
]
|
]
|
||||||
client_settings_dict = {}
|
client_settings_dict = {}
|
||||||
|
@ -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
|
/* 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:
|
* also has this dialog open. Use it like in this example in any dialog controller:
|
||||||
var editingStoppedCallback = EditingWarning.editingStarted('editing_name' + item.id);
|
var editingStoppedCallback = EditingWarning.editingStarted('editing_name' + item.id);
|
||||||
|
@ -17,13 +17,14 @@ angular.module('OpenSlidesApp.mediafiles.list', [
|
|||||||
'ngDialog',
|
'ngDialog',
|
||||||
'osTableFilter',
|
'osTableFilter',
|
||||||
'osTableSort',
|
'osTableSort',
|
||||||
|
'osTablePagination',
|
||||||
'ProjectionDefault',
|
'ProjectionDefault',
|
||||||
'Projector',
|
'Projector',
|
||||||
'User',
|
'User',
|
||||||
'Mediafile',
|
'Mediafile',
|
||||||
'MediafileForm',
|
'MediafileForm',
|
||||||
'Logos',
|
'Logos',
|
||||||
function ($http, $scope, gettext, ngDialog, osTableFilter, osTableSort,
|
function ($http, $scope, gettext, ngDialog, osTableFilter, osTableSort, osTablePagination,
|
||||||
ProjectionDefault, Projector, User, Mediafile, MediafileForm, Logos) {
|
ProjectionDefault, Projector, User, Mediafile, MediafileForm, Logos) {
|
||||||
$scope.$watch(function () {
|
$scope.$watch(function () {
|
||||||
return Mediafile.lastModified();
|
return Mediafile.lastModified();
|
||||||
@ -107,13 +108,7 @@ angular.module('OpenSlidesApp.mediafiles.list', [
|
|||||||
];
|
];
|
||||||
|
|
||||||
// pagination
|
// pagination
|
||||||
$scope.currentPage = 1;
|
$scope.pagination = osTablePagination.createInstance('MediafileTablePagination');
|
||||||
$scope.itemsPerPage = 25;
|
|
||||||
$scope.limitBegin = 0;
|
|
||||||
$scope.pageChanged = function() {
|
|
||||||
$scope.limitBegin = ($scope.currentPage - 1) * $scope.itemsPerPage;
|
|
||||||
$scope.gotoTop();
|
|
||||||
};
|
|
||||||
|
|
||||||
// open new/edit dialog
|
// open new/edit dialog
|
||||||
$scope.openDialog = function (mediafile) {
|
$scope.openDialog = function (mediafile) {
|
||||||
|
@ -144,9 +144,10 @@
|
|||||||
{{ mediafiles.length }} {{ "files" | translate }}<span ng-if="(mediafiles|filter:{selected:true}).length > 0">,
|
{{ mediafiles.length }} {{ "files" | translate }}<span ng-if="(mediafiles|filter:{selected:true}).length > 0">,
|
||||||
{{(mediafiles|filter:{selected:true}).length}} {{ "selected" | translate }}</span>
|
{{(mediafiles|filter:{selected:true}).length}} {{ "selected" | translate }}</span>
|
||||||
</div>
|
</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">
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -248,7 +249,7 @@
|
|||||||
| filter: {filetype: (filter.booleanFilters.isPdf.value ? 'application/pdf' : (filter.booleanFilters.isPdf.value === false ? '!application/pdf' : ''))}
|
| filter: {filetype: (filter.booleanFilters.isPdf.value ? 'application/pdf' : (filter.booleanFilters.isPdf.value === false ? '!application/pdf' : ''))}
|
||||||
| filter: {hidden: filter.booleanFilters.isHidden.value}
|
| filter: {hidden: filter.booleanFilters.isHidden.value}
|
||||||
| orderByEmptyLast: sort.column : sort.reverse)
|
| orderByEmptyLast: sort.column : sort.reverse)
|
||||||
| limitTo: itemsPerPage : limitBegin">
|
| limitTo: pagination.itemsPerPage : pagination.limitBegin">
|
||||||
|
|
||||||
<!-- select column -->
|
<!-- select column -->
|
||||||
<div ng-show="isSelectMode" os-perms="mediafiles.can_manage" class="col-xs-1 centered">
|
<div ng-show="isSelectMode" os-perms="mediafiles.can_manage" class="col-xs-1 centered">
|
||||||
@ -368,11 +369,11 @@
|
|||||||
</div><!-- end os-table -->
|
</div><!-- end os-table -->
|
||||||
|
|
||||||
<ul uib-pagination
|
<ul uib-pagination
|
||||||
ng-show="mediafilesFiltered.length > itemsPerPage"
|
ng-show="mediafilesFiltered.length > pagination.itemsPerPage"
|
||||||
total-items="mediafilesFiltered.length"
|
total-items="mediafilesFiltered.length"
|
||||||
items-per-page="itemsPerPage"
|
items-per-page="pagination.itemsPerPage"
|
||||||
ng-model="currentPage"
|
ng-model="pagination.currentPage"
|
||||||
ng-change="pageChanged()"
|
ng-change="pagination.pageChanged()"
|
||||||
class="pagination-sm"
|
class="pagination-sm"
|
||||||
direction-links="false"
|
direction-links="false"
|
||||||
boundary-links="true"
|
boundary-links="true"
|
||||||
|
@ -272,6 +272,14 @@ angular.module('OpenSlidesApp.motions', [
|
|||||||
title += this.getTitle();
|
title += this.getTitle();
|
||||||
return title;
|
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) {
|
getText: function (versionId) {
|
||||||
return this.getVersion(versionId).text;
|
return this.getVersion(versionId).text;
|
||||||
},
|
},
|
||||||
|
@ -16,8 +16,9 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
|
|||||||
'Config',
|
'Config',
|
||||||
'Motion',
|
'Motion',
|
||||||
'MotionComment',
|
'MotionComment',
|
||||||
function($q, operator, gettextCatalog, PDFLayout, PdfMakeConverter, ImageConverter, HTMLValidizer,
|
'OpenSlidesSettings',
|
||||||
Category, Config, Motion, MotionComment) {
|
function($q, operator, gettextCatalog, PDFLayout, PdfMakeConverter, ImageConverter,
|
||||||
|
HTMLValidizer, Category, Config, Motion, MotionComment, OpenSlidesSettings) {
|
||||||
/**
|
/**
|
||||||
* Provides the content as JS objects for Motions in pdfMake context
|
* Provides the content as JS objects for Motions in pdfMake context
|
||||||
* @constructor
|
* @constructor
|
||||||
@ -77,7 +78,8 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (Config.get('motions_export_sequential_number').value) {
|
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);
|
var subtitle = PDFLayout.createSubtitle(subtitleLines);
|
||||||
|
|
||||||
|
@ -962,13 +962,14 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
'ProjectionDefault',
|
'ProjectionDefault',
|
||||||
'osTableFilter',
|
'osTableFilter',
|
||||||
'osTableSort',
|
'osTableSort',
|
||||||
|
'osTablePagination',
|
||||||
'MotionExportForm',
|
'MotionExportForm',
|
||||||
'MotionPdfExport',
|
'MotionPdfExport',
|
||||||
'PersonalNoteManager',
|
'PersonalNoteManager',
|
||||||
function($scope, $state, $http, gettext, gettextCatalog, operator, ngDialog, MotionForm, Motion,
|
function($scope, $state, $http, gettext, gettextCatalog, operator, ngDialog, MotionForm, Motion,
|
||||||
MotionComment, Category, Config, Tag, Workflow, User, Agenda, MotionBlock, Projector,
|
MotionComment, Category, Config, Tag, Workflow, User, Agenda, MotionBlock, Projector,
|
||||||
ProjectionDefault, osTableFilter, osTableSort, MotionExportForm, MotionPdfExport,
|
ProjectionDefault, osTableFilter, osTableSort, osTablePagination, MotionExportForm,
|
||||||
PersonalNoteManager) {
|
MotionPdfExport, PersonalNoteManager) {
|
||||||
Category.bindAll({}, $scope, 'categories');
|
Category.bindAll({}, $scope, 'categories');
|
||||||
MotionBlock.bindAll({}, $scope, 'motionBlocks');
|
MotionBlock.bindAll({}, $scope, 'motionBlocks');
|
||||||
Tag.bindAll({}, $scope, 'tags');
|
Tag.bindAll({}, $scope, 'tags');
|
||||||
@ -1177,14 +1178,7 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
];
|
];
|
||||||
|
|
||||||
// pagination
|
// pagination
|
||||||
$scope.currentPage = 1;
|
$scope.pagination = osTablePagination.createInstance('MotionTablePagination');
|
||||||
$scope.itemsPerPage = 25;
|
|
||||||
$scope.limitBegin = 0;
|
|
||||||
$scope.pageChanged = function() {
|
|
||||||
$scope.limitBegin = ($scope.currentPage - 1) * $scope.itemsPerPage;
|
|
||||||
$scope.gotoTop();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// update state
|
// update state
|
||||||
$scope.updateState = function (motion, state_id) {
|
$scope.updateState = function (motion, state_id) {
|
||||||
@ -1497,6 +1491,7 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
var thisIndex = _.findIndex(motions, function (motion) {
|
var thisIndex = _.findIndex(motions, function (motion) {
|
||||||
return motion.id === $scope.motion.id;
|
return motion.id === $scope.motion.id;
|
||||||
});
|
});
|
||||||
|
this.count = motions.length;
|
||||||
this.nextMotion = thisIndex < motions.length-1 ? motions[thisIndex+1] : _.head(motions);
|
this.nextMotion = thisIndex < motions.length-1 ? motions[thisIndex+1] : _.head(motions);
|
||||||
this.previousMotion = thisIndex > 0 ? motions[thisIndex-1] : _.last(motions);
|
this.previousMotion = thisIndex > 0 ? motions[thisIndex-1] : _.last(motions);
|
||||||
},
|
},
|
||||||
|
@ -78,10 +78,13 @@
|
|||||||
<i class="fa fa-exclamation-triangle"></i>
|
<i class="fa fa-exclamation-triangle"></i>
|
||||||
<translate>This version is not permitted.</translate>
|
<translate>This version is not permitted.</translate>
|
||||||
</span>
|
</span>
|
||||||
|
<small>
|
||||||
|
<translate>Sequential number</translate> {{ motion.getSequentialNumber() }}
|
||||||
|
</small>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<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})"
|
<a ui-sref="motions.motion.detail({id: navigation.previousMotion.id})"
|
||||||
class="btn btn-default btn-sm" ng-disabled="!navigation.previousMotion">
|
class="btn btn-default btn-sm" ng-disabled="!navigation.previousMotion">
|
||||||
<i class="fa fa-angle-double-left"></i>
|
<i class="fa fa-angle-double-left"></i>
|
||||||
|
@ -126,9 +126,10 @@
|
|||||||
{{ motions.length }} {{ "motions" | translate }}<span ng-if="(motions|filter:{selected:true}).length > 0">,
|
{{ motions.length }} {{ "motions" | translate }}<span ng-if="(motions|filter:{selected:true}).length > 0">,
|
||||||
{{(motions|filter:{selected:true}).length}} {{ "selected" | translate }}</span>
|
{{(motions|filter:{selected:true}).length}} {{ "selected" | translate }}</span>
|
||||||
</div>
|
</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">
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -532,7 +533,7 @@
|
|||||||
| filter: {isAmendment: filter.booleanFilters.isAmendment.value}
|
| filter: {isAmendment: filter.booleanFilters.isAmendment.value}
|
||||||
| toArray
|
| toArray
|
||||||
| orderByEmptyLast: sort.column : sort.reverse)
|
| orderByEmptyLast: sort.column : sort.reverse)
|
||||||
| limitTo : itemsPerPage : limitBegin">
|
| limitTo : pagination.itemsPerPage : pagination.limitBegin">
|
||||||
|
|
||||||
<!-- select column -->
|
<!-- select column -->
|
||||||
<div ng-show="isSelectMode" os-perms="motions.can_manage" class="col-xs-1 centered">
|
<div ng-show="isSelectMode" os-perms="motions.can_manage" class="col-xs-1 centered">
|
||||||
@ -788,11 +789,11 @@
|
|||||||
</div> <!-- data row -->
|
</div> <!-- data row -->
|
||||||
|
|
||||||
<ul uib-pagination
|
<ul uib-pagination
|
||||||
ng-show="motionsFiltered.length > itemsPerPage"
|
ng-show="motionsFiltered.length > pagination.itemsPerPage"
|
||||||
total-items="motionsFiltered.length"
|
total-items="motionsFiltered.length"
|
||||||
items-per-page="itemsPerPage"
|
items-per-page="pagination.itemsPerPage"
|
||||||
ng-model="currentPage"
|
ng-model="pagination.currentPage"
|
||||||
ng-change="pageChanged()"
|
ng-change="pagination.pageChanged()"
|
||||||
class="pagination-sm"
|
class="pagination-sm"
|
||||||
direction-links="false"
|
direction-links="false"
|
||||||
boundary-links="true"
|
boundary-links="true"
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import base64
|
|
||||||
import re
|
import re
|
||||||
from typing import Optional # noqa
|
from typing import Optional # noqa
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.staticfiles import finders
|
|
||||||
from django.core.exceptions import ValidationError as DjangoValidationError
|
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||||
from django.db import IntegrityError, transaction
|
from django.db import IntegrityError, transaction
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
@ -24,7 +22,7 @@ from ..utils.rest_api import (
|
|||||||
ValidationError,
|
ValidationError,
|
||||||
detail_route,
|
detail_route,
|
||||||
)
|
)
|
||||||
from ..utils.views import APIView
|
from ..utils.views import BinaryTemplateView
|
||||||
from .access_permissions import (
|
from .access_permissions import (
|
||||||
CategoryAccessPermissions,
|
CategoryAccessPermissions,
|
||||||
MotionAccessPermissions,
|
MotionAccessPermissions,
|
||||||
@ -206,6 +204,16 @@ class MotionViewSet(ModelViewSet):
|
|||||||
# No comments here. Just do nothing.
|
# No comments here. Just do nothing.
|
||||||
pass
|
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.
|
# Validate data and update motion.
|
||||||
serializer = self.get_serializer(
|
serializer = self.get_serializer(
|
||||||
motion,
|
motion,
|
||||||
@ -222,6 +230,11 @@ class MotionViewSet(ModelViewSet):
|
|||||||
updated_motion.supporters.clear()
|
updated_motion.supporters.clear()
|
||||||
updated_motion.write_log([ugettext_noop('All supporters removed')], request.user)
|
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
|
# Send new submitters and supporters via autoupdate because users
|
||||||
# without permission to see users may not have them but can get it now.
|
# without permission to see users may not have them but can get it now.
|
||||||
new_users = list(updated_motion.submitters.all())
|
new_users = list(updated_motion.submitters.all())
|
||||||
@ -707,15 +720,10 @@ class WorkflowViewSet(ModelViewSet):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
# Special API views
|
# Special views
|
||||||
|
|
||||||
class MotionDocxTemplateView(APIView):
|
class MotionDocxTemplateView(BinaryTemplateView):
|
||||||
"""
|
"""
|
||||||
Returns the template for motions docx export
|
Returns the template for motions docx export
|
||||||
"""
|
"""
|
||||||
http_method_names = ['get']
|
template_name = 'templates/docx/motions.docx'
|
||||||
|
|
||||||
def get_context_data(self, **context):
|
|
||||||
with open(finders.find('templates/docx/motions.docx'), "rb") as file:
|
|
||||||
response = base64.b64encode(file.read())
|
|
||||||
return response
|
|
||||||
|
@ -12,6 +12,7 @@ urlpatterns += [
|
|||||||
url(r'^%s(?P<path>.*)$' % settings.MEDIA_URL.lstrip('/'), protected_serve, {'document_root': settings.MEDIA_ROOT}),
|
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'^(?P<url>.*[^/])$', RedirectView.as_view(url='/%(url)s/', permanent=True)),
|
||||||
url(r'^rest/', include(router.urls)),
|
url(r'^rest/', include(router.urls)),
|
||||||
|
url(r'^agenda/', include('openslides.agenda.urls')),
|
||||||
url(r'^motions/', include('openslides.motions.urls')),
|
url(r'^motions/', include('openslides.motions.urls')),
|
||||||
url(r'^users/', include('openslides.users.urls')),
|
url(r'^users/', include('openslides.users.urls')),
|
||||||
|
|
||||||
|
@ -551,12 +551,13 @@ angular.module('OpenSlidesApp.users.site', [
|
|||||||
'UserCsvExport',
|
'UserCsvExport',
|
||||||
'osTableFilter',
|
'osTableFilter',
|
||||||
'osTableSort',
|
'osTableSort',
|
||||||
|
'osTablePagination',
|
||||||
'gettext',
|
'gettext',
|
||||||
'UserPdfExport',
|
'UserPdfExport',
|
||||||
'ErrorMessage',
|
'ErrorMessage',
|
||||||
function($scope, $state, $http, $q, ngDialog, UserForm, User, Group, PasswordGenerator,
|
function($scope, $state, $http, $q, ngDialog, UserForm, User, Group, PasswordGenerator,
|
||||||
Projector, ProjectionDefault, Config, gettextCatalog, UserCsvExport, osTableFilter,
|
Projector, ProjectionDefault, Config, gettextCatalog, UserCsvExport, osTableFilter,
|
||||||
osTableSort, gettext, UserPdfExport, ErrorMessage) {
|
osTableSort, osTablePagination, gettext, UserPdfExport, ErrorMessage) {
|
||||||
$scope.$watch(function () {
|
$scope.$watch(function () {
|
||||||
return User.lastModified();
|
return User.lastModified();
|
||||||
}, function () {
|
}, function () {
|
||||||
@ -649,13 +650,7 @@ angular.module('OpenSlidesApp.users.site', [
|
|||||||
];
|
];
|
||||||
|
|
||||||
// pagination
|
// pagination
|
||||||
$scope.currentPage = 1;
|
$scope.pagination = osTablePagination.createInstance('UserTablePagination');
|
||||||
$scope.itemsPerPage = 25;
|
|
||||||
$scope.limitBegin = 0;
|
|
||||||
$scope.pageChanged = function() {
|
|
||||||
$scope.limitBegin = ($scope.currentPage - 1) * $scope.itemsPerPage;
|
|
||||||
$scope.gotoTop();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Toggle group from user
|
// Toggle group from user
|
||||||
$scope.toggleGroup = function (user, group) {
|
$scope.toggleGroup = function (user, group) {
|
||||||
@ -1121,15 +1116,13 @@ angular.module('OpenSlidesApp.users.site', [
|
|||||||
'User',
|
'User',
|
||||||
'Group',
|
'Group',
|
||||||
'UserCsvExport',
|
'UserCsvExport',
|
||||||
|
'osTablePagination',
|
||||||
'ErrorMessage',
|
'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
|
// import from textarea
|
||||||
$scope.importByLine = function () {
|
$scope.importByLine = function () {
|
||||||
var usernames = $scope.userlist[0].split("\n");
|
var usernames = $scope.userlist[0].split("\n");
|
||||||
// Ignore empty lines.
|
|
||||||
/*usernames = _.filter(usernames, function (name) {
|
|
||||||
return name !== '';
|
|
||||||
});*/
|
|
||||||
var users = _.map(usernames, function (name) {
|
var users = _.map(usernames, function (name) {
|
||||||
// Split each full name in first and last name.
|
// Split each full name in first and last name.
|
||||||
// The last word is set as last name, rest is the first name(s).
|
// The last word is set as last name, rest is the first name(s).
|
||||||
@ -1157,12 +1150,9 @@ angular.module('OpenSlidesApp.users.site', [
|
|||||||
};
|
};
|
||||||
|
|
||||||
// pagination
|
// pagination
|
||||||
$scope.currentPage = 1;
|
$scope.pagination = osTablePagination.createInstance('UserImportTablePagination', 100);
|
||||||
$scope.itemsPerPage = 100;
|
|
||||||
$scope.limitBegin = 0;
|
// Duplicates
|
||||||
$scope.pageChanged = function() {
|
|
||||||
$scope.limitBegin = ($scope.currentPage - 1) * $scope.itemsPerPage;
|
|
||||||
};
|
|
||||||
$scope.duplicateActions = [
|
$scope.duplicateActions = [
|
||||||
gettext('keep original'),
|
gettext('keep original'),
|
||||||
gettext('override new'),
|
gettext('override new'),
|
||||||
|
@ -68,6 +68,12 @@
|
|||||||
|
|
||||||
<div ng-show="users.length">
|
<div ng-show="users.length">
|
||||||
<h3 translate>Preview</h3>
|
<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">
|
<div class="scroll-x-container">
|
||||||
<table class="table table-striped table-bordered table-condensed">
|
<table class="table table-striped table-bordered table-condensed">
|
||||||
<thead>
|
<thead>
|
||||||
@ -106,7 +112,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr ng-repeat="user in users | limitTo : itemsPerPage : limitBegin">
|
<tr ng-repeat="user in users | limitTo : pagination.itemsPerPage : pagination.limitBegin">
|
||||||
<td class="minimum"
|
<td class="minimum"
|
||||||
ng-class="{ 'text-danger': (user.importerror || user.duplicateAction == duplicateActions[0]), 'text-success': user.imported }">
|
ng-class="{ 'text-danger': (user.importerror || user.duplicateAction == duplicateActions[0]), 'text-success': user.imported }">
|
||||||
<span ng-if="user.importerror || user.duplicateAction == duplicateActions[0]">
|
<span ng-if="user.importerror || user.duplicateAction == duplicateActions[0]">
|
||||||
@ -120,7 +126,7 @@
|
|||||||
<i class="fa fa-check-circle fa-lg"></i>
|
<i class="fa fa-check-circle fa-lg"></i>
|
||||||
</span>
|
</span>
|
||||||
<td class="nobr">
|
<td class="nobr">
|
||||||
{{ (currentPage - 1) * itemsPerPage + $index + 1 }}
|
{{ (pagination.currentPage - 1) * pagination.itemsPerPage + $index + 1 }}
|
||||||
<td>
|
<td>
|
||||||
{{ user.title }}
|
{{ user.title }}
|
||||||
<td ng-class="{ 'text-danger': user.name_error }">
|
<td ng-class="{ 'text-danger': user.name_error }">
|
||||||
@ -180,11 +186,11 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<ul uib-pagination
|
<ul uib-pagination
|
||||||
ng-show="users.length > itemsPerPage"
|
ng-show="users.length > pagination.itemsPerPage"
|
||||||
total-items="users.length"
|
total-items="users.length"
|
||||||
items-per-page="itemsPerPage"
|
items-per-page="pagination.itemsPerPage"
|
||||||
ng-model="currentPage"
|
ng-model="pagination.currentPage"
|
||||||
ng-change="pageChanged()"
|
ng-change="pagination.pageChanged()"
|
||||||
class="pagination-sm"
|
class="pagination-sm"
|
||||||
direction-links="false"
|
direction-links="false"
|
||||||
boundary-links="true"
|
boundary-links="true"
|
||||||
|
@ -147,9 +147,10 @@
|
|||||||
{{ users.length }} {{ "participants" | translate }}<span ng-if="(users|filter:{selected:true}).length > 0">,
|
{{ users.length }} {{ "participants" | translate }}<span ng-if="(users|filter:{selected:true}).length > 0">,
|
||||||
{{(users|filter:{selected:true}).length}} {{ "selected" | translate }}</span>
|
{{(users|filter:{selected:true}).length}} {{ "selected" | translate }}</span>
|
||||||
</div>
|
</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">
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -304,7 +305,7 @@
|
|||||||
ng-mouseleave="user.hover=false"
|
ng-mouseleave="user.hover=false"
|
||||||
ng-class="{'projected': user.isProjected().length}"
|
ng-class="{'projected': user.isProjected().length}"
|
||||||
ng-repeat="user in usersFiltered
|
ng-repeat="user in usersFiltered
|
||||||
| limitTo : itemsPerPage : limitBegin">
|
| limitTo : pagination.itemsPerPage : pagination.limitBegin">
|
||||||
|
|
||||||
<!-- select column -->
|
<!-- select column -->
|
||||||
<div ng-show="isSelectMode" os-perms="users.can_manage" class="col-xs-1 centered">
|
<div ng-show="isSelectMode" os-perms="users.can_manage" class="col-xs-1 centered">
|
||||||
@ -451,11 +452,11 @@
|
|||||||
</div><!-- end os-table -->
|
</div><!-- end os-table -->
|
||||||
|
|
||||||
<ul uib-pagination
|
<ul uib-pagination
|
||||||
ng-show="usersFiltered.length > itemsPerPage"
|
ng-show="usersFiltered.length > pagination.itemsPerPage"
|
||||||
total-items="usersFiltered.length"
|
total-items="usersFiltered.length"
|
||||||
items-per-page="itemsPerPage"
|
items-per-page="pagination.itemsPerPage"
|
||||||
ng-model="currentPage"
|
ng-model="pagination.currentPage"
|
||||||
ng-change="pageChanged()"
|
ng-change="pagination.pageChanged()"
|
||||||
class="pagination-sm"
|
class="pagination-sm"
|
||||||
direction-links="false"
|
direction-links="false"
|
||||||
boundary-links="true"
|
boundary-links="true"
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import base64
|
||||||
from typing import Any, Dict, List # noqa
|
from typing import Any, Dict, List # noqa
|
||||||
|
|
||||||
from django.contrib.staticfiles import finders
|
from django.contrib.staticfiles import finders
|
||||||
@ -67,8 +68,20 @@ class TemplateView(View):
|
|||||||
raise ImproperlyConfigured("'template_name' is not provided.")
|
raise ImproperlyConfigured("'template_name' is not provided.")
|
||||||
|
|
||||||
if self.template_name not in self.state:
|
if self.template_name not in self.state:
|
||||||
|
self.state[self.template_name] = self.load_template()
|
||||||
|
|
||||||
|
def load_template(self) -> str:
|
||||||
with open(finders.find(self.template_name)) as template:
|
with open(finders.find(self.template_name)) as template:
|
||||||
self.state[self.template_name] = template.read()
|
return template.read()
|
||||||
|
|
||||||
def get(self, *args: Any, **kwargs: Any) -> HttpResponse:
|
def get(self, *args: Any, **kwargs: Any) -> HttpResponse:
|
||||||
return HttpResponse(self.state[self.template_name])
|
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())
|
||||||
|
@ -8,11 +8,12 @@ from rest_framework import status
|
|||||||
from rest_framework.test import APIClient
|
from rest_framework.test import APIClient
|
||||||
|
|
||||||
from openslides.core.config import config
|
from openslides.core.config import config
|
||||||
from openslides.core.models import Tag
|
from openslides.core.models import ConfigStore, Tag
|
||||||
from openslides.motions.models import (
|
from openslides.motions.models import (
|
||||||
Category,
|
Category,
|
||||||
Motion,
|
Motion,
|
||||||
MotionBlock,
|
MotionBlock,
|
||||||
|
MotionLog,
|
||||||
State,
|
State,
|
||||||
Workflow,
|
Workflow,
|
||||||
)
|
)
|
||||||
@ -604,6 +605,35 @@ class UpdateMotion(TestCase):
|
|||||||
motion = Motion.objects.get()
|
motion = Motion.objects.get()
|
||||||
self.assertEqual(motion.versions.count(), 1)
|
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):
|
class DeleteMotion(TestCase):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user