diff --git a/web/blueprints/facilities/__init__.py b/web/blueprints/facilities/__init__.py index 0cc8c53aa..0c7214466 100644 --- a/web/blueprints/facilities/__init__.py +++ b/web/blueprints/facilities/__init__.py @@ -37,7 +37,7 @@ from web.blueprints.facilities.forms import ( RoomLogEntry, PatchPortForm, CreateRoomForm, EditRoomForm) from web.blueprints.helpers.log import format_room_log_entry -from web.blueprints.helpers.user import user_button +from web.blueprints.helpers.user import user_button, user_btn_response from web.blueprints.navigation import BlueprintNavigation from web.table.table import TableResponse, LinkColResponse, BtnColResponse from .address import get_address_entity, address_entity_search_query @@ -48,6 +48,9 @@ RoomOvercrowdedTable, PatchPortTable, SiteRow, + BuildingLevelRoomRow, + PatchPortRow, + RoomOvercrowdedRow, ) bp = Blueprint('facilities', __name__) @@ -307,14 +310,18 @@ def building_level_rooms_json(level, building_id=None, building_shortname=None): # Ensure room is in level_inhabitants level_inhabitants[room] - T = BuildingLevelRoomTable - return jsonify(items=[{ - 'room': T.room.value( - href=url_for(".room_show", room_id=room.id), - title=f"{level:02d} - {room.number}" - ), - 'inhabitants': [user_button(i) for i in inhabitants] - } for room, inhabitants in level_inhabitants.items()]) + return TableResponse[BuildingLevelRoomRow]( + items=[ + BuildingLevelRoomRow( + room=LinkColResponse( + href=url_for(".room_show", room_id=room.id), + title=f"{level:02d} - {room.number}", + ), + inhabitants=[user_btn_response(i) for i in inhabitants], + ) + for room, inhabitants in level_inhabitants.items() + ] + ).model_dump() @bp.route('/room//patch-port/create', methods=['GET', 'POST']) @@ -502,6 +509,7 @@ def room_show(room_id): @bp.route('/room//logs/json') def room_logs_json(room_id): + # TODO migrate this with all the other tables return jsonify(items=[format_room_log_entry(entry) for entry in reversed(Room.get(room_id).log_entries)]) @@ -518,38 +526,55 @@ def room_patchpanel_json(room_id): patch_ports = PatchPort.q.filter_by(switch_room=room).all() patch_ports = sort_ports(patch_ports) - T = PatchPortTable - - return jsonify(items=[{ - "name": port.name, - "room": T.room.value( - href=url_for(".room_show", room_id=port.room.id), - title=port.room.short_name - ), - "switch_port": T.switch_port.value( - href=url_for("infrastructure.switch_show", - switch_id=port.switch_port.switch.host_id), - title=f"{port.switch_port.switch.host.name}/{port.switch_port.name}" - ) if port.switch_port else None, - 'edit_link': T.edit_link.value( - hef=url_for(".patch_port_edit", switch_room_id=room.id, patch_port_id=port.id), - title="Bearbeiten", - icon='fa-edit', - # TODO decide on a convention here - btn_class='btn-link', - ), - 'delete_link': T.delete_link.value( - href=url_for(".patch_port_delete", switch_room_id=room.id, patch_port_id=port.id), - title="Löschen", - icon='fa-trash', - btn_class='btn-link' - ), - } for port in patch_ports]) + + return TableResponse[PatchPortRow]( + items=[ + PatchPortRow( + name=port.name, + room=LinkColResponse( + href=url_for(".room_show", room_id=port.room.id), + title=port.room.short_name, + ), + switch_port=LinkColResponse( + href=url_for( + "infrastructure.switch_show", + switch_id=port.switch_port.switch.host_id, + ), + title=f"{port.switch_port.switch.host.name}/{port.switch_port.name}", + ) + if port.switch_port + else None, + edit_link=BtnColResponse( + href=url_for( + ".patch_port_edit", + switch_room_id=room.id, + patch_port_id=port.id, + ), + title="Bearbeiten", + icon="fa-edit", + # TODO decide on a convention here + btn_class="btn-link", + ), + delete_link=BtnColResponse( + href=url_for( + ".patch_port_delete", + switch_room_id=room.id, + patch_port_id=port.id, + ), + title="Löschen", + icon="fa-trash", + btn_class="btn-link", + ), + ) + for port in patch_ports + ] + ).model_dump() @bp.route('/json/levels') @access.require('facilities_show') def json_levels(): + """Endpoint for the room field""" building_id = request.args.get('building', 0, type=int) level = request.args.get('level', 0, type=int) rooms = session.session.query( @@ -590,19 +616,24 @@ def overcrowded(building_id): @bp.route('/overcrowded/json', defaults={'building_id': None}) @bp.route('/overcrowded//json') def overcrowded_json(building_id): - rooms = get_overcrowded_rooms(building_id) - T = RoomOvercrowdedTable - - return jsonify(items=[{ - 'room': T.room.value( - title='{} / {:02d} / {}'.format( - inhabitants[0].room.building.short_name, - inhabitants[0].room.level, inhabitants[0].room.number), - href=url_for("facilities.room_show", - room_id=inhabitants[0].room.id) - ), - 'inhabitants': [user_button(user) for user in inhabitants] - } for inhabitants in rooms.values()]) + return TableResponse[RoomOvercrowdedRow]( + items=[ + RoomOvercrowdedRow( + room=LinkColResponse( + title="{} / {:02d} / {}".format( + inhabitants[0].room.building.short_name, + inhabitants[0].room.level, + inhabitants[0].room.number, + ), + href=url_for( + "facilities.room_show", room_id=inhabitants[0].room.id + ), + ), + inhabitants=[user_btn_response(user) for user in inhabitants], + ) + for inhabitants in get_overcrowded_rooms(building_id).values() + ] + ).model_dump() @bp.route('address/') diff --git a/web/blueprints/facilities/tables.py b/web/blueprints/facilities/tables.py index 5ab2a7ad4..2b8a8c4fe 100644 --- a/web/blueprints/facilities/tables.py +++ b/web/blueprints/facilities/tables.py @@ -45,6 +45,11 @@ def toolbar(self): ) +class BuildingLevelRoomRow(BaseModel): + room: LinkColResponse + inhabitants: list[BtnColResponse] + + class RoomLogTable(BootstrapTable): created_at = DateColumn("Erstellt um") user = LinkColumn("Nutzer") @@ -58,6 +63,10 @@ class RoomOvercrowdedTable(BootstrapTable): class Meta: table_args = {'data-sort-name': 'room'} +class RoomOvercrowdedRow(BaseModel): + room: LinkColResponse + inhabitants: list[BtnColResponse] + class PatchPortTable(BootstrapTable): class Meta: @@ -82,3 +91,11 @@ def toolbar(self): return href = url_for(".patch_port_create", switch_room_id=self.room_id) return button_toolbar("Patch-Port", href) + + +class PatchPortRow(BaseModel): + name: str + room: LinkColResponse + switch_port: LinkColResponse + edit_link: BtnColResponse + delete_link: BtnColResponse diff --git a/web/blueprints/helpers/user.py b/web/blueprints/helpers/user.py index 013932737..785175ed9 100644 --- a/web/blueprints/helpers/user.py +++ b/web/blueprints/helpers/user.py @@ -4,6 +4,7 @@ from flask_login import current_user from pycroft.model.user import User, PreMember +from web.table.table import BtnColResponse def user_btn_style(user): @@ -66,15 +67,23 @@ def user_btn_style(user): return btn_class, glyphicons, tooltip -def user_button(user): +def user_btn_response(user: User) -> BtnColResponse: + # TODO rename this to `user_button` after adoption btn_class, glyphicons, tooltip = user_btn_style(user) - return { - 'href': url_for("user.user_show", user_id=user.id), - 'title': user.name, - 'icon': glyphicons, - 'btn_class': btn_class, - 'tooltip': tooltip - } + return BtnColResponse( + href=url_for("user.user_show", user_id=user.id), + title=user.name, + icon=glyphicons, + btn_class=btn_class, + tooltip=tooltip, + ) + + +def user_button(user: User) -> dict: + import warnings + + warnings.warn("use user_btn_response instead", DeprecationWarning, stacklevel=2) + return user_btn_response(user).model_dump() def get_user_or_404(user_id: int) -> User | NoReturn: diff --git a/web/table/table.py b/web/table/table.py index 7356b8bf7..0a8ff1562 100644 --- a/web/table/table.py +++ b/web/table/table.py @@ -163,7 +163,7 @@ class BtnColResponse(BaseModel): title: str tooltip: str | None = None new_tab: bool | None = None - icon: IconClass | Iterable[IconClass] | None = None + icon: IconClass | list[IconClass] | None = None @custom_formatter_column('table.btnFormatter')