Skip to content

Commit bb14042

Browse files
feat(admin,orm): fix sync model save logic, add user/group CRUD forms and English admin UI\n\n- Fix sync save logic to avoid AutoField in id and always use correct insert/update\n- Add user/group create, edit, delete forms in admin (English)\n- Refactor admin templates for Django-like UX\n- Remove all await from admin model usage\n- Add debug prints for save logic\n- Remove async app example files
1 parent 7bc11ca commit bb14042

File tree

10 files changed

+404
-35
lines changed

10 files changed

+404
-35
lines changed

example/apps/admin/urls.py

Lines changed: 162 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ async def users_view(request: Request):
5656
@router.get("/groups", response_model=None)
5757
@requires("user_auth")
5858
async def groups_view(request: Request):
59-
groups = await GroupModel.objects.all().execute() # type: ignore
59+
groups = GroupModel.objects.all().execute() # type: ignore
6060

6161
return render_template(request=request, template_name="admin/groups.html", context={
6262
"url_for": url_for,
@@ -106,3 +106,164 @@ async def profile_view(request: Request):
106106
"segment": "test",
107107
"config": request.app.settings,
108108
})
109+
110+
@router.get("/users/edit/{user_id}", response_model=None)
111+
@requires("user_auth")
112+
async def user_edit_view(request: Request, user_id: int):
113+
user = UserModel.objects.filter(id=user_id).first()
114+
groups = GroupModel.objects.all()
115+
return render_template(request=request, template_name="admin/user_edit.html", context={
116+
"user": user,
117+
"groups": groups,
118+
"url_for": url_for,
119+
"config": request.app.settings,
120+
})
121+
122+
@router.post("/users/edit/{user_id}", response_model=None)
123+
@requires("user_auth")
124+
async def user_edit_post(request: Request, user_id: int):
125+
form = await request.form()
126+
user = UserModel.objects.filter(id=user_id).first()
127+
if user:
128+
user.name = form.get("name")
129+
user.age = int(form.get("age"))
130+
user.email = form.get("email")
131+
user.organization = form.get("organization")
132+
group_id = int(form.get("group_id"))
133+
user.group = group_id
134+
user.save()
135+
return render_template(request=request, template_name="admin/user_edit.html", context={
136+
"user": user,
137+
"groups": GroupModel.objects.all(),
138+
"url_for": url_for,
139+
"config": request.app.settings,
140+
"success": True
141+
})
142+
143+
@router.get("/groups/edit/{group_id}", response_model=None)
144+
@requires("user_auth")
145+
async def group_edit_view(request: Request, group_id: int):
146+
group = GroupModel.objects.filter(id=group_id).first()
147+
return render_template(request=request, template_name="admin/group_edit.html", context={
148+
"group": group,
149+
"url_for": url_for,
150+
"config": request.app.settings,
151+
})
152+
153+
@router.post("/groups/edit/{group_id}", response_model=None)
154+
@requires("user_auth")
155+
async def group_edit_post(request: Request, group_id: int):
156+
form = await request.form()
157+
group = GroupModel.objects.filter(id=group_id).first()
158+
if group:
159+
group.name = form.get("name")
160+
group.description = form.get("description")
161+
group.save()
162+
return render_template(request=request, template_name="admin/group_edit.html", context={
163+
"group": group,
164+
"url_for": url_for,
165+
"config": request.app.settings,
166+
"success": True
167+
})
168+
169+
# --- User Create ---
170+
@router.get("/users/create", response_model=None)
171+
@requires("user_auth")
172+
async def user_create_view(request: Request):
173+
groups = GroupModel.objects.all()
174+
return render_template(request=request, template_name="admin/user_create.html", context={
175+
"groups": groups,
176+
"url_for": url_for,
177+
"config": request.app.settings,
178+
})
179+
180+
@router.post("/users/create", response_model=None)
181+
@requires("user_auth")
182+
async def user_create_post(request: Request):
183+
form = await request.form()
184+
user = UserModel(
185+
name=form.get("name"),
186+
age=int(form.get("age")),
187+
email=form.get("email"),
188+
password_hash="", # Set password later or generate
189+
group=int(form.get("group_id")),
190+
organization=form.get("organization")
191+
)
192+
user.save()
193+
return render_template(request=request, template_name="admin/user_create.html", context={
194+
"groups": GroupModel.objects.all(),
195+
"url_for": url_for,
196+
"config": request.app.settings,
197+
"success": True
198+
})
199+
200+
# --- User Delete ---
201+
@router.get("/users/delete/{user_id}", response_model=None)
202+
@requires("user_auth")
203+
async def user_delete_confirm(request: Request, user_id: int):
204+
user = UserModel.objects.filter(id=user_id).first()
205+
return render_template(request=request, template_name="admin/user_delete.html", context={
206+
"user": user,
207+
"url_for": url_for,
208+
"config": request.app.settings,
209+
})
210+
211+
@router.post("/users/delete/{user_id}", response_model=None)
212+
@requires("user_auth")
213+
async def user_delete_post(request: Request, user_id: int):
214+
user = UserModel.objects.filter(id=user_id).first()
215+
if user:
216+
user.delete()
217+
# Redirect to users list after deletion
218+
return render_template(request=request, template_name="admin/user_delete.html", context={
219+
"deleted": True,
220+
"url_for": url_for,
221+
"config": request.app.settings,
222+
})
223+
224+
# --- Group Create ---
225+
@router.get("/groups/create", response_model=None)
226+
@requires("user_auth")
227+
async def group_create_view(request: Request):
228+
return render_template(request=request, template_name="admin/group_create.html", context={
229+
"url_for": url_for,
230+
"config": request.app.settings,
231+
})
232+
233+
@router.post("/groups/create", response_model=None)
234+
@requires("user_auth")
235+
async def group_create_post(request: Request):
236+
form = await request.form()
237+
group = GroupModel(
238+
name=form.get("name"),
239+
description=form.get("description")
240+
)
241+
group.save()
242+
return render_template(request=request, template_name="admin/group_create.html", context={
243+
"url_for": url_for,
244+
"config": request.app.settings,
245+
"success": True
246+
})
247+
248+
# --- Group Delete ---
249+
@router.get("/groups/delete/{group_id}", response_model=None)
250+
@requires("user_auth")
251+
async def group_delete_confirm(request: Request, group_id: int):
252+
group = GroupModel.objects.filter(id=group_id).first()
253+
return render_template(request=request, template_name="admin/group_delete.html", context={
254+
"group": group,
255+
"url_for": url_for,
256+
"config": request.app.settings,
257+
})
258+
259+
@router.post("/groups/delete/{group_id}", response_model=None)
260+
@requires("user_auth")
261+
async def group_delete_post(request: Request, group_id: int):
262+
group = GroupModel.objects.filter(id=group_id).first()
263+
if group:
264+
group.delete()
265+
return render_template(request=request, template_name="admin/group_delete.html", context={
266+
"deleted": True,
267+
"url_for": url_for,
268+
"config": request.app.settings,
269+
})
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{% extends 'layouts/base.html' %}
2+
{% block title %}Add Group{% endblock %}
3+
{% block content %}
4+
<div class="container py-4">
5+
<div class="row justify-content-center">
6+
<div class="col-md-8">
7+
<div class="card">
8+
<div class="card-header"><h5>Add Group</h5></div>
9+
<div class="card-body">
10+
{% if success %}
11+
<div class="alert alert-success">Group created!</div>
12+
{% endif %}
13+
<form method="post">
14+
<div class="mb-3">
15+
<label class="form-label">Name</label>
16+
<input type="text" class="form-control" name="name" required>
17+
</div>
18+
<div class="mb-3">
19+
<label class="form-label">Description</label>
20+
<input type="text" class="form-control" name="description">
21+
</div>
22+
<button type="submit" class="btn btn-primary">Create</button>
23+
<a href="{{ url_for('admin/groups') }}" class="btn btn-secondary">Back</a>
24+
</form>
25+
</div>
26+
</div>
27+
</div>
28+
</div>
29+
</div>
30+
{% endblock %}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{% extends 'layouts/base.html' %}
2+
{% block title %}Delete Group{% endblock %}
3+
{% block content %}
4+
<div class="container py-4">
5+
<div class="row justify-content-center">
6+
<div class="col-md-6">
7+
<div class="card">
8+
<div class="card-header"><h5>Delete Group</h5></div>
9+
<div class="card-body">
10+
{% if deleted %}
11+
<div class="alert alert-success">Group deleted!</div>
12+
<a href="{{ url_for('admin/groups') }}" class="btn btn-secondary">Back to groups</a>
13+
{% else %}
14+
<p>Are you sure you want to delete group <strong>{{ group.name }}</strong> (ID: {{ group.id }})?</p>
15+
<form method="post">
16+
<button type="submit" class="btn btn-danger">Delete</button>
17+
<a href="{{ url_for('admin/groups') }}" class="btn btn-secondary">Cancel</a>
18+
</form>
19+
{% endif %}
20+
</div>
21+
</div>
22+
</div>
23+
</div>
24+
</div>
25+
{% endblock %}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{% extends 'layouts/base.html' %}
2+
{% block title %}Edit Group{% endblock %}
3+
{% block content %}
4+
<div class="container py-4">
5+
<div class="row justify-content-center">
6+
<div class="col-md-8">
7+
<div class="card">
8+
<div class="card-header"><h5>Edit Group #{{ group.id }}</h5></div>
9+
<div class="card-body">
10+
{% if success %}
11+
<div class="alert alert-success">Changes saved!</div>
12+
{% endif %}
13+
<form method="post">
14+
<div class="mb-3">
15+
<label class="form-label">Name</label>
16+
<input type="text" class="form-control" name="name" value="{{ group.name }}" required>
17+
</div>
18+
<div class="mb-3">
19+
<label class="form-label">Description</label>
20+
<input type="text" class="form-control" name="description" value="{{ group.description }}">
21+
</div>
22+
<button type="submit" class="btn btn-primary">Save</button>
23+
<a href="{{ url_for('admin/groups') }}" class="btn btn-secondary">Back</a>
24+
</form>
25+
</div>
26+
</div>
27+
</div>
28+
</div>
29+
</div>
30+
{% endblock %}

