Дисклеймер!

Статья предназначена для людей, которые достаточно хорошо разбираются в веб разработке на python. Это моя первая статья, поэтому прошу сильно не ругаться :)

Привет, Хабр!

Создавая небольшой сайтик на Django появилась мысль сделать из него web приложение(Опыт в создании веб приложение был, правда только на Flask). И в итоге начались поиски, которые так и не привели к наиболее приятному способы создания PWA приложения на Django, к моему удивлению даже на хабре не нашлось статьи на это тему (. Спустя 3 дня упорных поисков , я совершенно случайно наткнулся на gitlab, где задача была реализована достаточно понятно, и без старонних библиотек. Если вы достаточно хорошо понимаете django и сам принцип создание веб сайтов, данный gitlab можно посмотреть и скачать здесь.

Если хотите разобраться вместе со мной, тогда вперёд!

Установка Django и создание проекта

Ну сперва-наперво нужно установить сам Django.

pip install django

Ну или, если у вас линукс или что-то такое:

sudo pip install django

На всякий случай оставлю ссылку на pip библиотеку здесь.

Теперь пора создавать сам проект

Переходим в папку, где будет размещаться проект, и пишем:

django-admin startproject имя вышего проекта

У меня проект будет называться django-pwa-test

Заходим в папку проекта. Там должны быть: папка с именем вашего проекта, файл manage.py и файл db.sqlite3.

Переходим в папку с именем вашего проекта, и создаем там папки: static(здесь будут храниться некоторые статические файлы) и templates(здесь будут храниться frontend файлы). По желанию так-же стоит создать папку apps, в ней будут храниться приложения(я создам, а вы - как хотите).

Дальше. Переходим в папку apps и создаём приложение(у меня оно будет называться 'Pwa')

django-admin startapp Pwa

Создание манифеста и сервис воркера

И так. Для тех, кто не знает: манифест и сервис воркер - это основные файлы, которые и буду отвечать за работу PWA.

Service worker обеспечивают возможность работы offline, также он — посредник между клиентом и сервером, пропускающий через себя все запросы к серверу.

В манифесте будет храниться информация: о названии приложения, его описание, стартовый url, его иконки и тд.

Для создание манифеста:переходим в папку static и создаём файл manifest.json. В файле пишем:

{
    "name": "Django PWA",
    "short_name": "djangopwa",
    "start_url": "/",
    "scope": ".",
    "display": "standalone",
    "background_color": "#FFF",
    "theme_color": "#493174",
    "description": "Test app for Django and PWA",
    "dir": "ltr",
    "lang": "en-US",
    "orientation": "portrait-primary",
    "icons": [{
        "src": "/static/icons/aurss.96x96.png",
        "type": "image/png",
        "sizes": "96x96"
    }, {
        "src": "/static/icons/aurss.512x512.png",
        "type": "image/png",
        "sizes": "512x512"
    }]
}

Где: name и shortname - имена приложения, starturl - ulr, который будет открываться при запуске PWA приложения, scope - уровни, на которых может работать сервис воркер, icons - путь к иконкам для приложения(мы создадим их немного позже). Подробнее можно почитать здесь.

Создание сервис воркера

Переходим в папку templates и создаём файл sw.js. Пишем в нём:

console.log('Hello from sw.js');

importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.2.0/workbox-sw.js');

if (workbox) {
  console.log(`Yay! Workbox is loaded ????`);

  workbox.precaching.precacheAndRoute([
    {
      "url": "/",
      "revision": "1"
    }
  ]);

  workbox.routing.registerRoute(
    /\.(?:js|css)$/,
    workbox.strategies.staleWhileRevalidate({
      cacheName: 'static-resources',
    }),
  );

  workbox.routing.registerRoute(
    /\.(?:png|gif|jpg|jpeg|svg)$/,
    workbox.strategies.cacheFirst({
      cacheName: 'images',
      plugins: [
        new workbox.expiration.Plugin({
          maxEntries: 60,
          maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
        }),
      ],
    }),
  );

  workbox.routing.registerRoute(
    new RegExp('https://fonts.(?:googleapis|gstatic).com/(.*)'),
    workbox.strategies.cacheFirst({
      cacheName: 'googleapis',
      plugins: [
        new workbox.expiration.Plugin({
          maxEntries: 30,
        }),
      ],
    }),
  );
} else {
  console.log(`Boo! Workbox didn't load ????`);
}

Если хотите больше узнать о том, как работает сервис воркер, то смотрите здесь.

Подключение PWA к html файлу

Переходим в папку templates и создаем html файл с любым именем (у меня будет home.html). В html файле прописываем:

<!DOCTYPE html>
<html>
<head>
	{% load static %}

	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="manifest" href="{% static 'manifest.json' %}">

	<title>hello world</title>
</head>
<body>

	 <script>
       if ('serviceWorker' in navigator) {
           navigator.serviceWorker.register('{% url "sw.js" %}', { scope: '/' }).then(function(reg) {
               // registration worked
               console.log('Registration succeeded. Scope is ' + reg.scope);
           }).catch(function(error) {
               // registration failed
               console.log('Registration failed with ' + error);
           });
       }
   </script>
	
</body>
</html>

Пробежимся по основному

  • {% load static %} дает нам доступ к папке static

  • Через тег link в 8 строке мы подключаем файл манифеста.

  • С помощью небольшого js скрипта, мы подключаем сервис воркер.

Иконки для манифеста

Если вспомнить код манифеста, а точнее часть с параметрами иконок:

"icons": [{
        "src": "/static/icons/aurss.96x96.png",
        "type": "image/png",
        "sizes": "96x96"
    }, {
        "src": "/static/icons/aurss.512x512.png",
        "type": "image/png",
        "sizes": "512x512"
    }]

Можно заметить, что иконки берутся из /static/icons, поэтому мы переходим в папку static и создаём там папку с именем icons. В этой папке будут храниться иконки разного размера, для вашего приложения. В данном случае, в папку icons нужно сохранить изображения с размерами: 96x96 512x512. С именем, которое указано в манифесте(При желании его можно изменить)

На этом создание дополнительных файлов подходит к концу)

