Python Pyramid - 使用 SQLAlchemy

在本章中,我们将学习如何使用关系数据库作为 Pyramid Web 应用程序的后端。 Python 可以使用相应的 DB-API 兼容连接器模块或驱动程序与几乎所有关系数据库进行交互。 但是,我们将使用 SQLAlchemy 库作为 Python 代码和数据库之间的接口(我们将使用 SQLite 数据库,因为 Python 对它有内置支持)。 SQLAlchemy 是流行的 SQL 工具包和对象关系映射器。

对象关系映射是一种编程技术,用于在面向对象编程语言中的不兼容类型系统之间转换数据。 通常,像 Python 这样的面向对象语言中使用的类型系统包含非标量类型。 然而,Oracle、MySQL等数据库产品中的数据类型大多是整型、字符串等原始类型。

在 ORM 系统中,每个类都映射到底层数据库中的一个表。 无需自己编写繁琐的数据库接口代码,ORM 会为您处理这些问题,同时您可以专注于系统逻辑的编程。

为了使用 SQLALchemy,我们需要先使用 PIP 安装程序安装库。

pip install sqlalchemy

SQLAlchemy 旨在与为特定数据库构建的 DBAPI 实现一起操作。 它使用方言系统与各种类型的 DBAPI 实现和数据库进行通信。 所有方言都需要安装适当的 DBAPI 驱动程序。

以下是收录的方言 −

  • Firebird

  • Microsoft SQL Server

  • MySQL

  • Oracle

  • PostgreSQL

  • SQLite

  • Sybase


数据库引擎

因为我们要使用 SQLite 数据库,所以我们需要为我们的数据库创建一个名为 test.db 的数据库引擎。从 sqlalchemy 模块导入 create_engine() 函数。

from sqlalchemy import create_engine
from sqlalchemy.dialects.sqlite import *
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args = {"check_same_thread": False})

为了与数据库进行交互,我们需要获取它的句柄。 Session 会话对象是数据库的句柄。 Session 会话类使用 sessionmaker() 定义 - 一个绑定到引擎对象的可配置会话工厂方法。

from sqlalchemy.orm import sessionmaker, Session
session = sessionmaker(autocommit=False, autoflush=False, bind=engine)

接下来,我们需要一个声明性基类,它在声明性系统中存储类目录和映射表。

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

Model 模型类

StudentsBase 的子类,映射到数据库中的 students 表。 Students 类中的属性对应于目标表中列的数据类型。 请注意,id 属性对应于 book 表中的主键。

class Students(Base):
   __tablename__ = 'student'
   id = Column(Integer, primary_key=True, nullable=False)
   name = Column(String(63), unique=True)
   marks = Column(Integer)
Base.metadata.create_all(bind=engine)

create_all() 方法在数据库中创建相应的表。 可以使用 SQLiteStudio 等 SQLite Visual 工具进行确认。

SQLiteStudio

我们现在将定义视图函数以对上述数据库中的学生表执行 CRUD 操作(即添加、显示、修改和删除行)。


添加新学生记录

首先,我们将创建一个 HTML 表单模板,供用户输入学生数据并定义呈现该模板的视图。 这是 myform.html 模板

示例

<html>
<body>
   <form method="POST" action="http://localhost:6543/add">
   <p>Student Id: <input type="text" name="id"/> </p>
   <p>student Name: <input type="text" name="name"/> </p>
   <p>Percentage: <input type="text" name="percent"/> </p>
   <p><input type="submit" value="Submit"> </p>
</body>
</html>

在 Pyramid 应用程序代码中,定义 index() 视图函数来呈现上面的表单。

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.view import view_config

@view_config(route_name='index', renderer='templates/myform.html')
def index(request):
   return {}

在应用程序配置中,为这个视图注册带有"/new"模式的路由 −

if __name__ == '__main__':
   with Configurator() as config:
      config.include('pyramid_jinja2')
      config.add_jinja2_renderer(".html")
      config.add_route('index', '/new')
      config.scan()
      app = config.make_wsgi_app()
   server = make_server('0.0.0.0', 6543, app)
   server.serve_forever()

由于上述模板中的 HTML 表单是通过 POST 操作提交到 /add URL,我们需要将此 URL 映射到添加路由并注册 add() 视图,该视图将表单数据解析到 Students 类的对象中。 该对象被添加到数据库会话中,并通过调用其 commit() 方法完成操作。

