Django создание сайта с нуля пошаговая инструкция

Writing your first Django app, part 1¶

Let’s learn by example.

Throughout this tutorial, we’ll walk you through the creation of a basic
poll application.

It’ll consist of two parts:

  • A public site that lets people view polls and vote in them.
  • An admin site that lets you add, change, and delete polls.

We’ll assume you have Django installed already. You can
tell Django is installed and which version by running the following command
in a shell prompt (indicated by the $ prefix):

/

$ python -m django --version
...> py -m django --version

If Django is installed, you should see the version of your installation. If it
isn’t, you’ll get an error telling “No module named django”.

This tutorial is written for Django 4.2, which supports Python 3.8 and
later. If the Django version doesn’t match, you can refer to the tutorial for
your version of Django by using the version switcher at the bottom right corner
of this page, or update Django to the newest version. If you’re using an older
version of Python, check What Python version can I use with Django? to find a compatible
version of Django.

See How to install Django for advice on how to remove
older versions of Django and install a newer one.

Where to get help:

If you’re having trouble going through this tutorial, please head over to
the Getting Help section of the FAQ.

Creating a project¶

If this is your first time using Django, you’ll have to take care of some
initial setup. Namely, you’ll need to auto-generate some code that establishes a
Django project – a collection of settings for an instance of Django,
including database configuration, Django-specific options and
application-specific settings.

From the command line, cd into a directory where you’d like to store your
code, then run the following command:

/

$ django-admin startproject mysite
...> django-admin startproject mysite

This will create a mysite directory in your current directory. If it didn’t
work, see Problems running django-admin.

Note

You’ll need to avoid naming projects after built-in Python or Django
components. In particular, this means you should avoid using names like
django (which will conflict with Django itself) or test (which
conflicts with a built-in Python package).

Where should this code live?

If your background is in plain old PHP (with no use of modern frameworks),
you’re probably used to putting code under the web server’s document root
(in a place such as /var/www). With Django, you don’t do that. It’s
not a good idea to put any of this Python code within your web server’s
document root, because it risks the possibility that people may be able
to view your code over the web. That’s not good for security.

Put your code in some directory outside of the document root, such as
/home/mycode.

Let’s look at what startproject created:

mysite/
    manage.py
    mysite/
        __init__.py
        settings.py
        urls.py
        asgi.py
        wsgi.py

These files are:

  • The outer mysite/ root directory is a container for your project. Its
    name doesn’t matter to Django; you can rename it to anything you like.
  • manage.py: A command-line utility that lets you interact with this
    Django project in various ways. You can read all the details about
    manage.py in django-admin and manage.py.
  • The inner mysite/ directory is the actual Python package for your
    project. Its name is the Python package name you’ll need to use to import
    anything inside it (e.g. mysite.urls).
  • mysite/__init__.py: An empty file that tells Python that this
    directory should be considered a Python package. If you’re a Python beginner,
    read more about packages in the official Python docs.
  • mysite/settings.py: Settings/configuration for this Django
    project. Django settings will tell you all about how settings
    work.
  • mysite/urls.py: The URL declarations for this Django project; a
    “table of contents” of your Django-powered site. You can read more about
    URLs in URL dispatcher.
  • mysite/asgi.py: An entry-point for ASGI-compatible web servers to
    serve your project. See How to deploy with ASGI for more details.
  • mysite/wsgi.py: An entry-point for WSGI-compatible web servers to
    serve your project. See How to deploy with WSGI for more details.

The development server¶

Let’s verify your Django project works. Change into the outer mysite directory, if
you haven’t already, and run the following commands:

/

$ python manage.py runserver
...> py manage.py runserver

You’ll see the following output on the command line:

Performing system checks...

System check identified no issues (0 silenced).

You have unapplied migrations; your app may not work properly until they are applied.
Run 'python manage.py migrate' to apply them.

July 11, 2023 - 15:50:53
Django version 4.2, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Note

Ignore the warning about unapplied database migrations for now; we’ll deal
with the database shortly.

You’ve started the Django development server, a lightweight web server written
purely in Python. We’ve included this with Django so you can develop things
rapidly, without having to deal with configuring a production server – such as
Apache – until you’re ready for production.

Now’s a good time to note: don’t use this server in anything resembling a
production environment. It’s intended only for use while developing. (We’re in
the business of making web frameworks, not web servers.)

Now that the server’s running, visit http://127.0.0.1:8000/ with your web
browser. You’ll see a “Congratulations!” page, with a rocket taking off.
It worked!

Changing the port

By default, the runserver command starts the development server
on the internal IP at port 8000.

If you want to change the server’s port, pass
it as a command-line argument. For instance, this command starts the server
on port 8080:

/

$ python manage.py runserver 8080
...> py manage.py runserver 8080

If you want to change the server’s IP, pass it along with the port. For
example, to listen on all available public IPs (which is useful if you are
running Vagrant or want to show off your work on other computers on the
network), use:

/

$ python manage.py runserver 0.0.0.0:8000
...> py manage.py runserver 0.0.0.0:8000

Full docs for the development server can be found in the
runserver reference.

Automatic reloading of runserver

The development server automatically reloads Python code for each request
as needed. You don’t need to restart the server for code changes to take
effect. However, some actions like adding files don’t trigger a restart,
so you’ll have to restart the server in these cases.

Creating the Polls app¶

Now that your environment – a “project” – is set up, you’re set to start
doing work.

Each application you write in Django consists of a Python package that follows
a certain convention. Django comes with a utility that automatically generates
the basic directory structure of an app, so you can focus on writing code
rather than creating directories.

Projects vs. apps

What’s the difference between a project and an app? An app is a web
application that does something – e.g., a blog system, a database of
public records or a small poll app. A project is a collection of
configuration and apps for a particular website. A project can contain
multiple apps. An app can be in multiple projects.

Your apps can live anywhere on your Python path. In
this tutorial, we’ll create our poll app in the same directory as your
manage.py file so that it can be imported as its own top-level module,
rather than a submodule of mysite.

To create your app, make sure you’re in the same directory as manage.py
and type this command:

/

$ python manage.py startapp polls
...> py manage.py startapp polls

That’ll create a directory polls, which is laid out like this:

polls/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    views.py

This directory structure will house the poll application.

Write your first view¶

Let’s write the first view. Open the file polls/views.py
and put the following Python code in it:

polls/views.py

from django.http import HttpResponse


def index(request):
    return HttpResponse("Hello, world. You're at the polls index.")

This is the simplest view possible in Django. To call the view, we need to map
it to a URL — and for this we need a URLconf.

To create a URLconf in the polls directory, create a file called urls.py.
Your app directory should now look like:

polls/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    urls.py
    views.py

In the polls/urls.py file include the following code:

polls/urls.py

from django.urls import path

from . import views

urlpatterns = [
    path("", views.index, name="index"),
]

The next step is to point the root URLconf at the polls.urls module. In
mysite/urls.py, add an import for django.urls.include and insert an
include() in the urlpatterns list, so you have:

mysite/urls.py

from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path("polls/", include("polls.urls")),
    path("admin/", admin.site.urls),
]

The include() function allows referencing other URLconfs.
Whenever Django encounters include(), it chops off whatever
part of the URL matched up to that point and sends the remaining string to the
included URLconf for further processing.

The idea behind include() is to make it easy to
plug-and-play URLs. Since polls are in their own URLconf
(polls/urls.py), they can be placed under “/polls/”, or under
“/fun_polls/”, or under “/content/polls/”, or any other path root, and the
app will still work.

When to use include()

You should always use include() when you include other URL patterns.
admin.site.urls is the only exception to this.

You have now wired an index view into the URLconf. Verify it’s working with
the following command:

/

$ python manage.py runserver
...> py manage.py runserver

Go to http://localhost:8000/polls/ in your browser, and you should see the
text “Hello, world. You’re at the polls index.”, which you defined in the
index view.

Page not found?

If you get an error page here, check that you’re going to
http://localhost:8000/polls/ and not http://localhost:8000/.

The path() function is passed four arguments, two required:
route and view, and two optional: kwargs, and name.
At this point, it’s worth reviewing what these arguments are for.

path() argument: route

route is a string that contains a URL pattern. When processing a request,
Django starts at the first pattern in urlpatterns and makes its way down
the list, comparing the requested URL against each pattern until it finds one
that matches.

Patterns don’t search GET and POST parameters, or the domain name. For example,
in a request to https://www.example.com/myapp/, the URLconf will look for
myapp/. In a request to https://www.example.com/myapp/?page=3, the
URLconf will also look for myapp/.

path() argument: view

When Django finds a matching pattern, it calls the specified view function with
an HttpRequest object as the first argument and any
“captured” values from the route as keyword arguments. We’ll give an example
of this in a bit.

path() argument: kwargs

Arbitrary keyword arguments can be passed in a dictionary to the target view. We
aren’t going to use this feature of Django in the tutorial.

path() argument: name

Naming your URL lets you refer to it unambiguously from elsewhere in Django,
especially from within templates. This powerful feature allows you to make
global changes to the URL patterns of your project while only touching a single
file.

When you’re comfortable with the basic request and response flow, read
part 2 of this tutorial to start working with the
database.

Напишем простое веб-приложение на Django.

Джанго — это Open Source фреймворк для создания веб-приложений различной сложности на Python. Одним из его основных преимуществ является то, что вам нужно позаботиться только о логике будущего веб-приложения, остальное сделает Django.

Мы создадим приложение, у которого будет панель администратора и возможность загружать загадки, а у пользователей, соответственно, возможность отвечать на них. Во время разработки будут использоваться Python 3.4.3 и Django 1.9.1.

  1. Устанавливаем Django
  2. Создаём проект

Устанавливаем Django

Делается это очень просто, в командной строке нужно написать: pip install Django==1.9.1.

Создаём проект

Если вы правильно установили Django, то после запуска django-admin --version вы увидите текущую версию фреймворка. Теперь создадим проект. Это можно сделать следующим образом: django-admin startproject django_example.

Как только создание проекта будет завершено, взглянем на директорию нашего проекта:

  • django_example/__init__.py — пустой файл, который говорит Python, что данная директория должна восприниматься в качестве пакета.
  • django_example/settings.py содержит конфигурацию нашего проекта.
  • django_example/urls.py — здесь объявляются URL.
  • django_example/wsgi.py — с помощью него приложение может работать с веб-сервером по протоколу WSGI.
  • manage.py позволяет взаимодействовать с проектом.

