Skip to content

Instantly share code, notes, and snippets.

@sunmeat
Created April 21, 2026 09:50
Show Gist options
  • Select an option

  • Save sunmeat/b4db9064dcd4db3a64e7980c7cbbc18e to your computer and use it in GitHub Desktop.

Select an option

Save sunmeat/b4db9064dcd4db3a64e7980c7cbbc18e to your computer and use it in GitHub Desktop.
CRUD операції. веб інтерфейс
# app / views.py:
from datetime import datetime
from django.shortcuts import render
from django.http import HttpRequest
from app.models import Author
def home(request):
message = None # повідомлення про успіх/помилку
# додавання автора
if request.method == 'POST' and request.POST.get('action') == 'create':
name = request.POST.get('name', '').strip()
birth_year = request.POST.get('birth_year', '').strip()
rating = request.POST.get('rating', '').strip()
if name:
Author.objects.create( # створюємо нового автора
name=name,
birth_year=int(birth_year) if birth_year else None,
rating=float(rating) if rating else None,
)
message = f'Автора "{name}" додано!'
else:
message = 'Помилка: введіть ім\'я автора.'
# оновлення автора
elif request.method == 'POST' and request.POST.get('action') == 'update':
author_id = request.POST.get('author_id')
name = request.POST.get('name', '').strip()
birth_year = request.POST.get('birth_year', '').strip()
rating = request.POST.get('rating', '').strip()
try:
author = Author.objects.get(id=author_id)
author.name = name
author.birth_year = int(birth_year) if birth_year else None
author.rating = float(rating) if rating else None
author.save()
message = f'Автора "{author.name}" оновлено!'
except Author.DoesNotExist:
message = 'Автора не знайдено.'
# видалити автора
elif request.method == 'POST' and request.POST.get('action') == 'delete':
author_id = request.POST.get('author_id')
try:
author = Author.objects.get(id=author_id)
name = author.name
author.delete()
message = f'Автора "{name}" видалено.'
except Author.DoesNotExist:
message = 'Автора не знайдено.'
# отримання списку всіх авторів для відображення
authors = Author.objects.all().order_by('id')
return render(request, 'app/index.html', {
'title': 'Автори',
'year': datetime.now().year,
'authors': authors,
'message': message,
})
###################################################################
def contact(request):
assert isinstance(request, HttpRequest)
return render(request, 'app/contact.html', {
'title': 'Contact',
'message': 'Your contact page.',
'year': datetime.now().year,
})
def about(request):
assert isinstance(request, HttpRequest)
return render(request, 'app/about.html', {
'title': 'About',
'message': 'Your application description page.',
'year': datetime.now().year,
})
=====================================================================================================================
app / templates / app / index.html:
{% extends "app/layout.html" %}
{% block content %}
<style>
@import url('https://fonts.googleapis.com/css2?family=Geologica:wght@300;400;500;600&family=Unbounded:wght@700&display=swap');
:root {
--bg: #0d0d0f;
--surface: #16161a;
--card: #1e1e24;
--border: #2a2a32;
--accent: #6c63ff;
--accent2: #a78bfa;
--green: #34d399;
--red: #f87171;
--text: #e8e8f0;
--muted: #6b6b80;
--radius: 10px;
}
.authors-page * {
box-sizing: border-box;
}
.authors-page {
font-family: 'Geologica', sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
padding: 40px 24px 80px;
}
.page-title {
font-family: 'Unbounded', sans-serif;
font-size: clamp(22px, 4vw, 36px);
font-weight: 700;
letter-spacing: -0.02em;
margin-bottom: 6px;
background: linear-gradient(135deg, var(--accent2), var(--accent));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.page-sub {
color: var(--muted);
font-size: 14px;
margin-bottom: 36px;
font-weight: 300;
}
.flash {
padding: 12px 18px;
border-radius: var(--radius);
margin-bottom: 28px;
font-size: 14px;
font-weight: 500;
border-left: 3px solid var(--green);
background: rgba(52, 211, 153, 0.08);
color: var(--green);
}
.add-card {
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 28px 28px 24px;
margin-bottom: 36px;
}
.card-label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--accent2);
margin-bottom: 18px;
display: flex;
align-items: center;
gap: 8px;
}
.card-label::after {
content: '';
flex: 1;
height: 1px;
background: var(--border);
}
.add-form-row {
display: grid;
grid-template-columns: 2fr 1fr 1fr auto;
gap: 12px;
align-items: end;
}
@media (max-width: 640px) {
.add-form-row {
grid-template-columns: 1fr;
}
}
.field-group {
display: flex;
flex-direction: column;
gap: 6px;
}
.field-group label {
font-size: 11px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--muted);
}
.field-group input {
background: var(--surface);
border: 1.5px solid var(--border);
border-radius: 7px;
padding: 10px 14px;
font-family: 'Geologica', sans-serif;
font-size: 14px;
color: var(--text);
outline: none;
transition: border-color 0.2s;
width: 100%;
}
.field-group input:focus {
border-color: var(--accent);
}
.field-group input::placeholder {
color: var(--muted);
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 10px 20px;
border: none;
border-radius: 7px;
font-family: 'Geologica', sans-serif;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: opacity 0.15s, transform 0.15s;
white-space: nowrap;
}
.btn:hover {
opacity: 0.85;
transform: translateY(-1px);
}
.btn:active {
transform: translateY(0);
}
.btn-add {
background: var(--accent);
color: #fff;
padding: 10px 24px;
align-self: flex-end;
}
.btn-save {
background: var(--surface);
color: var(--accent2);
border: 1px solid var(--accent);
font-size: 12px;
padding: 7px 14px;
}
.btn-delete {
background: transparent;
color: var(--red);
border: 1px solid rgba(248,113,113,0.35);
font-size: 12px;
padding: 7px 14px;
}
.btn-delete:hover {
background: rgba(248,113,113,0.1);
opacity: 1;
}
.table-card {
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
}
.table-header {
padding: 18px 24px;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
}
.table-header-title {
font-size: 13px;
font-weight: 600;
color: var(--text);
text-transform: uppercase;
letter-spacing: 0.08em;
}
.count-badge {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 20px;
padding: 3px 12px;
font-size: 12px;
font-family: monospace;
color: var(--accent2);
}
table {
width: 100%;
border-collapse: collapse;
}
thead th {
padding: 12px 20px;
text-align: left;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.15em;
color: var(--muted);
background: var(--surface);
border-bottom: 1px solid var(--border);
}
tbody tr {
border-bottom: 1px solid var(--border);
transition: background 0.15s;
}
tbody tr:last-child {
border-bottom: none;
}
tbody tr:hover {
background: rgba(108, 99, 255, 0.04);
}
td {
padding: 14px 20px;
font-size: 14px;
vertical-align: middle;
}
.id-cell {
font-family: monospace;
font-size: 12px;
color: var(--muted);
width: 50px;
}
.name-cell {
font-weight: 500;
}
.num-cell {
font-family: monospace;
color: var(--muted);
}
.rating-cell {
font-family: monospace;
font-weight: 600;
color: var(--accent2);
}
.actions-cell {
width: 1%;
white-space: nowrap;
}
.edit-form {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
.edit-form input[type="text"],
.edit-form input[type="number"] {
background: var(--surface);
border: 1.5px solid var(--border);
border-radius: 6px;
padding: 7px 10px;
font-family: 'Geologica', sans-serif;
font-size: 13px;
color: var(--text);
outline: none;
transition: border-color 0.2s;
}
.edit-form input[type="text"] {
width: 160px;
}
.edit-form input[type="number"] {
width: 80px;
}
.edit-form input:focus {
border-color: var(--accent);
}
.empty-state {
padding: 64px 24px;
text-align: center;
color: var(--muted);
}
.empty-state .es-icon {
font-size: 40px;
margin-bottom: 12px;
}
.empty-state p {
font-size: 14px;
}
.authors-page {
margin-left: calc(-50vw + 50%);
width: 100vw;
}
</style>
<div class="authors-page">
<div class="page-title">Автори</div>
{% if message %}
<div class="flash">✓ {{ message }}</div>
{% endif %}
<!-- форма додавання автора -->
<div class="add-card">
<div class="card-label">Додати нового автора</div>
<form method="POST">
{% csrf_token %}
<input type="hidden" name="action" value="create">
<div class="add-form-row">
<div class="field-group">
<label>Імʼя *</label>
<input type="text" name="name" placeholder="Тарас Шевченко" required>
</div>
<div class="field-group">
<label>Рік народження</label>
<input type="number" name="birth_year" placeholder="1814">
</div>
<div class="field-group">
<label>Рейтинг (0–10)</label>
<input type="number" name="rating" step="0.1" min="0" max="10" placeholder="9.8">
</div>
<button type="submit" class="btn btn-add">+ Додати</button>
</div>
</form>
</div>
<!-- таблиця авторів -->
<div class="table-card">
<div class="table-header">
<span class="table-header-title">Список авторів</span>
<span class="count-badge">{{ authors|length }}</span>
</div>
{% if authors %}
<table>
<tbody>
{% for author in authors %}
<tr>
<td class="id-cell">#{{ author.id }}</td>
<td class="name-cell" colspan="3">
<form method="POST" class="edit-form">
{% csrf_token %}
<input type="hidden" name="action" value="update">
<input type="hidden" name="author_id" value="{{ author.id }}">
<input type="text" name="name" value="{{ author.name }}" placeholder="Імʼя" required>
<input type="number" name="birth_year" value="{{ author.birth_year|default:'' }}" placeholder="Рік">
<input type="number" name="rating" value="{{ author.rating|default:'' }}" placeholder="0.0" step="0.1" min="0" max="10">
<button type="submit" class="btn btn-save">Оновити дані</button>
</form>
</td>
<td class="actions-cell">
<form method="POST" onsubmit="return confirm('Видалити {{ author.name }}?')">
{% csrf_token %}
<input type="hidden" name="action" value="delete">
<input type="hidden" name="author_id" value="{{ author.id }}">
<button type="submit" class="btn btn-delete">Видалити</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="empty-state">
<p>Авторів поки ще немає. Додайте першого!</p>
</div>
{% endif %}
</div>
</div>
{% endblock %}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment