【Python后端开发系列】#1 原生Django开发:从接口基础到数据库操作详解(jwt鉴权、中间件、装饰器)

【Python后端开发系列】#1 原生Django开发:从接口基础到数据库操作详解(jwt鉴权、中间件、装饰器)

RoyKe
1年前发布

原生django详解

基于📕django官方文档解析,用于了解django框架原理与ORM数据库操作概念

相关文档:

1.虚拟环境创建

为保证项目的版本统一,应全程使用虚拟环境进行开发

虚拟环境将创建在对应项目文件夹下 ps:vscode可可视化创建 Venv

虚拟环境使项目不被其余环境干扰,且易于部署

创建环境:python -m venv "name"
激活环境:environment\Scripts\activate
取消激活:deactivate

2.项目与接口创建

2.1 创建项目

1.项目创建
django-admin startproject <projectName>
2.项目测试(仅适用于开发环境)
python manage.py runserver

项目初始结构

<projectName>/
    manage.py 管理 Django 项目的命令行工具
    mysite/ 纯 Python 包,用于配置项目
        __init__.py
        settings.py Django 项目的配置文件
        urls.py  URL 声明应用
        asgi.py 配置asgi服务器的入口
        wsgi.py 配置wsgi服务器的入口

注意:1.在修改setting前需要先将TIME_ZONE设置为 'Asia/Shanghai'(本地时区)

2.对于完全自定义接口的项目,将INSTALLED_APPS中默认引入应用删除(引入应用会在 migrate命令执行时,在数据库中创建表)

2.2 创建应用

1.创建app
python manage.py startapp <appName>
2.初始结构
app/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    views.py
    urls.py 自己创建

2.3 创建视图

1.在views.py里创建视图代码(接口逻辑)

from django.http import HttpResponse
def index(request):
    return HttpResponse("Hello, world. You're at the polls index.")

2.定义应用内url映射配置视图

在app目录内,创建该app的URLconf urls.py

from django.urls import path
from . import views
urlpatterns = [
    path("", views.index, name="index"),
]

3.在项目全局中配置urlconf

from django.contrib import admin
from django.urls import include, path
urlpatterns = [
    path("myapp/", include("myapp.urls")),
    path("admin/", admin.site.urls),
]

include() 运行引用其余URLconfs

4.即可测试运行接口

python manage.py runserver

3.数据库配置

📕数据库API

3.1 基础配置

django默认采用sqlite作为数据库,可在setting中切换

seeting->DATABASES->default字段

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}
  • ENGINE -- 可选值有 'django.db.backends.sqlite3''django.db.backends.postgresql''django.db.backends.mysql''django.db.backends.oracle'其它 可用后端
  • NAME -- 数据库的名称。如果你使用 SQLite,数据库将是你电脑上的一个文件,在这种情况下,NAME 应该是此文件完整的绝对路径,包括文件名。默认值 BASE_DIR / 'db.sqlite3' 将把数据库文件储存在项目的根目录。

如果不使用 SQLite,则必须添加一些额外设置,比如 USER 、 PASSWORD 、 HOST 等等。想了解更多数据库设置方面的内容,请看文档:DATABASES 

3.2 创建数据模型

📕字段类型参考文档

数据模型对于数据库表的每个字段,用于在python中便捷操作数据库、避免sql注入风险并提高数据库更换高效性

模型可以直接在数据库中创建表(如果有权限)

每个字段对应数据库表中的一个字段

应用models.py

from django.db import models

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField("date published")
    def __str__(self):
        return self.question_text # 用于对每个数据对象进行展示

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0                
    def __str__(self):
        return self.choice_text)

未设置主键,会自动创建主键id,并在每次添加数据时自动填写

3.3 激活模型

  • 为这个应用创建数据库 schema(生成 CREATE TABLE 语句)。
  • 创建可以与 Question 和 Choice 对象进行交互的 Python 数据库 API
  • 将app注册到项目里

    将对应app的apps.py里的Config类注册到setting里

INSTALLED_APPS = [
    "myapp.apps.MyappConfig",
]

2.模型迁移

在python中进行数据模型操作

python manage.py makemigrations myapp

3.数据库迁移

基于已有内容对数据库进行迁移

python manage.py migrate

3.4 更改数据模型的三步

  • 编辑 models.py 文件,改变模型。
  • 运行 python manage.py makemigrations为模型的改变生成迁移文件。
  • 运行 python manage.py migrate 来应用数据库迁移

3.5 数据api的操作

1.举例:

from polls.models import Choice, Question
# 替换datetime.datetime.now(),已设置好时区.
>>> from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())
# q是是个数据模型类的实例,代表数据库表中的一行数据
# 将数据对象保存到数据库内.
>>> q.save()
# 完成写入后,即可获取到数据内的该对象ID.
>>> q.id
1
>>> q.question_text
"What's new?
>>> q.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=datetime.timezone.utc)
# 修改该对象所对应数据库中的值
>>> q.question_text = "What's up?"
>>> q.save()