Теперь пришло время запустить наше приложение. Для этого в командной строке нужно написать python manage.py runserver. После этого в адресной строке браузера нужно написать: http://127.0.0.1:8000/. Если вы увидели «You have unapplied migrations; your app may not work properly until they are applied.», то не волнуйтесь, мы вернемся к этому чуть позже.

Пишем веб-приложение на Django

Определим различие между проектом и приложением. Приложение — это программа, которая что-то делает, а проект — это группа приложений.

Итак, приступим к созданию приложения. Это делается следующим образом: python manage.py startapp riddles.
Как только создано веб-приложение, напишем вид, по правилам Джанго все виды должны храниться в файле views.py.

riddles/views.py

from django.http import HttpResponse


def index(request):
    return HttpResponse("Hello, World!")

Теперь, чтобы привязать наш вид к URL, создадим файл urls.py.

riddles/urls.py

from django.conf.urls import url

from . import views

app_name = 'riddles'

urlpatterns = [
    url(r'^$', views.index, name='index'),
]

В urls.py мы должны написать следующее:

django_example/urls.py

from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    url(r'^riddles/', include('riddles.urls')),
    url(r'^admin/', admin.site.urls),
]

Теперь, если мы запустим наше приложение http://127.0.0.1:8000/riddles/, мы увидим «Hello, World!».

Установка базы данных

По умолчанию в Django используется SQLite, если она вас не устраивает, то вы можете ознакомиться с нашей статьей, в которой мы рассказываем, как безболезненно перейти с SQLite на MySQL.

Теперь откроем django_example/settings.py и взглянем на переменную INSTALLED_APPS, она хранит все приложения, которые активны в текущем проекте. По умолчанию она содержит:

  • django.contrib.admin — админка, скоро мы ей воспользуемся.
  • django.contrib.auth — система аутентификации.
  • django.contrib.contenttypes — фреймворк для content types.
  • django.contrib.sessions — сессионный фреймворк.
  • django.contrib.messages — фреймворк для отправки сообщений.
  • django.contrib.staticfiles — фреймворк для работы со статичными файлами.

Некоторые из этих приложений используют базы данных, но они еще не установлены, поэтому мы и видели «You have unapplied migrations; your app may not work properly until they are applied.». Поправить это можно следующим образом: python manage.py migrate. Вы должны увидеть следующее:

Operations to perform:
  Apply all migrations: admin, sessions, auth, contenttypes
Running migrations:
  Rendering model states... DONE
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying sessions.0001_initial... OK

Теперь создадим нашу модель. Для начала создадим Riddle и Option. В Riddle будет содержаться загадка, в Option — один из возможных ответов на нее.

riddles/models.py

from django.db import models


class Riddle(models.Model):
    riddle_text = models.CharField(max_length=255)
    pub_date = models.DateTimeField('date published')


class Option(models.Model):
    riddle = models.ForeignKey(Riddle, on_delete=models.CASCADE)
    text = models.CharField(max_length=255)
    correct = models.BooleanField(default=False)

Данная модель обеспечивает Django информацией, необходимой для создания схемы базы данных и database-access API для доступа к объектам. Теперь нам нужно привязать наше приложение к нашему проекту, делается это следующим образом:

django_example/settings.py

INSTALLED_APPS = [
    'riddles.apps.RiddlesConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

После этого нужно сделать миграцию: python manage.py makemigrations riddles. Вы должны увидеть следующее:

Migrations for 'riddles':
  0001_initial.py:
    - Create model Option
    - Create model Riddle
    - Add field riddle to option

Так мы говорим Django, что в моделях были сделаны некоторые изменения, и их нужно сохранить в качестве миграции.

Проверить, что сделает миграция, можно так: python manage.py sqlmigrate riddles 0001 (0001 — версия миграции, которую мы хотим проверить). На выходе мы получим:

BEGIN;
--
-- Create model Option
--
CREATE TABLE "riddles_option" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "text" varchar(255) NOT NULL, "correct" bool NOT NULL);
--
-- Create model Riddle
--
CREATE TABLE "riddles_riddle" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "riddle_text" varchar(255) NOT NULL, "pub_date" datetime NOT NULL);
--
-- Add field riddle to option
--
ALTER TABLE "riddles_option" RENAME TO "riddles_option__old";
CREATE TABLE "riddles_option" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "text" varchar(255) NOT NULL, "correct" bool NOT NULL, "riddle_id" integer NOT NULL REFERENCES "riddles_riddle" ("id"));
INSERT INTO "riddles_option" ("riddle_id", "id", "text", "correct") SELECT NULL, "id", "text", "correct" FROM "riddles_option__old";
DROP TABLE "riddles_option__old";
CREATE INDEX "riddles_option_a7c97949" ON "riddles_option" ("riddle_id");

COMMIT;

Заметьте, что команда sqlmigrate нужна только для проверки, каждый раз ее запускать необязательно.

Теперь мы можем начать пользоваться панелью администратора. Но для этого нам нужен пользователь. Создать его можно следующим образом: python manage.py createsuperuser. После этого запускаем сервер, если он не запущен, и переходим на http://127.0.0.1:8000/admin/. Вы увидите следующее:
Форма логина

Теперь дадим админу возможность изменять наши модели. Делается это так:

riddles/admin.py

from django.contrib import admin

from .models import Option, Riddle

admin.site.register(Riddle)
admin.site.register(Option)

Вот что получится в итоге:

Панель администратора

Возьмите небольшую паузу и поиграйтесь с панелью администратора. Вы будете приятно удивлены тем, что умеет Джанго.

Главная страница

Что нам нужно для создания главной страницы?

  • Templates: скелет нашей страницы.
  • Views: функция на Python для отображения контента.

Начнем с шаблонов. Создадим папку templates внутри папки riddle, а в ней создадим index.html.

riddles/templates/index.html

<h1>Available Riddles</h1>

{% if message %}
    <p><strong>{{ message }}</strong></p>
{% endif %}

{% if latest_riddles %}

    <ul>
        {% for riddle in latest_riddles %}
            <li>
                <a href="/riddles/{{ riddle.id }}/">
                    {{ riddle.riddle_text }}
                </a>
            </li>
        {% endfor %}
    </ul>

{% else %}
    <p>No riddles are available right now.</p>
{% endif %}

Теперь создадим макет для ответов:

riddles/templates/answer.html

<h1>{{ riddle.riddle_text }}</h1>

{% if error_message %}
    <p>
        <strong>{{ error_message }}</strong>
    </p>
{% endif %}

<form action="answer' riddle.id %}" method="post">
    {% csrf_token %}
    {% for option in riddle.option_set.all %}
        <input type="radio" name="option" id="option{{ forloop.counter }}" value="{{ option.id }}" />
        <label for="option{{ forloop.counter }}">{{ option.text }}</label><br>
    {% endfor %}
    <input type="submit" value="Answer" />
</form>

Здесь мы используем csrf_token, он нужен для защиты от межсайтовой подделки запроса, каждая внутренняя форма должна его использовать. Теперь напишем виды для рендеринга наших шаблонов:

riddles/views.py

from django.http.response import HttpResponse
from django.shortcuts import get_object_or_404, render

from .models import Riddle, Option


def index(request):
    return render(request, "index.html", {"latest_riddles": Riddle.objects.order_by('-pub_date')[:5]})


def detail(request, riddle_id):
    return render(request, "answer.html", {"riddle": get_object_or_404(Riddle, pk=riddle_id)})


def answer(request, riddle_id):
    riddle = get_object_or_404(Riddle, pk=riddle_id)
    try:
        option = riddle.option_set.get(pk=request.POST['option'])
    except (KeyError, Option.DoesNotExist):
        return render(request, 'answer.html', {'riddle': riddle, 'error_message': 'Option does not exist'})
    else:
        if option.correct:
            return render(request, "index.html", {"latest_riddles": Riddle.objects.order_by('-pub_date')[:5], "message": "Nice! Choose another one!"})
        else:
            return render(request, 'answer.html', {'riddle': riddle, 'error_message': 'Wrong Answer!'})

  Давайте пройдемся по каждой функции веб-приложения на Django отдельно:

  • index: Index использует функцию render. На вход она получает HttpRequest, местонахождение шаблона и его содержимое, а возвращает HttpResponse с окончательным html.
  • detail: Detail делает практически то же самое, но только функция get_object_or_404 возвращает HttpResponse404, если нужный объект не был найден.
  • answer: Answer ищет предоставленную загадку (и возвращает 404, если она не найдена) и проверяет правильность ответа.

Теперь добавим наши функции в urls.py:

riddles/urls.py

from django.conf.urls import url

from . import views

app_name = 'riddles'

urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^(?P[0-9]+)/$', views.detail, name='detail'),
    url(r'^(?P[0-9]+)/answer/$', views.answer, name='answer')
]

Стили

Для начала создадим директорию static, а в ней создадим файл main.css.

riddles/static/main.css

body{
    margin:40px auto;
    max-width:650px;
    line-height:1.6;
    font-size:18px;
    color:#444;
    padding:0 10px;
}
h1,h2,h3{
    line-height:1.2;
    text-align: center;
}
a {
    color: blue;
}

form {
    margin: 0 auto;
    padding: 1em;
    border: 1px solid #CCC;
    border-radius: 1em;
}
form div + div {
    margin-top: 1em;
}
label {
    display: inline-block;
    text-align: center;
    width: 40%;
}
input {
    font: 1em sans-serif;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
    border: 1px solid #999;
    width: 50%;
}
input:focus {
    border-color: #000;
}
p, div.button {
    text-align: center;
}

p.error-message {
    color: lightcoral;
}

Немного изменим наши шаблоны:

riddles/templates/index.html

{% load staticfiles %}

<link rel="stylesheet" type="text/css" href="{% static 'main.css' %}" />

<h1>Available Riddles</h1>

{% if message %}
    <p><strong>{{ message }}</strong></p>
{% endif %}

{% if latest_riddles %}

    <ul>
        {% for riddle in latest_riddles %}
            <li>
                <a href="/riddles/{{ riddle.id }}/">
                    {{ riddle.riddle_text }}
                </a>
            </li>
        {% endfor %}
    </ul>

