Django模型(数据库)及Django Query常用方法

目录:

Django模型(数据库)及Django Query常用方法

Django 模型是与数据库相关的,与数据库相关的代码一般写在 models.py 中,Django 支持 sqlite3, MySQL, PostgreSQL等数据库,只需要在settings.py中配置即可,不用更改models.py中的代码,丰富的API极大的方便了使用。
新建项目和应用

django-admin.py startproject learn_models # 新建一个项目
cd learn_models # 进入到该项目的文件夹
django-admin.py startapp people # 新建一个 people 应用(app)

补充:新建app也可以用 python manage.py startapp people, 需要指出的是,django-admin.py 是安装Django后多出的一个命令,并不是运行的当前目录下的django-admin.py(当前目录下也没有),但创建项目会生成一个 manage.py 文件。
那project和app什么关系呢?
一个项目一般包含多个应用,一个应用也可以用在多个项目中。

添加应用
将我们新建的应用(people)添加到 settings.py 中的 INSTALLED_APPS中,也就是告诉Django有这么一个应用。

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
 
    'people',
)

修改models.py
我们打开 people/models.py 文件,修改其中的代码如下:

from django.db import models
 
 
class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

我们新建了一个Person类,继承自models.Model, 一个人有姓名和年龄。
这里用到了两种Field,更多Field类型可以参考教程最后的链接。
创建数据表
我们来同步一下数据库(我们使用默认的数据库 SQLite3,无需配置)

先 cd 进入 manage.py 所在的那个文件夹下,输入下面的命令
 
# Django 1.6.x 及以下
python manage.py syncdb
 
# Django 1.7 及以上的版本需要用以下命令
python manage.py makemigrations
python manage.py migrate

我们会看到,Django生成了一系列的表,也生成了我们新建的people_person这个表,那么如何使用这个表呢?

使用 Django 提供的 QuerySet API
Django提供了丰富的API, 下面演示如何使用它。

$ python manage.py shell
 
>>> from people.models import Person
>>> Person.objects.create(name="Tom", age=24)
<Person: Person object>
>>>

我们新建了一个用户WeizhongTu 那么如何从数据库是查询到它呢?

>>> Person.objects.get(name="Tom")
<Person: Person object>
>>>

