python/django基础部分随笔记

Django

准备


虚拟环境
为什么需要虚拟环境:
到目前位置,我们所有的第三方包安装都是直接通过 pip install xx 的方式进行安装的,这样安装会将那个包安装到你的系统级的 Python 环境中。但是这样有一个问题,就是如果你现在用 Django 1.10.x 写了个网站,然后你的领导跟你说,之前有一个旧项目是用 Django 0.9 开发的,让你来维护,但是 Django 1.10 不再兼容 Django 0.9 的一些语法了。这时候就会碰到一个问
题,我如何在我的电脑中同时拥有 Django 1.10 和 Django 0.9 两套环境呢?这时候我们就可以通过虚拟环境来解决这个问题。
虚拟环境原理介绍:
虚拟环境相当于一个抽屉,在这个抽屉中安装的任何软件包都不会影响到其他抽屉。并且在项目中,我可以指定这个项目的虚拟环境来配合我的项目。比如我们现在有一个项目是基于 Django1.10.x 版本,又有一个项目是基于 Django 0.9.x 的版本,那么这时候就可以创建两个虚拟环境,在这两个虚拟环境中分别安装 Django 1.10.x 和 Django 0.9.x 来适配我们的项目。
安装 virtualenv :
virtualenv 是用来创建虚拟环境的软件工具,我们可以通过 pip 或者 pip3 来安装:
pip install virtualenv
pip3 install virtualenv
创建虚拟环境:
创建虚拟环境非常简单,通过以下命令就可以创建了:
virtualenv [虚拟环境的名字]
如果你当前的 Python3/Scripts 的查找路径在 Python2/Scripts 的前面,那么将会使用 python3 作为这个虚拟环境的解释器。如果 python2/Scripts 在 python3/Scripts 前面,那么将会使用 Python2 来作为这个虚拟环境的解释器。
进入环境:
虚拟环境创建好了以后,那么可以进入到这个虚拟环境中,然后安装一些第三方包,进入虚拟环境在不同的操作系统中有不同的方式,一般分为两种,第一种是 Windows ,第二种是 *nix :

  1. windows 进入虚拟环境:进入到虚拟环境的 Scripts 文件夹中,然后执行 activate 。
  2. *nix 进入虚拟环境: source /path/to/virtualenv/bin/activate
    一旦你进入到了这个虚拟环境中,你安装包,卸载包都是在这个虚拟环境中,不会影响到外面的环境。
    退出虚拟环境:
    退出虚拟环境很简单,通过一个命令就可以完成: deactivate 。
    创建虚拟环境的时候指定 Python 解释器:在电脑的环境变量中,一般是不会去更改一些环境变量的顺序的。也就是说比如你的 Python2/Scripts 在 Python3/Scripts 的前面,那么你不会经常去更改他们的位置。但是这时候我确实是想在创建虚拟环境的时候用 Python3 这个版本,这时候可以通过 -p 参数来指定具体
    的 Python 解释器:
    virtualenv -p C:\Python36\python.exe [virutalenv name]

virtualenvwrapper
virtualenvwrapper 这个软件包可以让我们管理虚拟环境变得更加简单。不用再跑到某个目录下通过 virtualenv 来创建虚拟环境,并且激活的时候也要跑到具体的目录下去激活。
安装 virtualenvwrapper :

  1. *nix: pip install virtualenvwrapper 。
  2. windows: pip install virtualenvwrapper-win 。
    virtualenvwrapper 基本使用:
  3. 创建虚拟环境:
    mkvirtualenv my_env
    那么会在你当前用户下创建一个 Env 的文件夹,然后将这个虚拟环境安装到这个目录下。
    如果你电脑中安装了 python2 和 python3 ,并且两个版本中都安装了 virtualenvwrapper ,
    那么将会使用环境变量中第一个出现的 Python 版本来作为这个虚拟环境的 Python 解释器。
  4. 切换到某个虚拟环境:
    workon my_env
  1. 退出当前虚拟环境:
    deactivate
  2. 删除某个虚拟环境:
    rmvirtualenv my_env
  3. 列出所有虚拟环境:
    lsvirtualenv
  4. 进入到虚拟环境所在的目录:
    cdvirtualenv
    修改 mkvirtualenv 的默认路径:
    在 我的电脑->右键->属性->高级系统设置->环境变量->系统变量 中添加一个参数 WORKON_HOME ,将这个
    参数的值设置为你需要的路径。
    创建虚拟环境的时候指定 Python 版本:
    在使用 mkvirtualenv 的时候,可以指定 --python 的参数来指定具体的 python 路径:
    mkvirtualenv --python==C:\Python36\python.exe hy_env

*所以之前flask的安装笔记当时没有特别理解,一部分安装在虚拟环境中一部分安装在系统中了(也许)所以养成好习惯先进虚拟环境再装需要的包并且发现虚拟环境只能建在c盘才能正常

补充的一些和计算机网络相关配置

补充(一些计算机网络指令,日后用得到少年多复习)
(在同一个局域网,ip地址(host)改成0.0.0.0,即可在同一局域网相互访问
、ie浏览器不会只能添加http)
*ipconfig:查看本机p地址
如果没有同一局域网的机子可以通过虚拟机进行模拟

pycharm中

*改端口:右上角项目配置port改为想要的端口号
*让同局域网其他电脑访问本项目:host改为0.0.0.0
*另外需要在setting.py文件中修改域名‘ALLOWED_HOST=['本地ip地址']
注意:要关闭自己电脑的防火墙

项目结构介绍

manage.py:用于管理整个项目;cmd中’python manage.py help'可以查看其中所有指令
settings.py:用于配置项目信息
urls.py:用于保存项目url配置,用作url与视图函数映射
wsgi.py:项目与 WSGI 协议兼容的 web 服务器入口,部署的时候需要用到的,一般情况下
也是不需要修改的。

url与视图

“视图一般都写在 app 的 views.py 中。并且视图的第一个参数永远都是 request (一个HttpRequest)对象。这个对象存储了请求过来的所有信息,包括携带的参数以及一些头部信息等。在视图中,一般是完成逻辑相关的操作。比如这个请求是添加一篇博客,那么可以通过request来接收到这些数据,然后存储到数据库中,最后再把执行的结果返回给浏览器。视图函数的返回结果必须是 HttpResponseBase 对象或者子类的对象。示例代码如下:

from django.http import HttpResponse
def book_list(request):
return HttpResponse("书籍列表!")

1.首先创建一个app
cmd(虚拟环境下)进入项目目录,‘python manage.py startapp book'
2.视图函数
*第一个参数必须是request
*返回值必须是’django.hettp,response.HttpResponseBase'的子类对象
3.url可以传递多个参数通过'<变量名>'
4.采用查询字符串的方式,在url中,不需要单独的匹配查询字符串,只需要在视图函数中使用'request.GET.get('参数名称')的方式来获取。例:

def author_detail(request):
    author_id=request.GET.get('id')
    text='作者的id是,%s'%author_id
    return HttpResponse(text)

因为查询字符串是‘GET’请求,所以我们通过'request.GET‘来获取参数;
因为’GET‘是一个类似于字典的数据类型,所有获取值跟字典得方式都是一样的。

url命名

front用来管理前台相关的代码
cms用来管理后台相关的代码
1.给url命名原因:
url是经常变化的,如果代码写死改动工程量大,所以通过给url命名,可以通过命名反转,不用大量改动。
2.给url命名方式:
在'path'函数中,传递一个'name'参数就可以指定,比如:

urlpatterns=[
    path('',views.index,name='index'),
    path('login/',views.login,name='login')
]
'''
login_url=reverse('login')
return redirect(login_url)
'''

3.应用命名空间:
在多个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')
return redirect(login_url)

4.应用名空间和实例命名空间

Django模板

模板介绍

模板
在之前的章节中,视图函数只是直接返回文本,而在实际生产环境中其实很少这样用,因为实际的页面大多是带有样式的HTML代码,这可以让浏览器渲染出非常漂亮的页面。目前市面上有非常多的模板系统,其中最知名最好用的就是DTL和Jinja2。 DTL 是 Django Template Language 三个单词的缩写,也就是Django自带的模板语言。当然也可以配置Django支持Jinja2等其他模板引擎,但是作为Django内置的模板语言,和Django可以达到无缝衔接而不会产生一些不兼容的情况。因此建议大家学习好DTL。

