Исходный код: https://github.com/ITmind/django-svelte-template
В первой части в шаблон страницы Django был внедрен SPA. В данной части будет разработан пример приложения с обращением к базе данных. Приложение будет хранить и считывать из БД заметки.
Установим две библиотеки Python. Одна для работы с БД PostgreSQL, вторая для работы с переменными среды (.env файлами):
pip install psycopg[binare,pool] python-decouple
В корне проекта создадим файл “.env” следующего вида:
SECRET_KEY=django-insecure-074ktb7j
DEBUG=1
#0 - SQLite, 1 - PostgreSQL
USE_POSTGRES=1
DATABASE_NAME=spa
DATABASE_HOST=db
DATABASE_PORT=5432
#создание административного пользователя при первом запуске контейнера
#или при python manage.py createsuperuser --noinput
DJANGO_SUPERUSER_PASSWORD=admin
DJANGO_SUPERUSER_USERNAME=admin
DJANGO_SUPERUSER_EMAIL=admin@django.com
#одновременно и для docker postgres и для Django
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
Это переменные среды, которые мы будем использовать для настройки Django
Теперь в setting.py мы можем написать так:
SECRET_KEY = config('SECRET_KEY')
Полный текст settings.py смотрите в репозитории
Создадим модель (таблицу в БД) записи в файле model.py:
class Notes(models.Model):
date = models.DateField(primary_key=True, verbose_name="date published")
title = models.CharField(max_length=200)
note = models.CharField(max_length=2000)
# Данный атрибут нужен для переходов по url к конкретной записи.
# Если бы у нас primary key был например int, то slug можно было не делать
slug = models.SlugField(editable=False, unique=True)
def __str__(self):
"""
Возвращает представление экземпляра класса
:return:
"""
return f'{self.date}: {self.title}'
def save(self, *args, **kwargs):
"""
Перехват записи экземпляра класса.
Нужен для формирования slug
"""
to_slug = f"{self.date}-{self.title}"
self.slug = slugify(to_slug)
super().save(*args, **kwargs)
И добавим в views.py несколько представлений:
class DetailView(generic.DetailView):
"""
Встроенный класс для отображения детальных записей
"""
model = Notes
template_name = 'notes_detail.html'
def notes(request):
"""
GET запрос списка всех записей из БД
:param request: контекст запроса (подставляет Django)
:return: json содержащий массив элементов Notes
"""
note_list = list(Notes.objects.all().values())
return JsonResponse(note_list, safe=False)
@require_POST
def save(request):
"""
POST запрос на запись тела запроса в БД
:param request: контекст запроса (подставляет Django)
:return: возвращает ответ об окончании записи в БД
"""
body = json.loads(request.body)
try:
note = Notes.objects.get(pk=body['date'])
except Notes.DoesNotExist:
note = Notes(date=body['date'])
else:
note.title = body['title']
note.note = body["note"]
note.save()
return HttpResponse('note update')
Пропишем маршруты к нашим представлениям в файле urls.py:
urlpatterns = [
path('admin/', admin.site.urls),
path('', views.spa),
path('notes/<slug:slug>/', views.DetailView.as_view()),
path('notes/', views.notes),
path('save', views.save),
]
Здесь видно, зачем нам нужен был атрибут slug в модели.
На этом работа с Django закончена, переходим к Svelte.
Создаем в папке /src/lib проекта svelte новый компонент Notes.svelte. Данный компонент будет отображать таблицу записей с возможностью редактирования каждой записи непосредственно в самой таблице.
Тело компонента:
<table>
<thead>
<tr>
<th>Дата</th>
<th>Название</th>
<th colspan="2"><button class="btnadd" on:click={add}>Добавить</button></th>
</tr>
</thead>
<tbody>
{#each notes as note}
<tr on:click={event => onSelectRow(note)}>
{#if note.edit}
<td><input class="editable" type="date" bind:value={note.date}></td>
<td><input class="editable" bind:value={note.title}></td>
{:else}
<td>{note.date}</td>
<td>{note.title}</td>
{/if}
<td>
<button on:click={event => onEditClick(event, note)}>Edit</button>
</td>
<td><a href="notes/{note.slug}/">Details</a></td>
</tr>
{/each}
</tbody>
</table>
{#if curentNote.edit}
<input class="note editable" bind:value={curentNote.note}>
{:else}
<div class="note noteditable">{curentNote.note}</div>
{/if}
Получится такое представление:
При нажатии Edit в ячейках колонки и в нижнем поле появляется input для редактирования записи
Теперь пропишем в блок script переменные и функцию, получающую список записей из Django:
<script>
import {onMount} from "svelte";
let notes = [...Array(3).keys()].map(() => {
return {date:'', title:''}
});
let currentNote= {note: ''};
onMount(readTable);
async function readTable() {
let response = await fetch("/notes");
notes = await response.json();
notes.map((value) =>value['edit'] = false);
}
</script>
Здесь при запуске вызывается функция readTable которая отправляет GET запрос на точку /notes и заполняет массив notes. currentNote – текущая выбранная запись.
Добавим две функции для выбора и включения режима редактирования строки:
function onSelectRow(note){
if(currentNote !== note) {
notes.map((value) => saveNote(value));
currentNote = note;
notes = notes;
}
}
function onEditClick(event, note){
note["edit"] = true;
currentNote = note;
notes = notes;
event.stopPropagation();
}
notes = notes
необходимо из-за того, что добавление/изменение элементов массива не вызывает перерисовку зависимых элементов HTML и одним из способов перерисовать элементы HTML – это выполнить присвоение переменной содержащей массив.
Запись элемента в БД будет происходить когда меняется выбранный элемент в таблице (типа потери фокуса строки). Для записи используется следующая функция:
function saveNote(note) {
//TODO: запись нужно делать, только если есть изменения
//для этого текущий редактируемый элемент должен быть отдельной переменной
//и сравнивать с тем, что в notes
note['edit'] = false;
var csrftoken = getCookie('csrftoken');
fetch("/save", {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8',
'X-CSRFToken': csrftoken
},
body: JSON.stringify(note),
credentials: 'include'
}).then(()=> console.log("save success"));
}
В этой функции мы отправляем элемент в теле POST запроса и главное здесь это формирование заголовка. Т.к. в django есть защита POST запросов с помощью токена, то нам нужно передать его в заголовке. Получить токен можно из кода страницы, для этого мы в шаблоне помещали тег {% csrf_token %}
:
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
На этом блок кода окончен. Стили можно посмотреть в репозитории.