И так, если вы всё сделали правильно, то ваш проект будет выглядеть как-то так :

django-pwa-test
	manage.py
  db.sqlite3
	django-pwa-test
  	apps
    	Pwa
      	python файлы
		static
    	manifest.json
      icons
      	ваши иконки для приложения
    templates
    	home.html
      sw.js
      	

Настройка параметров в settings.py

Теперь пора немного изменить настройки приложения)

Переходим в основную папку проекта, находим settings.py и дополняем.

Для начала нужно импортировать модуль os:

import os

Теперь ищем переменную TEMPLATES, в ней ищем массив с именем DIRS, и добавляем в него:

os.path.join(BASE_DIR, 'имя вашего приложения/templates')

Должно получиться что-то такое:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            os.path.join(BASE_DIR, 'имя вашего приложения/templates'),

        ],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

Далее ищем переменную BASE_DIR(она будет в самом начале), и после неё пишем:

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'имя вышего приложения/static/'),
]

STATIC_ROOT = BASE_DIR / 'static'

На этом изменения в настройках закончены.

Рендер страницы и доступ к sw.js

Для того, чтобы наша страница могла получить доступ к файлу sw.js на нужно написать класс, который будет в этом участвовать.

Идём в папку с приложением(именно приложением, не проектом. Если не забыли, у меня приложение называется Pwa) и записываем в файл views.py:

from django.shortcuts import render
from django.views.generic import TemplateView


# Create your views here.


def home(request):
	return render(request, 'home.html')


class ServiceWorkerView(TemplateView):
    template_name = 'sw.js'
    content_type = 'application/javascript'
    name = 'sw.js'

Быстрый разбор:

  • Если вы хотя бы немного знаете Django, то функция home должна быть вам понятна(Если вы не знаете, как она работает, то советую сначала поучить Django, и потом уже приходить сюда. Без обид)

  • через urls.py методом .as_view() мы получим доступ к классу ServiceWorkerView (а также и его значениям), и достанем из него значение переменно name(она будет использована для получения доступа к файлу).

Добавление адресов в urls.py

Здесь мы пропишем адреса для отображения основной страницы и sw.js

Идем в основную папку проекта, открываем файл urls.py и пишем.

Для начала из созданного приложения (У меня это Pwa) нужно импортировать файл

В случае, если вы, как и я сохранили приложение в папке apps:

from .apps.Pwa import views

Если же вы самостоятельная личность, и сохранили без папки apps, то:

from .Pwa import views

Напоминаю: я импортирую из папки Pwa потому, что так называется моё приложение(у вас оно может быть другим, но тогда вам надо проверить все файлы и сменить их путь на свой)!!!