DTL与普通的HTML文件的区别:
DTL模板是一种带有特殊语法的HTML文件,这个HTML文件可以被Django编译,可以传递参数进去,实现数据动态化。在编译完成后,生成一个普通的HTML文件,然后发送给客户端。

渲染模板:
渲染模板有多种方式。这里讲下两种常用的方式。

  1. render_to_string :找到模板,然后将模板编译后渲染成Python的字符串格式。最后再通过 HttpResponse 类包装成一个 HttpResponse 对象返回回去。示例代码如下:
from django.template.loader import render_to_string
from django.http import HttpResponse
def book_detail(request,book_id):
html = render_to_string("detail.html")
return HttpResponse(html)
  1. 以上方式虽然已经很方便了。但是django还提供了一个更加简便的方式,直接将模板渲染成字符串和包装成 HttpResponse 对象一步到位完成。示例代码如下:
from django.shortcuts import render
def book_list(request):
return render(request,'list.html')

模板查找路径配置:

在项目的 settings.py 文件中。有一个 TEMPLATES 配置,这个配置包含了模板引擎的配置,模板
查找路径的配置,模板上下文的配置等。模板路径可以在两个地方配置。

  1. DIRS :这是一个列表,在这个列表中可以存放所有的模板路径,以后在视图中使用 render 或者 render_to_string 渲染模板的时候,会在这个列表的路径中查找模板。这里的路径是模板最高优先级的路径。
  2. APP_DIRS :默认为 True ,这个设置为 True 后,会在** INSTALLED_APPS** 的安装了的 APP 下的 templates 文件加中查找模板。
  3. 查找顺序:比如代码 render('list.html') 。
    先会在 DIRS 这个列表中依次查找路径下有没有这个模板,如果有,就返回;
    如果 DIRS 列表中所有的路径都没有找到,那么会先检查当前这个视图所处的 app 是否已经安装,如果已经安装了,那么就先在当前这个 app 下的 templates 文件夹中查找模板;
    如果没有找到,那么会在其他已经安装了的 app 中查找。
    如果所有路径下都没有找到,那么会抛出一个 TemplateDoesNotExist 的异常。
模板变量

1.在模板中使用变量,需要将变量放到‘{{变量}}’中
2.如果想要访问对象的属性,那么可以通过‘对象.属性名’来访问

class Person(object):
    def __init__(self,username):
        self.username=username
def index(request):
    username=request.GET.get('username')
    if username:
        p=Person(username)
    context={ 
                'person':p        
             }

访问时使用'{{person.username}}'

3.如果想要访问一个字典的key对应的value,那么只能通过‘字典.key’来访问,不能通过'字典[key]‘来访问

 context={
            'persons':{
                'username':'jubi',
            }
        }

访问时使用'{{persons.username}}'

4.因为在访问字典的key是通过’.'来访问,所以定义字典内容名要避开此类关键字

  context={
            'persons':{
                'username':'jubi',
                'keys':123  #不会报错,但是要避开这种操作
            }
        }

5.访问列表或者元祖,同样通过‘.’来访问,不通过‘[]’,这点不同于python语法

{{persons.0}}

{}:元祖
[ ]:列表

常用的模板标签
if 标签
  • if 标签相当于 Python 中的 if 语句,有 elif 和 else 相对应,但是所有的标签都需要用标签符号( {%%} )进行包裹。 if 标签中可以使用 ==、!=、<、<=、>、>=、in、not
    in、is、is not 等判断运算符。示例代码如下:
{% if "张三" in persons %}
<p>张三</p>
{% else %}
<p>李四</p>
{% endif %}
for...in... 标签

*for...in... 类似于 Python 中的 for...in... 。可以遍历列表、元组、字符串、字典等一切可以遍历的对象。示例代码如下:

{% for person in persons %}
<p>{{ person.name }}</p>
{% endfor %}

如果想要反向遍历,那么在遍历的时候就加上一个 reversed 。示例代码如下:

{% for person in persons reversed %}
<p>{{ person.name }}</p>
{% endfor %}

遍历字典的时候,需要使用 items 、 keys 和 values 等方法。在 DTL 中,执行一个方法不能使用圆括号的形式。遍历字典示例代码如下:

{% for key,value in person.items %}
<p>key:{{ key }}</p>
<p>value:{{ value }}</p>
{% endfor %}

在 for 循环中, DTL 提供了一些变量可供使用。这些变量如下:
_forloop.counter _:当前循环的下标。以1作为起始值。
_forloop.counter0 _:当前循环的下标。以0作为起始值。
forloop.revcounter :当前循环的反向下标值。比如列表有5个元素,那么第一次遍历这个属性是等于5,第二次是4,以此类推。并且是以1作为最后一个元素的下标。
forloop.revcounter0 :类似于forloop.revcounter。不同的是最后一个元素的下标是从0开始。
_forloop.first _:是否是第一次遍历。
forloop.last :是否是最后一次遍历。
forloop.parentloop :如果有多个循环嵌套,那么这个属性代表的是上一级的for循环。

【注意】 模板中的“for……in"没有”break“、"contiue"语句,这一点不同于python

for...in...empty 标签

*这个标签使用跟 for...in... 是一样的,只不过是在遍历的对象如果没有元素的情况下,会执行 empty 中的内容。示例代码如下:

{% for person in persons %}
<li>{{ person }}</li>
{% empty %}
暂时还没有任何人
{% endfor %}
url 标签

*在模版中,我们经常要写一些 url ,比如某个 a 标签中需要定义 href 属性。当然如果通过硬编码的方式直接将这个 url 写死在里面也是可以的。但是这样对于以后项目维护可能不是一件好事。因此建议使用这种反转的方式来实现,类似于 django 中的 reverse 一样。示例代码如下:

<a href="{% url 'book:list' %}">图书列表页面</a>

如果 url 反转的时候需要传递参数,那么可以在后面传递。但是参数分位置参数和关键字参数。位置参数和关键字参数不能同时使用。示例代码如下:

# path部分
path('detail/<book_id>/',views.book_detail,name='detail')
# url反转,使用位置参数
<a href="{% url 'book:detail' 1 %}">图书详情页面</a>
# url反转,使用关键字参数
<a href="{% url 'book:detail' book_id=1 %}">图书详情页面</a>

如果想要在使用 url 标签反转的时候要传递查询字符串的参数,那么必须要手动在在后面添
加。示例代码如下:

<a href="{% url 'book:detail' book_id=1 %}?page=1">图书详情页面</a>

如果需要传递多个参数,那么通过空格的方式进行分隔。示例代码如下:

<a href="{% url 'book:detail' book_id=1 page=2 %}">图书详情页面</a>
DTL常用过滤器

【过滤器存在意义】因为在DTL中,不支持函数的调用形式,这将有很大的局限性。而过滤器其实就是一个函数,可以对需要处理的参数进行处理,并且还可以额外接收一个参数(也就是说最多只能有两个参数)。

add过滤器

将传进来的参数添加到原来的值上面。这个过滤器会尝试将 值 和 参数 转换成整形然后进行相加。如果转换成整形过程中失败了,那么会将 值 和 参数 进行拼接。如果是字符串,那么会拼接成字符串,如果是列表,那么会拼接成一个列表。示例代码如下:

{{ value|add:"2" }}

如果 value 是等于4,那么结果将是6。如果 value 是等于一个普通的字符串,比如 abc ,那么结果将是 abc2 。 add 过滤器的源代码如下:

def add(value, arg):
"""Add the arg to the value."""
try:
return int(value) + int(arg)
except (ValueError, TypeError):
try:
return value + arg
except Exception:
return ''
cut过滤器

移除值中所有指定的字符串。类似于 python 中的 replace(args,"") 。示例代码如下:

{{ value|cut:" " }}
以上示例将会移除 value 中所有的空格字符。 cut 过滤器的源代码如下:
def cut(value, arg):
"""Remove all values of arg from the given string."""
safe = isinstance(value, SafeData)
value = value.replace(arg, '')
if safe and arg != ';':
return mark_safe(value)
return value
date过滤器

将一个日期按照指定的格式,格式化成字符串。示例代码如下:

# 数据
context = {
"birthday": datetime.now()
}
# 模版
{{ birthday|date:"Y/m/d" }}

那么将会输出 2018/02/01 。其中 Y 代表的是四位数字的年份, m 代表的是两位数字的月份, d 代表的是两位数字的日。还有更多时间格式化的方式。见下表。