# objects.all() 显示数据库中的所有question数据量.
>>> Question.objects.all()
<QuerySet [<Question: What's up?>]> # 数据对象所显示的名称是我们在数据结构中所定义的str

3.6 常用数据API汇总

📕完整数据库API

📕数据查询条件表达式

  1. 创建对象(插入数据)

    本质: INSERT SQL 语句

from blog.models import Blog
b = Blog(name="Beatles Blog", tagline="All the latest Beatles news.")
b.save()
  1. 修改对象(修改数据)

    本质:UPDATE SQL 语句

# b5是一个已经保存到数据库中的 Blog 数据实例
b5.name = "New name"
b5.save()
  1. 检索对象(提取数据)

    本质:SELECT 语句

    ①检索全部对象

    方法 all() 返回了一个包含数据库中所有对象的 QuerySet 对象

all_entries = Entry.objects.all()

②通过过滤器检索指定对象

filter(**kwargs)

返回一个新的 QuerySet,包含的对象满足给定查询参数;(若不存在,则返回一个空查询集)

空值判断:

if objects.exists():
    ...
else:
    ...

exclude(**kwargs)

返回一个新的 QuerySet,包含的对象 _不_ 满足给定查询参数

查询关键词:field__lookuptype=value (使用双下划线)分割 条件字段和查询条件

📕数据查询条件表达式

注:

1.可以将细化操作链接在一起

Entry.objects.filter(headline__startswith="What").exclude(
...     pub_date__gte=datetime.date.today()
... ).filter(pub_date__gte=datetime.date(2005, 1, 30))

2.每个 QuerySet 都是唯一的,且前后无关联,可独立存储、使用、复用

q1 = Entry.objects.filter(headline__startswith="What")
q2 = q1.exclude(pub_date__gte=datetime.date.today())
q3 = q1.filter(pub_date__gte=datetime.date.today())

3.创建 QuerySet 的过程不涉及任何数据库活动。你可以一直堆叠过滤条件

q = Entry.objects.filter(headline__startswith="What")
q = q.filter(pub_date__lte=datetime.date.today())
q = q.exclude(body_text__icontains="food")
print(q)  

get() 检索单个对象

查询条件表达式与query相同;(若不存在,则抛出DoseNotExist异常)

空值判断:

try:
    obj = Model.objects.get...
except Model.DoseNotExist:
    ...

空值快捷抛出404:

get_object_or_404()

from django.shortcuts import get_object_or_404, render
from .models import Question
# ...
def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, "polls/detail.html", {"question": question})

获取数据对象所有字段字典数据的方法(包括自动生成的ID字段):

# 获取所有字段名称
fields = [field.name for field in user_obj._meta.fields]
# 获取所有字段数据生成字典
user_obj_data = {field: getattr(user_obj, field) for field in fields}

📕完整查询检索语句文档

④限制 QuerySet 条目数

采用python的切片语法实现(不支持负索引)

Entry.objects.all()[5:10]
等价于sql:OFFSET 5 LIMIT 5

要检索 _单个_ 对象而不是列表(例如,SELECT foo FROM bar LIMIT 1),请使用索引而不是切片

Entry.objects.order_by("headline")[0]

⑤字段查询(where条件)

📕数据查询条件表达式

field__lookuptype=value (使用双下划线)

Entry.objects.filter(pub_date__lte="2006-01-01")
等价于
SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';

3.7 旧数据库接入(不创建新数据库)

📕不同类型数据库的连接方法

  1. 设置default数据库

    mysql配置

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.mysql",
        "NAME": "mydatabase",
        "USER": "mydatabaseuser",
        "PASSWORD": "mypassword",
        "HOST": "127.0.0.1",
        "PORT": "5432",
    }
}
  1. 根据数据库自动生成数据模型

    使用Django自带inspectdb实现,导出为指定文件

python manage.py inspectdb > models.py
  1. 从models.py里复制对应的表数据模型到对应的app的models.py里

4 多样化视图

4.1 接收参数的视图

  1. view.py 需要接受参数的视图
def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)

def results(request, question_id):
    response = "You're looking at the results of question %s."
    return HttpResponse(response % question_id)

def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)

app 里的urls.py

from django.urls import path

from . import views

urlpatterns = [
    # ex: /polls/
    path("", views.index, name="index"),
    # ex: /polls/5/
    path("<int:question_id>/", views.detail, name="detail"),
    # ex: /polls/5/results/
    path("<int:question_id>/results/", views.results, name="results"),
    # ex: /polls/5/vote/
    path("<int:question_id>/vote/", views.vote, name="vote"),
]

2.普通链接,通过request.GET和request.POST来获取传递的参数

GET

request.GET.get[param]

POST(json数据解析)

data = json.load(request.body)
param = data['param']

4.2 view常用返回

json数据返回

默认返回状态码为200,可自定义返回状态码

from django.http import HttpResponse, JsonResponse
def ...:
    ...
    return JsonResponse({
        "status": "success"
    }, status=200)

5 测试

6.JWT

6.1 自定义token生成与装饰器(简单)

  1. 安装PyJWT
pip install PyJWT
  1. 创建生成和验证 JWT 的函数

    在项目根目录下创建common文件夹,用于存放被全局app可调用的utils.py

