使用 Django 构建 URL 缩短器应用程序的指南

使用 Django 构建 URL 缩短器应用程序指南

掌握 Django 或任何其他技能的最佳途径在于实践,通过构建实际可用的项目来运用所学知识。

Django 是一个广泛应用的 Python Web 开发框架,其强大的内置功能和丰富的第三方库使其成为全球开发者最喜爱的选择之一。

它具备高效、可靠的特性,并提供诸多内置功能,例如内置的身份验证系统,让您可以专注于应用程序的核心功能。此外,您可以安装第三方扩展包来执行更为复杂的操作,例如使用 Django-allauth 实现社交账号登录。

然而,Django 的庞大规模有时会让初学者望而却步。Django 官方网站

今天,我们将引导您从零开始构建一个功能完善的 Django 应用程序。

完成本教程后,您将能够:

  • 构建一个 URL 缩短器应用程序
  • 深入了解 Django MVT 架构
  • 熟悉创建项目的工作流程

前提条件

以下所有要求均为可选,它们将有助于您更顺利地完成本教程。 但即使您没有相关经验,也无需担心,最重要的是迈出第一步。

  • 对 UNIX 命令(例如 ls、cd、rm、touch)的基本了解
  • 对 Python 类和函数的基本理解
  • 您的计算机已安装 Python(这一点可能显而易见,但仍需提及)
  • 如果您之前使用 Django 构建过项目,那就更好了

所有代码均可在 Github 代码仓库 中找到。

现在您已经了解了所需的基础知识,让我们正式开始吧。

项目概述

本教程的目标是构建一个 URL 缩短器。 简单来说,URL 缩短器是一种将长 URL 转换为更短、更易于管理的 URL 的服务。

举个例子,如果您想在 Twitter 上分享一个链接到您网站的推文,但受到字符数量的限制,则可以使用 URL 缩短器。

让我们通过图表来理解这个概念。

如图所示,URL 缩短器接收一个长 URL,并返回一个短 URL。 这正是您今天要创建的内容。

通过这个项目,您将实践 MVT 架构的运用,学习如何使用 Django 模型设计数据库,并掌握如何通过视图、URL 和模板向用户展示信息。

Django 项目的结构

通常,一个 Django 网站由一个项目和若干独立的应用程序组成。 每个应用程序都专注于特定的功能,并可以独立运行。

例如,设想一个复杂的 Web 应用程序,如 Stack Overflow。 它的功能可以划分为两个主要部分:

  • 用户管理:包括登录、注销、信誉、权限等
  • 论坛:涵盖问题、答案、标签、筛选等功能

按照 Django 网站的结构,该项目将命名为 StackOverflow,其中包含两个主要的应用程序:用户应用程序和论坛应用程序。

每个应用程序都具有独立的职责,这意味着它们都包含自身正常运行所需的所有代码。

这包括模型(数据库结构)、视图(请求和响应)、特定的 URL 模式,以及模板和静态文件(例如图片、CSS、JavaScript)。 这种架构使 Django 应用程序能够重复使用,因为它们可以独立运作。

简而言之,项目是指一组用于构建 Web 应用程序的配置和应用程序。 而 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

要验证您在新虚拟环境中没有安装任何软件包,请运行:

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

注意:请记住,$ 符号只是您的 shell 提示符。

为了验证安装是否成功,我们再次检查虚拟环境中安装的软件包:

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

如果您的版本与我的不同,请不要担心。 只要您的 Django 版本仍然是 3.x,您就可以继续进行。

启动 Django 项目

安装 Django 后,就可以创建 URL 缩短器网站的结构了。 还记得 Django 项目是什么吗? 让我们通过运行以下命令来创建一个。

django-admin startproject config

`django-admin` 是一个命令行实用程序,用于执行创建 Django 项目所需的所有任务,这解释了该命令的所有含义。“`startproject`” 部分是由 `django-admin` 实用程序执行的命令,而 `config` 是我们要创建的项目的名称。

需要强调的是,`config` 可以是您想要的任何名称。我之所以使用 `config` 作为这个项目的名称,只是为了方便。 在项目之间切换并保持相同的命名约定是一种好习惯。 所以请随时使用其他项目名称。

正如您可能已经注意到的那样,您现在有一个名为 `config/` 的文件夹,其中包含许多文件。 我们将在稍后讨论项目的文件结构。 现在,让我们进入项目目录并运行本地服务器。

cd config/

您将使用的最重要的文件是 `manage.py` 脚本。 它具有与 `django-admin` 相同的功能,但使用它的主要优势在于允许您在运行项目时管理设置。

现在,让我们看看一切是否正常。

python manage.py runserver

创建网址缩短器应用程序

现在是时候创建项目的主要应用程序了。 您将使用 `manage.py` 文件来完成此任务。

python manage.py startapp urlshortener

这将创建一个名为 `urlshortener` 的 Django 应用程序。 如果您运行 树状 命令,您将看到类似下面的结构:

