Django 基础知识详细图文教程 13-Django 模型定义与使用 3

来源引用网络知识与某站曹老师视频相互结合学习记录,仅供参考!

Django 模型定义和使用

Django5 对各种数据库提供了很好的支持,包括PostgreSQL、MySQL、SQLite和 Oracle,而且为这些数据库提供了统一的API方法,这些API统称为ORM 框架;就是通过一套接口实现不同的定义,然后使用同一套接口去操作不同的数据库,这个就是 ORM 框架;通过使用Django5内置的ORM框架可以实现数据库连接和读写操作(主要实现数据库连接和读写操作),也包括一些数据库事务。

七、Django ORM 执行 SQL 语句

Django在查询数据时,大多数查询都能使用ORM 提供的API方法,基本上都能实现一些查询,但对于一些复杂的 SQL 查询可能难以使用ORM的API方法实现,几十几百行的ORM 框架实现不了,这时候就需要执行原生的 SQL 语句,因此Django引入了SQL语句的执行方法,Django 提供了3种实现方式;

  • extra:结果集修改器,一种提供额外查询参数的机制;
  • raw:执行原始SQL并返回模型实例对象;
  • execute:直接执行自定义SQL,最纯粹,相当于最底层直接调用;
extra 方法

extra适合用于ORM难以实现的查询条件,将查询条件使用原生SQL语法实现,此方法需要依靠模型对象,在某程度上可防止SQL注入 ;它一共定义了6个参数,每个参数说明如下:

  • select:添加新的查询字段,即新增并定义模型之外的字段;就是模型里面没有定义的字段,在select 写上后它(extra)也是支持的;
  • where:设置查询条件,模型里面的参数;
  • params:如果where设置了字符串格式化%s,那么该参数为where提供数值的;
  • tables:连接其他数据表,实现多表查询。
  • order_by:设置数据的排序方式。
  • select_params:如果select设置字符串格式化%s,那么该参数为select提供数值;设置模型外的参数;

1、修改"djangoProject"项目"helloWorld"应用下的"views.py"配置文件中的"bookList"函数方法,使用BookInfo.objects.extra()方法;

复制代码
# 导包
import datetime
import os

from django.core.paginator import Paginator
from django.db.models import Q, Sum, Avg
from django.http import HttpResponse, HttpResponseNotFound, JsonResponse, StreamingHttpResponse, FileResponse
from django.shortcuts import render, redirect
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView

from helloWorld.forms import StudentForm
from helloWorld.models import StudentInfo, BookInfo, BookTypeInfo


# Create your views here.

# 定义类
class Person:
    # 属性 姓名
    name = None
    # 属性 年龄
    age = None

    def __init__(self, name, age):
        self.name = name
        self.age = age

# 自定义一个index方法,里面要加上request请求对象
def index(request):
    # 传递字符串类型数据,定义字符串类型变量str
    str = 'hello world!'
    # 日期对象date,导包引入import datetime
    date = datetime.datetime.now()
    # 定义字典类型数据
    myDict = {"Jack":"11", "Queen":"12", "King":"13"}
    # 创建一个对象,例如:zhangsan
    zhangsan = Person("张三", 18)
    # 定义列表myList、元组myTuple--列表和元组里面的元素类型没有限制,列表里面可以嵌套列表,元组里面可以嵌套元组
    myList = [1, 'apple', 3.1415926, True, [5, 6], (7, 8), {9, 10}]
    myTuple = (1, 'apple', 3.1415926, True, [5, 6], (7, 8), {9, 10})
    # 变量content_value是字典格式
    content_value = {"content_msg": str, "msg2": myDict, "msg3": zhangsan, "msg4": myList, "msg5": myTuple, "date": date}
    # context= 传递的本身要求格式是一个字典类型
    return render(request, 'index.html', context=content_value)

# 定义index_id方法,里面第一个参数request请求对象,第二个参数 id
def index_id(request, id):
    # 判断 id是否为0
    if id == 0:
        # 导包引入redirect模块,如果id等于0的话,重定向到一个目标静态地址
        return redirect("/static/error.html")
    else:
        # 使用HttpResponse直接返回内容--因为id是int类型,里面得使用str()进行转换
        return HttpResponse("id是" + str(id) + "的某某平台系统页面")

# 定义index_id方法,第一个参数request请求对象,里面继续跟多个路由变量"year, month, day, id"
def index_id02(request, year, month, day, id):
    # 使用HttpResponse直接返回内容--因为id是int类型,里面得使用str()进行转换
    return HttpResponse(str(year) + "/" + str(month) + "/" + str(day) + "/" +"id是" + str(id) + "的某某平台系统页面")

# 定义index_id方法,第一个参数request请求对象,后面跟多个路由变量"year, month, day"--注意,使用正则后不能与"id"再混合使用了
def index_id03(request, year, month, day):
    # 使用HttpResponse直接返回内容--因为id是int类型,里面得使用str()进行转换
    return HttpResponse(str(year) + '/' + str(month) + '/' + str(day) + "使用正则路由后的某某平台系统页面")


# 定义目标文件路径
file_path = "D:\\works\\djangoProject\\datas\\IDM.rar"

# 定义download_file1下载方法,请求需要带上request(所有的请求都要带上request)
def download_file1(request):
    # 定义文件--使用open函数方法以"rb"二进制读取模式打开文件--然后返回一个file文件对象
    file = open(file_path, 'rb')
    # 创建构造好了HttpResponse对象之后返回给response
    response = HttpResponse(file)
    # 指定文件类型
    response['Content-Type'] = 'application/rar-compressed'
    # 扩展--指定附件及文件名字(可以动态拼接filename,并不建议使用原始名称)
    response['Content-Disposition'] = 'attachment; filename=file1.rar'
    return response

def download_file2(request):
    file = open(file_path, 'rb')
    # 创建构造好了StreamingHttpResponse对象之后返回给response
    response = StreamingHttpResponse(file)
    response['Content-Type'] = 'application/rar-compressed'
    response['Content-Disposition'] = 'attachment; filename=file2.rar'
    return response

def download_file3(request):
    file = open(file_path, 'rb')
    # 创建构造好了FileResponse对象之后返回给response
    response = FileResponse(file)
    response['Content-Type'] = 'application/rar-compressed'
    response['Content-Disposition'] = 'attachment; filename=file3.rar'
    return response

def get_test(request):
    """
    get 请求测试
    :param request:
    :return:
    """
    print(f'打印请求方式request.method:{request.method}')
    print(f'打印常用属性request.content_type:{request.content_type}')
    print(f'打印常用属性request.content_params:{request.content_params}')
    print(f'打印常用属性request.COOKIES:{request.COOKIES}')
    print(f'打印常用属性request.scheme:{request.scheme}')
    print(f'打印常用方法request.is_secure:{request.is_secure()}')
    print(f'打印常用方法request.get_host:{request.get_host()}')
    print(f'打印常用方法request.get_full_path:{request.get_full_path()}')

    print(f'使用request.GET.get("name")方法获取对应key值参数:{request.GET.get("name")}' )
    print(f'使用request.GET.get("pwd")方法获取对应key值参数:{request.GET.get("pwd")}')
    print(f'使用request.GET.get("dev")方法获取不存在的参数,返回值是'None':{request.GET.get("dev")}')
    print(f'request.GET.get("prod", "555")获取的prod参数是不存在的,设置返回值'555':{request.GET.get("prod", "555")}')

    return HttpResponse("hello world!http get ok!")

def post_test(request):
    """
    post 请求测试方法
    :param request:
    :return:
    """
    print(f'打印请求方式request.method:{request.method}')
    print(f'使用request.POST.get("name")方法获取对应key值参数:{request.POST.get("name")}')
    print(f'使用request.POST.get("pwd")方法获取对应key值参数:{request.POST.get("pwd")}')
    print(f'使用request.POST.get("dev")方法获取不存在的参数,返回值是'None':{request.POST.get("dev")}')
    print(f'request.POST.get("prod", "556")获取的prod参数是不存在的,设置返回值'556':{request.POST.get("prod", "556")}')
    return HttpResponse("hello world!http post ok!")

def to_login(request):
    """
    跳转到登录页面
    :param request:
    :return:
    """
    # 跳转到登录页面可直接使用渲染render()方法
    return render(request, 'login.html')

def login(request):
    """
    登录到主页面
    :param request:
    :return:
    """
    # 首先获取用户名和密码
    user_name = request.POST.get('user_name')
    pwd = request.POST.get('pwd')
    # 模拟用户登录(暂不连接数据库)
    if user_name == "python3" and pwd == "123456":
        # session中存一个用户名
        request.session['currentUserName'] = user_name
        print(f'代码脚本中session获取:{request.session['currentUserName']}')
        # 获取HttpResponse
        response = render(request, 'main.html')
        # 通过response.set_Cookie()方法来设置cookie
        response.set_cookie('remember_me', True)
        return response
    else:
        # 定义变量content_value为报错提示信息,里面是键值对格式的对象
        content_value = {"error_info":"用户名或者密码错误!"}
        return render(request, 'login.html', context=content_value)

def to_upload(request):
    """
    跳转到上传文件页面
    :param request:
    :return:
    """
    return render(request, 'upload.html')

def upload(request):
    """
    上传文件处理
    :param request:
    :return:
    """
    # 获取上传的文件,如果没有文件,就默认为None(如果不写指定的None,也是默认None)
    myFile = request.FILES.get("myfile", None)
    # 判断如果是有myFile的情况,就是说明有文件
    if myFile:
        # 打开特定的文件进行二进制的写操作--上传文件路径烦请自行创建(例如:在项目datas路径下新建"myFile"目录文件)
        f = open(os.path.join("D:\\works\\djangoProject\\datas\\myFile", myFile.name), "wb+")
        # 分块写入文件
        for chunk in myFile.chunks():
            f.write(chunk)
        f.close()
        return HttpResponse("文件上传成功!")
    else:
        return HttpResponse("没有发现可以上传的文件!")

# 定义List类,继续继承ListView(导包引入 from django.views.generic import ListView)
class List(ListView):
    # 设置模版文件
    template_name = 'student/list.html'
    # 设置模型外的数据--例如:指定标题
    extra_context = {'title': '学生信息列表'}
    # 定义数据集(查询结果集)--StudentInfo(导入 from helloWorld.models import StudentInfo)
    queryset = StudentInfo.objects.all()
    # 每页展示数据的条数(自行设置)
    paginate_by = 5
    # 设置上下文对象名称(其实就是一个列表的对象),建议使用自定义名称,不然就默认为 objectlist
    context_object_name = 'student_list'

# 定义Detail类,继续继承DetailView(导包引入 from django.views.generic import DetailView)
class Detail(DetailView):
    # 设置模版文件
    template_name = 'student/detail.html'
    # 设置模型外的数据--例如:指定标题
    extra_context = {'title': '学生信息详情'}
    # 设置查询模型--指定model(不然无法查询数据)
    model = StudentInfo
    # 设置上下文对象名称
    context_object_name = 'student'

    # 指定URL中用于获取对象的唯一标识符的参数名称,默认为'pk';
    # 如若在urls.py文件中自定义了名称,则需要添加对应pk_url_kwarg参数为自定义名称
    # pk_url_kwarg = 'id'

# 定义Create类,继续继承CreateView(导包引入 from django.views.generic import CreateView)
class Create(CreateView):
    # 设置模版文件,名称修改为create(有的名称叫"add"也行)
    template_name = 'student/create.html'
    # 设置模型外的数据--例如:指定标题"添加"或者"新增"都行
    extra_context = {'title': '学生信息新增'}
    # 因为是表单,所以需要使用form_class参数来指定form表单
    form_class = StudentForm
    # 执行成功后跳转地址(跳转地址可以是一个新的页面,也可以进入一个列表页面)
    success_url = '/student/list'

# 定义Update类,继续继承UpdateView(导包引入 from django.views.generic import UpdateView)
class Update(UpdateView):
    # 设置模版文件,名称修改为update(自定义)
    template_name = 'student/update.html'
    # 设置模型外的数据--例如:指定标题"编辑"或者"修改"都行
    extra_context = {'title': '学生信息编辑'}
    # 设置查询模型--指定model(不然无法查询数据)--传来一个组件"pk"会自动的去查数据、封装对象、显示到页面上
    model = StudentInfo
    # 因为是表单,所以需要使用form_class参数来指定form表单
    form_class = StudentForm
    # 执行成功后跳转地址(跳转地址可以是一个新的页面,也可以进入一个列表页面)
    success_url = '/student/list'

