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:
FinnStutzenstein 2018-08-15 11:15:54 +02:00 committed by Emanuel Schütze
parent 944c00b8a0
commit 1a17862d6b
23 changed files with 501 additions and 231 deletions

View File

@ -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)

View File

@ -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 = []

View File

@ -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(

View 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),
]

View File

@ -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

View File

@ -49,6 +49,7 @@ class ItemSerializer(ModelSerializer):
'comment',
'closed',
'type',
'is_internal',
'is_hidden',
'duration',
'speakers',

View File

@ -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

View File

@ -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;

View File

@ -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
},
{

View File

@ -84,12 +84,14 @@ angular.module('OpenSlidesApp.agenda.projector', ['OpenSlidesApp.agenda'])
Config.lastModified('agenda_hide_internal_items_on_projector');
}, function () {
if ($scope.element.id) {
if (Config.get('agenda_hide_internal_items_on_projector').value) {
// remove hidden items
items = _.filter(Agenda.getAll(), function (item) {
return item.type === 1;
return !item.is_hidden;
});
if (Config.get('agenda_hide_internal_items_on_projector').value) {
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);
}

View File

@ -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');
}
]);

View File

@ -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">&middot;
<translate>Duration</translate>:
{{ sumDurations() | osMinutesToTime }}h
@ -196,20 +230,20 @@
<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!) -->
<!-- boolean Filters -->
<!-- State -->
<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),
ng-class="{'bold': filter.booleanFilters.closed.value !== filter.booleanFilters.closed.defaultValue,
'disabled': isSelectMode}"
ng-disabled="isSelectMode">
<translate>Items</translate>
<translate>State</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownItems">
@ -225,17 +259,34 @@
{{ filter.booleanFilters.closed.choiceNo | translate }}
</a>
</li>
<li class="divider"></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_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 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>
<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 }}
<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>
@ -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>
@ -275,7 +326,7 @@
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})
| 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>

View File

@ -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

View File

@ -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:

View File

@ -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) {

View File

@ -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)

View File

@ -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();

View File

@ -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) {

View File

@ -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)

View File

@ -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();

View File

@ -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'],

View File

@ -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()

View File

@ -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',