Merge pull request #3336 from normanjaeckel/RemoveElementsProjector
Unproject elements when they are deleted. Fixed #3292.
This commit is contained in:
commit
0c8bf5c5ba
@ -62,6 +62,7 @@ Core:
|
|||||||
- Highlight list entries in a light blue, if a related object is projected
|
- Highlight list entries in a light blue, if a related object is projected
|
||||||
(e. g. a list of speakers of a motion) [#3301].
|
(e. g. a list of speakers of a motion) [#3301].
|
||||||
- Select the projector resolution with a slider and an aspect ratio [#3311].
|
- Select the projector resolution with a slider and an aspect ratio [#3311].
|
||||||
|
- Fixed bug the elements are projected and the deleted [#3336].
|
||||||
- Delay the 'could not load projector' error 3 seconds to not irritate users
|
- Delay the 'could not load projector' error 3 seconds to not irritate users
|
||||||
with a slow internet connection [#3323].
|
with a slow internet connection [#3323].
|
||||||
- Added config value for standard font size in PDF [#3332].
|
- Added config value for standard font size in PDF [#3332].
|
||||||
|
@ -10,7 +10,7 @@ from django.utils.translation import ugettext as _
|
|||||||
from django.utils.translation import ugettext_lazy
|
from django.utils.translation import ugettext_lazy
|
||||||
|
|
||||||
from openslides.core.config import config
|
from openslides.core.config import config
|
||||||
from openslides.core.models import Countdown
|
from openslides.core.models import Countdown, Projector
|
||||||
from openslides.utils.exceptions import OpenSlidesError
|
from openslides.utils.exceptions import OpenSlidesError
|
||||||
from openslides.utils.models import RESTModelMixin
|
from openslides.utils.models import RESTModelMixin
|
||||||
from openslides.utils.utils import to_roman
|
from openslides.utils.utils import to_roman
|
||||||
@ -284,6 +284,17 @@ class Item(RESTModelMixin, models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
|
def delete(self, skip_autoupdate=False, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Customized method to delete an agenda item. Ensures that a respective
|
||||||
|
list of speakers projector element is disabled.
|
||||||
|
"""
|
||||||
|
Projector.remove_any(
|
||||||
|
skip_autoupdate=skip_autoupdate,
|
||||||
|
name='agenda/list-of-speakers',
|
||||||
|
id=self.pk)
|
||||||
|
return super().delete(skip_autoupdate=skip_autoupdate, *args, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def title(self):
|
def title(self):
|
||||||
"""
|
"""
|
||||||
|
@ -34,6 +34,8 @@ def listen_to_related_object_post_delete(sender, instance, **kwargs):
|
|||||||
if hasattr(instance, 'get_agenda_title'):
|
if hasattr(instance, 'get_agenda_title'):
|
||||||
content_type = ContentType.objects.get_for_model(instance)
|
content_type = ContentType.objects.get_for_model(instance)
|
||||||
try:
|
try:
|
||||||
|
# Attention: This delete() call is also necessary to remove
|
||||||
|
# respective active list of speakers projector elements.
|
||||||
Item.objects.get(object_id=instance.pk, content_type=content_type).delete()
|
Item.objects.get(object_id=instance.pk, content_type=content_type).delete()
|
||||||
except Item.DoesNotExist:
|
except Item.DoesNotExist:
|
||||||
# Item does not exist so we do not have to delete it.
|
# Item does not exist so we do not have to delete it.
|
||||||
|
@ -8,7 +8,7 @@ from django.utils.translation import ugettext_noop
|
|||||||
|
|
||||||
from openslides.agenda.models import Item, Speaker
|
from openslides.agenda.models import Item, Speaker
|
||||||
from openslides.core.config import config
|
from openslides.core.config import config
|
||||||
from openslides.core.models import Tag
|
from openslides.core.models import Projector, Tag
|
||||||
from openslides.poll.models import (
|
from openslides.poll.models import (
|
||||||
BaseOption,
|
BaseOption,
|
||||||
BasePoll,
|
BasePoll,
|
||||||
@ -165,14 +165,16 @@ class Assignment(RESTModelMixin, models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
def get_slide_context(self, **context):
|
def delete(self, skip_autoupdate=False, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Retuns the context to generate the assignment slide.
|
Customized method to delete an assignment. Ensures that a respective
|
||||||
|
assignment projector element is disabled.
|
||||||
"""
|
"""
|
||||||
return super().get_slide_context(
|
Projector.remove_any(
|
||||||
polls=self.polls.filter(published=True),
|
skip_autoupdate=skip_autoupdate,
|
||||||
vote_results=self.vote_results(only_published=True),
|
name='assignments/assignment',
|
||||||
**context)
|
id=self.pk)
|
||||||
|
return super().delete(skip_autoupdate=skip_autoupdate, *args, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def candidates(self):
|
def candidates(self):
|
||||||
@ -415,6 +417,18 @@ class AssignmentPoll(RESTModelMixin, CollectDefaultVotesMixin,
|
|||||||
class Meta:
|
class Meta:
|
||||||
default_permissions = ()
|
default_permissions = ()
|
||||||
|
|
||||||
|
def delete(self, skip_autoupdate=False, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Customized method to delete an assignment poll. Ensures that a respective
|
||||||
|
assignment projector element (with poll, so called poll slide) is disabled.
|
||||||
|
"""
|
||||||
|
Projector.remove_any(
|
||||||
|
skip_autoupdate=skip_autoupdate,
|
||||||
|
name='assignments/assignment',
|
||||||
|
id=self.assignment.pk,
|
||||||
|
poll=self.pk)
|
||||||
|
return super().delete(skip_autoupdate=skip_autoupdate, *args, **kwargs)
|
||||||
|
|
||||||
def get_assignment(self):
|
def get_assignment(self):
|
||||||
return self.assignment
|
return self.assignment
|
||||||
|
|
||||||
@ -432,9 +446,6 @@ class AssignmentPoll(RESTModelMixin, CollectDefaultVotesMixin,
|
|||||||
def get_percent_base_choice(self):
|
def get_percent_base_choice(self):
|
||||||
return config['assignments_poll_100_percent_base']
|
return config['assignments_poll_100_percent_base']
|
||||||
|
|
||||||
def get_slide_context(self, **context):
|
|
||||||
return super().get_slide_context(poll=self)
|
|
||||||
|
|
||||||
def get_root_rest_element(self):
|
def get_root_rest_element(self):
|
||||||
"""
|
"""
|
||||||
Returns the assignment to this instance which is the root REST element.
|
Returns the assignment to this instance which is the root REST element.
|
||||||
|
@ -192,6 +192,36 @@ class Projector(RESTModelMixin, models.Model):
|
|||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def remove_any(cls, skip_autoupdate=False, **kwargs):
|
||||||
|
"""
|
||||||
|
Removes all projector elements from all projectors with matching kwargs.
|
||||||
|
Additional properties of active projector elements are ignored:
|
||||||
|
|
||||||
|
Example: Sending {'name': 'assignments/assignment', 'id': 1} will remove
|
||||||
|
also projector elements with {'name': 'assignments/assignment', 'id': 1, 'poll': 2}.
|
||||||
|
"""
|
||||||
|
# Loop over all projectors.
|
||||||
|
for projector in cls.objects.all():
|
||||||
|
change_projector_config = False
|
||||||
|
projector_config = {}
|
||||||
|
# Loop over all projector elements of this projector.
|
||||||
|
for key, value in projector.config.items():
|
||||||
|
# Check if the kwargs match this element.
|
||||||
|
for kwarg_key, kwarg_value in kwargs.items():
|
||||||
|
if not value.get(kwarg_key) == kwarg_value:
|
||||||
|
# No match so the element should stay. Write it into
|
||||||
|
# new config field and break the loop.
|
||||||
|
projector_config[key] = value
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# All kwargs match this projector element. So mark this
|
||||||
|
# projector to be changed. Do not write it into new config field.
|
||||||
|
change_projector_config = True
|
||||||
|
if change_projector_config:
|
||||||
|
projector.config = projector_config
|
||||||
|
projector.save(skip_autoupdate=skip_autoupdate)
|
||||||
|
|
||||||
|
|
||||||
class ProjectionDefault(RESTModelMixin, models.Model):
|
class ProjectionDefault(RESTModelMixin, models.Model):
|
||||||
"""
|
"""
|
||||||
@ -308,6 +338,17 @@ class ProjectorMessage(RESTModelMixin, models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
default_permissions = ()
|
default_permissions = ()
|
||||||
|
|
||||||
|
def delete(self, skip_autoupdate=False, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Customized method to delete a projector message. Ensures that a respective
|
||||||
|
projector message projector element is disabled.
|
||||||
|
"""
|
||||||
|
Projector.remove_any(
|
||||||
|
skip_autoupdate=skip_autoupdate,
|
||||||
|
name='core/projector-message',
|
||||||
|
id=self.pk)
|
||||||
|
return super().delete(skip_autoupdate=skip_autoupdate, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Countdown(RESTModelMixin, models.Model):
|
class Countdown(RESTModelMixin, models.Model):
|
||||||
"""
|
"""
|
||||||
@ -326,6 +367,17 @@ class Countdown(RESTModelMixin, models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
default_permissions = ()
|
default_permissions = ()
|
||||||
|
|
||||||
|
def delete(self, skip_autoupdate=False, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Customized method to delete a countdown. Ensures that a respective
|
||||||
|
countdown projector element is disabled.
|
||||||
|
"""
|
||||||
|
Projector.remove_any(
|
||||||
|
skip_autoupdate=skip_autoupdate,
|
||||||
|
name='core/countdown',
|
||||||
|
id=self.pk)
|
||||||
|
return super().delete(skip_autoupdate=skip_autoupdate, *args, **kwargs)
|
||||||
|
|
||||||
def control(self, action):
|
def control(self, action):
|
||||||
if action not in ('start', 'stop', 'reset'):
|
if action not in ('start', 'stop', 'reset'):
|
||||||
raise ValueError("Action must be 'start', 'stop' or 'reset', not {}.".format(action))
|
raise ValueError("Action must be 'start', 'stop' or 'reset', not {}.".format(action))
|
||||||
|
@ -1343,10 +1343,6 @@ angular.module('OpenSlidesApp.core.site', [
|
|||||||
Countdown.create(countdown);
|
Countdown.create(countdown);
|
||||||
};
|
};
|
||||||
$scope.removeCountdown = function (countdown) {
|
$scope.removeCountdown = function (countdown) {
|
||||||
var isProjectedIds = countdown.isProjected();
|
|
||||||
_.forEach(isProjectedIds, function(id) {
|
|
||||||
countdown.project(id);
|
|
||||||
});
|
|
||||||
Countdown.destroy(countdown.id);
|
Countdown.destroy(countdown.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1359,10 +1355,6 @@ angular.module('OpenSlidesApp.core.site', [
|
|||||||
ProjectorMessage.create(message);
|
ProjectorMessage.create(message);
|
||||||
};
|
};
|
||||||
$scope.removeMessage = function (message) {
|
$scope.removeMessage = function (message) {
|
||||||
var isProjectedIds = message.isProjected();
|
|
||||||
_.forEach(isProjectedIds, function(id) {
|
|
||||||
message.project(id);
|
|
||||||
});
|
|
||||||
ProjectorMessage.destroy(message.id);
|
ProjectorMessage.destroy(message.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ from django.conf import settings
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
|
from ..core.models import Projector
|
||||||
from ..utils.autoupdate import inform_changed_data
|
from ..utils.autoupdate import inform_changed_data
|
||||||
from ..utils.models import RESTModelMixin
|
from ..utils.models import RESTModelMixin
|
||||||
from .access_permissions import MediafileAccessPermissions
|
from .access_permissions import MediafileAccessPermissions
|
||||||
@ -63,6 +64,17 @@ class Mediafile(RESTModelMixin, models.Model):
|
|||||||
inform_changed_data(self.uploader)
|
inform_changed_data(self.uploader)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def delete(self, skip_autoupdate=False, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Customized method to delete a mediafile. Ensures that a respective
|
||||||
|
mediafile projector element is disabled.
|
||||||
|
"""
|
||||||
|
Projector.remove_any(
|
||||||
|
skip_autoupdate=skip_autoupdate,
|
||||||
|
name='mediafiles/mediafile',
|
||||||
|
id=self.pk)
|
||||||
|
return super().delete(skip_autoupdate=skip_autoupdate, *args, **kwargs)
|
||||||
|
|
||||||
def get_filesize(self):
|
def get_filesize(self):
|
||||||
"""
|
"""
|
||||||
Transforms bytes to kilobytes or megabytes. Returns the size as string.
|
Transforms bytes to kilobytes or megabytes. Returns the size as string.
|
||||||
|
@ -10,7 +10,7 @@ from jsonfield import JSONField
|
|||||||
|
|
||||||
from openslides.agenda.models import Item
|
from openslides.agenda.models import Item
|
||||||
from openslides.core.config import config
|
from openslides.core.config import config
|
||||||
from openslides.core.models import Tag
|
from openslides.core.models import Projector, Tag
|
||||||
from openslides.mediafiles.models import Mediafile
|
from openslides.mediafiles.models import Mediafile
|
||||||
from openslides.poll.models import (
|
from openslides.poll.models import (
|
||||||
BaseOption,
|
BaseOption,
|
||||||
@ -300,6 +300,17 @@ class Motion(RESTModelMixin, models.Model):
|
|||||||
if not skip_autoupdate:
|
if not skip_autoupdate:
|
||||||
inform_changed_data(self)
|
inform_changed_data(self)
|
||||||
|
|
||||||
|
def delete(self, skip_autoupdate=False, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Customized method to delete a motion. Ensures that a respective
|
||||||
|
motion projector element is disabled.
|
||||||
|
"""
|
||||||
|
Projector.remove_any(
|
||||||
|
skip_autoupdate=skip_autoupdate,
|
||||||
|
name='motions/motion',
|
||||||
|
id=self.pk)
|
||||||
|
return super().delete(skip_autoupdate=skip_autoupdate, *args, **kwargs)
|
||||||
|
|
||||||
def version_data_changed(self, version):
|
def version_data_changed(self, version):
|
||||||
"""
|
"""
|
||||||
Compare the version with the last version of the motion.
|
Compare the version with the last version of the motion.
|
||||||
@ -859,6 +870,17 @@ class MotionBlock(RESTModelMixin, models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
|
def delete(self, skip_autoupdate=False, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Customized method to delete a motion block. Ensures that a respective
|
||||||
|
motion block projector element is disabled.
|
||||||
|
"""
|
||||||
|
Projector.remove_any(
|
||||||
|
skip_autoupdate=skip_autoupdate,
|
||||||
|
name='motions/motion-block',
|
||||||
|
id=self.pk)
|
||||||
|
return super().delete(skip_autoupdate=skip_autoupdate, *args, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def agenda_item(self):
|
def agenda_item(self):
|
||||||
"""
|
"""
|
||||||
|
@ -2,6 +2,7 @@ from django.contrib.contenttypes.fields import GenericRelation
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from ..agenda.models import Item
|
from ..agenda.models import Item
|
||||||
|
from ..core.models import Projector
|
||||||
from ..mediafiles.models import Mediafile
|
from ..mediafiles.models import Mediafile
|
||||||
from ..utils.models import RESTModelMixin
|
from ..utils.models import RESTModelMixin
|
||||||
from .access_permissions import TopicAccessPermissions
|
from .access_permissions import TopicAccessPermissions
|
||||||
@ -42,6 +43,17 @@ class Topic(RESTModelMixin, models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
|
def delete(self, skip_autoupdate=False, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Customized method to delete a topic. Ensures that a respective
|
||||||
|
topic projector element is disabled.
|
||||||
|
"""
|
||||||
|
Projector.remove_any(
|
||||||
|
skip_autoupdate=skip_autoupdate,
|
||||||
|
name='topics/topic',
|
||||||
|
id=self.pk)
|
||||||
|
return super().delete(skip_autoupdate=skip_autoupdate, *args, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def agenda_item(self):
|
def agenda_item(self):
|
||||||
"""
|
"""
|
||||||
|
@ -13,6 +13,7 @@ from django.db import models
|
|||||||
from django.db.models import Prefetch, Q
|
from django.db.models import Prefetch, Q
|
||||||
from jsonfield import JSONField
|
from jsonfield import JSONField
|
||||||
|
|
||||||
|
from ..core.models import Projector
|
||||||
from ..utils.collection import CollectionElement
|
from ..utils.collection import CollectionElement
|
||||||
from ..utils.models import RESTModelMixin
|
from ..utils.models import RESTModelMixin
|
||||||
from .access_permissions import (
|
from .access_permissions import (
|
||||||
@ -210,6 +211,17 @@ class User(RESTModelMixin, PermissionsMixin, AbstractBaseUser):
|
|||||||
CollectionElement.from_instance(self)
|
CollectionElement.from_instance(self)
|
||||||
return super().save(*args, **kwargs)
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
def delete(self, skip_autoupdate=False, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Customized method to delete an user. Ensures that a respective
|
||||||
|
user projector element is disabled.
|
||||||
|
"""
|
||||||
|
Projector.remove_any(
|
||||||
|
skip_autoupdate=skip_autoupdate,
|
||||||
|
name='users/user',
|
||||||
|
id=self.pk)
|
||||||
|
return super().delete(skip_autoupdate=skip_autoupdate, *args, **kwargs)
|
||||||
|
|
||||||
def has_perm(self, perm):
|
def has_perm(self, perm):
|
||||||
"""
|
"""
|
||||||
This method is closed. Do not use it but use openslides.utils.auth.has_perm.
|
This method is closed. Do not use it but use openslides.utils.auth.has_perm.
|
||||||
|
Loading…
Reference in New Issue
Block a user