{% else %}
    <p>No riddles are available right now.</p>
{% endif %}

riddles/templates/answer.html

{% load staticfiles %}

<link rel="stylesheet" type="text/css" href="{% static 'main.css' %}" />

<h1>{{ riddle.riddle_text }}</h1>

{% if error_message %}
    <p>
        <strong>{{ error_message }}</strong>
    </p>
{% endif %}

<form action="answer' riddle.id %}" method="post">
    {% csrf_token %}
    {% for option in riddle.option_set.all %}
        <input type="radio" name="option" id="option{{ forloop.counter }}" value="{{ option.id }}" />
        <label for="option{{ forloop.counter }}">{{ option.text }}</label><br>
    {% endfor %}
    <input type="submit" value="Answer" />
</form>

Первая строка загружает статические файлы, потом мы используем {% static '#' %}, где # — путь к вашему файлу. Аналогичная процедура проводится и для JavaScript.

Теперь вы можете создавать свои веб-приложения на Django. В качестве подсказки на старте работы с фреймворком воспользуйтесь одной из наших шпаргалок по Python.

Исходный код нашего приложения можно скачать по этой ссылке.

Если этот веб-проект на Django показался сложным, попробуйте пройти двухчасовой видеокурс. На нём вы пошагово создадите 3 веб-приложения: сокращатель ссылок, ToDo List и словарь английских слов.

Перевод статьи «Python Django Tutorial»

Публикация представляет собой незначительно сокращенное пособие Дэйна Хилларда Build a Blog Using Django, Vue, and GraphQL.

***

Это руководство проведет вас через процесс создания серверной части на Django и клиентской части на Vue со связкой между ними в виде GraphQL. Это большой пошаговый проект, делайте перерывы по мере необходимости.

Из руководства вы узнаете:

  • Как транслировать модели Django в GraphQL API.
  • Как одновременно запустить сервер Django и приложение на Vue.
  • Как администрировать Django-проект.
  • Как использовать GraphQL API для отображения данных в браузере с помощью Vue.

Традиционно стартовым проектом для веба явлется блог — такие проекты включают все стандартные CRUD-операции: создание, чтение, обновление и удаление.

Мы разработаем небольшое приложение для ведения блога с некоторыми элементарными функциями. Авторы могут писать множество сообщений, сообщения могут иметь множество тегов, могут публиковаться или сохраняться в черновиках.

Бэкенд блога мы сделаем на Django, затем реализуем передачу контента GraphQL API и, наконец, воспользуемся Vue для отображения данных в браузере. Вот наши шаги:

  1. Настроить блог Django.
  2. Создать администратора блога Django.
  3. Настроить Graphene-Django.
  4. Настроить django-cors-headers.
  5. Настроить Vue.js.
  6. Настроить Vue Router.
  7. Создать компоненты Vue.
  8. Получить и отобразить даные.

Предварительные знания

Руководство будет легче воспринять, если вы уже знакомы с концепцией веб-приложений: как работают HTTP-запросы и API. В этом плане будет полезно прочитать нашу публикацию про Python и API.

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

Поскольку мы будем использовать для интерфейса пользователя Vue, будет полезен опыт работы с реактивным JavaScript. Если в прошлом вы манипулировали DOM-элементами только с помощью jQuery, знакомство с Vue станет хорошим продолжением.

Запросы GraphQL похожи на JSON-объекты и возвращают данные в формате JSON. Поэтому полезно разобраться, что это такое. Позже в этом руководстве вам потребуется установить Node.js, прочитайте наше руководство для новичков.

Шаг 1. Настраиваем Django

Создадим каталог, в котором будем хранить код проекта. Назовем его dvg, сокращенно от Django-Vue-GraphQL:

        mkdir dvg/
cd dvg/
    

Мы будем разделять фронтенд и бэкенд-код, так что неплохо сразу создать подкаталоги для бэкенда:

        mkdir backend/
cd backend/
    

Весь код Django мы поместим в каталог backend, полностью изолировав его от кода Vue.

Устанавливаем Django

Чтобы отделить зависимости проекта от других ваших проектов, создадим виртуальное окружение. Далее в руководстве предполагается, что вы запускате команды, связанные с Python и Django, в активированном виртуальном окружении. Для установки зависимостей создадим в директории backend файл requirements.txt:

        Django==3.1.7
    

Устанавливаем Django в виртуальном окружении:

        (venv) $ python -m pip install -r requirements.txt
    

Django установлен, инициализируем проект:

        (venv) $ django-admin startproject backend .
    

Команда создаст в каталоге backend модуль manage.py и внутренний пакет backend. Структура каталогов теперь выглядит так:

        dvg
└── backend
    ├── manage.py
    ├── requirements.txt
    └── backend
        ├── __init__.py
        ├── asgi.py
        ├── settings.py
        ├── urls.py
        └── wsgi.py
    

Мы создали новый проект, а значит, нужно создать базу данных и осуществить миграцию:

        (venv) $ python manage.py migrate
    

Вы увидите список миграций:

        Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK
    

Это создаст файл базы данных SQLite с именем db.sqlite3, в котором будут храниться данные нашего проекта.

Есть база данных — можем создать суперпользователя:

        (venv) $ python manage.py createsuperuser
    

Итак, мы установили Django, создали проект, провели миграции и добавили суперпользователя. В итоге у нас есть полностью функционирующее (пусть пока и пустое) приложение. Запускается оно так:

        (venv) $ python manage.py runserver
    

Результат можно посмотреть по адресу http://localhost:8000/. Вы увидите стартовую страницу пустого Django-приложения. Кроме того, станет доступна страница http://localhost:8000/admin, с помощью которой можно администрировать проект. Чтобы попасть внутрь, используйте логин и пароль суперпользователя.

Шаг 2. Создаем приложение

Проект на Django может содержать множество различных приложений. Обычно одно приложение соответствует одному смысловому блоку сайта, например, ленте новостей, магазину товаров или корзине. Создадим приложение блога:

        (venv) $ python manage.py startapp blog
    

Будет создана директория blog с несколькими шаблонными файлами:

        blog
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│   └── __init__.py
├── models.py
├── tests.py
└── views.py
    

Позже в руководстве мы их дополним.

Вновь созданное приложение не добавляется по умолчанию в проект. Чтобы фреймворк знал, что приложение является частью проекта, дополняем список приложений в файле настроек проекта backend/settings.py:

        INSTALLED_APPS = [
  ...
  "blog",
]
    

Это поможет Django найти информацию о приложении: модели данных и шаблоны URL-адресов.

Создаем модели данных для блога

Создадим три следующие модели данных:

  • Profile хранит информацию о пользователях блога.
  • Tag содержит данные о категориях, по которым группируются записи блога.
  • Post используется для хранения контента и метаданных о каждом посте блога.

Все модели добавляются в соответствующий файл приложения blog/models.py. Каждая модель наследуется от стандартных моделей Django:

        from django.db import models
    

Модель Profile

Модель профиля содержит несколько полей:

  • user — связь с пользователем Django (связь один-к-одному).
  • website — опциональный URL, по которому можно узнать больше о пользователе.
  • bio — опциональное небольшое био («о себе»).

Импортируем из Django модуль настроек settings и опишем класс для нашей новой модели:

        from django.conf import settings

class Profile(models.Model):
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.PROTECT,
    )
    website = models.URLField(blank=True)
    bio = models.CharField(max_length=240, blank=True)

    def __str__(self):
        return self.user.get_username()
    

Метод __str__ сделает удобнее отображение профилей в панели администратора .

Модель Tag

В модели Tag будет единственное поле, короткое имя тега:

        class Tag(models.Model):
    name = models.CharField(max_length=50, unique=True)

    def __str__(self):
        return self.name
    

Модель Post

Модель Post — самая сложная, содержит множество полей: заголовок (title), подзаголовок (subtitle), слаг (slug, уникальная часть URL для нашего поста), контент поста (body) и т.д.

        class Post(models.Model):
    class Meta:
        ordering = ["-publish_date"]

    title = models.CharField(max_length=255, unique=True)
    subtitle = models.CharField(max_length=255, blank=True)
    slug = models.SlugField(max_length=255, unique=True)
    body = models.TextField()
    meta_description = models.CharField(max_length=150, blank=True)
    date_created = models.DateTimeField(auto_now_add=True)
    date_modified = models.DateTimeField(auto_now=True)
    publish_date = models.DateTimeField(blank=True, null=True)
    published = models.BooleanField(default=False)

    author = models.ForeignKey(Profile, on_delete=models.PROTECT)
    tags = models.ManyToManyField(Tag, blank=True)
    

Некоторые пояснения:

  1. В подклассе Meta мы указываем порядок сортировки постов (ordering) по дате публикации.
  2. Аргумент on_delete = models.PROTECT для поля author гарантирует, что при удалении постов мы случайно не удалим автора.
  3. Каждый тег может быть связан со многими сообщениями, поэтому для поля tags используется отношение ManyToManyField.

Конфигурируем панель администратора

Для того, чтобы определить, как будут отображаться записи в панели администрирования блога, переходим в blog/admin.py и импортируем созданные модели:

        from django.contrib import admin

from blog.models import Profile, Post, Tag
    

Создаем и регистрируем классы моделей:

        @admin.register(Profile)
class ProfileAdmin(admin.ModelAdmin):
    model = Profile

@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
    model = Tag

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    model = Post

    list_display = (
        "id",
        "title",
        "subtitle",
        "slug",
        "publish_date",
        "published",
    )
    list_filter = (
        "published",
        "publish_date",
    )
    list_editable = (
        "title",
        "subtitle",
        "slug",
        "publish_date",
        "published",
    )
    search_fields = (
        "title",
        "subtitle",
        "slug",
        "body",
    )
    prepopulated_fields = {
        "slug": (
            "title",
            "subtitle",
        )
    }
    date_hierarchy = "publish_date"
    save_on_top = True
    

У постов мы не показываем все поля подряд, а только необходимые для администрирования. К ним мы добавили возможности фильтрации, редактирования и поиска. Подробно эти настройки рассмотрены в статье Customize the Django Admin With Python.

Создаем миграции модели

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

        (venv) $ python manage.py makemigrations
Migrations for 'blog':
  blog/migrations/0001_initial.py
    - Create model Tag
    - Create model Profile
    - Create model Post
    

