Посібник зі створення програми URL Shortener за допомогою Django

Найкращий спосіб опанувати Django, або ж будь-яку іншу майстерність, полягає у практичному застосуванні наявних знань через створення реальних проєктів.

Django є одним з лідерів серед фреймворків для веб-розробки на Python. Його потужний функціонал “з коробки” та велика екосистема сторонніх пакетів зробили його улюбленцем багатьох веб-розробників у світі.

Він відрізняється швидкістю, надійністю та має широкий набір вбудованих інструментів. Наприклад, система аутентифікації, що дозволяє зосередитися на основній функціональності вашої програми. Також, ви можете використовувати сторонні пакети для реалізації складніших завдань, такі як Django-allauth, що надає можливість реєстрації користувачів через соціальні мережі.

Проте, варто відзначити, що Django – це доволі великий фреймворк, що може ускладнити початковий етап роботи з ним.

Сьогодні ми створимо з нуля повноцінну веб-аплікацію на Django.

По завершенню цього керівництва ви:

  • Розробите програму для скорочення URL-адрес.
  • Зрозумієте архітектурний патерн Django MVT.
  • Ознайомитесь з процесом створення проекту.

Необхідні знання

Перелічені нижче пункти є бажаними, але не обов’язковими для проходження цього керівництва. Якщо у вас немає досвіду з ними, не хвилюйтеся. Головне – зробити перший крок.

  • Базове розуміння команд UNIX (l, cd, rm, touch).
  • Базові знання про класи та функції Python.
  • Наявність встановленого Python на вашому комп’ютері (хоча це здається очевидним, варто зазначити).
  • Було б чудово, якби ви вже мали досвід створення чогось на Django.

Повний код проєкту доступний в репозиторії Github.

Тепер, коли всі попередні концепції вам зрозумілі, перейдемо до практичної частини.

Опис проєкту

У цьому посібнику ми створимо сервіс для скорочення URL-адрес. Завдання такого сервісу полягає у перетворенні довгої URL-адреси на коротку та компактну версію.

Наприклад, якщо ви бажаєте поділитися посиланням на свій сайт у твіті, але обмежені кількістю символів, сервіс скорочення URL-адрес стане вам у нагоді.

Давайте поглянемо на графічну ілюстрацію цього процесу.

Як бачимо, сервіс скорочення отримує довгу URL-адресу на вхід, і повертає коротку. Саме такий функціонал ми сьогодні створимо.

У процесі реалізації цього проєкту, ви попрактикуєтесь у використанні MVT-архітектури, вивчите основи моделювання баз даних через моделі Django та навчитесь відображати інформацію користувачеві через представлення, URL-адреси та шаблони.

Структура проекту Django

Веб-сайт на Django побудовано на основі одного проекту та декількох окремих застосунків. Кожен із цих застосунків має власну функціональність і може працювати самостійно.

Візьмемо для прикладу складний веб-ресурс, як StackOverflow. Його функціональність ґрунтується на двох головних складових:

  • Керування користувачами: авторизація, реєстрація, репутація, дозволи.
  • Форум: питання, відповіді, теги, фільтри.

Відповідно до структури веб-сайту Django, проект мав би назву StackOverflow, що містив би два основних застосунки: застосунок для користувачів і застосунок для форуму.

Кожен із цих застосунків є самодостатнім, тобто містить весь код, необхідний для його коректної роботи.

Це включає моделі (структура бази даних), представлення (запити та відповіді), специфічні URL-шаблони, а також шаблони та статичні файли (зображення, CSS, JavaScript). Це означає, що будь-який застосунок Django може бути повторно використаний, оскільки він здатний працювати незалежно.

Узагальнюючи, проект – це набір конфігурацій і застосунків, що разом створюють веб-програму. Застосунок Django, зі свого боку, є складовою частиною проекту, що є незалежною (містить все необхідне для роботи) і призначений для виконання конкретного завдання.

Налаштування проекту Django

У цьому розділі ми налаштуємо проект Django, використовуючи такі інструменти, як віртуальне середовище для ізоляції залежностей Python, а також важливі скрипти Django: django-admin та manage.py.

Віртуальне середовище

Я завжди рекомендую використовувати віртуальні середовища під час розробки на Django. Це ефективний спосіб підтримувати потрібний набір залежностей. Основне призначення віртуального середовища — ізолювати пакети розробки від глобальних.

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

Примітка: для коректної роботи цього методу потрібен Python 3.6 або новішої версії.

python -m venv .venv

Ця команда використовує команду python -m або python –mod. Вона запускає модуль або бібліотеку як скрипт. У цьому випадку venv — це бібліотека, яку ми запускаємо, а .venv — це назва віртуального середовища, яке ми створюємо.