common/
    __init__.py
    utils.py # 公共的实用函数

utils.py

# utils.py
import jwt
from django.conf import settings
from datetime import datetime, timedelta, timezone

def generate_jwt(user):
     payload = {
          'openid': user.openid,
          'exp': datetime.now(timezone.utc) + timedelta(minutes=5),
          'iat': datetime.now(timezone.utc),
     }
     token = jwt.encode(payload, settings.SECRET_KEY, algorithm='HS256')
     return f'Bearer {token}'

def decode_jwt(auth_header):
     if not auth_header.startswith('Bearer '):
        return None

     token = auth_header.split(' ')[1]
     try:
          payload = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'])
          return payload
     except jwt.ExpiredSignatureError:
          return None
     except jwt.InvalidTokenError:
          return None
  1. 创建一个装饰器来保护视图(接口装饰器)

    1.请求头中名称“'AUTHORIZATION”,在获取时,需要加上HTTP_

# decorators.py
from django.http import JsonResponse
from common.utils import decode_jwt

def jwt_required(view_func):
    def _wrapped_view(request, *args, **kwargs):
        auth_header = request.META.get('HTTP_AUTHORIZATION')
        if not auth_header or not auth_header.startswith('Bearer '):
            return JsonResponse({'error': 'Token missing'}, status=401)

        payload = decode_jwt(auth_header)
        if payload is None:
            return JsonResponse({'error': 'Invalid or expired token'}, status=401)

        request.openid = payload['openid']
        return view_func(request, *args, **kwargs)

    return _wrapped_view
  1. 创建登录接口用于获取token
# views.py
from django.http import JsonResponse
from common.utils import generate_jwt

# 登录测试接口
@require_http_methods(["POST"])
def login(request):
    data = json.loads(request.body)
    openid = data['openid']
    try:
        user = User.objects.get(openid=openid)
        token = generate_jwt(user)
        return JsonResponse({
            "status": "success",
            "token": token
        })
    except User.DoesNotExist:
        return JsonResponse({
            "status": "error",
            "msg": "请先注册"
        }, status = 403)
  1. 创建保护视图(使用装饰器)
# views.py
from django.http import HttpResponse, JsonResponse
from django.views.decorators.http import require_http_methods
# 数据模型
from user.models import User
from common.utils import generate_jwt
from common.decorators import jwt_required

# 用户昵称、头像信息修改接口
@require_http_methods(["POST"])
@jwt_required
def userinfo_update(request):
    openid = request.openid
    data = json.loads(request.body)
    avatar = data['avatar']
    username = data['username']
    user_obj = User.objects.get(openid=openid)
    msg = ""
    if (avatar != "") & (avatar is not None) & (avatar != user_obj.avatar):
        user_obj.avatar = avatar
        user_obj.save()
        msg += '头像更新成功'
    if (username != "") & (username is not None) & (username != user_obj.username):
        user_obj.username = username
        user_obj.save()
        msg += '昵称更新成功'
    return JsonResponse({
        "status": "success",
        "msg": msg
        })

6.2 自定义token生成、装饰与存储校验(含数据库)

  1. 安装JWT
  2. 创建token存储模型
  3. 创建生成和验证 JWT 的函数
  4. 创建装饰器
  5. 创建登录获取token接口
  6. 创建保护接口

7.跨域请求

  1. 使用django-cors-headers,可以配置运行的来源请求

安装 django-cors-headers

首先,安装 django-cors-headers

pip install django-cors-headers

配置 Django 设置

接下来,在 Django 项目的 settings.py 中进行以下配置:

  1. 添加 corsheaders 到 INSTALLED_APPS
INSTALLED_APPS = [    ...    'corsheaders',    ...]
  1. 添加 corsheaders.middleware.CorsMiddleware 到中间件列表,并确保它位于其他中间件之前:
MIDDLEWARE = [    'corsheaders.middleware.CorsMiddleware',    'django.middleware.common.CommonMiddleware',    ...]
  1. 配置 CORS 设置

您可以根据需要配置不同的 CORS 设置。以下是一些常见的设置选项:

  • 允许所有来源

    CORS_ALLOW_ALL_ORIGINS = True
  • 只允许特定来源

    CORS_ALLOWED_ORIGINS = [    'https://example.com',    'https://sub.example.com',]
  • 允许所有方法

    CORS_ALLOW_METHODS = [    'DELETE',    'GET',    'OPTIONS',    'PATCH',    'POST',    'PUT',]
  • 允许所有头部

    CORS_ALLOW_HEADERS = [    'accept',    'accept-encoding',    'authorization',    'content-type',    'dnt',    'origin',    'user-agent',    'x-csrftoken',    'x-requested-with',]
  1. 使用 django的中间件和装饰器
# setting.py
MIDDLEWARE = [
    'django.middleware.csrf.CsrdView,iddleware',
]
from django.view.decorators.csrf import csrf_exempt

# 在接口前修饰,即可豁免该接口的所有CSRF检查
@csrf_exempt
def ...
©原创版权声明
THE END
喜欢就支持一下吧
点赞 0 分享 收藏
评论 抢沙发
取消
易航博客