.
├── 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` 文件,并通过添加 `urlshortener` 应用来修改 `INSTALLED_APPS` 变量。

# 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 架构

模型、视图、模板(MVT)模式是 Django 开发人员用于创建 Web 应用程序的软件设计模式。

它基于三个核心概念:模型(数据)、视图(用户与数据的交互)和模板(用户查看数据的方式)。

模型是 Python 类,用于定义您希望存储的数据的所有字段和行为。 通常,每个模型都对应数据库中的一个唯一表。

最简单的视图是一个可调用的对象,它接收用户的请求并生成响应。 在此过程中会执行业务逻辑。 我知道 “业务逻辑” 是一个相当模糊的概念,所以我来解释一下它的确切含义。 业务逻辑就是创建、存储和删除数据的方式,仅此而已。

最后,模板是向用户展示的文本文档(通常是 HTML)。 它的目的是尽可能清晰地呈现数据。 Django 集成了一种名为 Django 模板语言 (DTL) 的微型语言,它允许您在文本文档中加入 Python 的一些强大功能。

创建 Shortener 模型

快速了解 MVT 模式后,让我们从头开始创建 Django URL 缩短器。

首先,让我们在 `models.py` 文件中定义 `Shortener` 模型。

'''
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 应用程序中的任何模型都必须是 `models.Model` 类。

接下来,我们定义了模型将在数据库中拥有的所有字段。 `created` 字段是创建缩短链接的日期和时间,因此我们使用 `DateTimeField` 来创建此类功能。 我们使用参数 `auto_now_add=True`,因为我们希望该字段仅在创建实例时被更改。

第二个字段 `times_followed` 指的是缩短的 URL 被使用的次数。 它是一个 `PositiveIntegerField`,我们将其默认值设置为零。 这意味着每次创建实例时,Django 都会用 `0` 填充该字段。

另一方面,`long_url` 指的是用户输入的 URL。 它是一个 `URLField`,因为我们只希望用户输入诸如 `http://yoursite.com` 这样的格式。

最后一个字段是 `short_url`,它有一些有趣的细节。 我们指定它的长度只能是 15 个字符,并且必须是唯一的,也就是说该字段中不能有重复的元素。 最后,我们指出它可以为空,这意味着用户在使用表单时不需要编写自己的缩短代码。

内部类 `Meta` 告诉我们该类的行为方式。 我们设置缩短对象的顺序(通过调用 `Shortener.objects.all()`)将由最近创建的对象区分开来。

`__str__` 方法告诉我们如何打印模型。 因此,如果我们有一个 `long_url = “https://techblik.com.com/”` 和缩短部分为 `“123456”` 的对象,我们将打印它:

https://techblik.com.com/ to 123456

现在是时候寻找一种以随机方式保存短链接的方法了。

创建缩短功能

我们将创建两个自定义函数。 第一个将生成一个随机代码,第二个将防止从 `Shortener` 模型中获取重复的随机代码。 为此,请在 `urlshortener` 应用程序中创建一个文件 `utils.py`。

touch utils.py

在此文件中,我们将使用 `random` 内置模块中的 `choice` 函数。 这有助于选择随机字符来创建代码。

'''
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 的可能性。

尽管可以存在如此多的排列,但重复缩短部分的概率很小。 这是一个问题,因为我们将 `shortened_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`。

稍后,您将与 shell 进行交互,以便仔细研究此功能。

创建实用程序函数后,让我们使用它在缩短模型中创建随机代码。

修改保存方法

在 `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://techblik.com.com")
>>> s.short_url
''
>>> s.save()
>>> s.short_url
'kdWFVIc'
>>> s.long_url
'https://techblik.com.com'
>>> print(s)
https://techblik.com.com to kdWFVIc
    

这几乎就是所有缩短对象的工作方式。

编写视图

正如我之前所说,视图是一个接受请求并返回响应的简单函数。 那么,让我们看看如何创建一个简单的 “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 模式。

创建 `urls.py` 文件:

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` 函数,它返回一个元素,以包含在应用程序的 `urlpatterns` 中。 `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 缩短器应用程序中的 `urlpatterns` 包含到了整个项目中。

这只是一个起点。 现在是时候创建一个表单,让用户可以自行创建缩短的 URL 了。

创建表单

在 Django 中,表单 是一个简单的类,用于接收用户的输入。

您将创建一个 `forms.py` 文件。 将应用程序的所有表单存储在该文件中是一种惯例。

cd urlshortener/
touch forms.py

在该文件中,您将创建一个继承自 `ModelForm` 的类 `ShortenerForm`。

'''
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` 文件,并修改 `home_view` 视图。 您可以查看 Github 代码仓库,以了解当前的项目结构。

URL 缩短器应用程序包含两个视图:

  • 主页视图:显示用于输入长 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` 稍微简单一些。 这是一个详细视图,这意味着该视图仅适用于对象。

此函数将用户的请求和 URL 的缩短部分作为参数。 没有必要断言我们收到的请求类型,因为我们不在此视图中使用表单。

def redirect_url_view(request, shortened_part):

    try:
        shortener = Shortener.objects.get(short_url=shortened_part)

        shortener.times_followed += 1        

        shortener.save()
        
        return HttpResponseRedirect(shortener.long_url)
        
    except:
        raise Http404('Sorry this link is broken :(')
    

我们使用 `try/except` 语句来保护视图,以防在数据库中找不到缩短的部分。 如果找到该对象,它会将 1 添加到 `times_followed` 字段,并使用 `HttpResponseRedirect` 函数重定向到与随机代码对应的站点 URL。

更新 URL

创建应用程序的两个视图后,可以通过包含指向 `redirect_url_view` 的路径来创建最终 URL 模式。

与往常一样,您首先导入视图,然后创建 `path` 函数,并将以下参数传递给它:

  • URL 路由
  • 指向路径的视图
  • 路径的名称
'''
Urls for shortener app urlshortener/urls.py
'''

from django.urls import path

# Import the home view
from .views import home_view, redirect_url_view

appname = "shortener"

urlpatterns = [
    # Home view
    path('', home_view, name="home"),
    path('<str:shortened_part>', redirect_url_view, name="redirect"),
]
    

使用此 URL 设置,应用程序的路由如下所示:

  • `localhost:8000/`: 主页视图
  • `localhost:8000/URL-code`: 重定向到