Это создаст миграцию с именем по умолчанию 0001_initial.py. Запустим миграцию с помощью команды управления migrate:

        (venv) $ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
  Applying blog.0001_initial... OK
    

Теперь у нас есть модели данных и мы настроили админпанель Django, чтобы добавлять и редактировать эти модели.

Запустите или перезапустите сервер разработки Django и зайдите в панель по адресу http://localhost:8000/admin посмотреть, что изменилось. Вы увидите ссылки на списки тегов, профилей и сообщений, а также ссылки для добавления и редактирования каждого из них. Добавьте и отредактируйте несколько из них, чтобы увидеть, как отреагирует интерфейс администратора.

Шаг 3. Настройка Graphene-Django

В результате предыдущего этапа мы завершили основную работу над бэкендом. Далее можно было бы использовать механизмы маршрутизации URL и шаблонов Django для создания страниц, которые будут показывать читателям контент. Но вместо этого мы обернем созданную нами серверную часть в GraphQL API. За счет этого мы обеспечим более удобную работу на стороне клиента.

GraphQL позволяет получать только те данные, которые нам действительно нужны, что выгодно отличает эту технологию от RESTful API. GraphQL обеспечивает гибкость при проектировании данных, за счет чего мы можем получать новые структуры данных, не изменяя логику службы, предоставляющей GraphQL API.

Устанавливаем Graphene-Django

Для интеграции Django и GraphQL мы используем библиотеку Graphene-Django. Для установки библиотеки дополняем файл requirements.txt:

        graphene-django==2.14.0
    

Запускаем установку через менеджер пакетов pip:

        (venv) $ python -m pip install -r requirements.txt
    

Теперь нужно добавить приложение "graphene_django" в список INSTALLED_APPS в модуле settings.py:

        INSTALLED_APPS = [
  ...
  "blog",
  "graphene_django",
]
    

Настраиваем Graphene-Django

Параметр GRAPHENE в файле settings.py указывает Graphene-Django расположение схемы GraphQL. Для нашего примера этот путь соответствует blog.schema.schema (саму схему мы вскоре создадим):

        GRAPHENE = {
  "SCHEMA": "blog.schema.schema",
}
    

Добавляем шаблон URL для GraphQL и GraphiQL. Чтобы позволить Django обслуживать конечную точку GraphQL и интерфейс GraphiQL, добавим новый шаблон URL в backend/urls.py. Поскольку мы не используем функции защиты от подделки межсайтовых запросов (CSRF) шаблонизатора Django, нам необходимо импортировать декоратор Django csrf_exempt, чтобы пометить представление, как свободное от CSRF-защиты:

backend/urls.py
        from django.views.decorators.csrf import csrf_exempt
from graphene_django.views import GraphQLView
    

Добавляем паттерн в список переменной urlpatterns:

        urlpatterns = [
    ...
    path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True))),
]
    

Аргумент graphiql = True указывает Graphene-Django сделать доступным GraphiQL-интерфейс .

Создаем GraphQL-схему. Теперь мы создадим схему GraphQL, похожую по своей логике на конфигурацию панели администратора. Схема состоит из нескольких классов, каждый из которых связан с определенной моделью Django, а также ещё одного класса, который показывает, как разрешать несколько важных типов запросов, которые понадобятся нам во внешнем интерфейсе.

В каталоге blog/ создадим новый модуль schema.py . Импортируем из Graphene-Django DjangoObjectType, модели блога и модель пользователя Django:

blog/schema.py
        from django.conf import settings
from graphene_django import DjangoObjectType

from blog import models
    

Создадим класс для каждой из наших моделей и модели User. Имя каждого класса должно заканчиваться на Type, потому что каждое из них соответствует типу GraphQL. Классы должны выглядеть следующим образом:

blog/schema.py
        class UserType(DjangoObjectType):
    class Meta:
        model = settings.AUTH_USER_MODEL

class AuthorType(DjangoObjectType):
    class Meta:
        model = models.Profile

class PostType(DjangoObjectType):
    class Meta:
        model = models.Post

class TagType(DjangoObjectType):
    class Meta:
        model = models.Tag
    

Ещё нам нужно создать класс Query, наследуемый от graphene.ObjectType. Этот класс объединит все созданные нами классы типов, и мы добавим к нему методы, указывающие способы запроса моделей. Сначала импортируем модуль graphene:

        import graphene
    

Класс Query требует ряда атрибутов, которые являются либо graphene.List, (если запрос возращает несколько элементов), либо graphene.Field (если запрос возвращает один элемент).

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

В итоге получается следующий сниппет:

        class Query(graphene.ObjectType):
    all_posts = graphene.List(PostType)
    author_by_username = graphene.Field(AuthorType, username=graphene.String())
    post_by_slug = graphene.Field(PostType, slug=graphene.String())
    posts_by_author = graphene.List(PostType, username=graphene.String())
    posts_by_tag = graphene.List(PostType, tag=graphene.String())

    def resolve_all_posts(root, info):
        return (
            models.Post.objects.prefetch_related("tags")
            .select_related("author")
            .all()
        )

    def resolve_author_by_username(root, info, username):
        return models.Profile.objects.select_related("user").get(
            user__username=username
        )

    def resolve_post_by_slug(root, info, slug):
        return (
            models.Post.objects.prefetch_related("tags")
            .select_related("author")
            .get(slug=slug)
        )

    def resolve_posts_by_author(root, info, username):
        return (
            models.Post.objects.prefetch_related("tags")
            .select_related("author")
            .filter(author__user__username=username)
        )

    def resolve_posts_by_tag(root, info, tag):
        return (
            models.Post.objects.prefetch_related("tags")
            .select_related("author")
            .filter(tags__name__iexact=tag)
        )
    

Теперь у нас есть все типы и преобразователи для нашей схемы. Но помним, что переменная GRAPHENE указывает на blog.schema.schema. Создаем переменную схемы, которая обертывает класс Query в graphene.Schema, чтобы связать все это вместе:

        schema = graphene.Schema(query=Query)
    

В результатае переменная соответствует значению blog.schema.schema, которое мы настроили для Graphene-Django ранее в этом руководстве.

Итак, мы обернули модель данных с помощью Graphene-Django, чтобы использовать эти данные в GraphQL API. Запустите сервер разработки Django и посетите страницу http://localhost:8000/graphql. Вы должны увидеть интерфейс GraphiQL с некоторыми комментариями, объясняющими, как использовать инструмент.

Разверните раздел Docs в правом верхнем углу экрана и щелкните по query:Query. Вы должны увидеть каждый из запросов и типов, которые мы настроили в схеме.

Если вы еще не наполняли блог тестовыми данными, сделайте это сейчас. Попробуйте выполнить следующий запрос. Он должен вернуть список всех созданных сообщений:

        {
  allPosts {
    title
    subtitle
    author {
      user {
        username
      }
    }
    tags {
      name
    }
  }
}
    

Ответ должен вернуть список постов. Структура каждого поста должна соответствовать форме запроса, как в следующем примере:

        {
  "data": {
    "allPosts": [
      {
        "title": "The Great Coney Island Debate",
        "subtitle": "American or Lafayette?",
        "author": {
          "user": {
            "username": "coney15land"
          }
        },
        "tags": [
          {
            "name": "food"
          },
          {
            "name": "coney island"
          }
        ]
      }
    ]
  }
}
    

Если вы сохранили несколько постов и видите их в ответе, значит, можно продолжать.

Шаг 4. Настраиваем django-cors-headers

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

Библиотека django-cors-headers делает работу с CORS довольно безболезненной. Мы будем использовать эту библиотеку, чтобы указать Django отвечать на запросы, даже если они исходят из другого источника. Это позволит фронтенду правильно взаимодействовать с GraphQL API.

Установка. Добавляем название модуля в зависимости (requirements.txt):

        django-cors-headers==3.6.0
    

Устанавливаем:

        (venv) $ python -m pip install -r requirements.txt
    

Добавляем в список приложений INSTALLED_APPS в файле settings.py:

settings.py
        INSTALLED_APPS = [
  ...
  "corsheaders",
]
    

Теперь нужно подключить библиотеку в качестве промежуточного обработчика в переменной MIDDLEWARE:

settings.py
        MIDDLEWARE = [
  "corsheaders.middleware.CorsMiddleware",
  ...
]
    

Документация django-cors-headers советует ставить эту строку как можно выше в списке обработчиков.

CORS существует не просто так. Мы не хотим, чтобы наше приложение было доступно для использования из любого места в Интернете. Чтобы этого избежать, мы используем две настройки, чтобы определить, насколько мы хотим открыть GraphQL API:

  1. CORS_ORIGIN_ALLOW_ALL определяет, должен ли Django быть полностью открыт или полностью закрыт по умолчанию.
  2. CORS_ORIGIN_WHITELIST определяет, для каких доменов приложение Django будет разрешать запросы.

Соответственно добавляем в settings.py две следующие строки:

        CORS_ORIGIN_ALLOW_ALL = False
CORS_ORIGIN_WHITELIST = ["http://localhost:8080"]
    

Такие настройки разрешат запросы только от нашего фронтенда, которые в конечном итоге мы будем запускать локально на порту 8080.

Бэкенд готов! У нас есть модель данных, интерфейс администратора, GraphQL API на базе GraphiQL и возможность запрашивать API из внешнего интерфейса, который мы создадим дальше. Отличный момент, чтобы передохнуть, если вы ещё этого не делали ⛱️.

Шаг 5. Настраиваем Vue.js

В качестве фронтенд-фреймворка мы будем использовать Vue. Как и Django, Vue предоставляет интерфейс для создания проекта. Используя этот подход, нам не придется устанавливать вручную множество отдельных зависимостей, необходимых для запуска проекта на Vue. Достаточно использовать npx:

        $ cd путь_к_директории_с_проектом
$ npx @vue/cli create frontend --default
...
🎉  Successfully created project frontend.
...
$ cd frontend/
    

Указанная комана создаст подкаталог frontend/ рядом с уже существующим каталогом backend/, установит ряд зависимостей JavaScript и создаст некоторые каркасные файлы для нашего будущего фронтенд-приложения.

Установим плагины Vue

Чтобы правильно выполнять маршрутизацию и взаимодействовать с GraphQL API, нам понадобятся плагины Vue Router и Vue Apollo. При появлении запросов выбирайте параметры по умолчанию:

        $ npx @vue/cli add router
