Исходный код: 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;
}

На этом блок кода окончен. Стили можно посмотреть в репозитории.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *