django学习路

搭环境

	1、下载python
	2、下载django
	3、下载mysql
	4、安装mysql驱动
	5、创建虚拟环境

django部署

在开发机上的准备工作:

  1. 确认项目没有bug。
  2. pip freeze > requirements.txt将当前环境的包导出到requirements.txt文件中,方便在部署的时候安装。
  3. 将项目上传到服务器上的/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

在服务器上的准备工作:

  1. 安装好项目用到的Python
    • sudo apt install python
    • sudo apt install python-pip
    • pip install --upgrade pip
  2. 安装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
  3. 安装git
    sudo apt install git
    
  4. 为了方便XShell或者CRT连接服务器,建议安装OpenSSH
    sudo apt install openssh-server openssh-client
    service ssh restart
    
  5. 安装MySQL服务器和客户端:
    sudo apt install mysql-server mysql-client
    sudo apt-get install libmysqld-dev
    
  6. 进入虚拟环境中,然后进入到项目所在目录,执行命令:pip install -r requirements.txt,安装好相应的包。
  7. mysql数据库中,创建相应的数据库。
  8. 执行python manage.py migrate命令,将迁移文件,映射到数据库中,创建相应的表。
  9. 执行python manage.py runserver 0.0.0.0:8000,然后在你自己电脑上,在浏览器中输入http://你的服务器的ip地址:8000/,访问下网站所有页 面,确保所有页面都没有错误。
  10. 设置ALLOW_HOST为你的域名,以及ip地址。
  11. 设置DEBUG=False,避免如果你的网站产生错误,而将错误信息暴漏给用户。
  12. 收集静态文件:python manage.py collectstatic

安装uwsgi

  1. uwsgi是一个应用服务器,非静态文件的网络请求就必须通过他完成,他也可以充当静态文件服务器,但不是他的强项。uwsgi是使用python编写的,因此通过pip install uwsgi就可以了。(uwsgi必须安装在系统级别的Python环境中,不要安装到虚拟环境中)。
  2. 使用命令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:

  1. nginx是一个web服务器。用来加载静态文件和接收http请求的。通过命令sudo apt install nginx即可安装。
  2. 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发生意外的情况下,会自动的重启。

  1. supervisor的安装:在系统级别的python环境下pip install supervisor
  2. 在项目的根目录下创建一个文件叫做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. 在appurls.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的时候产生混淆,可以使用应用命名空间,来做区分。定义应用命名空间非常简单,只要在appurls.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映射:

  1. 为什么会去urls.py文件中寻找映射呢?
    是因为在settings.py文件中配置了ROOT_URLCONFurls.py。所有django会去urls.py中寻找。
  2. urls.py中我们所有的映射,都应该放在urlpatterns这个变量中。
  3. 所有的映射不是随便写的,而是使用path函数或者是re_path函数进行包装的。

url传参数:

  1. 采用在url中使用变量的方式:在path的第一个参数中,使用<参数名>的方式可以传递参数。然后在视图函数中也要写一个参数,视图函数中的参数必须和url中的参数名称保持一致,不然就找不到这个参数。另外,url中可以传递多个参数。
  2. 采用查询字符串的方式:在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参数的转换器:

  1. str:除了斜杠/以外所有的字符都是可以的。
  2. int:只有是一个或者多个的阿拉伯数字。
  3. path:所有的字符都是满足的。
  4. uuid:只有满足uuid.uuid4()这个函数返回的字符串的格式。
  5. 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='三国演义')