格式字符 描述 示例
Y 四位数字的年份 2018
m 两位数字的月份 01-12
n 月份,1-9前面没有0前缀 1-12
d 两位数字的天 01-31
j 天,但是1-9前面没有0前缀 1-31
g 小时,12小时格式的,1-9前面没有0前缀 1-12
h 小时,12小时格式的,1-9前面有0前缀 01-12
G 小时,24小时格式的,1-9前面没有0前缀 1-23
H 小时,24小时格式的,1-9前面有0前缀 01-23
i 分钟,1-9前面有0前缀 00-59
s 秒,1-9前面有0前缀 00-59
模板继承
在前端页面开发中。有些代码是需要重复使用的。这种情况可以使用 include 标签来实现。也可以使用另外一个比较强大的方式来实现,那就是模版继承。模版继承类似于 Python 中的类,在父类中可以先定义好一些变量和方法,然后在子类中实现。模版继承也可以在父模版中先定义好一些子模版需要用到的代码,然后子模版直接继承就可以了。并且因为子模版肯定有自己的不同代码,因此可以在父模版中定义一个block接口,然后子模版再去实现。以下是父模版的代码:
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="{% static 'style.css' %}" />
<title>{% block title %}我的站点{% endblock %}</title>
</head>
<body>
<div id="sidebar">
{% block sidebar %}
<ul>
<li><a href="/">首页</a></li>
<li><a href="/blog/">博客</a></li>
</ul>
{% endblock %}
</div>
<div id="content">
{% block content %}{% endblock %}
</div>
</body>
</html>

这个模版,我们取名叫做 base.html ,定义好一个简单的 html 骨架,然后定义好两个 block 接口,让子模版来根据具体需求来实现。子模板然后通过 extends 标签来实现,示例代码如下:

{% extends "base.html" %}
{% block title %}博客列表{% endblock %}
{% block content %}
{% for entry in blog_entries %}
<h2>{{ entry.title }}</h2>
<p>{{ entry.body }}</p>
{% endfor %}
{% endblock %}

需要注意的是:extends标签必须放在模版的第一行。
子模板中的代码必须放在block中,否则将不会被渲染。

如果在某个 block 中需要使用父模版的内容,那么可以使用 {{block.super}} 来继承。比如上例, {%block title%} ,如果想要使用父模版的 title ,那么可以在子模版的 title block 中使用 {{ block.super }} 来实现。在定义 block 的时候,除了在 block 开始的地方定义这个 block 的名字,还可以在 block 结束的时候定义名字。比如 {% block title %}{% endblock title %} 。这在大型模版中显得尤其有用,能让你快速的看到 block 包含在哪里。
加载静态文件
在一个网页中,不仅仅只有一个 html 骨架,还需要 css 样式文件, js 执行文件以及一些图片等。因此在 DTL 中加载静态文件是一个必须要解决的问题。在 DTL 中,使用 static 标签来加载静态文件。要使用 static 标签,首先需要 {% load static %} 。加载静态文件的步骤如下:
  1. 首先确保 django.contrib.staticfiles 已经添加到 settings.INSTALLED_APPS 中。

  2. 确保在 settings.py 中设置了 STATIC_URL 。

  3. 在已经安装了的 app 下创建一个文件夹叫做 static ,然后再在这个 static 文件夹下创建一个当前 app 的名字的文件夹,再把静态文件放到这个文件夹下。例如你的 app 叫做 book ,有一个静态文件叫做 zhiliao.jpg ,那么路径为 book/static/book/zhiliao.jpg 。(为什么在 app 下创建一个 static 文件夹,还需要在这个 static 下创建一个同 app 名字的文件夹呢?原因是如果直接把静态文件放在 static 文件夹下,那么在模版加载静态文件的时候就是使用 zhiliao.jpg ,如果在多个 app 之间有同名的静态文件,这时候可能就会产生混淆。而在 static 文件夹下加了一个同名 app 文件夹,在模版中加载的时候就是使用 app/zhiliao.jpg ,这样就可以避免产生混淆。)

  4. 如果有一些静态文件是不和任何 app 挂钩的。那么可以在 settings.py 中添加 STATICFILES_DIRS ,以后 DTL 就会在这个列表的路径中查找静态文件。比如可以设置为:

STATICFILES_DIRS = [
os.path.join(BASE_DIR,"static")
]
  1. 在模版中使用 load 标签加载 static 标签。比如要加载在项目的 static 文件夹下的 style.css 的文件。那么示例代码如下:
{% load static %}
<link rel="stylesheet" href="{% static 'style.css' %}">
  1. 如果不想每次在模版中加载静态文件都使用 load 加载 static 标签,那么可以在 settings.py 中的 TEMPLATES/OPTIONS 添加 'builtins':['django.templatetags.static'] ,这样以后在模版中就可以直接使用 static 标签,而不用手动的 load 了。

  2. 如果没有在 settings.INSTALLED_APPS 中添加 django.contrib.staticfiles 。那么我们就需要手动的将请求静态文件的 url 与静态文件的路径进行映射了。示例代码如下:

from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
# 其他的url映射
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

Django配置连接数据库

在操作数据库之前,首先先要连接数据库。这里我们以配置 MySQL 为例来讲解。 Django 连接数据库,不需要单独的创建一个连接对象。只需要在 settings.py 文件中做好数据库相关的配置就可以了。示例代码如下:

DATABASES = {
'default': {
# 数据库引擎(是mysql还是oracle等)
'ENGINE': 'django.db.backends.mysql',
# 数据库的名字
'NAME': 'dfz',
# 连接mysql数据库的用户名
'USER': 'root',
# 连接mysql数据库的密码
'PASSWORD': 'root',
# mysql数据库的主机地址
'HOST': '127.0.0.1',
# mysql数据库的端口号
'PORT': '3306',
}
}

在Django中操作数据库:
在 Django 中操作数据库有两种方式。第一种方式就是使用原生 sql 语句操作,第二种就是使用 ORM 模型来操作。这节课首先来讲下第一种。在 Django 中使用原生 sql 语句操作其实就是使用 python db api 的接口来操作。如果你
的 mysql 驱动使用的是 pymysql ,那么你就是使用 pymysql 来操作的,只不过 Django 将数据库连接的这一部分封装好了,我们只要在 settings.py 中配置好了数据库连接信息后直接使用 Django 封装好的接口就可以操作了。示例代码如下:

# 使用django封装好的connection对象,会自动读取settings.py中数据库的配置信息
from django.db import connection
# 获取游标对象
cursor = connection.cursor()
# 拿到游标对象后执行sql语句
cursor.execute("select * from book")
# 获取所有的数据
rows = cursor.fetchall()
# 遍历查询到的数据
for row in rows:
print(row)

以上的 execute 以及 fetchall 方法都是 Python DB API 规范中定义好的。任何使用 Python 来操作 MySQL 的驱动程序都应该遵循这个规范。所以不管是使用 pymysql 或者是 mysqlclient 或者是 mysqldb ,他们的接口都是一样的。
更多规范请参考:https://www.python.org/dev/peps/pep-0249/

Python DB API下规范下cursor对象常用接口:
  1. description :如果 cursor 执行了查询的 sql 代码。那么读取 cursor.description 属性的时候,将返回一个列表,这个列表中装的是元组,元组中装的分别是 (name,type_code,display_size,internal_size,precision,scale,null_ok) ,其中 name 代
    表的是查找出来的数据的字段名称,其他参数暂时用处不大。
  2. rowcount :代表的是在执行了 sql 语句后受影响的行数。
  3. close :关闭游标。关闭游标以后就再也不能使用了,否则会抛出异常。
  4. execute(sql[,parameters]) :执行某个 sql 语句。如果在执行 sql 语句的时候还需要传递参数,那么可以传给 parameters 参数。示例代码如下:
cursor.execute("select * from article where id=%s",(1,))
  1. fetchone :在执行了查询操作以后,获取第一条数据。
  2. fetchmany(size) :在执行查询操作以后,获取多条数据。具体是多少条要看传的 size 参数。如果不传 size 参数,那么默认是获取第一条数据。
  3. fetchall :获取所有满足 sql 语句的数据。
(views.py)
from django.shortcuts import render,reverse,redirect
from django.db import connection

# Create your views here.
def get_corsor():
    return connection.cursor()


