import logging import sys from collections import defaultdict from typing import Dict, List from django.apps import apps from django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType from django.db.models import Q from django.dispatch import Signal from ..utils.autoupdate import Element, inform_changed_elements # This signal is send when the migrate command is done. That means it is sent # 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() # This signal is sent if a permission is changed (e. g. a group gets a new # permission). Connected receivers may yield Collections. permission_change = Signal() 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( Q(app_label="auth") | Q(app_label="contenttypes") | Q(app_label="sessions") ) Permission.objects.filter(content_type__in=contenttypes).delete() 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() def get_permission_change_data(sender, permissions, **kwargs): """ Yields all necessary Cachables if the respective permissions change. """ core_app = apps.get_app_config(app_label="core") for permission in permissions: if permission.content_type.app_label == core_app.label: 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") yield core_app.get_model("ProjectionDefault") def autoupdate_for_many_to_many_relations(sender, instance, **kwargs): """ Send autoupdate for many-to-many related objects if the other side is deleted. """ # Hotfix for #4501: Skip autoupdate for many-to-many related objects # during migrations. if "migrate" in sys.argv: return 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, ) ] )