# 定义Delete类,继续继承DeleteView(导包引入 from django.views.generic import DeleteView)
class   Delete(DeleteView):
    # 设置模版文件,名称修改为delete(自定义)
    template_name = 'student/delete.html'
    # 设置模型外的数据--例如:指定标题"删除"或者"修改"都行
    extra_context = {'title': '学生信息删除'}
    # 设置上下文对象名称
    context_object_name = 'student'
    # 设置查询模型--指定model(不然无法查询数据)--传来一个组件"pk"会自动的去查数据、封装对象、显示到页面上
    model = StudentInfo
    # 执行成功后跳转地址(跳转地址可以是一个新的页面,也可以进入一个列表页面)
    success_url = '/student/list'

# 自定义跳转子类模板页面方法
def to_course(request):
    """
    跳转到子类模板页面
    :param request:
    :return:
    """
    return render(request, 'course.html')

# 定义bookList方法
def bookList(request):
    """
    图书列表查询
    """
    # 查询显示所有数据
    # bookList = BookInfo.objects.all()

    # 使用BookInfo.objects.extra()方法,里面使用where加参数(元组格式),在params加上具体的参数,也是一个元组
    # 如果是模型外的参数(models.py里面没有定义的参数),可以使用BookInfo.objects.select()方法
    bookList = BookInfo.objects.extra(where=["price>%s"], params=[90])
    content_value = {"title": "图书列表", "bookList": bookList}
    return render(request, 'book/list.html', context=content_value)

def bookList2(request):
    """
    多表查询,有正向查询和反向查询
    :param request:
    :return:
    """
    # 正向查询(经常使用)--多的一端BookInfo条件id=2且只查询一条数据,返回图书book并指定类型BookInfo
    book: BookInfo = BookInfo.objects.filter(id=2).first()
    # 能够输出book的所有信息,也能输出所关联的bookType.bookTypeName数据信息
    print(book.bookName, book.bookType.bookTypeName)

    # 反向查询(很少使用)--(导包引入from helloWorld.models import BookTypeInfo)
    # 一的一端BookTypeInfo条件id=1且只查询一条数据,返回图书类别bookType并指定类型BookTypeInfo
    bookType: BookTypeInfo = BookTypeInfo.objects.filter(id=1).first()
    # 输出读取关联的这个图书类别下的所有图书的数据集中第一条数据,固定语法bookinfo_set
    print(bookType.bookinfo_set.first().bookName)
    # 显示这个类别下的所有数据,返回的结果是一个QuerySet数据集,这个类型下有两条数据,
    print(bookType.bookinfo_set.all())
    # 把QuerySet这个数据集转发到页面上就可以进行遍历了
    content_value = {"title": "图书列表", "bookList": bookType.bookinfo_set.all()}
    return render(request, 'book/list.html', context=content_value)

def preAdd(request):
    """
    预处理,添加操作
    :param request:
    :return:
    """
    # 预处理操作需要先找到所有数据
    bookTypeList = BookTypeInfo.objects.all()
    print(f'可自行打印查看bookTypeList是否查询出所有数据:{bookTypeList}')
    # 预处理操作之后要返回到图书添加页面
    content_value = {"title": "图书添加", "bookTypeList": bookTypeList}
    return render(request, 'book/add.html', context=content_value)

def add(request):
    """
    图书添加
    :param request:
    :return:
    """
    # 封装对象
    book = BookInfo()
    # 添加数据
    book.bookName = request.POST.get("bookName")
    book.publishDate = request.POST.get("publishDate")
    book.bookType_id = request.POST.get("bookType_id")
    book.price = request.POST.get("price")
    # 因为是模板,所以直接使用save()方法
    book.save()
    # 数据添加完成之后,可以获取新增数据的主键id
    print(f'数据添加完成之后,获取新增数据的主键id:{book.id}')
    # 添加完成之后,需要返回到列表,直接return返回那个对象
    return bookList(request)

# 修改预处理,定义修改操作方法;页面需要传一个id来确定是要修改哪一条记录
def preUpdate(request, id):
    """
    预处理,修改操作
    :param request:
    :return:
    """
    print("通过path里面的变量,页面需要传一个id来确定是要修改哪一条记录:", id)
    # 根据传过来的id去查询数据,并返回一个book对象
    book = BookInfo.objects.get(id=id)
    print(f'可自行打印查看返回的book对象:{book}')
    bookTypeList = BookTypeInfo.objects.all()
    print(bookTypeList)
    # book对象也需要传到页面上去,到时候页面上就能够取值了
    content_value = {"title": "图书修改", "bookTypeList": bookTypeList, "book": book}
    return render(request, 'book/edit.html', context=content_value)

def update(request):
    """
    图书修改
    :param request:
    :return:
    """
    book = BookInfo()
    book.id = request.POST.get("id")
    book.bookName = request.POST.get("bookName")
    book.publishDate = request.POST.get("publishDate")
    book.bookType_id = request.POST.get("bookType_id")
    book.price = request.POST.get("price")
    book.save()
    return bookList(request)

def delete(request, id):
    """
    图书删除
    :param request:
    :return:
    """
    # 删除指定id数据
    BookInfo.objects.get(id=id).delete()
    # 根据条件删除多条数据
    # BookInfo.objects.filter(price__gte=100).delete()
    # 删除所有数据
    # BookInfo.objects.all().delete()
    return bookList(request)

2、重新运行项目,浏览器输入"http://127.0.0.1:8000/book/list"打开图书列表页面测试验证,可以发现已经成功过滤掉不符合条件的数据,只显示(where="price\>%s", params=90)图书价格大于 90 的数据记录;

raw 方法

raw只能实现数据查询操作,并且也要依靠模型对象,但是它直接执行的是 SQL 语句,这个模型对象功能比较强大,可根据 SQL 语句查询出外键关联数据信息记录,它一共定义了4个参数,每个参数说明如下:

  • raw_query:直接是执行 SQL语句;
  • params:如果raw_query设置字符串格式化%s,那么该参数为raw_query提供数值;
  • translations:为查询的字段设置别名;
  • using:数据库对象,即 Django 所连接的数据库;可以不写,因为本来就已经连接了;

3、修改"djangoProject"项目"helloWorld"应用下的"views.py"配置文件中的"bookList"函数方法,使用BookInfo.objects.raw()方法,需要注意里面的 SQL 语句的语法千万别写错了;

复制代码
# 导包
import datetime
import os

from django.core.paginator import Paginator
from django.db.models import Q, Sum, Avg
from django.http import HttpResponse, HttpResponseNotFound, JsonResponse, StreamingHttpResponse, FileResponse
from django.shortcuts import render, redirect
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView

from helloWorld.forms import StudentForm
from helloWorld.models import StudentInfo, BookInfo, BookTypeInfo


# Create your views here.

# 定义类
class Person:
    # 属性 姓名
    name = None
    # 属性 年龄
    age = None

    def __init__(self, name, age):
        self.name = name
        self.age = age

# 自定义一个index方法,里面要加上request请求对象
def index(request):
    # 传递字符串类型数据,定义字符串类型变量str
    str = 'hello world!'
    # 日期对象date,导包引入import datetime
    date = datetime.datetime.now()
    # 定义字典类型数据
    myDict = {"Jack":"11", "Queen":"12", "King":"13"}
    # 创建一个对象,例如:zhangsan
    zhangsan = Person("张三", 18)
    # 定义列表myList、元组myTuple--列表和元组里面的元素类型没有限制,列表里面可以嵌套列表,元组里面可以嵌套元组
    myList = [1, 'apple', 3.1415926, True, [5, 6], (7, 8), {9, 10}]
    myTuple = (1, 'apple', 3.1415926, True, [5, 6], (7, 8), {9, 10})
    # 变量content_value是字典格式
    content_value = {"content_msg": str, "msg2": myDict, "msg3": zhangsan, "msg4": myList, "msg5": myTuple, "date": date}
    # context= 传递的本身要求格式是一个字典类型
    return render(request, 'index.html', context=content_value)

# 定义index_id方法,里面第一个参数request请求对象,第二个参数 id
def index_id(request, id):
    # 判断 id是否为0
    if id == 0:
        # 导包引入redirect模块,如果id等于0的话,重定向到一个目标静态地址
        return redirect("/static/error.html")
    else:
        # 使用HttpResponse直接返回内容--因为id是int类型,里面得使用str()进行转换
        return HttpResponse("id是" + str(id) + "的某某平台系统页面")

# 定义index_id方法,第一个参数request请求对象,里面继续跟多个路由变量"year, month, day, id"
def index_id02(request, year, month, day, id):
    # 使用HttpResponse直接返回内容--因为id是int类型,里面得使用str()进行转换
    return HttpResponse(str(year) + "/" + str(month) + "/" + str(day) + "/" +"id是" + str(id) + "的某某平台系统页面")

# 定义index_id方法,第一个参数request请求对象,后面跟多个路由变量"year, month, day"--注意,使用正则后不能与"id"再混合使用了
def index_id03(request, year, month, day):
    # 使用HttpResponse直接返回内容--因为id是int类型,里面得使用str()进行转换
    return HttpResponse(str(year) + '/' + str(month) + '/' + str(day) + "使用正则路由后的某某平台系统页面")


# 定义目标文件路径
file_path = "D:\\works\\djangoProject\\datas\\IDM.rar"

# 定义download_file1下载方法,请求需要带上request(所有的请求都要带上request)
def download_file1(request):
    # 定义文件--使用open函数方法以"rb"二进制读取模式打开文件--然后返回一个file文件对象
    file = open(file_path, 'rb')
    # 创建构造好了HttpResponse对象之后返回给response
    response = HttpResponse(file)
    # 指定文件类型
    response['Content-Type'] = 'application/rar-compressed'
    # 扩展--指定附件及文件名字(可以动态拼接filename,并不建议使用原始名称)
    response['Content-Disposition'] = 'attachment; filename=file1.rar'
    return response

def download_file2(request):
    file = open(file_path, 'rb')
    # 创建构造好了StreamingHttpResponse对象之后返回给response
    response = StreamingHttpResponse(file)
    response['Content-Type'] = 'application/rar-compressed'
    response['Content-Disposition'] = 'attachment; filename=file2.rar'
    return response

def download_file3(request):
    file = open(file_path, 'rb')
    # 创建构造好了FileResponse对象之后返回给response
    response = FileResponse(file)
    response['Content-Type'] = 'application/rar-compressed'
    response['Content-Disposition'] = 'attachment; filename=file3.rar'
    return response

def get_test(request):
    """
    get 请求测试
    :param request:
    :return:
    """
    print(f'打印请求方式request.method:{request.method}')
    print(f'打印常用属性request.content_type:{request.content_type}')
    print(f'打印常用属性request.content_params:{request.content_params}')
    print(f'打印常用属性request.COOKIES:{request.COOKIES}')
    print(f'打印常用属性request.scheme:{request.scheme}')
    print(f'打印常用方法request.is_secure:{request.is_secure()}')
    print(f'打印常用方法request.get_host:{request.get_host()}')
    print(f'打印常用方法request.get_full_path:{request.get_full_path()}')

    print(f'使用request.GET.get("name")方法获取对应key值参数:{request.GET.get("name")}' )
    print(f'使用request.GET.get("pwd")方法获取对应key值参数:{request.GET.get("pwd")}')
    print(f'使用request.GET.get("dev")方法获取不存在的参数,返回值是'None':{request.GET.get("dev")}')
    print(f'request.GET.get("prod", "555")获取的prod参数是不存在的,设置返回值'555':{request.GET.get("prod", "555")}')

    return HttpResponse("hello world!http get ok!")

def post_test(request):
    """
    post 请求测试方法
    :param request:
    :return:
    """
    print(f'打印请求方式request.method:{request.method}')
    print(f'使用request.POST.get("name")方法获取对应key值参数:{request.POST.get("name")}')
    print(f'使用request.POST.get("pwd")方法获取对应key值参数:{request.POST.get("pwd")}')
    print(f'使用request.POST.get("dev")方法获取不存在的参数,返回值是'None':{request.POST.get("dev")}')
    print(f'request.POST.get("prod", "556")获取的prod参数是不存在的,设置返回值'556':{request.POST.get("prod", "556")}')
    return HttpResponse("hello world!http post ok!")

def to_login(request):
    """
    跳转到登录页面
    :param request:
    :return:
    """
    # 跳转到登录页面可直接使用渲染render()方法
    return render(request, 'login.html')