def index(request):
    cursor=get_corsor()
    cursor.execute("select id,name,author from book ")
    books=cursor.fetchall()
    return render(request,'index.html',context={"books":books})

def add_book(request):
    if request.method=='GET':
       return render(request,'add_book.html')
    else:
        name=request.POST.get("name")
        author=request.POST.get("author")
        cursor=get_corsor()
        cursor.execute("insert into book(id,name,author) value(null,'%s','%s')" % (name,author))
        return  redirect(reverse('index'))


def book_detail(request,book_id):
    cursor=get_corsor()
    cursor.execute("select id,name,author from book where id=%s " % book_id)
    book = cursor.fetchone()
    return render(request,'book_detail.html',context={"book":book})

def delete_book(request):
    if request.method == 'POST':
        book_id=request.POST.get('book_id')
        cursor=get_corsor()
        cursor.execute("delete from book where id=%s"% book_id)
        return redirect(reverse('index'))
    else:
        raise RuntimeError("删除图书method错误")
ORM模型介绍

随着项目越来越大,采用写原生SQL的方式在代码中会出现大量的SQL语句,那么问题就出现了:

  1. SQL语句重复利用率不高,越复杂的SQL语句条件越多,代码越长。会出现很多相近的SQL语句。
  2. 很多SQL语句是在业务逻辑中拼出来的,如果有数据库需要更改,就要去修改这些逻辑,这会很容易漏掉对某些SQL语句的修改。
  3. 写SQL时容易忽略web安全问题,给未来造成隐患。SQL注入。ORM ,全称 Object Relational Mapping ,中文叫做对象关系映射,通过 ORM 我们可以通过类的方式去操作数据库,而不用再写原生的SQL语句。通过把表映射成类,把行作实例,把字段作为属性, ORM 在执行对象操作的时候最终还是会把对应的操作转换为数据库原生语句。使用 ORM 有许多优点:
  4. 易用性:使用 ORM 做数据库的开发可以有效的减少重复SQL语句的概率,写出来的模型也更加直观、清晰。
  5. 性能损耗小: ORM 转换成底层数据库操作指令确实会有一些开销。但从实际的情况来看,这种性能损耗很少(不足5%),只要不是对性能有严苛的要求,综合考虑开发效率、代码的阅读性,带来的好处要远远大于性能损耗,而且项目越大作用越明显。
  6. 设计灵活:可以轻松的写出复杂的查询。4. 可移植性: Django 封装了底层的数据库实现,支持多个关系数据库引擎,包括流行的 MySQL 、 PostgreSQL 和 SQLite 。可以非常轻松的切换数据库。

[图片上传失败...(image-f05570-1535721616106)]

创建ORM模型:
ORM 模型一般都是放在 app 的 models.py 文件中。每个 app 都可以拥有自己的模型。并且如果这个模型想要映射到数据库中,那么这个 app 必须要放在 settings.py 的 INSTALLED_APP 中进行安装。以下是写一个简单的书籍 ORM 模型。示例代码如下:

from django.db import models
class Book(models.Model):
name = models.CharField(max_length=20,null=False)
author = models.CharField(max_length=20,null=False)
pub_time = models.DateTimeField(default=datetime.now)
price = models.FloatField(default=0)

以上便定义了一个模型。这个模型继承自 django.db.models.Model ,如果这个模型想要映射到数据库中,就必须继承自这个类。这个模型以后映射到数据库中,表名是模型名称的小写形式,为 book 。在这个表中,有四个字段,一个为 name ,这个字段是保存的是书的名称,是 varchar 类型,最长不能超过20个字符,并且不能为空。第二个字段是作者名字类型,同样也
是 varchar 类型,长度不能超过20个。第三个是出版时间,数据类型是 datetime 类型,默认是保存这本书籍的时间。第五个是这本书的价格,是浮点类型。还有一个字段我们没有写,就是主键 id ,在 django 中,如果一个模型没有定义主键,那么将会自动生成一个自动增长的 int 类型的主键,并且这个主键的名字就叫做 id 。

映射模型到数据库中:

将 ORM 模型映射到数据库中,总结起来就是以下几步:

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

在 ORM 框架中,所有模型相关的操作,比如添加/删除等。其实都是映射到数据库中一条数据的操作。因此模型操作也就是数据库表中数据的操作。

添加一个模型到数据库中:

添加模型到数据库中。首先需要创建一个模型。创建模型的方式很简单,就跟创建普通的 Python 对象是一摸一样的。在创建完模型之后,需要调用模型的 save 方法,这样 Django 会自动的将这个模型转换成 sql 语句,然后存储到数据库中。示例代码如下:

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)
book = Book(name='三国演义',desc='三国英雄!')
book.save()

添加数据:只要使用哪个ORM模型创建一个对象,然后再调用这个ORM模型的‘save’方法就可以了

book=Book(name='三国演义',author='罗贯中',price=200)
book.save()

查找数据:
查找数据都是通过模型下的 objects 对象来实现的。
查找所有数据:
要查找 Book 这个模型对应的表下的所有数据。那么示例代码如下:

books = Book.objects.all()

查找数据:所有的查找工作都是使用模型上的'objects‘属性来完成的,当然也可以自定义查询对象
1.使用主键进行查找

    # book=Book.objects.get(pk=1)
    # print(book)

2.根据其他条件查找

    # books=Book.objects.filter(name='三国演义').first()
    # print(books)

以上将返回 Book 模型下的所有数据。
数据过滤:
在查找数据的时候,有时候需要对一些数据进行过滤。那么这时候需要调用 objects 的 filter 方法。实例代码如下:

books = Book.objects.filter(name='三国演义')
>[<Book:三国演义>]
books = Book.objects.filter(name='三国演义',desc='test')

**使用filter方法返回的是一个“QuerySet对象,这个对象类似于列表,我们可以使用这个对象的’first()‘方法来获取第一个值。

获取单个对象:
使用 filter 返回的是所有满足条件的结果集。有时候如果只需要返回第一个满足条件的对象。那
么可以使用 get 方法。示例代码如下:

book = Book.objects.get(name='三国演义')
> <Book:三国演义>

当然,如果没有找到满足条件的对象,那么就会抛出一个异常。而 filter 在没有找到满足条件的
数据的时候,是返回一个空的列表。

数据排序:
在之前的例子中,数据都是无序的。如果你想在查找数据的时候使用某个字段来进行排序,那么可
以使用 order_by 方法来实现。示例代码如下:

books = Book.objects.order_by("pub_date")

以上代码在提取所有书籍的数据的时候,将会使用 pub_date 从小到大进行排序。如果想要进行倒
序排序,那么可以在 pub_date 前面加一个负号。实例代码如下:

books = Book.objects.order_by("-pub_date")

修改数据:
在查找到数据后,便可以进行修改了。修改的方式非常简单,只需要将查找出来的对象的某个属性
进行修改,然后再调用这个对象的 save 方法便可以进行修改。示例代码如下:

from datetime import datetime
book = Book.objects.get(name='三国演义')
book.pub_date = datetime.now()
book.save()

删除数据:
在查找到数据后,便可以进行删除了。删除数据非常简单,只需要调用这个对象的 delete 方法即可。实例代码如下:

book = Book.objects.get(name='三国演义')
book.delete()
常用字段:

在 Django 中,定义了一些 Field 来与数据库表中的字段类型来进行映射。以下将介绍那些常用
的字段类型。
AutoField:
映射到数据库中是 int 类型,可以有自动增长的特性。一般不需要使用这个类型,如果不指定主键,那么模型会自动的生成一个叫做 id 的自动增长的主键。如果你想指定一个其他名字的并且具有自动增长的主键,使用 AutoField 也是可以的。
BigAutoField:
64位的整形,类似于 AutoField ,只不过是产生的数据的范围是从 1-9223372036854775807 。
BooleanField:
在模型层面接收的是 True/False 。在数据库层面是 tinyint 类型。如果没有指定默认值,默认值是 None 。


#该属性常被用作标记网页文章是否被删除(数据库备份不删)
#指定默认值为true
removed=models.NullBooleanFiled()

CharField:
在数据库层面是 varchar 类型。在 Python 层面就是普通的字符串。这个类型在使用的时候必须要指定最大的长度,也即必须要传递 max_length 这个关键字参数进去。
如果超过254个字符就不建议使用该字段,而建议使用TextField。