Простіше кажучи, команда означає:

Python, запусти вбудовану бібліотеку venv як скрипт і створи віртуальне середовище з назвою .venv

Тепер активуємо віртуальне середовище, використовуючи наступну команду:

source .venv/bin/activate

Щоб перевірити, чи не встановлено пакетів у новому venv, виконайте:

pip freeze

Якщо віртуальне середовище було активовано коректно, ви не отримаєте жодних результатів, оскільки ми ще нічого не встановили.

Встановлення Django

Для створення програми скорочення URL-адрес, спочатку встановимо пакет Django. Django є стороннім пакетом, тому потрібно встановити його за допомогою Pip (Pip Installs Packages):

$ pip install django
Collecting django
  Downloading Django-3.2.1-py3-none-any.whl (7.9 MB)
     |████████████████████████████████| 7.9 MB 344 kB/s 
Collecting asgiref<4,>=3.3.2
  Using cached asgiref-3.3.4-py3-none-any.whl (22 kB)
Collecting sqlparse>=0.2.2
  Using cached sqlparse-0.4.1-py3-none-any.whl (42 kB)
Collecting pytz
  Using cached pytz-2021.1-py2.py3-none-any.whl (510 kB)
Installing collected packages: asgiref, sqlparse, pytz, django
Successfully installed asgiref-3.3.4 django-3.2.1 pytz-2021.1 sqlparse-0.4.1

Примітка: символ $ є вашим символом командного рядка.

Перевірте, чи правильно встановився Django, ще раз виконавши команду перевірки встановлених пакетів у venv:

$ pip freeze
asgiref==3.3.4
Django==3.2.1
pytz==2021.1
sqlparse==0.4.1

Не турбуйтеся, якщо ваші версії відрізняються. Якщо Django має версію 3.x, ви можете продовжувати без проблем.

Запуск проєкту Django

Після встановлення Django, прийшов час створити структуру веб-сайту URL shortener. Пригадайте, що таке проєкт Django. Створимо його, виконавши команду:

django-admin startproject config

Пояснимо команду: django-admin – це утиліта командного рядка, призначена для виконання всіх необхідних дій для створення проєкту Django. Частина “startproject” – це команда утиліти, а config – назва проєкту, який ми створюємо.

Важливо зазначити, що назва config є умовною і може бути будь-якою. Я використовую назву config для зручності. Таким чином, не бійтесь використовувати інші назви для своїх проєктів.

Тепер ви маєте папку config/, яка містить файли. Розглянемо файлову структуру проєкту пізніше. Зараз перейдемо до каталогу проєкту та запустимо локальний сервер:

cd config/

Найважливішим файлом, який ви будете використовувати, є сценарій manage.py. Він має такі ж функції, як django-admin, але головною перевагою його використання є те, що ви можете керувати налаштуваннями під час запуску проєкту.

Перевіримо, чи все працює коректно:

python manage.py runserver

Створення застосунку Url shortener

Прийшов час створити головний застосунок проєкту. Для цього скористаємось файлом manage.py:

python manage.py startapp urlshortener

Це створить застосунок Django з назвою urlshortener. Якщо ви виконаєте команду tree, то отримаєте наступну структуру:

.
├── config
│   ├── asgi.py
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── manage.py
└── urlshortener
    ├── admin.py
    ├── apps.py
    ├── __init__.py
    ├── migrations
    │   └── __init__.py
    ├── models.py
    ├── tests.py
    └── views.py

Пояснимо призначення різних файлів. “config” – назва нашого проєкту. В середині “config” знаходиться файл settings.py, в якому задаються всі параметри проєкту. urls.py – це загальна конфігурація URL-адрес проєкту. Він визначає URL-шляхи всіх застосунків проєкту.

Файли asgi.py та wsgi.py необхідні для налаштування програми під час розгортання.

Manage.py – це скрипт Python, що дозволяє запускати всі доступні команди Django-admin.

Перейшовши в середину “urlshortener”, ви побачите папку “migrations/” та файли, що важливі для логіки будь-якого застосунку.

apps.py містить конфігурацію застосунку. Зазвичай ви не редагуєте цей файл, якщо не виконуєте складні налаштування.

admin.py – місце, де реєструються моделі для їх відображення в панелі адміністратора Django.

models.py є важливим. В середині цього модуля визначаються моделі, що є способом зберігання даних. Про моделі поговоримо пізніше.

migrations/ – папка, де зберігаються міграції Django. Розглянемо це детальніше згодом.

