Add title field to User model and update related views for user management
This commit is contained in:
parent
fd148eb484
commit
f89b825ef0
6 changed files with 94 additions and 14 deletions
|
@ -19,6 +19,7 @@ class User(db.Model, ImageAttachable):
|
|||
active: Mapped[Optional[bool]] = mapped_column(Boolean, server_default=text('((0))'))
|
||||
last_name: Mapped[Optional[str]] = mapped_column(Unicode(255), nullable=True)
|
||||
first_name: Mapped[Optional[str]] = mapped_column(Unicode(255), nullable=True)
|
||||
title: Mapped[Optional[str]] = mapped_column(Unicode(255), nullable=True, default=None)
|
||||
location_id: Mapped[Optional[int]] = mapped_column(ForeignKey("rooms.id"), nullable=True, index=True)
|
||||
supervisor_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("users.id"), nullable=True, index=True)
|
||||
image_id: Mapped[Optional[int]] = mapped_column(ForeignKey('images.id', ondelete='SET NULL'), nullable=True, index=True)
|
||||
|
@ -35,10 +36,12 @@ class User(db.Model, ImageAttachable):
|
|||
return f"{self.first_name or ''} {self.last_name or ''}".strip()
|
||||
|
||||
def __init__(self, first_name: Optional[str] = None, last_name: Optional[str] = None,
|
||||
location_id: Optional[int] = None, supervisor_id: Optional[int] = None,
|
||||
staff: Optional[bool] = False, active: Optional[bool] = False):
|
||||
title: Optional[str] = None,location_id: Optional[int] = None,
|
||||
supervisor_id: Optional[int] = None, staff: Optional[bool] = False,
|
||||
active: Optional[bool] = False):
|
||||
self.first_name = first_name
|
||||
self.last_name = last_name
|
||||
self.title = title
|
||||
self.location_id = location_id
|
||||
self.supervisor_id = supervisor_id
|
||||
self.staff = staff
|
||||
|
@ -53,6 +56,7 @@ class User(db.Model, ImageAttachable):
|
|||
'id': self.id,
|
||||
'first_name': self.first_name,
|
||||
'last_name': self.last_name,
|
||||
'title': self.title,
|
||||
'location_id': self.location_id,
|
||||
'supervisor_id': self.supervisor_id,
|
||||
'staff': self.staff,
|
||||
|
@ -66,6 +70,7 @@ class User(db.Model, ImageAttachable):
|
|||
active=bool(data.get("active", False)),
|
||||
last_name=data.get("last_name"),
|
||||
first_name=data.get("first_name"),
|
||||
title=data.get("title"),
|
||||
location_id=data.get("location_id"),
|
||||
supervisor_id=data.get("supervisor_id")
|
||||
)
|
|
@ -52,6 +52,7 @@ FILTER_MAP = {
|
|||
user_headers = {
|
||||
"Last Name": lambda i: {"text": i.last_name},
|
||||
"First Name": lambda i: {"text": i.first_name},
|
||||
"Title": lambda i: {"text": i.title},
|
||||
"Supervisor": lambda i: {"text": i.supervisor.identifier, "url": url_for("main.user", id=i.supervisor.id)} if i.supervisor else {"text": None},
|
||||
"Location": lambda i: {"text": i.location.identifier} if i.location else {"text": None},
|
||||
"Staff?": lambda i: {"text": i.staff, "type": "bool", "html": checked_box if i.staff else unchecked_box},
|
||||
|
|
|
@ -14,7 +14,7 @@ def search():
|
|||
|
||||
if not query:
|
||||
return redirect(url_for('main.index'))
|
||||
|
||||
|
||||
InventoryAlias = aliased(Inventory)
|
||||
UserAlias = aliased(User)
|
||||
|
||||
|
@ -25,15 +25,18 @@ def search():
|
|||
Inventory.barcode.ilike(f"%{query}%"),
|
||||
Inventory.notes.ilike(f"%{query}%"),
|
||||
UserAlias.first_name.ilike(f"%{query}%"),
|
||||
UserAlias.last_name.ilike(f"%{query}%")
|
||||
UserAlias.last_name.ilike(f"%{query}%"),
|
||||
UserAlias.title.ilike(f"%{query}%")
|
||||
))
|
||||
inventory_results = inventory_query.all()
|
||||
user_query = eager_load_user_relationships(db.session.query(User).join(UserAlias, User.supervisor)).filter(
|
||||
or_(
|
||||
User.first_name.ilike(f"%{query}%"),
|
||||
User.last_name.ilike(f"%{query}%"),
|
||||
User.title.ilike(f"%{query}%"),
|
||||
UserAlias.first_name.ilike(f"%{query}%"),
|
||||
UserAlias.last_name.ilike(f"%{query}%")
|
||||
UserAlias.last_name.ilike(f"%{query}%"),
|
||||
UserAlias.title.ilike(f"%{query}%")
|
||||
))
|
||||
user_results = user_query.all()
|
||||
worklog_query = eager_load_worklog_relationships(db.session.query(WorkLog).join(UserAlias, WorkLog.contact).join(InventoryAlias, WorkLog.work_item)).filter(
|
||||
|
@ -41,26 +44,27 @@ def search():
|
|||
WorkLog.notes.ilike(f"%{query}%"),
|
||||
UserAlias.first_name.ilike(f"%{query}%"),
|
||||
UserAlias.last_name.ilike(f"%{query}%"),
|
||||
UserAlias.title.ilike(f"%{query}%"),
|
||||
InventoryAlias.name.ilike(f"%{query}%"),
|
||||
InventoryAlias.serial.ilike(f"%{query}%"),
|
||||
InventoryAlias.barcode.ilike(f"%{query}%")
|
||||
))
|
||||
worklog_results = worklog_query.all()
|
||||
|
||||
|
||||
results = {
|
||||
'inventory': {
|
||||
'results': inventory_query,
|
||||
'headers': inventory_headers,
|
||||
'results': inventory_query,
|
||||
'headers': inventory_headers,
|
||||
'rows': [{"id": item.id, "cells": [fn(item) for fn in inventory_headers.values()]} for item in inventory_results]
|
||||
},
|
||||
'users': {
|
||||
'results': user_query,
|
||||
'headers': user_headers,
|
||||
'results': user_query,
|
||||
'headers': user_headers,
|
||||
'rows': [{"id": user.id, "cells": [fn(user) for fn in user_headers.values()]} for user in user_results]
|
||||
},
|
||||
'worklog': {
|
||||
'results': worklog_query,
|
||||
'headers': worklog_headers,
|
||||
'results': worklog_query,
|
||||
'headers': worklog_headers,
|
||||
'rows': [{"id": log.id, "cells": [fn(log) for fn in worklog_headers.values()]} for log in worklog_results]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,6 +70,32 @@ def user(id):
|
|||
worklog_rows=[{"id": log.id, "cells": [fn(log) for fn in filtered_worklog_headers.values()]} for log in worklog]
|
||||
)
|
||||
|
||||
@main.route("/user/<id>/org")
|
||||
def user_org(id):
|
||||
user = eager_load_user_relationships(db.session.query(User).filter(User.id == id)).first()
|
||||
if not user:
|
||||
return render_template('error.html', title='User Not Found', message=f'User with ID {id} not found.')
|
||||
|
||||
current_user = user
|
||||
org_chart = []
|
||||
while current_user:
|
||||
subordinates = (
|
||||
eager_load_user_relationships(
|
||||
db.session.query(User).filter(User.supervisor_id == current_user.id)
|
||||
).all()
|
||||
)
|
||||
org_chart.insert(0, {
|
||||
"user": current_user,
|
||||
"subordinates": [subordinate for subordinate in subordinates if subordinate.active and subordinate.staff]
|
||||
})
|
||||
current_user = current_user.supervisor
|
||||
|
||||
return render_template(
|
||||
"user_org.html",
|
||||
user=user,
|
||||
org_chart=org_chart
|
||||
)
|
||||
|
||||
@main.route("/user/new", methods=["GET"])
|
||||
def new_user():
|
||||
rooms = eager_load_room_relationships(db.session.query(Room)).all()
|
||||
|
@ -116,6 +142,7 @@ def update_user(id):
|
|||
user.active = bool(data.get("active", user.active))
|
||||
user.last_name = data.get("last_name", user.last_name)
|
||||
user.first_name = data.get("first_name", user.first_name)
|
||||
user.title = data.get("title", user.title)
|
||||
user.location_id = data.get("location_id", user.location_id)
|
||||
user.supervisor_id = data.get("supervisor_id", user.supervisor_id)
|
||||
|
||||
|
@ -155,6 +182,7 @@ def get_user_csv():
|
|||
"active",
|
||||
"last_name",
|
||||
"first_name",
|
||||
"title",
|
||||
"location",
|
||||
"supervisor"
|
||||
]
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
active: document.querySelector("input[name='activeCheck']").checked,
|
||||
last_name: document.querySelector("input[name='lastName']").value,
|
||||
first_name: document.querySelector("input[name='firstName']").value,
|
||||
title: document.querySelector("input[name='title']").value,
|
||||
supervisor_id: parseInt(document.querySelector("input[name='supervisor']").value) || null,
|
||||
location_id: parseInt(document.querySelector("input[name='location']").value) || null
|
||||
};
|
||||
|
@ -84,15 +85,20 @@
|
|||
<div class="container">
|
||||
<form action="POST">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="col">
|
||||
<label for="lastName" class="form-label">Last Name</label>
|
||||
<input type="text" class="form-control" id="lastName" name="lastName" placeholder="Doe" value="{{ user.last_name if user.last_name else '' }}"{% if not user.active %} disabled readonly{% endif %}>
|
||||
</div>
|
||||
|
||||
<div class="col-6">
|
||||
<div class="col">
|
||||
<label for="firstName" class="form-label">First Name</label>
|
||||
<input type="text" class="form-control" id="firstName" name="firstName" placeholder="John" value="{{ user.first_name if user.first_name else '' }}"{% if not user.active %} disabled readonly{% endif %}>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<label for="title" class="form-label">Title</label>
|
||||
<input type="text" class="form-control" id="title" name="title" placeholder="President" value="{{ user.title if user.title else '' }}"{% if not user.active %} disabled readonly{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-2">
|
||||
|
|
36
inventory/templates/user_org.html
Normal file
36
inventory/templates/user_org.html
Normal file
|
@ -0,0 +1,36 @@
|
|||
{% extends 'layout.html' %}
|
||||
|
||||
{% block content %}
|
||||
{% for layer in org_chart %}
|
||||
{# Only show the top-level user on the first iteration #}
|
||||
{% if loop.first %}
|
||||
<div class="row align-items-center justify-content-center mb-3">
|
||||
<div class="column text-center fw-bold">
|
||||
{{ layer.user.identifier }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Render this layer's subordinates #}
|
||||
{% if layer['subordinates'] %}
|
||||
<div class="row align-items-center justify-content-center mb-3 border">
|
||||
{% set current_index = loop.index0 %}
|
||||
{% set next_user = org_chart[current_index + 1].user if current_index + 1 < org_chart|length else None %}
|
||||
|
||||
{% for subordinate in layer.subordinates %}
|
||||
<div class="col text-center {% if next_user and subordinate.id == next_user.id %}fw-bold{% endif %}">
|
||||
{% if subordinate == user %}
|
||||
{{ subordinate.identifier }}
|
||||
{% else %}
|
||||
<a class="link-success link-underline-opacity-0"
|
||||
href="{{ url_for('main.user_org', id=subordinate.id) }}">
|
||||
{{ subordinate.identifier }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% endblock %}
|
Loading…
Add table
Add a link
Reference in a new issue