DateField:
日期类型。在 Python 中是 datetime.date 类型,可以记录年月日。在映射到数据库中也是 date 类型。使用这个 Field 可以传递以下几个参数:

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

DateTimeField:
日期时间类型,类似于 DateField 。不仅仅可以存储日期,还可以存储时间。映射到数据库中是 datetime 类型。这个 Field 也可以使用 auto_now 和 auto_now_add 两个属性。

TimeField:
时间类型。在数据库中是 time 类型。在 Python 中是 datetime.time 类型。

EmailField:
类似于 CharField 。在数据库底层也是一个 varchar 类型。最大长度是254个字符。
在数据库层面并不会限制邮箱格式,但是在后续ModelForm表单相关操作中会起作用。

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/

外键和表关系

外键:

在 MySQL 中,表有两种引擎,一种是 InnoDB ,另外一种是 myisam 。如果使用的是 InnoDB 引擎,是支持外键约束的。外键的存在使得 ORM 框架在处理表关系的时候异常的强大。因此这里我们首先来介绍下外键在 Django 中的使用。
类定义为 class ForeignKey(to,on_delete,**options) 。第一个参数是引用的是哪个模型,第二个参数是在使用外键引用的模型数据被删除了,这个字段该如何处理,比如有 CASCADE 、 SET_NULL 等。这里以一个实际案例来说明。比如有一个 User 和一个 Article 两个模型。一个 User 可以发表多篇文章,一个 Article 只能有一个 Author ,并且通过外键进行引用。那么相关的示例代码如下:

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)

以上使用 ForeignKey 来定义模型之间的关系。即在 article 的实例中可以通过 author 属性来操作对应的 User 模型。这样使用起来非常的方便。示例代码如下:

article = Article(title='abc',content='123')
author = User(username='张三',password='111111')
article.author = author
article.save()
# 修改article.author上的值
article.author.username = '李四'
article.save()

为什么使用了 ForeignKey 后,就能通过 author 访问到对应的 user 对象呢。因此在底层, Django 为 Article 表添加了一个 属性名_id 的字段(比如author的字段名称是author_id),这个字段是一个外键,记录着对应的作者的主键。以后通过article.author 访问的时候,实际上是先通过 author_id 找到对应的数据,然后再提取 User 表中的这条数据,形成一个模型。
如果想要引用另外一个 app 的模型,那么应该在传递 to 参数的时候,使用 app.model_name 进行指定。以上例为例,如果 User 和 Article 不是在同一个 app 中,那么在引用的时候的示例代码如下:

# User模型在user这个app中
class User(models.Model):
username = models.CharField(max_length=20)
password = models.CharField(max_length=100)
# Article模型在article这个app中
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
author = models.ForeignKey("user.User",on_delete=models.CASCADE)

如果模型的外键引用的是本身自己这个模型,那么 to 参数可以为 'self' ,或者是这个模型的名字。在论坛开发中,一般评论都可以进行二级评论,即可以针对另外一个评论进行评论,那么在定义模型的时候就需要使用外键来引用自身。示例代码如下:

class Comment(models.Model):
content = models.TextField()
origin_comment = models.ForeignKey('self',on_delete=models.CASCADE,null=True)
# 或者
# origin_comment = models.ForeignKey('Comment',on_delete=models.CASCADE,null=True)

外键删除操作:
如果一个模型使用了外键。那么在对方那个模型被删掉后,该进行什么样的操作。可以通过 on_delete 来指定。可以指定的类型如下:

  1. CASCADE :级联操作。如果外键对应的那条数据被删除了,那么这条数据也会被删除。
  2. PROTECT :受保护。即只要这条数据引用了外键的那条数据,那么就不能删除外键的那条数据。
  3. SET_NULL :设置为空。如果外键的那条数据被删除了,那么在本条数据上就将这个字段设置为空。如果设置这个选项,前提是要指定这个字段可以为空。
  4. SET_DEFAULT :设置默认值。如果外键的那条数据被删除了,那么本条数据上就将这个字段设置为默认值。如果设置这个选项,前提是要指定这个字段一个默认值。
  5. SET() :如果外键的那条数据被删除了。那么将会获取 SET 函数中的值来作为这个外键的值。 SET 函数可以接收一个可以调用的对象(比如函数或者方法),如果是可以调用的对象,那么会将这个对象调用后的结果作为值返回回去。
  6. DO_NOTHING :不采取任何行为。一切全看数据库级别的约束。以上这些选项只是Django级别的,数据级别依旧是RESTRICT!
表关系:

表之间的关系都是通过外键来进行关联的。而表之间的关系,无非就是三种关系:一对一、一对多(多对一)、多对多等。以下将讨论一下三种关系的应用场景及其实现方式。

一对多:
  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='1SA11',content='22SAS2')
  article.author=FrontUser.objects.first()

  category.articles.add(article,bulk=False)

*使用“bulk=False”
那么Django会自动保存article,而不需要再添加到category前保存article

一对一:
  1. 应用场景:比如一个用户表和一个用户信息表。在实际网站中,可能需要保存用户的许多信息,但是有些信息是不经常用的。如果把所有信息都存放到一张表中可能会影响查询效率,因此可以把用户的一些不常用的信息存放到另外一张表中我们叫做 UserExtension 。但是用户表 User 和用户信息表 UserExtension 就是典型的一对一了。
  2. 实现方式: Django 为一对一提供了一个专门的 Field 叫做 OneToOneField 来实现一对一操作。示例代码如下:
class User(models.Model):
username = models.CharField(max_length=20)
password = models.CharField(max_length=100)
class UserExtension(models.Model):
birthday = models.DateTimeField(null=True)
school = models.CharField(blank=True,max_length=50)
user = models.OneToOneField("User", on_delete=models.CASCADE)

在 UserExtension 模型上增加了一个一对一的关系映射。其实底层是在 UserExtension 这个表上增加了一个 user_id ,来和 user 表进行关联,并且这个外键数据在表中必须是唯一的,来保证一对一。

查询操作

查找是数据库操作中一个非常重要的技术。查询一般就是使用 filter 、 exclude 以及 get 三个方法来实现。我们可以在调用这些方法的时候传递不同的参数来实现查询需求。在 ORM 层面,这些查询条件都是使用 field + __ + condition 的方式来使用的。以下将那些常用的查询条件来一一解释。

查询条件

exact

exact:
使用精确的 = 进行查找。如果提供的是一个 None ,那么在 SQL 层面就是被解释为 NULL 。示例代码如下:

article = Article.objects.get(id__exact=14)
article = Article.objects.get(id__exact=None)

以上的两个查找在翻译为 SQL 语句为如下:

select ... from article where id=14;
select ... from article where id IS NULL;
iexact

iexact:
使用 like 进行查找。示例代码如下:

article = Article.objects.filter(title__iexact='hello world')

那么以上的查询就等价于以下的 SQL 语句:

select ... from article where title like 'hello world';

*注意上面这个 sql 语句,因为在 MySQL 中,没有一个叫做 ilike 的。所以 exact 和 iexact 的区别实际上就是 LIKE 和 = 的区别,在大部分 collation=utf8_general_ci 情况下都是一样的( collation 是用来对字符串比较的),只有少数情况是不等价的。
即大多数查找中article = Article.objects.get(id=14)等价article = Article.objects.get(id__exact=14)等价article = Article.objects.get(id__iexact=14),可以直接使用=,更加简洁。
like字段如果在某一规则中是和=产生差别的,那么在语句中会表现成含有%的字段,进行的是模糊查找
这里涉及到的是mysql中的排序规则。这些排序规则和自己的规则包括系统(win or linux都有区别)

QuerySet.query:
*’query‘可以用来查看这个‘ORM’查询语句最终被翻译完成的‘SQL’语句,但是‘query'只能用在’QuerySet'对象上,不能用在普通的ORM模型上。因此如果你得查询语句是通过‘get’来获取数据的,那么久不能使用‘query;,’get‘方法返回的是满足条件的ORM模型。
*通过’filter‘等其他查询方法返回的是’QuerySet‘,那么就可以使用’query‘。

conatins

contains
使用大小写不明感的判断,某个字符串是否包含在指定的字段中,这个查询条件会使用大小写敏感。因此在被翻译成SQL语句的时候,会使用’like binary‘,而’like binary‘是使用大小写敏感的。

iconatins

