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