Теперь в массив urlpatterns нужно добавить следующие значения: path("", views.home), path('sw.js',views.ServiceWorkerView.as_view(), name=views.ServiceWorkerView.name,)

Теперь ваш urls.py должен выглядеть как-то так:

from django.contrib import admin
from django.urls import path
from .apps.Pwa import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path("", views.home),
    path('sw.js',
        views.ServiceWorkerView.as_view(),
        name=views.ServiceWorkerView.name,
    ),
]

Разбор:

  • path('', views.home) прорисует основную страницу вызвав функцию home() из файла view.py в нашем приложении

  • path('sw.js',views.ServiceWorkerView.as_view(),name=views.ServiceWorkerView.name) позволяет получить доступ к файлу sw.js

На этом пока всё. Если хотите протестировать, то переходите в папку с файлом manage.py, и выполняйте команду python3 manage.py runserver

Открывайте браузер по указанной ссылке(она в терминале), и если ваш браузер поддерживает pwa, то на панели поиска(справа, вверху) появится предложение установить приложение. Если у вас FireFox, то скорее всего не появится.

Ели вы в поддерживающем pwa браузере, но предложение установить приложение, или иконка установки так и не появились, то нажимайте F12, и посмотрите в консоли ошибки, если ошибок нет, то нажимайте F12 переходите на вкладку 'Приложения', и проверьте манифест и сервис воркер. Если ничего не помогло, тогда гугл в помощь!

P.S. Открыт для критики, буду рад, если укажите на ошибки и недочёты :)

