2019-03-13 10:58:01 +01:00
|
|
|
import sys
|
2019-07-23 12:52:28 +02:00
|
|
|
from collections import defaultdict
|
|
|
|
from typing import Dict, List
|
2019-03-13 10:58:01 +01:00
|
|
|
|
2017-02-21 09:34:24 +01:00
|
|
|
from django.apps import apps
|
2016-01-25 14:48:00 +01:00
|
|
|
from django.contrib.auth.models import Permission
|
|
|
|
from django.contrib.contenttypes.models import ContentType
|
|
|
|
from django.db.models import Q
|
2015-02-12 20:57:05 +01:00
|
|
|
from django.dispatch import Signal
|
2013-03-11 21:32:09 +01:00
|
|
|
|
2019-08-29 14:25:02 +02:00
|
|
|
from ..utils import logging
|
2019-01-20 13:06:38 +01:00
|
|
|
from ..utils.autoupdate import Element, inform_changed_elements
|
|
|
|
|
2018-07-09 23:22:26 +02:00
|
|
|
|
2017-02-21 09:34:24 +01:00
|
|
|
# This signal is send when the migrate command is done. That means it is sent
|
2015-02-12 20:57:05 +01:00
|
|
|
# after post_migrate sending and creating all Permission objects. Don't use it
|
|
|
|
# for other things than dealing with Permission objects.
|
|
|
|
post_permission_creation = Signal()
|
|
|
|
|
2017-03-06 16:34:20 +01:00
|
|
|
# This signal is sent if a permission is changed (e. g. a group gets a new
|
|
|
|
# permission). Connected receivers may yield Collections.
|
2017-02-21 09:34:24 +01:00
|
|
|
permission_change = Signal()
|
|
|
|
|
2013-03-01 17:13:12 +01:00
|
|
|
|
2016-01-25 14:48:00 +01:00
|
|
|
def delete_django_app_permissions(sender, **kwargs):
|
|
|
|
"""
|
|
|
|
Deletes the permissions, Django creates by default. Only required
|
|
|
|
for auth, contenttypes and sessions.
|
|
|
|
"""
|
|
|
|
contenttypes = ContentType.objects.filter(
|
2019-01-06 16:22:33 +01:00
|
|
|
Q(app_label="auth") | Q(app_label="contenttypes") | Q(app_label="sessions")
|
|
|
|
)
|
2016-10-01 01:30:55 +02:00
|
|
|
Permission.objects.filter(content_type__in=contenttypes).delete()
|
2017-02-21 09:34:24 +01:00
|
|
|
|
|
|
|
|
2019-07-23 12:52:28 +02:00
|
|
|
def cleanup_unused_permissions(sender, **kwargs):
|
|
|
|
"""
|
|
|
|
Deletes all permissions, that are not defined in any model meta class
|
|
|
|
"""
|
|
|
|
# Maps the content type id to codenames of perms for this content type.
|
|
|
|
content_type_codename_mapping: Dict[int, List[str]] = defaultdict(list)
|
|
|
|
|
|
|
|
# Maps content type ids to the content type.
|
|
|
|
content_type_id_mapping = {}
|
|
|
|
|
|
|
|
# Collect all perms from all apps.
|
|
|
|
for model in apps.get_models():
|
|
|
|
content_type = ContentType.objects.get_for_model(
|
|
|
|
model, for_concrete_model=False
|
|
|
|
)
|
|
|
|
content_type_id_mapping[content_type.id] = content_type
|
|
|
|
|
|
|
|
for perm in model._meta.permissions:
|
|
|
|
content_type_codename_mapping[content_type.id].append(perm[0])
|
|
|
|
|
|
|
|
# Cleanup perms per content type.
|
|
|
|
logger = logging.getLogger("openslides.core.migrations")
|
|
|
|
for content_type_id, codenames in content_type_codename_mapping.items():
|
|
|
|
app_label = content_type_id_mapping[content_type_id].app_label
|
|
|
|
unused_perms = Permission.objects.filter(
|
|
|
|
content_type__pk=content_type_id
|
|
|
|
).exclude(codename__in=codenames)
|
|
|
|
if unused_perms.exists():
|
|
|
|
verbose_permissions = ", ".join(
|
|
|
|
[f"{app_label}.{perm.codename}" for perm in unused_perms.all()]
|
|
|
|
)
|
|
|
|
logger.info(f"cleaning unused permissions: {verbose_permissions}")
|
|
|
|
unused_perms.delete()
|
|
|
|
|
|
|
|
|
2017-03-06 16:34:20 +01:00
|
|
|
def get_permission_change_data(sender, permissions, **kwargs):
|
2017-02-21 09:34:24 +01:00
|
|
|
"""
|
2018-07-09 23:22:26 +02:00
|
|
|
Yields all necessary Cachables if the respective permissions change.
|
2017-02-21 09:34:24 +01:00
|
|
|
"""
|
2019-01-06 16:22:33 +01:00
|
|
|
core_app = apps.get_app_config(app_label="core")
|
2017-02-21 09:34:24 +01:00
|
|
|
for permission in permissions:
|
|
|
|
if permission.content_type.app_label == core_app.label:
|
2019-01-06 16:22:33 +01:00
|
|
|
if permission.codename == "can_see_projector":
|
|
|
|
yield core_app.get_model("Projector")
|
|
|
|
elif permission.codename == "can_manage_projector":
|
|
|
|
yield core_app.get_model("ProjectorMessage")
|
|
|
|
yield core_app.get_model("Countdown")
|
2019-05-24 12:14:21 +02:00
|
|
|
yield core_app.get_model("ProjectionDefault")
|
2019-01-20 13:06:38 +01:00
|
|
|
|
|
|
|
|
|
|
|
def autoupdate_for_many_to_many_relations(sender, instance, **kwargs):
|
|
|
|
"""
|
|
|
|
Send autoupdate for many-to-many related objects if the other side
|
|
|
|
is deleted.
|
|
|
|
"""
|
2019-03-13 10:58:01 +01:00
|
|
|
# Hotfix for #4501: Skip autoupdate for many-to-many related objects
|
|
|
|
# during migrations.
|
|
|
|
if "migrate" in sys.argv:
|
|
|
|
return
|
|
|
|
|
2019-01-20 13:06:38 +01:00
|
|
|
m2m_fields = (
|
|
|
|
field
|
|
|
|
for field in instance._meta.get_fields(include_hidden=True)
|
|
|
|
if field.many_to_many and field.auto_created
|
|
|
|
)
|
|
|
|
for field in m2m_fields:
|
|
|
|
queryset = getattr(instance, field.get_accessor_name()).all()
|
|
|
|
for related_instance in queryset:
|
|
|
|
if hasattr(related_instance, "get_root_rest_element"):
|
|
|
|
# The related instance is or has a root rest element.
|
|
|
|
# So lets send it via autoupdate.
|
|
|
|
root_rest_element = related_instance.get_root_rest_element()
|
|
|
|
inform_changed_elements(
|
|
|
|
[
|
|
|
|
Element(
|
|
|
|
collection_string=root_rest_element.get_collection_string(),
|
|
|
|
id=root_rest_element.pk,
|
|
|
|
full_data=None,
|
|
|
|
reload=True,
|
|
|
|
)
|
|
|
|
]
|
|
|
|
)
|