$ npx @vue/cli add apollo
    

Этим командам потребуется время для установки зависимостей, они добавят или изменят некоторые файлы в проекте. Теперь мы можем запустить сервер разработки:

        $ npm run serve
    

Итак, у нас есть приложение Django, работающее по адресу http://localhost:8000, и приложение Vue, которое запускается по адресу http://localhost:8080.

Шаг 6. Настраиваем Vue Router

Важной частью клиентских приложений является обработка маршрутизации без необходимости делать новые запросы к серверу. Распространенным решением в Vue является плагин Vue Router, который мы установили ранее.

В каталоге src/ создадим модуль router.js . Этот файл будет содержать настройки сопоставления URL-адресов и компонентов Vue. Начнем с импорта Vue и Vue Router:

        import Vue from 'vue'
import VueRouter from 'vue-router'
    

Каждый из следующих элементов импорта соответствует нашим будущим компонентам:

        import Post from '@/components/Post'
import Author from '@/components/Author'
import PostsByTag from '@/components/PostsByTag'
import AllPosts from '@/components/AllPosts'
    

Регистрируем плагин:

        Vue.use(VueRouter)
    

Теперь создадим список маршрутов. Каждый маршрут имеет два параметра:

  1. path — URL-шаблон, похожий по смыслу на URL-шаблоны Django.
  2. component — компонент Vue, соответствующий пути.

Создадим константу routes:

        const routes = [
  { path: '/author/:username', component: Author },
  { path: '/post/:slug', component: Post },
  { path: '/tag/:tag', component: PostsByTag },
  { path: '/', component: AllPosts },
]
    

Создадим новый экземпляр VueRouter и экспортируем его из модуля router.js, чтобы другие модули могли его использовать:

        const router = new VueRouter({
  routes: routes,
  mode: 'history',
})
export default router

    

Далее в начале файла src/main.js импортируем router :

        import router from '@/router'
    

Передаем маршрут в экземпляр Vue:

        new Vue({
  router,
  ...
})
    

На этом настройка Vue Router завершена. Мы создали маршруты для внешнего интерфейса, которые сопоставляют шаблон URL-адреса с отображаемым компонентом. Сами маршруты пока не работают, потому как указывают на компоненты, которые еще не созданы.

Шаг 7. Создаем компоненты Vue

Теперь Vue умеет работать с маршрутами, пора создать компоненты, которые будут отображать данные из конечной точки GraphQL:

  • AuthorLink – ссылка на страницу автора (используется в Post и PostList).
  • PostList – список постов в блоге (используется в AllPosts, Author и PostsByTag).
  • AllPosts – список постов, начиная с самых последних.
  • PostsByTag – список постов, связанных с заданным тегом, начиная с самых недавних.
  • Post – метаданные и контент публикации.
  • Author – информация об авторе и список написанных им постов.

Компонент AuthorLink

Первый компонент, который мы создадим, отображает ссылку на автора. В каталоге src/components/ создадим файл AuthorLink.vue. Этот файл представляет собой однофайловый компонент (single file component, SFC) Vue. SFC в одном файле хранит HTML, JavaScript и CSS, необходимые для визуализации компонента.

AuthorLink принимает prop-объект author, структура которого соответствует данным об авторах в GraphQL API. Компонент должен отображать имя и фамилию пользователя, если они указаны, в противном случае — имя пользователя.

Файл AuthorLink.vue должен выглядеть следующим образом:

AuthorLink.vue
        <template>
  <router-link
      :to="`/author/${author.user.username}`"
  >{{ displayName }}</router-link>
</template>

<script>
export default {
  name: 'AuthorLink',
  props: {
    author: {
      type: Object,
      required: true,
    },
  },
  computed: {
    displayName () {
      return (
        this.author.user.firstName &&
        this.author.user.lastName &&
        `${this.author.user.firstName} ${this.author.user.lastName}`
      ) || `${this.author.user.username}`
    },
  },
}
</script>
    

Этот компонент не будет использовать GraphQL напрямую. Вместо этого другие компоненты передают информацию об авторе, используя свойство author.

Компонент PostList

Компонент PostList принимает prop-объект posts, структура которого соответствует данным о сообщениях в нашем GraphQL API. Компонент отображает следующие вещи:

  • Заголовок и подзаголовок поста, слинкованный с самой страницей поста.
  • Ссылка на автора поста через AuthorLink (если логическая переменная showAuthor равна true).
  • Дата публикации поста.
  • Мета-описание поста.
  • Список тегов.

Создайте PostList.vue в каталоге src/components/. Шаблон компонента должен выглядеть следующим образом:

PostList.vue
        <template>
  <div>
    <ol class="post-list">
      <li class="post" v-for="post in publishedPosts" :key="post.title">
          <span class="post__title">
            <router-link
              :to="`/post/${post.slug}`"
            >{{ post.title }}: {{ post.subtitle }}</router-link>
          </span>
          <span v-if="showAuthor">
            by <AuthorLink :author="post.author" />
          </span>
          <div class="post__date">{{ displayableDate(post.publishDate) }}</div>
        <p class="post__description">{{ post.metaDescription }}</p>
        <ul>
          <li class="post__tags" v-for="tag in post.tags" :key="tag.name">
            <router-link :to="`/tag/${tag.name}`">#{{ tag.name }}</router-link>
          </li>
        </ul>
      </li>
    </ol>
  </div>
</template>
    

Код JavaScript должен выглядеть так:

PostList.vue
        <script>
import AuthorLink from '@/components/AuthorLink'

export default {
  name: 'PostList',
  components: {
    AuthorLink,
  },
  props: {
    posts: {
      type: Array,
      required: true,
    },
    showAuthor: {
      type: Boolean,
      required: false,
      default: true,
    },
  },
  computed: {
    publishedPosts () {
      return this.posts.filter(post => post.published)
    }
  },
  methods: {
    displayableDate (date) {
      return new Intl.DateTimeFormat(
        'en-US',
        { dateStyle: 'full' },
      ).format(new Date(date))
    }
  },
}
</script>
    

Компонент PostList получает данные через prop вместо прямого использования GraphQL.

В том же файле можно добавить несколько дополнительных стилей CSS, чтобы сделать список постов удобнее для чтения:

PostList.vue
        <style>
.post-list {
  list-style: none;
}

.post {
  border-bottom: 1px solid #ccc;
  padding-bottom: 1rem;
}

.post__title {
  font-size: 1.25rem;
}

.post__description {
  color: #777;
  font-style: italic;
}

.post__tags {
  list-style: none;
  font-weight: bold;
  font-size: 0.8125rem;
}
</style>
    

Компонент AllPosts

Следующий компонент, который мы создадим, — список постов в блоге. Он должен отображать две вещи:

  • Заголовок, например «Недавние сообщения» или «Recent Posts».
  • Список постов с помощью PostList.

Создаём AllPosts.vue в каталоге src/components/. Должно получиться так:

AllPosts.vue
        <template>
  <div>
    <h2>Recent posts</h2>
    <PostList v-if="allPosts" :posts="allPosts" />
  </div>
</template>

<script>
import PostList from '@/components/PostList'

export default {
  name: 'AllPosts',
  components: {
    PostList,
  },
  data () {
    return {
        allPosts: null,
    }
  },
}
</script>
    

Позже мы заполним переменную allPosts динамически с помощью GraphQL-запроса.

Компонент PostsByTag

Компонент PostsByTag очень похож на компонент AllPosts:

PostsByTag.vue
        <template>
  <div>
    <h2>Posts in #{{ $route.params.tag }}</h2>
    <PostList :posts="posts" v-if="posts" />
  </div>
</template>

<script>
import PostList from '@/components/PostList'

export default {
  name: 'PostsByTag',
  components: {
    PostList,
  },
  data () {
    return {
      posts: null,
    }
  },
}
</script>
    

Компонент Author

Компонент Author действует, как страница профиля автора. То есть компонент должен отображать следующее:

  • Заголовок с именем автора.
  • Ссылка на сайт автора (если указана).
  • «О себе» автора (если предоставлено).
  • Список постов автора.
Author.vue
        <template>
  <div v-if="author">
    <h2>{{ displayName }}</h2>
    <a
      :href="author.website"
      target="_blank"
      rel="noopener noreferrer"
    >Website</a>
    <p>{{ author.bio }}</p>

    <h3>Posts by {{ displayName }}</h3>
    <PostList :posts="author.postSet" :showAuthor="false" />
  </div>
</template>

<script>
import PostList from '@/components/PostList'

export default {
  name: 'Author',
  components: {
    PostList,
  },
  data () {
    return {
      author: null,
    }
  },
  computed: {
    displayName () {
      return (
        this.author.user.firstName &&
        this.author.user.lastName &&
        `${this.author.user.firstName} ${this.author.user.lastName}`
      ) || `${this.author.user.username}`
    },
  },
}
</script>
    

Компонент Post

Компонент Post является наиболее интересным, поскольку отвечает за отображение всей информации о публикации:

  1. Заголовок и подзаголовок.
  2. Автор (с помощью ссылки AuthorLink).
  3. Дата публикации.
  4. Мета-описание.
  5. Содержание («тело» поста).
  6. Список связанных тегов в виде ссылок.

За счет используемой модели данных и компонентной архитектуры нам потребуется совсем немного кода:

        <template>
  <div class="post" v-if="post">
      <h2>{{ post.title }}: {{ post.subtitle }}</h2>
      By <AuthorLink :author="post.author" />
      <div>{{ displayableDate(post.publishDate) }}</div>
    <p class="post__description">{{ post.metaDescription }}</p>
    <article>
      {{ post.body }}
    </article>
    <ul>
      <li class="post__tags" v-for="tag in post.tags" :key="tag.name">
        <router-link :to="`/tag/${tag.name}`">#{{ tag.name }}</router-link>
      </li>
    </ul>
  </div>
</template>

<script>
import AuthorLink from '@/components/AuthorLink'

export default {
  name: 'Post',
  components: {
    AuthorLink,
  },
  data () {
    return {
      post: null,
    }
  },
  methods: {
    displayableDate (date) {
      return new Intl.DateTimeFormat(
        'en-US',
        { dateStyle: 'full' },
      ).format(new Date(date))
    }
  },
}
</script>
    