example/templates/admin/groups.html

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
<div class="row">
1010
<div class="col-12">
1111
<div class="card mb-4">
12-
<div class="card-header pb-0">
12+
<div class="card-header pb-0 d-flex justify-content-between align-items-center">
1313
<h6>Groups table</h6>
14+
<a href="{{ url_for('admin/groups/create') }}" class="btn btn-success btn-sm">Add group</a>
1415
</div>
1516
<div class="card-body px-0 pt-0 pb-2">
1617
<div class="table-responsive p-0">
@@ -48,9 +49,9 @@ <h6 class="mb-0 text-sm">{{ group.name }}</h6>
4849
</td>
4950
<!-- Actions -->
5051
<td class="align-middle">
51-
<a href="javascript:;" class="text-secondary font-weight-bold text-xs" data-toggle="tooltip" data-original-title="Edit user">
52-
Edit
53-
</a>
52+
<a href="{{ url_for('admin/groups/edit', group_id=group.id) }}" class="text-secondary font-weight-bold text-xs" data-toggle="tooltip" data-original-title="Edit group">Edit</a>
53+
|
54+
<a href="{{ url_for('admin/groups/delete', group_id=group.id) }}" class="text-danger font-weight-bold text-xs" data-toggle="tooltip" data-original-title="Delete group">Delete</a>
5455
</td>
5556
</tr>
5657
{% endfor %}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{% extends 'layouts/base.html' %}
2+
{% block title %}Add User{% endblock %}
3+
{% block content %}
4+
<div class="container py-4">
5+
<div class="row justify-content-center">
6+
<div class="col-md-8">
7+
<div class="card">
8+
<div class="card-header"><h5>Add User</h5></div>
9+
<div class="card-body">
10+
{% if success %}
11+
<div class="alert alert-success">User created!</div>
12+
{% endif %}
13+
<form method="post">
14+
<div class="mb-3">
15+
<label class="form-label">Name</label>
16+
<input type="text" class="form-control" name="name" required>
17+
</div>
18+
<div class="mb-3">
19+
<label class="form-label">Age</label>
20+
<input type="number" class="form-control" name="age" required>
21+
</div>
22+
<div class="mb-3">
23+
<label class="form-label">Email</label>
24+
<input type="email" class="form-control" name="email" required>
25+
</div>
26+
<div class="mb-3">
27+
<label class="form-label">Group</label>
28+
<select class="form-select" name="group_id" required>
29+
{% for group in groups %}
30+
<option value="{{ group.id }}">{{ group.name }}</option>
31+
{% endfor %}
32+
</select>
33+
</div>
34+
<div class="mb-3">
35+
<label class="form-label">Organization</label>
36+
<input type="text" class="form-control" name="organization">
37+
</div>
38+
<button type="submit" class="btn btn-primary">Create</button>
39+
<a href="{{ url_for('admin/users') }}" class="btn btn-secondary">Back</a>
40+
</form>
41+
</div>
42+
</div>
43+
</div>
44+
</div>
45+
</div>
46+
{% endblock %}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{% extends 'layouts/base.html' %}
2+
{% block title %}Delete User{% endblock %}
3+
{% block content %}
4+
<div class="container py-4">
5+
<div class="row justify-content-center">
6+
<div class="col-md-6">
7+
<div class="card">
8+
<div class="card-header"><h5>Delete User</h5></div>
9+
<div class="card-body">
10+
{% if deleted %}
11+
<div class="alert alert-success">User deleted!</div>
12+
<a href="{{ url_for('admin/users') }}" class="btn btn-secondary">Back to users</a>
13+
{% else %}
14+
<p>Are you sure you want to delete user <strong>{{ user.name }}</strong> (ID: {{ user.id }})?</p>
15+
<form method="post">
16+
<button type="submit" class="btn btn-danger">Delete</button>
17+
<a href="{{ url_for('admin/users') }}" class="btn btn-secondary">Cancel</a>
18+
</form>
19+
{% endif %}
20+
</div>
21+
</div>
22+
</div>
23+
</div>
24+
</div>
25+
{% endblock %}

0 commit comments

Comments
 (0)