@view_config(route_name='add', request_method='POST')
def add(request):
   id=request.POST['id']
   name=request.POST['name']
   percent=int(request.POST['percent'])
   student=Students(id=id, name=name, percent=percent)
   session.add(student)
   session.commit()
   return HTTPFound(location='http://localhost:6543/')

确保在配置中添加了 add 路由,映射到 /add URL 模式。

config.add_route('add','/add')

输出

如果我们启动服务器并在浏览器中打开http://localhost:6543/new,Entry 表单将显示如下 −

学生详细信息

填写表格并按"submit"提交按钮。 将调用 add() 视图并将新记录添加到 students 学生表中。 重复该过程几次以添加几条记录。 这是一个示例数据 −

学生数据库

显示所有记录列表

Students 模型的所有对象(对应students表中的行)都是通过查询模型得到的。

rows = session.query(Students).all()

每一行都转换成一个dict对象,所有的都附加到一个dict对象列表中,作为上下文返回给list.html模板,以HTML模板的形式显示。 该过程由与列表路由关联的 showall() 视图函数执行。

@view_config(route_name='list', renderer='templates/marklist.html')
def showall(request):
   rows = session.query(Students).all()
   students=[]
   for row in rows:
      students.append({"id":row.id, "name":row.name, "percent":row.percent})
   return{'students':students}

示例

marklist.html 模板将学生列表呈现为 HTML 表格。 其HTML/jinja2脚本如下 −

<html>
<body>
<table border=1>
   <thead> 
      <tr>
         <th>Student ID</th>
         <th>Student Name</th>
         <th>percentage</th>
         <th>Edit</th>
         <th>Delete</th>
      </tr> 
   </thead>
   <tbody>
      {% for Student in students %}
         <tr>
         <td>{{ Student.id }}</td> <td>{{ Student.name }}</td>
         <td>{{ Student.percent }}</td>
         <td><a href="/show/{{ Student.id }}">edit</a></td>
         <td><a href="/delete/{{ Student.id }}">delete</a></td>
         </tr>
      {% endfor %}
   </tbody>
</table>
<h3><a href="http://localhost:6543/new">Add new</a></h3>
   </body>
</html>

在配置中添加列表路由并使用'/' URL 注册它。

config.add_route('list', '/')

输出

启动服务器后在浏览器中打开http://localhost:6543/。 将显示学生表中现有记录的列表。

新增

注意最后两列中的超链接。 例如,"id=1"之前的"edit"链接指向http://localhost:6543/show/1。 这些链接旨在执行更新和删除操作。


更新现有记录

在 /show/1 URL 中,有一个尾随路径参数。 它被映射到配置中的"show"路由。

config.add_route('show', '/show/{id}')

此路由调用 show() 函数。 它获取与给定 id 参数对应的记录,用其内容填充 HTML 表单,并允许用户更新名称和/或百分比字段的值。

@view_config(route_name='show', renderer='templates/showform.html')
def show(request):
   id=request.matchdict['id']
   row = session.query(Students).filter(Students.id == id).first()
   student={'id':row.id, 'name':row.name, 'percent':row.percent}
   return {'student':student}

示例

showform.html模板的HTML/jinja2代码如下 −

<html>
<body>
   <form method="POST" action="http://localhost:6543/update">
   <p>Student Id: <input type="text" name="id" value="{{ student.id }} " readonly/> </p>
   <p>student Name: <input type="text" name="name" value="{{ student.name }}"/> </p>
   <p>Percentage: <input type="text" name="percent" value="{{ student.percent }}"/> </p>
   <p><input type="submit" value="Submit"> </p>
</body>
</html>

输出

让我们用 id=3 更新记录。 单击相应的编辑链接导航到 http://localhost:6543/show/3

百分比

更改标记文本字段中的值并按提交。 表单被重定向到 /update URL 并调用 update() 视图。 它获取提交的数据并更新相应的对象,从而更新 students 表中的基础行。

@view_config(route_name='update', request_method='POST')
def update(request):
   id=int(request.POST['id'])
   student = session.query(Students).filter(Students.id == id).first()
   student.percent=int(request.POST['percent'])
   session.commit()
   return HTTPFound(location='http://localhost:6543/')

return 语句将浏览器重定向回"/"URL,它指向 list() 函数并显示更新后的标记列表。

更新标记列表

确保更新路由在运行前添加到配置中。

config.add_route('update', '/update')

删除记录

