这篇博客为个人学习记录,不定期更新
创建方法:终端输入python manage.py startapp news
,其中news是新的应用名
注册创建的APP,在项目的配置文件中(settings.py)找到INSTALLED_APPS列表,并在该列表中添加项目名,如下:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'music',
'sport',
'news',
]
最后的news即为新添加的应用
创建分布式路由的目的是为了让单个应用有单个应用的专属路由,不用一直在项目主路由中添加地址,正常的逻辑是创建了新的应用后,应该在项目主路由中为该应用配置专属的路由,而这个新建应用的路由则在自己的路由文件中去添加
在项目路由中添加新的应用路由地址,如下:
urlpatterns = [
path('admin/', admin.site.urls),
path('img', views.test_static),
path('music/', include('music.urls')),
path('sport/', include('sport.urls')),
path('news/', include('news.urls')),
]
列表的最后即为news应用的专属路由,而使用include方法,表明以后news的路由应该到news自己的urls.py文件中去添加
在新建应用下,创建一个urls.py来添加本应用的路由地址和对应的视图函数,如下:
from django.urls import path
from news import views
urlpatterns = [
path('index', views.index_view),
]
index是news应用的地址,这种情况下,访问域名127.0.0.1:8000/news/index
,浏览器返回的就是视图函数views中index_view方法所写的内容
模型:
是Python的一个类,要求必须继承自django.db.models.Model, 一个模型类代表数据库中的一张数据表,模型类的每个熟悉代表数据库的一个字段,模型是数据增删改查的接口,用来操作数据库
ORM框架:
对象关系映射,允许使用类和对象对数据库进行操作,避免通过SQL语句操作数据库
可以从cmd终端进入,也可以从编译器终端进入,指令都一样
mysql -uroot -p
接下来在终端输入密码
退出在终端输入quit即可
通过models.py生成数据表,如下
from django.db import models
# Create your models here.
class Book(models.Model):
title = models.CharField("书名", max_length=50, default='')
price = models.DecimalField("定价", max_digits=7, decimal_places=2, default=0.0)
通过上面这个文件,执行后应该会在数据库中生成一张Book数据表,这张表有两个字段,分别是title和price,但仅写完这个文件是不够的,需要在终端执行如下命名:
python manage.py makemigrations
,将应用下的models.py文件生成一个中间件,并保存在migration文件夹中python manage.py migrate
,将每个应用下的migration目录中的中间文件同步回数据库使用内部类给模型赋予属性,比如修改表名
class Meta:
db_table = 'book'
models.Model模型类中的objects对象被继承,增删改查都是通过这个objects对象来实现
终端进入Django shell的命令:
python manage.py shell
第一种方法:
MyModel.models.Model.objects.create()
第二种方法:
创建模型对象实例,调用save()保存
obj = MyModel(attr1=value1, attr2=value2....)
obj.attr = value
obj.save()
objects.all() == select * from tabel,返回QuerySet容器对象,内部存放模型实例
一般情况下,为了更好的显示查询结果,都要重新____str____函数,这是为了设置模型的返回值
以下是查询示例
from bookstore.models import Book
al = Book.models.all()
重写str函数
def __str__(self):
return '%s_%s_%s_%s' % (self.title, self.pub, self.price, self.market_price)
查询结果
, , , , ]>
objects.values(‘列1’, ‘列2’),查询部分列数据,返回QuerySet字典列表,等同于
select lie1, lie2
from Book
objects.values_list(‘列1’, ‘列2’),查询部分列数据,返回QuerySet元组列表
order_by()查询结果排序
ORM综合练习
目的:将数据库中,所有的book信息显示在all_book页面上,最终结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aUlMgjO7-1651579758556)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20220429095725708.png)]
创建分布式路由:在主项目的urls.py文件中添加bookstore应用的路由,如下
from django.contrib import admin
from django.urls import path, include
from mysite import views
urlpatterns = [
path('admin/', admin.site.urls),
path('img', views.test_static),
path('music/', include('music.urls')),
path('sport/', include('sport.urls')),
path('news/', include('news.urls')),
path('bookstore/', include('bookstore.urls')),
]
最后一行即为bookstore应用的分布式路由
在bookstore应用中创建该应用的路由,在应用目录下新建一个urls.py,填入一下路由信息
from django.urls import path
from bookstore import views
urlpatterns = [
path('all_book', views.all_book),
]
也就是当我访问http://127.0.0.1:8000/bookstore/all_book
时指向的文件和视图函数
视图函数,在views.py文件中添加查询和渲染网页的代码
from django.shortcuts import render
from .models import Book
# Create your views here.
def all_book(request):
all_book = Book.objects.all()
return render(request, 'bookstore/all_book.html', locals())
使用all()方法查询到所有的数据,并把查到的all_book传递给模板文件夹(templates)下bookstore文件夹中的all_book.html文件,locals()函数将查询到的局部变量转换成字典数据,也就是把all_book转换成字典,讲真,我真没理解这个locals到底怎么用的
模板文件,在模板中填写网页展示的内容,并接受视图函数传递过来的数据,并进行展示
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>查看所有表格title>
<style>
td, th {
border: 1px solid;
}
.mt {border-collapse: collapse;}
style>
head>
<body>
<table class="mt">
<tr>
<th>idth>
<th>titleth>
<th>pubth>
<th>priceth>
<th>market_priceth>
<th>opth>
tr>
{% for book in all_book %}
<tr>
<td>{{ book.id }}td>
<td>{{ book.title }}td>
<td>{{ book.pub }}td>
<td>{{ book.price }}td>
<td>{{ book.market_price }}td>
<td>
<a href="#" >更新a>
<a href="#">删除a>
td>
tr>
{% endfor %}
<tr>tr>
table>
body>
html>
这里关键是要理解那一段for循环的写法
不写CSS样式显示出来的表格很丑,没有边框,我在样式中改了一下边框,在两个地方
其实写到这里,Django的作用和整个操作流程,已经大致显示出来了,有必要做个小结:
filter()方法,返回QuerySet
MyModel.objects.filter(attr1=value1, attr2=valur2…)
多个属性在一起时为“与”关系
exclude(条件),不包含此条件的数据集
get(条件),返回单条数据,如果给定的条件超过一条,或者没有数据,都会报错
查询示例:
Book.objects.filter(id__exact=1)
等同于
select * from Book where id = 1
由字段名__谓词组成
Book.objects.filter(id__in = [1,3,5])
Book.objects.filter(id__range = (20,40))
流程:查(使用get)->改->存(使用save)
单条数据更新的具体示例:
# 单条数据更新
b1 = Book.objects.get(id=1)
b1.price = 22
b1.save()
批量更新,直接将查询结果使用update方法更新
# 批量更新
b1 = Book.objects.filter(price__gt=50)
b1.update(market_price=100)
将所有定价大于50的书市场价均设置为100
前面的图书管理界面上,最后一列“op”绑定了两个空链接,现在先来实现更新功能
一步步来
views.py中添加的内容
def update_book(request, book_id):
try:
book = Book.objects.get(id=book_id)
except Exception as e:
print('--update book error is %s' % (e))
return HttpResponse("书本不存在")
if request.method == 'GET':
return render(request, 'bookstore/update_book.html', locals())
elif request.method == 'POST':
price = request.POST['price']
market_price = request.POST['market_price']
book.price = price
book.market_price = market_price
book.save()
return HttpResponseRedirect('/bookstore/all_book')
这个视图函数还挺复杂的,记录一下逻辑
既然是要更改数据,就说明要像数据库传递数据,那么一般是post请求,但是超链接是get请求,所以实际上要对请求进行判断
首先,如果是get请求,就跳转到路由指定的模板文件上,并通过local()将get到的局部变量打包成字典,传递给模板文件update_book.html,模板文件见下面小节(添加模板文件)
如果是post请求,也就是说要通过页面给后台传递参数了,那么把要实现的功能在这里写好,也就是在这里实现数据的更新
查数据
改数据
存数据
返回HttpResponse实体,代码中是重定向,指定了路由的相对路径,绝对路径应该是http://127.0.0.1:8000/bookstore/all_book
强调一下,这里的return必须写,至于是render、HttpResponse还是HttpResponseRedirect根据业务需求,不然项目不知道下一步该往哪里去
逻辑上已经没什么问题,但有一点要注意,查询book是用的get方法,get方法具有唯一性,极易报错,所以有必要进行异常检测
视图函数中我传递了一个book_id参数,这点非常重要。首先这个参数是我们要查询的书名,其次,路由中也要用到,下面会讲到
实际上就是修改一下表格中“更新”的超链接地址,我上一下完整的内容,后面与此类似,就不上完整的内容了
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>查看所有表格title>
<style>
{#为表格增加边框#}
td, th {
border: 1px solid;
}
{#删掉表格单元格之间的空白#}
.mt {border-collapse: collapse;}
style>
head>
<body>
<table class="mt">
<tr>
<th>idth>
<th>titleth>
<th>pubth>
<th>priceth>
<th>market_priceth>
<th>opth>
tr>
{% for book in all_book %}
<tr>
<td>{{ book.id }}td>
<td>{{ book.title }}td>
<td>{{ book.pub }}td>
<td>{{ book.price }}td>
<td>{{ book.market_price }}td>
<td>
<a href="/bookstore/update_book/{{ book.id }}" >更新a>
<a href="#">删除a>
td>
tr>
{% endfor %}
<tr>tr>
table>
body>
html>
视图函数完成后,就要进行前端页面的设计了,前面的逻辑是点击超链接(get请求)的话,就去渲染网页文件,并向网页传递book参数
update_book.html里的内容
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>更改书籍title>
head>
<body>
<form action="/bookstore/update_book/{{ book.id }}" method="post">
<p>
title <input type="text" value="{{ book.title }}" disabled="disabled" />
p>
<p>
publish <input type="text" value="{{ book.pub }}" disabled="disabled" />
p>
<p>
price <input type="text" value="{{ book.price }}" name="price" />
p>
<p>
market_price <input type="text" value="{{ book.market_price }}" name="market_price" />
p>
<p>
<input type="submit" value="更新" />
p>
form>
body>
html>
看看界面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JzrhwxiX-1651579758558)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20220429142029973.png)]
界面还有待优化,这是CSS的事情,暂时不进行美化
很明显是表单数据,因此使用form标签,form的action参数是表单数据请求指向的地址,也就是路由,这个路由的写法也是用的Django语法
注意get请求向这个网页文件传递了book参数,也就是通过book_id获取到的book信息,因此book.id就是数据库中的id字段,title,pub等等也是,4个input标签中各填入了book的4个参数,通过value匹配,用的也是Django的语法,其中title,pub是不可修改的(“disabled” )
最后一个input标签是提交按钮,也就是点击后发送post请求到数据库,并重定向到视图函数中写好的地址
目前数据库中是有5本书的,也就是说,如果按绝对路径来定义路由的话,要定义5个路由,分别是
path('update_book/1', views.update_book),
path('update_book/2', views.update_book),
path('update_book/3', views.update_book),
path('update_book/4', views.update_book),
path('update_book/5', views.update_book),
这么写明显不合理,如果有100个,是不是要写100行
这里有多种替代方式
这里使用的是path转换器,用path转换器记得在视图函数中传递参数,这点非常重要,前面就已经提到了,视图函数中传递了一个book_id这个参数,视图函数通过这个book_id进行了一系列的操作,使用path路由的时候也要用到,正确的写法如下
urls.py中的内容
from django.urls import path
from bookstore import views
urlpatterns = [
path('all_book', views.all_book),
path('update_book/' , views.update_book),
]
添加了一个path('update_book/
path转换器的精髓就在这里,接收视图函数传递的形参,使用<>来转换,前面是这个数据的类型,后面是参数,有点像java中的泛型
这里算是完成了一个相对初学者来说,比较复杂的功能,涉及到的技术栈有:
单条删除:先get(),然后delete()
批量删除:filter()查找,然后delete()删除
伪删除:在表中添加字段is_active,布尔类型,做伪删除时,把值设为false,显示的时候只显示is_active=True的数据,这样就实现了伪删除操作
ORM查询中,表格中还有个删除功能没实现,现在补充一下
过程如下
class Book(models.Model):
def __str__(self):
return '%s_%s_%s_%s' % (self.title, self.pub, self.price, self.market_price)
title = models.CharField("书名", max_length=50, default='', unique=True)
pub = models.CharField("出版社", max_length=100, default='')
price = models.DecimalField("定价", max_digits=7, decimal_places=2, default=0.0)
market_price = models.DecimalField("零售价", max_digits=7, decimal_places=2, default=0.0)
# info = models.CharField("描述", max_length=100, default='')
is_active = models.BooleanField('是否活跃', default=True)
添加的是最后一行,后面删除实际上是将这个字段设置为false
在views.py中添加以下函数:
def delete_book(request):
book_id = request.GET.get('book_id')
if not book_id:
return HttpResponse('---book_id不存在')
try:
book = Book.objects.get(id=book_id, is_active=True)
except Exception as e:
print('delete book get error %s' % e)
# print('--update book error is %s' % (e))
return HttpResponse("书本不存在")
book.is_active = False
book.save()
return HttpResponseRedirect('/bookstore/all_book')
注意,这里在函数中没有传形参book_id了,说明路由配置不是之前的那种了,这里跟着老师使用了第二种路由配置方式**,就是通过request.GET.get()方法获取请求中的参数,注意,get方法是寻找名为name(本例中为‘book_id’)的GET参数,而且如果参数没有提交,返回一个空的字符串,如果我点击第5条图书,那么请求的链接应该是http://127.0.0.1:8000/bookstore/delte_book?id=5
,也就是说,我的book_id=5**,这一点的理解很重要,不然会陷入一个理解的死循环,就像月读一样,为什么请求链接是这样的呢,那就要看我的模板文件(all_book.html)了。
这里的模板文件只涉及all_book.html,因为删除后直接重定向到这个文件了,所以对比上面的更新要简单一点,在模板文件中修改成下面的内容
<td>
<a href="/bookstore/update_book/{{ book.id }}" >更新a>
<a href="/bookstore/delete_book?book_id={{ book.id }}" onclick="return confirm('是否确认删除');">删除a>
td>
回到上面的问题,我增加了一个传递参数的符号?,表明后面的book_id是要传的参数,其实上面get()方法得到的参数就传到了这里,也就是超链接应该是bookstore/delete_book?book_id=5
然后再回到视图函数,后面是删除操作,说是删除,其实不如理解为修改,因为执行的是伪删除操作,注意修改后需要save一下
注意这里的路由不再是用path转换器了,而是直接传递参数,写法如下
from django.urls import path
from bookstore import views
urlpatterns = [
path('all_book', views.all_book),
path('update_book/' , views.update_book),
path('delete_book', views.delete_book),
]
最后一行就是添加的路由,后面通过?传参,理解上相对第一种更麻烦一点,我推荐使用第一种
先上一下数据库中book表
id | title | price | market_price | pub | is_active |
---|---|---|---|---|---|
1 | python | 25.00 | 50.00 | 清华大学出版社 | 1 |
2 | Django | 70.00 | 75.00 | 清华大学出版社 | 1 |
3 | JQuery | 90.00 | 85.00 | 机械工业出版社 | 1 |
4 | Linux | 80.00 | 65.00 | 机械工业出版社 | 1 |
5 | HTML5 | 90.00 | 105.00 | 清华大学出版社 | 1 |
F对象代表数据库中某条记录的字段的信息,是对数据库中的字段值在不获取的情况下进行操作,可以解决并发的问题,类似于给数据库加锁,把并发事件转为串联事件,第二个作用的用于字段之间的比较,比如比较售价大于定价的书籍,写法如下:
Book.objects.filter(market_price__gt=F('prive'))
使用复杂的逻辑或、逻辑非等操作,可以灵活组合
语法
Book.objects.filter(Q(price__lt=20)|Q(pub="清华大学出版社"))
聚合函数: Sum Avg Count Max Min
语法:Book.objects.aggregate(结果变量名=Sum(列))
结果变量名就是sql中的查询结果别名
示例:
Book.objects.aggregate(res=Count('id'))
返回:
{res: 4}
语法:QuerySet.annotate(结果变量名=Sum(列))
sql中的group by
示例——统计每个出版社有多少本书:
pub_set = Book.objects.values('pub')
pub_set_count = put_set.annotate(mycount=Count('pub'))
语法:Book.objects.raw(sql语句, 拼接参数)
个人对基础的sql语句是比较熟的,因此个人喜欢用原生的数据库操作,按理说也可以用pandas操作
但是原生的有很多缺陷和漏洞,SQL注入
解决方法:把参数放入拼接参数中
语法:
from django.db import connection
with connection.cursor() as cur:
cur.execute('sql语句', '拼接参数')
还是推荐使用ORM
创建超级用户指令
python manage.py createsuperuser
这里需要设置用户名、邮箱和密码
进入用户管理系统地址
http://127.0.0.1:8000/admin
根据上面设置的用户名密码登录,可以进入Django的后台管理系统
目前模型文件(models.py)文件中有Book和Author两个类,也就是数据库中的两张表,如果要自己定义的模型类也能在/admin后台管理系统中显示和管理,需要将自己的类注册到后台管理界面
方法步骤:
from .models import Book
admin.site.register(Book)
可以为后台管理界面添加便于操作的新功能,继承自django.contrib.admin中的ModelAdmin类
什么意思,就是我们自建的模型展现形式很丑,而自带的展示起来更加美观
使用方法:
在应用的admin.py里进行定义,class XXXManager(admin.ModelAdmin)
绑定注册模型管理器和模型类
from django.contrib import admin
from .models import *
# Register your models here.
class BookManager(admin.ModelAdmin):
list_display = ['id', 'title', 'pub', 'price', 'market_price', 'is_active']
admin.site.register(Book, BookManager)
list_display参数是将数据以表格的形式展示是管理后台上,列表中有几个数据,就展示多少个列
除了list_display参数,还有以下几个常用的参数:
模型(models.py)中添加Meta类,见前文**ORM基础字段及选项中**的模型类Meta
示例:
class Book(models.Model):
def __str__(self):
return '%s_%s_%s_%s_%s' % (self.title, self.pub, self.price, self.market_price, self.is_active)
title = models.CharField("书名", max_length=50, default='', unique=True)
pub = models.CharField("出版社", max_length=100, default='')
price = models.DecimalField("定价", max_digits=7, decimal_places=2, default=0.0)
market_price = models.DecimalField("零售价", max_digits=7, decimal_places=2, default=0.0)
# info = models.CharField("描述", max_length=100, default='')
is_active = models.BooleanField('是否活跃', default=True)
class Meta:
verbose_name = '图书' # 英文中的单数
verbose_name_plural = verbose_name # 英文中的复数,如果不该的话,会在中文字符后面多一个s
通常不会把所有的数据都放在同一张表,不易于扩展,如一个学生可以报多个课程,一个课程可以有多个学生报名
语法:OneToOneField(类名,on_delete=xxx),on_delete是级联删除
新概念:
外键——如果A表中的X字段引用了B表中的主键,A表叫做子表,B表叫做主表,X字段称为A表的外键,外键描述的是表之间的关系。级联删除——如员工表中一项数据是部专门ID,部门ID是部门表的主键,如果是级联删除,当删除了部门A的时候,会把所有属于部门A的员工都属给删除。
级联删除on_delete=models.CASCADE实例,步骤,新建应用oto,在设置文件中添加这个应用,然后在oto应用的模型文件中创建author及wife表
from django.db import models
# Create your models here.
class Author(models.Model):
name = models.CharField('姓名', max_length=11)
class Meta:
verbose_name = '作者'
verbose_name_plural = verbose_name
class Wife(models.Model):
name = models.CharField('姓名', max_length=11)
author = models.OneToOneField(Author, on_delete=models.CASCADE) # 级联删除,与Author表共生死
class Meta:
verbose_name = '作者之妻'
verbose_name_plural = verbose_name
创建数据:
from oto.models import *
a1 = Author.objects.create(name='hg') # Author中的表数据创建
w1 = Wife.objects.create(name='zl', author=a1) # wife表中的数据创建
# 还可以用下面的方式创建外键连接
w1 = Wife.objects.create(name='zl', author_id=a1.id)
正向查询方法:
wife = Wife.objects.get(name='zl')
# 关联到wife的丈夫
wife.author.name
反向查询方法:
author = Author.objects.get(name='hg')
# 关联到author的妻子
author.wife.name
反向查询时,没有外键的一方,可以调用反向属性查询到关联的另一方,author虽然没有wife的外键,但Django在创建Wife模型时,默认创建反向属性