Skip to content

Commit

Permalink
[web] introduce pydantic models for facilities tables
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasjuhrich committed Jul 3, 2023
1 parent 000be5d commit 8132fcf
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 58 deletions.
129 changes: 80 additions & 49 deletions web/blueprints/facilities/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -48,6 +48,9 @@
RoomOvercrowdedTable,
PatchPortTable,
SiteRow,
BuildingLevelRoomRow,
PatchPortRow,
RoomOvercrowdedRow,
)

bp = Blueprint('facilities', __name__)
Expand Down Expand Up @@ -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/<int:switch_room_id>/patch-port/create', methods=['GET', 'POST'])
Expand Down Expand Up @@ -502,6 +509,7 @@ def room_show(room_id):

@bp.route('/room/<int:room_id>/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)])

Expand All @@ -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 <select> field"""
building_id = request.args.get('building', 0, type=int)
levels = session.session.query(Room.level.label('level')).filter_by(
building_id=building_id).order_by(Room.level).distinct()
Expand All @@ -559,6 +584,7 @@ def json_levels():
@bp.route('/json/rooms')
@access.require('facilities_show')
def json_rooms():
"""Endpoint for the room <select> field"""
building_id = request.args.get('building', 0, type=int)
level = request.args.get('level', 0, type=int)
rooms = session.session.query(
Expand Down Expand Up @@ -590,19 +616,24 @@ def overcrowded(building_id):
@bp.route('/overcrowded/json', defaults={'building_id': None})
@bp.route('/overcrowded/<int:building_id>/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/<string:type>')
Expand Down
17 changes: 17 additions & 0 deletions web/blueprints/facilities/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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:
Expand All @@ -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
25 changes: 17 additions & 8 deletions web/blueprints/helpers/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion web/table/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down

0 comments on commit 8132fcf

Please sign in to comment.