tests.py – файл, де зберігаються тести. Тестування не входить в рамки даного керівництва.

views.py – файл, що зберігає представлення. По суті, цей файл визначає, як користувач взаємодіє з усіма аспектами вашої програми.

Встановлення застосунку Django

Перед продовженням, відкрийте файл settings.py і змініть змінну INSTALLED_APPS, додавши застосунок “urlshortener”:

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # Custom apps
    'urlshortener',
]

Це стандартний процес при створенні нового застосунку. Не забувайте встановлювати його в налаштуваннях проекту.

Розуміння архітектури MVT

Архітектура Model-View-Template (MVT) – це патерн проєктування програмного забезпечення, що використовується розробниками Django для створення веб-застосунків.

Вона ґрунтується на 3 основних концепціях: Модель (дані), Представлення (взаємодія користувача з даними) та Шаблон (спосіб відображення даних користувачеві).

Моделі – це класи Python, що визначають поля та поведінку даних, які ви бажаєте зберігати. Зазвичай кожна модель відповідає певній таблиці в базі даних.

Представлення – це функції, що приймають запит від користувача і повертають відповідь. Між цим процесом відбувається бізнес-логіка. Під бізнес-логікою розуміється спосіб створення, зберігання та видалення даних.

Шаблони – це текстові документи (зазвичай Html), що відображаються користувачам. Їх мета полягає у представленні даних у зрозумілому вигляді. Django включає міні-мову під назвою Django Template Language (DTL), що дозволяє використовувати деякі можливості Python у текстових документах.

Створення моделі Shortener

Після ознайомлення з архітектурою MVT, почнемо створення програми для скорочення URL-адрес.

Визначимо модель скорочення у файлі models.py:

'''
Url shortener model
'''

from django.db import models

# Create your models here.

class Shortener(models.Model):
    '''
    Creates a short url based on the long one
    
    created -> Hour and date a shortener was created 
    
    times_followed -> Times the shortened link has been followed

    long_url -> The original link

    short_url ->  shortened link https://domain/(short_url)
    ''' 
    created = models.DateTimeField(auto_now_add=True)

    times_followed = models.PositiveIntegerField(default=0)    

    long_url = models.URLField()

    short_url = models.CharField(max_length=15, unique=True, blank=True)

    class Meta:

        ordering = ["-created"]


    def __str__(self):

        return f'{self.long_url} to {self.short_url}'

Це досить великий клас, в якому відбувається багато речей, але не хвилюйтесь, ми розглянемо кожен важливий аспект.

Пояснення моделі

Спочатку імпортуємо модуль models. Цей модуль включає функції, необхідні для створення моделі Django.

Зверніть увагу, що модель Shortener розширює models.Model. Будь-яка модель в Django повинна бути підкласом моделі.Model.

Далі визначимо поля, що матиме модель в базі даних. Поле “created” – це дата та час створення скороченого посилання. Для його створення використаємо DateTimeField. Аргумент auto_now_add=True забезпечує, що значення поля змінюватиметься тільки під час створення екземпляра.

Друге поле “times_followed” вказує на кількість переходів за скороченим URL-адресом. Це PositiveIntegerField зі значенням за замовчуванням “0”. Це означає, що при створенні екземпляра, Django заповнює це поле значенням 0.

З іншого боку, поле “long_url” містить URL-адресу, введену користувачем. Це URLField, оскільки ми очікуємо, що користувач вводитиме значення у форматі “http://yoursite.com”.

Останнє поле – “short_url”. Воно має обмеження у 15 символів, має бути унікальним та може залишатись порожнім. Останнє означає, що користувачам не потрібно буде створювати власний короткий код при роботі з формами.

Внутрішній клас Meta визначає, як поводиться клас. Ми встановили, що порядок об’єктів shortener (викликаних Shortener.objects.all()) повинен сортуватися за часом створення (останні створені – першими).

Метод __str__ визначає, як модель має виводитись у вигляді рядка. Наприклад, якщо у нас є об’єкт з long_url = “https://techukraine.net.com/” і short_url = “123456”, виведеться:

https://techukraine.net.com/ to 123456

Тепер потрібно створити механізм генерації випадкового короткого посилання.

Генерація випадкового коду

Ми створимо 2 функції: перша генеруватиме випадковий код, а друга – унікальний випадковий код для моделі Shortener. Створіть файл utils.py у застосунку “urlshortener”:

touch utils.py

У цьому файлі використаємо функцію вибору з вбудованого модуля random. Це полегшить процес вибору випадкових символів для створення коду.

'''
Utilities for Shortener
'''
from django.conf import settings

from random import choice

