# Создание сайта на Django
Django — фрэймворк для создания сложных и комплексных сайтов на языке Python.
В этой статье на примере создания сайта, показывающего погоду в разных городах мира, мы продемонстрируем работу с API, HTML-шаблонами и базами данных в Django.
Запускать проект будем на Джино.Хостинге.
# Подготовка к работе
Создадим для нашего сайта новый хостинг-контейнер. Для этого в разделе АккаунтХостинг выберем «Создать новый хостинг». После создания перейдём в раздел «Услуги» и подключим «Поддержку скриптовых языков» и «Поддержку SSH».
После подключения выбранных услуг подключимся к хостингу по SSH. Данные для SSH-подключения указаны в разделе УправлениеНастройки SSH.
Перед первым подключением к хостинг-контейнеру в разделе УправлениеНастройки SSH нужно задать пароль пользователя. Порт для подключения используется стандартный — 22.
Также в этом разделе можно настроить ограничение доступа к хостингу только с указанных 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
# Создание приложения
Вернёмся к нашей 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, который мы только что изменили.
Проверим работу нашего приложения! Загрузим его на сервер и перезагрузим веб-сервер (УправлениеНастройки веб-сервера). Откроем браузер и проверим страницу нашего сайта:
# API
Для работы нашего приложения, нам будут нужны актуальные данные о погоде в разных городах мира. Получать их мы будем от одного из свободных погодных сервисов — Open Weather Map (opens new window).
Для получения данных по API нам нужно зарегистрироваться на сайте и в личном кабинете получить уникальный ключ, которым будем подписывать запрос
Чтобы проверить работу полученного ключа, можно через бразуер отправить запрос к приложению в следующем формате:
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>
...
Теперь можно проверить, что у нас получилось. В адресной строке браузера адрес нашего сайта. В зависимости от добавленных в список городов, главная страница сайта должна выглядеть примерно так:
# Создание формы ввода
Последним, что осталось сделать для нашего сайта — создать форму ввода, чтобы пользователи могли добавлять города прямо с главной страницы сайта.
Для этого создадим новый файл 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 с использованием HTML-шаблонов, запросов по API, баз данных и формой для добавления информации в базу данных.