icontains
使用大小写不敏感的判断,某个字符串是否包含在指定的字段值,这个查询语句在被翻译陈’SQL'语句的时候,使用的是‘like’,而‘like’在‘MySQL'层面就是不区分大小写的。

*contains和icontains翻译成sql语句是%bulabula%,包含即可
而iexact和exact需要完全相等。

startapp 操作的小插入

这一步可以在pycharm编译器内部完成
tool->run manage.py task->指令
但是即使这里出错cmd中也总是万能不会出错的!
startapp ^^^
makemigrations
migrate

ORM聚合函数

1.所有的聚合函数都是放在‘django.db.models'下面。
2.聚合函数不能够单独执行,需要放在一些可以执行聚合函数的方法下面中去执行,比如“aggregate”,示例代码:

result=Book.objects.aggregate(Avg("price"))

3.聚合函数执行完成后,会给这个聚合函数的值取个名字,取名字的规则,默认是“price_avg”,如果不想使用默认的名字,那么可以在使用聚合函数的时候传递关键字参数进去,参数的名字就是聚合函数执行的完成的名字,示例代码:

result=Book.objects.aggregate(avg=Avg("price"))

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

*更多的聚合函数请参考官方文档:https://docs.djangoproject.com/en/2.0/ref/models/querysets/#aggregation-functions

objects对象所属类原理剖析

book.objects返回的类是<class 'django.db.models.manager.Manager'>

class Manager(BaseManager.from_queryset(QuerySet)):
    pass

Manager中具体方法是没有的(pass),继承的父类便是Manager的全部属性,其中传递参数是(QuerySet)

    def from_queryset(cls, queryset_class, class_name=None):
        if class_name is None:
            class_name = '%sFrom%s' % (cls.__name__, queryset_class.__name__)
        return type(class_name, (cls,), {
            '_queryset_class': queryset_class,
            **cls._get_queryset_methods(queryset_class),
        })

继承的from_queryset方法最后一个参数是在没有定义的情况下给方法命名,返回的是type方法各参数继续传递

class type(object):
    """
    type(object_or_name, bases, dict)
    type(object) -> the object's type
    type(name, bases, dict) -> a new type
    """
    def mro(self, *args, **kwargs): # real signature unknown
        """ Return a type's method resolution order. """
        pass

    def __call__(self, *args, **kwargs): # real signature unknown
        """ Call self as a function. """
        pass

    def __delattr__(self, *args, **kwargs): # real signature unknown
        """ Implement delattr(self, name). """
        pass

    def __dir__(self, *args, **kwargs): # real signature unknown
        """ Specialized __dir__ implementation for types. """
        pass

    def __getattribute__(self, *args, **kwargs): # real signature unknown
        """ Return getattr(self, name). """
        pass

    def __init__(cls, what, bases=None, dict=None): # known special case of type.__init__
        """
        type(object_or_name, bases, dict)
        type(object) -> the object's type
        type(name, bases, dict) -> a new type
        # (copied from class doc)
        """
        pass

    def __instancecheck__(self, *args, **kwargs): # real signature unknown
        """ Check if an object is an instance. """
        pass

    @staticmethod # known case of __new__
    def __new__(*args, **kwargs): # real signature unknown
        """ Create and return a new object.  See help(type) for accurate signature. """
        pass

    def __prepare__(self): # real signature unknown; restored from __doc__
        """
        __prepare__() -> dict
        used to create the namespace for the class statement
        """
        return {}

    def __repr__(self, *args, **kwargs): # real signature unknown
        """ Return repr(self). """
        pass

    def __setattr__(self, *args, **kwargs): # real signature unknown
        """ Implement setattr(self, name, value). """
        pass

    def __sizeof__(self, *args, **kwargs): # real signature unknown
        """ Return memory consumption of the type object. """
        pass

    def __subclasscheck__(self, *args, **kwargs): # real signature unknown
        """ Check if a class is a subclass. """
        pass

    def __subclasses__(self, *args, **kwargs): # real signature unknown
        """ Return a list of immediate subclasses. """
        pass

    __abstractmethods__ = property(lambda self: object(), lambda self, v: None, lambda self: None)  # default


    __bases__ = (
        object,
    )
    __base__ = object
    __basicsize__ = 864
    __dictoffset__ = 264
    __dict__ = None # (!) real value is ''
    __flags__ = -2146675712
    __itemsize__ = 40
    __mro__ = (
        None, # (!) forward: type, real value is ''
        object,
    )
    __name__ = 'type'
    __qualname__ = 'type'
    __text_signature__ = None
    __weakrefoffset__ = 368

可见type动态的时候创建类:
第一个参数是用来指导创建的类的名字,创建的类名是:BaseManagerFrom(QuerySet)
第二个参数是用来指定这个类的父类
第三个参数是用来指定这个类的属性和方法
return type(class_name,(cls,),class_dict)
*type函数还可以用于查看一个对象属于哪个类

{
            '_queryset_class': queryset_class,
            **cls._get_queryset_methods(queryset_class)
 }
  def _get_queryset_methods(cls, queryset_class):
        def create_method(name, method):
            def manager_method(self, *args, **kwargs):
                return getattr(self.get_queryset(), name)(*args, **kwargs)
            manager_method.__name__ = method.__name__
            manager_method.__doc__ = method.__doc__
            return manager_method

        new_methods = {}
#从queryset_class中遍历,获取其中成员(过滤只要方法)
        for name, method in inspect.getmembers(queryset_class, predicate=inspect.isfunction):
            # Only copy missing methods.
            if hasattr(cls, name):
                continue
            # Only copy public methods or methods with the attribute `queryset_only=False`.
            queryset_only = getattr(method, 'queryset_only', None)
            if queryset_only or (queryset_only is None and name.startswith('_')):
                continue
            # Copy the method onto the manager.
            new_methods[name] = create_method(name, method)
        return new_methods

可以得出结论:我们通常做查询操作的时候,都是通过 模型名字.objects 的方式进行操作。其实 模型名字.objects 是一个 django.db.models.manager.Manager 对象,而 Manager 这个类是一个“空壳”的类,他本身是没有任何的属性和方法的。他的方法全部都是通过 Python 动态添加的方式,从 QuerySet 类中拷贝过来的

[图片上传失败...(image-28f7f-1535721616106)]

QuerySet API详解-filter、exclude、annotate

  1. filter :将满足条件的数据提取出来,返回一个新的 QuerySet 。具体的 filter 可以提供
    什么条件查询。请见查询操作章节。
books=Book.objects.filter(id__gte=2).filter(~Q(id=3))
  1. exclude :排除满足条件的数据,返回一个新的 QuerySet 。示例代码如下:
books = Book.objects.filter(id__gte=2).exclude(id=3)

以上代码的意思是提取那些标题不包含 hello 的图书。

  1. annotate :给 QuerySet 中的每个对象都添加一个使用查询表达式(聚合函数、F表达式、Q表达式、Func表达式等,可以根据提示导入)的新字段。示例代码如下:
    books=Book.objects.annotate(author_name=F("author__name"))#这种方式只用在数据库查找一次,效率比较高
    for book in books:
        print('%s/%s'%(book.name,book.author_name))

以上代码将在每个对象中都添加一个 author__name 的字段,用来显示书的作者。

  1. order_by :指定将查询的结果根据某个字段进行排序。如果要倒叙排序,那么可以在这个字段的前面加一个负号。示例代码如下:
    #1.根据price从小到大进行排序
     price=BookOrder.objects.order_by('price')
    # 2.根据price从大到小进行排序
     price=BookOrder.objects.order_by("-price")
    # 3.首先根据price从大到小进行排序,如果price是一样的,那么根据id从大到小进行排序
     price = BookOrder.objects.order_by("-price","-id")
    # for p in price:
    #     # print(("%s/%s")%(p.id,p.price))
    #4.根据订单的评分从小到大进行排序
     orders=BookOrder.objects.order_by("book__rating")
     for order in orders:
         print("%s/%s"%(order.id,order.book.rating))
    #5.根据图书数据,根据图书的销量进行排序   
     books=Book.objects.annotate(order_nums=Count("bookorder")).order_by(("-order_nums"))#表名小写代表关联表,没有特别标注默认对其关联id从小到大排序 for book in books:                    
     print("%s/%s"%(book.name,book.order_nums))

一定要注意的一点是,多个 order_by ,会把前面排序的规则给打乱,而使用后面的排序方式。比如以下代码:

