Django 和现代 JS 库 - 后端 (1)
本教程讲的是什么?
《Django与现代JS框架》系列教程将整合Django以及React、Svelte等现代前端解决方案。未来还将计划整合其他框架/库。
项目描述
-
我们将搭建一个 Django 服务器,并创建两个简单的单页应用程序。每个应用程序将使用不同的 JavaScript 库,并且它们都将与 Django 服务器通信。
-
第二部分将使用 webpack 从零开始编写 React 应用。另请注意,不会使用 create-react-app。
-
本教程的最新部分,也是第三部分,将是 Svelte 集成。
本系列共有 2 个项目和 3 篇文章:
-
Django 服务器和 GraphQL API 设置
-
使用 webpack 搭建 React 应用并将其与后端集成。
-
使用 webpack 设置 Svelte 应用程序并将其与我们的后端集成。
需要遵循哪些要求?
-
具备 Python 和 Django 框架的基础知识
-
必须具备Javascript和React的基础知识。
动机和初步信息
Python 是我的第一门编程语言。当时我们在开发一个电影推荐引擎,因为我们希望它是一个单页应用,所以必须将其与 Facebook 的 React 库集成。我对 JavaScript 的了解还处于入门阶段。精通一门陌生的编程语言需要时间。此外,我喜欢 Python 的生态系统,因为它拥有优秀的数据科学库,所以放弃 Python 对我来说从来都不是一个选项。总而言之,将 Django 和 React 集成起来确实花了不少时间。最近我发布了我的开发博客和伊斯坦布尔旅行指南,并编辑更新了我之前的所有文章。在此期间,另一个前端库 Svelte 发布了,这让我非常兴奋。我还添加了一篇关于 Svelte 和 Django 集成的文章。我希望这个系列文章能够帮助新手解决一些问题。
每个项目在生产环境中只会运行一台服务器。
介绍
什么是单页应用程序?
在传统的网页设计中,所有 HTML、CSS 和 JS 代码都由服务器整理并以可渲染的形式传输。浏览器收到代码后,会立即在屏幕上渲染元素。如果用户点击链接,浏览器会向服务器发出另一个请求。服务器会进行所有逻辑运算,并返回另一个可渲染的代码。
在现代客户端应用程序中,一些逻辑操作由 JavaScript 代码处理,这些代码在用户的浏览器中执行。因此,服务器会在首次请求中发送所有网站代码。所以,浏览器需要额外的时间进行首次内容绘制。
除了首次加载之外,客户端应用运行速度更快,用户体验更接近原生应用,因为某些操作会在浏览器端立即执行,并且 I/O 操作可以通过 JavaScript 的异步特性完成。因此,用户看到的仍然是您的应用,而不是空白页面。
浏览器功能强大,能够完成许多令人印象深刻的任务。正因如此,在用户浏览器中处理资源密集型操作不失为一个合适的选择。否则,这些操作会占用服务器大量资源,增加账单费用。
任何在网页上贴上“此页面使用浏览器 X 浏览效果最佳”标签的人,似乎都在怀念互联网出现之前的糟糕旧时代,那时你几乎没有机会阅读在其他电脑、其他文字处理软件或其他网络上编写的文档。——蒂姆·伯纳斯-李
使用 Django 创建后端
第一步:从零开始创建一个 Django 项目
让我们创建一个虚拟环境来进行全新安装。
这个虚拟环境将为这三篇文章提供一个活跃的交流环境。
python3 -m venv tutorial-env
# activate
source ./tutorial-env/bin/activate
安装 Django 及其依赖项
# install our dependencies
pip install ipython django django_extensions django-cors-headers "graphene-django>=2.0"
#create a django project
django-admin startproject djangoproject
# change directory
cd djangoproject
# create templates directory
mkdir templates
# create static folder
mkdir static
# create utils folder for initial data
mkdir utils
2- 配置和运行
更新您的djangoproject /djangoproject/ settings.py文件。新增设置标记为“ New ...”。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
"django_extensions",# New! (useful extension)
'graphene_django', # New! (for graphql communication)
'corsheaders', # New! (for cors request in dev env)
]
# New (for improved interactive shell)
SHELL_PLUS = "ipython"
# New (it allows webpack development server to make cross origin request)
CORS_ORIGIN_WHITELIST = (
'http://localhost:8080',
)
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'corsheaders.middleware.CorsMiddleware', # New Add this
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': (os.path.join(BASE_DIR, 'templates'),), # New
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
#New
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'),
)
在开始项目之前,我们应该先进行数据库迁移。之后,我们将运行服务器并检查其是否正常工作。
# create migration for django-orm
python manage.py migrate
如果一切顺利,Django 服务器将会启动。您可以打开浏览器并访问地址 127.0.0.1:8000,您将看到类似这样的屏幕:
步骤 3:创建电影应用
我们将创建一个电影模型,其中包含电影应该具备的基本字段。
在此之前,我们应该先介绍一下专业方向的选择情况。
为什么海报的输入框是网址(URL)而不是图片输入框?
由于不建议在生产环境中提供静态文件,我们仅使用 URL 字段。从远程获取图片并将其保存到生产存储是另一个话题,我们将在另一篇文章中讨论。因此,我们只会保存海报的 URL,而不是海报本身的图片文件。此外,直接发送静态文件(例如图片)也不是一个好方法。我们会将图片的完整 URL 发送给用户。然后,用户的浏览器会从该 URL 获取图片。
*什么是鼻涕虫?为什么鼻涕虫应该是独一无二的? *
让我举个例子来说明:我最初在cbsofyalioglu[com]/post/django-and-modern-js-libraries-backend上发表了这篇文章。
URL 的最后一部分django-and-modern-js-libraries-backend是帖子的别名,也是一个标识符,使该 URL 与其他帖子页面区分开来。
在本教程的 GraphQL 部分,您会看到我们将使用此 slug 作为查询参数,这意味着我们将根据 slug 执行数据库查询。因此,它应该是唯一的。
我们还可以选择另一个标识符作为 URL 标识符,但很明显,该 URL 将不是人类可读的地址。
搜索引擎索引和排名对于任何面向新用户的网站都至关重要。易读的URL地址不仅对用户有益,搜索引擎指南也建议使用易读的URL地址。此外,谷歌网站管理员指南也建议使用简洁明了的URL结构。
让我们来创建模型并定义它的属性和方法。下一步,我们将向数据库中填充初始记录。因此,我添加了一个负责数据库填充的类方法。
让我们创建一个 Django 应用。这个应用将包含我们的模型。数据库表将根据这个模型创建。API 请求也将基于这个模型。
# create new Django app
python manage.py startapp items
更新设置.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
"django_extensions",
'graphene_django',
'corsheaders',
"items" # New! (make our app will active)
]
打开djangoproject / items/models.py文件,复制以下代码。
# items.models
from django.db import models
class Movie(models.Model):
id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=100)
year = models.IntegerField(null=True)
summary = models.TextField(max_length=5000,null=True)
poster_url = models.URLField(blank=True, null=True)
slug = models.SlugField(max_length=50, null=True,blank =True, unique=True)
# order items in descending order
class Meta:
ordering = ["-year"]
# the method which defines string output of class
def __str__(self):
return self.name
# the method which loads initial data
@classmethod
def import_records(cls, record_list):
for record in record_list:
# create record if id is not exist
if not cls.objects.filter(id=record.get("id")).exists():
new_movie = cls.objects.create(**record)
else:
print(f"Id:{record.get('id')} is already exist.")
print("Import operation done successfully")
# make database migrations
python manage.py makemigrations
python manage.py migrate
步骤四:用初始数据填充数据库
目前我们的数据库中没有任何电影记录。我们将提供少量初始数据来创建一些电影记录。所有数据均来自社区构建的电影数据库(TMDb)。我们将在应用程序中使用这些记录。
首先,在djangoproject /utils文件夹中创建一个名为"initial_data.py" 的文件。之后,您可以将以下数据复制粘贴到这个新文件中。
initial_data = [{
'id': 503919,
'name': 'The Lighthouse',
'year': 2019,
'summary': 'The hypnotic and hallucinatory tale of two lighthouse keepers on a remote and mysterious New England island in the 1890s.',
'slug': 'the-lighthouse-2019',
'poster_url': 'https://image.tmdb.org/t/p/w185/3nk9UoepYmv1G9oP18q6JJCeYwN.jpg'
},{
'id': 475557,
'name': 'Joker',
'year': 2019,
'summary': 'During the 1980s, a failed stand-up comedian is driven insane and turns to a life of crime and chaos in Gotham City while becoming an infamous psychopathic crime figure.',
'slug': 'joker-2019',
'poster_url': 'https://image.tmdb.org/t/p/w185/udDclJoHjfjb8Ekgsd4FDteOkCU.jpg'
},{
'id': 530385,
'name': 'Midsommar',
'year': 2019,
'summary': "A couple travels to Sweden to visit a rural hometown's fabled mid-summer festival. What begins as an idyllic retreat quickly devolves into an increasingly violent and bizarre competition at the hands of a pagan cult.",
'slug': 'midsommar-2019',
'poster_url': 'https://image.tmdb.org/t/p/w185/rXsh4MI6uyVgZBSSzXCfitJnVPy.jpg'
},{
'id': 531428,
'name': 'Portrait of a Lady on Fire',
'year': 2019,
'summary': 'On an isolated island in Bretagne at the end of the eighteenth century, a female painter is obliged to paint a wedding portrait of a young woman.',
'slug': 'portrait-of-a-lady-on-fire-2019',
'poster_url': 'https://image.tmdb.org/t/p/w185/3NTEMlG5mQdIAlKDl3AJG0rX29Z.jpg'
}]
现在,我们将导入并创建新的数据库记录。通常情况下,我们需要打开 Django shell。但是,由django_extensions提供的shell_plus命令功能更强大,因此我们将使用它。它会自动导入我们创建的所有应用。
# open interactive shell
python manage.py shell_plus
# let's check database and verify it's empty
Movie.objects.all()
# prints: <QuerySet []>
# import the records which we took it from github repo
from utils.initial_data import initial_data
# create records in the database
Movie.import_records(initial_data)
# prints 'Import operation done successfully'
# query database and verify it is not empty
Movie.objects.all()
我们的模型和数据库已准备就绪。您可以使用 * quit * 命令关闭 shell 。
下一节将介绍如何创建 GraphQL API。
GraphQL API
在本节中,我们将使用 Graphene(Python 的 GraphQL 框架实现)来构建应用程序的 API 部分。
本节内容包括:
-
创建另一个 Django 应用:我们将把所有 API 配置都放在那里。
-
创建一个包含三个部分的 API 架构:API 模型、解析器和查询。
-
创建 URL 端点:客户端应用程序将向此 URL 地址请求所有信息。
步骤 1 - 创建另一个用于 API 配置的 Django 应用
实际上,由于此应用不会创建或更新任何数据库表,因此没有义务开发另一个应用。但是,为了将所有 API 相关的配置集中在一个地方,我选择了这种方式。
让我们创建第二个后端应用程序。应用程序的名称不一定是“gql”,但如果您设置了其他名称,则稍后还应在settings.py 文件中更改 schema 的名称。
在项目根目录下打开终端。
# create app with the name gql
python manage.py startapp gql
# change directory
cd gql
# create schema.py file
touch schema.py
步骤 2 - 创建 API 架构:API 模型、查询和解析器
考虑到本文的范围,API架构将分为三个部分。
具体如下:
-
* API模型类型: * 一个类,它是电影模型的映射版本。如果响应不是原始类型,您可以基于此类发送响应。
-
*查询: * 客户端应用程序将使用这些查询来处理不同的请求。
-
*解析器: * 这些是字段的响应函数。当客户端请求与查询匹配时,解析器会发挥作用,完成所有逻辑运算,然后将信息发送回客户端。
* A ) * * API 模型类型和解析器*
ModelType 类是现有 Django 模型的映射版本。它是 Django 模型(或数据库)与 API 响应之间的中间层。ModelType 的字段与对应模型的字段相同。我们还可以创建不属于对应模型的自定义字段。
您可以从 Graphene Python 文档中查看其他标量类型。
我们将逐步编写 schema.py 文件。您可以复制粘贴。
import graphene
from items.models import Movie
from graphene_django.types import DjangoObjectType
# api-movie-model
class MovieType(DjangoObjectType):
id = graphene.Int()
name = graphene.String()
year = graphene.Int()
summary = graphene.String()
poster_url = graphene.String()
slug = graphene.String()
# define which model will be the base
class Meta:
model = Movie
# 'self' corresponds to the item of Django model
# like The Lighthouse or Joker
def resolve_id(self, info):
return self.id
def resolve_name(self, info):
return self.name
def resolve_year(self, info):
return self.year
def resolve_summary(self, info):
return self.summary
def resolve_poster_url(self, info):
return self.poster_url
def resolve_slug(self, info):
return self.slug
让我来解释一下上面的代码。
“MovieType”类是Movie模型的映射版本。您可能会注意到所有字段都相同。我们在Meta类中定义了基础模型,因此Movie模型将作为基础模型。
需要说明的是,解析器名称采用蛇形命名法,例如“resolve_poster_url”。但是,当我们编写客户端查询时,它们将采用帕斯卡命名法,例如“posterUrl”。稍后您会看到这一点。
* B)查询和解析器*
客户端应用程序将使用这些查询来处理不同的请求。我们也会在其代码部分编写客户端查询。客户端查询应与服务器端查询相匹配。因此,这部分也定义了前端允许的请求。
为了简单起见,我们只定义两个查询。
-
movie_list 查询(* resolve_movie_list *)返回数据库中的所有电影。
-
电影查询(* resolve_movie)仅在参数(slug *)匹配时才返回特定电影。
让我们在 MovieType 类下方添加这段代码。
class Query(graphene.ObjectType):
movie_list = graphene.List(MovieType)
movie = graphene.Field(MovieType, slug=graphene.String())
def resolve_movie_list(self, info, *_):
# for large lists only query what you need
return Movie.objects.all().only("name", "poster_url", "slug")
def resolve_movie(self, info, slug):
movie_queryset = Movie.objects.filter(slug=slug)
if movie_queryset.exists():
return movie_queryset.first()
schema = graphene.Schema(query=Query)
在最后一行,您会看到一个 schema 对象。这是 API 的根节点。我们需要告诉 Django 服务器使用此对象作为我们的 API schema。为此,请更新 * settings.py * 文件。
# djangoproject/djangoproject/settings.py
# New - Add this part
GRAPHENE= {'SCHEMA': 'gql.schema.schema'}
# MIDDLEWARE = [..]
步骤 3 - 创建 URL 端点
在 REST API 中,我们会为不同的请求定义不同的 URL 端点。GraphQL 的一个优点是,我们只需要定义一个端点,所有请求都将通过该端点进行。
复制以下代码并将其粘贴到djangoproject/djangoproject/ urls.py**** 文件中。
from django.contrib import admin
from django.urls import path
from graphene_django.views import GraphQLView
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import TemplateView
urlpatterns = [
path('admin/', admin.site.urls),
# apiclient on client-side will request this adress later
path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True))),
# index.html file will be our root template. When a user opens our webste,
# this file will be sent by server at first. After then, api requests
# will directed above address.
# (it points to ~/Blog/djr/templates/index.html)
# (currently there is no file, webpack production bundle will come here )
path("", TemplateView.as_view(template_name="index.html")),
]
您注意到我们设置了 ` graphiql=True`。这是 GraphQL 交互式面板。我们可以通过此面板像客户端应用程序一样进行查询。您还可以看到所有查询的详细信息。
现在,请在根文件夹“djangoproject/”下运行服务器。
python manage.py runserver
在浏览器中打开 * 127.0.0.1:8000/graphql * 地址。我们将使用特定的标识符(slug)查询电影。在左侧面板中粘贴此标识符,然后点击 *执行查询* 按钮。
请注意,我们要求字段采用 PascalCase 命名法。(* posterUrl *)
query {
movie(slug:"the-lighthouse-2019"){
id, name, posterUrl
}
}
响应将采用如下所示的 JSON 格式。
{
"data": {
"movie": {
"id": 503919,
"name": "The Lighthouse",
"posterUrl": "https://image.tmdb.org/t/p/w185/3nk9UoepYmv1G9oP18q6JJCeYwN.jpg"
}
}
}
我们的API已准备就绪,可以响应请求。本部分教程到此结束。
现在,我们将制作两个不同的客户端应用程序。请选择其中一个继续。
文章来源:https://dev.to/canburaks/django-and-modern-js-libraries-backend-1-2p9p





