New item type internal.
The old hidden type was used as internal, so everything is changed to not be shown if the item is internal. hidden is "new", and actually behaves as hidden now.
This commit is contained in:
parent
944c00b8a0
commit
1a17862d6b
@ -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].
|
||||
@ -20,8 +23,9 @@ Motions:
|
||||
|
||||
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,43 @@
|
||||
</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">
|
||||
<translate>Set closed</translate>
|
||||
</a>
|
||||
<a ng-show="selectedAction == 'setState'"
|
||||
ng-click="setStateMultiple(false)" class="btn btn-default btn-sm">
|
||||
<translate>Set not closed</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 +185,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 +230,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 +305,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 +324,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 +412,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="{{ 'Set the type' | 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 +482,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 +490,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 +500,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 +513,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) {
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
@ -540,15 +541,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',
|
||||
@ -2223,7 +2216,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 +2275,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) {
|
||||
|
@ -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'],
|
||||
|
@ -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