Projector V

* Changed wording: element is one element on the projector. A slide is a functoin to render one element
* Use AllData as first argument all the time
* Render username on server
* Add exceptions for erros on projector
* Fix motion recommendation
* Only show state extension, if it is allowed by the state
* Add motion_change_recommendations to motion full_data
This commit is contained in:
Oskar Hahn 2019-01-27 13:17:17 +01:00
parent 0d8cbbaab9
commit 1a709a59a9
22 changed files with 142 additions and 116 deletions

View File

@ -13,7 +13,7 @@ class AgendaAppConfig(AppConfig):
from django.db.models.signals import pre_delete, post_save
from ..core.signals import permission_change
from ..utils.rest_api import router
from .projector import register_projector_elements
from .projector import register_projector_slides
from .signals import (
get_permission_change_data,
listen_to_related_object_post_delete,
@ -24,7 +24,7 @@ class AgendaAppConfig(AppConfig):
from ..utils.access_permissions import required_user
# Define projector elements.
register_projector_elements()
register_projector_slides()
# Connect signals.
post_save.connect(

View File

@ -1,7 +1,11 @@
from collections import defaultdict
from typing import Any, Dict, List, Tuple
from ..utils.projector import AllData, register_projector_element
from ..utils.projector import (
AllData,
ProjectorElementException,
register_projector_slide,
)
# Important: All functions have to be prune. This means, that thay can only
@ -41,7 +45,7 @@ def get_tree(
return get_children(children[parent_id])
def items(element: Dict[str, Any], all_data: AllData) -> Dict[str, Any]:
def items_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
"""
Item list slide.
@ -63,7 +67,9 @@ def items(element: Dict[str, Any], all_data: AllData) -> Dict[str, Any]:
return {"items": agenda_items}
def list_of_speakers(element: Dict[str, Any], all_data: AllData) -> Dict[str, Any]:
def list_of_speakers_slide(
all_data: AllData, element: Dict[str, Any]
) -> Dict[str, Any]:
"""
List of speakers slide.
@ -76,7 +82,7 @@ def list_of_speakers(element: Dict[str, Any], all_data: AllData) -> Dict[str, An
try:
item = all_data["agenda/item"][item_id]
except KeyError:
return {"error": f"Item {item_id} does not exist"}
raise ProjectorElementException(f"Item {item_id} does not exist")
user_ids = []
for speaker in item["speakers"]:
@ -84,6 +90,6 @@ def list_of_speakers(element: Dict[str, Any], all_data: AllData) -> Dict[str, An
return {"user_ids": user_ids}
def register_projector_elements() -> None:
register_projector_element("agenda/item-list", items)
register_projector_element("agenda/list-of-speakers", list_of_speakers)
def register_projector_slides() -> None:
register_projector_slide("agenda/item-list", items_slide)
register_projector_slide("agenda/list-of-speakers", list_of_speakers_slide)

View File

@ -15,12 +15,12 @@ class AssignmentsAppConfig(AppConfig):
from ..utils.access_permissions import required_user
from ..utils.rest_api import router
from . import serializers # noqa
from .projector import register_projector_elements
from .projector import register_projector_slides
from .signals import get_permission_change_data
from .views import AssignmentViewSet, AssignmentPollViewSet
# Define projector elements.
register_projector_elements()
register_projector_slides()
# Connect signals.
permission_change.connect(

View File

@ -1,6 +1,6 @@
from typing import Any, Dict
from ..utils.projector import register_projector_element
from ..utils.projector import AllData, register_projector_slide
# Important: All functions have to be prune. This means, that thay can only
@ -9,9 +9,7 @@ from ..utils.projector import register_projector_element
# to be fast!
def assignment(
element: Dict[str, Any], all_data: Dict[str, Dict[int, Dict[str, Any]]]
) -> Dict[str, Any]:
def assignment_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
"""
Assignment slide.
"""
@ -19,5 +17,5 @@ def assignment(
return {"error": "TODO"}
def register_projector_elements() -> None:
register_projector_element("assignments/assignment", assignment)
def register_projector_slides() -> None:
register_projector_slide("assignments/assignment", assignment_slide)

View File

@ -17,7 +17,7 @@ class CoreAppConfig(AppConfig):
def ready(self):
# Import all required stuff.
from .config import config
from .projector import register_projector_elements
from .projector import register_projector_slides
from . import serializers # noqa
from .signals import (
delete_django_app_permissions,
@ -49,7 +49,7 @@ class CoreAppConfig(AppConfig):
config.collect_config_variables_from_apps()
# Define projector elements.
register_projector_elements()
register_projector_slides()
# Connect signals.
post_permission_creation.connect(

View File

@ -1,6 +1,6 @@
from typing import Any, Dict
from ..utils.projector import register_projector_element
from ..utils.projector import AllData, register_projector_slide
# Important: All functions have to be prune. This means, that thay can only
@ -9,9 +9,7 @@ from ..utils.projector import register_projector_element
# to be fast!
def countdown(
element: Dict[str, Any], all_data: Dict[str, Dict[int, Dict[str, Any]]]
) -> Dict[str, Any]:
def countdown_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
"""
Countdown slide.
@ -30,9 +28,7 @@ def countdown(
return {"error": f"Countdown {countdown_id} does not exist"}
def message(
element: Dict[str, Any], all_data: Dict[str, Dict[int, Dict[str, Any]]]
) -> Dict[str, Any]:
def message_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
"""
Message slide.
@ -51,7 +47,7 @@ def message(
return {"error": f"Message {message_id} does not exist"}
def register_projector_elements() -> None:
register_projector_element("core/countdown", countdown)
register_projector_element("core/projector-message", message)
def register_projector_slides() -> None:
register_projector_slide("core/countdown", countdown_slide)
register_projector_slide("core/projector-message", message_slide)
# TODO: Add clock slide

View File

@ -1,6 +1,6 @@
from typing import Any
from ..utils.projector import projector_elements
from ..utils.projector import projector_slides
from ..utils.rest_api import Field, IntegerField, ModelSerializer, ValidationError
from ..utils.validate import validate_html
from .models import (
@ -56,7 +56,7 @@ def elements_validator(value: Any) -> None:
raise ValidationError(
{"detail": "Every dictionary must have a key 'name'."}
)
if element["name"] not in projector_elements:
if element["name"] not in projector_slides:
raise ValidationError(
{"detail": f"Unknown projector element {element['name']},"}
)

View File

@ -12,14 +12,14 @@ class MediafilesAppConfig(AppConfig):
# Import all required stuff.
from openslides.core.signals import permission_change
from openslides.utils.rest_api import router
from .projector import register_projector_elements
from .projector import register_projector_slides
from .signals import get_permission_change_data
from .views import MediafileViewSet
from . import serializers # noqa
from ..utils.access_permissions import required_user
# Define projector elements.
register_projector_elements()
register_projector_slides()
# Connect signals.
permission_change.connect(

View File

@ -1,6 +1,6 @@
from typing import Any, Dict
from ..utils.projector import register_projector_element
from ..utils.projector import AllData, register_projector_slide
# Important: All functions have to be prune. This means, that thay can only
@ -9,14 +9,12 @@ from ..utils.projector import register_projector_element
# to be fast!
def mediafile(
element: Dict[str, Any], all_data: Dict[str, Dict[int, Dict[str, Any]]]
) -> Dict[str, Any]:
def mediafile_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
"""
Slide for Mediafile.
"""
return {"error": "TODO"}
def register_projector_elements() -> None:
register_projector_element("mediafiles/mediafile", mediafile)
def register_projector_slides() -> None:
register_projector_slide("mediafiles/mediafile", mediafile_slide)

View File

@ -13,7 +13,7 @@ class MotionsAppConfig(AppConfig):
# Import all required stuff.
from openslides.core.signals import permission_change
from openslides.utils.rest_api import router
from .projector import register_projector_elements
from .projector import register_projector_slides
from .signals import create_builtin_workflows, get_permission_change_data
from . import serializers # noqa
from .views import (
@ -30,7 +30,7 @@ class MotionsAppConfig(AppConfig):
from ..utils.access_permissions import required_user
# Define projector elements.
register_projector_elements()
register_projector_slides()
# Connect signals.
post_migrate.connect(

View File

@ -87,6 +87,7 @@ class MotionManager(models.Manager):
"tags",
"submitters",
"supporters",
"change_recommendations",
)
)

View File

@ -1,6 +1,12 @@
from typing import Any, Dict
from ..utils.projector import AllData, get_config, get_user, register_projector_element
from ..users.projector import get_user_name
from ..utils.projector import (
AllData,
ProjectorElementException,
get_config,
register_projector_slide,
)
# Important: All functions have to be prune. This means, that thay can only
@ -21,10 +27,12 @@ def get_state(
for state in states:
if state["id"] == state_id:
return state
return {"error": f"motion {motion['id']} can not have to id {state_id}"}
raise ProjectorElementException(
f"motion {motion['id']} can not be on the state with id {state_id}"
)
def motion_slide(element: Dict[str, Any], all_data: AllData) -> Dict[str, Any]:
def motion_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
"""
Motion slide.
@ -53,7 +61,7 @@ def motion_slide(element: Dict[str, Any], all_data: AllData) -> Dict[str, Any]:
try:
motion = all_data["motions/motion"][motion_id]
except KeyError:
return {"error": f"motion with id {motion_id} does not exist"}
raise ProjectorElementException(f"motion with id {motion_id} does not exist")
show_meta_box = not get_config(all_data, "motions_disable_sidebox_on_projector")
@ -68,31 +76,38 @@ def motion_slide(element: Dict[str, Any], all_data: AllData) -> Dict[str, Any]:
if not get_config(all_data, "motions_disable_reason_on_projector"):
return_value["reason"] = motion["reason"]
if mode == "final":
return_value["modified_final_version"] = motion["modified_final_version"]
if show_meta_box:
state = get_state(all_data, motion, motion["state_id"])
if state.get("error"):
return state
return_value["state"] = state["name"]
return_value["state_extension"] = motion["state_extension"]
if state["show_state_extension_field"]:
return_value["state_extension"] = motion["state_extension"]
if (
not get_config(all_data, "motions_disable_recommendation_on_projector")
and motion["recommendation_id"]
):
return_value["recommendation"] = all_data[
"motions/motion-change-recommendation"
][motion["recommendation_id"]]["text"]
return_value["recommendation_extension"] = motion[
"recommendation_extension"
recommendation_state = get_state(
all_data, motion, motion["recommendation_id"]
)
return_value["recommendation"] = recommendation_state[
"recommendation_label"
]
if recommendation_state["show_recommendation_extension_field"]:
return_value["recommendation_extension"] = motion[
"recommendation_extension"
]
return_value["change_recommendations"] = motion["change_recommendations"]
return_value["submitter"] = [
get_user(all_data, submitter["user_id"])
for submitter in motion["submitters"]
get_user_name(all_data, submitter["user_id"])
for submitter in sorted(
motion["submitters"], key=lambda submitter: submitter["weight"]
)
]
for poll in motion["polls"][::-1]:
@ -107,13 +122,13 @@ def motion_slide(element: Dict[str, Any], all_data: AllData) -> Dict[str, Any]:
return return_value
def motion_block(element: Dict[str, Any], all_data: AllData) -> Dict[str, Any]:
def motion_block_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
"""
Motion slide.
"""
return {"error": "TODO"}
def register_projector_elements() -> None:
register_projector_element("motions/motion", motion_slide)
register_projector_element("motions/motion-block", motion_block)
def register_projector_slides() -> None:
register_projector_slide("motions/motion", motion_slide)
register_projector_slide("motions/motion-block", motion_block_slide)

View File

@ -400,6 +400,9 @@ class MotionSerializer(ModelSerializer):
)
agenda_parent_id = IntegerField(write_only=True, required=False, min_value=1)
submitters = SubmitterSerializer(many=True, read_only=True)
change_recommendations = MotionChangeRecommendationSerializer(
many=True, read_only=True
)
class Meta:
model = Motion
@ -436,6 +439,7 @@ class MotionSerializer(ModelSerializer):
"weight",
"created",
"last_modified",
"change_recommendations",
)
read_only_fields = (
"state",

View File

@ -10,13 +10,13 @@ class TopicsAppConfig(AppConfig):
# Import all required stuff.
from openslides.core.signals import permission_change
from ..utils.rest_api import router
from .projector import register_projector_elements
from .projector import register_projector_slides
from .signals import get_permission_change_data
from .views import TopicViewSet
from . import serializers # noqa
# Define projector elements.
register_projector_elements()
register_projector_slides()
# Connect signals.
permission_change.connect(

View File

@ -1,6 +1,6 @@
from typing import Any, Dict
from ..utils.projector import register_projector_element
from ..utils.projector import AllData, register_projector_slide
# Important: All functions have to be prune. This means, that thay can only
@ -9,14 +9,12 @@ from ..utils.projector import register_projector_element
# to be fast!
def topic(
element: Dict[str, Any], all_data: Dict[str, Dict[int, Dict[str, Any]]]
) -> Dict[str, Any]:
def topic_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
"""
Topic slide.
"""
return {"error": "TODO"}
def register_projector_elements() -> None:
register_projector_element("topics/topic", topic)
def register_projector_slides() -> None:
register_projector_slide("topics/topic", topic_slide)

View File

@ -13,12 +13,12 @@ class UsersAppConfig(AppConfig):
from . import serializers # noqa
from ..core.signals import post_permission_creation, permission_change
from ..utils.rest_api import router
from .projector import register_projector_elements
from .projector import register_projector_slides
from .signals import create_builtin_groups_and_admin, get_permission_change_data
from .views import GroupViewSet, PersonalNoteViewSet, UserViewSet
# Define projector elements.
register_projector_elements()
register_projector_slides()
# Connect signals.
post_permission_creation.connect(

View File

@ -1,6 +1,6 @@
from typing import Any, Dict
from typing import Any, Dict, List
from ..utils.projector import register_projector_element
from ..utils.projector import AllData, register_projector_slide
# Important: All functions have to be prune. This means, that thay can only
@ -9,14 +9,28 @@ from ..utils.projector import register_projector_element
# to be fast!
def user(
element: Dict[str, Any], all_data: Dict[str, Dict[int, Dict[str, Any]]]
) -> Dict[str, Any]:
def user_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
"""
User slide.
"""
return {"error": "TODO"}
def register_projector_elements() -> None:
register_projector_element("users/user", user)
def get_user_name(all_data: AllData, user_id: int) -> str:
"""
Returns the short name for an user_id.
"""
user = all_data["users/user"][user_id]
name_parts: List[str] = []
for name_part in ("title", "first_name", "last_name"):
if user[name_part]:
name_parts.append(user[name_part])
if not name_part:
name_parts.append(user["username"])
if user["structure_level"]:
name_parts.append(f"({user['structure_level']})")
return " ".join(name_parts)
def register_projector_slides() -> None:
register_projector_slide("users/user", user_slide)

View File

@ -11,19 +11,25 @@ from .cache import element_cache
AllData = Dict[str, Dict[int, Dict[str, Any]]]
ProjectorElementCallable = Callable[[Dict[str, Any], AllData], Dict[str, Any]]
ProjectorSlide = Callable[[AllData, Dict[str, Any]], Dict[str, Any]]
projector_elements: Dict[str, ProjectorElementCallable] = {}
projector_slides: Dict[str, ProjectorSlide] = {}
def register_projector_element(name: str, element: ProjectorElementCallable) -> None:
class ProjectorElementException(Exception):
"""
Registers a projector element.
Exception for errors in one element on the projector.
"""
def register_projector_slide(name: str, slide: ProjectorSlide) -> None:
"""
Registers a projector slide.
Has to be called in the app.ready method.
"""
projector_elements[name] = element
projector_slides[name] = slide
async def get_projector_data(
@ -75,10 +81,12 @@ async def get_projector_data(
projector_data[projector_id] = []
for element in projector["elements"]:
projector_element = projector_elements[element["name"]]
projector_data[projector_id].append(
{"data": projector_element(element, all_data), "element": element}
)
projector_slide = projector_slides[element["name"]]
try:
data = projector_slide(all_data, element)
except ProjectorElementException as err:
data = {"error": err}
projector_data[projector_id].append({"data": data, "element": element})
return projector_data
@ -92,15 +100,3 @@ def get_config(all_data: AllData, key: str) -> Any:
return all_data[config.get_collection_string()][config.get_key_to_id()[key]][
"value"
]
def get_user(all_data: AllData, user_id: int) -> Dict[str, Any]:
"""
Returns the value of a user to show his name.
"""
user = all_data["users/user"][user_id]
return {
"title": user["title"],
"first_name": user["first_name"],
"last_name": user["last_name"],
}

View File

@ -6,7 +6,7 @@ from django.test.utils import CaptureQueriesContext
from openslides.core.config import config
from openslides.core.models import Projector
from openslides.users.models import User
from openslides.utils.projector import AllData, get_config, register_projector_element
from openslides.utils.projector import AllData, get_config, register_projector_slide
class TConfig:
@ -90,19 +90,19 @@ class TProjector:
return elements
def slide1(element: Dict[str, Any], all_data: AllData) -> Dict[str, Any]:
def slide1(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
"""
Slide that shows the general_event_name.
"""
return {"name": "slide1", "event_name": get_config(all_data, "general_event_name")}
def slide2(element: Dict[str, Any], all_data: AllData) -> Dict[str, Any]:
def slide2(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
return {"name": "slide2"}
register_projector_element("test/slide1", slide1)
register_projector_element("test/slide2", slide2)
register_projector_slide("test/slide1", slide1)
register_projector_slide("test/slide2", slide2)
def count_queries(func, *args, **kwargs) -> int:

View File

@ -48,7 +48,8 @@ def test_motion_db_queries():
* 1 request to get the polls,
* 1 request to get the attachments,
* 1 request to get the tags,
* 2 requests to get the submitters and supporters.
* 2 requests to get the submitters and supporters,
* 1 request for change_recommendations.
Two comment sections are created and for each motions two comments.
"""
@ -70,7 +71,7 @@ def test_motion_db_queries():
)
# TODO: Create some polls etc.
assert count_queries(Motion.get_elements) == 12
assert count_queries(Motion.get_elements) == 13
@pytest.mark.django_db(transaction=False)

View File

@ -86,32 +86,32 @@ def all_data():
def test_items(all_data):
config: Dict[str, Any] = {}
element: Dict[str, Any] = {}
data = projector.items(config, all_data)
data = projector.items_slide(all_data, element)
assert data == {"items": ["Item1", "item2"]}
def test_items_parent(all_data):
config: Dict[str, Any] = {"id": 1}
element: Dict[str, Any] = {"id": 1}
data = projector.items(config, all_data)
data = projector.items_slide(all_data, element)
assert data == {"items": ["item4"]}
def test_items_tree(all_data):
config: Dict[str, Any] = {"tree": True}
element: Dict[str, Any] = {"tree": True}
data = projector.items(config, all_data)
data = projector.items_slide(all_data, element)
assert data == {"items": [("Item1", [("item4", [])]), ("item2", [])]}
def test_items_tree_parent(all_data):
config: Dict[str, Any] = {"tree": True, "id": 1}
element: Dict[str, Any] = {"tree": True, "id": 1}
data = projector.items(config, all_data)
data = projector.items_slide(all_data, element)
assert data == {"items": [("item4", [])]}

View File

@ -156,7 +156,7 @@ def all_data():
def test_motion_slide(all_data):
element: Dict[str, Any] = {"id": 1}
data = projector.motion_slide(element, all_data)
data = projector.motion_slide(all_data, element)
assert data == {
"identifier": "4",
@ -167,7 +167,6 @@ def test_motion_slide(all_data):
"show_meta_box": True,
"reason": "",
"state": "submitted",
"state_extension": None,
"submitter": [{"first_name": "", "last_name": "Administrator", "title": ""}],
"submitter": ["Administrator"],
"poll": {"yes": "10.000000", "no": "-1.000000", "abstain": "20.000000"},
}