搭环境
1、下载python
2、下载django
3、下载mysql
4、安装mysql驱动
5、创建虚拟环境
django部署
在开发机上的准备工作:
- 确认项目没有bug。
- 用
pip freeze > requirements.txt
将当前环境的包导出到requirements.txt
文件中,方便在部署的时候安装。 - 将项目上传到服务器上的
/srv
目录下。这里以git
的形式为例。scp
- git init
- git remote add origin xxx.git
- git add .
- git commit -m ‘first commit’
- git pull origin master --allow-unrelated-histories
- git push origin master
在服务器上的准备工作:
- 安装好项目用到的
Python
。- sudo apt install python
- sudo apt install python-pip
- pip install --upgrade pip
- 安装
virtualenv
以及virutalenvwrapper
。并创建虚拟环境。- pip install virtualenv
- pip install virtualenvwrapper
- sudo apt install vim
- vim ~/.bashrc 进入文件中,填入以下两行代码:
export WORKON_HOME=$HOME/.virtualenvs source /usr/local/bin/virtualenvwrapper.sh
- source ~/.bashrc
- 安装
git
:sudo apt install git
- 为了方便XShell或者CRT连接服务器,建议安装
OpenSSH
:sudo apt install openssh-server openssh-client service ssh restart
- 安装
MySQL
服务器和客户端:sudo apt install mysql-server mysql-client sudo apt-get install libmysqld-dev
- 进入虚拟环境中,然后进入到项目所在目录,执行命令:
pip install -r requirements.txt
,安装好相应的包。 - 在
mysql
数据库中,创建相应的数据库。 - 执行
python manage.py migrate
命令,将迁移文件,映射到数据库中,创建相应的表。 - 执行
python manage.py runserver 0.0.0.0:8000
,然后在你自己电脑上,在浏览器中输入http://你的服务器的ip地址:8000/
,访问下网站所有页 面,确保所有页面都没有错误。 - 设置
ALLOW_HOST
为你的域名,以及ip地址。 - 设置
DEBUG=False
,避免如果你的网站产生错误,而将错误信息暴漏给用户。 - 收集静态文件:
python manage.py collectstatic
。
安装uwsgi
- uwsgi是一个应用服务器,非静态文件的网络请求就必须通过他完成,他也可以充当静态文件服务器,但不是他的强项。uwsgi是使用python编写的,因此通过
pip install uwsgi
就可以了。(uwsgi必须安装在系统级别的Python环境中,不要安装到虚拟环境中)。 - 使用命令
uwsgi --http :8000 --module zhiliaoketang.wsgi --vritualenv=/root/.virtualenvs/django-env-py2
。用uwsgi
启动项目,如果能够在浏览器中访问到这个页面,说明uwsgi
可以加载项目了。
编写uwsgi配置文件:
在项目的路径下面,创建一个文件叫做zhiliaoketang_uwsgi.ini
的文件,然后填写以下代码:
[uwsgi]
# Django相关的配置
# 必须全部为绝对路径
# 项目的路径
chdir = /srv/zhiliaoketang
# Django的wsgi文件
module = zhiliaoketang.wsgi
# Python虚拟环境的路径
home = /root/.virtualenvs/django-env-py2
# 进程相关的设置
# 主进程
master = true
# 最大数量的工作进程
processes = 10
# socket文件路径,绝对路径
socket = /srv/zhiliaoketang/zhiliaoketang.sock
# 设置socket的权限
chmod-socket = 666
# 退出的时候是否清理环境
vacuum = true
然后使用命令uwsgi --ini zhiliaoketang.ini
,看下是否还能启动这个项目。
安装nginx:
- nginx是一个web服务器。用来加载静态文件和接收http请求的。通过命令
sudo apt install nginx
即可安装。 nginx
常用命令:- 启动nginx:service nginx start
- 关闭nginx:service nginx stop
- 重启nginx:service nginx restart
收集静态文件:
静态文件应该让nginx来服务,而不是让django来做。首先确保你的settings.py
文件中有一个STATIC_ROOT
配置,这个配置应该指定你的静态文件要放在哪个目录下。那么我们可以执行以下命令:python manage.py collectstatic
来收集所有静态文件,将这些静态文件放在指定的目录下。
编写nginx配置文件:
在/etc/nginx/conf.d
目录下,新建一个文件,叫做zhiliaoketang.conf
,然后将以下代码粘贴进去:
upstream zhiliaoketang {
server unix:///srv/zhiliaoketang/zhiliaoketang.sock;
}
# 配置服务器
server {
# 监听的端口号
listen 80;
# 域名
server_name 192.168.0.101;
charset utf-8;
# 最大的文件上传尺寸
client_max_body_size 75M;
# 静态文件访问的url
location /static {
# 静态文件地址
alias /srv/zhiliaoketang/static_dist;
}
# 最后,发送所有非静态文件请求到django服务器
location / {
uwsgi_pass zhiliaoketang;
# uwsgi_params文件地址
include /etc/nginx/uwsgi_params;
}
}
写完配置文件后,为了测试配置文件是否设置成功,运行命令:service nginx configtest
,如果不报错,说明成功。
每次修改完了配置文件,都要记得运行service nginx restart
。
使用supervisor配置:
让supervisor管理uwsgi,可以在uwsgi发生意外的情况下,会自动的重启。
supervisor
的安装:在系统级别的python环境下pip install supervisor
。- 在项目的根目录下创建一个文件叫做
zlkt_supervisor.conf
。内容如下:# supervisor的程序名字 [program:mysite] # supervisor执行的命令 command=uwsgi --ini zlkt_uwsgi.ini # 项目的目录 directory = /srv/zhiliaoketang # 开始的时候等待多少秒 startsecs=0 # 停止的时候等待多少秒 stopwaitsecs=0 # 自动开始 autostart=true # 程序挂了后自动重启 autorestart=true # 输出的log文件 stdout_logfile=/srv/zhiliaoketang/log/supervisord.log # 输出的错误文件 stderr_logfile=/srv/zhiliaoketang/log/supervisord.err [supervisord] # log的级别 loglevel=info # 使用supervisorctl的配置 [supervisorctl] # 使用supervisorctl登录的地址和端口号 serverurl = http://127.0.0.1:9001 # 登录supervisorctl的用户名和密码 username = admin password = 123 [inet_http_server] # supervisor的服务器 port = :9001 # 用户名和密码 username = admin password = 123 [rpcinterface:supervisor] supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
然后使用命令supervisord -c zlkt_supervisor.conf
运行就可以了。
以后如果想要启动uwsgi
,就可以通过命令supervisorctl -c supervisor.conf
进入到管理控制台,然后可以执行相关的命令进行管理:
* status # 查看状态
* start program_name #启动程序
* restart program_name #重新启动程序
* stop program_name # 关闭程序
* reload # 重新加载配置文件
* quit # 退出控制台
知识讲点集合
命令行创建项目:django-admin startproject project
pycharm创建项目:文件->新建项目->选择django。然后指定项目所在的路径, 以及Python解释器,再点击Create就可以创建项目了
运行项目
终端:python manage.py runserver
pycharm:直接点击右上角的绿色三角箭头按钮就可以了。**注意:用pycharm运行项目,要避免一个项目运行多次。**。在项目配置中,把“只用单一实例”那个选项勾选上,避免以上的问题。
改变端口号:
终端:python manage.py runserver 9000(直接修改)
pycharm:右上角->项目配置->port。改成你想要的端口号,重新运行
让同局域网中的其他电脑访问本机的项目:(host为0.0.0.0)
项目运行的时候:
终端:python manage.py runserver 0.0.0.0:8000
pycharm:右上角->项目配置->host。改成`0.0.0.0`
修改配置文件:
在`settings.py`文件中,配置`ALLOWED_HOSTS`,将本机的ip地址添加进去。示例代码如下:python ALLOWED_HOSTS = ['192.168.0.103']
注意:要关闭自己电脑的防火墙才行。
创建应用:python manage.py startapp app
DEBUG模式
1. 如果开启了DEBUG模式,那么以后我们修改了Django项目的代码,然后按下ctrl+s,那么Django就会自动的给我们重启项目,不需要手动重启。
2. 如果开启了DEBUG模式,那么以后Django项目中的代码出现bug了,那么在浏览器中和控制台会打印出错信息。
3. 在生产环境中,禁止开启DEBUG模式,不然有很大的安全隐患。
4. 如果将DEBUG设置为False,那么必须要设置ALLOWED_HOSTS.
ALLOWED_HOSTS:这个变量是用来设置以后别人只能通过这个变量中的ip地址或者域名来进行访问。
url配置使用
url传参(指定参数类型):path('blog/page<int:num>/',views.page)
自定义转换器:
之前已经学到过一些django内置的url转换器,包括有int、uuid等。有时候这些 内置的url转换器并不能满足我们的需求,因此django给我们提供了一个接口可以让我们自己定义自己的url转换器。
自定义url转换器按照以下五个步骤来走就可以了:
1. 定义一个类,直接继承自object就可以了。
2. 在类中定义一个属性regex,这个属性是用来限制url转换器规则的正则表达式。
3. 实现to_python(self,value)方法,这个方法是将url中的值转换一下,然后传给视图函数的。
4. 实现to_url(self,value)方法,这个方法是在做url反转的时候,将传进来的参数转换后拼接成一个正确的url。
5. 将定义好的转换器,使用`django.urls.converters.register_converter`方法注册到django中。
示例代码如下:
from django.urls import register_converter
class CategoryConverter(object):
regex = r'\w+|(\w+\+\w+)+'
def to_python(self,value):
# python+django+flask
# ['python','django','flask']
result = value.split("+")
return result
def to_url(self,value):
# value:['python','django','flask']
# python+django+flask
if isinstance(value,list):
result = "+".join(value)
return result
else:
raise RuntimeError("转换url的时候,分类参数必须为列表!")
register_converter(CategoryConverter,'cate')
reverse反转:
1. 如果在反转url的时候,需要添加参数,那么可以传递`kwargs`参数到`revers`函数中。示例代码如下:
```python
detail_url = reverse('detail',kwargs={"article_id":1,'page':2})
```
2. 如果想要添加查询字符串的参数,则必须手动的进行拼接。示例代码如下:
```python
login_url = reverse('login') + "?next=/"
url模块化:
如果项目变得越来越大。那么url会变得越来越多。如果都放在主 urls.py
文件中,那么将不太好管理。因此我们可以将每个app自己的urls放到自己的app中进行管理。一般我们会在app中新建一个urls.py文件用来存储所有和这个app相关的子url。
注意:
1. 应该使用include
函数包含子urls.py
,并且这个urls.py
的路 径是相对于项目的路径。示例代码如下:
urlpatterns = [
path(‘admin/’, admin.site.urls),
path(‘book’,include(‘book.urls’))
]
2. 在app
的urls.py
中,所有的url匹配也要放在一个叫做urlpatterns
的变量中,否则找不到。
3. url
是会根据主urls.py
和app中的urls.py
进行拼接的,因此注意不要多加斜杠。
# include函数的用法:
1. include(module,namespace=None):
* module:子url的模块字符串。
* namespace:实例命名空间。这个地方需要注意一点。如果指定实例命名空间,那么前提必须要先指定应用命名空间。也就是在子urls.py
中添加app_name
变量。
2. include((pattern_list, app_namespace), namespace=None):include
函数的第一个参数既可以为一个字符串,也可以为一个元组,如果是元组,那么元组的第一个参数是子urls.py
模块的字符串,元组的第二个参数是应用命名空间。也就是说,应用命名空间既可以在子urls.py
中通过app_name
指定,也可以在include
函数中指定。
3. include(pattern_list):pattern_list
是一个列表。这个列表中装的是path
或者re_path
函数。实例代码如下:
path(‘movie/’,include([
path(’’,views.movie),
path(‘list/’,views.movie_list),
]))
url命名:
url是经常变化的。如果在代码中写死可能会经常改代码。给url取个名字,以后使用url的时候就使用他的名字进行反转就可以了,就不需要写死url了。
如何给一个url指定名称?
在path
函数中,传递一个name
参数就可以指定。示例代码如下:
urlpatterns = [
path('',views.index,name='index'),
path('login/',views.login,name='login')
]
应用命名空间:
在多个app之间,有可能产生同名的url。这时候为了避免反转url的时候产生混淆,可以使用应用命名空间,来做区分。定义应用命名空间非常简单,只要在app
的urls.py
中定义一个叫做app_name
的变量,来指定这个应用的命名空间即可。示例代码如下:
app_name = 'front'
urlpatterns = [
path('',views.index,name='index'),
path('login/',views.login,name='login')
]
以后在做反转的时候就可以使用应用命名空间:url名称
的方式进行反转。示例代码如下:login_url = reverse(‘front:login’)
应用(app)命名空间和实例命名空间:
一个app,可以创建多个实例。可以使用多个url映射同一个app。所以这就会产生一个问题。以后在做反转的时候,如果使用应用命名空间,那么就会发生混淆。为了避免这个问题。我们可以使用实例命名空间。实例命名空间也是非常简单,只要在include
函数中传递一个namespace
变量即可。示例代码如下:
urlpatterns = [
path('',include('front.urls')),
# 同一个app下有两个实例
path('cms1/',include('cms.urls',namespace='cms1')),
path('cms2/',include('cms.urls',namespace='cms2')),
]
以后在做反转的时候,就可以根据实例命名空间来指定具体的url。示例代码如下:
def index(request):
username = request.GET.get("username")
if username:
return HttpResponse('CMS首页')
else:
# 获取当前的命名空间
current_namespace = request.resolver_match.namespace
return redirect(reverse("%s:login"%current_namespace))
url传递参数:
url映射:
- 为什么会去urls.py文件中寻找映射呢?
是因为在settings.py
文件中配置了ROOT_URLCONF
为urls.py
。所有django会去urls.py
中寻找。 - 在
urls.py
中我们所有的映射,都应该放在urlpatterns
这个变量中。 - 所有的映射不是随便写的,而是使用
path
函数或者是re_path
函数进行包装的。
url传参数:
- 采用在url中使用变量的方式:在path的第一个参数中,使用
<参数名>
的方式可以传递参数。然后在视图函数中也要写一个参数,视图函数中的参数必须和url中的参数名称保持一致,不然就找不到这个参数。另外,url中可以传递多个参数。 - 采用查询字符串的方式:在url中,不需要单独的匹配查询字符串的部分。只需要在视图函数中使用
request.GET.get('参数名称')
的方式来获取。示例代码如下:
因为查询字符串使用的是def author_detail(request): author_id = request.GET['id'] text = '作者的id是:%s' % author_id return HttpResponse(text)
GET
请求,所以我们通过request.GET
来获取参数。并且因为GET
是一个类似于字典的数据类型,所有获取值跟字典的方式都是一样的。
url参数的转换器:
- str:除了斜杠
/
以外所有的字符都是可以的。 - int:只有是一个或者多个的阿拉伯数字。
- path:所有的字符都是满足的。
- uuid:只有满足
uuid.uuid4()
这个函数返回的字符串的格式。 - slug:英文中的横杆或者英文字符或者阿拉伯数字或者下划线才满足。
视图:
视图函数:
1. 视图函数的第一个参数必须是request。这个参数绝对不能少。
2. 视图函数的返回值必须是`django.http.response.HttpResponseBase`的子类的对象。
django中的orm模型
基本操作:
增:
book = Book(name='西游记',author='吴承恩',price=100)
book.save()
删:
book = Book.objects.get(pk=1)
book.delete()
改:
book = Book.objects.get(pk=2)
book.price = 200
book.save()
查:
根据主键查找:Book.objects.get(pk=2)
根据其他字段查找:Book.objects.filter(name='三国演义')
聚合函数:
-
所有的聚合函数都是放在
django.db.models
下面。 -
聚合函数不能够单独的执行,需要放在一些可以执行聚合函数的方法下面中去执行。比如
aggregate
。示例代码如下:result = Book.objects.aggregate(Avg("price"))
-
聚合函数执行完成后,给这个聚合函数的值取个名字。取名字的规则,默认是
filed+__+聚合函数名字
形成的。比如以上代码形成的名字叫做price__avg
。如果不想使用默认的名字,那么可以在使用聚合函数的时候传递关键字参数进去,参数的名字就是聚合函数执行完成的名字。实示例代码如下:result = Book.objects.aggregate(avg=Avg("price")) 以上传递了关键字参数`avg=Avg("price")`,那么以后`Avg`聚合函数执行完成的名字就叫做`avg`。
-
aggregate
:这个方法不会返回一个QuerySet
对象,而是返回一个字典。这个字典中的key就是聚合函数的名字,值就是聚合函数执行后的结果。 -
aggregate
和annotate
的相同和不同:
* 相同:这两个方法都可以执行聚合函数。
* 不同:
-aggregate
返回的是一个字典,在这个字典中存储的是这个聚合函数执行的结果。而annotate
返回的是一个QuerySet
对象,并且会在查找的模型上添加一个聚合函数的属性。
-aggregate
不会做分组,而annotate
会使用group by
子句进行分组,只有调用了group by
子句,才能对每一条数据求聚合函数的值。 -
Count
:用来求某个数据的个数。比如要求所有图书的数量,那么可以使用以下代码:result = Book.objects.aggregate(book_nums=Count("id"))
并且
Count
可以传递distinct=True
参数,用来剔除那些重复的值,只保留一个。比如要获取作者表中,不同邮箱的个数,那么这时候可以使用distinct=True
。示例代码如下:result = Author.objects.aggregate(email_nums=Count('email',distinct=True))
-
Max
和Min
:求指定字段的最大值和最小值。示例代码如下:result = Author.objects.aggregate(max=Max("age"),min=Min("age"))
-
Sum
:求某个字段值的总和。示例代码如下:result = BookOrder.objects.aggregate(total=Sum('price'))
aggregate
和annotate
方法可以在任何的QuerySet
对象上调用。因此只要是返回了QuerySet
对象,那么就可以进行链式调用。比如要获取2018年度的销售总额,那么可以先过滤年份,再求聚合函数。示例代码如下:BookOrder.objects.filter(create_time__year=2018).aggregate(total=Sum('price'))
-
F表达式
: 动态的获取某个字段上的值。并且这个F表达式,不会真正的去数据库中查询数据,他相当于只是起一个标识的作用。比如想要将原来每本图书的价格都在原来的基础之上增加10元,那么可以使用以下代码来实现:from django.db.models import F Book.objects.update(price=F("price")+10)
-
Q表达式
:使用Q
表达式包裹查询条件,可以在条件之间进行多种操作。与/或非等,从而实现一些复杂的查询操作。例子如下:- 查找价格大于100,并且评分达到4.85以上的图书:
# 不使用Q表达式的 books = Book.objects.filter(price__gte=100,rating__gte=4.85) # 使用Q表达式的 books = Book.objects.filter(Q(price__gte=100)&Q(rating__gte=4.85))
- 查找价格低于100元,或者评分低于4分的图书:
books = Book.objects.filter(Q(price__gte=100)&Q(rating__gte=4.85))
- 获取价格大于100,并且图书名字中不包含”传“字的图书:
books = Book.objects.filter(Q(price__gte=100)&~Q(name__icontains='传'))
- 查找价格大于100,并且评分达到4.85以上的图书:
常用field:
navie时间和aware时间:
- navie时间:不知道自己的时间表示的是哪个时区的。也就是不知道自己几斤几两。比较幼稚。
- aware时间:知道自己的时间表示的是哪个时区的。也就是比较清醒。
pytz库:专门用来处理时区的库。这个库会经常更新一些时区的数据, 不需要我们担心。并且这个库在安装Django的时候会默认的安装。如果没有安装,那么可以通过pip install pytz
的方式进行安装。
astimezone方法:
将一个时区的时间转换为另外一个时区的时间。这个方法只能被aware
类型的时间调用。不能被navie
类型的时间调用。
示例代码如下:
import pytz
from datetime import datetime
now = datetime.now() # 这是一个navie类型的时间
utc_timezone = pytz.timezone("UTC") # 定义UTC的时区对象
utc_now = now.astimezone(utc_timezone) # 将当前的时间转换为UTC时区的时间
>> ValueError: astimezone() cannot be applied to a naive datetime # 会抛出一个异常,原因就是因为navie类型的时间不能调用astimezone方法
now = now.replace(tzinfo=pytz.timezone('Asia/Shanghai'))
utc_now = now.astimezone(utc_timezone)
# 这时候就可以正确的转换。
replace方法:可以将一个时间的某些属性进行更改。
django.utils.timezone.now方法:会根据settings.py
中是否设置了USE_TZ=True
获取当前的时间。如果设置了,那么就获取一个aware
类型的UTC
时间。如果没有设置,那么就会获取一个navie
类型的时间。
django.utils.timezone.localtime方法:会根据setting.py
中的TIME_ZONE
来将一个aware
类型的时间转换为TIME_ZONE
指定时区的时间。
DateField:
日期类型。在Python
中是datetime.date
类型,可以记录年月日。在映射到数据库中也是date
类型。使用这个Field
可以传递以下几个参数:
auto_now
:在每次这个数据保存的时候,都使用当前的时间。比如作为一个记录修改日期的字段,可以将这个属性设置为True
。auto_now_add
:在每次数据第一次被添加进去的时候,都使用当前的时间。比如作为一个记录第一次入库的字段,可以将这个属性设置为True
。
DateTimeField:日期时间类型,类似于DateField
。不仅仅可以存储日期,还可以存储时间。映射到数据库中是datetime
类型。这个Field
也可以使用auto_now
和auto_now_add
两个属性。
TimeField:时间类型。在数据库中是time
类型。在Python
中是datetime.time
类型。
navie和aware介绍以及在django中的用法:
https://docs.djangoproject.com/en/2.0/topics/i18n/timezones/
EmailField:类似于CharField
。在数据库底层也是一个varchar
类型。最大长度是254个字符。
FileField:用来存储文件的。这个请参考后面的文件上传章节部分。
ImageField:用来存储图片文件的。这个请参考后面的图片上传章节部分。
FloatField:浮点类型。映射到数据库中是float
类型。
IntegerField:整形。值的区间是-2147483648——2147483647
。
BigIntegerField:大整形。值的区间是-9223372036854775808——9223372036854775807
。
PositiveIntegerField:正整形。值的区间是0——2147483647
。
SmallIntegerField:小整形。值的区间是-32768——32767
。
PositiveSmallIntegerField:正小整形。值的区间是0——32767
。
TextField:大量的文本类型。映射到数据库中是longtext类型。
UUIDField:只能存储uuid
格式的字符串。uuid
是一个32位的全球唯一的字符串,一般用来作为主键。
URLField:类似于CharField
,只不过只能用来存储url
格式的字符串。并且默认的max_length
是200。
Field常用的参数
null:如果设置为True
,Django
将会在映射表的时候指定是否为空。默认是为False
。在使用字符串相关Field
(CharField/TextField)的时候,官方推荐尽量不要使用这个参数,也就是保持默认值False
。因为Django
在处理字符串相关的Field
的时候,即使这个Field
的null=False
,如果你没有给这个Field
传递任何值,那么Django
也会使用一个空的字符串""
来作为默认值存储进去。因此如果再使用null=True
,Django
会产生两种空值的情形(NULL或者空字符串)。如果想要在表单验证的时候允许这个字符串为空,那么建议使用blank=True
。如果你的Field
是BooleanField
,那么对应的可空的字段则为NullBooleanField
。
blank:标识这个字段在表单验证的时候是否可以为空。默认是False
。这个和null
是有区别的,null
是一个纯数据库级别的。而blank
是表单验证级别的。
db_column:这个字段在数据库中的名字。如果没有设置这个参数,那么将会使用模型中属性的名字。
default:默认值。可以为一个值,或者是一个函数,但是不支持lambda
表达式。并且不支持列表/字典/集合等可变的数据结构。
primary_key:是否为主键。默认是False
。
unique:在表中这个字段的值是否唯一。一般是设置手机号码/邮箱等。
更多Field
参数请参考官方文档:https://docs.djangoproject.com/zh-hans/2.0/ref/models/fields/
模型中Meta
配置:
对于一些模型级别的配置。我们可以在模型中定义一个类,叫做Meta
。然后在这个类中添加一些类属性来控制模型的作用。比如我们想要在数据库映射的时候使用自己指定的表名,而不是使用模型的名称。那么我们可以在Meta
类中添加一个db_table
的属性。示例代码如下:
class Book(models.Model):
name = models.CharField(max_length=20,null=False)
desc = models.CharField(max_length=100,name='description',db_column="description1")
class Meta:
db_table = 'book_model'
以下将对Meta
类中的一些常用配置进行解释。
db_table:这个模型映射到数据库中的表名。如果没有指定这个参数,那么在映射的时候将会使用模型名来作为默认的表名。
ordering:设置在提取数据的排序方式。后面章节会讲到如何查找数据。比如我想在查找数据的时候根据添加的时间排序,那么示例代码如下:
class Book(models.Model):
name = models.CharField(max_length=20,null=False)
desc = models.CharField(max_length=100,name='description',db_column="description1")
pub_date = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = 'book_model'
ordering = ['pub_date']
更多的配置后面会慢慢介绍到。
官方文档:https://docs.djangoproject.com/en/2.0/ref/models/options/
创建orm模型:
ORM
模型一般都是放在app
的models.py
文件中。每个app
都可以拥有自己的模型。并且如果这个模型想要映射到数据库中,那么这个app
必须要放在settings.py
的INSTALLED_APP
中进行安装。
映射模型到数据库中:
- 在
settings.py
中,配置好DATABASES
,做好数据库相关的配置。 - 在
app
中的models.py
中定义好模型,这个模型必须继承自django.db.models
。 - 将这个
app
添加到settings.py
的INSTALLED_APP
中。 - 在命令行终端,进入到项目所在的路径,然后执行命令
python manage.py makemigrations
来生成迁移脚本文件。 - 同样在命令行中,执行命令
python manage.py migrate
来将迁移脚本文件映射到数据库中。
查询条件:
-
exact:在底层会被翻译成
=
。 -
iexact:在底层会被翻译成
LIKE
。- LIKE和=:大部分情况下都是等价的,只有少数情况下是不等价的。
- exict和iexact:他们的区别其实就是LIKE和=的区别,因为exact会被翻译成=,而iexact会被翻译成LIKE。
- 因为
field__exact=xxx
其实等价于filed=xxx
,因此我们直接使用filed=xxx
就可以了,并且因为大部分情况exact
和iexact
又是等价的,因此我们以后直接使用field=xxx
就可以了。
-
QuerySet.query:
query
可以用来查看这个ORM
查询语句最终被翻译成的SQL
语句。但是query
只能被用在QuerySet
对象上,不能用在普通的ORM模型
上。因此如果你的查询语句是通过get
来获取数据的,那么就不能使用query
,因为get
返回的是满足条件的ORM
模型,而不是QuerySet
。如果你是通过filter
等其他返回QuerySet
的方法查询的,那么就可以使用query
。 -
contains:使用大小写敏感的判断,某个字符串是否在指定的字段中。这个判断条件会使用大小敏感,因此在被翻译成
SQL
语句的时候,会使用like binary
,而like binary
就是使用大小写敏感的。 -
icontains:使用大小写不敏感的判断,某个字符串是否被包含在指定的字段中。这个查询语句在被翻译成
SQL
的时候,使用的是like
,而like
在MySQL
层面就是不区分大小写的。 -
contains和icontains:在被翻译成
SQL
的时候使用的是%hello%
,就是只要整个字符串中出现了hello
都能过够被找到,而iexact
没有百分号,那么意味着只有完全相等的时候才会被匹配到。 -
in:可以直接指定某个字段的是否在某个集合中。示例代码如下:
articles = Article.objects.filter(id__in=[1,2,3])
也可以通过其他的表的字段来判断是否在某个集合中。示例代码如下:
categories = Category.objects.filter(article__id__in=[1,2,3])
如果要判断相关联的表的字段,那么也是通过
__
来连接。并且在做关联查询的时候,不需要写models_set
,直接使用模型的名字的小写化
就可以了。比如通过分类去查找相应的文章,那么通过article__id__in
就可以了,而不是写成article_set__id__in
的形式。当然如果你不想使用默认的形式,可以在外键定义的时候传递related_query_name
来指定反向查询的名字。示例代码如下:class Category(models.Model): name = models.CharField(max_length=100) class Meta: db_table = 'category' class Article(models.Model): title = models.CharField(max_length=200) content = models.TextField() cateogry = models.ForeignKey("Category",on_delete=models.CASCADE,null=True,related_query_name='articles') class Meta: db_table = 'article'
因为在
cateogry
的ForeignKey
中指定了related_query_name
为articles
,因此你不能再使用article
来进行反向查询了。这时候就需要通过articles__id__in
来进行反向查询。反向查询是将模型名字小写化。比如
article__in
。可以通过related_query_name
来指定自己的方式,而不使用默认的方式。
反向引用是将模型名字小写化,然后再加上_set
,比如article_set
,可以通过related_name
来指定自己的方式,而不是用默认的方式。并且,如果在做反向查询的时候,如果查询的字段就是模型的主键,那么可以省略掉这个字段,直接写成
article__in
就可以了,不需要这个id
了。in
不仅仅可以指定列表/元组,还可以为QuerySet
。比如要查询“文章标题中包含有hello的所有分类”,那么可以通过以下代码来实现:articles = Article.objects.filter(title__icontains='hello') categories = Category.objects.filter(articles__in=articles) for cateogry in categories: print(cateogry)
-
gt、gte、lt、lte:代表的是大于、大于等于、小于、小于等于的条件。示例代码如下:
articles = Article.objects.filter(id__lte=3)
-
startswith、istartswith、endswith、iendswith:表示以某个值开始,不区分大小写的以某个值开始、以某个值结束、不区分大小写的以某个值结束。示例代码如下:
articles = Article.objects.filter(title__endswith="hello")
-
关于时间的查询条件:
-
range:可以指定一个时间段。并且时间应该标记为
aware
时间,不然django会报警告。示例代码如下:start_time = make_aware(datetime(year=2018,month=4,day=4,hour=17,minute=0,second=0)) end_time = make_aware(datetime(year=2018,month=4,day=4,hour=18,minute=0,second=0)) articles = Article.objects.filter(create_time__range=(start_time,end_time)) print(articles.query) print(articles)
-
date:用年月日来进行过滤。如果想要使用这个过滤条件,那么前提必须要在
MySQL
中添加好那些时区文件。如何添加呢?参考教案。示例代码如下:articles = Article.objects.filter(create_time__date=datetime(year=2018,month=4,day=4))
-
year/month/day:表示根据年/月/日进行查找。示例代码如下:
articles = Article.objects.filter(create_time__year__gte=2018)
-
week_day:根据星期来进行查找。1表示星期天,7表示星期六,2-6代表的是星期一到星期五。比如要查找星期三的所有文章,那么可以通过以下代码来实现:
articles = Article.objects.filter(create_time__week_day=4)
-
time:根据分时秒来进行查找。如果要具体到秒,一般比较难匹配到,可以使用区间的方式来进行查找。区间使用
range
条件。比如想要获取17时/10分/27-28秒之间的文章,那么可以通过以下代码来实现:start_time = time(hour=17,minute=10,second=27) end_time = time(hour=17,minute=10,second=28) articles = Article.objects.filter(create_time__time__range=(start_time,end_time))
-
migrate怎么判断哪些迁移脚本需要执行:
他会将代码中的迁移脚本和数据库中django_migrations
中的迁移脚本进行对比,如果发现数据库中,没有这个迁移脚本,那么就会执行这个迁移脚本。
migrate运行原理
- 将相关的迁移脚本翻译成SQL语句,在数据库中执行这个SQL语句。
- 如果这个SQL语句执行没有问题,那么就会将这个迁移脚本的名字记录到
django_migrations
中。
问:执行migrate命令的时候报错的解决办法
原因:执行migrate命令会报错的原因是。数据库的django_migrations
表中的迁移版本记录和代码中的迁移脚本不一致导致的。
解决办法:
1.使用–fake参数:首先对比数据库中的迁移脚本和代码中的迁移脚本。然后找到哪个不同,之后再使用--fake
,将代码中的迁移脚本添加到django_migrations
中,但是并不会执行sql语句。这样就可以避免每次执行migrate
的时候,都执行一些重复的迁移脚本。
2.终极解决方案:如果代码中的迁移脚本和数据库中的迁移脚本实在太多,就是搞不清了。那么这时候就可以使用以下终极解决方案:
- 终极解决方案原理:就是将之前的那些迁移脚本都不用了。重新来过。要将出问题的app下的所有模型和数据库中表保持一致,重新映射。
- 将出问题的app下的所有模型,都和数据库中的表保持一致。
- 将出问题的app下的所有迁移脚本文件都删掉。再在
django_migrations
表中将出问题的app相关的迁移记录都删掉。 - 使用
makemigrations
,重新将模型生成一个迁移脚本。 - 使用
migrate --fake-initial
参数,将刚刚生成的迁移脚本,标记为已经完成(因为这些模型相对应的表,其实都已经在数据库中存在了,不需要重复执行了。) - 可以做其他的映射了。
QuerySet API:
models.objects:
这个对象是django.db.models.manager.Manager
的对象,这个类是一个空壳类,他上面的所有方法都是从QuerySet
这个类上面拷贝过来的。因此我们只要学会了QuerySet
,这个objects
也就知道该如何使用了。
Manager
源码解析:
class_name = "BaseManagerFromQuerySet"
class_dict = {
'_queryset_class': QuerySet
}
class_dict.update(cls._get_queryset_methods(QuerySet))
# type动态的时候创建类
# 第一个参数是用来指定创建的类的名字。创建的类名是:BaseManagerFromQuerySet
# 第二个参数是用来指定这个类的父类。
# 第三个参数是用来指定这个类的一些属性和方法
return type(class_name,(cls,),class_dict)
**_get_queryset_methods**:这个方法就是将QuerySet中的一些方法拷贝出来
filter/exclude/annotate:过滤/排除满足条件的/给模型添加新的字段。
order_by:
# 根据创建的时间正序排序
articles = Article.objects.order_by("create_time")
# 根据创建的时间倒序排序
articles = Article.objects.order_by("-create_time")
# 根据作者的名字进行排序
articles = Article.objects.order_by("author__name")
# 首先根据创建的时间进行排序,如果时间相同,则根据作者的名字进行排序
articles = Article.objects.order_by("create_time",'author__name')
一定要注意的一点是,多个`order_by`,会把前面排序的规则给打乱,而使用后面的排序方式。比如以下代码:
articles = Article.objects.order_by("create_time").order_by("author__name")
他会根据作者的名字进行排序,而不是使用文章的创建时间。
当然,也可以在模型定义的在Meta
类中定义ordering
来指定默认的排序方式。示例代码如下:
class Meta:
db_table = 'book_order'
ordering = ['create_time','-price']
还可以根据annotate
定义的字段进行排序。比如要实现图书的销量进行排序,那么示例代码如下:
books = Book.objects.annotate(order_nums=Count("bookorder")).order_by("-order_nums")
for book in books:
print('%s/%s'%(book.name,book.order_nums))
表关系笔记:
一对多:
- 应用场景:比如文章和作者之间的关系。一个文章只能由一个作者编写,但是一个作者可以写多篇文章。文章和作者之间的关系就是典型的多对一的关系。
- 实现方式:一对多或者多对一,都是通过
ForeignKey
来实现的。还是以文章和作者的案例进行讲解。
class User(models.Model):
username = models.CharField(max_length=20)
password = models.CharField(max_length=100)
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
author = models.ForeignKey("User",on_delete=models.CASCADE)
那么以后在给Article
对象指定author
,就可以使用以下代码来完成:
article = Article(title='abc',content='123')
author = User(username='zhiliao',password='111111')
# 要先保存到数据库中
author.save()
article.author = author
article.save()
并且以后如果想要获取某个用户下所有的文章,可以通过article_set
来实现。示例代码如下:
user = User.objects.first()
# 获取第一个用户写的所有文章
articles = user.article_set.all()
for article in articles:
print(article)
并且如果想要将文章添加到某个分类中。可以使用一下的方式:
category = Category.objects.first()
article = Article(title='bbb',content='vvv')
article.author = FrontUser.objects.first()
category.article_set.add(article,bulk=False)
- 使用
bulk=False
,那么Django会自动的保存article,而不需要在添加到category之前先保存article。 - 或者是另外一种解决方式是,在添加到
category.article_set
中之前,先将article
保存到数据库中。但是如果article.category
不能为空,那么就产生一种死循环了,article没有category
不能保存,而将article添加到cateogry.artile_set
中,又需要article之前是已经存储到数据库中的。 - 如果是上面的那种需求,建议使用
bulk=False
的解决方案。
一对一:
- 在Django中一对一是通过
models.OnetToOneField
来实现的。这个OneToOneField
其实本质上就是一个外键,只不过这个外键有一个唯一约束(unique key)
,来实现一对一。 - 以后如果想要反向引用,那么是通过引用的模型的名字转换为小写的形式进行访问。比如以下模型:
class FrontUser(models.Model): username = models.CharField(max_length=200) class UserExtension(models.Model): school = models.CharField(max_length=100) user = models.OneToOneField("FrontUser",on_delete=models.CASCADE) # 通过userextension来访问UserExtension对象 user = FrontUser.objects.first() print(user.userextension)
UserExtension
的对象,可以通过user
来访问到对应的user对象。并且FrontUser
对象可以使用userextension
来访问对应的UserExtension
对象。
如果不想使用Django默认的引用属性名字。那么可以在OneToOneField
中添加一个related_name
参数。示例代码如下:
那么以后就class FrontUser(models.Model): username = models.CharField(max_length=200) class UserExtension(models.Model): school = models.CharField(max_length=100) user = models.OneToOneField("FrontUser",on_delete=models.CASCADE,related_name='extension') # 通过extension来访问到UserExtension对象 user = FrontUser.objects.first() print(user.extension)
FrontUser
的对象就可以通过extension
属性来访问到对应的UserExtension
对象。
多对多:
-
应用场景:比如文章和标签的关系。一篇文章可以有多个标签,一个标签可以被多个文章所引用。因此标签和文章的关系是典型的多对多的关系。
-
实现方式:
Django
为这种多对多的实现提供了专门的Field
。叫做ManyToManyField
。还是拿文章和标签为例进行讲解。示例代码如下:
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
tags = models.ManyToManyField("Tag",related_name="articles")
class Tag(models.Model):
name = models.CharField(max_length=50)
在数据库层面,实际上Django
是为这种多对多的关系建立了一个中间表。这个中间表分别定义了两个外键,引用到article
和tag
两张表的主键。