def login(request):
    """
    登录到主页面
    :param request:
    :return:
    """
    # 首先获取用户名和密码
    user_name = request.POST.get('user_name')
    pwd = request.POST.get('pwd')
    # 模拟用户登录(暂不连接数据库)
    if user_name == "python3" and pwd == "123456":
        # session中存一个用户名
        request.session['currentUserName'] = user_name
        print(f'代码脚本中session获取:{request.session['currentUserName']}')
        # 获取HttpResponse
        response = render(request, 'main.html')
        # 通过response.set_Cookie()方法来设置cookie
        response.set_cookie('remember_me', True)
        return response
    else:
        # 定义变量content_value为报错提示信息,里面是键值对格式的对象
        content_value = {"error_info":"用户名或者密码错误!"}
        return render(request, 'login.html', context=content_value)

def to_upload(request):
    """
    跳转到上传文件页面
    :param request:
    :return:
    """
    return render(request, 'upload.html')

def upload(request):
    """
    上传文件处理
    :param request:
    :return:
    """
    # 获取上传的文件,如果没有文件,就默认为None(如果不写指定的None,也是默认None)
    myFile = request.FILES.get("myfile", None)
    # 判断如果是有myFile的情况,就是说明有文件
    if myFile:
        # 打开特定的文件进行二进制的写操作--上传文件路径烦请自行创建(例如:在项目datas路径下新建"myFile"目录文件)
        f = open(os.path.join("D:\\works\\djangoProject\\datas\\myFile", myFile.name), "wb+")
        # 分块写入文件
        for chunk in myFile.chunks():
            f.write(chunk)
        f.close()
        return HttpResponse("文件上传成功!")
    else:
        return HttpResponse("没有发现可以上传的文件!")

# 定义List类,继续继承ListView(导包引入 from django.views.generic import ListView)
class List(ListView):
    # 设置模版文件
    template_name = 'student/list.html'
    # 设置模型外的数据--例如:指定标题
    extra_context = {'title': '学生信息列表'}
    # 定义数据集(查询结果集)--StudentInfo(导入 from helloWorld.models import StudentInfo)
    queryset = StudentInfo.objects.all()
    # 每页展示数据的条数(自行设置)
    paginate_by = 5
    # 设置上下文对象名称(其实就是一个列表的对象),建议使用自定义名称,不然就默认为 objectlist
    context_object_name = 'student_list'

# 定义Detail类,继续继承DetailView(导包引入 from django.views.generic import DetailView)
class Detail(DetailView):
    # 设置模版文件
    template_name = 'student/detail.html'
    # 设置模型外的数据--例如:指定标题
    extra_context = {'title': '学生信息详情'}
    # 设置查询模型--指定model(不然无法查询数据)
    model = StudentInfo
    # 设置上下文对象名称
    context_object_name = 'student'

    # 指定URL中用于获取对象的唯一标识符的参数名称,默认为'pk';
    # 如若在urls.py文件中自定义了名称,则需要添加对应pk_url_kwarg参数为自定义名称
    # pk_url_kwarg = 'id'

# 定义Create类,继续继承CreateView(导包引入 from django.views.generic import CreateView)
class Create(CreateView):
    # 设置模版文件,名称修改为create(有的名称叫"add"也行)
    template_name = 'student/create.html'
    # 设置模型外的数据--例如:指定标题"添加"或者"新增"都行
    extra_context = {'title': '学生信息新增'}
    # 因为是表单,所以需要使用form_class参数来指定form表单
    form_class = StudentForm
    # 执行成功后跳转地址(跳转地址可以是一个新的页面,也可以进入一个列表页面)
    success_url = '/student/list'

# 定义Update类,继续继承UpdateView(导包引入 from django.views.generic import UpdateView)
class Update(UpdateView):
    # 设置模版文件,名称修改为update(自定义)
    template_name = 'student/update.html'
    # 设置模型外的数据--例如:指定标题"编辑"或者"修改"都行
    extra_context = {'title': '学生信息编辑'}
    # 设置查询模型--指定model(不然无法查询数据)--传来一个组件"pk"会自动的去查数据、封装对象、显示到页面上
    model = StudentInfo
    # 因为是表单,所以需要使用form_class参数来指定form表单
    form_class = StudentForm
    # 执行成功后跳转地址(跳转地址可以是一个新的页面,也可以进入一个列表页面)
    success_url = '/student/list'

# 定义Delete类,继续继承DeleteView(导包引入 from django.views.generic import DeleteView)
class   Delete(DeleteView):
    # 设置模版文件,名称修改为delete(自定义)
    template_name = 'student/delete.html'
    # 设置模型外的数据--例如:指定标题"删除"或者"修改"都行
    extra_context = {'title': '学生信息删除'}
    # 设置上下文对象名称
    context_object_name = 'student'
    # 设置查询模型--指定model(不然无法查询数据)--传来一个组件"pk"会自动的去查数据、封装对象、显示到页面上
    model = StudentInfo
    # 执行成功后跳转地址(跳转地址可以是一个新的页面,也可以进入一个列表页面)
    success_url = '/student/list'

# 自定义跳转子类模板页面方法
def to_course(request):
    """
    跳转到子类模板页面
    :param request:
    :return:
    """
    return render(request, 'course.html')

# 定义bookList方法
def bookList(request):
    """
    图书列表查询
    """
    # 查询显示所有数据
    # bookList = BookInfo.objects.all()

    # 使用BookInfo.objects.extra()方法,里面使用where加参数(元组格式),在params加上具体的参数,也是一个元组
    # 如果是模型外的参数(models.py里面没有定义的参数),可以使用BookInfo.objects.select()方法
    # bookList = BookInfo.objects.extra(where=["price>%s"], params=[90])

    # # 使用BookInfo.objects.raw()方法,直接写一个SQL语句
    bookList = BookInfo.objects.raw('SELECT * FROM t_book where price>%s', params=[90])
    content_value = {"title": "图书列表", "bookList": bookList}
    return render(request, 'book/list.html', context=content_value)

def bookList2(request):
    """
    多表查询,有正向查询和反向查询
    :param request:
    :return:
    """
    # 正向查询(经常使用)--多的一端BookInfo条件id=2且只查询一条数据,返回图书book并指定类型BookInfo
    book: BookInfo = BookInfo.objects.filter(id=2).first()
    # 能够输出book的所有信息,也能输出所关联的bookType.bookTypeName数据信息
    print(book.bookName, book.bookType.bookTypeName)

    # 反向查询(很少使用)--(导包引入from helloWorld.models import BookTypeInfo)
    # 一的一端BookTypeInfo条件id=1且只查询一条数据,返回图书类别bookType并指定类型BookTypeInfo
    bookType: BookTypeInfo = BookTypeInfo.objects.filter(id=1).first()
    # 输出读取关联的这个图书类别下的所有图书的数据集中第一条数据,固定语法bookinfo_set
    print(bookType.bookinfo_set.first().bookName)
    # 显示这个类别下的所有数据,返回的结果是一个QuerySet数据集,这个类型下有两条数据,
    print(bookType.bookinfo_set.all())
    # 把QuerySet这个数据集转发到页面上就可以进行遍历了
    content_value = {"title": "图书列表", "bookList": bookType.bookinfo_set.all()}
    return render(request, 'book/list.html', context=content_value)

def preAdd(request):
    """
    预处理,添加操作
    :param request:
    :return:
    """
    # 预处理操作需要先找到所有数据
    bookTypeList = BookTypeInfo.objects.all()
    print(f'可自行打印查看bookTypeList是否查询出所有数据:{bookTypeList}')
    # 预处理操作之后要返回到图书添加页面
    content_value = {"title": "图书添加", "bookTypeList": bookTypeList}
    return render(request, 'book/add.html', context=content_value)

def add(request):
    """
    图书添加
    :param request:
    :return:
    """
    # 封装对象
    book = BookInfo()
    # 添加数据
    book.bookName = request.POST.get("bookName")
    book.publishDate = request.POST.get("publishDate")
    book.bookType_id = request.POST.get("bookType_id")
    book.price = request.POST.get("price")
    # 因为是模板,所以直接使用save()方法
    book.save()
    # 数据添加完成之后,可以获取新增数据的主键id
    print(f'数据添加完成之后,获取新增数据的主键id:{book.id}')
    # 添加完成之后,需要返回到列表,直接return返回那个对象
    return bookList(request)

# 修改预处理,定义修改操作方法;页面需要传一个id来确定是要修改哪一条记录
def preUpdate(request, id):
    """
    预处理,修改操作
    :param request:
    :return:
    """
    print("通过path里面的变量,页面需要传一个id来确定是要修改哪一条记录:", id)
    # 根据传过来的id去查询数据,并返回一个book对象
    book = BookInfo.objects.get(id=id)
    print(f'可自行打印查看返回的book对象:{book}')
    bookTypeList = BookTypeInfo.objects.all()
    print(bookTypeList)
    # book对象也需要传到页面上去,到时候页面上就能够取值了
    content_value = {"title": "图书修改", "bookTypeList": bookTypeList, "book": book}
    return render(request, 'book/edit.html', context=content_value)

def update(request):
    """
    图书修改
    :param request:
    :return:
    """
    book = BookInfo()
    book.id = request.POST.get("id")
    book.bookName = request.POST.get("bookName")
    book.publishDate = request.POST.get("publishDate")
    book.bookType_id = request.POST.get("bookType_id")
    book.price = request.POST.get("price")
    book.save()
    return bookList(request)

def delete(request, id):
    """
    图书删除
    :param request:
    :return:
    """
    # 删除指定id数据
    BookInfo.objects.get(id=id).delete()
    # 根据条件删除多条数据
    # BookInfo.objects.filter(price__gte=100).delete()
    # 删除所有数据
    # BookInfo.objects.all().delete()
    return bookList(request)

4、运行项目,浏览器输入"http://127.0.0.1:8000/book/list"打开图书列表页面自行测试验证;

execute 方法

当有几百行 SQL 语句,甚至需要执行事务等操作时,就要使用 execute 方法来调用存储过程之类的,只能使用此方法,这个就是原生的一个实现;execute的语法,它执行SQL语句无须经过Django的ORM框架;大家知道Django连接数据库需要借助第三方模块实现连接过程,例如 MySQL的mysqlclient模块和SQLite 的sqlite3 模块等,这些模块连接数据库之后,可通过游标的方式来执行SQL语句,而 execute就是使用这种方式执行SQL语句;但是这不是直接使用原生 SQL 语句,它(Django)是又封装了一层通用的;

5、修改"djangoProject"项目"helloWorld"应用下的"views.py"配置文件中的"bookList"函数方法,具体详情可查看每行代码注释说明;

复制代码
# 导包
import datetime
import os

from django.core.paginator import Paginator
from django.db import connection
from django.db.backends.utils import CursorDebugWrapper
from django.db.models import Q, Sum, Avg
from django.http import HttpResponse, HttpResponseNotFound, JsonResponse, StreamingHttpResponse, FileResponse
from django.shortcuts import render, redirect
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView

from helloWorld.forms import StudentForm
from helloWorld.models import StudentInfo, BookInfo, BookTypeInfo


# Create your views here.

# 定义类
class Person:
    # 属性 姓名
    name = None
    # 属性 年龄
    age = None

    def __init__(self, name, age):
        self.name = name
        self.age = age

# 自定义一个index方法,里面要加上request请求对象
def index(request):
    # 传递字符串类型数据,定义字符串类型变量str
    str = 'hello world!'
    # 日期对象date,导包引入import datetime
    date = datetime.datetime.now()
    # 定义字典类型数据
    myDict = {"Jack":"11", "Queen":"12", "King":"13"}
    # 创建一个对象,例如:zhangsan
    zhangsan = Person("张三", 18)
    # 定义列表myList、元组myTuple--列表和元组里面的元素类型没有限制,列表里面可以嵌套列表,元组里面可以嵌套元组
    myList = [1, 'apple', 3.1415926, True, [5, 6], (7, 8), {9, 10}]
    myTuple = (1, 'apple', 3.1415926, True, [5, 6], (7, 8), {9, 10})
    # 变量content_value是字典格式
    content_value = {"content_msg": str, "msg2": myDict, "msg3": zhangsan, "msg4": myList, "msg5": myTuple, "date": date}
    # context= 传递的本身要求格式是一个字典类型
    return render(request, 'index.html', context=content_value)