from string import ascii_letters, digits

# Try to get the value from the settings module
SIZE = getattr(settings, "MAXIMUM_URL_CHARS", 7)

AVAIABLE_CHARS = ascii_letters + digits


def create_random_code(chars=AVAIABLE_CHARS):
    """
    Creates a random string with the predetermined size
    """
    return "".join(
        [choice(chars) for _ in range(SIZE)]
    )

Ця функція повертає випадковий рядок довжиною, заданою в файлі налаштувань, або 7 за замовчуванням. Функція getattr використовується для отримання змінної з модуля налаштувань без повідомлення про помилку, якщо змінна не задана.

Розрахуємо кількість можливих комбінацій. Якщо маємо 7 позицій, де може знаходитись 62 доступних символи, кількість перестановок становить:

Отже, кількість можливих унікальних скорочень складає 2.5 трильйона. Тому ми можемо не турбуватися про повторення випадкових скорочених URL-адрес.

Проте, існує невелика ймовірність отримання повторюваних скорочень. Це є проблемою, оскільки ми задали short_url як унікальне поле. Тому нам потрібна наступна функція:

def create_shortened_url(model_instance):
    random_code = create_random_code()
    # Gets the model class

    model_class = model_instance.__class__

    if model_class.objects.filter(short_url=random_code).exists():
        # Run the function again
        return create_shortened_url(model_instance)

    return random_code

Пояснимо, що відбувається. Функція приймає екземпляр моделі Shortener. Спочатку генерується випадковий код за допомогою create_random_code. Потім функція отримує клас моделі та перевіряє, чи існує інший об’єкт з таким же short_url. Якщо такий об’єкт існує, функція викликає себе знову. Якщо все гаразд, функція повертає random_code.

Пізніше, через оболонку, ви детально розглянете роботу цієї функції.

Створивши допоміжну функцію, використаємо її для генерації випадкового коду в моделі скорочення.

Зміна методу збереження

В кінці класу Shortener змінимо метод збереження моделі. Метод save викликається кожного разу, коли об’єкт зберігається в базі даних. Подивимось, як цим скористатись.

# Import the function used to create random codes
from .utils import create_shortened_url

# At the end of the  Shortener model
    def save(self, *args, **kwargs):

        # If the short url wasn't specified
        if not self.short_url:
            # We pass the model instance that is being saved
            self.short_url = create_shortened_url(self)

        super().save(*args, **kwargs)

Ми переписуємо метод save, додаючи нову функціональність до вже існуючого методу батьківського класу. По суті, ми повідомляємо Django, що кожного разу, коли об’єкт Shortener зберігається, і short_url не задано, його потрібно заповнити випадковим кодом.

Застосування міграцій

Прийшов час створити та застосувати міграції моделі Shortener. Виконайте наступні команди в кореневій папці проєкту:

$ python manage.py makemigrations
Migrations for 'urlshortener':
  urlshortener/migrations/0001_initial.py
    - Create model Shortener

$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, urlshortener
Running migrations:
  ......
  # Apply the URL shortener migrations
  Applying urlshortener.0001_initial... OK

Наразі не турбуйтесь про міграції. Під час виконання цих двох команд, Django створює файл бази даних db.sqlite на основі визначених вами моделей.

Створимо декілька об’єктів через Django shell:

$ python manage.py shell

>>> from urlshortener.models import Shortener
>>> s = Shortener(long_url="https://techukraine.net.com")
>>> s.short_url
''
>>> s.save()
>>> s.short_url
'kdWFVIc'
>>> s.long_url
'https://techukraine.net.com'
>>> print(s)
https://techukraine.net.com to kdWFVIc

Саме так працюватимуть усі об’єкти shortener.

Написання представлень

Як зазначалося раніше, представлення — це функція, яка приймає запит і повертає відповідь. Створимо представлення “Hello World”:

Базовий шаблон відповіді

У файлі urlshortener/views.py створіть функцію home_view:

'''
Shortener views
'''
from django.shortcuts import render, get_object_or_404 # We will use it later

from django.http import HttpResponse 

# Create your views here.

def home_view(request):
    return HttpResponse("Hello world")

Функція повертає повідомлення “Hello world”. Пізніше подивимось, як це виглядає у браузері. Тепер створимо файл urls.py, де зберігатимуться шаблони URL-адрес застосунку:

touch urls.py

Додайте наступний код:

'''
Urls for shortener app urlshortener/urls.py
'''

from django.urls import path

# Import the home view
from .views import home_view

appname = "shortener"

urlpatterns = [
    # Home view
    path("", home_view, name="home")
]

