New Agenda Item
Changed agenda item, so it can not be manualy created, but is always created when a custom slide, motion or assignment is created.
This commit is contained in:
parent
d3a6c05a68
commit
12a08b9732
@ -8,6 +8,7 @@ cache:
|
||||
python:
|
||||
- "3.3"
|
||||
- "3.4"
|
||||
- "3.5"
|
||||
install:
|
||||
- "pip install --upgrade --requirement requirements.txt"
|
||||
- "npm install"
|
||||
@ -18,7 +19,7 @@ script:
|
||||
- "isort --check-only --recursive openslides tests"
|
||||
|
||||
- "DJANGO_SETTINGS_MODULE='tests.settings' coverage run ./manage.py test tests.unit"
|
||||
- "coverage report --fail-under=43"
|
||||
- "coverage report --fail-under=40"
|
||||
|
||||
- "DJANGO_SETTINGS_MODULE='tests.settings' coverage run ./manage.py test tests.integration"
|
||||
- "coverage report --fail-under=50"
|
||||
|
@ -14,15 +14,23 @@ class AgendaAppConfig(AppConfig):
|
||||
from . import projector # noqa
|
||||
|
||||
# Import all required stuff.
|
||||
from django.db.models.signals import pre_delete
|
||||
from django.db.models.signals import pre_delete, post_save
|
||||
from openslides.core.signals import config_signal
|
||||
from openslides.utils.rest_api import router
|
||||
from .signals import setup_agenda_config, listen_to_related_object_delete_signal
|
||||
from .signals import (
|
||||
setup_agenda_config,
|
||||
listen_to_related_object_post_delete,
|
||||
listen_to_related_object_post_save)
|
||||
from .views import ItemViewSet
|
||||
|
||||
# Connect signals.
|
||||
config_signal.connect(setup_agenda_config, dispatch_uid='setup_agenda_config')
|
||||
pre_delete.connect(listen_to_related_object_delete_signal, dispatch_uid='agenda_listen_to_related_object_delete_signal')
|
||||
post_save.connect(
|
||||
listen_to_related_object_post_save,
|
||||
dispatch_uid='listen_to_related_object_post_save')
|
||||
pre_delete.connect(
|
||||
listen_to_related_object_post_delete,
|
||||
dispatch_uid='listen_to_related_object_post_delete')
|
||||
|
||||
# Register viewsets.
|
||||
router.register('agenda/item', ItemViewSet)
|
||||
|
46
openslides/agenda/migrations/0004_auto_20151027_1423.py
Normal file
46
openslides/agenda/migrations/0004_auto_20151027_1423.py
Normal file
@ -0,0 +1,46 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agenda', '0003_auto_20150904_1732'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='item',
|
||||
options={
|
||||
'permissions': (
|
||||
('can_see', 'Can see agenda'),
|
||||
('can_manage', 'Can manage agenda'),
|
||||
('can_see_hidden_items',
|
||||
'Can see hidden items and time scheduling of agenda'))},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='item',
|
||||
name='type',
|
||||
field=models.IntegerField(
|
||||
choices=[(1, 'Agenda item'), (2, 'Hidden item')],
|
||||
verbose_name='Type',
|
||||
default=1),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='item',
|
||||
unique_together=set([('content_type', 'object_id')]),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='item',
|
||||
name='tags',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='item',
|
||||
name='text',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='item',
|
||||
name='title',
|
||||
),
|
||||
]
|
@ -5,13 +5,11 @@ from django.conf import settings
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models, transaction
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||
|
||||
from openslides.core.config import config
|
||||
from openslides.core.models import Tag
|
||||
from openslides.core.projector import Countdown
|
||||
from openslides.utils.exceptions import OpenSlidesError
|
||||
from openslides.utils.models import RESTModelMixin
|
||||
@ -19,6 +17,45 @@ from openslides.utils.utils import to_roman
|
||||
|
||||
|
||||
class ItemManager(models.Manager):
|
||||
def get_only_agenda_items(self, queryset=None):
|
||||
"""
|
||||
Generator, which yields only agenda items. Skips hidden items.
|
||||
"""
|
||||
if queryset is None:
|
||||
queryset = self.all()
|
||||
|
||||
# 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)
|
||||
|
||||
def yield_items(items):
|
||||
"""
|
||||
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_root_and_children(self, queryset=None, only_agenda_items=False):
|
||||
"""
|
||||
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 queryset is None:
|
||||
queryset = self.order_by('weight')
|
||||
|
||||
item_children = defaultdict(list)
|
||||
root_items = []
|
||||
for item in queryset:
|
||||
if only_agenda_items and item.type == item.HIDDEN_ITEM:
|
||||
continue
|
||||
if item.parent_id is not None:
|
||||
item_children[item.parent_id].append(item)
|
||||
else:
|
||||
root_items.append(item)
|
||||
|
||||
return root_items, item_children
|
||||
|
||||
def get_tree(self, only_agenda_items=False, include_content=False):
|
||||
"""
|
||||
Generator that yields dictonaries. Each dictonary has two keys, id
|
||||
@ -30,15 +67,7 @@ class ItemManager(models.Manager):
|
||||
If include_content is True, the yielded dictonaries have no key 'id'
|
||||
but a key 'item' with the entire object.
|
||||
"""
|
||||
item_queryset = self.order_by('weight')
|
||||
if only_agenda_items:
|
||||
item_queryset = item_queryset.filter(type__exact=Item.AGENDA_ITEM)
|
||||
|
||||
# Index the items to get the children for each item
|
||||
item_children = defaultdict(list)
|
||||
for item in item_queryset:
|
||||
if item.parent:
|
||||
item_children[item.parent_id].append(item)
|
||||
root_items, item_children = self.get_root_and_children(only_agenda_items=only_agenda_items)
|
||||
|
||||
def get_children(items):
|
||||
"""
|
||||
@ -50,7 +79,7 @@ class ItemManager(models.Manager):
|
||||
else:
|
||||
yield dict(id=item.pk, children=get_children(item_children[item.pk]))
|
||||
|
||||
yield from get_children(filter(lambda item: item.parent is None, item_queryset))
|
||||
yield from get_children(root_items)
|
||||
|
||||
@transaction.atomic
|
||||
def set_tree(self, tree):
|
||||
@ -101,27 +130,17 @@ class Item(RESTModelMixin, models.Model):
|
||||
slide_callback_name = 'agenda'
|
||||
|
||||
AGENDA_ITEM = 1
|
||||
ORGANIZATIONAL_ITEM = 2
|
||||
HIDDEN_ITEM = 2
|
||||
|
||||
ITEM_TYPE = (
|
||||
(AGENDA_ITEM, ugettext_lazy('Agenda item')),
|
||||
(ORGANIZATIONAL_ITEM, ugettext_lazy('Organizational item')))
|
||||
(HIDDEN_ITEM, ugettext_lazy('Hidden item')))
|
||||
|
||||
item_number = models.CharField(blank=True, max_length=255, verbose_name=ugettext_lazy("Number"))
|
||||
"""
|
||||
Number of agenda item.
|
||||
"""
|
||||
|
||||
title = models.CharField(null=True, max_length=255, verbose_name=ugettext_lazy("Title"))
|
||||
"""
|
||||
Title of the agenda item.
|
||||
"""
|
||||
|
||||
text = models.TextField(null=True, blank=True, verbose_name=ugettext_lazy("Text"))
|
||||
"""
|
||||
The optional text of the agenda item.
|
||||
"""
|
||||
|
||||
comment = models.TextField(null=True, blank=True, verbose_name=ugettext_lazy("Comment"))
|
||||
"""
|
||||
Optional comment to the agenda item. Will not be shoun to normal users.
|
||||
@ -132,7 +151,10 @@ class Item(RESTModelMixin, models.Model):
|
||||
Flag, if the item is finished.
|
||||
"""
|
||||
|
||||
type = models.IntegerField(choices=ITEM_TYPE, default=AGENDA_ITEM, verbose_name=ugettext_lazy("Type"))
|
||||
type = models.IntegerField(
|
||||
choices=ITEM_TYPE,
|
||||
default=AGENDA_ITEM,
|
||||
verbose_name=ugettext_lazy("Type"))
|
||||
"""
|
||||
Type of the agenda item.
|
||||
|
||||
@ -175,32 +197,15 @@ class Item(RESTModelMixin, models.Model):
|
||||
True, if the list of speakers is closed.
|
||||
"""
|
||||
|
||||
tags = models.ManyToManyField(Tag, blank=True)
|
||||
"""
|
||||
Tags to categorise agenda items.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
('can_see', ugettext_noop("Can see agenda")),
|
||||
('can_manage', ugettext_noop("Can manage agenda")),
|
||||
('can_see_orga_items', ugettext_noop("Can see orga items and time scheduling of agenda")))
|
||||
('can_see_hidden_items', ugettext_noop("Can see hidden items and time scheduling of agenda")))
|
||||
unique_together = ('content_type', 'object_id')
|
||||
|
||||
def __str__(self):
|
||||
return self.get_title()
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
Ensures that the children of orga items are only orga items.
|
||||
"""
|
||||
if (self.type == self.AGENDA_ITEM and
|
||||
self.parent is not None and
|
||||
self.parent.type == self.ORGANIZATIONAL_ITEM):
|
||||
raise ValidationError(_('Agenda items can not be child elements of an organizational item.'))
|
||||
if (self.type == self.ORGANIZATIONAL_ITEM and
|
||||
self.children.filter(type=self.AGENDA_ITEM).exists()):
|
||||
raise ValidationError(_('Organizational items can not have agenda items as child elements.'))
|
||||
return super().clean()
|
||||
return self.title
|
||||
|
||||
def delete(self, with_children=False):
|
||||
"""
|
||||
@ -216,91 +221,27 @@ class Item(RESTModelMixin, models.Model):
|
||||
child.save()
|
||||
super().delete()
|
||||
|
||||
def get_title(self):
|
||||
@property
|
||||
def title(self):
|
||||
"""
|
||||
Return the title of this item.
|
||||
Return get_agenda_title() from the content_object.
|
||||
"""
|
||||
if not self.content_object:
|
||||
agenda_title = self.title or ""
|
||||
else:
|
||||
try:
|
||||
agenda_title = self.content_object.get_agenda_title()
|
||||
except AttributeError:
|
||||
raise NotImplementedError('You have to provide a get_agenda_title '
|
||||
'method on your related model.')
|
||||
return '%s %s' % (self.item_no, agenda_title) if self.item_no else agenda_title
|
||||
|
||||
def get_title_supplement(self):
|
||||
"""
|
||||
Return a supplement for the title.
|
||||
"""
|
||||
if not self.content_object:
|
||||
return ''
|
||||
try:
|
||||
return self.content_object.get_agenda_title_supplement()
|
||||
title = self.content_object.get_agenda_title()
|
||||
except AttributeError:
|
||||
raise NotImplementedError('You have to provide a get_agenda_title_supplement method on your related model.')
|
||||
raise NotImplementedError('You have to provide a get_agenda_title '
|
||||
'method on your related model.')
|
||||
return '%s %s' % (self.item_no, title) if self.item_no else title
|
||||
|
||||
def get_list_of_speakers(self, old_speakers_count=None, coming_speakers_count=None):
|
||||
def is_hidden(self):
|
||||
"""
|
||||
Returns the list of speakers as a list of dictionaries. Each
|
||||
dictionary contains a prefix, the speaker and its type. Types
|
||||
are old_speaker, actual_speaker and coming_speaker.
|
||||
Returns True if the type of this object itself is a hidden item or any
|
||||
of its ancestors has such a type.
|
||||
|
||||
Attention! This executes one query for each ancestor of the item.
|
||||
"""
|
||||
list_of_speakers = []
|
||||
|
||||
# Parse old speakers
|
||||
old_speakers = self.speakers.exclude(begin_time=None).exclude(end_time=None).order_by('end_time')
|
||||
if old_speakers_count is None:
|
||||
old_speakers_count = old_speakers.count()
|
||||
last_old_speakers_count = max(0, old_speakers.count() - old_speakers_count)
|
||||
old_speakers = old_speakers[last_old_speakers_count:]
|
||||
for number, speaker in enumerate(old_speakers):
|
||||
prefix = old_speakers_count - number
|
||||
speaker_dict = {
|
||||
'prefix': '-%d' % prefix,
|
||||
'speaker': speaker,
|
||||
'type': 'old_speaker',
|
||||
'first_in_group': False,
|
||||
'last_in_group': False}
|
||||
if number == 0:
|
||||
speaker_dict['first_in_group'] = True
|
||||
if number == old_speakers_count - 1:
|
||||
speaker_dict['last_in_group'] = True
|
||||
list_of_speakers.append(speaker_dict)
|
||||
|
||||
# Parse actual speaker
|
||||
try:
|
||||
actual_speaker = self.speakers.filter(end_time=None).exclude(begin_time=None).get()
|
||||
except Speaker.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
list_of_speakers.append({
|
||||
'prefix': '0',
|
||||
'speaker': actual_speaker,
|
||||
'type': 'actual_speaker',
|
||||
'first_in_group': True,
|
||||
'last_in_group': True})
|
||||
|
||||
# Parse coming speakers
|
||||
coming_speakers = self.speakers.filter(begin_time=None).order_by('weight')
|
||||
if coming_speakers_count is None:
|
||||
coming_speakers_count = coming_speakers.count()
|
||||
coming_speakers = coming_speakers[:max(0, coming_speakers_count)]
|
||||
for number, speaker in enumerate(coming_speakers):
|
||||
speaker_dict = {
|
||||
'prefix': number + 1,
|
||||
'speaker': speaker,
|
||||
'type': 'coming_speaker',
|
||||
'first_in_group': False,
|
||||
'last_in_group': False}
|
||||
if number == 0:
|
||||
speaker_dict['first_in_group'] = True
|
||||
if number == coming_speakers_count - 1:
|
||||
speaker_dict['last_in_group'] = True
|
||||
list_of_speakers.append(speaker_dict)
|
||||
|
||||
return list_of_speakers
|
||||
return (self.type == self.HIDDEN_ITEM or
|
||||
(self.parent is not None and self.parent.is_hidden()))
|
||||
|
||||
def get_next_speaker(self):
|
||||
"""
|
||||
@ -421,11 +362,12 @@ class Speaker(RESTModelMixin, models.Model):
|
||||
speaking, end his speech.
|
||||
"""
|
||||
try:
|
||||
actual_speaker = Speaker.objects.filter(item=self.item, end_time=None).exclude(begin_time=None).get()
|
||||
current_speaker = (Speaker.objects.filter(item=self.item, end_time=None)
|
||||
.exclude(begin_time=None).get())
|
||||
except Speaker.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
actual_speaker.end_speech()
|
||||
current_speaker.end_speech()
|
||||
self.weight = None
|
||||
self.begin_time = datetime.now()
|
||||
self.save()
|
||||
|
@ -1,7 +1,6 @@
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from openslides.core.exceptions import ProjectorException
|
||||
from openslides.core.views import TagViewSet
|
||||
from openslides.utils.projector import ProjectorElement, ProjectorRequirement
|
||||
|
||||
from .models import Item
|
||||
@ -80,8 +79,3 @@ class ItemDetailSlide(ProjectorElement):
|
||||
view_class=speaker.user.get_view_class(),
|
||||
view_action='retrieve',
|
||||
pk=str(speaker.user_id))
|
||||
for tag in item.tags.all():
|
||||
yield ProjectorRequirement(
|
||||
view_class=TagViewSet,
|
||||
view_action='retrieve',
|
||||
pk=str(tag.pk))
|
||||
|
@ -1,7 +1,6 @@
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from openslides.utils.rest_api import (
|
||||
CharField,
|
||||
ModelSerializer,
|
||||
RelatedField,
|
||||
get_collection_and_id_from_url,
|
||||
@ -45,10 +44,7 @@ class ItemSerializer(ModelSerializer):
|
||||
"""
|
||||
Serializer for agenda.models.Item objects.
|
||||
"""
|
||||
get_title = CharField(read_only=True)
|
||||
get_title_supplement = CharField(read_only=True)
|
||||
content_object = RelatedItemRelatedField(read_only=True)
|
||||
item_no = CharField(read_only=True)
|
||||
speakers = SpeakerSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
@ -56,11 +52,7 @@ class ItemSerializer(ModelSerializer):
|
||||
fields = (
|
||||
'id',
|
||||
'item_number',
|
||||
'item_no',
|
||||
'title',
|
||||
'get_title',
|
||||
'get_title_supplement',
|
||||
'text',
|
||||
'comment',
|
||||
'closed',
|
||||
'type',
|
||||
@ -68,6 +60,5 @@ class ItemSerializer(ModelSerializer):
|
||||
'speakers',
|
||||
'speaker_list_closed',
|
||||
'content_object',
|
||||
'tags',
|
||||
'weight',
|
||||
'parent',)
|
||||
|
@ -72,15 +72,24 @@ def setup_agenda_config(sender, **kwargs):
|
||||
group=ugettext_lazy('Agenda'))
|
||||
|
||||
|
||||
def listen_to_related_object_delete_signal(sender, instance, **kwargs):
|
||||
def listen_to_related_object_post_save(sender, instance, created, **kwargs):
|
||||
"""
|
||||
Receiver function to change agenda items of a related item that is to
|
||||
be deleted. It is connected to the signal
|
||||
django.db.models.signals.pre_delete during app loading.
|
||||
Receiver function to create agenda items. It is connected to the signal
|
||||
django.db.models.signals.post_save during app loading.
|
||||
"""
|
||||
if created and hasattr(instance, 'get_agenda_title'):
|
||||
Item.objects.create(content_object=instance)
|
||||
|
||||
|
||||
def listen_to_related_object_post_delete(sender, instance, **kwargs):
|
||||
"""
|
||||
Receiver function to delete agenda items. It is connected to the signal
|
||||
django.db.models.signals.post_delete during app loading.
|
||||
"""
|
||||
if hasattr(instance, 'get_agenda_title'):
|
||||
for item in Item.objects.filter(content_type=ContentType.objects.get_for_model(sender), object_id=instance.pk):
|
||||
item.title = '< Item for deleted (%s) >' % instance.get_agenda_title()
|
||||
item.content_type = None
|
||||
item.object_id = None
|
||||
item.save()
|
||||
content_type = ContentType.objects.get_for_model(instance)
|
||||
try:
|
||||
Item.objects.get(object_id=instance.pk, content_type=content_type).delete()
|
||||
except Item.DoesNotExist:
|
||||
# Item does not exist so we do not have to delete it.
|
||||
pass
|
||||
|
@ -33,6 +33,27 @@ angular.module('OpenSlidesApp.agenda', ['OpenSlidesApp.users'])
|
||||
methods: {
|
||||
getResourceName: function () {
|
||||
return name;
|
||||
},
|
||||
getContentObject: function () {
|
||||
return DS.get(this.content_object.collection, this.content_object.id);
|
||||
},
|
||||
getContentResource: function () {
|
||||
return DS.definitions[this.content_object.collection];
|
||||
},
|
||||
getTitle: function () {
|
||||
var title;
|
||||
try {
|
||||
title = this.getContentObject().getAgendaTitle();
|
||||
} catch (e) {
|
||||
// Only use this.title when the content object is not
|
||||
// in the DS store.
|
||||
title = this.title;
|
||||
}
|
||||
return _.trim(
|
||||
title + ' ' + (
|
||||
this.getContentResource().agendaSupplement || ''
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
relations: {
|
||||
@ -97,6 +118,7 @@ angular.module('OpenSlidesApp.agenda', ['OpenSlidesApp.users'])
|
||||
});
|
||||
return getChildren(parentItems);
|
||||
},
|
||||
|
||||
// Returns a list of all items as a flat tree the attribute parentCount
|
||||
getFlatTree: function(items) {
|
||||
var tree = this.getTree(items);
|
||||
|
@ -99,6 +99,7 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
||||
// Bind agenda tree to the scope
|
||||
$scope.$watch(function () {
|
||||
return Agenda.lastModified();
|
||||
|
||||
}, function () {
|
||||
$scope.items = AgendaTree.getFlatTree(Agenda.getAll());
|
||||
});
|
||||
|
@ -103,7 +103,7 @@
|
||||
<!-- agenda data columns -->
|
||||
<td>
|
||||
<span ng-repeat="n in [].constructor(item.parentCount) track by $index">–</span>
|
||||
{{ item.item_number }} {{ item.title }}
|
||||
{{ item.item_number }} {{ item.getTitle() }}
|
||||
<div ng-if="item.comment">
|
||||
<small><i class="fa fa-info-circle"></i> {{ item.comment }}</small>
|
||||
</div>
|
||||
|
@ -1,16 +0,0 @@
|
||||
{% load i18n %}
|
||||
{% load highlight %}
|
||||
|
||||
{% if perms.agenda.can_see and result.object.type == result.object.AGENDA_ITEM %}
|
||||
<li>
|
||||
<a href="{{ result.object.get_absolute_url }}">{{ result.object }}</a><br>
|
||||
<span class="app">{% trans "Agenda" %}</a></span><br>
|
||||
{% highlight result.text with request.GET.q %}
|
||||
</li>
|
||||
{% elif perms.agenda.can_see_orga_items and result.object.type == result.object.ORGANIZATIONAL_ITEM %}
|
||||
<li>
|
||||
<a href="{{ result.object.get_absolute_url }}"><i>[{{ result.object }}]</i></a><br>
|
||||
<span class="app">{% trans "Agenda" %} ({% trans "Organizational item" %})</a></span><br>
|
||||
{% highlight result.text with request.GET.q %}
|
||||
</li>
|
||||
{% endif %}
|
@ -1,3 +0,0 @@
|
||||
{{ object.title }}
|
||||
{{ object.text }}
|
||||
{{ object.tags.all }}
|
@ -8,8 +8,11 @@ from reportlab.platypus import Paragraph
|
||||
from openslides.utils.exceptions import OpenSlidesError
|
||||
from openslides.utils.pdf import stylesheet
|
||||
from openslides.utils.rest_api import (
|
||||
ModelViewSet,
|
||||
GenericViewSet,
|
||||
ListModelMixin,
|
||||
Response,
|
||||
RetrieveModelMixin,
|
||||
UpdateModelMixin,
|
||||
ValidationError,
|
||||
detail_route,
|
||||
list_route,
|
||||
@ -22,7 +25,7 @@ from .serializers import ItemSerializer
|
||||
|
||||
# Viewsets for the REST API
|
||||
|
||||
class ItemViewSet(ModelViewSet):
|
||||
class ItemViewSet(ListModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericViewSet):
|
||||
"""
|
||||
API endpoint for agenda items.
|
||||
|
||||
@ -40,9 +43,9 @@ class ItemViewSet(ModelViewSet):
|
||||
result = self.request.user.has_perm('agenda.can_see')
|
||||
# For manage_speaker and tree requests the rest of the check is
|
||||
# done in the specific method. See below.
|
||||
elif self.action in ('create', 'partial_update', 'update', 'destroy'):
|
||||
elif self.action in ('partial_update', 'update'):
|
||||
result = (self.request.user.has_perm('agenda.can_see') and
|
||||
self.request.user.has_perm('agenda.can_see_orga_items') and
|
||||
self.request.user.has_perm('agenda.can_see_hidden_items') and
|
||||
self.request.user.has_perm('agenda.can_manage'))
|
||||
elif self.action == 'speak':
|
||||
result = (self.request.user.has_perm('agenda.can_see') and
|
||||
@ -56,7 +59,7 @@ class ItemViewSet(ModelViewSet):
|
||||
Checks if the requesting user has permission to see also an
|
||||
organizational item if it is one.
|
||||
"""
|
||||
if obj.type == obj.ORGANIZATIONAL_ITEM and not request.user.has_perm('agenda.can_see_orga_items'):
|
||||
if obj.is_hidden() and not request.user.has_perm('agenda.can_see_hidden_items'):
|
||||
self.permission_denied(request)
|
||||
|
||||
def get_queryset(self):
|
||||
@ -64,9 +67,10 @@ class ItemViewSet(ModelViewSet):
|
||||
Filters organizational items if the user has no permission to see them.
|
||||
"""
|
||||
queryset = super().get_queryset()
|
||||
if not self.request.user.has_perm('agenda.can_see_orga_items'):
|
||||
queryset = queryset.exclude(type__exact=Item.ORGANIZATIONAL_ITEM)
|
||||
return queryset
|
||||
if self.request.user.has_perm('agenda.can_see_hidden_items'):
|
||||
return queryset
|
||||
else:
|
||||
return Item.objects.get_only_agenda_items(queryset)
|
||||
|
||||
@detail_route(methods=['POST', 'DELETE'])
|
||||
def manage_speaker(self, request, pk=None):
|
||||
@ -197,7 +201,7 @@ class ItemViewSet(ModelViewSet):
|
||||
"""
|
||||
if request.method == 'PUT':
|
||||
if not (request.user.has_perm('agenda.can_manage') and
|
||||
request.user.has_perm('agenda.can_see_orga_items')):
|
||||
request.user.has_perm('agenda.can_see_hidden_items')):
|
||||
self.permission_denied(request)
|
||||
try:
|
||||
tree = request.data['tree']
|
||||
@ -242,7 +246,7 @@ class AgendaPDF(PDFView):
|
||||
if ancestors:
|
||||
space = " " * 6 * ancestors
|
||||
story.append(Paragraph(
|
||||
"%s%s" % (space, escape(item.get_title())),
|
||||
"%s%s" % (space, escape(item.title)),
|
||||
stylesheet['Subitem']))
|
||||
else:
|
||||
story.append(Paragraph(escape(item.get_title()), stylesheet['Item']))
|
||||
story.append(Paragraph(escape(item.title), stylesheet['Item']))
|
||||
|
@ -1,5 +1,5 @@
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.fields import GenericRelation
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django.utils.datastructures import SortedDict
|
||||
from django.utils.translation import ugettext as _
|
||||
@ -116,11 +116,6 @@ class Assignment(RESTModelMixin, models.Model):
|
||||
Tags for the assignment.
|
||||
"""
|
||||
|
||||
items = GenericRelation(Item)
|
||||
"""
|
||||
Agenda items for this assignment.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
('can_see', ugettext_noop('Can see elections')),
|
||||
@ -308,8 +303,20 @@ class Assignment(RESTModelMixin, models.Model):
|
||||
def get_agenda_title(self):
|
||||
return str(self)
|
||||
|
||||
def get_agenda_title_supplement(self):
|
||||
return '(%s)' % _('Assignment')
|
||||
@property
|
||||
def agenda_item(self):
|
||||
"""
|
||||
Returns the related agenda item.
|
||||
"""
|
||||
content_type = ContentType.objects.get_for_model(self)
|
||||
return Item.objects.get(object_id=self.pk, content_type=content_type)
|
||||
|
||||
@property
|
||||
def agenda_item_id(self):
|
||||
"""
|
||||
Returns the id of the agenda item object related to this object.
|
||||
"""
|
||||
return self.agenda_item.pk
|
||||
|
||||
|
||||
class AssignmentVote(RESTModelMixin, BaseVote):
|
||||
|
@ -170,6 +170,7 @@ class AssignmentFullSerializer(ModelSerializer):
|
||||
'assignment_related_users',
|
||||
'poll_description_default',
|
||||
'polls',
|
||||
'agenda_item_id',
|
||||
'tags',)
|
||||
|
||||
|
||||
@ -191,4 +192,5 @@ class AssignmentShortSerializer(AssignmentFullSerializer):
|
||||
'assignment_related_users',
|
||||
'poll_description_default',
|
||||
'polls',
|
||||
'agenda_item_id',
|
||||
'tags',)
|
||||
|
@ -4,18 +4,34 @@
|
||||
|
||||
angular.module('OpenSlidesApp.assignments', [])
|
||||
|
||||
.factory('Assignment', ['DS', 'jsDataModel', function(DS, jsDataModel) {
|
||||
var name = 'assignments/assignment';
|
||||
return DS.defineResource({
|
||||
name: name,
|
||||
useClass: jsDataModel,
|
||||
methods: {
|
||||
getResourceName: function () {
|
||||
return name;
|
||||
.factory('Assignment', [
|
||||
'DS',
|
||||
'jsDataModel',
|
||||
function(DS, jsDataModel) {
|
||||
var name = 'assignments/assignment';
|
||||
return DS.defineResource({
|
||||
name: name,
|
||||
useClass: jsDataModel,
|
||||
agendaSupplement: '(Assignment)',
|
||||
methods: {
|
||||
getResourceName: function () {
|
||||
return name;
|
||||
},
|
||||
getAgendaTitle: function () {
|
||||
return this.title;
|
||||
}
|
||||
},
|
||||
relations: {
|
||||
belongsTo: {
|
||||
'agenda/item': {
|
||||
localKey: 'agenda_item_id',
|
||||
localField: 'agenda_item',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}])
|
||||
});
|
||||
}
|
||||
])
|
||||
|
||||
.run(['Assignment', function(Assignment) {}]);
|
||||
|
||||
|
@ -79,6 +79,7 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
||||
|
||||
.controller('AssignmentDetailCtrl', function($scope, Assignment, assignment) {
|
||||
Assignment.bindOne(assignment.id, $scope, 'assignment');
|
||||
Assignment.loadRelations(assignment);
|
||||
})
|
||||
|
||||
.controller('AssignmentCreateCtrl', function($scope, $state, Assignment) {
|
||||
|
@ -24,6 +24,8 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{{ assignment.agenda_item }}
|
||||
|
||||
<h3 translate>Description</h3>
|
||||
<div class="white-space-pre-line">{{ assignment.description }}</div>
|
||||
|
||||
|
@ -1,10 +0,0 @@
|
||||
{% load i18n %}
|
||||
{% load highlight %}
|
||||
|
||||
{% if perms.assignments.can_see %}
|
||||
<li>
|
||||
<a href="{{ result.object.get_absolute_url }}">{{ result.object }}</a><br>
|
||||
<span class="app">{% trans "Election" %}</a></span><br>
|
||||
{% highlight result.text with request.GET.q %}
|
||||
</li>
|
||||
{% endif %}
|
@ -1,4 +0,0 @@
|
||||
{{ object.title }}
|
||||
{{ object.description }}
|
||||
{{ object.candidates }}
|
||||
{{ object.tags.all }}
|
@ -1,4 +1,5 @@
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||
@ -135,6 +136,26 @@ class CustomSlide(RESTModelMixin, models.Model):
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
@property
|
||||
def agenda_item(self):
|
||||
"""
|
||||
Returns the related agenda item.
|
||||
"""
|
||||
# TODO: Move the agenda app in the core app to fix circular dependencies
|
||||
from openslides.agenda.models import Item
|
||||
content_type = ContentType.objects.get_for_model(self)
|
||||
return Item.objects.get(object_id=self.pk, content_type=content_type)
|
||||
|
||||
@property
|
||||
def agenda_item_id(self):
|
||||
"""
|
||||
Returns the id of the agenda item object related to this object.
|
||||
"""
|
||||
return self.agenda_item.pk
|
||||
|
||||
def get_agenda_title(self):
|
||||
return self.title
|
||||
|
||||
|
||||
class Tag(RESTModelMixin, models.Model):
|
||||
"""
|
||||
|
@ -39,7 +39,7 @@ class CustomSlideSerializer(ModelSerializer):
|
||||
"""
|
||||
class Meta:
|
||||
model = CustomSlide
|
||||
fields = ('id', 'title', 'text', 'weight', )
|
||||
fields = ('id', 'title', 'text', 'weight', 'agenda_item_id')
|
||||
|
||||
|
||||
class TagSerializer(ModelSerializer):
|
||||
|
@ -140,18 +140,33 @@ angular.module('OpenSlidesApp.core', [
|
||||
return BaseModel;
|
||||
}])
|
||||
|
||||
.factory('Customslide', ['DS', 'jsDataModel', function(DS, jsDataModel) {
|
||||
var name = 'core/customslide';
|
||||
return DS.defineResource({
|
||||
name: name,
|
||||
useClass: jsDataModel,
|
||||
methods: {
|
||||
getResourceName: function () {
|
||||
return name;
|
||||
.factory('Customslide', [
|
||||
'DS',
|
||||
'jsDataModel',
|
||||
function(DS, jsDataModel) {
|
||||
var name = 'core/customslide';
|
||||
return DS.defineResource({
|
||||
name: name,
|
||||
useClass: jsDataModel,
|
||||
methods: {
|
||||
getResourceName: function () {
|
||||
return name;
|
||||
},
|
||||
getAgendaTitle: function () {
|
||||
return this.title;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
}])
|
||||
relations: {
|
||||
belongsTo: {
|
||||
'agenda/item': {
|
||||
localKey: 'agenda_item_id',
|
||||
localField: 'agenda_item',
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
])
|
||||
|
||||
.factory('Tag', ['DS', function(DS) {
|
||||
return DS.defineResource({
|
||||
|
@ -646,6 +646,7 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
|
||||
.controller('CustomslideDetailCtrl', function($scope, Customslide, customslide) {
|
||||
Customslide.bindOne(customslide.id, $scope, 'customslide');
|
||||
Customslide.loadRelations(customslide);
|
||||
})
|
||||
|
||||
.controller('CustomslideCreateCtrl', function($scope, $state, Customslide) {
|
||||
|
@ -18,5 +18,7 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{{ customslide.agenda_item }}
|
||||
|
||||
<div class="white-space-pre-line">{{ customslide.text }}</div>
|
||||
|
||||
|
@ -82,7 +82,6 @@ INSTALLED_APPS = (
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.humanize',
|
||||
'haystack', # full-text-search
|
||||
'rest_framework',
|
||||
'openslides.poll', # TODO: try to remove this line
|
||||
'openslides.agenda',
|
||||
|
@ -1,4 +1,5 @@
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django.db.models import Max
|
||||
from django.utils import formats
|
||||
@ -6,6 +7,7 @@ from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||
from jsonfield import JSONField
|
||||
|
||||
from openslides.agenda.models import Item
|
||||
from openslides.core.config import config
|
||||
from openslides.core.models import Tag
|
||||
from openslides.mediafiles.models import Mediafile
|
||||
@ -447,13 +449,23 @@ class Motion(RESTModelMixin, models.Model):
|
||||
"""
|
||||
Return a title for the agenda.
|
||||
"""
|
||||
# There has to be a function with the same return value in javascript.
|
||||
return str(self)
|
||||
|
||||
def get_agenda_title_supplement(self):
|
||||
@property
|
||||
def agenda_item(self):
|
||||
"""
|
||||
Returns the supplement to the title for the agenda item.
|
||||
Returns the related agenda item.
|
||||
"""
|
||||
return '(%s)' % _('Motion')
|
||||
content_type = ContentType.objects.get_for_model(self)
|
||||
return Item.objects.get(object_id=self.pk, content_type=content_type)
|
||||
|
||||
@property
|
||||
def agenda_item_id(self):
|
||||
"""
|
||||
Returns the id of the agenda item object related to this object.
|
||||
"""
|
||||
return self.agenda_item.pk
|
||||
|
||||
def get_allowed_actions(self, person):
|
||||
"""
|
||||
|
@ -152,7 +152,7 @@ def motion_to_pdf(pdf, motion):
|
||||
|
||||
def convert_html_to_reportlab(pdf, text):
|
||||
# parsing and replacing not supported html tags for reportlab...
|
||||
soup = BeautifulSoup(text)
|
||||
soup = BeautifulSoup(text, "html5lib")
|
||||
# read all list elements...
|
||||
for element in soup.find_all('li'):
|
||||
# ... and replace ul list elements with <para><bullet>•</bullet>...<para>
|
||||
|
@ -204,6 +204,7 @@ class MotionSerializer(ModelSerializer):
|
||||
'tags',
|
||||
'attachments',
|
||||
'polls',
|
||||
'agenda_item_id',
|
||||
'log_messages',)
|
||||
read_only_fields = ('parent',) # Some other fields are also read_only. See definitions above.
|
||||
|
||||
|
@ -2,63 +2,79 @@
|
||||
|
||||
angular.module('OpenSlidesApp.motions', [])
|
||||
|
||||
.factory('Motion', ['DS', 'jsDataModel', function(DS, jsDataModel) {
|
||||
var name = 'motions/motion'
|
||||
return DS.defineResource({
|
||||
name: name,
|
||||
useClass: jsDataModel,
|
||||
methods: {
|
||||
getResourceName: function () {
|
||||
return name;
|
||||
},
|
||||
getVersion: function(versionId) {
|
||||
versionId = versionId || this.active_version;
|
||||
var index;
|
||||
if (versionId == -1) {
|
||||
index = this.versions.length - 1;
|
||||
} else {
|
||||
index = _.findIndex(this.versions, function(element) {
|
||||
return element.id == versionId
|
||||
});
|
||||
}
|
||||
return this.versions[index];
|
||||
},
|
||||
getTitle: function(versionId) {
|
||||
return this.getVersion(versionId).title;
|
||||
},
|
||||
getText: function(versionId) {
|
||||
return this.getVersion(versionId).text;
|
||||
},
|
||||
getReason: function(versionId) {
|
||||
return this.getVersion(versionId).reason;
|
||||
}
|
||||
},
|
||||
relations: {
|
||||
belongsTo: {
|
||||
'motions/category': {
|
||||
localField: 'category',
|
||||
localKey: 'category_id',
|
||||
.factory('Motion', [
|
||||
'DS',
|
||||
'jsDataModel',
|
||||
function(DS, jsDataModel) {
|
||||
var name = 'motions/motion'
|
||||
return DS.defineResource({
|
||||
name: name,
|
||||
useClass: jsDataModel,
|
||||
agendaSupplement: '(Motion)',
|
||||
methods: {
|
||||
getResourceName: function () {
|
||||
return name;
|
||||
},
|
||||
},
|
||||
hasMany: {
|
||||
'core/tag': {
|
||||
localField: 'tags',
|
||||
localKeys: 'tags_id',
|
||||
},
|
||||
'users/user': [
|
||||
{
|
||||
localField: 'submitters',
|
||||
localKeys: 'submitters_id',
|
||||
},
|
||||
{
|
||||
localField: 'supporters',
|
||||
localKeys: 'supporters_id',
|
||||
getVersion: function (versionId) {
|
||||
versionId = versionId || this.active_version;
|
||||
var index;
|
||||
if (versionId == -1) {
|
||||
index = this.versions.length - 1;
|
||||
} else {
|
||||
index = _.findIndex(this.versions, function (element) {
|
||||
return element.id == versionId
|
||||
});
|
||||
}
|
||||
],
|
||||
return this.versions[index];
|
||||
},
|
||||
getTitle: function (versionId) {
|
||||
return this.getVersion(versionId).title;
|
||||
},
|
||||
getText: function (versionId) {
|
||||
return this.getVersion(versionId).text;
|
||||
},
|
||||
getReason: function (versionId) {
|
||||
return this.getVersion(versionId).reason;
|
||||
},
|
||||
getAgendaTitle: function () {
|
||||
var value = '';
|
||||
if (this.identifier) {
|
||||
value = this.identifier + ' | ';
|
||||
}
|
||||
return value + this.getTitle();
|
||||
}
|
||||
},
|
||||
relations: {
|
||||
belongsTo: {
|
||||
'motions/category': {
|
||||
localField: 'category',
|
||||
localKey: 'category_id',
|
||||
},
|
||||
'agenda/item': {
|
||||
localKey: 'agenda_item_id',
|
||||
localField: 'agenda_item',
|
||||
}
|
||||
},
|
||||
hasMany: {
|
||||
'core/tag': {
|
||||
localField: 'tags',
|
||||
localKeys: 'tags_id',
|
||||
},
|
||||
'users/user': [
|
||||
{
|
||||
localField: 'submitters',
|
||||
localKeys: 'submitters_id',
|
||||
},
|
||||
{
|
||||
localField: 'supporters',
|
||||
localKeys: 'supporters_id',
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}])
|
||||
});
|
||||
}
|
||||
])
|
||||
|
||||
.factory('Category', ['DS', function(DS) {
|
||||
return DS.defineResource({
|
||||
@ -240,6 +256,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
Motion.bindOne(motion.id, $scope, 'motion');
|
||||
Category.bindAll({}, $scope, 'categories');
|
||||
User.bindAll({}, $scope, 'users');
|
||||
Motion.loadRelations(motion);
|
||||
})
|
||||
|
||||
.controller('MotionCreateCtrl',
|
||||
|
@ -7,6 +7,8 @@
|
||||
</h1>
|
||||
{{ motion.tags }}
|
||||
|
||||
{{ motion.agenda_item }}
|
||||
|
||||
<div id="submenu">
|
||||
<a ui-sref="motions.motion.list" class="btn btn-sm btn-default">
|
||||
<i class="fa fa-angle-double-left fa-lg"></i>
|
||||
|
@ -1,8 +0,0 @@
|
||||
{{ object.identifier }}
|
||||
{{ object.title }}
|
||||
{{ object.text }}
|
||||
{{ object.reason }}
|
||||
{{ object.submitters.all }}
|
||||
{{ object.supporters.all }}
|
||||
{{ object.category }}
|
||||
{{ object.tags.all }}
|
@ -1,10 +0,0 @@
|
||||
{% load i18n %}
|
||||
{% load highlight %}
|
||||
|
||||
{% if perms.motions.can_see %}
|
||||
<li>
|
||||
<a href="{{ result.object.get_absolute_url }}">{{ result.object }}</a><br>
|
||||
<span class="app">{% trans "Motion" %}</a></span><br>
|
||||
{% highlight result.text with request.GET.q %}
|
||||
</li>
|
||||
{% endif %}
|
@ -105,7 +105,7 @@ def create_builtin_groups_and_admin(**kwargs):
|
||||
'agenda.can_be_speaker',
|
||||
'agenda.can_manage',
|
||||
'agenda.can_see',
|
||||
'agenda.can_see_orga_items',
|
||||
'agenda.can_see_hidden_items',
|
||||
'assignments.can_manage',
|
||||
'assignments.can_nominate_other',
|
||||
'assignments.can_nominate_self',
|
||||
@ -141,7 +141,7 @@ def create_builtin_groups_and_admin(**kwargs):
|
||||
# Anonymous (pk 1) and Registered (pk 2)
|
||||
base_permissions = (
|
||||
permission_dict['agenda.can_see'],
|
||||
permission_dict['agenda.can_see_orga_items'],
|
||||
permission_dict['agenda.can_see_hidden_items'],
|
||||
permission_dict['assignments.can_see'],
|
||||
permission_dict['core.can_see_dashboard'],
|
||||
permission_dict['core.can_see_projector'],
|
||||
|
@ -1,4 +0,0 @@
|
||||
{{ object.django_user }}
|
||||
{{ object.structure_level }}
|
||||
{{ object.committee }}
|
||||
{{ object.about_me }}
|
@ -1,10 +0,0 @@
|
||||
{% load i18n %}
|
||||
{% load highlight %}
|
||||
|
||||
{% if perms.users.can_see %}
|
||||
<li>
|
||||
<a href="{{ result.object.get_absolute_url }}">{{ result.object }}</a><br>
|
||||
<span class="app">{% trans "User" %}</a></span><br>
|
||||
{% highlight result.text with request.GET.q %}
|
||||
</li>
|
||||
{% endif %}
|
@ -5,7 +5,12 @@ from urllib.parse import urlparse
|
||||
from rest_framework import status # noqa
|
||||
from rest_framework.decorators import detail_route, list_route # noqa
|
||||
from rest_framework.metadata import SimpleMetadata # noqa
|
||||
from rest_framework.mixins import DestroyModelMixin, UpdateModelMixin # noqa
|
||||
from rest_framework.mixins import ( # noqa
|
||||
DestroyModelMixin,
|
||||
ListModelMixin,
|
||||
RetrieveModelMixin,
|
||||
UpdateModelMixin,
|
||||
)
|
||||
from rest_framework.response import Response # noqa
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from rest_framework.serializers import ModelSerializer as _ModelSerializer
|
||||
|
@ -1,4 +1,3 @@
|
||||
from django.core.management import call_command
|
||||
from django.test import TestCase as _TestCase
|
||||
from django.test.runner import DiscoverRunner
|
||||
|
||||
@ -40,6 +39,4 @@ class TestCase(_TestCase):
|
||||
except AttributeError:
|
||||
# The cache has only to be deleted if it exists.
|
||||
pass
|
||||
# Clear the whoosh search index
|
||||
call_command('clear_index', interactive=False, verbosity=0)
|
||||
return return_value
|
||||
|
@ -3,11 +3,11 @@ Django>=1.7.1,<1.9
|
||||
beautifulsoup4>=4.1,<4.5
|
||||
django-haystack>=2.1,<2.5
|
||||
djangorestframework>=3.2.0,<3.3.0
|
||||
html5lib>=0.9,<1.0
|
||||
jsonfield>=0.9.19,<1.1
|
||||
natsort>=3.2,<4.1
|
||||
reportlab>=3.0,<3.3
|
||||
roman>=2.0,<2.1
|
||||
setuptools>=2.2,<19.0
|
||||
sockjs-tornado>=1.0,<1.1
|
||||
whoosh>=2.5.6,<2.8
|
||||
|
||||
|
15
tests/integration/agenda/test_models.py
Normal file
15
tests/integration/agenda/test_models.py
Normal file
@ -0,0 +1,15 @@
|
||||
from openslides.agenda.models import Item
|
||||
from openslides.core.models import CustomSlide
|
||||
from openslides.utils.test import TestCase
|
||||
|
||||
|
||||
class TestItemManager(TestCase):
|
||||
def test_get_root_and_children_db_queries(self):
|
||||
"""
|
||||
Test that get_root_and_children needs only one db query.
|
||||
"""
|
||||
for i in range(10):
|
||||
CustomSlide.objects.create(title='item{}'.format(i))
|
||||
|
||||
with self.assertNumQueries(1):
|
||||
Item.objects.get_root_and_children()
|
@ -3,14 +3,17 @@ import json
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from openslides.agenda.models import Item
|
||||
from openslides.core.models import CustomSlide
|
||||
from openslides.utils.test import TestCase
|
||||
|
||||
|
||||
class AgendaTreeTest(TestCase):
|
||||
def setUp(self):
|
||||
Item.objects.create(title='item1')
|
||||
item2 = Item.objects.create(title='item2')
|
||||
Item.objects.create(title='item2a', parent=item2)
|
||||
CustomSlide.objects.create(title='item1')
|
||||
item2 = CustomSlide.objects.create(title='item2').agenda_item
|
||||
item3 = CustomSlide.objects.create(title='item2a').agenda_item
|
||||
item3.parent = item2
|
||||
item3.save()
|
||||
self.client = APIClient()
|
||||
self.client.login(username='admin', password='admin')
|
||||
|
||||
@ -87,7 +90,7 @@ class TestAgendaPDF(TestCase):
|
||||
"""
|
||||
Tests that a requst on the pdf-page returns with statuscode 200.
|
||||
"""
|
||||
Item.objects.create(title='item1')
|
||||
CustomSlide.objects.create(title='item1')
|
||||
self.client.login(username='admin', password='admin')
|
||||
|
||||
response = self.client.get('/agenda/print/')
|
||||
|
@ -2,9 +2,9 @@ from django.contrib.auth import get_user_model
|
||||
from django.core.urlresolvers import reverse
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from openslides.agenda.models import Item, Speaker
|
||||
from openslides.agenda.models import Speaker
|
||||
from openslides.core.config import config
|
||||
from openslides.core.models import Projector
|
||||
from openslides.core.models import CustomSlide, Projector
|
||||
from openslides.utils.test import TestCase
|
||||
|
||||
|
||||
@ -15,7 +15,8 @@ class ManageSpeaker(TestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.client.login(username='admin', password='admin')
|
||||
self.item = Item.objects.create(title='test_title_aZaedij4gohn5eeQu8fe')
|
||||
|
||||
self.item = CustomSlide.objects.create(title='test_title_aZaedij4gohn5eeQu8fe').agenda_item
|
||||
self.user = get_user_model().objects.create_user(
|
||||
username='test_user_jooSaex1bo5ooPhuphae',
|
||||
password='test_password_e6paev4zeeh9n')
|
||||
@ -132,7 +133,7 @@ class Speak(TestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.client.login(username='admin', password='admin')
|
||||
self.item = Item.objects.create(title='test_title_KooDueco3zaiGhiraiho')
|
||||
self.item = CustomSlide.objects.create(title='test_title_KooDueco3zaiGhiraiho').agenda_item
|
||||
self.user = get_user_model().objects.create_user(
|
||||
username='test_user_Aigh4vohb3seecha4aa4',
|
||||
password='test_password_eneupeeVo5deilixoo8j')
|
||||
|
@ -4,57 +4,31 @@ from unittest.mock import patch
|
||||
from openslides.agenda.models import Item
|
||||
|
||||
|
||||
class ItemTitle(TestCase):
|
||||
def test_get_title_without_item_no(self):
|
||||
item = Item(title='test_title')
|
||||
self.assertEqual(
|
||||
item.get_title(),
|
||||
'test_title')
|
||||
|
||||
@patch('openslides.agenda.models.Item.item_no', '5')
|
||||
def test_get_title_with_item_no(self):
|
||||
item = Item(title='test_title')
|
||||
self.assertEqual(
|
||||
item.get_title(),
|
||||
'5 test_title')
|
||||
|
||||
class TestItemTitle(TestCase):
|
||||
@patch('openslides.agenda.models.Item.content_object')
|
||||
def test_get_title_from_related(self, content_object):
|
||||
item = Item(title='test_title')
|
||||
def test_title_from_content_object(self, content_object):
|
||||
item = Item()
|
||||
content_object.get_agenda_title.return_value = 'related_title'
|
||||
|
||||
self.assertEqual(
|
||||
item.get_title(),
|
||||
item.title,
|
||||
'related_title')
|
||||
|
||||
@patch('openslides.agenda.models.Item.item_no', '5')
|
||||
@patch('openslides.agenda.models.Item.content_object')
|
||||
def test_get_title_invalid_related(self, content_object):
|
||||
item = Item(title='test_title')
|
||||
def test_title_with_item_no(self, content_object):
|
||||
item = Item()
|
||||
content_object.get_agenda_title.return_value = 'related_title'
|
||||
|
||||
self.assertEqual(
|
||||
item.title,
|
||||
'5 related_title')
|
||||
|
||||
@patch('openslides.agenda.models.Item.content_object')
|
||||
def test_title_invalid_related(self, content_object):
|
||||
item = Item()
|
||||
content_object.get_agenda_title.return_value = 'related_title'
|
||||
del content_object.get_agenda_title
|
||||
|
||||
with self.assertRaises(NotImplementedError):
|
||||
item.get_title()
|
||||
|
||||
def test_title_supplement_without_related(self):
|
||||
item = Item()
|
||||
self.assertEqual(
|
||||
item.get_title_supplement(),
|
||||
'')
|
||||
|
||||
@patch('openslides.agenda.models.Item.content_object')
|
||||
def test_title_supplement_with_related(self, content_object):
|
||||
item = Item()
|
||||
content_object.get_agenda_title_supplement.return_value = 'related_title_supplement'
|
||||
|
||||
self.assertEqual(
|
||||
item.get_title_supplement(),
|
||||
'related_title_supplement')
|
||||
|
||||
@patch('openslides.agenda.models.Item.content_object')
|
||||
def test_title_supplement_invalid_related(self, content_object):
|
||||
item = Item()
|
||||
del content_object.get_agenda_title_supplement
|
||||
|
||||
with self.assertRaises(NotImplementedError):
|
||||
item.get_title_supplement()
|
||||
item.title
|
||||
|
@ -308,8 +308,8 @@ class UserManagerCreateOrResetAdminUser(TestCase):
|
||||
|
||||
manager.create_or_reset_admin_user()
|
||||
|
||||
mock_group.objects.get.assert_called_once(pk=2)
|
||||
admin_user.groups.add.assert_called_once('mock_staff')
|
||||
mock_group.objects.get.assert_called_once_with(pk=4)
|
||||
admin_user.groups.add.assert_called_once_with('mock_staff')
|
||||
|
||||
def test_password_set_to_admin(self, mock_group):
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user