articles = Article.objects.order_by("create_time").order_by("author__name")

他会根据作者的名字进行排序,而不是使用文章的创建时间。

另一种排序用法(1)先在models的Meta中定义一种排序方式(2)views视图函数用对象all方法

#(1)
class BookOrder(models.Model):
# """图书订单模型"""
    book = models.ForeignKey("Book",on_delete=models.CASCADE)
    price = models.FloatField()
    class Meta:
        db_table = 'book_order'
        ordering=['-price','id']
#(2)
orders=BookOrder.objects.all()

个人感受是数据库这块尤其涉及外键的代码,在查找过滤上还是逻辑性比较强需要练习摸索的

orm练习

1.查询平均成绩大于60分的同学的id和平均成绩

 # 1.查询平均成绩大于60分的同学的id和平均成绩;
    students1=Student.objects.annotate(score_avg=Avg('score__number')).filter(score_avg__gt=60).values('id','score_avg')#__gte:>=,gt:>
    for student in students1:  
        print(student)

SELECT student.id, AVG(score.number) AS score_avg FROM student LEFT OUTER JOIN score ON (student.id = score.student_id) GROUP BY student.id HAVING AVG(score.number) > 60 ORDER BY NULL'

2.查询所有同学的id、姓名、选课的数量、总成绩;values('id','name')

 # 2.查询所有同学的id、姓名、选课的数量、总成绩;
 students2=Student.objects.annotate(course_num=Count('score'),score_sum=Sum('score__number')).values('id','name','course_num','score_sum')
 for student in students2: 
  print(student)

SELECT student.id, student.name, COUNT(score.id) AS course_num, SUM(score.number) AS score_sum FROM student LEFT OUTER JOIN score ON (student.id = score.student_id) GROUP BY student.id ORDER BY NULL'

3.查询姓“李”的老师的个数;

 # 3.查询姓“李”的老师的个数;
 teachers3=Teacher.objects.filter(name__contains='李').annotate(teacher_num=Count('name')).values('teacher_num')#contains->startswith
 for teacher in teachers3:   
  print(teacher)

SELECT COUNT(teacher.name) AS teacher_num FROM teacher WHERE teacher.name LIKE BINARY '%李%' GROUP BY teacher.id ORDER BY NULL

4.查询没学过“李老师”课的同学的id、姓名;

 # 4.查询没学过“李老师”课的同学的id、姓名;
 students4=Student.objects.exclude(score__course__teacher__name__contains='李').values('id','name')#contains->startswith
 for student in students4:   
  print(student)

SELECT student.id, student.name FROM student WHERE NOT (student.id IN (SELECT U1.student_id FROM score U1 INNER JOIN course U2 ON (U1.course_id = U2.id) INNER JOIN teacher U3 ON (U2.teacher_id = U3.id) WHERE U3.name LIKE BINARY '%李%'))

5.查询学过课程id为1和2的所有同学的id、姓名;

 # 5.查询学过课程id为1和2的所有同学的id、姓名;_lte
 students5=Student.objects.filter(Q(score__course_id=1)|Q(score__course_id=2)).filter().values('id','name').distinct()
 for student in students5: 
  print(student)

SELECT DISTINCT student.id, student.name FROM student INNER JOIN score ON (student.id = score.student_id) WHERE (score.course_id = 1 OR score.course_id = 2)

6.查询学过“黄老师”所教的“所有课”的同学的id、姓名;

 # 6.查询学过“黄老师”所教的“所有课”的同学的id、姓名;
 students6=Student.objects.filter(score__course__teacher__name__startswith='黄').values('id','name').distinct()
 for student in students6: 
  print(student)

SELECT DISTINCT student.id, student.name FROM student INNER JOIN score ON (student.id = score.student_id) INNER JOIN course ON (score.course_id = course.id) INNER JOIN teacher ON (course.teacher_id = teacher.id) WHERE teacher.name LIKE BINARY '黄%

7.查询所有课程成绩小于60分的同学的id和姓名;

 # 7.查询所有课程成绩小于60分的同学的id和姓名;
 students7=Student.objects.exclude(score__number__gte=60).values('id','name')
 for student in students7:  
  print(student)

SELECT student.id, student.name FROM student WHERE NOT (student.id IN (SELECT U1.student_id FROM score U1 WHERE U1.number >= 60))

8.查询没有学全所有课的同学的id、姓名;

 # 8.查询没有学全所有课的同学的id、姓名;
 students8=Student.objects.annotate(course_num=Count('score__course_id')).filter(course_num__lt=4).values('id','name')
 for student in students8:   
  print(student)

SELECT student.id, student.name FROM student LEFT OUTER JOIN score ON (student.id = score.student_id) GROUP BY student.id HAVING COUNT(score.course_id) < 4 ORDER BY NULL

9.查询所有学生的姓名、平均分,并且按照平均分从高到低排序;

 # 9.查询所有学生的姓名、平均分,并且按照平均分从高到低排序;

  students9=Student.objects.annotate(avg=Avg('score__number')).order_by('-avg').values('name','avg')
  for student in students9:  
   print(student)

SELECT student.name, AVG(score.number) AS avg FROM student LEFT OUTER JOIN score ON (student.id = score.student_id) GROUP BY student.id ORDER BY avg DESC

10.查询各科成绩的最高和最低分,以如下形式显示:课程ID,课程名称,最高分,最低分;

 # 10.查询各科成绩的最高和最低分,以如下形式显示:课程ID,课程名称,最高分,最低分;
 courses10=Course.objects.annotate(smax=Max('score__number'),smin=Min('score__number')).values('id','name','smax','smin')
 for course in courses10:   
  print(course)

SELECT course.id, course.name, MAX(score.number) AS smax, MIN(score.number) AS smin FROM course LEFT OUTER JOIN score ON (course.id = score.course_id) GROUP BY course.id ORDER BY NULL

_ 11.查询每门课程的平均成绩,按照平均成绩进行排序;_

 # 11.查询每门课程的平均成绩,按照平均成绩进行排序;
 courses11=Course.objects.annotate(avg=Avg('score__number')).order_by('avg').values('name','avg')
 for course in courses11: 
  print(course)

SELECT course.name, AVG(score.number) AS avg FROM course LEFT OUTER JOIN score ON (course.id = score.course_id) GROUP BY course.id ORDER BY avg ASC'

__

 # 12.统计总共有多少女生,多少男生;
 students12=Student.objects.aggregate(male_gender=Count('gender',filter=Q(gender='1')),female_gender=Count('gender',filter=Q(gender='2')))
 print(students12)

13.将“黄老师”的每一门课程都在原来的基础之上加5分

 # 13.将“黄老师”的每一门课程都在原来的基础之上加5分;
 scores13=Score.objects.filter(course__teacher__name='黄老师').update(number=F('number')+5)
 print(scores13)

SELECT score.id FROM score INNER JOIN course ON (score.course_id = course.id) INNER JOIN teacher ON (course.teacher_id = teacher.id) WHERE teacher.name = '黄老师'
UPDATE score SET number = (score.number + 5) WHERE score.id IN (1, 5, 9, 13, 3, 7, 11, 15)

14.查询两门以上不及格的同学的id、姓名、以及不及格课程数;

 # 14.查询两门以上不及格的同学的id、姓名、以及不及格课程数;
 students14=Student.objects.annotate(fail_num=Count('score__number',filter=Q(score__number__lt='60'))).filter(fail_num__gt=2).values('id','name','fail_num')
 for student in students14:
  print(student)

SELECT student.id, student.name, COUNT(CASE WHEN score.number < 60 THEN score.number ELSE NULL END) AS fail_num FROM student LEFT OUTER JOIN score ON (student.id = score.student_id) GROUP BY student.id HAVING COUNT(CASE WHEN (score.number < 60) THEN score.number ELSE NULL END) > 2 ORDER BY NULL

_ 15.查询每门课的选课人数;_

 # 15.查询每门课的选课人数;
 courses15=Course.objects.annotate(sum=Count('score__id')).values('name','sum')
 for course in courses15:
  print(course)