要删除与 marklist 表中的行对应的记录,请点击最后一列中的删除链接。 例如,点击第 3 行的 Delete 会发出 http://localhost:6543/delete/3 URL 并调用以下视图函数 −

@view_config(route_name='delete', renderer='templates/deleted.html')
def delete(request):
   id=request.matchdict['id']
   row = session.query(Students).filter(Students.id == id).delete()
   return {'message':'Redcord has been deleted'}

示例

从 URL 中解析出的路径参数对应的对象被删除,相应的消息由以下模板 - deleted.html 呈现 −

<html>
<body>
   <h3>{{ message}}</h3>
   <br><br>
   <a href="http://localhost:6543/">Click here to refresh the mark list</a>
</body>
</html>

显然,删除路由必须添加到应用程序配置注册表中。

config.add_route('delete', '/delete/{id}')

输出

记录删除动作结果如下图 −

记录

按照以下步骤执行上述活动 −

  • 在 Pyramid 虚拟环境中创建名为 testapp 的文件夹

  • testapp 中,创建 templates 文件夹。

  • 在 testapp 中创建一个空白的 __init__.py,使其成为一个包。

  • 将 marklist.html、myform.html、showform.html 和 deleted.html 文件放在"testapp\templates"文件夹中。 上面已经给出了这些文件的代码。

  • testapp 中将以下代码保存为 models.py

from sqlalchemy.dialects.sqlite import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import Session
from sqlalchemy import Column, Integer, String
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"

Base = declarative_base()

class Students(Base):
      __tablename__ = 'student'
   id = Column(Integer, primary_key=True, nullable=False)
   name = Column(String(63), unique=True)
   percent = Column(Integer)
   
def getsession():
   engine = create_engine(
      SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
   )
   Base.metadata.create_all(bind=engine)
   Session = sessionmaker(bind = engine)
   session = Session()
   return session
  • 将以下代码保存为 views.pytestapp 文件夹中。

from pyramid.response import Response
from pyramid.view import view_config
from pyramid.httpexceptions import HTTPFound
from models import Students
from main import session

@view_config(route_name='list', renderer='templates/marklist.html')
def showall(request):
   rows = session.query(Students).all()
   students=[]
   for row in rows:
      students.append({"id":row.id, "name":row.name, "percent":row.percent})
      return{'students':students}
      
@view_config(route_name='index', renderer='templates/myform.html')
def index(request):
   return {}
   
@view_config(route_name='add', request_method='POST')
def add(request):
   id=request.POST['id']
   name=request.POST['name']
   percent=int(request.POST['percent'])
   student=Students(id=id, name=name, percent=percent)
   session.add(student)
   session.commit()
   return HTTPFound(location='http://localhost:6543/')
   
@view_config(route_name='update', request_method='POST')
def update(request):
   id=int(request.POST['id'])
   student = session.query(Students).filter(Students.id == id).first()
   student.percent=int(request.POST['percent'])
   session.commit()
   return HTTPFound(location='http://localhost:6543/')

@view_config(route_name='show', renderer='templates/showform.html')
def show(request):
   id=request.matchdict['id']
   row = session.query(Students).filter(Students.id == id).first()
   student={'id':row.id, 'name':row.name, 'percent':row.percent}
   return {'student':student}
   
@view_config(route_name='delete', renderer='templates/deleted.html')
def delete(request):
   id=request.matchdict['id']
   row = session.query(Students).filter(Students.id == id).delete()
   return {'message':'Redcord has been deleted'}
  • 将以下代码保存为 testapp 文件夹中的 main.py。

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from models import getsession
session=getsession()

if __name__ == '__main__':
   with Configurator() as config:
      config.include('pyramid_jinja2')
      config.add_jinja2_renderer(".html")
      config.add_route('list', '/')
      config.add_route('index', '/new')
      config.add_route('add','/add')
      config.add_route('show', '/show/{id}')
      config.add_route('update', '/update')
      config.add_route('delete', '/delete/{id}')
      config.scan('testapp')
      app = config.make_wsgi_app()
   server = make_server('0.0.0.0', 6543, app)
   server.serve_forever()    
  • 从命令提示符运行 main.py

Python main.py
  • 在浏览器窗口中使用 http://localhost:6543/ URL。 将显示只有标题而没有记录的表格。

  • 按照表格下方的添加新链接添加记录。

  • 单击表格中的"Edit"链接以更新记录。

  • 单击表中的"Delete"链接以删除所选记录。