Комментарии (28)


  1. WondeRu
    16.08.2022 12:22
    +3

    >sudo pip install django

    Мой совет: пользуйтесь виртуальными окружениями (virtual environments). Достаточно потратить 20 минут на изучение, дальше проекты можно будет легко поднимать. удачи)


    1. Programming_owl Автор
      16.08.2022 13:13
      +1

      Спасибо за совет. Обязательно им воспользуюсь ;)


  1. funca
    16.08.2022 13:00
    +2

    Не очень понятно, зачем sw.js класть в templates/? Если положить в static/, тогда не понадобится отдельная view и роут. В продакшне статика наверняка будет отдаваться не джангой, а каким-нибудь CDN.


    1. Programming_owl Автор
      16.08.2022 13:11

      Вы полностью правы. Мои поиски смогли привести только к такому способу, и я совсем не подумал, что файл можно положить в static/. Большое спасибо)


      1. Programming_owl Автор
        16.08.2022 19:54

        Я переправил вашу теорию на счёт sw.js в static, и пришел к выводу, что этот способ является не рабочим(сервис воркеру принципиально, чтобы его отправляли через urls.py, такая же загвостка появляется и при работе с Flask


  1. Fox_exe
    16.08.2022 13:44
    +2

    Возникает резонный вопрос: А зачем тут Django?

    Да, понятно, что можно и json'ку генерить, и рулить её переменными через админку и авторизацию прикрутить и тд и тп...

    Но всё это в статье вообще никак не затрагивается... А затрагивается лиш вопрос отдачи статики, с чем справится вообще любой веб-сервер (Который, при желании, на томже Python'е пишется в пару строк кода вообще без сторонних либ (только socket'ы)


    1. Programming_owl Автор
      16.08.2022 13:53

      Вы конечно же правы, но изначально эта статья писалась только для того, чтобы объяснить: какие файлы нужно использовать, как прикрутить нужные настройки, и сделать веб приложение без библиотеки django-pwa. А также объяснить это тем, кто только начал интересоваться разработкой веб приложений.


      1. Fox_exe
        16.08.2022 14:17
        +2

        Но, согласитесь, сейчас статья выглядит слабеньким переводом (перепечаткой) документации Django (Django tutorial 01) и не привносит ничего нового. Скорее даже немного вредит (Отдавать файл через шаблоны? Обычно такое просто кладут в static, о чем уже писали в комментах выше)


        1. Programming_owl Автор
          16.08.2022 15:45

          И да, и нет. Повторюсь, я не собирался вносить что-то волшебное, а просто показать один из способов(согласитесь, ведь новичку не так уж и просто понять даже это). А насчёт отдачи файла через шаблон, согласен, это не самый лучший способ.


  1. rSedoy
    16.08.2022 16:28

    Всё таки это сборник каких-то плохих советов: sudo, раздача статики через django, непонимание как работает импорт, os.path.join вмесе с pathlib (да перейди уже наконец на pathlib), манифесты с жесткими путями к static, а название app Pwa чего только стоит (ни в pep8, ни в Красную Армию). Слишком много всего кривого для такой мелкой статьи.


    1. DonAgosto
      16.08.2022 18:58

      да перейди уже наконец на pathlib
      Сейчас на 100% перейти не возможно, к сожалению. Достаточно мого фич из «старых» модулей не реализовано пока. Например: github.com/python/cpython/issues/77609


      1. rSedoy
        16.08.2022 19:31

        так разговор конкретно про django, скорее даже про settings.py


  1. Ustas4
    16.08.2022 17:45

    Прочитал всю статью и все комментарии и возник вопрос.

    А как надо? И поподробнее, пожалуйста


    1. Programming_owl Автор
      16.08.2022 17:50

      Что именно вызвало вопрос?


      1. Programming_owl Автор
        16.08.2022 18:32

        Если вам интересно, какой именно способ нужно использовать для sw.js, то советую использовать метод описанный в статьей (он затратнее по коду, но рабочий)


        1. rSedoy
          16.08.2022 19:32

          нет, не нужно его советовать, из-за кучи перечисленных проблем


          1. Programming_owl Автор
            16.08.2022 19:43

            Возможно этот способ и проблемный, но он хотя бы рабочий. sw.js нужно именно отправлять через urls, а не брать из static(Такой метод применяется даже при создании pwa для Flask). Если не верите, попробуйте сделать все так, как в статье, только sw.js брать из static. И файл сервис воркера просто не будет работать


            1. rSedoy
              16.08.2022 20:32

              а зачем мне вообще делать всё как в статье? Без проблем клал сервис воркер в статик и не привязывал его напрямую через django, нет ни одной причины это делать, максимум это сгенерировать его при деплое. Кстати, в статье причина тоже не раскрыта.


              1. Programming_owl Автор
                16.08.2022 20:34

                Не могли бы вы тогда показать, ка к это сделать через static?


                1. rSedoy
                  16.08.2022 20:43

                  берешь и кладешь, проблема в чём возникла? в serviceWorker.register не смог путь до него описать? или прочитать ошибку в консоли разработчика?


                  1. Programming_owl Автор
                    16.08.2022 20:51

                    Спасибо за замечания (хотелось бы по мягче, но спасибо) Проблема была в том, что браузер отказывался исполнять изменения из-за кэширования. Способ и правда рабочий. Обязательно добавлю его в стотью


      1. Ustas4
        16.08.2022 21:33

        Просто тут много критики о статье.

        Но не пишут как надо правильно по мнению критиков.


        1. rSedoy
          16.08.2022 22:24

          по мнению критиков, правильно просто удалить отсюда django.


  1. Programming_owl Автор
    16.08.2022 20:51

    Вопрос с sw.js решился в пользу 2 способов:

    1. Отправлять через urls

    2. Прогркжать через static


    1. magiavr
      16.08.2022 22:23

      Тогда ждём новой версии с исправлениями и улучшениями.


  1. ALito
    17.08.2022 17:28

    Автор, а почему Вы не пользуетесь проверкой орфографии? С таким количеством ошибок в тексте, очень тяжело его читать.


  1. danilovmy
    17.08.2022 23:48

    Привет @Programming_owl. Работаю с Django давно, можешь посмотреть мои заметки тут, на Habr, и потому понимаю, что статья, конечно, ужасна с технической точки зрения. Но есть хорошие моменты которые и хочу отметить. Вероятно, кто-то еще увидит мой комментарий и ему это поможет.

    Во-первых. Отлично, что использовал классовое представление (DGCBV). По мне - это верное направление в разработке Django проекта. Но непонятен кишмиш: в выдаче манифеста представление на классе, в home - представление на функции. Ты уж определись что ли ;)

    Во-вторых. Это просто супер, что для выдачи манифеста используешь GCBV. Гони взашей всех советчиков класть манифест в статику. Да. Для проекта со статической информацией, вроде твоего примера, манифест кладем в статику и идем спать. Но! Для такого проекта не надо PWA. Прогрессивное приложение на то и нужно, когда проект живет. В манифесте пишутся важные клентские данные, которые, в случае мультитеннантной организациии проекта, скорее всего, сохранены в базе. И никакой статикой не сделать манифест с описанием полученным из базы, картинками бэкграундом и т.п., а вот сгенерить его в представлении - запросто. Ну и закешить потом.

    Странно, что никто не отметил, что имя приложения у тебя с большой буквы (Pwa). Это конечно косяк. В Django иногда app_label преобразовывается в нижний регистр. Например, если будешь писать что-то с подгрузкой по _meta из relatedField по app_label, можешь попасть в ситуацию, что модуль не будет найден.

    Странно, что никто не заметил, что у тебя чистый js в Django шаблоне. Не кошерно. Можно и нужно его подключить через файл.

    Наш недавний фейл тоже у тебя есть. Не надо подгружать шрифты с google. У нас недавно app не работал из-за того, что google не отдавал шрифт. Пришлось переделывать на подгрузку с нашего сервера. Времени разобраться, что там с гуглом не было. "Надо, что б работало"

    В общем - успеха тебе. Твой совет "Если вы не знаете, как Django работает, то советую сначала поучить Django, и потом уже приходить сюда" - попробуй прменить и к себе, узнаешь много нового.

    p.s. Если будешь на Django Con EU 2022 или на Django Con US 2022, можем пересечься, я там выступаю с докладами.


  1. alexshipin
    18.08.2022 09:10
    +1

    У меня свой список замечаний будет.

    Если вы хотя бы немного знаете Django, то функция home должна быть вам понятна(Если вы не знаете, как она работает, то советую сначала поучить Django, и потом уже приходить сюда. Без обид)

    Интересно, что объяснение того, что это за функция, заняло бы меньше текста, чем ваше "без обид".

    "Функция def home(request) отображает файл шаблона home.html при заходе на сайт".

    Переходим в папку templates и создаем html файл с любым именем (у меня будет home.html). В html файле прописываем:

    home.html, допустим, но index.html будет понятнее, так как это главная страница всего вашего "PWA".

    Переходим в папку templates и создаём файл sw.js. Пишем в нём:

    serviceWorker.js. Не используйте лишние сокращения, чтобы в дальнейшем понимать, что это именно сервис воркер, а не что-то иное, к примеру: sw.js для меня это startWeb, smartWatch и тд, но только потом serviceWorker

    django-admin startapp Pwa

    Дать бы вам по рукам за такое название. django-admin startapp pwa

    def home(request):
    return render(request, 'home.html')

    class ServiceWorkerView(TemplateView):
    template_name = 'sw.js'
    content_type = 'application/javascript'
    name = 'sw.js'

    Функции и классы. Зачем?. Либо функции, либо классы. Это не сложно, но более понятно для стороннего разработчика

    urlpatterns = [
    path('admin/', admin.site.urls),
    path("", views.home),
    path('sw.js', views.ServiceWorkerView.as_view(), name=views.ServiceWorkerView.name)
    ]

    Вам уже не нужен путь "admin/", так как вы его не используете.

    python3 manage.py runserver

    В Linux - да, в Windows - лесом.
    python3 manage.py runserver - если у вас Linux
    python manage.py runserver - если у вас Windows

    Общее впечатление

    Статья просто описывает первые шаги из tutorial по Django, но пытается ещё и в PWA, при этом, весьма скудно и посредственно.

    Минимального функционала нет от слова "совсем". То есть, говоря другими словами, пользователь, проделав все выше описанные манипуляции, на выходе получит пустую страницу с заголовком hello world, и предложением установить приложение, что для понимания весьма мало.

    Для того, чтобы пользователь получил несколько больше опыта, необходимо было хотя бы вывести какой-то минимальный текст на самой странице, к примеру "Ваше первое Progressive Web Application" если скрипт Service Worker отработал успешно (загрузился) в моменте срабатывания console.log('Registration succeeded. Scope is ' + reg.scope);

    При этом всём, код загрузки Service Worker располагается только на одной странице (да и весь пример только об одной странице), но при этом, если пользователь захочет создать другую, то ему придётся данный скрипт перетаскивать в каждый документ. То есть, не раскрыта тема работы с шаблонами, что является большим преимуществом Django для простого пользователя.

    В общем и целом, я бы рекомендовал автору внимательнее отнестись к статье, взяв на вооружение https://peps.python.org/pep-0008 и все указанные выше рекомендации от других членов сообщества.