Компонент App

Прежде чем увидеть результаты работы, необходимо обновить компонент App, созданный при первоначальной настройке Vue. Вместо отображения страницы-заставки Vue должен отображаться компонент AllPosts.

В каталоге src/ откроем App.vue и заменим содержимое следующим кодом:

        <template>
    <div id="app">
        <header>
          <router-link to="/">
            <h1>Awesome Blog</h1>
          </router-link>
        </header>
        <router-view />
    </div>
</template>

<script>
export default {
  name: 'App',
}
</script>

<style>
* {
  margin: 0;
  padding: 0;
}

body {
  margin: 0;
  padding: 1.5rem;
}

* + * {
  margin-top: 1.5rem;
}

#app {
  margin: 0;
  padding: 0;
}
</style>
    

Здесь описан заголовок с названием блога, который ведет на главную страницу, а также компонент Vue Router, который отображает компонент для текущего маршрута.

Итак, мы подошли к концу седьмого (предпоследнего) шага. Если вы раньше не использовали Vue, этот шаг, возможно, был трудоемким. Однако мы достигли важной вехи — работающего приложения Vue с маршрутами и представлениями, готовыми для отображения данных.

Запустите сервер разработки Vue и перейдите по адресу http://localhost:8080. Вы должны увидеть заголовок блога и заголовки недавних публикаций. На последнем шаге мы воспользуемся Apollo для обращений к GraphQL API, чтобы соединить между собой интерфейс и серверную часть.

Шаг 8. Собираем данные

Пора получить данные из GraphQL API. Плагин Vue Apollo, который мы установили ранее, интегрирует Apollo во Vue и делает удобнее процедуру выполнения запросов к GraphQL API.

Vue Apollo уже настроен «из коробки», но нужно указать правильную конечную точку запроса. Мы также можем отключить WebSocket-соединение, которое плагин пытается использовать по умолчанию, поскольку это создает лишний шум на вкладках «Сеть» и «Консоль» в средствах разработки в браузере. Отредактируем определение apolloProvider в модуле src/main.js, указав свойства httpEndpoint и wsEndpoint:

        new Vue({
  ...
  apolloProvider: createProvider({
    httpEndpoint: 'http://localhost:8000/graphql',
    wsEndpoint: null,
  }),
  ...
})
    

Теперь мы можем добавить запросы для заполнения страниц. Мы сделаем это, добавив функцию created() в несколько наших SFC. Эта функция — специальный хук жизненного цикла Vue, выполняемый в момент, когда компонент готовится к рендерингу на странице. Функцию можно использовать для запроса данных, которые мы хотим визуализировать. Мы создадим запросы для следующих компонентов:

  • Post
  • Author
  • PostByTag
  • AllPosts

Запрос для получения информации о посте (Post)

Запрос для отдельного поста принимает slug нужного сообщения и возвращает необходимую информацию для отображения публикации. Для создания запроса в функции created() мы используем функции-помощники $apollo.query и gql . Функция created() будет выглядеть следующим образом:

        <script>
  ...
  async created () {
    const post = await this.$apollo.query({
        query: gql`query ($slug: String!) {
          postBySlug(slug: $slug) {
            title
            subtitle
            publishDate
            metaDescription
            slug
            body
            author {
              user {
                username
                firstName
                lastName
              }
            }
            tags {
              name
            }
          }
        }`,
        variables: {
          slug: this.$route.params.slug,
        },
    })
    this.post = post.data.postBySlug
  },
  ...
</script>
    

Запрос извлекает большую часть данных о публикации, авторе и тегах. Обратите внимание, что в запросе используется заполнитель $slug, для заполнения которого применяется свойство variables, передаваемое в $apollo.query. Свойство slug соответствует имени заполнителя $slug. Мы встретим такой же шаблон в следующих запросах.

Запрос для Author

Запрос Author принимает username автора и возвращает информацию, необходимую для отображения автора и его списка постов. Должно получиться так:

        <script>
  ...
  async created () {
    const user = await this.$apollo.query({
      query: gql`query ($username: String!) {
        authorByUsername(username: $username) {
          website
          bio
          user {
            firstName
            lastName
            username
          }
          postSet {
            title
            subtitle
            publishDate
            published
            metaDescription
            slug
            tags {
              name
            }
          }
        }
      }`,
      variables: {
        username: this.$route.params.username,
      },
    })
    this.author = user.data.authorByUsername
  },
  ...
</script>
    

В этом запросе используется postSet, который может показаться знакомым по нашей модели данных Django. Название «post set» происходит от связи, которую Django создает для поля внешнего ключа. Graphene-Django автоматически предоставляет postSet в GraphQL API.

Запрос для PostByTag

Запрос для PostsByTag очень похож на предыдущие. Он принимает желаемый тег и возвращает список подходящих постов:

        <script>
  ...
  async created () {
    const posts = await this.$apollo.query({
      query: gql`query ($tag: String!) {
        postsByTag(tag: $tag) {
          title
          subtitle
          publishDate
          published
          metaDescription
          slug
          author {
            user {
              username
              firstName
              lastName
            }
          }
          tags {
            name
          }
        }
      }`,
      variables: {
        tag: this.$route.params.tag,
      },
    })
    this.posts = posts.data.postsByTag
  },
  ...
</script>
    

Вы уже могли заметить, что некоторые части запросов очень похожи друг на друга. Чтобы уменьшить дублирование кода, обратите внимание на концепцию GraphQL fragments.

Запрос для AllPosts

Запрос AllPosts не требует ввода информации и возвращает тот же набор данных, что и запрос PostsByTag. Должно получиться так:

        <script>
  ...
  async created () {
    const posts = await this.$apollo.query({
      query: gql`query {
        allPosts {
          title
          subtitle
          publishDate
          published
          metaDescription
          slug
          author {
            user {
              username
              firstName
              lastName
            }
          }
          tags {
            name
          }
        }
      }`,
    })
    this.allPosts = posts.data.allPosts
  },
  ...
</script>
    

Ура! Это последний запрос. Теперь каждый компонент получает данные, необходимые для отображения, а мы получили работающий блог. Запустите сервер разработки Django и сервер разработки Vue. Откройте http://localhost:8080 и посмотрите на результат.

Возможные следующие шаги

Мы начали с создания серверной части блога Django для администрирования, сохранения и обслуживания данных блога. Затем создали интерфейс Vue для использования и отображения этих данных. Наконец, научили их общаться с GraphQL, используя Graphene и Apollo.

Чтобы еще раз убедиться, что блог работает должным образом, можно попробовать следующее:

  • Добавить пользователей и посты, чтобы увидеть разделение по авторам.
  • Создать несколько сообщений без публикации, чтобы убедиться, что они не отображаются в блоге, а сохраняются в форме черновика.

Заключение

Итак, мы узнали, как использовать GraphQL для создания гибких типизированных представлений данных. Вы можете применять эти методы как в уже созданных приложениях Django, так и в тех, что вы только планируете создать. Как и другие API, этот подход применим для большинства современных фронтенд-фреймворков. Надеемся, что эта концепция пригодится вам ещё не раз.

Вот ещё несколько материалов о GraphQL:

  • Сравним GraphQL и библиотеку Graphene в Python
  • Хватит использовать REST для API!
  • Создаем мощный API на Node.js, GraphQL, MongoDB, Hapi и Swagger

#статьи

  • 3 авг 2022

  • 0

Разберёмся в том, как устроен фреймворк Django, как он работает, и напишем своё первое приложение.

Иллюстрация: rawpixel.com / Freepik / mulyadi / Sebastian Dumitru / Unsplash / Дима Руденок для Skillbox Media

Антон Яценко

Изучает Python, его библиотеки и занимается анализом данных. Любит путешествовать в горах.

Django — это фреймворк для веб-разработки, написанный на Python в 2005 году. Он позволяет создавать сайты и веб-приложения разной сложности — от личного блога до аудиоплатформы (на нём собран Spotify). От других фреймворков Django отличается модульностью и простотой освоения.

В этой статье мы увидим, как создавать веб-приложения на Django: рассмотрим его структуру и напишем первое веб-приложение — движок для блога, который умеет публиковать и редактировать записи. Это первая часть урока по Django — во второй мы будем настраивать админку и подтюним внешний вид наших постов.

Согласно исследованию Stack Overflow за 2022 год, Django занимает девятое место среди всех веб-фреймворков, обгоняя, например, популярные Spring и Ruby on Rails.

Фреймворк Django расположился в середине списка популярных веб-фреймворков
Скриншот: Stack Overflow

На Django работают многие известные сервисы — например, «Инстаграм»*, Pinterest, Spotify, Last.fm и сайт Mozilla. Однако его используют не только в энтерпрайзе: нередко на нём собирают личные сайты: блоги, фотогалереи, портфолио и так далее.

Одно из главных преимуществ Django — принцип DRY (don’t repeat yourself): единожды написанный код можно повторно использовать в различных проектах. Поэтому Django часто сравнивают с конструктором Lego.

Кроме принципа DRY Django имеет и другие преимущества:

  • понятый и простой синтаксис (он написан на Python);
  • большое количество готовых шаблонов и модулей, которые облегчают разработку и позволяют не использовать внешние библиотеки или расширения. Это помогает избегать конфликтов между ними после обновлений;
  • подробная документация и большое комьюнити. Если у вас появился какой-то вопрос о коде, то вы легко найдёте на него ответ на Stack Overflow или других сайтах.

Фреймворк основывается на четырёх главных компонентах:

  • Модели (Models) — взаимодействуют с базой данных и достают из неё ту информацию, которую необходимо отобразить в браузере.
  • Представления (Views) — обрабатывают запрос и обращаются к модели, сообщая ей, какую информацию необходимо достать из базы данных.
  • Шаблоны (Templates) — показывают, каким именно образом необходимо показать информацию, полученную из базы данных.
  • URL-маршруты (URL dispatcher) — перенаправляют HTTP-запрос от браузера в представления.

Сейчас структура может казаться запутанной, но на самом деле она простая — мы разберёмся с ней на практике, и вы быстро всё поймёте.

Прежде чем перейти к установке Django и virtualenv, необходимо убедиться в том, что у вас уже установлен Python. Проверить это и узнать версию Python можно с помощью терминала. Откройте Terminal в своей IDE или операционной системе и введите команду:

% python --version