# 定义index_id方法,里面第一个参数request请求对象,第二个参数 id
def index_id(request, id):
    # 判断 id是否为0
    if id == 0:
        # 导包引入redirect模块,如果id等于0的话,重定向到一个目标静态地址
        return redirect("/static/error.html")
    else:
        # 使用HttpResponse直接返回内容--因为id是int类型,里面得使用str()进行转换
        return HttpResponse("id是" + str(id) + "的某某平台系统页面")

# 定义index_id方法,第一个参数request请求对象,里面继续跟多个路由变量"year, month, day, id"
def index_id02(request, year, month, day, id):
    # 使用HttpResponse直接返回内容--因为id是int类型,里面得使用str()进行转换
    return HttpResponse(str(year) + "/" + str(month) + "/" + str(day) + "/" +"id是" + str(id) + "的某某平台系统页面")

# 定义index_id方法,第一个参数request请求对象,后面跟多个路由变量"year, month, day"--注意,使用正则后不能与"id"再混合使用了
def index_id03(request, year, month, day):
    # 使用HttpResponse直接返回内容--因为id是int类型,里面得使用str()进行转换
    return HttpResponse(str(year) + '/' + str(month) + '/' + str(day) + "使用正则路由后的某某平台系统页面")


# 定义目标文件路径
file_path = "D:\\works\\djangoProject\\datas\\IDM.rar"

# 定义download_file1下载方法,请求需要带上request(所有的请求都要带上request)
def download_file1(request):
    # 定义文件--使用open函数方法以"rb"二进制读取模式打开文件--然后返回一个file文件对象
    file = open(file_path, 'rb')
    # 创建构造好了HttpResponse对象之后返回给response
    response = HttpResponse(file)
    # 指定文件类型
    response['Content-Type'] = 'application/rar-compressed'
    # 扩展--指定附件及文件名字(可以动态拼接filename,并不建议使用原始名称)
    response['Content-Disposition'] = 'attachment; filename=file1.rar'
    return response

def download_file2(request):
    file = open(file_path, 'rb')
    # 创建构造好了StreamingHttpResponse对象之后返回给response
    response = StreamingHttpResponse(file)
    response['Content-Type'] = 'application/rar-compressed'
    response['Content-Disposition'] = 'attachment; filename=file2.rar'
    return response

def download_file3(request):
    file = open(file_path, 'rb')
    # 创建构造好了FileResponse对象之后返回给response
    response = FileResponse(file)
    response['Content-Type'] = 'application/rar-compressed'
    response['Content-Disposition'] = 'attachment; filename=file3.rar'
    return response

def get_test(request):
    """
    get 请求测试
    :param request:
    :return:
    """
    print(f'打印请求方式request.method:{request.method}')
    print(f'打印常用属性request.content_type:{request.content_type}')
    print(f'打印常用属性request.content_params:{request.content_params}')
    print(f'打印常用属性request.COOKIES:{request.COOKIES}')
    print(f'打印常用属性request.scheme:{request.scheme}')
    print(f'打印常用方法request.is_secure:{request.is_secure()}')
    print(f'打印常用方法request.get_host:{request.get_host()}')
    print(f'打印常用方法request.get_full_path:{request.get_full_path()}')

    print(f'使用request.GET.get("name")方法获取对应key值参数:{request.GET.get("name")}' )
    print(f'使用request.GET.get("pwd")方法获取对应key值参数:{request.GET.get("pwd")}')
    print(f'使用request.GET.get("dev")方法获取不存在的参数,返回值是'None':{request.GET.get("dev")}')
    print(f'request.GET.get("prod", "555")获取的prod参数是不存在的,设置返回值'555':{request.GET.get("prod", "555")}')

    return HttpResponse("hello world!http get ok!")

def post_test(request):
    """
    post 请求测试方法
    :param request:
    :return:
    """
    print(f'打印请求方式request.method:{request.method}')
    print(f'使用request.POST.get("name")方法获取对应key值参数:{request.POST.get("name")}')
    print(f'使用request.POST.get("pwd")方法获取对应key值参数:{request.POST.get("pwd")}')
    print(f'使用request.POST.get("dev")方法获取不存在的参数,返回值是'None':{request.POST.get("dev")}')
    print(f'request.POST.get("prod", "556")获取的prod参数是不存在的,设置返回值'556':{request.POST.get("prod", "556")}')
    return HttpResponse("hello world!http post ok!")

def to_login(request):
    """
    跳转到登录页面
    :param request:
    :return:
    """
    # 跳转到登录页面可直接使用渲染render()方法
    return render(request, 'login.html')

def login(request):
    """
    登录到主页面
    :param request:
    :return:
    """
    # 首先获取用户名和密码
    user_name = request.POST.get('user_name')
    pwd = request.POST.get('pwd')
    # 模拟用户登录(暂不连接数据库)
    if user_name == "python3" and pwd == "123456":
        # session中存一个用户名
        request.session['currentUserName'] = user_name
        print(f'代码脚本中session获取:{request.session['currentUserName']}')
        # 获取HttpResponse
        response = render(request, 'main.html')
        # 通过response.set_Cookie()方法来设置cookie
        response.set_cookie('remember_me', True)
        return response
    else:
        # 定义变量content_value为报错提示信息,里面是键值对格式的对象
        content_value = {"error_info":"用户名或者密码错误!"}
        return render(request, 'login.html', context=content_value)

def to_upload(request):
    """
    跳转到上传文件页面
    :param request:
    :return:
    """
    return render(request, 'upload.html')

def upload(request):
    """
    上传文件处理
    :param request:
    :return:
    """
    # 获取上传的文件,如果没有文件,就默认为None(如果不写指定的None,也是默认None)
    myFile = request.FILES.get("myfile", None)
    # 判断如果是有myFile的情况,就是说明有文件
    if myFile:
        # 打开特定的文件进行二进制的写操作--上传文件路径烦请自行创建(例如:在项目datas路径下新建"myFile"目录文件)
        f = open(os.path.join("D:\\works\\djangoProject\\datas\\myFile", myFile.name), "wb+")
        # 分块写入文件
        for chunk in myFile.chunks():
            f.write(chunk)
        f.close()
        return HttpResponse("文件上传成功!")
    else:
        return HttpResponse("没有发现可以上传的文件!")

# 定义List类,继续继承ListView(导包引入 from django.views.generic import ListView)
class List(ListView):
    # 设置模版文件
    template_name = 'student/list.html'
    # 设置模型外的数据--例如:指定标题
    extra_context = {'title': '学生信息列表'}
    # 定义数据集(查询结果集)--StudentInfo(导入 from helloWorld.models import StudentInfo)
    queryset = StudentInfo.objects.all()
    # 每页展示数据的条数(自行设置)
    paginate_by = 5
    # 设置上下文对象名称(其实就是一个列表的对象),建议使用自定义名称,不然就默认为 objectlist
    context_object_name = 'student_list'

# 定义Detail类,继续继承DetailView(导包引入 from django.views.generic import DetailView)
class Detail(DetailView):
    # 设置模版文件
    template_name = 'student/detail.html'
    # 设置模型外的数据--例如:指定标题
    extra_context = {'title': '学生信息详情'}
    # 设置查询模型--指定model(不然无法查询数据)
    model = StudentInfo
    # 设置上下文对象名称
    context_object_name = 'student'

    # 指定URL中用于获取对象的唯一标识符的参数名称,默认为'pk';
    # 如若在urls.py文件中自定义了名称,则需要添加对应pk_url_kwarg参数为自定义名称
    # pk_url_kwarg = 'id'

# 定义Create类,继续继承CreateView(导包引入 from django.views.generic import CreateView)
class Create(CreateView):
    # 设置模版文件,名称修改为create(有的名称叫"add"也行)
    template_name = 'student/create.html'
    # 设置模型外的数据--例如:指定标题"添加"或者"新增"都行
    extra_context = {'title': '学生信息新增'}
    # 因为是表单,所以需要使用form_class参数来指定form表单
    form_class = StudentForm
    # 执行成功后跳转地址(跳转地址可以是一个新的页面,也可以进入一个列表页面)
    success_url = '/student/list'

# 定义Update类,继续继承UpdateView(导包引入 from django.views.generic import UpdateView)
class Update(UpdateView):
    # 设置模版文件,名称修改为update(自定义)
    template_name = 'student/update.html'
    # 设置模型外的数据--例如:指定标题"编辑"或者"修改"都行
    extra_context = {'title': '学生信息编辑'}
    # 设置查询模型--指定model(不然无法查询数据)--传来一个组件"pk"会自动的去查数据、封装对象、显示到页面上
    model = StudentInfo
    # 因为是表单,所以需要使用form_class参数来指定form表单
    form_class = StudentForm
    # 执行成功后跳转地址(跳转地址可以是一个新的页面,也可以进入一个列表页面)
    success_url = '/student/list'

# 定义Delete类,继续继承DeleteView(导包引入 from django.views.generic import DeleteView)
class   Delete(DeleteView):
    # 设置模版文件,名称修改为delete(自定义)
    template_name = 'student/delete.html'
    # 设置模型外的数据--例如:指定标题"删除"或者"修改"都行
    extra_context = {'title': '学生信息删除'}
    # 设置上下文对象名称
    context_object_name = 'student'
    # 设置查询模型--指定model(不然无法查询数据)--传来一个组件"pk"会自动的去查数据、封装对象、显示到页面上
    model = StudentInfo
    # 执行成功后跳转地址(跳转地址可以是一个新的页面,也可以进入一个列表页面)
    success_url = '/student/list'

# 自定义跳转子类模板页面方法
def to_course(request):
    """
    跳转到子类模板页面
    :param request:
    :return:
    """
    return render(request, 'course.html')

# 定义bookList方法
def bookList(request):
    """
    图书列表查询
    """
    # 查询显示所有数据
    # bookList = BookInfo.objects.all()

    # 使用BookInfo.objects.extra()方法,里面使用where加参数(元组格式),在params加上具体的参数,也是一个元组
    # 如果是模型外的参数(models.py里面没有定义的参数),可以使用BookInfo.objects.select()方法
    # bookList = BookInfo.objects.extra(where=["price>%s"], params=[90])

    # # 使用BookInfo.objects.raw()方法,直接写一个SQL语句
    bookList = BookInfo.objects.raw('SELECT * FROM t_book where price>%s', params=[90])

    # 使用connection方法需要导包引入from django.db import connection模块,返回一个cursor游标
    # 指定cursor游标为CursorDebugWrapper类型,需导包引入from django.db.backends.utils import CursorDebugWrapper模块
    cursor: CursorDebugWrapper = connection.cursor()
    # 然后就可以执行使用execute方法,查询图书价格大于90的记录数
    cursor.execute('select count(*) from t_book where price>90')
    print('预计提取的一个结果是(2,):', cursor.fetchone())

    content_value = {"title": "图书列表", "bookList": bookList}
    return render(request, 'book/list.html', context=content_value)

def bookList2(request):
    """
    多表查询,有正向查询和反向查询
    :param request:
    :return:
    """
    # 正向查询(经常使用)--多的一端BookInfo条件id=2且只查询一条数据,返回图书book并指定类型BookInfo
    book: BookInfo = BookInfo.objects.filter(id=2).first()
    # 能够输出book的所有信息,也能输出所关联的bookType.bookTypeName数据信息
    print(book.bookName, book.bookType.bookTypeName)

    # 反向查询(很少使用)--(导包引入from helloWorld.models import BookTypeInfo)
    # 一的一端BookTypeInfo条件id=1且只查询一条数据,返回图书类别bookType并指定类型BookTypeInfo
    bookType: BookTypeInfo = BookTypeInfo.objects.filter(id=1).first()
    # 输出读取关联的这个图书类别下的所有图书的数据集中第一条数据,固定语法bookinfo_set
    print(bookType.bookinfo_set.first().bookName)
    # 显示这个类别下的所有数据,返回的结果是一个QuerySet数据集,这个类型下有两条数据,
    print(bookType.bookinfo_set.all())
    # 把QuerySet这个数据集转发到页面上就可以进行遍历了
    content_value = {"title": "图书列表", "bookList": bookType.bookinfo_set.all()}
    return render(request, 'book/list.html', context=content_value)

def preAdd(request):
    """
    预处理,添加操作
    :param request:
    :return:
    """
    # 预处理操作需要先找到所有数据
    bookTypeList = BookTypeInfo.objects.all()
    print(f'可自行打印查看bookTypeList是否查询出所有数据:{bookTypeList}')
    # 预处理操作之后要返回到图书添加页面
    content_value = {"title": "图书添加", "bookTypeList": bookTypeList}
    return render(request, 'book/add.html', context=content_value)