SELECT student.id, student.name, COUNT(CASE WHEN score.number < 60 THEN score.number ELSE NULL END) AS fail_num FROM student LEFT OUTER JOIN score ON (student.id = score.student_id) GROUP BY student.id HAVING COUNT(CASE WHEN (score.number < 60) THEN score.number ELSE NULL END) > 2 ORDER BY NULL', 'time': '0.000'}, {'sql': 'SELECT course.name, COUNT(score.id) AS sum FROM course LEFT OUTER JOIN score ON (course.id = score.course_id) GROUP BY course.id ORDER BY NULL

django表单

表单:
HTML中的表单:
单纯从前端的 html 来说,表单是用来提交数据给服务器的,不管后台的服务器用的是 Django 还是 PHP 语言还是其他语言。只要把 input 标签放在 form 标签中,然后再添加一个提交按钮,那么以后点击提交按钮,就可以将 input 标签中对应的值提交给服务器了。
Django中的表单:
Django 中的表单丰富了传统的 HTML 语言中的表单。在 Django 中的表单,主要做以下两件事:

  1. 渲染表单模板。
  2. 表单验证数据是否合法。
    Django中表单使用流程:
    在讲解 Django 表单的具体每部分的细节之前。我们首先先来看下整体的使用流程。这里以一个做一个留言板为例。首先我们在后台服务器定义一个表单类,继承自 django.forms.Form 。示例代码
    如下:
# forms.py
class MessageBoardForm(forms.Form):
title = forms.CharField(max_length=3,label='标题',min_length=2,error_messages={"min
_length":'标题字符段不符合要求!'})
content = forms.CharField(widget=forms.Textarea,label='内容')
email = forms.EmailField(label='邮箱')
reply = forms.BooleanField(required=False,label='回复')

然后在视图中,根据是 GET 还是 POST 请求来做相应的操作。如果是 GET 请求,那么返回一个空的表单,如果是 POST 请求,那么将提交上来的数据进行校验。示例代码如下:

# views.py
class IndexView(View):
def get(self,request):
form = MessageBoardForm()
return render(request,'index.html',{'form':form})
def post(self,request):
form = MessageBoardForm(request.POST)
if form.is_valid():
title = form.cleaned_data.get('title')
content = form.cleaned_data.get('content')
email = form.cleaned_data.get('email')
reply = form.cleaned_data.get('reply')
return HttpResponse('success')
else:
print(form.errors)
return HttpResponse('fail')

在使用 GET 请求的时候,我们传了一个 form 给模板,那么以后模板就可以使用 form 来生成一个表单的 html 代码。在使用 POST 请求的时候,我们根据前端上传上来的数据,构建一个新的表单,这个表单是用来验证数据是否合法的,如果数据都验证通过了,那么我们可以通过 cleaned_data 来获取相应的数据。在模板中渲染表单的 HTML 代码如下:

<form action="" method="post">
<table>
<tr>
<td></td>
<td><input type="submit" value="提交"></td>
</tr>
</table>
</form>

我们在最外面给了一个 form 标签,然后在里面使用了 table 标签来进行美化,在使用 form 对象渲染的时候,使用的是 table 的方式,当然还可以使用 ul 的方式( as_ul ),也可以使用 p 标签的方式( as_p ),并且在后面我们还加上了一个提交按钮。这样就可以生成一个表单了

*在记不清一些具体方法时可以先type到所记得的类型,然后通过引入+ ctrl b查看包含具体方法来查看
比如想用form.erros.get_json_data()只记得在form.errors下,
先type(form.erros)得到类型class ‘django.forms.utils.ErrorDict
然后引入:from django.forms.utils.import ErrorDict 再进入详细查看寻找需要的那个方法
forms文件中各条目可以通过添加字段error_message={'a':'b'}a是语法中定义的各类错误,b是自定义的错误提示。可以在开发中方便用户。eg.title=forms.CharField(max_length=100,min_length=2,label='标题',error_message={min_length:"最少不能少于一字符})

用表单验证数据是否合法

request.POST.get():接收字符串类型
form.cleaned_data.get():对应类型
常用的Field:
使用 Field 可以是对数据验证的第一步。你期望这个提交上来的数据是什么类型,那么就使用什么类型的 Field 。

CharField:
用来接收文本。
参数:
max_length:这个字段值的最大长度。
min_length:这个字段值的最小长度。
required:这个字段是否是必须的。默认是必须的。
error_messages:在某个条件验证失败的时候,给出错误信息。

EmailField:
用来接收邮件,会自动验证邮件是否合法。
错误信息的key: required 、 invalid 。
FloatField:用来接收浮点类型,并且如果验证通过后,会将这个字段的值转换为浮点类型。
参数:
max_value:最大的值。
min_value:最小的值。
错误信息的key: required 、 invalid 、 max_value 、 min_value 。
IntegerField:用来接收整形,并且验证通过后,会将这个字段的值转换为整形。
参数:
max_value:最大的值。
min_value:最小的值。
错误信息的key: required 、 invalid 、 max_value 、 min_value 。

URLField:
用来接收 url 格式的字符串。
错误信息的key: required 、 invalid 。

常用验证器

常用验证器:
在验证某个字段的时候,可以传递一个 validators 参数用来指定验证器,进一步对数据进行过滤。验证器有很多,但是很多验证器我们其实已经通过这个 Field 或者一些参数就可以指定了。比如 EmailValidator ,我们可以通过 EmailField 来指定,比如 MaxValueValidator ,我们可以通过 max_value 参数来指定。以下是一些常用的验证器:

  1. MaxValueValidator :验证最大值。
  2. MinValueValidator :验证最小值。
  3. MinLengthValidator :验证最小长度。
  4. MaxLengthValidator :验证最大长度。
  5. EmailValidator :验证是否是邮箱格式。
  6. URLValidator :验证是否是 URL 格式。
  7. RegexValidator :如果还需要更加复杂的验证,那么我们可以通过正则表达式的验证器: RegexValidator 。比如现在要验证手机号码是否合格,那么我们可以通过以下代码实现:
class MyForm(forms.Form):
telephone = forms.CharField(validators=[validators.RegexValidator("1[345678]\d
{9}",message='请输入正确格式的手机号码!')])

自定义验证:
有时候对一个字段验证,不是一个长度,一个正则表达式能够写清楚的,还需要一些其他复杂的逻辑,那么我们可以对某个字段,进行自定义的验证。比如在注册的表单验证中,我们想要验证手机号码是否已经被注册过了,那么这时候就需要在数据库中进行判断才知道。对某个字段进行自定义的验证方式是,定义一个方法,这个方法的名字定义规则是: clean_fieldname 。如果验证失败,那么就抛出一个验证错误。比如要验证用户表中手机号码之前是否在数据库中存在,那么可以通过以下代码实现:

class MyForm(forms.Form):
    telephone = forms.CharField(validators=[validators.RegexValidator("1[345678]\d{9}",
    message='请输入正确格式的手机号码!')])
    def clean_telephone(self):
        telephone = self.cleaned_data.get('telephone')
        exists = User.objects.filter(telephone=telephone).exists()
        if exists:
            raise forms.ValidationError("手机号码已经存在!")
            return telephone

以上是对某个字段进行验证,如果验证数据的时候,需要针对多个字段进行验证,那么可以重写 clean 方法。比如要在注册的时候,要判断提交的两个密码是否相等。那么可以使用以下代码来完成:

class MyForm(forms.Form):
telephone = forms.CharField(validators=[validators.RegexValidator("1[345678]\d{9}",
message='请输入正确格式的手机号码!')])
pwd1 = forms.CharField(max_length=12)
pwd2 = forms.CharField(max_length=12)
def clean(self):
cleaned_data = super().clean()
pwd1 = cleaned_data.get('pwd1')
pwd2 = cleaned_data.get('pwd2')
if pwd1 != pwd2:
raise forms.ValidationError('两个密码不一致!')

温馨提示:

*下划线的使用这里很多都是双下划线,看着很不明显。

pip补充

当pip什么无法成功pip的时候可以从以下网址查找下载然后直接 pip install 路径+包名
https://www.lfd.uci.edu/~gohlke/pythonlibs/

快捷键

tab 是补全快捷键
ctrl+/ 是注释快捷键(pycharm的不是python的)
ctrl+shift+r:不使用缓存去重新加载一个页面
ctrl+鼠标左键:看函数(属性)源代码
ctrl+w选中一个单词,再按再选中一个……
ctrl-折叠代码
ctrl+展开代码
pycharm快捷键不可用时,可以file->settings->vim emulation->将失效的快捷键由vim改选ide即可

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349