Merge remote-tracking branch 'upstream/master' into OpenSlides-3
This commit is contained in:
commit
d11d2844b8
@ -7,6 +7,9 @@ https://openslides.org/
|
||||
Version 2.3 (unreleased)
|
||||
========================
|
||||
|
||||
Agenda:
|
||||
- New item type 'hidden'. New visibilty filter in agenda [#3790].
|
||||
|
||||
Motions:
|
||||
- New feature to scroll the projector to a specific line [#3748].
|
||||
- New possibility to sort submitters [#3647].
|
||||
@ -17,11 +20,13 @@ Motions:
|
||||
- New table of contents with page numbers and categories in PDF [#3766].
|
||||
- New teporal field "modified final version" where the final version can
|
||||
be edited [#3781].
|
||||
- New config to show amendments also in motions table [#3792]
|
||||
|
||||
Core:
|
||||
- Python 3.4 is not supported anymore [#3777].
|
||||
- Support Python 3.7.
|
||||
- Support Python 3.7 [#3786].
|
||||
- Updated pdfMake to 0.1.37 [#3766].
|
||||
- Updated Django to 2.1 [#3777, #3786].
|
||||
|
||||
|
||||
Version 2.2 (2018-06-06)
|
||||
|
@ -23,7 +23,8 @@ class ItemAccessPermissions(BaseAccessPermissions):
|
||||
|
||||
return ItemSerializer
|
||||
|
||||
# TODO: In the following method we use full_data['is_hidden'] but this can be out of date.
|
||||
# TODO: In the following method we use full_data['is_hidden'] and
|
||||
# full_data['is_internal'] but this can be out of date.
|
||||
|
||||
def get_restricted_data(
|
||||
self,
|
||||
@ -33,8 +34,10 @@ class ItemAccessPermissions(BaseAccessPermissions):
|
||||
Returns the restricted serialized data for the instance prepared
|
||||
for the user.
|
||||
|
||||
Hidden items can only be seen by managers with can_manage permission.
|
||||
|
||||
We remove comments for non admins/managers and a lot of fields of
|
||||
hidden items for users without permission to see hidden items.
|
||||
internal items for users without permission to see internal items.
|
||||
"""
|
||||
def filtered_data(full_data, blocked_keys):
|
||||
"""
|
||||
@ -45,38 +48,45 @@ class ItemAccessPermissions(BaseAccessPermissions):
|
||||
|
||||
# Parse data.
|
||||
if has_perm(user, 'agenda.can_see'):
|
||||
if has_perm(user, 'agenda.can_manage') and has_perm(user, 'agenda.can_see_hidden_items'):
|
||||
if has_perm(user, 'agenda.can_manage') and has_perm(user, 'agenda.can_see_internal_items'):
|
||||
# Managers with special permission can see everything.
|
||||
data = full_data
|
||||
elif has_perm(user, 'agenda.can_see_hidden_items'):
|
||||
# Non managers with special permission can see everything but comments.
|
||||
elif has_perm(user, 'agenda.can_see_internal_items'):
|
||||
# Non managers with special permission can see everything but
|
||||
# comments and hidden items.
|
||||
data = [full for full in full_data if not full['is_hidden']] # filter hidden items
|
||||
blocked_keys = ('comment',)
|
||||
data = [filtered_data(full, blocked_keys) for full in full_data]
|
||||
data = [filtered_data(full, blocked_keys) for full in data] # remove blocked_keys
|
||||
else:
|
||||
# Users without special permissin for hidden items.
|
||||
# Users without special permission for internal items.
|
||||
|
||||
# In hidden case managers and non managers see only some fields
|
||||
# so that list of speakers is provided regardless.
|
||||
blocked_keys_hidden_case = set(full_data[0].keys()) - set((
|
||||
# In internal and hidden case managers and non managers see only some fields
|
||||
# so that list of speakers is provided regardless. Hidden items can only be seen by managers.
|
||||
blocked_keys_internal_hidden_case = set(full_data[0].keys()) - set((
|
||||
'id',
|
||||
'title',
|
||||
'speakers',
|
||||
'speaker_list_closed',
|
||||
'content_object'))
|
||||
|
||||
# In non hidden case managers see everything and non managers see
|
||||
# In non internal case managers see everything and non managers see
|
||||
# everything but comments.
|
||||
if has_perm(user, 'agenda.can_manage'):
|
||||
blocked_keys_non_hidden_case = [] # type: Iterable[str]
|
||||
blocked_keys_non_internal_hidden_case = [] # type: Iterable[str]
|
||||
can_see_hidden = True
|
||||
else:
|
||||
blocked_keys_non_hidden_case = ('comment',)
|
||||
blocked_keys_non_internal_hidden_case = ('comment',)
|
||||
can_see_hidden = False
|
||||
|
||||
data = []
|
||||
for full in full_data:
|
||||
if full['is_hidden']:
|
||||
data.append(filtered_data(full, blocked_keys_hidden_case))
|
||||
else:
|
||||
data.append(filtered_data(full, blocked_keys_non_hidden_case))
|
||||
if full['is_hidden'] and can_see_hidden:
|
||||
# Same filtering for internal and hidden items
|
||||
data.append(filtered_data(full, blocked_keys_internal_hidden_case))
|
||||
if full['is_internal']:
|
||||
data.append(filtered_data(full, blocked_keys_internal_hidden_case))
|
||||
else: # agenda item
|
||||
data.append(filtered_data(full, blocked_keys_non_internal_hidden_case))
|
||||
else:
|
||||
data = []
|
||||
|
||||
|
@ -59,6 +59,19 @@ def get_config_variables():
|
||||
group='Agenda',
|
||||
subgroup='General')
|
||||
|
||||
yield ConfigVariable(
|
||||
name='agenda_new_items_default_visibility',
|
||||
default_value='2',
|
||||
input_type='choice',
|
||||
choices=(
|
||||
{'value': '1', 'display_name': 'Public item'},
|
||||
{'value': '2', 'display_name': 'Internal item'},
|
||||
{'value': '3', 'display_name': 'Hidden item'}),
|
||||
label='Default visibility for new agenda items',
|
||||
weight=227,
|
||||
group='Agenda',
|
||||
subgroup='General')
|
||||
|
||||
# List of speakers
|
||||
|
||||
yield ConfigVariable(
|
||||
|
53
openslides/agenda/migrations/0005_auto_20180815_1109.py
Normal file
53
openslides/agenda/migrations/0005_auto_20180815_1109.py
Normal file
@ -0,0 +1,53 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.8 on 2018-08-15 09:09
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.db import migrations, models
|
||||
|
||||
from openslides.utils.migrations import \
|
||||
add_permission_to_groups_based_on_existing_permission
|
||||
|
||||
|
||||
def delete_old_can_see_hidden_permission(apps, schema_editor):
|
||||
perm = Permission.objects.filter(codename='can_see_hidden_items')
|
||||
if len(perm):
|
||||
perm = perm.delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agenda', '0004_speaker_marked'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='item',
|
||||
options={
|
||||
'default_permissions': (),
|
||||
'permissions': (
|
||||
('can_see', 'Can see agenda'),
|
||||
('can_manage', 'Can manage agenda'),
|
||||
('can_manage_list_of_speakers', 'Can manage list of speakers'),
|
||||
('can_see_internal_items', 'Can see internal items and time scheduling of agenda')
|
||||
)
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='item',
|
||||
name='type',
|
||||
field=models.IntegerField(
|
||||
choices=[
|
||||
(1, 'Agenda item'),
|
||||
(2, 'Internal item'),
|
||||
(3, 'Hidden item')
|
||||
],
|
||||
default=3
|
||||
),
|
||||
),
|
||||
migrations.RunPython(add_permission_to_groups_based_on_existing_permission(
|
||||
'can_see_hidden_items', 'item', 'agenda', 'can_see_internal_items', 'Can see internal items and time scheduling of agenda'
|
||||
)),
|
||||
migrations.RunPython(delete_old_can_see_hidden_permission),
|
||||
]
|
@ -33,58 +33,43 @@ class ItemManager(models.Manager):
|
||||
"""
|
||||
return self.get_queryset().prefetch_related('speakers', 'content_object')
|
||||
|
||||
def get_only_agenda_items(self):
|
||||
def get_only_non_public_items(self):
|
||||
"""
|
||||
Generator, which yields only agenda items. Skips hidden items.
|
||||
Generator, which yields only internal and hidden items, that means only items
|
||||
which type is INTERNAL_ITEM or HIDDEN_ITEM or which are children of hidden items.
|
||||
"""
|
||||
# Do not execute item.is_hidden() because this would create a lot of db queries
|
||||
root_items, item_children = self.get_root_and_children(only_agenda_items=True)
|
||||
# Do not execute non-hidden items because this would create a lot of db queries
|
||||
root_items, item_children = self.get_root_and_children(only_item_type=None)
|
||||
|
||||
def yield_items(items):
|
||||
def yield_items(items, parent_is_not_public=False):
|
||||
"""
|
||||
Generator that yields a list of items and their children.
|
||||
"""
|
||||
for item in items:
|
||||
yield item
|
||||
yield from yield_items(item_children[item.pk])
|
||||
|
||||
yield from yield_items(root_items)
|
||||
|
||||
def get_only_hidden_items(self):
|
||||
"""
|
||||
Generator, which yields only hidden items, that means only items
|
||||
which type is HIDDEN_ITEM or which are children of hidden items.
|
||||
"""
|
||||
# Do not execute item.is_hidden() because this would create a lot of db queries
|
||||
root_items, item_children = self.get_root_and_children(only_agenda_items=False)
|
||||
|
||||
def yield_items(items, parent_is_hidden=False):
|
||||
"""
|
||||
Generator that yields a list of items and their children.
|
||||
"""
|
||||
for item in items:
|
||||
if parent_is_hidden or item.type == item.HIDDEN_ITEM:
|
||||
item_is_hidden = True
|
||||
if parent_is_not_public or item.type in (item.INTERNAL_ITEM, item.HIDDEN_ITEM):
|
||||
item_is_not_public = True
|
||||
yield item
|
||||
else:
|
||||
item_is_hidden = False
|
||||
yield from yield_items(item_children[item.pk], parent_is_hidden=item_is_hidden)
|
||||
item_is_not_public = False
|
||||
yield from yield_items(
|
||||
item_children[item.pk],
|
||||
parent_is_not_public=item_is_not_public)
|
||||
|
||||
yield from yield_items(root_items)
|
||||
|
||||
def get_root_and_children(self, only_agenda_items=False):
|
||||
def get_root_and_children(self, only_item_type=None):
|
||||
"""
|
||||
Returns a list with all root items and a dictonary where the key is an
|
||||
item pk and the value is a list with all children of the item.
|
||||
|
||||
If only_agenda_items is True, the tree hides items with type
|
||||
HIDDEN_ITEM and all of their children.
|
||||
If only_item_type is given, the tree hides items with other types and
|
||||
all of their children.
|
||||
"""
|
||||
queryset = self.order_by('weight')
|
||||
item_children = defaultdict(list) # type: Dict[int, List[Item]]
|
||||
root_items = []
|
||||
for item in queryset:
|
||||
if only_agenda_items and item.type == item.HIDDEN_ITEM:
|
||||
if only_item_type is not None and item.type != only_item_type:
|
||||
continue
|
||||
if item.parent_id is not None:
|
||||
item_children[item.parent_id].append(item)
|
||||
@ -92,19 +77,19 @@ class ItemManager(models.Manager):
|
||||
root_items.append(item)
|
||||
return root_items, item_children
|
||||
|
||||
def get_tree(self, only_agenda_items=False, include_content=False):
|
||||
def get_tree(self, only_item_type=None, include_content=False):
|
||||
"""
|
||||
Generator that yields dictonaries. Each dictonary has two keys, id
|
||||
and children, where id is the id of one agenda item and children is a
|
||||
generator that yields dictonaries like the one discribed.
|
||||
|
||||
If only_agenda_items is True, the tree hides items with type
|
||||
HIDDEN_ITEM and all of their children.
|
||||
If only_item_type is given, the tree hides items with other types and
|
||||
all of their children.
|
||||
|
||||
If include_content is True, the yielded dictonaries have no key 'id'
|
||||
but a key 'item' with the entire object.
|
||||
"""
|
||||
root_items, item_children = self.get_root_and_children(only_agenda_items=only_agenda_items)
|
||||
root_items, item_children = self.get_root_and_children(only_item_type=only_item_type)
|
||||
|
||||
def get_children(items):
|
||||
"""
|
||||
@ -184,10 +169,10 @@ class ItemManager(models.Manager):
|
||||
walk_tree(tree_element['children'], item_number)
|
||||
|
||||
# Start numbering visable agenda items.
|
||||
walk_tree(self.get_tree(only_agenda_items=True, include_content=True))
|
||||
walk_tree(self.get_tree(only_item_type=Item.AGENDA_ITEM, include_content=True))
|
||||
|
||||
# Reset number of hidden items.
|
||||
for item in self.get_only_hidden_items():
|
||||
for item in self.get_only_non_public_items():
|
||||
item.item_number = ''
|
||||
item.save()
|
||||
|
||||
@ -200,10 +185,12 @@ class Item(RESTModelMixin, models.Model):
|
||||
objects = ItemManager()
|
||||
|
||||
AGENDA_ITEM = 1
|
||||
HIDDEN_ITEM = 2
|
||||
INTERNAL_ITEM = 2
|
||||
HIDDEN_ITEM = 3
|
||||
|
||||
ITEM_TYPE = (
|
||||
(AGENDA_ITEM, ugettext_lazy('Agenda item')),
|
||||
(INTERNAL_ITEM, ugettext_lazy('Internal item')),
|
||||
(HIDDEN_ITEM, ugettext_lazy('Hidden item')))
|
||||
|
||||
item_number = models.CharField(blank=True, max_length=255)
|
||||
@ -281,7 +268,7 @@ class Item(RESTModelMixin, models.Model):
|
||||
('can_see', 'Can see agenda'),
|
||||
('can_manage', 'Can manage agenda'),
|
||||
('can_manage_list_of_speakers', 'Can manage list of speakers'),
|
||||
('can_see_hidden_items', 'Can see hidden items and time scheduling of agenda'))
|
||||
('can_see_internal_items', 'Can see internal items and time scheduling of agenda'))
|
||||
unique_together = ('content_type', 'object_id')
|
||||
|
||||
def __str__(self):
|
||||
@ -320,6 +307,16 @@ class Item(RESTModelMixin, models.Model):
|
||||
raise NotImplementedError('You have to provide a get_agenda_list_view_title '
|
||||
'method on your related model.')
|
||||
|
||||
def is_internal(self):
|
||||
"""
|
||||
Returns True if the type of this object itself is a internal item or any
|
||||
of its ancestors has such a type.
|
||||
|
||||
Attention! This executes one query for each ancestor of the item.
|
||||
"""
|
||||
return (self.type == self.INTERNAL_ITEM or
|
||||
(self.parent is not None and self.parent.is_internal()))
|
||||
|
||||
def is_hidden(self):
|
||||
"""
|
||||
Returns True if the type of this object itself is a hidden item or any
|
||||
|
@ -49,6 +49,7 @@ class ItemSerializer(ModelSerializer):
|
||||
'comment',
|
||||
'closed',
|
||||
'type',
|
||||
'is_internal',
|
||||
'is_hidden',
|
||||
'duration',
|
||||
'speakers',
|
||||
|
@ -55,13 +55,13 @@ def listen_to_related_object_post_delete(sender, instance, **kwargs):
|
||||
def get_permission_change_data(sender, permissions, **kwargs):
|
||||
"""
|
||||
Yields all necessary collections if 'agenda.can_see' or
|
||||
'agenda.can_see_hidden_items' permissions changes.
|
||||
'agenda.can_see_internal_items' permissions changes.
|
||||
"""
|
||||
agenda_app = apps.get_app_config(app_label='agenda')
|
||||
for permission in permissions:
|
||||
# There could be only one 'agenda.can_see' and then we want to return data.
|
||||
if (permission.content_type.app_label == agenda_app.label
|
||||
and permission.codename in ('can_see', 'can_see_hidden_items')):
|
||||
and permission.codename in ('can_see', 'can_see_internal_items')):
|
||||
yield from agenda_app.get_startup_elements()
|
||||
break
|
||||
|
||||
|
@ -39,6 +39,11 @@ angular.module('OpenSlidesApp.agenda', ['OpenSlidesApp.users'])
|
||||
name: name,
|
||||
useClass: jsDataModel,
|
||||
verboseName: gettext('Agenda'),
|
||||
computed: {
|
||||
is_public: function () {
|
||||
return !this.is_internal && !this.is_hidden;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getResourceName: function () {
|
||||
return name;
|
||||
|
@ -17,10 +17,9 @@ angular.module('OpenSlidesApp.agenda.pdf', ['OpenSlidesApp.core.pdf'])
|
||||
// generate the item list with all subitems
|
||||
var createItemList = function() {
|
||||
var agenda_items = [];
|
||||
angular.forEach(items, function (item) {
|
||||
if (item.is_hidden === false) {
|
||||
|
||||
var itemIndent = item.parentCount * 20;
|
||||
_.forEach(items, function (item) {
|
||||
if (item.is_public) {
|
||||
var itemIndent = item.parentCount * 15;
|
||||
|
||||
var itemStyle;
|
||||
if (item.parentCount === 0) {
|
||||
@ -29,13 +28,6 @@ angular.module('OpenSlidesApp.agenda.pdf', ['OpenSlidesApp.core.pdf'])
|
||||
itemStyle = 'listChild';
|
||||
}
|
||||
|
||||
var itemNumberWidth;
|
||||
if (item.item_number === "") {
|
||||
itemNumberWidth = 0;
|
||||
} else {
|
||||
itemNumberWidth = 60;
|
||||
}
|
||||
|
||||
var agendaJsonString = {
|
||||
style: itemStyle,
|
||||
columns: [
|
||||
@ -44,7 +36,7 @@ angular.module('OpenSlidesApp.agenda.pdf', ['OpenSlidesApp.core.pdf'])
|
||||
text: ''
|
||||
},
|
||||
{
|
||||
width: itemNumberWidth,
|
||||
width: 60,
|
||||
text: item.item_number
|
||||
},
|
||||
{
|
||||
|
@ -84,12 +84,14 @@ angular.module('OpenSlidesApp.agenda.projector', ['OpenSlidesApp.agenda'])
|
||||
Config.lastModified('agenda_hide_internal_items_on_projector');
|
||||
}, function () {
|
||||
if ($scope.element.id) {
|
||||
// remove hidden items
|
||||
items = _.filter(Agenda.getAll(), function (item) {
|
||||
return !item.is_hidden;
|
||||
});
|
||||
if (Config.get('agenda_hide_internal_items_on_projector').value) {
|
||||
items = _.filter(Agenda.getAll(), function (item) {
|
||||
return item.type === 1;
|
||||
items = _.filter(items, function (item) {
|
||||
return item.is_public;
|
||||
});
|
||||
} else {
|
||||
items = Agenda.getAll();
|
||||
}
|
||||
var tree = AgendaTree.getTree(items);
|
||||
|
||||
@ -115,7 +117,7 @@ angular.module('OpenSlidesApp.agenda.projector', ['OpenSlidesApp.agenda'])
|
||||
});
|
||||
} else if ($scope.element.tree) {
|
||||
items = _.filter(Agenda.getAll(), function (item) {
|
||||
return item.type === 1;
|
||||
return item.is_public;
|
||||
});
|
||||
$scope.tree = AgendaTree.getTree(items);
|
||||
} else {
|
||||
@ -124,7 +126,7 @@ angular.module('OpenSlidesApp.agenda.projector', ['OpenSlidesApp.agenda'])
|
||||
orderBy: 'weight'
|
||||
});
|
||||
items = _.filter(items, function (item) {
|
||||
return item.type === 1;
|
||||
return item.is_public;
|
||||
});
|
||||
$scope.tree = AgendaTree.getTree(items);
|
||||
}
|
||||
|
@ -85,6 +85,30 @@ angular.module('OpenSlidesApp.agenda.site', [
|
||||
}
|
||||
])
|
||||
|
||||
.factory('ShowAsAgendaItemField', [
|
||||
'operator',
|
||||
'gettext',
|
||||
'gettextCatalog',
|
||||
function (operator, gettext, gettextCatalog) {
|
||||
return function (managePermission) {
|
||||
return {
|
||||
key: 'agenda_type',
|
||||
type: 'select-single',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Agenda visibility'),
|
||||
options: [
|
||||
{type: 1, displayName: gettext('Public item')},
|
||||
{type: 2, displayName: gettext('Internal item')},
|
||||
{type: 3, displayName: gettext('Hidden item')}
|
||||
],
|
||||
ngOptions: 'type.type as (type.displayName | translate) for type in to.options',
|
||||
},
|
||||
hide: !(operator.hasPerms(managePermission) && operator.hasPerms('agenda.can_manage'))
|
||||
};
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.controller('ItemListCtrl', [
|
||||
'$scope',
|
||||
'$filter',
|
||||
@ -109,6 +133,11 @@ angular.module('OpenSlidesApp.agenda.site', [
|
||||
function($scope, $filter, $http, $state, DS, operator, ngDialog, Agenda, TopicForm,
|
||||
AgendaTree, Projector, ProjectionDefault, gettextCatalog, gettext, osTableFilter,
|
||||
osTablePagination, AgendaCsvExport, AgendaPdfExport, AgendaDocxExport, ErrorMessage) {
|
||||
|
||||
$scope.AGENDA_ITEM = 1;
|
||||
$scope.INTERNAL_ITEM = 2;
|
||||
$scope.HIDDEN_ITEM = 3;
|
||||
|
||||
// Bind agenda tree to the scope
|
||||
$scope.$watch(function () {
|
||||
return Agenda.lastModified();
|
||||
@ -143,16 +172,31 @@ angular.module('OpenSlidesApp.agenda.site', [
|
||||
$scope.filter.booleanFilters = {
|
||||
closed: {
|
||||
value: undefined,
|
||||
defaultValue: undefined,
|
||||
displayName: gettext('Closed items'),
|
||||
choiceYes: gettext('Closed items'),
|
||||
choiceNo: gettext('Open items'),
|
||||
},
|
||||
is_hidden: {
|
||||
value: undefined,
|
||||
displayName: gettext('Internal items'),
|
||||
// The next filters are just on-off, so no undefined there
|
||||
is_public: {
|
||||
value: true,
|
||||
defaultValue: true,
|
||||
choiceYes: gettext('Public items'),
|
||||
choiceNo: gettext('No public items'),
|
||||
},
|
||||
is_internal: {
|
||||
value: true,
|
||||
defaultValue: true,
|
||||
choiceYes: gettext('Internal items'),
|
||||
choiceNo: gettext('No internal items'),
|
||||
permission: 'agenda.can_see_hidden_items',
|
||||
permission: 'agenda.can_see_internal_items',
|
||||
},
|
||||
is_hidden: {
|
||||
value: false,
|
||||
defaultValue: false,
|
||||
choiceYes: gettext('Hidden items'),
|
||||
choiceNo: gettext('No hidden items'),
|
||||
permission: 'agenda.can_manage',
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -160,10 +204,23 @@ angular.module('OpenSlidesApp.agenda.site', [
|
||||
$scope.filter.propertyFunctionList = [
|
||||
function (item) {return item.getListViewTitle();},
|
||||
];
|
||||
$scope.filter.propertyDict = {
|
||||
'speakers' : function (speaker) {
|
||||
return '';
|
||||
},
|
||||
$scope.areFiltersSet = function () {
|
||||
return ($scope.areVisibilityFiltersSet() ||
|
||||
$scope.filter.booleanFilters.closed.value !== $scope.filter.booleanFilters.closed.defaultValue);
|
||||
};
|
||||
$scope.areVisibilityFiltersSet = function () {
|
||||
return ($scope.filter.booleanFilters.is_public.value !== $scope.filter.booleanFilters.is_public.defaultValue ||
|
||||
$scope.filter.booleanFilters.is_internal.value !== $scope.filter.booleanFilters.is_internal.defaultValue ||
|
||||
$scope.filter.booleanFilters.is_hidden.value !== $scope.filter.booleanFilters.is_hidden.defaultValue);
|
||||
|
||||
};
|
||||
$scope.resetFilters = function (isSelectMode) {
|
||||
if (!isSelectMode) {
|
||||
_.forEach($scope.filter.booleanFilters, function (filter) {
|
||||
filter.value = filter.defaultValue;
|
||||
});
|
||||
$scope.filter.save();
|
||||
}
|
||||
};
|
||||
|
||||
// Expand all items during searching.
|
||||
@ -333,9 +390,31 @@ angular.module('OpenSlidesApp.agenda.site', [
|
||||
});
|
||||
}
|
||||
};
|
||||
// set type for selected items
|
||||
$scope.setTypeMultiple = function (type) {
|
||||
_.forEach($scope.items, function (item) {
|
||||
if (item.selected) {
|
||||
item.type = type;
|
||||
$scope.save(item);
|
||||
}
|
||||
});
|
||||
$scope.isSelectMode = false;
|
||||
$scope.uncheckAll();
|
||||
};
|
||||
// set closed for selected items
|
||||
$scope.setStateMultiple = function (closed) {
|
||||
_.forEach($scope.items, function (item) {
|
||||
if (item.selected) {
|
||||
item.closed = closed;
|
||||
$scope.save(item);
|
||||
}
|
||||
});
|
||||
$scope.isSelectMode = false;
|
||||
$scope.uncheckAll();
|
||||
};
|
||||
// delete selected items
|
||||
$scope.deleteMultiple = function () {
|
||||
angular.forEach($scope.items, function (item) {
|
||||
_.forEach($scope.items, function (item) {
|
||||
if (item.selected) {
|
||||
DS.destroy(item.content_object.collection, item.content_object.id);
|
||||
}
|
||||
@ -421,6 +500,20 @@ angular.module('OpenSlidesApp.agenda.site', [
|
||||
}
|
||||
])
|
||||
|
||||
// Filter for the item type that filters the selected items by type. filters
|
||||
// are the boolean filters from the ui.
|
||||
.filter('itemTypeFilter', [
|
||||
function () {
|
||||
return function (items, filters) {
|
||||
return _.filter(items, function (item) {
|
||||
return (item.is_public && filters.is_public.value) ||
|
||||
(item.is_internal && filters.is_internal.value) ||
|
||||
(item.is_hidden && filters.is_hidden.value);
|
||||
});
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
// filter to hide collapsed items. Items has to be a flat tree.
|
||||
.filter('collapsedItemFilter', [
|
||||
function () {
|
||||
@ -789,6 +882,8 @@ angular.module('OpenSlidesApp.agenda.site', [
|
||||
gettext('Couple countdown with the list of speakers');
|
||||
gettext('[Begin speech] starts the countdown, [End speech] stops the ' +
|
||||
'countdown.');
|
||||
gettext('Agenda visibility');
|
||||
gettext('Default visibility for new agenda items');
|
||||
}
|
||||
]);
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
<!-- import -->
|
||||
<span os-perms="agenda.can_manage">
|
||||
<a ui-sref="topics.topic.import"
|
||||
os-perms="agenda.can_see_hidden_items"
|
||||
os-perms="agenda.can_see_internal_items"
|
||||
class="btn btn-default btn-sm">
|
||||
<i class="fa fa-download fa-lg"></i>
|
||||
<translate>Import</translate>
|
||||
@ -137,9 +137,45 @@
|
||||
</div>
|
||||
</div>
|
||||
<div uib-collapse="!isSelectMode" class="row spacer">
|
||||
<div class="col-sm-12 text-left">
|
||||
<div class="col-sm-12 text-left form-inline" os-perms="agenda.can_manage">
|
||||
<!-- actions -->
|
||||
<select ng-model="selectedAction" class="form-control input-sm">
|
||||
<option value="" translate>--- Select action ---</option>
|
||||
<option value="delete" translate>Delete</option>
|
||||
<option value="setType" translate>Set visibility</option>
|
||||
<option value="setState" translate>Set state</option>
|
||||
</select>
|
||||
<!-- Type (visibility) -->
|
||||
<select ng-show="selectedAction == 'setType'" ng-model="selectedType" class="form-control input-sm">
|
||||
<option value="" translate>--- Select visibility ---</option>
|
||||
<option value="{{ AGENDA_ITEM }}" translate>
|
||||
Public
|
||||
</option>
|
||||
<option value="{{ INTERNAL_ITEM }}" translate>
|
||||
Internal
|
||||
</option>
|
||||
<option value="{{ HIDDEN_ITEM }}" translate>
|
||||
Hidden
|
||||
</option>
|
||||
</select>
|
||||
<!-- set type button -->
|
||||
<a ng-show="selectedAction == 'setType' && selectedType"
|
||||
ng-click="setTypeMultiple(selectedType)" class="btn btn-default btn-sm">
|
||||
<translate>Set visibility</translate>
|
||||
</a>
|
||||
<!-- set state buttons -->
|
||||
<a ng-show="selectedAction == 'setState'"
|
||||
ng-click="setStateMultiple(true)" class="btn btn-default btn-sm">
|
||||
<i class="fa fa-check-square-o"></i>
|
||||
<translate>Done</translate>
|
||||
</a>
|
||||
<a ng-show="selectedAction == 'setState'"
|
||||
ng-click="setStateMultiple(false)" class="btn btn-default btn-sm">
|
||||
<i class="fa fa-square-o"></i>
|
||||
<translate>Open</translate>
|
||||
</a>
|
||||
<!-- delete button -->
|
||||
<a ng-show="isSelectMode" os-perms="agenda.can_manage"
|
||||
<a ng-if="selectedAction === 'delete'"
|
||||
ng-bootbox-confirm="{{ 'Are you sure you want to delete all selected agenda items?' | translate }}"
|
||||
ng-bootbox-confirm-action="deleteMultiple()"
|
||||
class="btn btn-default btn-sm btn-danger">
|
||||
@ -151,10 +187,10 @@
|
||||
|
||||
<div class="spacer-top-lg italic row">
|
||||
<div class="col-md-6">
|
||||
<span os-perms="agenda.can_see_hidden_items">{{ itemsFiltered.length }} /</span>
|
||||
{{ items.length }} {{ "items" | translate }}<span ng-if="(items|filter:{selected:true}).length > 0">,
|
||||
<span os-perms="agenda.can_see_internal_items">{{ itemsFiltered.length }} /</span>
|
||||
{{ (items|filter:{is_hidden:false}).length }} {{ "items" | translate }}<span ng-if="(items|filter:{selected:true}).length > 0">,
|
||||
{{(items|filter:{selected:true}).length}} {{ "selected" | translate }}</span>
|
||||
<span os-perms="agenda.can_see_hidden_items" class="optional">
|
||||
<span os-perms="agenda.can_see_internal_items" class="optional">
|
||||
<span ng-if="sumDurations() > 0">·
|
||||
<translate>Duration</translate>:
|
||||
{{ sumDurations() | osMinutesToTime }}h
|
||||
@ -196,50 +232,67 @@
|
||||
<div class="col-xs-11 main-header">
|
||||
<span class="form-inline text-right pull-right">
|
||||
<!-- clear all filters -->
|
||||
<span class="sort-spacer pointer" ng-click="filter.reset(isSelectMode)"
|
||||
ng-if="filter.areFiltersSet()" ng-disabled="isSelectMode"
|
||||
<span class="sort-spacer pointer" ng-click="resetFilters(isSelectMode)"
|
||||
ng-if="areFiltersSet()" ng-disabled="isSelectMode"
|
||||
ng-class="{'disabled': isSelectMode}">
|
||||
<i class="fa fa-window-close"></i>
|
||||
<translate>Filter</translate>
|
||||
</span>
|
||||
<!-- boolean Filters (combined!) -->
|
||||
<span uib-dropdown>
|
||||
<span class="sort-spacer pointer" id="dropdownItems" uib-dropdown-toggle
|
||||
ng-class="{'bold': (filter.booleanFilters.closed.value !== undefined) ||
|
||||
(filter.booleanFilters.is_hidden.value !== undefined),
|
||||
'disabled': isSelectMode}"
|
||||
ng-disabled="isSelectMode">
|
||||
<translate>Items</translate>
|
||||
<span class="caret"></span>
|
||||
</span>
|
||||
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownItems">
|
||||
<li>
|
||||
<a href ng-click="filter.booleanFilters.closed.value = (filter.booleanFilters.closed.value ? undefined : true); filter.save();">
|
||||
<i class="fa" ng-class="{'fa-check': filter.booleanFilters.closed.value === true}"></i>
|
||||
{{ filter.booleanFilters.closed.choiceYes | translate }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href ng-click="filter.booleanFilters.closed.value = (filter.booleanFilters.closed.value === false) ? undefined : false; filter.save();">
|
||||
<i class="fa" ng-class="{'fa-check': filter.booleanFilters.closed.value === false}"></i>
|
||||
{{ filter.booleanFilters.closed.choiceNo | translate }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li>
|
||||
<a href ng-click="filter.booleanFilters.is_hidden.value = (filter.booleanFilters.is_hidden.value ? undefined : true); filter.save();">
|
||||
<i class="fa" ng-class="{'fa-check': filter.booleanFilters.is_hidden.value === true}"></i>
|
||||
{{ filter.booleanFilters.is_hidden.choiceYes | translate }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href ng-click="filter.booleanFilters.is_hidden.value = (filter.booleanFilters.is_hidden.value === false) ? undefined : false; filter.save();">
|
||||
<i class="fa" ng-class="{'fa-check': filter.booleanFilters.is_hidden.value === false}"></i>
|
||||
{{ filter.booleanFilters.is_hidden.choiceNo | translate }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
<!-- boolean Filters -->
|
||||
<!-- State -->
|
||||
<span uib-dropdown>
|
||||
<span class="sort-spacer pointer" id="dropdownItems" uib-dropdown-toggle
|
||||
ng-class="{'bold': filter.booleanFilters.closed.value !== filter.booleanFilters.closed.defaultValue,
|
||||
'disabled': isSelectMode}"
|
||||
ng-disabled="isSelectMode">
|
||||
<translate>State</translate>
|
||||
<span class="caret"></span>
|
||||
</span>
|
||||
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownItems">
|
||||
<li>
|
||||
<a href ng-click="filter.booleanFilters.closed.value = (filter.booleanFilters.closed.value ? undefined : true); filter.save();">
|
||||
<i class="fa" ng-class="{'fa-check': filter.booleanFilters.closed.value === true}"></i>
|
||||
{{ filter.booleanFilters.closed.choiceYes | translate }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href ng-click="filter.booleanFilters.closed.value = (filter.booleanFilters.closed.value === false) ? undefined : false; filter.save();">
|
||||
<i class="fa" ng-class="{'fa-check': filter.booleanFilters.closed.value === false}"></i>
|
||||
{{ filter.booleanFilters.closed.choiceNo | translate }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
<!-- Visibility -->
|
||||
<span uib-dropdown>
|
||||
<span class="sort-spacer pointer" id="dropdownItems" uib-dropdown-toggle
|
||||
ng-class="{'bold': areVisibilityFiltersSet(),
|
||||
'disabled': isSelectMode}"
|
||||
ng-disabled="isSelectMode">
|
||||
<translate>Visibility</translate>
|
||||
<span class="caret"></span>
|
||||
</span>
|
||||
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownItems">
|
||||
<li>
|
||||
<a href ng-click="filter.booleanFilters.is_public.value = !filter.booleanFilters.is_public.value; filter.save();">
|
||||
<i class="fa" ng-class="{'fa-check': filter.booleanFilters.is_public.value}"></i>
|
||||
<translate>Public items</translate>
|
||||
</a>
|
||||
</li>
|
||||
<li os-perms="agenda.can_see_internal_items">
|
||||
<a href ng-click="filter.booleanFilters.is_internal.value = !filter.booleanFilters.is_internal.value; filter.save();">
|
||||
<i class="fa" ng-class="{'fa-check': filter.booleanFilters.is_internal.value}"></i>
|
||||
<translate>Internal items</translate>
|
||||
</a>
|
||||
</li>
|
||||
<li os-perms="agenda.can_see_internal_items agenda.can_manage">
|
||||
<a href ng-click="filter.booleanFilters.is_hidden.value = !filter.booleanFilters.is_hidden.value; filter.save();">
|
||||
<i class="fa" ng-class="{'fa-check': filter.booleanFilters.is_hidden.value}"></i>
|
||||
<translate>Hidden items</translate>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
<!-- search field -->
|
||||
<span class="form-group">
|
||||
<span class="input-group">
|
||||
@ -254,9 +307,9 @@
|
||||
<span>
|
||||
<!-- for all boolean Filters -->
|
||||
<span ng-repeat="(name, booleanFilter) in filter.booleanFilters"
|
||||
ng-hide="booleanFilter.value === undefined"
|
||||
ng-hide="booleanFilter.value === booleanFilter.defaultValue"
|
||||
class="pointer spacer-left-lg"
|
||||
ng-click="booleanFilter.value = undefined; filter.save();"
|
||||
ng-click="booleanFilter.value = booleanFilter.defaultValue; filter.save();"
|
||||
ng-class="{'disabled': isSelectMode}">
|
||||
<span class="nobr">
|
||||
<i class="fa fa-times-circle"></i>
|
||||
@ -273,9 +326,9 @@
|
||||
ng-class="{'projected': item.isProjected().length,
|
||||
'related-projected': item.isRelatedProjected().length}"
|
||||
ng-repeat="item in itemsFiltered = (itemsSearched = (items
|
||||
| osFilter: filter.filterString : filter.getObjectQueryString)
|
||||
| filter: {closed: filter.booleanFilters.closed.value}
|
||||
| filter: {is_hidden: filter.booleanFilters.is_hidden.value})
|
||||
| osFilter : filter.filterString : filter.getObjectQueryString)
|
||||
| filter : {closed: filter.booleanFilters.closed.value}
|
||||
| itemTypeFilter : filter.booleanFilters)
|
||||
| collapsedItemFilter
|
||||
| limitTo : pagination.itemsPerPage : pagination.limitBegin">
|
||||
|
||||
@ -361,6 +414,63 @@
|
||||
<div class="col-xs-4 content" ng-style="{'width': isSelectMode ? 'calc(50% - 120px)' : 'calc(50% - 70px)'}">
|
||||
<div style="width: 60%;" class="optional">
|
||||
<small>
|
||||
<!-- type dropdown for managers -->
|
||||
<div os-perms="agenda.can_manage">
|
||||
<div ng-mouseover="typeHover=true" ng-mouseleave="typeHover=false">
|
||||
<span uib-dropdown>
|
||||
<span id="dropdownType{{ item.id }}" class="pointer"
|
||||
uib-dropdown-toggle uib-tooltip="{{ 'Change visibility' | translate }}"
|
||||
tooltip-class="nobr">
|
||||
<span ng-if="item.is_public && item.hover">
|
||||
<i class="fa fa-eye"></i>
|
||||
<translate>Public</translate>
|
||||
</span>
|
||||
<span ng-if="item.is_internal">
|
||||
<i class="fa fa-eye"></i>
|
||||
<translate>Internal</translate>
|
||||
</span>
|
||||
<span ng-if="item.is_hidden">
|
||||
<i class="fa fa-eye"></i>
|
||||
<translate>Hidden</translate>
|
||||
</span>
|
||||
<i class="fa fa-cog fa-lg spacer-left" ng-show="typeHover"></i>
|
||||
</span>
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownType{{ item.id }}">
|
||||
<li>
|
||||
<a href ng-click="item.type = AGENDA_ITEM; save(item);" translate>
|
||||
Public item
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href ng-click="item.type = INTERNAL_ITEM; save(item);" translate>
|
||||
Internal item
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href ng-click="item.type = HIDDEN_ITEM; save(item);" translate>
|
||||
Hidden item
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- type for non-managers -->
|
||||
<div os-perms="!agenda.can_manage">
|
||||
<span ng-if="item.is_public && item.hover">
|
||||
<i class="fa fa-eye"></i>
|
||||
<translate>Public</translate>
|
||||
</span>
|
||||
<span ng-if="item.is_internal">
|
||||
<i class="fa fa-eye"></i>
|
||||
<translate>Internal</translate>
|
||||
</span>
|
||||
<span ng-if="item.is_hidden">
|
||||
<i class="fa fa-eye"></i>
|
||||
<translate>Hidden</translate>
|
||||
</span>
|
||||
</div>
|
||||
<!-- Duration -->
|
||||
<div ng-style="{'visibility': (item.duration || item.hover) ? 'visible' : 'hidden'}">
|
||||
<div class="popover-wrapper" os-perms="agenda.can_manage">
|
||||
<i class="fa fa-clock-o"></i>
|
||||
@ -374,7 +484,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<div os-perms="!agenda.can_manage">
|
||||
<div os-perms="agenda.can_see_hidden_items">
|
||||
<div os-perms="agenda.can_see_internal_items">
|
||||
<span ng-if="item.duration">
|
||||
<i class="fa fa-clock-o"></i> {{ item.duration | osMinutesToTime }}
|
||||
<translate translate-comment="'h' means time in hours">h</translate>
|
||||
@ -382,6 +492,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Comment -->
|
||||
<div ng-style="{'visibility': (item.comment || item.hover) ? 'visible' : 'hidden'}">
|
||||
<div class="popover-wrapper" os-perms="agenda.can_manage">
|
||||
<i class="fa fa-info-circle"></i>
|
||||
@ -391,7 +502,8 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-style="{'visibility': ((item.type == 1) && item.hover) ? 'visible' : 'hidden'}" os-perms="agenda.can_manage">
|
||||
<!-- Number -->
|
||||
<div ng-style="{'visibility': ((item.is_public) && item.hover) ? 'visible' : 'hidden'}" os-perms="agenda.can_manage">
|
||||
<div class="popover-wrapper" os-perms="agenda.can_manage">
|
||||
<i class="fa fa-info-circle"></i>
|
||||
<span editable-text="item.item_number" onaftersave="save(item)">
|
||||
@ -403,22 +515,14 @@
|
||||
<template-hook hook-name="agendaListAdditionalContentColumn"></template-hook>
|
||||
</small>
|
||||
</div>
|
||||
<div style="width: 40%; overflow: hidden;" class="pull-right">
|
||||
<div style="width: 40%;" class="pull-right">
|
||||
<div os-perms="agenda.can_manage">
|
||||
<div class="pointer nobr" ng-click="item.type = (item.type == 1) ? 2 : 1; save(item);" ng-show="item.hover || item.is_hidden">
|
||||
<i class="fa" ng-class="item.is_hidden ? 'fa-check-square-o' : 'fa-square-o'"></i>
|
||||
<span class="spacer-left" translate>Internal</span>
|
||||
</div>
|
||||
<div class="pointer nobr" ng-click="item.closed = !item.closed; save(item);" ng-show="item.hover || item.closed">
|
||||
<i class="fa" ng-class="item.closed ? 'fa-check-square-o' : 'fa-square-o'"></i>
|
||||
<span class="spacer-left" translate>Done</span>
|
||||
</div>
|
||||
</div>
|
||||
<div os-perms="!agenda.can_manage" >
|
||||
<div ng-show="item.is_hidden">
|
||||
<i class="fa fa-ban"></i>
|
||||
<span class="spacer-left" translate>Internal</span>
|
||||
</div>
|
||||
<div ng-show="item.closed">
|
||||
<i class="fa fa-check-square-o"></i>
|
||||
<span class="spacer-left" translate>Done</span>
|
||||
|
@ -46,7 +46,7 @@ class ItemViewSet(ListModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericV
|
||||
# done in the specific method. See below.
|
||||
elif self.action in ('partial_update', 'update'):
|
||||
result = (has_perm(self.request.user, 'agenda.can_see') and
|
||||
has_perm(self.request.user, 'agenda.can_see_hidden_items') and
|
||||
has_perm(self.request.user, 'agenda.can_see_internal_items') and
|
||||
has_perm(self.request.user, 'agenda.can_manage'))
|
||||
elif self.action in ('speak', 'sort_speakers'):
|
||||
result = (has_perm(self.request.user, 'agenda.can_see') and
|
||||
@ -62,13 +62,14 @@ class ItemViewSet(ListModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericV
|
||||
"""
|
||||
Customized view endpoint to update all children if, the item type has changed.
|
||||
"""
|
||||
hidden = self.get_object().type == Item.HIDDEN_ITEM
|
||||
old_type = self.get_object().type
|
||||
|
||||
result = super().update(*args, **kwargs)
|
||||
|
||||
# update all children, if the item type has changed
|
||||
item = self.get_object()
|
||||
if hidden != (item.type == Item.HIDDEN_ITEM):
|
||||
|
||||
if old_type != item.type:
|
||||
items_to_update = []
|
||||
|
||||
# rekursively add children to items_to_update
|
||||
|
@ -200,7 +200,7 @@ class AssignmentFullSerializer(ModelSerializer):
|
||||
"""
|
||||
assignment_related_users = AssignmentRelatedUserSerializer(many=True, read_only=True)
|
||||
polls = AssignmentAllPollSerializer(many=True, read_only=True)
|
||||
agenda_type = IntegerField(write_only=True, required=False, min_value=1, max_value=2)
|
||||
agenda_type = IntegerField(write_only=True, required=False, min_value=1, max_value=3)
|
||||
agenda_parent_id = IntegerField(write_only=True, required=False, min_value=1)
|
||||
|
||||
class Meta:
|
||||
|
@ -102,7 +102,8 @@ angular.module('OpenSlidesApp.assignments.site', [
|
||||
'Assignment',
|
||||
'Agenda',
|
||||
'AgendaTree',
|
||||
function (gettextCatalog, operator, Editor, Mediafile, Tag, Assignment, Agenda, AgendaTree) {
|
||||
'ShowAsAgendaItemField',
|
||||
function (gettextCatalog, operator, Editor, Mediafile, Tag, Assignment, Agenda, AgendaTree, ShowAsAgendaItemField) {
|
||||
return {
|
||||
// ngDialog for assignment form
|
||||
getDialog: function (assignment) {
|
||||
@ -159,15 +160,7 @@ angular.module('OpenSlidesApp.assignments.site', [
|
||||
|
||||
// show as agenda item + parent item
|
||||
if (isCreateForm) {
|
||||
formFields.push({
|
||||
key: 'showAsAgendaItem',
|
||||
type: 'checkbox',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Show as agenda item'),
|
||||
description: gettextCatalog.getString('If deactivated the election appears as internal item on agenda.')
|
||||
},
|
||||
hide: !(operator.hasPerms('assignments.can_manage') && operator.hasPerms('agenda.can_manage'))
|
||||
});
|
||||
formFields.push(ShowAsAgendaItemField('assignments.can_manage'));
|
||||
formFields.push({
|
||||
key: 'agenda_parent_id',
|
||||
type: 'select-single',
|
||||
@ -623,17 +616,18 @@ angular.module('OpenSlidesApp.assignments.site', [
|
||||
'Assignment',
|
||||
'AssignmentForm',
|
||||
'Agenda',
|
||||
'Config',
|
||||
'ErrorMessage',
|
||||
function($scope, $state, Assignment, AssignmentForm, Agenda, ErrorMessage) {
|
||||
$scope.model = {};
|
||||
function($scope, $state, Assignment, AssignmentForm, Agenda, Config, ErrorMessage) {
|
||||
$scope.model = {
|
||||
agenda_type: parseInt(Config.get('agenda_new_items_default_visibility').value),
|
||||
};
|
||||
// set default value for open posts form field
|
||||
$scope.model.open_posts = 1;
|
||||
// get all form fields
|
||||
$scope.formFields = AssignmentForm.getFormFields(true);
|
||||
// save assignment
|
||||
$scope.save = function(assignment, gotoDetailView) {
|
||||
assignment.agenda_type = assignment.showAsAgendaItem ? 1 : 2;
|
||||
// The attribute assignment.agenda_parent_id is set by the form, see form definition.
|
||||
Assignment.create(assignment).then(
|
||||
function (success) {
|
||||
if (gotoDetailView) {
|
||||
|
@ -372,7 +372,7 @@ angular.module('OpenSlidesApp.core.pdf', [])
|
||||
},
|
||||
tocCategoryTitle: {
|
||||
fontSize: 12,
|
||||
margin: [0,0,0,0],
|
||||
margin: [0,0,0,4],
|
||||
bold: true,
|
||||
},
|
||||
tocCategorySection: {
|
||||
@ -960,6 +960,9 @@ angular.module('OpenSlidesApp.core.pdf', [])
|
||||
// not be empty otherwise it will be removed and the empty line is not displayed
|
||||
if (element.nextSibling && element.nextSibling.nodeName === 'BR') {
|
||||
currentParagraph.text.push(create('text', ' '));
|
||||
} else if (isInsideAList(element) && lineNumberMode === 'none') {
|
||||
// Put a spacer there, if there is one BR in a list
|
||||
alreadyConverted.push(create('text', ' '));
|
||||
}
|
||||
currentParagraph.lineHeight = 1.25;
|
||||
alreadyConverted.push(currentParagraph);
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -157,6 +157,15 @@ def get_config_variables():
|
||||
group='Motions',
|
||||
subgroup='Amendments')
|
||||
|
||||
yield ConfigVariable(
|
||||
name='motions_amendments_main_table',
|
||||
default_value=False,
|
||||
input_type='boolean',
|
||||
label='Show amendments together with motions',
|
||||
weight=337,
|
||||
group='Motions',
|
||||
subgroup='Amendments')
|
||||
|
||||
yield ConfigVariable(
|
||||
name='motions_amendments_prefix',
|
||||
default_value='-',
|
||||
|
@ -50,7 +50,7 @@ class MotionBlockSerializer(ModelSerializer):
|
||||
"""
|
||||
Serializer for motion.models.Category objects.
|
||||
"""
|
||||
agenda_type = IntegerField(write_only=True, required=False, min_value=1, max_value=2)
|
||||
agenda_type = IntegerField(write_only=True, required=False, min_value=1, max_value=3)
|
||||
agenda_parent_id = IntegerField(write_only=True, required=False, min_value=1)
|
||||
|
||||
class Meta:
|
||||
@ -382,7 +382,7 @@ class MotionSerializer(ModelSerializer):
|
||||
required=False,
|
||||
validators=[validate_workflow_field],
|
||||
write_only=True)
|
||||
agenda_type = IntegerField(write_only=True, required=False, min_value=1, max_value=2)
|
||||
agenda_type = IntegerField(write_only=True, required=False, min_value=1, max_value=3)
|
||||
agenda_parent_id = IntegerField(write_only=True, required=False, min_value=1)
|
||||
submitters = SubmitterSerializer(many=True, read_only=True)
|
||||
|
||||
|
@ -111,6 +111,17 @@
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
.motion-text.line-numbers-none li > br {
|
||||
margin-top: 8px;
|
||||
content: " ";
|
||||
display: block;
|
||||
&.os-line-break {
|
||||
margin-top: 0;
|
||||
content: "";
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin addChangeRecommendationBtn {
|
||||
cursor: pointer;
|
||||
content: "\f067";
|
||||
|
@ -53,7 +53,8 @@ angular.module('OpenSlidesApp.motions.motionBlock', [])
|
||||
'gettextCatalog',
|
||||
'Agenda',
|
||||
'AgendaTree',
|
||||
function ($http, operator, gettextCatalog, Agenda, AgendaTree) {
|
||||
'ShowAsAgendaItemField',
|
||||
function ($http, operator, gettextCatalog, Agenda, AgendaTree, ShowAsAgendaItemField) {
|
||||
return {
|
||||
// Get ngDialog configuration.
|
||||
getDialog: function (motionBlock) {
|
||||
@ -82,15 +83,7 @@ angular.module('OpenSlidesApp.motions.motionBlock', [])
|
||||
|
||||
// show as agenda item + parent item
|
||||
if (isCreateForm) {
|
||||
formFields.push({
|
||||
key: 'showAsAgendaItem',
|
||||
type: 'checkbox',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Show as agenda item'),
|
||||
description: gettextCatalog.getString('If deactivated it appears as internal item on agenda.')
|
||||
},
|
||||
hide: !(operator.hasPerms('motions.can_manage') && operator.hasPerms('agenda.can_manage'))
|
||||
});
|
||||
formFields.push(ShowAsAgendaItemField('motions.can_manage'));
|
||||
formFields.push({
|
||||
key: 'agenda_parent_id',
|
||||
type: 'select-single',
|
||||
@ -197,18 +190,18 @@ angular.module('OpenSlidesApp.motions.motionBlock', [])
|
||||
'$scope',
|
||||
'MotionBlock',
|
||||
'MotionBlockForm',
|
||||
function($scope, MotionBlock, MotionBlockForm) {
|
||||
'Config',
|
||||
function($scope, MotionBlock, MotionBlockForm, Config) {
|
||||
// Prepare form.
|
||||
$scope.model = {};
|
||||
$scope.model.showAsAgendaItem = true;
|
||||
$scope.model = {
|
||||
agenda_type: parseInt(Config.get('agenda_new_items_default_visibility').value),
|
||||
};
|
||||
|
||||
// Get all form fields.
|
||||
$scope.formFields = MotionBlockForm.getFormFields(true);
|
||||
|
||||
// Save form.
|
||||
$scope.save = function (motionBlock) {
|
||||
motionBlock.agenda_type = motionBlock.showAsAgendaItem ? 1 : 2;
|
||||
// The attribute motionBlock.agenda_parent_id is set by the form, see form definition.
|
||||
MotionBlock.create(motionBlock).then(
|
||||
function (success) {
|
||||
$scope.closeThisDialog();
|
||||
|
@ -442,8 +442,9 @@ angular.module('OpenSlidesApp.motions.site', [
|
||||
'Workflow',
|
||||
'Agenda',
|
||||
'AgendaTree',
|
||||
function ($filter, gettextCatalog, operator, Editor, MotionComment, Category,
|
||||
Config, Mediafile, MotionBlock, Tag, User, Workflow, Agenda, AgendaTree) {
|
||||
'ShowAsAgendaItemField',
|
||||
function ($filter, gettextCatalog, operator, Editor, MotionComment, Category, Config,
|
||||
Mediafile, MotionBlock, Tag, User, Workflow, Agenda, AgendaTree, ShowAsAgendaItemField) {
|
||||
return {
|
||||
// ngDialog for motion form
|
||||
// If motion is given and not null, we're editing an already existing motion
|
||||
@ -467,6 +468,10 @@ angular.module('OpenSlidesApp.motions.site', [
|
||||
},
|
||||
// angular-formly fields for motion form
|
||||
getFormFields: function (isCreateForm, isParagraphBasedAmendment) {
|
||||
if (!isParagraphBasedAmendment) { // catch null and undefined. Angular formy doesn't like this.
|
||||
isParagraphBasedAmendment = false;
|
||||
}
|
||||
|
||||
var workflows = Workflow.getAll();
|
||||
var images = Mediafile.getAllImages();
|
||||
var formFields = [];
|
||||
@ -540,15 +545,7 @@ angular.module('OpenSlidesApp.motions.site', [
|
||||
|
||||
// show as agenda item + parent item
|
||||
if (isCreateForm) {
|
||||
formFields.push({
|
||||
key: 'showAsAgendaItem',
|
||||
type: 'checkbox',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Show as agenda item'),
|
||||
description: gettextCatalog.getString('If deactivated the motion appears as internal item on agenda.')
|
||||
},
|
||||
hide: !(operator.hasPerms('motions.can_manage') && operator.hasPerms('agenda.can_manage'))
|
||||
});
|
||||
formFields.push(ShowAsAgendaItemField('motions.can_manage'));
|
||||
formFields.push({
|
||||
key: 'agenda_parent_id',
|
||||
type: 'select-single',
|
||||
@ -1215,7 +1212,14 @@ angular.module('OpenSlidesApp.motions.site', [
|
||||
return Motion.lastModified();
|
||||
}, function () {
|
||||
// get all main motions and order by identifier (after custom ordering)
|
||||
$scope.motions = _.orderBy(Motion.filter({parent_id: undefined}), ['identifier']);
|
||||
var motions;
|
||||
if (Config.get('motions_amendments_main_table').value) {
|
||||
motions = Motion.getAll();
|
||||
} else {
|
||||
motions = Motion.filter({parent_id: undefined});
|
||||
}
|
||||
|
||||
$scope.motions = _.orderBy(motions, ['identifier']);
|
||||
_.forEach($scope.motions, function (motion) {
|
||||
MotionComment.populateFields(motion);
|
||||
motion.personalNote = PersonalNoteManager.getNote(motion);
|
||||
@ -2223,7 +2227,10 @@ angular.module('OpenSlidesApp.motions.site', [
|
||||
User.bindAll({}, $scope, 'users');
|
||||
Workflow.bindAll({}, $scope, 'workflows');
|
||||
|
||||
$scope.model = {};
|
||||
$scope.model = {
|
||||
agenda_type: parseInt(Config.get('agenda_new_items_default_visibility').value),
|
||||
};
|
||||
|
||||
$scope.alert = {};
|
||||
|
||||
// Check whether this is a new amendment.
|
||||
@ -2279,8 +2286,6 @@ angular.module('OpenSlidesApp.motions.site', [
|
||||
|
||||
// save motion
|
||||
$scope.save = function (motion, gotoDetailView) {
|
||||
motion.agenda_type = motion.showAsAgendaItem ? 1 : 2;
|
||||
|
||||
if (isAmendment && motion.paragraphNo !== undefined) {
|
||||
var orig_paragraphs = parentMotion.getTextParagraphs(parentMotion.active_version, false);
|
||||
motion.amendment_paragraphs = orig_paragraphs.map(function (_, idx) {
|
||||
@ -3235,6 +3240,7 @@ angular.module('OpenSlidesApp.motions.site', [
|
||||
// subgroup Amendments
|
||||
gettext('Amendments');
|
||||
gettext('Activate amendments');
|
||||
gettext('Show amendments together with motions');
|
||||
gettext('Prefix for the identifier for amendments');
|
||||
gettext('Apply text for new amendments');
|
||||
gettext('The title of the motion is always applied.');
|
||||
|
@ -31,6 +31,12 @@
|
||||
<span ng-class="{'hiddenDiv': !selectHover}" uib-dropdown>
|
||||
<i class="fa fa-cog pointer" uib-dropdown-toggle id="selectDropdown"></i>
|
||||
<ul class="dropdown-menu" aria-labelledby="selectDropdown">
|
||||
<li>
|
||||
<a href ng-click="selectLeadMotion(null)" translate>
|
||||
All motions
|
||||
</a>
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li ng-repeat="motion in leadMotions">
|
||||
<a href ng-click="selectLeadMotion(motion)">
|
||||
<span ng-if="motion.identifier">
|
||||
@ -39,12 +45,6 @@
|
||||
{{ motion.getTitle() | limitTo: 35 }}{{ motion.getTitle().length > 35 ? '...' : '' }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="divider" ng-if="amendment.state.getNextStates().length"></li>
|
||||
<li>
|
||||
<a href ng-click="selectLeadMotion(null)" translate>
|
||||
All motions
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
</h3>
|
||||
@ -357,10 +357,11 @@
|
||||
{{ getTextPreview(amendment.getText(), 400) }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- last column -->
|
||||
<div class="col-xs-4 content" ng-style="{'width': isSelectMode ? 'calc(33.33% - 120px)' : 'calc(33.33% - 70px)'}">
|
||||
<div style="width: 90%;">
|
||||
<div ng-repeat="(id, field) in noSpecialCommentsFields">
|
||||
<div class="nobr">
|
||||
<div class="nobr" style="overflow: hidden;">
|
||||
<i class="fa pointer spacer-right" ng-class="field[amendment.id] ? 'fa-caret-down' : 'fa-caret-right'"
|
||||
ng-click="field[amendment.id] = !field[amendment.id]"
|
||||
ng-if="isTextExpandable(amendment.comments[id], 30)"></i>
|
||||
|
@ -79,12 +79,7 @@
|
||||
<span class="change-title" ng-if="motion.isAllowed('update') && !title_change_recommendation"></span>
|
||||
</span>
|
||||
|
||||
<a ui-sref="motions.motion.detail({id: motion.getParentMotion().id })" ng-if="motion.isAmendment">
|
||||
{{ motion.getTitleWithChanges(viewChangeRecommendations.mode) }}
|
||||
</a>
|
||||
<span ng-if="!motion.isAmendment">
|
||||
{{ motion.getTitleWithChanges(viewChangeRecommendations.mode) }}
|
||||
</span>
|
||||
{{ motion.getTitleWithChanges(viewChangeRecommendations.mode) }}
|
||||
|
||||
<i class="fa pointer" ng-class="motion.personalNote.star ? 'fa-star' : 'fa-star-o'"
|
||||
ng-if="operator.user"
|
||||
@ -102,6 +97,14 @@
|
||||
</span>
|
||||
<small>
|
||||
<translate>Sequential number</translate> {{ motion.getSequentialNumber() }}
|
||||
|
||||
<span ng-if="motion.isAmendment">
|
||||
·
|
||||
<a ui-sref="motions.motion.detail({id: motion.getParentMotion().id })" ng-if="motion.isAmendment">
|
||||
<translate>Amendment to</translate>
|
||||
{{ motion.getParentMotion().identifier || motion.getParentMotion().getTitle() }}
|
||||
</a>
|
||||
</span>
|
||||
</small>
|
||||
</h2>
|
||||
</div>
|
||||
@ -552,9 +555,6 @@
|
||||
</translate>
|
||||
</div>
|
||||
|
||||
<div ng-bind-html="motion.getTextByMode('agreed', version, highlight) | trusted"
|
||||
class="motion-text motion-text-changed line-numbers-{{ lineNumberMode }}"></div>
|
||||
|
||||
<div style="text-align: right;" ng-if="(change_recommendations | filter:{motion_version_id:version}:true).length > 0">
|
||||
<button class="btn btn-default"
|
||||
ng-bootbox-confirm="{{ 'Do you want to copy the final version to the modified final version field?' | translate }}"
|
||||
@ -572,6 +572,9 @@
|
||||
<translate>New version on these changes</translate>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div ng-bind-html="motion.getTextByMode('agreed', version, highlight) | trusted"
|
||||
class="motion-text motion-text-changed line-numbers-{{ lineNumberMode }}"></div>
|
||||
</div>
|
||||
|
||||
<!-- modified agreed view -->
|
||||
|
@ -5,7 +5,8 @@
|
||||
<i class="fa fa-plus fa-lg"></i>
|
||||
<translate>New</translate>
|
||||
</a>
|
||||
<a ui-sref="motions.motion.allamendments" class="btn btn-default btn-sm">
|
||||
<a ui-sref="motions.motion.allamendments" ng-if="config('motions_amendments_enabled')"
|
||||
class="btn btn-default btn-sm">
|
||||
<i class="fa fa-book fa-lg"></i>
|
||||
<translate>Amendments</translate>
|
||||
</a>
|
||||
|
@ -8,7 +8,7 @@ class TopicSerializer(ModelSerializer):
|
||||
"""
|
||||
Serializer for core.models.Topic objects.
|
||||
"""
|
||||
agenda_type = IntegerField(write_only=True, required=False, min_value=1, max_value=2)
|
||||
agenda_type = IntegerField(write_only=True, required=False, min_value=1, max_value=3)
|
||||
agenda_parent_id = IntegerField(write_only=True, required=False, min_value=1)
|
||||
agenda_comment = CharField(write_only=True, required=False, allow_blank=True)
|
||||
agenda_duration = IntegerField(write_only=True, required=False, min_value=1)
|
||||
|
@ -68,7 +68,9 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics', 'OpenSlides
|
||||
'Mediafile',
|
||||
'Agenda',
|
||||
'AgendaTree',
|
||||
function ($filter, gettextCatalog, operator, Editor, Mediafile, Agenda, AgendaTree) {
|
||||
'ShowAsAgendaItemField',
|
||||
function ($filter, gettextCatalog, operator, Editor, Mediafile, Agenda,
|
||||
AgendaTree, ShowAsAgendaItemField) {
|
||||
return {
|
||||
// ngDialog for topic form
|
||||
getDialog: function (topic) {
|
||||
@ -120,15 +122,7 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics', 'OpenSlides
|
||||
|
||||
// show as agenda item + parent item
|
||||
if (isCreateForm) {
|
||||
formFields.push({
|
||||
key: 'showAsAgendaItem',
|
||||
type: 'checkbox',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Show as agenda item'),
|
||||
description: gettextCatalog.getString('If deactivated it appears as internal item on agenda.')
|
||||
},
|
||||
hide: !operator.hasPerms('agenda.can_manage')
|
||||
});
|
||||
formFields.push(ShowAsAgendaItemField('agenda.can_manage'));
|
||||
formFields.push({
|
||||
key: 'agenda_parent_id',
|
||||
type: 'select-single',
|
||||
@ -187,17 +181,16 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics', 'OpenSlides
|
||||
'Topic',
|
||||
'TopicForm',
|
||||
'Agenda',
|
||||
'Config',
|
||||
'ErrorMessage',
|
||||
function($scope, $state, Topic, TopicForm, Agenda, ErrorMessage) {
|
||||
$scope.topic = {};
|
||||
$scope.model = {};
|
||||
$scope.model.showAsAgendaItem = true;
|
||||
function($scope, $state, Topic, TopicForm, Agenda, Config, ErrorMessage) {
|
||||
$scope.model = {
|
||||
agenda_type: parseInt(Config.get('agenda_new_items_default_visibility').value),
|
||||
};
|
||||
// get all form fields
|
||||
$scope.formFields = TopicForm.getFormFields(true);
|
||||
// save form
|
||||
$scope.save = function (topic) {
|
||||
topic.agenda_type = topic.showAsAgendaItem ? 1 : 2;
|
||||
// The attribute topic.agenda_parent_id is set by the form, see form definition.
|
||||
Topic.create(topic).then(
|
||||
function (success) {
|
||||
$scope.closeThisDialog();
|
||||
|
@ -33,7 +33,7 @@ def create_builtin_groups_and_admin(**kwargs):
|
||||
'agenda.can_manage',
|
||||
'agenda.can_manage_list_of_speakers',
|
||||
'agenda.can_see',
|
||||
'agenda.can_see_hidden_items',
|
||||
'agenda.can_see_internal_items',
|
||||
'assignments.can_manage',
|
||||
'assignments.can_nominate_other',
|
||||
'assignments.can_nominate_self',
|
||||
@ -74,7 +74,7 @@ def create_builtin_groups_and_admin(**kwargs):
|
||||
# Default (pk 1)
|
||||
base_permissions = (
|
||||
permission_dict['agenda.can_see'],
|
||||
permission_dict['agenda.can_see_hidden_items'],
|
||||
permission_dict['agenda.can_see_internal_items'],
|
||||
permission_dict['assignments.can_see'],
|
||||
permission_dict['core.can_see_frontpage'],
|
||||
permission_dict['core.can_see_projector'],
|
||||
@ -87,7 +87,7 @@ def create_builtin_groups_and_admin(**kwargs):
|
||||
# Delegates (pk 2)
|
||||
delegates_permissions = (
|
||||
permission_dict['agenda.can_see'],
|
||||
permission_dict['agenda.can_see_hidden_items'],
|
||||
permission_dict['agenda.can_see_internal_items'],
|
||||
permission_dict['agenda.can_be_speaker'],
|
||||
permission_dict['assignments.can_see'],
|
||||
permission_dict['assignments.can_nominate_other'],
|
||||
@ -105,7 +105,7 @@ def create_builtin_groups_and_admin(**kwargs):
|
||||
# Staff (pk 3)
|
||||
staff_permissions = (
|
||||
permission_dict['agenda.can_see'],
|
||||
permission_dict['agenda.can_see_hidden_items'],
|
||||
permission_dict['agenda.can_see_internal_items'],
|
||||
permission_dict['agenda.can_be_speaker'],
|
||||
permission_dict['agenda.can_manage'],
|
||||
permission_dict['agenda.can_manage_list_of_speakers'],
|
||||
@ -136,7 +136,7 @@ def create_builtin_groups_and_admin(**kwargs):
|
||||
# Admin (pk 4)
|
||||
admin_permissions = (
|
||||
permission_dict['agenda.can_see'],
|
||||
permission_dict['agenda.can_see_hidden_items'],
|
||||
permission_dict['agenda.can_see_internal_items'],
|
||||
permission_dict['agenda.can_be_speaker'],
|
||||
permission_dict['agenda.can_manage'],
|
||||
permission_dict['agenda.can_manage_list_of_speakers'],
|
||||
@ -178,7 +178,7 @@ def create_builtin_groups_and_admin(**kwargs):
|
||||
# Committees (pk 5)
|
||||
committees_permissions = (
|
||||
permission_dict['agenda.can_see'],
|
||||
permission_dict['agenda.can_see_hidden_items'],
|
||||
permission_dict['agenda.can_see_internal_items'],
|
||||
permission_dict['assignments.can_see'],
|
||||
permission_dict['core.can_see_frontpage'],
|
||||
permission_dict['core.can_see_projector'],
|
||||
|
@ -79,6 +79,15 @@ DATABASES = {
|
||||
use_redis = False
|
||||
|
||||
if use_redis:
|
||||
# Redis configuration for django-redis-session. Keep this synchronized to
|
||||
# the caching settings
|
||||
|
||||
SESSION_REDIS = {
|
||||
'host': '127.0.0.1',
|
||||
'post': 6379,
|
||||
'db': 0,
|
||||
}
|
||||
|
||||
# Django Channels
|
||||
|
||||
# Unless you have only a small assembly uncomment the following lines to
|
||||
|
@ -4,6 +4,6 @@
|
||||
# Requirements for Redis and PostgreSQL support
|
||||
asgi-redis>=1.3,<1.5
|
||||
django-redis>=4.7.0,<4.10
|
||||
django-redis-sessions>=0.5.6,<0.7
|
||||
django-redis-sessions>=0.6.1,<0.7
|
||||
psycopg2-binary>=2.7,<2.8
|
||||
txredisapi==1.4.4
|
||||
|
@ -1,4 +1,5 @@
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext
|
||||
from django_redis import get_redis_connection
|
||||
@ -25,9 +26,9 @@ class RetrieveItem(TestCase):
|
||||
config['general_system_enable_anonymous'] = True
|
||||
self.item = Topic.objects.create(title='test_title_Idais2pheepeiz5uph1c').agenda_item
|
||||
|
||||
def test_normal_by_anonymous_without_perm_to_see_hidden_items(self):
|
||||
def test_normal_by_anonymous_without_perm_to_see_internal_items(self):
|
||||
group = get_user_model().groups.field.related_model.objects.get(pk=1) # Group with pk 1 is for anonymous users.
|
||||
permission_string = 'agenda.can_see_hidden_items'
|
||||
permission_string = 'agenda.can_see_internal_items'
|
||||
app_label, codename = permission_string.split('.')
|
||||
permission = group.permissions.get(content_type__app_label=app_label, codename=codename)
|
||||
group.permissions.remove(permission)
|
||||
@ -36,12 +37,27 @@ class RetrieveItem(TestCase):
|
||||
response = self.client.get(reverse('item-detail', args=[self.item.pk]))
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_hidden_by_anonymous_without_perm_to_see_hidden_items(self):
|
||||
def test_hidden_by_anonymous_without_manage_perms(self):
|
||||
response = self.client.get(reverse('item-detail', args=[self.item.pk]))
|
||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def test_hidden_by_anonymous_with_manage_perms(self):
|
||||
group = get_user_model().groups.field.related_model.objects.get(pk=1) # Group with pk 1 is for anonymous users.
|
||||
permission_string = 'agenda.can_see_hidden_items'
|
||||
permission_string = 'agenda.can_manage'
|
||||
app_label, codename = permission_string.split('.')
|
||||
permission = Permission.objects.get(content_type__app_label=app_label, codename=codename)
|
||||
group.permissions.add(permission)
|
||||
response = self.client.get(reverse('item-detail', args=[self.item.pk]))
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_internal_by_anonymous_without_perm_to_see_internal_items(self):
|
||||
group = get_user_model().groups.field.related_model.objects.get(pk=1) # Group with pk 1 is for anonymous users.
|
||||
permission_string = 'agenda.can_see_internal_items'
|
||||
app_label, codename = permission_string.split('.')
|
||||
permission = group.permissions.get(content_type__app_label=app_label, codename=codename)
|
||||
group.permissions.remove(permission)
|
||||
self.item.type = Item.INTERNAL_ITEM
|
||||
self.item.save()
|
||||
response = self.client.get(reverse('item-detail', args=[self.item.pk]))
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(sorted(response.data.keys()), sorted((
|
||||
@ -56,6 +72,7 @@ class RetrieveItem(TestCase):
|
||||
'comment',
|
||||
'closed',
|
||||
'type',
|
||||
'is_internal',
|
||||
'is_hidden',
|
||||
'duration',
|
||||
'weight',
|
||||
@ -101,13 +118,15 @@ class TestDBQueries(TestCase):
|
||||
* 1 request to get all speakers,
|
||||
* 3 requests to get the assignments, motions and topics and
|
||||
|
||||
* 1 request to get an agenda item (why?)
|
||||
|
||||
* 2 requests for the motionsversions.
|
||||
|
||||
TODO: The last two request for the motionsversions are a bug.
|
||||
"""
|
||||
self.client.force_login(User.objects.get(pk=1))
|
||||
get_redis_connection("default").flushall()
|
||||
with self.assertNumQueries(14):
|
||||
with self.assertNumQueries(15):
|
||||
self.client.get(reverse('item-list'))
|
||||
|
||||
def test_anonymous(self):
|
||||
@ -118,12 +137,14 @@ class TestDBQueries(TestCase):
|
||||
* 1 request to get all speakers,
|
||||
* 3 requests to get the assignments, motions and topics and
|
||||
|
||||
* 1 request to get an agenda item (why?)
|
||||
|
||||
* 2 requests for the motionsversions.
|
||||
|
||||
TODO: The last two request for the motionsversions are a bug.
|
||||
"""
|
||||
get_redis_connection("default").flushall()
|
||||
with self.assertNumQueries(10):
|
||||
with self.assertNumQueries(11):
|
||||
self.client.get(reverse('item-list'))
|
||||
|
||||
|
||||
@ -410,8 +431,8 @@ class Numbering(TestCase):
|
||||
self.assertEqual(Item.objects.get(pk=self.item_2_1.pk).item_number, 'II.1')
|
||||
self.assertEqual(Item.objects.get(pk=self.item_3.pk).item_number, 'III')
|
||||
|
||||
def test_with_hidden_item(self):
|
||||
self.item_2.type = Item.HIDDEN_ITEM
|
||||
def test_with_internal_item(self):
|
||||
self.item_2.type = Item.INTERNAL_ITEM
|
||||
self.item_2.save()
|
||||
|
||||
response = self.client.post(reverse('item-numbering'))
|
||||
@ -422,9 +443,9 @@ class Numbering(TestCase):
|
||||
self.assertEqual(Item.objects.get(pk=self.item_2_1.pk).item_number, '')
|
||||
self.assertEqual(Item.objects.get(pk=self.item_3.pk).item_number, '2')
|
||||
|
||||
def test_reset_numbering_with_hidden_item(self):
|
||||
def test_reset_numbering_with_internal_item(self):
|
||||
self.item_2.item_number = 'test_number_Cieghae6ied5ool4hiem'
|
||||
self.item_2.type = Item.HIDDEN_ITEM
|
||||
self.item_2.type = Item.INTERNAL_ITEM
|
||||
self.item_2.save()
|
||||
self.item_2_1.item_number = 'test_number_roQueTohg7fe1Is7aemu'
|
||||
self.item_2_1.save()
|
||||
|
@ -512,7 +512,7 @@ class GroupUpdate(TestCase):
|
||||
'agenda.can_be_speaker',
|
||||
'agenda.can_manage',
|
||||
'agenda.can_see',
|
||||
'agenda.can_see_hidden_items',
|
||||
'agenda.can_see_internal_items',
|
||||
'assignments.can_manage',
|
||||
'assignments.can_nominate_other',
|
||||
'assignments.can_nominate_self',
|
||||
|
Loading…
Reference in New Issue
Block a user