def add(request):
    """
    图书添加
    :param request:
    :return:
    """
    # 封装对象
    book = BookInfo()
    # 添加数据
    book.bookName = request.POST.get("bookName")
    book.publishDate = request.POST.get("publishDate")
    book.bookType_id = request.POST.get("bookType_id")
    book.price = request.POST.get("price")
    # 因为是模板,所以直接使用save()方法
    book.save()
    # 数据添加完成之后,可以获取新增数据的主键id
    print(f'数据添加完成之后,获取新增数据的主键id:{book.id}')
    # 添加完成之后,需要返回到列表,直接return返回那个对象
    return bookList(request)

# 修改预处理,定义修改操作方法;页面需要传一个id来确定是要修改哪一条记录
def preUpdate(request, id):
    """
    预处理,修改操作
    :param request:
    :return:
    """
    print("通过path里面的变量,页面需要传一个id来确定是要修改哪一条记录:", id)
    # 根据传过来的id去查询数据,并返回一个book对象
    book = BookInfo.objects.get(id=id)
    print(f'可自行打印查看返回的book对象:{book}')
    bookTypeList = BookTypeInfo.objects.all()
    print(bookTypeList)
    # book对象也需要传到页面上去,到时候页面上就能够取值了
    content_value = {"title": "图书修改", "bookTypeList": bookTypeList, "book": book}
    return render(request, 'book/edit.html', context=content_value)

def update(request):
    """
    图书修改
    :param request:
    :return:
    """
    book = BookInfo()
    book.id = request.POST.get("id")
    book.bookName = request.POST.get("bookName")
    book.publishDate = request.POST.get("publishDate")
    book.bookType_id = request.POST.get("bookType_id")
    book.price = request.POST.get("price")
    book.save()
    return bookList(request)

def delete(request, id):
    """
    图书删除
    :param request:
    :return:
    """
    # 删除指定id数据
    BookInfo.objects.get(id=id).delete()
    # 根据条件删除多条数据
    # BookInfo.objects.filter(price__gte=100).delete()
    # 删除所有数据
    # BookInfo.objects.all().delete()
    return bookList(request)

6、运行项目,浏览器输入"http://127.0.0.1:8000/book/list"打开图书列表页面测试验证;

八、Django ORM 数据库事务

事务是指作为单个逻辑执行的一系列操作,这些操作具有原子性,即这些操作要么完全执行,要么完全不执行,就是保证一个原子性;数据库事务处理可以确保事务性单元内的所有操作都成功完成,否则不会执行数据操作; 事务应该具有4个属性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),这4个属性通常称为ACID特性;

  • 原子性:一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做;
  • 一致性:事务必须使数据库从某个一致性状态变到另一个一致性状态,一致性与原子性是密切相关的;
  • 隔离性:一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对其他事务是隔离的,各个事务之间不能互相干扰;
  • 持久性:持久性也称永久性(Permanence),指一个事务一旦提交,它对数据库中数据的改变应该是永久性的,其他操作或故障不应该对其有任何影响;
模拟用户转账实例
创建账户信息模型

7、在"djangoProject"项目"helloWorld"应用下的"models.py"文件里面新建 AccountInfo 账户信息模型;

复制代码
from django.db import models

# Create your models here.

class StudentInfo(models.Model):
    # 新建3个字段,第一个字段是 主键id
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=20)
    age = models.IntegerField()

    # 默认会有个StudentInfo表,但是不好;所以务必通过Meta属性db_table来指定表名
    class Meta:
        db_table = 't_student'

# 先创建BookTypeInfo图书类型类,需要继承于models.Model
class BookTypeInfo(models.Model):
    # 定义字段,首先定义id是自增主键
    id = models.AutoField(primary_key=True)
    # 再定义一个bookTypeName字段是CharField类型并指定最大长度20
    bookTypeName = models.CharField(max_length=20)

    # 定义Meta
    class Meta:
        # 指定表名
        db_table = "t_bookType"
        # 给模型取个直观的名字,主要是在Django5内置的admin系统里面给它娶个名字,以便管理
        verbose_name = "图书类别信息"

# 定义BookInfo类,依然是继承models.Model
class BookInfo(models.Model):
    id = models.AutoField(primary_key=True)
    # 图书名称
    bookName = models.CharField(max_length=20)
    # 图书价格
    price = models.FloatField()
    # 出版日期
    publishDate = models.DateField()
    # 定义图书和图书类别的外键关联--bookType对象通过外键值取到图书类别的一个数据对象的封装
    bookType = models.ForeignKey(BookTypeInfo, on_delete=models.PROTECT)

    class Meta:
        db_table = "t_book"
        verbose_name = "图书信息"

# 定义AccountInfo类,继承models.Model
class AccountInfo(models.Model):
    # id字段可定义亦可不定义
    id = models.AutoField(primary_key=True)
    # 定义user用户
    user = models.CharField(max_length=20)
    # 定义某个用户的账户余额
    account = models.FloatField()

    class Meta:
        # 数据库表名
        db_table = "t_account"
        # 给模型取个直观的名字
        verbose_name = "用户账户信息"

8、 通过Meta属性 db_table 来指定表名后生成,PyCharm 工具(I)菜单栏选中"运行 manage.py 任务...(快捷键 Ctrl+Alt+R)",使用 Django5 manage.py 提供的内置命令 migrate(根据迁移文件的内容,在数据库里生成相应的数据表) 来创建Django内置功能的数据表;

8-1、先执行: python manage.py makemigrations 生成数据库迁移文件;

8-2、再执行:python manage.py migrate 执行迁移文件,同步到数据库中;会在数据库中生成生成名称为"t_account"的表;

添加测试数据

9、目前用户账户信息表"t_account"是空的,需要添加一些测试数据,新建查询输入命令或者手动新增,最少需要添加两条数据信息,然后运行,执行成功后刷新"t_account"表,查看测试数据;

复制代码
insert into t_account VALUES(null,'A用户_赵大',100);
insert into t_account VALUES(null,'B用户_钱二',100);
insert into t_account VALUES(null,'C用户_张三',100);
定义转账transfer方法

10、修改"djangoProject"项目"helloWorld"应用下的"views.py"配置文件内容,模拟转账,定义一个转账transfer方法;

  1. def transfer(request)--定义转账transfer方法;
  2. a1 = AccountInfo.objects.filter(user='A用户_赵大')--导包引入from helloWorld.models import AccountInfo方法,然后查找'A用户_赵大'这个用户;
  3. a1.update(account = F('account') + 100)--给这个"'A用户_赵大'"账户加account余额,若按数据库里面是"(account = account + 100)"这样的,但是在语句里面并不是这样执行的,Django 内置提供了一个"F"方法,使用时需要导包引入from django.db.models import F 模块,然后里面写上对应的字段信息;
  4. 这个"'A用户_赵大'"账户是加account余额,那么对应的"'B 用户_ 钱二'"账户就是减account余额;
  5. 执行完两个操作之后,模拟转账完成,返回到界面;
复制代码
# 导包
import datetime
import os

from django.core.paginator import Paginator
from django.db import connection
from django.db.backends.utils import CursorDebugWrapper
from django.db.models import Q, Sum, Avg, F
from django.http import HttpResponse, HttpResponseNotFound, JsonResponse, StreamingHttpResponse, FileResponse
from django.shortcuts import render, redirect
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView

from helloWorld.forms import StudentForm
from helloWorld.models import StudentInfo, BookInfo, BookTypeInfo, AccountInfo


# Create your views here.

# 定义类
class Person:
    # 属性 姓名
    name = None
    # 属性 年龄
    age = None

    def __init__(self, name, age):
        self.name = name
        self.age = age

# 自定义一个index方法,里面要加上request请求对象
def index(request):
    # 传递字符串类型数据,定义字符串类型变量str
    str = 'hello world!'
    # 日期对象date,导包引入import datetime
    date = datetime.datetime.now()
    # 定义字典类型数据
    myDict = {"Jack":"11", "Queen":"12", "King":"13"}
    # 创建一个对象,例如:zhangsan
    zhangsan = Person("张三", 18)
    # 定义列表myList、元组myTuple--列表和元组里面的元素类型没有限制,列表里面可以嵌套列表,元组里面可以嵌套元组
    myList = [1, 'apple', 3.1415926, True, [5, 6], (7, 8), {9, 10}]
    myTuple = (1, 'apple', 3.1415926, True, [5, 6], (7, 8), {9, 10})
    # 变量content_value是字典格式
    content_value = {"content_msg": str, "msg2": myDict, "msg3": zhangsan, "msg4": myList, "msg5": myTuple, "date": date}
    # context= 传递的本身要求格式是一个字典类型
    return render(request, 'index.html', context=content_value)

# 定义index_id方法,里面第一个参数request请求对象,第二个参数 id
def index_id(request, id):
    # 判断 id是否为0
    if id == 0:
        # 导包引入redirect模块,如果id等于0的话,重定向到一个目标静态地址
        return redirect("/static/error.html")
    else:
        # 使用HttpResponse直接返回内容--因为id是int类型,里面得使用str()进行转换
        return HttpResponse("id是" + str(id) + "的某某平台系统页面")

# 定义index_id方法,第一个参数request请求对象,里面继续跟多个路由变量"year, month, day, id"
def index_id02(request, year, month, day, id):
    # 使用HttpResponse直接返回内容--因为id是int类型,里面得使用str()进行转换
    return HttpResponse(str(year) + "/" + str(month) + "/" + str(day) + "/" +"id是" + str(id) + "的某某平台系统页面")

# 定义index_id方法,第一个参数request请求对象,后面跟多个路由变量"year, month, day"--注意,使用正则后不能与"id"再混合使用了
def index_id03(request, year, month, day):
    # 使用HttpResponse直接返回内容--因为id是int类型,里面得使用str()进行转换
    return HttpResponse(str(year) + '/' + str(month) + '/' + str(day) + "使用正则路由后的某某平台系统页面")


# 定义目标文件路径
file_path = "D:\\works\\djangoProject\\datas\\IDM.rar"

# 定义download_file1下载方法,请求需要带上request(所有的请求都要带上request)
def download_file1(request):
    # 定义文件--使用open函数方法以"rb"二进制读取模式打开文件--然后返回一个file文件对象
    file = open(file_path, 'rb')
    # 创建构造好了HttpResponse对象之后返回给response
    response = HttpResponse(file)
    # 指定文件类型
    response['Content-Type'] = 'application/rar-compressed'
    # 扩展--指定附件及文件名字(可以动态拼接filename,并不建议使用原始名称)
    response['Content-Disposition'] = 'attachment; filename=file1.rar'
    return response

def download_file2(request):
    file = open(file_path, 'rb')
    # 创建构造好了StreamingHttpResponse对象之后返回给response
    response = StreamingHttpResponse(file)
    response['Content-Type'] = 'application/rar-compressed'
    response['Content-Disposition'] = 'attachment; filename=file2.rar'
    return response

def download_file3(request):
    file = open(file_path, 'rb')
    # 创建构造好了FileResponse对象之后返回给response
    response = FileResponse(file)
    response['Content-Type'] = 'application/rar-compressed'
    response['Content-Disposition'] = 'attachment; filename=file3.rar'
    return response

def get_test(request):
    """
    get 请求测试
    :param request:
    :return:
    """
    print(f'打印请求方式request.method:{request.method}')
    print(f'打印常用属性request.content_type:{request.content_type}')
    print(f'打印常用属性request.content_params:{request.content_params}')
    print(f'打印常用属性request.COOKIES:{request.COOKIES}')
    print(f'打印常用属性request.scheme:{request.scheme}')
    print(f'打印常用方法request.is_secure:{request.is_secure()}')
    print(f'打印常用方法request.get_host:{request.get_host()}')
    print(f'打印常用方法request.get_full_path:{request.get_full_path()}')

    print(f'使用request.GET.get("name")方法获取对应key值参数:{request.GET.get("name")}' )
    print(f'使用request.GET.get("pwd")方法获取对应key值参数:{request.GET.get("pwd")}')
    print(f'使用request.GET.get("dev")方法获取不存在的参数,返回值是'None':{request.GET.get("dev")}')
    print(f'request.GET.get("prod", "555")获取的prod参数是不存在的,设置返回值'555':{request.GET.get("prod", "555")}')

    return HttpResponse("hello world!http get ok!")