Если Python установлен, то терминал покажет его версию:

Скриншот: Django / Skillbox Media

Если же Python не установлен, то можно воспользоваться другим нашим руководством. После этого переходим к настройке виртуального окружения.

Виртуальное окружение, создаваемое с помощью virtualenv, — это специальный инструмент, который помогает управлять зависимостями и изолировать проекты друг от друга: например, устанавливать дополнительные библиотеки и пакеты локально для каждого проекта, а не глобально для всего компьютера. Установить virtualenv тоже можно через терминал:

% sudo pip3 install virtualenv

После этого необходимо создать директорию для проекта, внутри которой мы развернём виртуальное окружение:

% mkdir blog
% cd blog

С помощью команды mkdir мы создаём папку blog, а благодаря команде cd в терминале переходим в неё. Теперь папка blog — это каталог, где мы организуем виртуальное окружение и будем хранить все наши файлы, связанные с нашим проектом Django.

Развернём виртуальное окружение внутри папки development:

% virtualenv venv -p python3

Теперь его необходимо активировать, иначе оно не будет работать:

% source venv/bin/activate

После активации виртуального окружения мы увидим результат в самом терминале. Обратите внимание, что во второй строке команда теперь начинается с (venv), а не с (base), как раньше:

Скриншот: Django / Skillbox Media

Сам фреймворк Django тоже устанавливается с помощью терминала:

% pip install django

Результат установки:

Скриншот: Django / Skillbox Media

В последней строке видно, что установка прошла успешно. При этом мы установили не только Django последней версии, но и ещё две библиотеки:

  • asgiref — интерфейс для взаимодействия с асинхронными веб-сервисами, платформами и приложениями на Python;
  • sqlparse — простой парсер для работы с базами данных SQL.

Итак, убедитесь, что вы сделали всё по чек-листу:

  • проверили, что у вас есть Python;
  • создали директорию и развернули в ней виртуальное окружение с помощью virtualenv;
  • установили в ту же папку Django.

Теперь можно переходить к созданию самого проекта.

Разобраться в основных понятиях фреймворка Django лучше всего на практике. Напишем веб-приложение для небольшого блога с текстовыми записями и поговорим про его работу. Весь процесс разобьём на семь простых шагов — да здравствует модульность :)


Готовим и настраиваем окружение

Проще всего создать базовую структуру проекта с помощью терминала:

% django-admin startproject blog . 

Очень важно не забыть точку в конце команды — она указывает на то, что проект создаётся в текущей папке без необходимости создания нового каталога. Если точку не поставить, то ничего не произойдёт и терминал выдаст ошибку.

Если всё прошло хорошо, то Django создаст в нашей директории blog файл manage.py, управляющий выполнением всех задач в терминале, и папку blog с пятью файлами:

Скриншот: Django / Skillbox Media

Мы не будем подробно обсуждать предназначение каждого из этих файлов — не все из них нам надо будет редактировать. Если вам интересны подробности, просто откройте файлы, в шапке каждого из них будет написано, для чего он нужен.

Теперь нам надо продумать, где мы станем хранить записи блога. Для этого потребуется создать базу данных. Django может работать с различными базами данных, но по умолчанию работает с SQLite. Её нам будет достаточно — у нас всего один пользователь и простая структура записей.

Самое приятное — можно создать базу данных SQLite с помощью одной команды.

% python manage.py migrate

И всё. Если посмотреть в каталог проекта, вы увидите, что там появился файл db.sqlite3. Это и есть наша база данных.

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

% python manage.py createsuperuser

После выполнения команды терминал попросит ввести имя пользователя, email и установить пароль. Запомните их — они нам ещё понадобятся.

Ещё одна команда, которой мы будем часто пользоваться — runserver. Она запускает веб-сервер для разработки:

% python manage.py runserver

По умолчанию сервер запускается на порту 8000 по адресу 127.0.0.1 и доступен только на вашем компьютере. Порт и IP-адрес можно указать самостоятельно, но сейчас это нам не нужно.

Когда сервер запущен, вы можете открыть проект Django в браузере, введя http://127.0.0.1:8000 или http://localhost:8000. Если все предыдущие этапы выполнены правильно, вы увидите приветственное окно Django:

Скриншот: Django / Skillbox Media

Завершим настройку нашего проекта и перейдём на страницу администрирования http://localhost:8000/admin. В форме введём имя и пароль суперпользователя, которые мы задали в предыдущем пункте. В результате нам откроется панель администратора:

Скриншот: Django / Skillbox Media

Панель администратора позволяет управлять контентом и пользователями сайта. Она понадобится нам на следующих этапах создания блога.


Проект Django содержит одно или несколько приложений. Вначале можно запутаться между понятиями «проект» и «приложение», но разница между ними простая:

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

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

В нашем случае нам достаточно хранить и отображать текстовые заметки, поэтому мы обойдёмся одним приложением. Назовём его entries. Прежде чем создать его, необходимо остановить работу сервера с помощью команды в терминале. Для этого нажмём комбинацию клавиш Ctrl + C в Windows или Control + C в macOS.

Теперь создадим само приложение:

% python manage.py startapp entries

Эта команда создаст папку entries в проекте blog с набором предопределённых файлов (помните, что все эти команды терминала необходимо вводить в папке проекта). С некоторыми из них мы поработаем позже.

Скриншот: Django / Skillbox Media

Пока что Django не видит созданное приложение entries. Чтобы его подключить, необходимо добавить название приложения в конец списка INSTALLED_APPS в файл blog/settings.py:

# Открываем файл blog/settings.py и добавляем в конец списка запись.
 
INSTALLED_APPS = [
   'django.contrib.admin',
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.messages',
   'django.contrib.staticfiles',
   'entries.apps.EntriesConfig',
]

Теперь приложение entries подключено к нашему проекту и Django видит его конфигурации. Одна из этих конфигураций — та самая модель, которая описывает, как записи блога должны выглядеть в базе данных.


После создания базы данных необходимо создать для неё таблицу, в которой будут храниться записи блога. В Django это делается с помощью класса Models. Как и обычные классы в Python, имена моделей принято задавать в единственном числе, и начинаться они должны с заглавной буквы. Так как наше приложение называется entries, модель будет называться Entry.

Поля Entry — это элементы, которыми будет обладать любая запись в блоге. На самом сайте они будут показаны в виде полей формы, а в базе данных — столбцами. У записи в блоге будет три элемента:

  • title — заголовок;
  • content — основной текст;
  • date_created — дата и время создания.

В файле entries/models.py сначала импортируем модуль timezone из django.utils, а затем создадим класс Entry (в комментариях поясняется, за что отвечает тот или иной кусочек кода):

from django.db import models
from django.utils import timezone
 
class Entry(models.Model): # Создаём новый класс, который будет служить для блога моделью, указывая все необходимые элементы.
   title = models.CharField(max_length=200)
   content = models.TextField()
   date_created = models.DateTimeField(default=timezone.now)
 
   def __str__(self): # С помощью функции меняем то, как будет представлена запись в модели.
       return self.title # Указываем, что она будет идентифицироваться с помощью своего заголовка.
 
   class Meta:
       verbose_name_plural = "Entries" # Указываем правильное написание для множественного числа слова Entry.

После импорта модуля timezone мы можем использовать параметр timezone.now в качестве аргумента по умолчанию для date_created. Теперь при создании новой записи не придётся вручную указывать время и дату. Это пригодится нам позже, когда мы будем создавать форму для создания постов.

В дополнение к title, content и date_created Django автоматически добавит id как уникальный первичный ключ для всех записей. Строковое представление записи с первичным ключом 1 по умолчанию будет Entry object (1). Добавив функцию __str__(), мы настраиваем то, что будет отображаться вместо этой нумерации. Так как мы создаём блог, то лучше идентифицировать запись с помощью заголовка.

Ещё одна переменная, которую необходимо настроить вручную, — это verbose_name_plural. Если этого не сделать, то Django будет неправильно указывать множественное число Entry как Entrys (нам надо Entries).

Чтобы наша модель Entry отображалась в панели администрирования Django, регистрируем её в файле entries/admin.py:

from django.contrib import admin
from .models import Entry
 
admin.site.register(Entry) # Регистрируем модель.

Если этого не сделать, то Django не выдаст ошибку, однако при работе с блогом возможности управлять моделью через панель администратора уже не будет, а это неудобно.

После добавления нового класса и его регистрации в панели администратора необходимо создать файлы миграции для Django и запустить их. Создаются они с помощью команды makemigrations, а реализуются через команду migrate. Обе команды пишем в терминале:

python manage.py makemigrations
python manage.py migrate

После завершения миграции переходим в панель администратора http://localhost:8000/admin. В ней появился раздел Entries с нашим приложением:

Скриншот: Django / Skillbox Media

Сейчас в этом разделе нет никаких записей. Можно добавить их вручную, нажав Add. Однако удобнее реализовать добавление новых записей в блог через пользовательский интерфейс, а не панель администратора. Но это мы сделаем в следующей части статьи — следите за анонсами в Telegram-канале «Люди и код» и подписывайтесь на нашу рассылку, чтобы не пропустить.


* Решением суда запрещена «деятельность компании Meta Platforms Inc. по реализации продуктов — социальных сетей Facebook и Instagram на территории Российской Федерации по основаниям осуществления экстремистской деятельности».

Нейросети вам помогут.
Большой вебинар по нейросетям. 15 экспертов, 7 топ-нейросетей. Научитесь использовать ИИ в своей работе и повысьте эффективность.

Узнать больше

Django — фрэймворк для создания сложных и комплексных сайтов на языке Python.

В этой статье на примере создания сайта, показывающего погоду в разных городах мира, мы продемонстрируем работу с API, HTML-шаблонами и базами данных в Django.

Запускать проект будем на Джино.Хостинге.

# Подготовка к работе

Создадим для нашего сайта новый хостинг-контейнер. Для этого в разделе АккаунтХостинг выберем «Создать новый хостинг». После создания перейдём в раздел «Услуги» и подключим «Поддержку скриптовых языков» и «Поддержку SSH».

«settings»

После подключения выбранных услуг подключимся к хостингу по SSH. Данные для SSH-подключения указаны в разделе УправлениеНастройки SSH.

Перед первым подключением к хостинг-контейнеру в разделе УправлениеНастройки SSH нужно задать пароль пользователя. Порт для подключения используется стандартный — 22.