我们用了一个 .objects.get() 方法查询出来符合条件的对象,但是大家注意到了没有,查询结果中显示<Person: Person object>,这里并没有显示出与Tom的相关信息,如果用户多了就无法知道查询出来的到底是谁,查询结果是否正确,我们重新修改一下 people/models.py
name 和 age 等字段中不能有 __(双下划线,因为在Django QuerySet API中有特殊含义(用于关系,包含,不区分大小写,以什么开头或结尾,日期的大于小于,正则等)
也不能有Python中的关键字,name 是合法的,student_name 也合法,但是student__name不合法,try, class, continue 也不合法,因为它是Python的关键字( import keyword; print(keyword.kwlist) 可以打出所有的关键字)

from django.db import models
 
 
class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()
     
    def __unicode__(self):
    # 在Python3中使用 def __str__(self):
        return self.name

按 CTRL + C 退出当前的 Python shell, 重复上面的操作,再来看看效果

新建一个对象的方法有以下几种:

1. Person.objects.create(name=name,age=age)

2. p = Person(name="Tim", age=23)
p.save()

3. p = Person(name="Tony")
p.age = 23
p.save()

4. Person.objects.get_or_create(name="Rose", age=23)

这种方法是防止重复很好的方法,但是速度要相对慢些,返回一个元组,第一个为Person对象,第二个为True或False, 新建时返回的是True, 已经存在时返回False.
获取对象有以下方法:

Person.objects.all()

Person.objects.all()[:10] 切片操作,获取10个人,不支持负索引,切片可以节约内存

Person.objects.get(name=name)

get是用来获取一个对象的,如果需要获取满足条件的一些人,就要用到filter

Person.objects.filter(name="abc")  
# 等于Person.objects.filter(name__exact="abc") 名称严格等于 "abc" 的人

Person.objects.filter(name__iexact="abc") 
# 名称为 abc 但是不区分大小写,可以找到 ABC, Abc, aBC,这些都符合条件


Person.objects.filter(name__contains="abc")  # 名称中包含 "abc"的人

Person.objects.filter(name__icontains="abc")  #名称中包含 "abc",且abc不区分大小写


Person.objects.filter(name__regex="^abc")  # 正则表达式查询

Person.objects.filter(name__iregex="^abc")  # 正则表达式不区分大小写

filter是找出满足条件的,当然也有排除符合某条件的

Person.objects.exclude(name__contains="WZ")  
# 排除包含 WZ 的Person对象

Person.objects.filter(name__contains="abc").exclude(age=23)  
# 找出名称含有abc, 但是排除年龄是23岁的

从数据库中查询出来的结果一般是一个集合,这个集合叫做 QuerySet。

from django.db import models
 
 
class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()
 
    def __unicode__(self):  # __str__ on Python 3
        return self.name
 
class Author(models.Model):
    name = models.CharField(max_length=50)
    email = models.EmailField()
 
    def __unicode__(self):  # __str__ on Python 3
        return self.name
 
class Entry(models.Model):
    blog = models.ForeignKey(Blog)
    headline = models.CharField(max_length=255)
    body_text = models.TextField()
    pub_date = models.DateField()
    mod_date = models.DateField()
    authors = models.ManyToManyField(Author)
    n_comments = models.IntegerField()
    n_pingbacks = models.IntegerField()
    rating = models.IntegerField()
 
    def __unicode__(self):  # __str__ on Python 3
        return self.headline

QuerySet 创建对象的方法

>>> from blog.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
>>> b.save()
 
总之,一共有四种方法
# 方法 1
Author.objects.create(name="Tom", email="tom@163.com")
 
# 方法 2
twz = Author(name="Tom", email="tom@163.com")
twz.save()
 
# 方法 3
twz = Author()
twz.name="Tom"
twz.email="tom@163.com"
twz.save()
 
# 方法 4,首先尝试获取,不存在就创建,可以防止重复
Author.objects.get_or_create(name="Tom", email="tom@163.com")
# 返回值(object, True/False)

备注:前三种方法返回的都是对应的 object,最后一种方法返回的是一个元组,(object, True/False),创建时返回 True, 已经存在时返回 False
当有一对多,多对一,或者多对多的关系的时候,先把相关的对象查询出来

>>> from blog.models import Entry
>>> entry = Entry.objects.get(pk=1)
>>> cheese_blog = Blog.objects.get(name="Cheddar Talk")
>>> entry.blog = cheese_blog
>>> entry.save()

删除符合条件的结果
和上面类似,得到满足条件的结果,然后 delete 就可以(危险操作,正式场合操作务必谨慎),比如:

Person.objects.filter(name__contains="abc").delete() # 删除 名称中包含 "abc"的人
 
如果写成 
people = Person.objects.filter(name__contains="abc")
people.delete()
效果也是一样的,Django实际只执行一条 SQL 语句。

更新某个内容
批量更新,适用于 .all() .filter() .exclude() 等后面 (危险操作,正式场合操作务必谨慎)

Person.objects.filter(name__contains="abc").update(name='xxx') # 名称中包含 "abc"的人 都改成 xxx
Person.objects.all().delete() # 删除所有 Person 记录

单个 object 更新,适合于 .get(), get_or_create(), update_or_create() 等得到的 obj,和新建很类似。

aut = Author.objects.get(name="Sara")
aut.name="LiMing"
aut.email="Liming@163.com"
aut.save()  # 最后不要忘了保存!!!

QuerySet 是可迭代的,比如:

es = Entry.objects.all()
for e in es:
    print(e.headline)

Entry.objects.all() 或者 es 就是 QuerySet 是查询所有的 Entry 条目。
注意事项:
(1). 如果只是检查 Entry 中是否有对象,应该用 Entry.objects.all().exists()
(2). QuerySet 支持切片 Entry.objects.all()[:10] 取出10条,可以节省内存
(3). 用 len(es) 可以得到Entry的数量,但是推荐用 Entry.objects.count()来查询数量,后者用的是SQL:SELECT COUNT(*)
(4). list(es) 可以强行将 QuerySet 变成 列表
QuerySet 查询结果排序
作者按照名称排序

Author.objects.all().order_by('name')
Author.objects.all().order_by('-name') # 在 column name 前加一个负号,可以实现倒序

QuerySet 支持链式查询

Author.objects.filter(name__contains="Tom").filter(email="tom@163.com")
Author.objects.filter(name__contains="Tom").exclude(email="tom@163.com")
 
# 找出名称含有abc, 但是排除年龄是23岁的
Person.objects.filter(name__contains="abc").exclude(age=23)

QuerySet 不支持负索引

Person.objects.all()[:10] 切片操作,前10条
Person.objects.all()[-10:] 会报错!!!
 
# 1. 使用 reverse() 解决
Person.objects.all().reverse()[:2] # 最后两条
Person.objects.all().reverse()[0] # 最后一条
 
# 2. 使用 order_by,在栏目名(column name)前加一个负号
Author.objects.order_by('-id')[:20] # id最大的20条

扩展:QuerySet 重复的问题,使用 .distinct() 去重复
一般的情况下,QuerySet 中不会出来重复的,重复是很罕见的,但是当跨越多张表进行检索后,结果并到一起,可以会出来重复的值

qs1 = Pathway.objects.filter(label__name='x')
qs2 = Pathway.objects.filter(reaction__name='A + B >> C')
qs3 = Pathway.objects.filter(inputer__name='WeizhongTu')
 
# 合并到一起
qs = qs1 | qs2 | qs3
这个时候就有可能出现重复的
 
# 去重方法
qs = qs.distinct()

实例代码操作

需求一:编写登录和注册

编写注册功能,用户名不能已经存在,如果存在需要提示重新输入
注册后可以登录,成功后可以跳转到成功登录界面

文件树形图显示

代码操作:

  • register.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册页面</title>
</head>
<body>
    <form action="" method="post">
        {% csrf_token %}
        用户名:{{form.userName}}{{error}}<br>
        密  码:{{form.password}}<br>
        确认密码:{{form.repassword}}<br>{{form.non_field_errors}}<br>

    <input type="submit" value="注册">
    <a href="{% url 'login' %}">已有账号,去登录</a>

        </form>
</body>
</html>
  • login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
    <form action="" method="POST">
        {% csrf_token %}

        <a href="{% url 'register' %}">没有账号?去注册</a><br>


        用户名:{{form.userName}}<br>
        密  码:{{form.password}}<br>{{error}}<br>

        <input type="submit" value="登录">


    </form>


</body>
</html>
  • loginsuc.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录成功</title>
</head>
<body>
    登陆成功了,开不开心,意不意外。怎么还有一个网页?哈哈哈哈哈哈~<br>
        用户ID:{{ request.session.id }}<br>
        用户名:{{ request.session.username }}


</body>
</html>
  • weekForm.py
# -*- coding:utf-8 -*-
from django.forms import Form,widgets,fields,ValidationError




class register(Form):
    userName = fields.CharField(max_length=10)
    password = fields.CharField(max_length=10,widget=widgets.PasswordInput)
    repassword = fields.CharField(max_length=10,widget=widgets.PasswordInput)

    def  clean(self):
        userName = self.cleaned_data['userName']
        password = self.cleaned_data['password']
        repassword = self.cleaned_data['repassword']
        if not password == repassword:
            myerror = '两次密码不一致,请重新输入'
            raise ValidationError(myerror)

        return self.cleaned_data


class login(Form):
    userName = fields.CharField(max_length=10)
    password = fields.CharField(max_length=10,widget=widgets.PasswordInput)

  • models.py
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models

# Create your models here.
class UserModel(models.Model):
    userName = models.CharField(max_length=10)
    password = models.CharField(max_length=10)
  • views.py
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.shortcuts import render,redirect
from weekForms import weekForm
from models import UserModel
from django.http import HttpResponse
from . import models

# Create your views here.
def register(request):
    if request.method == 'GET':
        form = weekForm.register()
        return render(request,'register.html',{'form':form})
    elif request.method == 'POST':
        form = weekForm.register(request.POST)
        if form.is_valid():
            temp = models.UserModel.objects.filter(userName=form.cleaned_data['userName']).exists()

            if temp == False:
                userModel = UserModel()
                userModel.userName = form.cleaned_data['userName']
                userModel.password = form.cleaned_data['password']

                userModel.save()
                return HttpResponse('数据提交成功!您可以登录了.')
            else:
                error = '用户名已经存在!'
                return render(request,'register.html',{'form':form,'error':error})

        else:
            return render(request,'register.html',{'form':form})


def login(request):
    if request.method == 'GET':
        loginform = weekForm.login()
        return render(request,'login.html',{'form':loginform})
    elif request.method == 'POST':
        loginform = weekForm.login(request.POST)
        if loginform.is_valid():
            userName = loginform.cleaned_data['userName']
            password = loginform.cleaned_data['password']

            user = UserModel.objects.filter(userName=userName,password=password)
            if user:

                obj = UserModel.objects.get(userName = userName)
                request.session['id'] = obj.id
                request.session['username'] = obj.userName
                return render(request,'loginsuc.html')


            #如果想直接显示可以在setting中加SESSION_SERIALIZER='django.contrib.sessions.serializers.PickleSerializer'


            else:
                error = '用户名或者密码输入有误.'
                return render(request,'login.html',{'form':loginform,'error':error})
        else:
            return render(request,'login.html',{'form':loginform})
    else:
        return redirect('https://www.zhihu.com/')


  • urls.py
from django.conf.urls import url
from . import views

urlpatterns=[
    url(r'^register/$',views.register,name='register'),
    url(r'^login/$',views.login,name='login')
]

效果展示:

需求二:编写宠物管理系统

宠物系统文件树形图
  • petForm.py

# -*- coding: utf-8 -*-
from django.forms import Form,widgets,fields

class petInfo(Form):
    petName = fields.CharField(max_length=10,)
    petId = fields.CharField(max_length=4)
    gender = fields.CharField(initial = '雄性',
    widget = widgets.Select(choices=(('雄性','雄性'),('雌性','雌性'))))
    year = fields.IntegerField(max_value=25,min_value=0)
    kind = fields.CharField(max_length=10)


class PetInfo(Form):
    petName = fields.CharField(max_length=10,)
    petId = fields.CharField(max_length=4)
    gender = fields.CharField(initial = '雄性',widget = widgets.Select(choices=(('雄性','雄性'),('雌性','雌性'))))
    year = fields.IntegerField(max_value=25,min_value=0)
    kind = fields.CharField(max_length=10)

  • html
  • petlist.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>宠物管理系统</title>
</head>
<body>
    <table>
        <tr>
            <td>宠物名</td>
            <td>宠物编号</td>
            <td>宠物性别</td>
            <td>宠物年龄</td>
            <td>种类</td>

        </tr>

        {% for pet in pets %}
        <tr>
            <td>{{pet.petName}}{{disable}}</td>
            <td>{{pet.petId}}</td>
            <td>{{pet.gender}}</td>
            <td>{{pet.year}}</td>
            <td>{{pet.kind}}</td>

            <td><a href="">删除</a></td>
            <td><a href="{% url 'editpet' %}?id={{ pet.id }}">修改</a></td>

        </tr>
        {% empty %}

        没有宠物信息

        {% endfor %}
    </table>

    <a href="{% url 'addPet' %}">添加新宠物</a>


</body>
</html>
  • html
  • addpet.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>添加宠物信息</title>
</head>
<body>

    <form action="" method="POST">
            {% csrf_token %}
             <input type="hidden" name="id" value="{{ id }}">
            宠物名:{{form.petName}}<br>
            宠物编号:{{form.petId}}<br>
            宠物性别:{{form.gender}}<br>
            年 龄:{{form.year}}<br>
            种类:{{form.kind}}<br>

            <input type="submit" value="增加宠物">


</body>
</html>
  • html
  • editpet.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>修改宠物信息</title>
</head>
<body>
        <form action="" method="POST">
            {% csrf_token %}
             <input type="hidden" name="id" value="{{ id }}">
            宠物名:{{pet.petName}}<br>
            宠物编号:{{pet.petId}}<br>
            宠物性别:{{pet.gender}}<br>
            年 龄:{{pet.year}}<br>
            种类:{{pet.kind}}<br>

            <input type="submit" value="修改">


        </form>



</body>
</html>
  • models.py
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models

# Create your models here.
class pet(models.Model):
    petName = models.CharField(max_length=10)
    petId = models.CharField(max_length=4)
    gender = models.CharField(max_length=1)
    year = models.IntegerField()
    kind = models.CharField(max_length=10)
  • views.py
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.shortcuts import render
from models import pet
from django.http import HttpResponse
from petForms import petForm
from . import models


from django.core.urlresolvers import reverse
from django.shortcuts import redirect

# Create your views here.
def petlist(request):
    pets = models.pet.objects.all().filter(isDelete=True)
    return render(request,'petlist.html',{'pets':pets})

def addPet(request):
    if request.method == 'GET':
        petform = petForm.PetInfo()
        return render(request,'addpet.html',{'form':petform})
    elif request.method == 'POST':
        petform = petForm.PetInfo(request.POST)
        if petform.is_valid():
            data = petform.cleaned_data
            newPet = pet()
            newPet.petName = data['petName']
            newPet.petId = data['petId']
            newPet.gender = data['gender']
            newPet.year = data['year']
            newPet.kind = data['kind']
            newPet.save()
            return redirect(reverse('petlist'))
        else:
            return render(request,'addpet.html',{'form':petform})


def editPet(request):
    if request.method == 'GET':
        id = request.GET.get('id',0)
        pets = models.pet.objects.get(pk=int(id))
        petform = petForm.petInfo(
            initial={
                'petName':pets.petName,
                'petId':pets.petId,
                'gender':pets.gender,
                'year':pets.year,
                'kind':pets.kind,
            }             #给需要修改的宠物添加初始属性
        )
        return render(request,'editpet.html',{'pet':petform,'id':id})

    elif request.method == 'POST':
        petform = petForm.petInfo(request.POST)
        id = request.POST['id']
        if petform.is_valid():
            data = petform.cleaned_data
            oldPet = models.pet.objects.get(pk=int(id))
            # oldPet.petName = models.Pet.objects.get(pk=int(id))['petName']
            oldPet.petName = data['petName']
            oldPet.petId = data['petId']
            oldPet.gender = data['gender']
            oldPet.year = data['year']
            oldPet.kind = data['kind']
            oldPet.save()
            return redirect(reverse('petlist'))  #重定向
        else:
            pet = models.pet.objects.get(pk=int(id))
            return render(request,'editpet.html',{'pet':petform,'id':id})


def petsin(request):
    Pets = pet.objects.all()
    id = request.GET['id']
    new = models.pet.objects.get(pk=int(id))
    values = {
        'petName':new.petName,
        'petId':new.petId,
        'gender':new.gender,
        'year':new.year,
        'kind':new.kind,

    }
    return render(request,'pet.html',{'values':values,})

def delpet(request):
    if request.method == 'GET':
        id = request.GET.get('id',0)
        pets = models.pet.objects.get(pk=int(id))
        pets.isDelete=0
        pets.save()
        pets = models.pet.objects.all().filter(isDelete = True)
        return render(request, 'petlist.html', {'pets': pets})
  • urls.py
from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'addPet/$',views.addPet,name='addPet'),
    url(r'petlist/$',views.petlist,name='petlist'),
    url(r'editpet/$',views.editPet,name='editpet'),
]

效果展示:

功能完善中,未完待续...

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

推荐阅读更多精彩内容