Змінна appname задає простір імен застосунку urlshortener.

Імпортується функція path, яка повертає елемент для включення в шаблони URL-адрес програми. Атрибут name – простір імен шляху, який можна викликати в середині шаблонів.

Змінимо загальні URL-адреси проєкту:

# config/urls.py

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

urlpatterns = [
    path('admin/', admin.site.urls),
    
    # Shortener Urls
    path('', include('urlshortener.urls'))
]

Знову запустимо сервер:

python manage.py runserver

Якщо ви запустите сервер, то отримаєте повідомлення “Hello world”. Це через те, що ви включаєте шаблони URL-адрес з застосунку urlshortener в загальний проект.

Це лише відправна точка. Створимо форму, що дозволить користувачеві створювати скорочені URL-адреси самостійно.

Створення форм

В Django форма – це простий клас, що дозволяє отримувати дані від користувача.

Створимо файл forms.py. За домовленістю в цьому файлі зберігаються усі форми застосунку:

cd urlshortener/
touch forms.py

В цьому файлі створимо клас “ShortenerForm”, що розширює “ModelForm”:

'''
Shortener Forms urlshortener/forms.py
'''

from django import forms

from .models import Shortener

class ShortenerForm(forms.ModelForm):
    
    long_url = forms.URLField(widget=forms.URLInput(
        attrs={"class": "form-control form-control-lg", "placeholder": "Your URL to shorten"}))
    
    class Meta:
        model = Shortener

        fields = ('long_url',)

Це модельна форма, оскільки її метою є створення об’єкта моделі з даних, введених користувачем. Ми також використовуємо аргумент “widget”, що дозволяє задати атрибут “class” (клас в CSS, а не python). Це необхідно, тому що пізніше ми будемо стилізувати програму за допомогою Bootstrap.

Завершення представлень

Після створення форми, потрібно додати бізнес-логіку програми.

Перейдіть до файлу views.py в застосунку “urlshortener” і змініть home_view. Погляньте на репозиторій Github, щоб мати уявлення про структуру проєкту.

Програма для скорочення URL-адрес має два види відображень:

  • Домашня сторінка: тут відображається форма для скорочення та нова URL-адреса, якщо форма вже відправлена.
  • Перенаправлення: перенаправляє на довгу URL-адресу та додає 1 до лічильника переходів.
  • Почнемо з домашнього відображення, яке є більш складним. Вам потрібно буде імпортувати модель та форму Shortener. Будемо використовувати функцію, щоб зрозуміти весь потік даних. Також, ми використаємо шлях до шаблону (який ще не створено).

    Домашнє відображення

    '''
    Shortener views
    '''
    from django.shortcuts import render # We will use it later
    
    from django.http import HttpResponse, Http404, HttpResponseRedirect
    
    
    # Model
    from .models import Shortener
    
    # Custom form
    
    from .forms import ShortenerForm
    
    # Create your views here.
    
    def home_view(request):
        
        template="urlshortener/home.html"
    
        
        context = {}
    
        # Empty form
        context['form'] = ShortenerForm()
    
        if request.method == 'GET':
            return render(request, template, context)
    
        elif request.method == 'POST':
    
            used_form = ShortenerForm(request.POST)
    
            if used_form.is_valid():
                
                shortened_object = used_form.save()
    
                new_url = request.build_absolute_uri('/') + shortened_object.short_url
                
                long_url = shortened_object.long_url 
                 
                context['new_url']  = new_url
                context['long_url'] = long_url
                 
                return render(request, template, context)
    
            context['errors'] = used_form.errors
    
            return render(request, template, context)
    

    Відображення базується на двох умовах:

  • Коли метод HTTP дорівнює GET: як контекст передаємо форму Shortener, що використовується для створення об’єктів Shortener.
  • Коли метод HTTP дорівнює POST: як контекст передаємо форму, щоб користувач міг ввести іншу URL-адресу. Але запит POST передається в іншу форму, що називається “used_form”.
  • Хитрий спосіб динамічного отримання повної URL-адреси сайту – використання методу об’єкту запиту build_absolute_uri:

    >>> print(request.build_absolute_uri('/'))
    'https://localhost:8080/'

    Для обробки неправильного запиту (коли користувач не ввів коректну URL-адресу), ми отримуємо помилки форми, передаємо їх як контекст і відображаємо шаблон. Пізніше ви побачите, як відобразити помилки в шаблоні.

    Відображення перенаправлення

    redirect_url_view дещо простіше. Це детальний перегляд, який працює тільки з певним об’єктом.

    Ця функція приймає як параметри запит користувача та shortened_part з URL-адреси. Не