# Создание сайта на Django

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

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

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

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

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

«settings»

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

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

«settings»

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

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

/opt/alt/python37/bin/virtualenv --python /opt/alt/python310/bin/python3 weather_venv

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

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

source ./weather_venv/bin/activate

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

pip install django

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

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

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

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

django-admin startproject weather_project

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

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

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

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

import sys, os
INTERP = os.path.expanduser("~/weather_venv/bin/python3")

if sys.executable != INTERP: os.execl(INTERP, INTERP, *sys.argv)

from weather_project.wsgi import application

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

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

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

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

«django»

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

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

django-admin startapp weather_app

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

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

from django.urls import path

urlpatterns = [
]

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

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

…
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'weather_app',
]

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

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

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('weather.urls')),
]

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

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

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

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

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>What's the weather like?</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.2/css/bulma.css" />
</head>
<body>
    <section class="hero is-primary">
        <div class="hero-body">
            <div class="container">
                <h1 class="title">
                    What's the weather like?
                </h1>
            </div>
        </div>
    </section>
    <section class="section">
        <div class="container">
            <div class="columns">
                <div class="column is-offset-4 is-4">
                    <form method="POST">
                        <div class="field has-addons">
                            <div class="control is-expanded">
                                <input class="input" type="text" placeholder="City Name">
                            </div>
                            <div class="control">
                                <button class="button is-info">
                                    Add City
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </section>
    <section class="section">
        <div class="container">
            <div class="columns">
                <div class="column is-offset-4 is-4">
                    <div class="box">
                        <article class="media">
                            <div class="media-left">
                                <figure class="image is-50x50">
                                    <img src="http://openweathermap.org/img/w/10d.png" alt="Image">
                                </figure>
                            </div>
                            <div class="media-content">
                                <div class="content">
                                    <p>
                                        <span class="title">Sun City</span>
                                        <br>
                                        <span class="subtitle">59° F</span>
                                        <br> Solar wind with some prominence
                                    </p>
                                </div>
                            </div>
                        </article>
                    </div>
                </div>
            </div>
        </div>
    </section>
    <footer class="footer">
    </footer>
</body>
</html>

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

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

from django.shortcuts import render

def index(request):
    return render(request, 'weather/index.html') #returns the index.html template

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

from django.urls import path
from . import views

urlpatterns = [
    path('', views.index),  #the path for our index view
]

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

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

«django_app»

# API

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

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

«django_app»

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

http://api.openweathermap.org/data/2.5/weather?q=Penza&units=imperial&appid=ваш_API_ключ

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

{"coord":{"lon":45.0046,"lat":53.2007},"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"base":"stations","main":{"temp":56.82,"feels_like":54.61,"temp_min":56.82,"temp_max":56.82,"pressure":1009,"humidity":51},"visibility":10000,"wind":{"speed":5.3,"deg":174,"gust":8.41},"clouds":{"all":0},"dt":1652067877,"sys":{"type":2,"id":219846,"country":"RU","sunrise":1652058692,"sunset":1652114482},"timezone":10800,"id":511565,"name":"Penza","cod":200}

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

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

from django.shortcuts import render
import requests

def index(request):
    url = 'http://api.openweathermap.org/data/2.5/weather?q={}&units=imperial&appid=ваш_API_ключ'

    city = 'Penza'

    city_weather = requests.get(url.format(city)).json()

    return render(request, 'weather/index.html')

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

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

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

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

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

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

...
def index(request):
    ...
    weather = {
        'city' : city,
        'temperature' : city_weather['main']['temp'],
        'description' : city_weather['weather'][0]['description'],
        'icon' : city_weather['weather'][0]['icon']
    }

    return render(request, 'weather/index.html') #returns the index.html template

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

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

...
def index(request):
    ...
    context = {'weather' : weather}

    return render(request, 'weather/index.html', context) #returns the index.html template

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

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

...
<div class="box">
    <article class="media">
        <div class="media-left">
            <figure class="image is-50x50">
                <img src="http://openweathermap.org/img/w/{{ weather.icon }}.png" alt="Image">
            </figure>
        </div>
        <div class="media-content">
            <div class="content">
                <p>
                    <span class="title">{{ weather.city }}</span>
                    <br>
                    <span class="subtitle">{{ weather.temperature }}° F</span>
                    <br> {{ weather.description }}
                </p>
            </div>
        </div>
    </article>
</div>
...

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

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

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

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

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

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

python manage.py createsuperuser

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

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

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

from django.db import models

class City(models.Model):
    name = models.CharField(max_length=25)

    def __str__(self): #show the actual city name on the dashboard
        return self.name

    class Meta: #show the plural of city as cities instead of cities
        verbose_name_plural = 'cities'

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

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

python manage.py makemigrations
python manage.py migrate

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

from django.contrib import admin
from .models import City

admin.site.register(City)

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

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

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

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

from django.shortcuts import render
import requests
from .models import City

def index(request):
    url = 'http://api.openweathermap.org/data/2.5/weather?q={}&units=imperial&appid=ваш_API_ключ'

    cities = City.objects.all() #return all the cities in the database
    ...

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

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

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

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

...
def index(request):
    ...
    cities = City.objects.all() #return all the cities in the database

    weather_data = []

    for city in cities:

        city_weather = requests.get(url.format(city)).json() #request the API data and convert the JSON to Python data types

        weather = {
            'city' : city,
            'temperature' : city_weather['main']['temp'],
            'description' : city_weather['weather'][0]['description'],
            'icon' : city_weather['weather'][0]['icon']
        }

        weather_data.append(weather) #add the data for the current city into our list

    context = {'weather_data' : weather_data}

    return render(request, 'weather/index.html', context) #returns the index.html template

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

...
<div class="column is-offset-4 is-4">
    {% for weather in weather_data %}
    <div class="box">
        <article class="media">
            <div class="media-left">
                <figure class="image is-50x50">
                    <img src="http://openweathermap.org/img/w/{{ weather.icon }}.png" alt="Image">
                </figure>
            </div>
            <div class="media-content">
                <div class="content">
                    <p>
                        <span class="title">{{ weather.city }}</span>
                        <br>
                        <span class="subtitle">{{ weather.temperature }}° F</span>
                        <br> {{ weather.description }}
                    </p>
                </div>
            </div>
        </article>
    </div>
    {% endfor %}
</div>
...

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

«django_app»

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

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

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

from django.forms import ModelForm, TextInput
from .models import City

class CityForm(ModelForm):
    class Meta:
        model = City
        fields = ['name']
        widgets = {
            'name': TextInput(attrs={'class' : 'input', 'placeholder' : 'City Name'}),
        } #updates the input class to have the correct Bulma class and placeholder

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

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

...
from .forms import CityForm

def index(request):
    ...
    form = CityForm()

    weather_data = []
    ...
    context = {'weather_data' : weather_data, 'form' : form}

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

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

...
<form method="POST">
    {% csrf_token %}
    <div class="field has-addons">
        <div class="control is-expanded">
            {{ form.name }}
        </div>
        <div class="control">
            <button class="button is-info">
                Add City
            </button>
        </div>
    </div>
</form>
...

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

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

...
def index(request):
    url = 'http://api.openweathermap.org/data/2.5/weather?q={}&units=imperial&appid=ваш_API_ключ'

    cities = City.objects.all() #return all the cities in the database

    if request.method == 'POST': # only true if form is submitted
        form = CityForm(request.POST) # add actual request data to form for processing
        form.save() # will validate and save if validate

    form = CityForm()
    ...

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

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

«django_app»

# Заключение

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