def post_test(request):
    """
    post 请求测试方法
    :param request:
    :return:
    """
    print(f'打印请求方式request.method:{request.method}')
    print(f'使用request.POST.get("name")方法获取对应key值参数:{request.POST.get("name")}')
    print(f'使用request.POST.get("pwd")方法获取对应key值参数:{request.POST.get("pwd")}')
    print(f'使用request.POST.get("dev")方法获取不存在的参数,返回值是'None':{request.POST.get("dev")}')
    print(f'request.POST.get("prod", "556")获取的prod参数是不存在的,设置返回值'556':{request.POST.get("prod", "556")}')
    return HttpResponse("hello world!http post ok!")

def to_login(request):
    """
    跳转到登录页面
    :param request:
    :return:
    """
    # 跳转到登录页面可直接使用渲染render()方法
    return render(request, 'login.html')

def login(request):
    """
    登录到主页面
    :param request:
    :return:
    """
    # 首先获取用户名和密码
    user_name = request.POST.get('user_name')
    pwd = request.POST.get('pwd')
    # 模拟用户登录(暂不连接数据库)
    if user_name == "python3" and pwd == "123456":
        # session中存一个用户名
        request.session['currentUserName'] = user_name
        print(f'代码脚本中session获取:{request.session['currentUserName']}')
        # 获取HttpResponse
        response = render(request, 'main.html')
        # 通过response.set_Cookie()方法来设置cookie
        response.set_cookie('remember_me', True)
        return response
    else:
        # 定义变量content_value为报错提示信息,里面是键值对格式的对象
        content_value = {"error_info":"用户名或者密码错误!"}
        return render(request, 'login.html', context=content_value)

def to_upload(request):
    """
    跳转到上传文件页面
    :param request:
    :return:
    """
    return render(request, 'upload.html')

def upload(request):
    """
    上传文件处理
    :param request:
    :return:
    """
    # 获取上传的文件,如果没有文件,就默认为None(如果不写指定的None,也是默认None)
    myFile = request.FILES.get("myfile", None)
    # 判断如果是有myFile的情况,就是说明有文件
    if myFile:
        # 打开特定的文件进行二进制的写操作--上传文件路径烦请自行创建(例如:在项目datas路径下新建"myFile"目录文件)
        f = open(os.path.join("D:\\works\\djangoProject\\datas\\myFile", myFile.name), "wb+")
        # 分块写入文件
        for chunk in myFile.chunks():
            f.write(chunk)
        f.close()
        return HttpResponse("文件上传成功!")
    else:
        return HttpResponse("没有发现可以上传的文件!")

# 定义List类,继续继承ListView(导包引入 from django.views.generic import ListView)
class List(ListView):
    # 设置模版文件
    template_name = 'student/list.html'
    # 设置模型外的数据--例如:指定标题
    extra_context = {'title': '学生信息列表'}
    # 定义数据集(查询结果集)--StudentInfo(导入 from helloWorld.models import StudentInfo)
    queryset = StudentInfo.objects.all()
    # 每页展示数据的条数(自行设置)
    paginate_by = 5
    # 设置上下文对象名称(其实就是一个列表的对象),建议使用自定义名称,不然就默认为 objectlist
    context_object_name = 'student_list'

# 定义Detail类,继续继承DetailView(导包引入 from django.views.generic import DetailView)
class Detail(DetailView):
    # 设置模版文件
    template_name = 'student/detail.html'
    # 设置模型外的数据--例如:指定标题
    extra_context = {'title': '学生信息详情'}
    # 设置查询模型--指定model(不然无法查询数据)
    model = StudentInfo
    # 设置上下文对象名称
    context_object_name = 'student'

    # 指定URL中用于获取对象的唯一标识符的参数名称,默认为'pk';
    # 如若在urls.py文件中自定义了名称,则需要添加对应pk_url_kwarg参数为自定义名称
    # pk_url_kwarg = 'id'

# 定义Create类,继续继承CreateView(导包引入 from django.views.generic import CreateView)
class Create(CreateView):
    # 设置模版文件,名称修改为create(有的名称叫"add"也行)
    template_name = 'student/create.html'
    # 设置模型外的数据--例如:指定标题"添加"或者"新增"都行
    extra_context = {'title': '学生信息新增'}
    # 因为是表单,所以需要使用form_class参数来指定form表单
    form_class = StudentForm
    # 执行成功后跳转地址(跳转地址可以是一个新的页面,也可以进入一个列表页面)
    success_url = '/student/list'

# 定义Update类,继续继承UpdateView(导包引入 from django.views.generic import UpdateView)
class Update(UpdateView):
    # 设置模版文件,名称修改为update(自定义)
    template_name = 'student/update.html'
    # 设置模型外的数据--例如:指定标题"编辑"或者"修改"都行
    extra_context = {'title': '学生信息编辑'}
    # 设置查询模型--指定model(不然无法查询数据)--传来一个组件"pk"会自动的去查数据、封装对象、显示到页面上
    model = StudentInfo
    # 因为是表单,所以需要使用form_class参数来指定form表单
    form_class = StudentForm
    # 执行成功后跳转地址(跳转地址可以是一个新的页面,也可以进入一个列表页面)
    success_url = '/student/list'

# 定义Delete类,继续继承DeleteView(导包引入 from django.views.generic import DeleteView)
class   Delete(DeleteView):
    # 设置模版文件,名称修改为delete(自定义)
    template_name = 'student/delete.html'
    # 设置模型外的数据--例如:指定标题"删除"或者"修改"都行
    extra_context = {'title': '学生信息删除'}
    # 设置上下文对象名称
    context_object_name = 'student'
    # 设置查询模型--指定model(不然无法查询数据)--传来一个组件"pk"会自动的去查数据、封装对象、显示到页面上
    model = StudentInfo
    # 执行成功后跳转地址(跳转地址可以是一个新的页面,也可以进入一个列表页面)
    success_url = '/student/list'

# 自定义跳转子类模板页面方法
def to_course(request):
    """
    跳转到子类模板页面
    :param request:
    :return:
    """
    return render(request, 'course.html')

# 定义bookList方法
def bookList(request):
    """
    图书列表查询
    """
    # 查询显示所有数据
    # bookList = BookInfo.objects.all()

    # 使用BookInfo.objects.extra()方法,里面使用where加参数(元组格式),在params加上具体的参数,也是一个元组
    # 如果是模型外的参数(models.py里面没有定义的参数),可以使用BookInfo.objects.select()方法
    # bookList = BookInfo.objects.extra(where=["price>%s"], params=[90])

    # # 使用BookInfo.objects.raw()方法,直接写一个SQL语句
    bookList = BookInfo.objects.raw('SELECT * FROM t_book where price>%s', params=[90])

    # 使用connection方法需要导包引入from django.db import connection模块,返回一个cursor游标
    # 指定cursor游标为CursorDebugWrapper类型,需导包引入from django.db.backends.utils import CursorDebugWrapper模块
    cursor: CursorDebugWrapper = connection.cursor()
    # 然后就可以执行使用execute方法,查询图书价格大于90的记录数
    cursor.execute('select count(*) from t_book where price>90')
    print('预计提取的一个结果是(2,):', cursor.fetchone())

    content_value = {"title": "图书列表", "bookList": bookList}
    return render(request, 'book/list.html', context=content_value)

def bookList2(request):
    """
    多表查询,有正向查询和反向查询
    :param request:
    :return:
    """
    # 正向查询(经常使用)--多的一端BookInfo条件id=2且只查询一条数据,返回图书book并指定类型BookInfo
    book: BookInfo = BookInfo.objects.filter(id=2).first()
    # 能够输出book的所有信息,也能输出所关联的bookType.bookTypeName数据信息
    print(book.bookName, book.bookType.bookTypeName)

    # 反向查询(很少使用)--(导包引入from helloWorld.models import BookTypeInfo)
    # 一的一端BookTypeInfo条件id=1且只查询一条数据,返回图书类别bookType并指定类型BookTypeInfo
    bookType: BookTypeInfo = BookTypeInfo.objects.filter(id=1).first()
    # 输出读取关联的这个图书类别下的所有图书的数据集中第一条数据,固定语法bookinfo_set
    print(bookType.bookinfo_set.first().bookName)
    # 显示这个类别下的所有数据,返回的结果是一个QuerySet数据集,这个类型下有两条数据,
    print(bookType.bookinfo_set.all())
    # 把QuerySet这个数据集转发到页面上就可以进行遍历了
    content_value = {"title": "图书列表", "bookList": bookType.bookinfo_set.all()}
    return render(request, 'book/list.html', context=content_value)

def preAdd(request):
    """
    预处理,添加操作
    :param request:
    :return:
    """
    # 预处理操作需要先找到所有数据
    bookTypeList = BookTypeInfo.objects.all()
    print(f'可自行打印查看bookTypeList是否查询出所有数据:{bookTypeList}')
    # 预处理操作之后要返回到图书添加页面
    content_value = {"title": "图书添加", "bookTypeList": bookTypeList}
    return render(request, 'book/add.html', context=content_value)

def add(request):
    """
    图书添加
    :param request:
    :return:
    """
    # 封装对象
    book = BookInfo()
    # 添加数据
    book.bookName = request.POST.get("bookName")
    book.publishDate = request.POST.get("publishDate")
    book.bookType_id = request.POST.get("bookType_id")
    book.price = request.POST.get("price")
    # 因为是模板,所以直接使用save()方法
    book.save()
    # 数据添加完成之后,可以获取新增数据的主键id
    print(f'数据添加完成之后,获取新增数据的主键id:{book.id}')
    # 添加完成之后,需要返回到列表,直接return返回那个对象
    return bookList(request)

# 修改预处理,定义修改操作方法;页面需要传一个id来确定是要修改哪一条记录
def preUpdate(request, id):
    """
    预处理,修改操作
    :param request:
    :return:
    """
    print("通过path里面的变量,页面需要传一个id来确定是要修改哪一条记录:", id)
    # 根据传过来的id去查询数据,并返回一个book对象
    book = BookInfo.objects.get(id=id)
    print(f'可自行打印查看返回的book对象:{book}')
    bookTypeList = BookTypeInfo.objects.all()
    print(bookTypeList)
    # book对象也需要传到页面上去,到时候页面上就能够取值了
    content_value = {"title": "图书修改", "bookTypeList": bookTypeList, "book": book}
    return render(request, 'book/edit.html', context=content_value)

def update(request):
    """
    图书修改
    :param request:
    :return:
    """
    book = BookInfo()
    book.id = request.POST.get("id")
    book.bookName = request.POST.get("bookName")
    book.publishDate = request.POST.get("publishDate")
    book.bookType_id = request.POST.get("bookType_id")
    book.price = request.POST.get("price")
    book.save()
    return bookList(request)

def delete(request, id):
    """
    图书删除
    :param request:
    :return:
    """
    # 删除指定id数据
    BookInfo.objects.get(id=id).delete()
    # 根据条件删除多条数据
    # BookInfo.objects.filter(price__gte=100).delete()
    # 删除所有数据
    # BookInfo.objects.all().delete()
    return bookList(request)


# 定义转账transfer方法
def transfer(request):
    """
    模拟转账
    :param request:
    :return:
    """
    # 导包引入from helloWorld.models import AccountInfo方法
    # AccountInfo.objects.filter()方法查找A用户
    a1 = AccountInfo.objects.filter(user='A用户_赵大')
    # 导包引入from django.db.models import F 模块
    a1.update(account = F('account') + 100)
    # 这个"'A用户_赵大'"账户是加account余额,那么对应的"'B 用户_ 钱二'"账户就是减account余额
    b2 = AccountInfo.objects.filter(user='B用户_钱二')
    b2.update(account = F('account') - 100)
    # 执行完两个操作之后,模拟转账完成,返回到界面
    return HttpResponse("OK!转账完成!")
定义映射关系

11、修改"djangoProject"应用下的"urls.py"配置文件内容,配置定义模拟转账实例映射关系;

