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))'))
|
active: Mapped[Optional[bool]] = mapped_column(Boolean, server_default=text('((0))'))
|
||||||
last_name: Mapped[Optional[str]] = mapped_column(Unicode(255), nullable=True)
|
last_name: Mapped[Optional[str]] = mapped_column(Unicode(255), nullable=True)
|
||||||
first_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)
|
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)
|
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)
|
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()
|
return f"{self.first_name or ''} {self.last_name or ''}".strip()
|
||||||
|
|
||||||
def __init__(self, first_name: Optional[str] = None, last_name: Optional[str] = None,
|
def __init__(self, first_name: Optional[str] = None, last_name: Optional[str] = None,
|
||||||
location_id: Optional[int] = None, supervisor_id: Optional[int] = None,
|
title: Optional[str] = None,location_id: Optional[int] = None,
|
||||||
staff: Optional[bool] = False, active: Optional[bool] = False):
|
supervisor_id: Optional[int] = None, staff: Optional[bool] = False,
|
||||||
|
active: Optional[bool] = False):
|
||||||
self.first_name = first_name
|
self.first_name = first_name
|
||||||
self.last_name = last_name
|
self.last_name = last_name
|
||||||
|
self.title = title
|
||||||
self.location_id = location_id
|
self.location_id = location_id
|
||||||
self.supervisor_id = supervisor_id
|
self.supervisor_id = supervisor_id
|
||||||
self.staff = staff
|
self.staff = staff
|
||||||
|
@ -53,6 +56,7 @@ class User(db.Model, ImageAttachable):
|
||||||
'id': self.id,
|
'id': self.id,
|
||||||
'first_name': self.first_name,
|
'first_name': self.first_name,
|
||||||
'last_name': self.last_name,
|
'last_name': self.last_name,
|
||||||
|
'title': self.title,
|
||||||
'location_id': self.location_id,
|
'location_id': self.location_id,
|
||||||
'supervisor_id': self.supervisor_id,
|
'supervisor_id': self.supervisor_id,
|
||||||
'staff': self.staff,
|
'staff': self.staff,
|
||||||
|
@ -66,6 +70,7 @@ class User(db.Model, ImageAttachable):
|
||||||
active=bool(data.get("active", False)),
|
active=bool(data.get("active", False)),
|
||||||
last_name=data.get("last_name"),
|
last_name=data.get("last_name"),
|
||||||
first_name=data.get("first_name"),
|
first_name=data.get("first_name"),
|
||||||
|
title=data.get("title"),
|
||||||
location_id=data.get("location_id"),
|
location_id=data.get("location_id"),
|
||||||
supervisor_id=data.get("supervisor_id")
|
supervisor_id=data.get("supervisor_id")
|
||||||
)
|
)
|
|
@ -52,6 +52,7 @@ FILTER_MAP = {
|
||||||
user_headers = {
|
user_headers = {
|
||||||
"Last Name": lambda i: {"text": i.last_name},
|
"Last Name": lambda i: {"text": i.last_name},
|
||||||
"First Name": lambda i: {"text": i.first_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},
|
"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},
|
"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},
|
"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:
|
if not query:
|
||||||
return redirect(url_for('main.index'))
|
return redirect(url_for('main.index'))
|
||||||
|
|
||||||
InventoryAlias = aliased(Inventory)
|
InventoryAlias = aliased(Inventory)
|
||||||
UserAlias = aliased(User)
|
UserAlias = aliased(User)
|
||||||
|
|
||||||
|
@ -25,15 +25,18 @@ def search():
|
||||||
Inventory.barcode.ilike(f"%{query}%"),
|
Inventory.barcode.ilike(f"%{query}%"),
|
||||||
Inventory.notes.ilike(f"%{query}%"),
|
Inventory.notes.ilike(f"%{query}%"),
|
||||||
UserAlias.first_name.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()
|
inventory_results = inventory_query.all()
|
||||||
user_query = eager_load_user_relationships(db.session.query(User).join(UserAlias, User.supervisor)).filter(
|
user_query = eager_load_user_relationships(db.session.query(User).join(UserAlias, User.supervisor)).filter(
|
||||||
or_(
|
or_(
|
||||||
User.first_name.ilike(f"%{query}%"),
|
User.first_name.ilike(f"%{query}%"),
|
||||||
User.last_name.ilike(f"%{query}%"),
|
User.last_name.ilike(f"%{query}%"),
|
||||||
|
User.title.ilike(f"%{query}%"),
|
||||||
UserAlias.first_name.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()
|
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(
|
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}%"),
|
WorkLog.notes.ilike(f"%{query}%"),
|
||||||
UserAlias.first_name.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}%"),
|
||||||
InventoryAlias.name.ilike(f"%{query}%"),
|
InventoryAlias.name.ilike(f"%{query}%"),
|
||||||
InventoryAlias.serial.ilike(f"%{query}%"),
|
InventoryAlias.serial.ilike(f"%{query}%"),
|
||||||
InventoryAlias.barcode.ilike(f"%{query}%")
|
InventoryAlias.barcode.ilike(f"%{query}%")
|
||||||
))
|
))
|
||||||
worklog_results = worklog_query.all()
|
worklog_results = worklog_query.all()
|
||||||
|
|
||||||
results = {
|
results = {
|
||||||
'inventory': {
|
'inventory': {
|
||||||
'results': inventory_query,
|
'results': inventory_query,
|
||||||
'headers': inventory_headers,
|
'headers': inventory_headers,
|
||||||
'rows': [{"id": item.id, "cells": [fn(item) for fn in inventory_headers.values()]} for item in inventory_results]
|
'rows': [{"id": item.id, "cells": [fn(item) for fn in inventory_headers.values()]} for item in inventory_results]
|
||||||
},
|
},
|
||||||
'users': {
|
'users': {
|
||||||
'results': user_query,
|
'results': user_query,
|
||||||
'headers': user_headers,
|
'headers': user_headers,
|
||||||
'rows': [{"id": user.id, "cells": [fn(user) for fn in user_headers.values()]} for user in user_results]
|
'rows': [{"id": user.id, "cells": [fn(user) for fn in user_headers.values()]} for user in user_results]
|
||||||
},
|
},
|
||||||
'worklog': {
|
'worklog': {
|
||||||
'results': worklog_query,
|
'results': worklog_query,
|
||||||
'headers': worklog_headers,
|
'headers': worklog_headers,
|
||||||
'rows': [{"id": log.id, "cells": [fn(log) for fn in worklog_headers.values()]} for log in worklog_results]
|
'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]
|
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"])
|
@main.route("/user/new", methods=["GET"])
|
||||||
def new_user():
|
def new_user():
|
||||||
rooms = eager_load_room_relationships(db.session.query(Room)).all()
|
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.active = bool(data.get("active", user.active))
|
||||||
user.last_name = data.get("last_name", user.last_name)
|
user.last_name = data.get("last_name", user.last_name)
|
||||||
user.first_name = data.get("first_name", user.first_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.location_id = data.get("location_id", user.location_id)
|
||||||
user.supervisor_id = data.get("supervisor_id", user.supervisor_id)
|
user.supervisor_id = data.get("supervisor_id", user.supervisor_id)
|
||||||
|
|
||||||
|
@ -155,6 +182,7 @@ def get_user_csv():
|
||||||
"active",
|
"active",
|
||||||
"last_name",
|
"last_name",
|
||||||
"first_name",
|
"first_name",
|
||||||
|
"title",
|
||||||
"location",
|
"location",
|
||||||
"supervisor"
|
"supervisor"
|
||||||
]
|
]
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
active: document.querySelector("input[name='activeCheck']").checked,
|
active: document.querySelector("input[name='activeCheck']").checked,
|
||||||
last_name: document.querySelector("input[name='lastName']").value,
|
last_name: document.querySelector("input[name='lastName']").value,
|
||||||
first_name: document.querySelector("input[name='firstName']").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,
|
supervisor_id: parseInt(document.querySelector("input[name='supervisor']").value) || null,
|
||||||
location_id: parseInt(document.querySelector("input[name='location']").value) || null
|
location_id: parseInt(document.querySelector("input[name='location']").value) || null
|
||||||
};
|
};
|
||||||
|
@ -84,15 +85,20 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<form action="POST">
|
<form action="POST">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6">
|
<div class="col">
|
||||||
<label for="lastName" class="form-label">Last Name</label>
|
<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 %}>
|
<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>
|
||||||
|
|
||||||
<div class="col-6">
|
<div class="col">
|
||||||
<label for="firstName" class="form-label">First Name</label>
|
<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 %}>
|
<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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
<div class="row mt-2">
|
<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