OpenSlides/openslides/utils/collection.py

161 lines
5.9 KiB
Python
Raw Normal View History

2018-08-22 07:59:22 +02:00
from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Type
2017-08-23 20:51:06 +02:00
from asgiref.sync import async_to_sync
from django.apps import apps
2017-08-24 12:26:55 +02:00
from django.db.models import Model
from mypy_extensions import TypedDict
from .cache import element_cache
2017-08-24 12:26:55 +02:00
if TYPE_CHECKING:
2018-08-22 22:00:08 +02:00
from .access_permissions import BaseAccessPermissions
AutoupdateFormat = TypedDict(
'AutoupdateFormat',
2018-10-14 08:26:51 +02:00
{
'changed': Dict[str, List[Dict[str, Any]]],
'deleted': Dict[str, List[int]],
'from_change_id': int,
'to_change_id': int,
'all_data': bool,
2018-10-14 08:26:51 +02:00
},
)
2017-08-24 12:26:55 +02:00
class CollectionElement:
2018-08-08 21:09:22 +02:00
def __init__(self, instance: Model = None, deleted: bool = False, collection_string: str = None,
2018-08-22 07:59:22 +02:00
id: int = None, full_data: Dict[str, Any] = None) -> None:
"""
Do not use this. Use the methods from_instance() or from_values().
"""
self.instance = instance
self.deleted = deleted
self.full_data = full_data
if instance is not None:
# Collection element is created via instance
self.collection_string = instance.get_collection_string()
2017-08-22 14:17:20 +02:00
self.id = instance.pk
elif collection_string is not None and id is not None:
# Collection element is created via values
self.collection_string = collection_string
2017-08-24 12:26:55 +02:00
self.id = id
else:
raise RuntimeError(
'Invalid state. Use CollectionElement.from_instance() or '
'CollectionElement.from_values() but not CollectionElement() '
'directly.')
if not self.deleted:
self.get_full_data() # This raises DoesNotExist, if the element does not exist.
2017-08-24 12:26:55 +02:00
@classmethod
def from_instance(
2018-08-22 07:59:22 +02:00
cls, instance: Model, deleted: bool = False) -> 'CollectionElement':
2017-08-24 12:26:55 +02:00
"""
Returns a collection element from a database instance.
This will also update the instance in the cache.
If deleted is set to True, the element is deleted from the cache.
"""
2018-08-22 07:59:22 +02:00
return cls(instance=instance, deleted=deleted)
2017-08-24 12:26:55 +02:00
@classmethod
2018-08-08 21:09:22 +02:00
def from_values(cls, collection_string: str, id: int, deleted: bool = False,
2018-08-22 07:59:22 +02:00
full_data: Dict[str, Any] = None) -> 'CollectionElement':
2017-08-24 12:26:55 +02:00
"""
Returns a collection element from a collection_string and an id.
If deleted is set to True, the element is deleted from the cache.
With the argument full_data, the content of the CollectionElement can be set.
It has to be a dict in the format that is used be access_permission.get_full_data().
"""
2018-08-22 07:59:22 +02:00
return cls(collection_string=collection_string, id=id, deleted=deleted, full_data=full_data)
2017-08-24 12:26:55 +02:00
def __eq__(self, collection_element: 'CollectionElement') -> bool: # type: ignore
"""
Compares two collection_elements.
Two collection elements are equal, if they have the same collection_string
and id.
"""
return (self.collection_string == collection_element.collection_string and
self.id == collection_element.id)
2017-08-24 12:26:55 +02:00
def get_model(self) -> Type[Model]:
"""
Returns the django model that is used for this collection.
"""
return get_model_from_collection_string(self.collection_string)
2017-08-24 12:26:55 +02:00
def get_access_permissions(self) -> 'BaseAccessPermissions':
"""
Returns the get_access_permissions object for the this collection element.
"""
return self.get_model().get_access_permissions()
def get_full_data(self) -> Dict[str, Any]:
"""
Returns the full_data of this collection_element from with all other
dics can be generated.
Raises a DoesNotExist error on the requested the coresponding model, if
the object does neither exist in the cache nor in the database.
"""
# If the full_data is already loaded, return it
# If there is a db_instance, use it to get the full_data
# else: use the cache.
if self.full_data is None:
if self.instance is None:
# The type of data has to be set for mypy
2018-08-22 22:00:08 +02:00
data: Optional[Dict[str, Any]] = None
2018-09-01 08:00:00 +02:00
data = async_to_sync(element_cache.get_element_full_data)(self.collection_string, self.id)
if data is None:
raise self.get_model().DoesNotExist(
"Collection {} with id {} does not exist".format(self.collection_string, self.id))
self.full_data = data
else:
self.full_data = self.get_access_permissions().get_full_data(self.instance)
return self.full_data
2017-08-24 12:26:55 +02:00
def is_deleted(self) -> bool:
"""
Returns Ture if the item is marked as deleted.
"""
return self.deleted
2018-08-22 22:00:08 +02:00
_models_to_collection_string: Dict[str, Type[Model]] = {}
2017-08-24 12:26:55 +02:00
def get_model_from_collection_string(collection_string: str) -> Type[Model]:
"""
Returns a model class which belongs to the argument collection_string.
"""
2017-08-24 12:26:55 +02:00
def model_generator() -> Generator[Type[Model], None, None]:
"""
Yields all models of all apps.
"""
for app_config in apps.get_app_configs():
for model in app_config.get_models():
yield model
# On the first run, generate the dict. It can not change at runtime.
if not _models_to_collection_string:
for model in model_generator():
try:
get_collection_string = model.get_collection_string
except AttributeError:
# Skip models which do not have the method get_collection_string.
pass
else:
_models_to_collection_string[get_collection_string()] = model
try:
model = _models_to_collection_string[collection_string]
except KeyError:
raise ValueError('Invalid message. A valid collection_string is missing. Got {}'.format(collection_string))
return model