复制代码
"""
URL configuration for djangoProject project.

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/5.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.conf import settings
from django.contrib import admin
from django.urls import path, re_path, include
from django.views.generic import RedirectView
from django.views.static import serve
import helloWorld.views

urlpatterns = [
    path('admin/', admin.site.urls),
    # 可直接复制上面"admin",把"admin"修改为"index";后面是跟的"处理函数helloWorld.views.index"
    # 是helloWorld应用的views里面的index()--引入helloWorld.views,记得index后不能加小括号,因为传入的是对象;
    path('index/', helloWorld.views.index, name='index'),
    # 要是请求"redirectTo"这个请求的话给重定向到"index"--重定向使用RedirectView.as_view(),里面使用关键词参数url
    path('redirectTo', RedirectView.as_view(url="index/")),
    # 自定义名称,例如叫"index_id/",后面再跟上某网页id;<int:id> int指定类型,路由变量为id;对应对应的路由映射
    path('index_id/<int:id>', helloWorld.views.index_id),
    # 多个路由变量
    path('index_id02/<int:year>/<int:month>/<int:day>/<int:id>', helloWorld.views.index_id02),
    # 正则表达式匹配是必须使用re_path方法,正则表达式"?P"开头是固定格式
    re_path('index_id03/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})', helloWorld.views.index_id03),
    # 配置媒体文件的路由地址
    re_path('media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}, name='media'),
    # 命名空间namespace--比如说是"user/"开头的,第一个参数include(),第二个参数namespace
    # 导包引入include模块,include()里面也有两个参数,第一个是指定user的urls,第二个是项目名;
    path('user/', include(('user.urls', 'user'), namespace='user')),
    path('order/', include(('order.urls', 'order'), namespace='order')),
    # 配置 download_file
    path('download1/', helloWorld.views.download_file1),
    path('download2/', helloWorld.views.download_file2),
    path('download3/', helloWorld.views.download_file3),

    path('get', helloWorld.views.get_test),
    path('post', helloWorld.views.post_test),

    path('toLogin/', helloWorld.views.to_login),
    path('login', helloWorld.views.login),

    path('toUpload/', helloWorld.views.to_upload),
    path('upload', helloWorld.views.upload),

    path('student/list', helloWorld.views.List.as_view()),

    # 默认名称就是"pk",建议不要更改,在开发过程中就默认为pk即可
    path('student/<int:pk>', helloWorld.views.Detail.as_view()),
    # 如果把默认名称"pk"更改为其他(例如:id),则需要在urls.py配置文件Detail类中添加pk_url_kwarg参数
    # path('student/<int:id>', helloWorld.views.Detail.as_view())

    path('student/create', helloWorld.views.Create.as_view()),
    path('student/update/<int:pk>', helloWorld.views.Update.as_view()),
    path('student/delete/<int:pk>', helloWorld.views.Delete.as_view()),

    path('toCourse/', helloWorld.views.to_course),

    path('book/list', helloWorld.views.bookList),
    path('book/list2', helloWorld.views.bookList2),

    path('book/preAdd', helloWorld.views.preAdd),
    path('book/add', helloWorld.views.add),

    path('book/preUpdate/<int:id>', helloWorld.views.preUpdate),
    path('book/delete/<int:id>', helloWorld.views.delete),
    path('book/update', helloWorld.views.update),

    path('transfer/', helloWorld.views.transfer)

]

12、运行项目,浏览器输入"http://127.0.0.1:8000/transfer/"测试验证,刷新数据库表,查看数据信息account余额,A 用户成功加了 100 变成了 200 余额,而 B 用户则减少了 100 余额后为 0 余额;

可以发现,在功能正常的状况下是没有问题的;这里执行了多个数据库操作(涉及到2个数据库操作),如果某一个出现问题了,它就不再是一个原子性操作,也不符合一致性了;

异常情况

先还原 A 用户和 B 用户都是"100"余额;

13、如果说第二个数据库操作失败,开始模拟,修改"helloWorld"应用下的"views.py"文件内容;

复制代码
# 定义转账transfer方法
def transfer(request):
    """
    模拟转账
    :param request:
    :return:
    """
    # 导包引入from helloWorld.models import AccountInfo方法
    # AccountInfo.objects.filter()方法查找A用户
    a1 = AccountInfo.objects.filter(user='A用户_赵大')
    # 导包引入from django.db.models import F 模块
    a1.update(account = F('account') + 100)
    # 这个"'A用户_赵大'"账户是加account余额,那么对应的"'B 用户_ 钱二'"账户就是减account余额
    b2 = AccountInfo.objects.filter(user='B用户_钱二')
    # 修改操作
    b2.update(account = F('account') - 100 / 0)
    # 执行完两个操作之后,模拟转账完成,返回到界面
    return HttpResponse("OK!转账完成!")

14、运行项目,浏览器输入"http://127.0.0.1:8000/transfer/"测试验证,刷新数据库表,查看数据信息account余额,A 用户成功加了 100 变成了 200 余额,而 B 用户则因为页面报错而没有减少了 100 余额,出现了不一致性结果,这时候经济账单就对不上了,出了 BUG;

引入事务操作

上述问题中出现了不一致性结果,而 Django 提供的事务功能就派上用场了,可以很好的解决此类问题;Django5主要有4个事务方法:

  • atomic():在视图函数或视图类里使用事务;需要添加在方法上面;
  • savepoint():开启事务;
  • savepoint_rollback():回滚事务;假如出现异常了,就回滚事务;
  • savepoint_commit():提交事务;
使用事务操作

恢复 A 用户和 B 用户都是"100"余额;

15、修改"helloWorld"应用下的"views.py"文件内容,在模拟转账transfer方法中使用事务;

  • 首先,需要在transfer方法上添加"@transaction.atomic"来表示使用事务;
  • 其次,开启事务,因为默认是自动提交的;使用transaction.savepoint()方法来开启事务并返回一个事务"sid",到时候需要使用这个 id 来操作的,因为是某一个事务;
  • 然后添加 try......except......异常处理;
  • 如果出现了异常的话,需要进行 transaction.savepoint_rollback(sid) 事务回滚操作,里面传一个sid表示需要知道回滚哪一个事务;
  • 然后有个注意点是要 transaction.savepoint_commit(sid) 提交事务,如果不写也可以,则当程序执行完成后,会自动提交事务,建议最好还是写上提交事务,这样可以更好的进行控制;
复制代码
# 导包
import datetime
import os

from django.core.paginator import Paginator
from django.db import connection, transaction
from django.db.backends.utils import CursorDebugWrapper
from django.db.models import Q, Sum, Avg, F
from django.http import HttpResponse, HttpResponseNotFound, JsonResponse, StreamingHttpResponse, FileResponse
from django.shortcuts import render, redirect
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView

from helloWorld.forms import StudentForm
from helloWorld.models import StudentInfo, BookInfo, BookTypeInfo, AccountInfo


# Create your views here.

# 定义类
class Person:
    # 属性 姓名
    name = None
    # 属性 年龄
    age = None

    def __init__(self, name, age):
        self.name = name
        self.age = age

# 自定义一个index方法,里面要加上request请求对象
def index(request):
    # 传递字符串类型数据,定义字符串类型变量str
    str = 'hello world!'
    # 日期对象date,导包引入import datetime
    date = datetime.datetime.now()
    # 定义字典类型数据
    myDict = {"Jack":"11", "Queen":"12", "King":"13"}
    # 创建一个对象,例如:zhangsan
    zhangsan = Person("张三", 18)
    # 定义列表myList、元组myTuple--列表和元组里面的元素类型没有限制,列表里面可以嵌套列表,元组里面可以嵌套元组
    myList = [1, 'apple', 3.1415926, True, [5, 6], (7, 8), {9, 10}]
    myTuple = (1, 'apple', 3.1415926, True, [5, 6], (7, 8), {9, 10})
    # 变量content_value是字典格式
    content_value = {"content_msg": str, "msg2": myDict, "msg3": zhangsan, "msg4": myList, "msg5": myTuple, "date": date}
    # context= 传递的本身要求格式是一个字典类型
    return render(request, 'index.html', context=content_value)

# 定义index_id方法,里面第一个参数request请求对象,第二个参数 id
def index_id(request, id):
    # 判断 id是否为0
    if id == 0:
        # 导包引入redirect模块,如果id等于0的话,重定向到一个目标静态地址
        return redirect("/static/error.html")
    else:
        # 使用HttpResponse直接返回内容--因为id是int类型,里面得使用str()进行转换
        return HttpResponse("id是" + str(id) + "的某某平台系统页面")

# 定义index_id方法,第一个参数request请求对象,里面继续跟多个路由变量"year, month, day, id"
def index_id02(request, year, month, day, id):
    # 使用HttpResponse直接返回内容--因为id是int类型,里面得使用str()进行转换
    return HttpResponse(str(year) + "/" + str(month) + "/" + str(day) + "/" +"id是" + str(id) + "的某某平台系统页面")

# 定义index_id方法,第一个参数request请求对象,后面跟多个路由变量"year, month, day"--注意,使用正则后不能与"id"再混合使用了
def index_id03(request, year, month, day):
    # 使用HttpResponse直接返回内容--因为id是int类型,里面得使用str()进行转换
    return HttpResponse(str(year) + '/' + str(month) + '/' + str(day) + "使用正则路由后的某某平台系统页面")


# 定义目标文件路径
file_path = "D:\\works\\djangoProject\\datas\\IDM.rar"

# 定义download_file1下载方法,请求需要带上request(所有的请求都要带上request)
def download_file1(request):
    # 定义文件--使用open函数方法以"rb"二进制读取模式打开文件--然后返回一个file文件对象
    file = open(file_path, 'rb')
    # 创建构造好了HttpResponse对象之后返回给response
    response = HttpResponse(file)
    # 指定文件类型
    response['Content-Type'] = 'application/rar-compressed'
    # 扩展--指定附件及文件名字(可以动态拼接filename,并不建议使用原始名称)
    response['Content-Disposition'] = 'attachment; filename=file1.rar'
    return response

def download_file2(request):
    file = open(file_path, 'rb')
    # 创建构造好了StreamingHttpResponse对象之后返回给response
    response = StreamingHttpResponse(file)
    response['Content-Type'] = 'application/rar-compressed'
    response['Content-Disposition'] = 'attachment; filename=file2.rar'
    return response

def download_file3(request):
    file = open(file_path, 'rb')
    # 创建构造好了FileResponse对象之后返回给response
    response = FileResponse(file)
    response['Content-Type'] = 'application/rar-compressed'
    response['Content-Disposition'] = 'attachment; filename=file3.rar'
    return response

def get_test(request):
    """
    get 请求测试
    :param request:
    :return:
    """
    print(f'打印请求方式request.method:{request.method}')
    print(f'打印常用属性request.content_type:{request.content_type}')
    print(f'打印常用属性request.content_params:{request.content_params}')
    print(f'打印常用属性request.COOKIES:{request.COOKIES}')
    print(f'打印常用属性request.scheme:{request.scheme}')
    print(f'打印常用方法request.is_secure:{request.is_secure()}')
    print(f'打印常用方法request.get_host:{request.get_host()}')
    print(f'打印常用方法request.get_full_path:{request.get_full_path()}')

    print(f'使用request.GET.get("name")方法获取对应key值参数:{request.GET.get("name")}' )
    print(f'使用request.GET.get("pwd")方法获取对应key值参数:{request.GET.get("pwd")}')
    print(f'使用request.GET.get("dev")方法获取不存在的参数,返回值是'None':{request.GET.get("dev")}')
    print(f'request.GET.get("prod", "555")获取的prod参数是不存在的,设置返回值'555':{request.GET.get("prod", "555")}')

    return HttpResponse("hello world!http get ok!")

def post_test(request):
    """
    post 请求测试方法
    :param request:
    :return:
    """
    print(f'打印请求方式request.method:{request.method}')
    print(f'使用request.POST.get("name")方法获取对应key值参数:{request.POST.get("name")}')
    print(f'使用request.POST.get("pwd")方法获取对应key值参数:{request.POST.get("pwd")}')
    print(f'使用request.POST.get("dev")方法获取不存在的参数,返回值是'None':{request.POST.get("dev")}')
    print(f'request.POST.get("prod", "556")获取的prod参数是不存在的,设置返回值'556':{request.POST.get("prod", "556")}')
    return HttpResponse("hello world!http post ok!")

def to_login(request):
    """
    跳转到登录页面
    :param request:
    :return:
    """
    # 跳转到登录页面可直接使用渲染render()方法
    return render(request, 'login.html')

def login(request):
    """
    登录到主页面
    :param request:
    :return:
    """
    # 首先获取用户名和密码
    user_name = request.POST.get('user_name')
    pwd = request.POST.get('pwd')
    # 模拟用户登录(暂不连接数据库)
    if user_name == "python3" and pwd == "123456":
        # session中存一个用户名
        request.session['currentUserName'] = user_name
        print(f'代码脚本中session获取:{request.session['currentUserName']}')
        # 获取HttpResponse
        response = render(request, 'main.html')
        # 通过response.set_Cookie()方法来设置cookie
        response.set_cookie('remember_me', True)
        return response
    else:
        # 定义变量content_value为报错提示信息,里面是键值对格式的对象
        content_value = {"error_info":"用户名或者密码错误!"}
        return render(request, 'login.html', context=content_value)

def to_upload(request):
    """
    跳转到上传文件页面
    :param request:
    :return:
    """
    return render(request, 'upload.html')

def upload(request):
    """
    上传文件处理
    :param request:
    :return:
    """
    # 获取上传的文件,如果没有文件,就默认为None(如果不写指定的None,也是默认None)
    myFile = request.FILES.get("myfile", None)
    # 判断如果是有myFile的情况,就是说明有文件
    if myFile:
        # 打开特定的文件进行二进制的写操作--上传文件路径烦请自行创建(例如:在项目datas路径下新建"myFile"目录文件)
        f = open(os.path.join("D:\\works\\djangoProject\\datas\\myFile", myFile.name), "wb+")
        # 分块写入文件
        for chunk in myFile.chunks():
            f.write(chunk)
        f.close()
        return HttpResponse("文件上传成功!")
    else:
        return HttpResponse("没有发现可以上传的文件!")

# 定义List类,继续继承ListView(导包引入 from django.views.generic import ListView)
class List(ListView):
    # 设置模版文件
    template_name = 'student/list.html'
    # 设置模型外的数据--例如:指定标题
    extra_context = {'title': '学生信息列表'}
    # 定义数据集(查询结果集)--StudentInfo(导入 from helloWorld.models import StudentInfo)
    queryset = StudentInfo.objects.all()
    # 每页展示数据的条数(自行设置)
    paginate_by = 5
    # 设置上下文对象名称(其实就是一个列表的对象),建议使用自定义名称,不然就默认为 objectlist
    context_object_name = 'student_list'

# 定义Detail类,继续继承DetailView(导包引入 from django.views.generic import DetailView)
class Detail(DetailView):
    # 设置模版文件
    template_name = 'student/detail.html'
    # 设置模型外的数据--例如:指定标题
    extra_context = {'title': '学生信息详情'}
    # 设置查询模型--指定model(不然无法查询数据)
    model = StudentInfo
    # 设置上下文对象名称
    context_object_name = 'student'

    # 指定URL中用于获取对象的唯一标识符的参数名称,默认为'pk';
    # 如若在urls.py文件中自定义了名称,则需要添加对应pk_url_kwarg参数为自定义名称
    # pk_url_kwarg = 'id'

# 定义Create类,继续继承CreateView(导包引入 from django.views.generic import CreateView)
class Create(CreateView):
    # 设置模版文件,名称修改为create(有的名称叫"add"也行)
    template_name = 'student/create.html'
    # 设置模型外的数据--例如:指定标题"添加"或者"新增"都行
    extra_context = {'title': '学生信息新增'}
    # 因为是表单,所以需要使用form_class参数来指定form表单
    form_class = StudentForm
    # 执行成功后跳转地址(跳转地址可以是一个新的页面,也可以进入一个列表页面)
    success_url = '/student/list'

# 定义Update类,继续继承UpdateView(导包引入 from django.views.generic import UpdateView)
class Update(UpdateView):
    # 设置模版文件,名称修改为update(自定义)
    template_name = 'student/update.html'
    # 设置模型外的数据--例如:指定标题"编辑"或者"修改"都行
    extra_context = {'title': '学生信息编辑'}
    # 设置查询模型--指定model(不然无法查询数据)--传来一个组件"pk"会自动的去查数据、封装对象、显示到页面上
    model = StudentInfo
    # 因为是表单,所以需要使用form_class参数来指定form表单
    form_class = StudentForm
    # 执行成功后跳转地址(跳转地址可以是一个新的页面,也可以进入一个列表页面)
    success_url = '/student/list'

# 定义Delete类,继续继承DeleteView(导包引入 from django.views.generic import DeleteView)
class   Delete(DeleteView):
    # 设置模版文件,名称修改为delete(自定义)
    template_name = 'student/delete.html'
    # 设置模型外的数据--例如:指定标题"删除"或者"修改"都行
    extra_context = {'title': '学生信息删除'}
    # 设置上下文对象名称
    context_object_name = 'student'
    # 设置查询模型--指定model(不然无法查询数据)--传来一个组件"pk"会自动的去查数据、封装对象、显示到页面上
    model = StudentInfo
    # 执行成功后跳转地址(跳转地址可以是一个新的页面,也可以进入一个列表页面)
    success_url = '/student/list'

# 自定义跳转子类模板页面方法
def to_course(request):
    """
    跳转到子类模板页面
    :param request:
    :return:
    """
    return render(request, 'course.html')

# 定义bookList方法
def bookList(request):
    """
    图书列表查询
    """
    # 查询显示所有数据
    # bookList = BookInfo.objects.all()

    # 使用BookInfo.objects.extra()方法,里面使用where加参数(元组格式),在params加上具体的参数,也是一个元组
    # 如果是模型外的参数(models.py里面没有定义的参数),可以使用BookInfo.objects.select()方法
    # bookList = BookInfo.objects.extra(where=["price>%s"], params=[90])

    # # 使用BookInfo.objects.raw()方法,直接写一个SQL语句
    bookList = BookInfo.objects.raw('SELECT * FROM t_book where price>%s', params=[90])

    # 使用connection方法需要导包引入from django.db import connection模块,返回一个cursor游标
    # 指定cursor游标为CursorDebugWrapper类型,需导包引入from django.db.backends.utils import CursorDebugWrapper模块
    cursor: CursorDebugWrapper = connection.cursor()
    # 然后就可以执行使用execute方法,查询图书价格大于90的记录数
    cursor.execute('select count(*) from t_book where price>90')
    print('预计提取的一个结果是(2,):', cursor.fetchone())

    content_value = {"title": "图书列表", "bookList": bookList}
    return render(request, 'book/list.html', context=content_value)

def bookList2(request):
    """
    多表查询,有正向查询和反向查询
    :param request:
    :return:
    """
    # 正向查询(经常使用)--多的一端BookInfo条件id=2且只查询一条数据,返回图书book并指定类型BookInfo
    book: BookInfo = BookInfo.objects.filter(id=2).first()
    # 能够输出book的所有信息,也能输出所关联的bookType.bookTypeName数据信息
    print(book.bookName, book.bookType.bookTypeName)

    # 反向查询(很少使用)--(导包引入from helloWorld.models import BookTypeInfo)
    # 一的一端BookTypeInfo条件id=1且只查询一条数据,返回图书类别bookType并指定类型BookTypeInfo
    bookType: BookTypeInfo = BookTypeInfo.objects.filter(id=1).first()
    # 输出读取关联的这个图书类别下的所有图书的数据集中第一条数据,固定语法bookinfo_set
    print(bookType.bookinfo_set.first().bookName)
    # 显示这个类别下的所有数据,返回的结果是一个QuerySet数据集,这个类型下有两条数据,
    print(bookType.bookinfo_set.all())
    # 把QuerySet这个数据集转发到页面上就可以进行遍历了
    content_value = {"title": "图书列表", "bookList": bookType.bookinfo_set.all()}
    return render(request, 'book/list.html', context=content_value)

def preAdd(request):
    """
    预处理,添加操作
    :param request:
    :return:
    """
    # 预处理操作需要先找到所有数据
    bookTypeList = BookTypeInfo.objects.all()
    print(f'可自行打印查看bookTypeList是否查询出所有数据:{bookTypeList}')
    # 预处理操作之后要返回到图书添加页面
    content_value = {"title": "图书添加", "bookTypeList": bookTypeList}
    return render(request, 'book/add.html', context=content_value)

def add(request):
    """
    图书添加
    :param request:
    :return:
    """
    # 封装对象
    book = BookInfo()
    # 添加数据
    book.bookName = request.POST.get("bookName")
    book.publishDate = request.POST.get("publishDate")
    book.bookType_id = request.POST.get("bookType_id")
    book.price = request.POST.get("price")
    # 因为是模板,所以直接使用save()方法
    book.save()
    # 数据添加完成之后,可以获取新增数据的主键id
    print(f'数据添加完成之后,获取新增数据的主键id:{book.id}')
    # 添加完成之后,需要返回到列表,直接return返回那个对象
    return bookList(request)

# 修改预处理,定义修改操作方法;页面需要传一个id来确定是要修改哪一条记录
def preUpdate(request, id):
    """
    预处理,修改操作
    :param request:
    :return:
    """
    print("通过path里面的变量,页面需要传一个id来确定是要修改哪一条记录:", id)
    # 根据传过来的id去查询数据,并返回一个book对象
    book = BookInfo.objects.get(id=id)
    print(f'可自行打印查看返回的book对象:{book}')
    bookTypeList = BookTypeInfo.objects.all()
    print(bookTypeList)
    # book对象也需要传到页面上去,到时候页面上就能够取值了
    content_value = {"title": "图书修改", "bookTypeList": bookTypeList, "book": book}
    return render(request, 'book/edit.html', context=content_value)

def update(request):
    """
    图书修改
    :param request:
    :return:
    """
    book = BookInfo()
    book.id = request.POST.get("id")
    book.bookName = request.POST.get("bookName")
    book.publishDate = request.POST.get("publishDate")
    book.bookType_id = request.POST.get("bookType_id")
    book.price = request.POST.get("price")
    book.save()
    return bookList(request)

def delete(request, id):
    """
    图书删除
    :param request:
    :return:
    """
    # 删除指定id数据
    BookInfo.objects.get(id=id).delete()
    # 根据条件删除多条数据
    # BookInfo.objects.filter(price__gte=100).delete()
    # 删除所有数据
    # BookInfo.objects.all().delete()
    return bookList(request)


# 定义转账transfer方法--导包引入from django.db import transaction模块使用事务
@transaction.atomic
def transfer(request):
    """
    模拟转账
    :param request:
    :return:
    """
    # 开启事务,
    sid = transaction.savepoint()
    # 添加异常处理机制
    try:
        # 导包引入from helloWorld.models import AccountInfo方法
        # AccountInfo.objects.filter()方法查找A用户
        a1 = AccountInfo.objects.filter(user='A用户_赵大')
        # 导包引入from django.db.models import F 模块
        a1.update(account = F('account') + 100)
        # 这个"'A用户_赵大'"账户是加account余额,那么对应的"'B 用户_ 钱二'"账户就是减account余额
        b2 = AccountInfo.objects.filter(user='B用户_钱二')
        # 修改操作
        b2.update(account = F('account') - 100 / 0)
        # 提交事务,如果不设置,当程序执行完成后,会自动提交事务
        transaction.savepoint_commit(sid)
    except Exception as e:
        print(f'异常信息为:{e}')
        # 事务回滚--传一个sid表示需要事务回滚哪一个事务
        transaction.savepoint_rollback(sid)
    # 执行完两个操作之后,模拟转账完成,返回到界面
    return HttpResponse("OK!转账完成!")

16、重新运行项目,浏览器输入"http://127.0.0.1:8000/transfer/"测试验证,刷新数据库表,查看数据信息account余额,在 PyCharm 工具运行控制台中也可以看到异常被捕获的信息;

经过测试能够发现,在有异常报错的情况下,它能进行事务回滚操作,在执行事务回滚操作时,会把"第一个 A 用户account余额添加 100"的操作给回滚出来,从而保证了事务的一致性、原子性。

未完待续......

相关推荐
SimonKing1 小时前
QClaw关停之后:一个工具的退场,一段关系的告别
java·后端·程序员
麦壳饼1 小时前
Docker 快速上手:5 分钟运行 SonnetDB
数据库·docker·sonnetdb
小静AI工程实验室1 小时前
robots.txt 不是门禁:RFC 9309 规则、缓存与 Python 爬虫的 10 个边界
爬虫·python·缓存
沉下心来学鲁班1 小时前
DeepAgents 记忆机制:让 AI Agent 拥有“跨对话“的记忆
服务器·人工智能·python
for_ever_love__1 小时前
缓存穿透、击穿、雪崩:三个经典问题与完整解决方案
java·数据库·redis·缓存·哈希算法·布隆过滤器·雪崩
Helix2501 小时前
Python与其他编程语言优劣势对比:2026初学者选型指南
java·python·教程·编程语言·入门·初学者编程选型
麦壳饼1 小时前
CLI 工具安装与使用:sndb 命令行指南
数据库·sonnetdb
龙腾AI白云1 小时前
数字孪生驱动大模型工业知识库:为具身机器人植入领域专业经验
数据库·人工智能·机器学习·知识图谱