聚合函数:

  1. 所有的聚合函数都是放在django.db.models下面。

  2. 聚合函数不能够单独的执行,需要放在一些可以执行聚合函数的方法下面中去执行。比如aggregate。示例代码如下:

     result = Book.objects.aggregate(Avg("price"))
    
  3. 聚合函数执行完成后,给这个聚合函数的值取个名字。取名字的规则,默认是filed+__+聚合函数名字形成的。比如以上代码形成的名字叫做price__avg。如果不想使用默认的名字,那么可以在使用聚合函数的时候传递关键字参数进去,参数的名字就是聚合函数执行完成的名字。实示例代码如下:

     result = Book.objects.aggregate(avg=Avg("price"))
     
     以上传递了关键字参数`avg=Avg("price")`,那么以后`Avg`聚合函数执行完成的名字就叫做`avg`。
    
  4. aggregate:这个方法不会返回一个QuerySet对象,而是返回一个字典。这个字典中的key就是聚合函数的名字,值就是聚合函数执行后的结果。

  5. aggregateannotate的相同和不同:
    * 相同:这两个方法都可以执行聚合函数。
    * 不同:
    - aggregate返回的是一个字典,在这个字典中存储的是这个聚合函数执行的结果。而annotate返回的是一个QuerySet对象,并且会在查找的模型上添加一个聚合函数的属性。
    - aggregate不会做分组,而annotate会使用group by子句进行分组,只有调用了group by子句,才能对每一条数据求聚合函数的值。

  6. Count:用来求某个数据的个数。比如要求所有图书的数量,那么可以使用以下代码:

    result = Book.objects.aggregate(book_nums=Count("id"))
    

    并且Count可以传递distinct=True参数,用来剔除那些重复的值,只保留一个。比如要获取作者表中,不同邮箱的个数,那么这时候可以使用distinct=True。示例代码如下:

    result = Author.objects.aggregate(email_nums=Count('email',distinct=True))
    
  7. MaxMin:求指定字段的最大值和最小值。示例代码如下:

    result = Author.objects.aggregate(max=Max("age"),min=Min("age"))
    
  8. Sum:求某个字段值的总和。示例代码如下:

    result = BookOrder.objects.aggregate(total=Sum('price'))
    

    aggregateannotate方法可以在任何的QuerySet对象上调用。因此只要是返回了QuerySet对象,那么就可以进行链式调用。比如要获取2018年度的销售总额,那么可以先过滤年份,再求聚合函数。示例代码如下:

    BookOrder.objects.filter(create_time__year=2018).aggregate(total=Sum('price'))
    
  9. F表达式: 动态的获取某个字段上的值。并且这个F表达式,不会真正的去数据库中查询数据,他相当于只是起一个标识的作用。比如想要将原来每本图书的价格都在原来的基础之上增加10元,那么可以使用以下代码来实现:

    from django.db.models import F
    Book.objects.update(price=F("price")+10)
    
  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='传'))
      

常用field:
navie时间和aware时间:

  1. navie时间:不知道自己的时间表示的是哪个时区的。也就是不知道自己几斤几两。比较幼稚。
  2. 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可以传递以下几个参数:

  1. auto_now:在每次这个数据保存的时候,都使用当前的时间。比如作为一个记录修改日期的字段,可以将这个属性设置为True
  2. auto_now_add:在每次数据第一次被添加进去的时候,都使用当前的时间。比如作为一个记录第一次入库的字段,可以将这个属性设置为True

DateTimeField:日期时间类型,类似于DateField。不仅仅可以存储日期,还可以存储时间。映射到数据库中是datetime类型。这个Field也可以使用auto_nowauto_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:如果设置为TrueDjango将会在映射表的时候指定是否为空。默认是为False。在使用字符串相关Field(CharField/TextField)的时候,官方推荐尽量不要使用这个参数,也就是保持默认值False。因为Django在处理字符串相关的Field的时候,即使这个Fieldnull=False,如果你没有给这个Field传递任何值,那么Django也会使用一个空的字符串""来作为默认值存储进去。因此如果再使用null=TrueDjango会产生两种空值的情形(NULL或者空字符串)。如果想要在表单验证的时候允许这个字符串为空,那么建议使用blank=True。如果你的FieldBooleanField,那么对应的可空的字段则为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模型一般都是放在appmodels.py文件中。每个app都可以拥有自己的模型。并且如果这个模型想要映射到数据库中,那么这个app必须要放在settings.pyINSTALLED_APP中进行安装。
映射模型到数据库中:

  1. settings.py中,配置好DATABASES,做好数据库相关的配置。
  2. app中的models.py中定义好模型,这个模型必须继承自django.db.models
  3. 将这个app添加到settings.pyINSTALLED_APP中。
  4. 在命令行终端,进入到项目所在的路径,然后执行命令python manage.py makemigrations来生成迁移脚本文件。
  5. 同样在命令行中,执行命令python manage.py migrate来将迁移脚本文件映射到数据库中。