«settings»

Также в этом разделе можно настроить ограничение доступа к хостингу только с указанных IP-адресов.

После подключения к хостингу по SSH создадим виртуальное окружение для Python:

Эта команда создаёт виртуальное окружение weather_venv для Python 3.10. Список всех доступных версий Python и пути к их интерпретаторам указаны в разделе УправлениеТехническая информация.

После создания виртуального окружения активируем его

и установим Django с помощью стандартного пакетного менеджера

Теперь наше виртуальное окружение готово к работе. Перейдём к созданию проекта.

# Создание и запуск проекта

Создавать новый проект будем на локальной машине с последующим деплоем проекта на Хостинг. О том, как подготовить Хостинг к развёртыванию нового проекта и о способах загрузки проекта на сервер можно прочитать в нашей инструкции по развёртыванию проекта на Джино.Хостинге.

Создадим новый проект Django:

Эта команда создаст папку weather_project со вложенной одноимённой папкой weather_project и файлом manage.py, отвечающим за управление проектом.

Уже на данном этапе можно настроить загрузку проекта на сервер и проверить работу пустого Django-проекта. Поскольку наше приложение будет объёмным и в течение работы мы будем несоклько раз обновлять его и изменять, рекомендуем само приложение создавать в IDE VSCode или PyCharm, из которых удобно настроить выгрузку приложения непосредственно на сервер.

После загрузки проекта на хостинг в папку с доменным именем, на котором он будет работать, проверим его работу.

Для запуска Django-проекта на Хостинге нужно создать файл passenger_wsgi.py, который будет отвечать за связь проекта с веб-сервером. Содержимое файла должно быть следующим:

Здесь ~/weather_venv/bin/python3 — путь к виртуальному окружению на хостинге; weather_project.wsgi — указание на основной файл, который будет запускать приложение.

Теперь нужно изменить настройки самого нашего проекта, чтобы он мог запускаться на выбранном домене. Для этого в папке weather_project откроем файл settings.py и в строке ALLOWED_HOSTS укажем выбранное доменное имя, например ALLOWED_HOSTS = [example.com].

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

В адресной строке браузера укажем наше доменное имя. Если всё работает правильно, мы увидим стандартную стартовую страницу Django

«django»

# Создание приложения

Вернёмся к нашей IDE и создадим приложение в нашем проекте:

Эта команда создаст папку weather_app, в которой уже находятся шаблоны для создания основных файлов нашего приложения.

Теперь перейдём в папку weather_app и создадим здесь файл urls.py:

Этот файл дублирует файл urls.py всего проекта. Основное отличие его в том, что в этом файле будут только те URL, которые относятся к нашему приложению, а не ко всему проекту в целом.

Чтобы добавить наше приложение к проекту, добавим его название в список уже установленных приложений проекта. Для этого в папке weather_project откроем файл settings.py и в блок INSTALLED_APPS добавим название нашего приложения:

Теперь изменим основной файл urls.py, находящийся в папке weather_project. Мы добавим к нему строку, указывающую на использование нашего нового файла urls.py:

# Создание HTML-шаблона

Следующим шагом нам нужно создать HTML-шаблон, в который будут экспортироваться полученные по API данные и отображаться на главной странице нашего сайта.

Создадим в папке нашего приложения weather_app папку для шаблонов templates. В папке templates создадим папку для шаблонов приложения weather и уже в этой папке создадим файл index.html.

Содержимое индексного файла (~/domains/example.com/weather_app/templates/weather/index.html) будет следующим:

index.html

В Django-проекте основное взаимодействие происходит между файлами views.py приложения и urls.py проекта. Теперь, когда шаблон готов, изменим эти файлы для запуска приложения.

Откроем файл views.py и приведём его к следующему виду:

Здесь мы назвали наш основной запрос index и использовали функцию render для обращения к файлу шаблона. Теперь добавим url, который будет отправлять запрос к этому шаблону. Откроем файл urls.py, который мы создали в папке с нашим приложением и приведём его к следующему виду:

Здесь описано перенаправление запроса к файлу views.py, который мы только что изменили.

Проверим работу нашего приложения! Загрузим его на сервер и перезагрузим веб-сервер (УправлениеНастройки веб-сервера). Откроем браузер и проверим страницу нашего сайта:

«django_app»

# API

Для работы нашего приложения, нам будут нужны актуальные данные о погоде в разных городах мира. Получать их мы будем от одного из свободных погодных сервисов — Open Weather Map (opens new window).

Для получения данных по API нам нужно зарегистрироваться на сайте и в личном кабинете получить уникальный ключ, которым будем подписывать запрос

«django_app»

Чтобы проверить работу полученного ключа, можно через бразуер отправить запрос к приложению в следующем формате:

В ответ в браузере должна появиться информация в JSON-формате:

Из этого объёма нам будут нужны только основные сведения. Для этого наше приложение будет конвертировать JSON-формат в формат данных, воспринимаемых Python.

Настроим работу запросов и конвертирование полученные данных из JSON-формата в формат данных Python. Для этого добавим в файл views.py строки import requests, url, city, city_weather:

Мы добавили URL, к которому будем отправлять запрос и строку, отвечающую за конвертирование данных из JSON в Python-формат.

В нашем случае в URL не указан город, город указан отдельно в строке city и сейчас это Penza. Позже мы исправим это и будем указывать город в базе данных и через специальную форму ввода в самом приложении.

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

# Отправка данных в шаблон

Чтобы отобразить полученные данные на странице сайта, нужно передать их в index.html. Для этого создадим библиотеку значений, данные из которой и будем передавать в шаблон.

В файле views.py добавим следующие строки:

Здесь мы добавили блок weather={}, в котором определили переменные city, temperarute, description и icon. Этим переменным мы присвоили определённые значения из получаемого по API массива данных.

Теперь, чтобы передать эти данные в шаблон создадим дополнительную переменную context, которая и будет передавать все данные в шаблон. Здесь же, в файле views.py добавим строки:

Переменная context — это словарь, в котором содержатся все данные блока weather.

Теперь нужно настроить отображение получаемых данных в самом HTML-шаблоне. Откроем файл index.html и вместо значений, которые на предыдущем шаге задавали вручную вставим значения наших новых переменных, используя

В данном случае важно помнить, что при вызове переменной из словаря нужно использовать ., а не стандартный синтаксис Python (например, вызов переменной city будет выглядеть как weather.city вместо привычного weather[‘city’].

После внесения необходимых изменений снова обновим наше приложение на сервере и на главной странице нашего сайта увидим, что информация о погоде в указанном городе получена.

Следующая «проблема» нашего приложения — указание города. В данном случае он указан напрямую в коде. Это неправильно — сделаем так, чтобы список городов, для которых нужно отображать информацию, хранился в базе данных.

Воспользуемся для хранения списка городов SQLite, идущей в составе Django. Чтобы добавить города в базу, создадим дополнительную форму в окне администратора приложения.

# Администрирование приложения

Создадим администратора нашего приложения командой:

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

После создания администратора, вход в панель администрирования осуществляется через браузер по адресу http://your_domain/admin.

Теперь добавим в список доступных администратору операций возможность создавать список городов. Для этого воспользуемся файлом models.py, который находится в папке нашего приложения. Приведём этот файл к следующему виду:

Эта директива создаст в базе данных приложения таблицу из одной колонки, которая будет называться «name» и в которой будут храниться названия городов.

Чтобы создать эту таблицу в базе, нужно создать связи командой makemigrations и затем на основании этих связей обновить содержимое приложения:

Таблица в базе создана и готова к заполнению. Чтобы заполнить её, добавим форму ввода города в панель администрирования. Изменим для этого файл admin.py нашего приложения. Привдём его к следующем виду:

Теперь в панели администрирования появилась новая форма для ввода городов. Добавим несколько.

# Добавление городов на главную страницу сайта

После того, как мы добавили в базу список городов, нам нужно изменить файлы views.py и index.html, чтобы на главной странице нашего сайта отображались все города.

Начнём с изменения файла views.py. Импортируем столбец City и сделаем отдельный запрос по API для каждого из городов:

Теперь нам нужно сохранить данные для каждого города из списка и передать их в HTML-шаблон. Для этого создадим отдельный список weather_data, в котором будут храниться данные для каждого города.

Затем заменим переменную City на cities.

После — добавим словарь weather для каждого города в общий список weather_data и в конце немного изменим context, чтобы он передавал весь список в HTML-шаблон, а не только один блок данных.

В итоге файл views.py должен прийти к такому виду:

Далее, изменим index.html — добавим цикл в отображение данных в шаблоне. Для этого добавим строки {% for weather in weather_data %} и {% endfor %} в начало и конец отображения данных о городах. В итоге, блок отображения данных должен стать таким:

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

«django_app»

# Создание формы ввода

Последним, что осталось сделать для нашего сайта — создать форму ввода, чтобы пользователи могли добавлять города прямо с главной страницы сайта.

Для этого создадим новый файл forms.py в папке нашего приложения. Содержимое файла будет следующим:

Это форма ввода нового имени города. Чтобы она заработала, нужно добавить её описание во views.py и передать данные из неё в шаблон.

Начнём с изменений во views.py

Мы добавили описание передачи данных из формы и описали саму форму ввода данных.

Теперь изменим index.html

В индексный файл мы добавили блок описания формы и CSRF-токен, необходимый для отправки запросов из Django.

После описания формы ввода в шаблоне, нам нужно проверить добавить во views.py блок, который будет отвечать за обработку запросов, отправленных в форме:

Здесь мы добавили if для проверки наличия запроса, отправленного из HTML-шаблона. Далее полученные данные сохраняются и передаются в обработку.

Сохраните все сделанные изменения и перезагрузите веб-сервер. Теперь можно проверять работу формы для добавления городов. Добавим, например, «Sydney»:

«django_app»

# Заключение

Мы описали создание сайта на Django с использованием HTML-шаблонов, запросов по API, баз данных и формой для добавления информации в базу данных.

Понравилась статья? Поделить с друзьями:
  • Dizomap инструкция по применению таблетки взрослым
  • Diyaflor инструкция по применению на русском
  • Diya med tablet инструкция на русском языке
  • Diy windshield repair kit инструкция на русском
  • Diy track железная дорога инструкция по сборке