查询条件:

  1. exact:在底层会被翻译成=

  2. iexact:在底层会被翻译成LIKE

    • LIKE和=:大部分情况下都是等价的,只有少数情况下是不等价的。
    • exict和iexact:他们的区别其实就是LIKE和=的区别,因为exact会被翻译成=,而iexact会被翻译成LIKE。
    • 因为field__exact=xxx其实等价于filed=xxx,因此我们直接使用filed=xxx就可以了,并且因为大部分情况exactiexact又是等价的,因此我们以后直接使用field=xxx就可以了。
  3. QuerySet.query:query可以用来查看这个ORM查询语句最终被翻译成的SQL语句。但是query只能被用在QuerySet对象上,不能用在普通的ORM模型上。因此如果你的查询语句是通过get来获取数据的,那么就不能使用query,因为get返回的是满足条件的ORM模型,而不是QuerySet。如果你是通过filter等其他返回QuerySet的方法查询的,那么就可以使用query

  4. contains:使用大小写敏感的判断,某个字符串是否在指定的字段中。这个判断条件会使用大小敏感,因此在被翻译成SQL语句的时候,会使用like binary,而like binary就是使用大小写敏感的。

  5. icontains:使用大小写不敏感的判断,某个字符串是否被包含在指定的字段中。这个查询语句在被翻译成SQL的时候,使用的是like,而likeMySQL层面就是不区分大小写的。

  6. contains和icontains:在被翻译成SQL的时候使用的是%hello%,就是只要整个字符串中出现了hello都能过够被找到,而iexact没有百分号,那么意味着只有完全相等的时候才会被匹配到。

  7. 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'
    

    因为在cateogryForeignKey中指定了related_query_namearticles,因此你不能再使用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)
    
  8. gt、gte、lt、lte:代表的是大于、大于等于、小于、小于等于的条件。示例代码如下:

    articles = Article.objects.filter(id__lte=3)
    
  9. startswith、istartswith、endswith、iendswith:表示以某个值开始,不区分大小写的以某个值开始、以某个值结束、不区分大小写的以某个值结束。示例代码如下:

    articles = Article.objects.filter(title__endswith="hello")
    
  10. 关于时间的查询条件:

    • 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运行原理

  1. 将相关的迁移脚本翻译成SQL语句,在数据库中执行这个SQL语句。
  2. 如果这个SQL语句执行没有问题,那么就会将这个迁移脚本的名字记录到django_migrations中。

问:执行migrate命令的时候报错的解决办法
原因:执行migrate命令会报错的原因是。数据库的django_migrations表中的迁移版本记录和代码中的迁移脚本不一致导致的。
解决办法:
1.使用–fake参数:首先对比数据库中的迁移脚本和代码中的迁移脚本。然后找到哪个不同,之后再使用--fake,将代码中的迁移脚本添加到django_migrations中,但是并不会执行sql语句。这样就可以避免每次执行migrate的时候,都执行一些重复的迁移脚本。
2.终极解决方案:如果代码中的迁移脚本和数据库中的迁移脚本实在太多,就是搞不清了。那么这时候就可以使用以下终极解决方案:

  1. 终极解决方案原理:就是将之前的那些迁移脚本都不用了。重新来过。要将出问题的app下的所有模型和数据库中表保持一致,重新映射。
  2. 将出问题的app下的所有模型,都和数据库中的表保持一致。
  3. 将出问题的app下的所有迁移脚本文件都删掉。再在django_migrations表中将出问题的app相关的迁移记录都删掉。
  4. 使用makemigrations,重新将模型生成一个迁移脚本。
  5. 使用migrate --fake-initial参数,将刚刚生成的迁移脚本,标记为已经完成(因为这些模型相对应的表,其实都已经在数据库中存在了,不需要重复执行了。)
  6. 可以做其他的映射了。

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))

表关系笔记:

一对多:

  1. 应用场景:比如文章和作者之间的关系。一个文章只能由一个作者编写,但是一个作者可以写多篇文章。文章和作者之间的关系就是典型的多对一的关系。
  2. 实现方式:一对多或者多对一,都是通过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的解决方案。

一对一:

  1. 在Django中一对一是通过models.OnetToOneField来实现的。这个OneToOneField其实本质上就是一个外键,只不过这个外键有一个唯一约束(unique key),来实现一对一。
  2. 以后如果想要反向引用,那么是通过引用的模型的名字转换为小写的形式进行访问。比如以下模型:
    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对象。

多对多:

  1. 应用场景:比如文章和标签的关系。一篇文章可以有多个标签,一个标签可以被多个文章所引用。因此标签和文章的关系是典型的多对多的关系。

  2. 实现方式: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是为这种多对多的关系建立了一个中间表。这个中间表分别定义了两个外键,引用到articletag两张表的主键。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值