django从传说到入门

1. 介绍

我们从开发环境的安装,到创建项目,编写前后端代码开始了解一下django大概是什么样子的。
看一下django的文档其实就知道,django很强大,但是同时细节也真的非常多,同时相同的功能可能也有不同的实现。我们本着入门了解的态度,先看一下它大致是什么样子的。

2. 安装虚拟环境

python使用virtualenv来管理虚拟环境,它可以给当前的python程序一个隔离的依赖环境,在这个环境中安装依赖,使用依赖就都不依赖全局了,可以很好的解决多个项目依赖不同的情况。
使用命令pip install virtualenv安装。

virtualenv安装

安装后,我们创建目录trydjango,然后cd到目录中运行 virtualenv -p python3 . 来在当前目录下面创建一个依赖目前系统中的python3版本的虚拟环境。具体使用哪个版本的python就看主机上具体的安装情况了。

命令执行后,会创建如下内容:


环境创建好后,使用命令source bin/activate就可以进入虚拟环境中。这时所有的依赖和python版本就都是当前虚拟环境中的了,跟系统就隔离开了。


如图,前面出现了 (trydjango) 就说明已经进入了虚拟环境,如果需要离开环境,使用 deactivate命令即可。

3. 安装Django

在虚拟环境中直接运行 pip install Django即可(其他方式可以参考文档)
安装之后,需要重新运行一次source bin/activate,才能使用django安装时安装的一些命令,比如django-admin。

4. 初始化项目

创建一个文件夹叫src,然后cd进入src,再执行命令django-admin startproject firstapp . 通过这个命令来初始化一个项目,初始化完成后,会出现manage.py文件和firstapp文件夹。

5. 运行服务

这时我们就可以通过执行命令python manage.py runserver来启动django服务了


这里面红色的警告是因为没有初始化数据库造成的。

服务启动后,访问启动服务时给出的服务地址http://127.0.0.1:8000/,出现如下界面就说明服务启动成功了。

6. 初始化数据库

django需要连接数据库才能正常进行使用,我们通过如下命令来初始化数据库。
python manage.py migrate

image.png

7. 新建自己的app

app是什么我们后面再说,现在先放一下。

执行命令python manage.py startapp products来创建一个名叫products的app,
命令执行后,会创建一个products的文件夹:

创建完app之后,要记得在settings.py里面注册我们的app,这个app才能被使用,我们修改里面对应的代码,增加products的app声明

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'products',  # 我们新增的app,前面都是默认创建的app
]

7.1 创建模型

a. 修改模型文件,创建一个Product模型
修改models.py, 代码如下:

from django.db import models

# Create your models here.
class Product(models.Model):
    title = models.TextField()
    description = models.TextField()
    price = models.TextField()

b. 创建移植脚本
每次修改模型文件之后,都要先运行python manage.py makemigrations 来生成移植脚本,这个脚本就是用来把我们对模型做出的改变,同步到数据库用的。

然后再运行python manage.py migration命令来移植变化,这样数据库结构就会同步成我们新的模型的样子。

7.2 尝试修改模型

修改models.py, 增加一个summary字段,代码如下:

from django.db import models

# Create your models here.
class Product(models.Model):
    title = models.TextField()
    description = models.TextField()
    price = models.TextField()
    summary = models.TextField()

再次运行python manage.py makemigrations, 会自动生成更新文件


然后我们再次运行一下 python manage.py migration就可以把变化同步到数据库。

7.3 操作模型

Django默认提供了一些app给我们使用,其中admin就是一个。通过admin我们可以对模型、用户进行一些基本的管理,我们可以把我们的Product模型注册到admin中,然后通过admin来对它进行管理。
a. 在admin里面注册模型
修改admin.py,注册刚才创建的Product模型

from django.contrib import admin
from .models import Product

# Register your models here.
admin.site.register(Product)

b. 通过网页服务操作模型
前面我们已经启动了服务。我们访问http://127.0.0.1:8000/admin/ ,会进入一个登陆界面:


这时我们是没有用户的,我们需要到命令行去创建一个系统的用户出来。
执行命令 python manage.py createsuperuser 来创建超级用户(系统第一个用户),按照步骤创建即可:

警告是因为密码太短造成的
然后我们回到登录界面,使用刚才创建的用户名密码登录界面,就可以看到我们新增的模型了:

通过界面就可以进行对模型的各项操作。

c. 通过命令行操作模型
除了通过界面来操作模型之外,我们也可以通过在命令行打开一个shell来进入python的执行环境,然后直接操作模型。
在命令行运行命令python manage.py shell可以打开一个shell,我们在shell里面就可以引入模型,然后操作模型,如下:


基本就是引入Product之后,通过Product.objects上面的方法来进行各种操作。
在几个新增操作后,我们也可以回到上面提到的网页服务来查看对象列表:

7.4 修改模型

模型定义好之后,有可能还需要进行修改,我们尝试把模型修改如下:

from django.db import models

# Create your models here.
class Product(models.Model):
    title = models.CharField(max_length=64)  # CharField必须填写max_length属性
    description = models.TextField()
    price = models.DecimalField(decimal_places=2, max_digits=10000)  # DecimalField必须有这两个属性,否则报错
    summary = models.TextField(blank=True, null=False) # blank控制界面上是否允许为空的检查,如果是True,就不检查空值,  null控制如果数据是空时,应该传什么值给数据库

修改好后还是执行 python manage.py makemigrations , python manage.py migrate命令来使改动生效。
这时如果刷新列表界面或者使用命令行Product.objects.all()查看数据可能会报错,那是因为我们修改了字段类型,原有的数据类型不对,导致出错。这时我们在命令行执行Product.objects.all().delete()删除旧数据即可。

8. 界面

8.1 创建一个新的app

创建一个新的app并不是必要的操作,我们可以在上面的products里面继续创建界面,只是为了方便起见,我们再建一个app来做下面的操作。
使用命令创建pages app python manage.py startapp pages。创建之后记得要在settings.py中注册新的app:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'products',
    'pages',
]

8.2 编写界面文件

下面我们先修改一下网站的首页,修改首页需要如下几个步骤
a. 编写界面文件pages/views.py

from django.shortcuts import render
from django.http import HttpResponse # 引入HttpResponse来返回请求

# Create your views here.
def home_view(request, *args, **kwargs):
    print(request.user) # request里面可以查看用户会话信息
    return HttpResponse("<h1>this is the homepage</h1>") # 返回一段html

b. 配置路由信息
路由信息通过查看settings.py里面的ROOT_URLCONF配置得知使用的配置文件是 firstapp.urls


也就是这个文件:

我们在这个文件中配置我们新写的首页内容,内容如下:

from django.contrib import admin
from django.urls import path
from pages.views import  home_view

urlpatterns = [
    path('', home_view),
    path('admin/', admin.site.urls),
]

在这个文件里,我们导入了新写的界面文件,然后注册了路由,这时访问服务即可看到效果:



同理,如果需要增加其他页面,一样操作即可。

8.3 编写templates而不是在python中直接返回字符串

在views.py中,可以通过render方法来渲染一个模板来返回请求

from django.shortcuts import render
from django.http import HttpResponse # 引入HttpResponse来返回请求

# Create your views here.
def home_view(request, *args, **kwargs):
    print(request.user)
    # return HttpResponse("<h1>this is the homepage</h1>") # 返回一段html
    return render(request, 'home.html', {})

为了让home.html正确渲染,我们需要做两件事,一个是创建home.html,另外一个是把它注册到Django中。
a. 创建home.html


内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <h1>home page</h1>
    <p>当前登录的用户是: {{request.user}}</p>
</body>
</html>

我们创建templates目录,然后在其中新建home.html文件,在html文件中,我们获取了一下当前登录的用户。

b. 注册模板文件
模板文件在settings.py中进行注册,配置DIRS进行注册模板目录,代码如下:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],  # 这里就是存放template的目录
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

现在我们刷新界面,就可以看到我们的模板正确被渲染了:


8.4 模板相关的一些用法

8.4.1 模板继承

对于一个有很多页面的应用来说,经常会遇到结构固定,但是内容各异的情况,这种时候通过模板继承的方式,我们就可以很好的来复用定制好的结构,在每一个详细的页面中,可以只关心需要替换掉的部分。
a. 我们在templates目录中新建base.html文件,内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <h1>base.html中的内容1</h1>

    {% block content %}
        <h1>默认内容</h1>
    {% endblock %}

    <h1>base.html中的内容2</h1>
    {% block bottom %}
        <h1>默认bottom</h1>
    {% endblock %}

    <h1>base.html中的内容3</h1>
    {% block other %}
        <h1>默认other信息</h1>
    {% endblock %}
</body>
</html>

其中

{% block content %}
        <h1>默认内容</h1>
{% endblock %}

类似结构部分是在模板中定义的可替换区域,block和endblock定义了一个内容的替换区域, content是区域的名称(用于多个block存在的情况下,指定继承模板时继承的是一块),block和endblock中间的内容就是没有替换内容的情况下,默认显示的内容。

我们修改home.html为如下内容:

{% extends 'base.html' %}

{% block content %}
<p>当前登录的用户是: {{request.user}}</p>
{% endblock content%}

<p>外部的信息</p>

{% block bottom %}
<p>底部块的信息</p>
{% endblock bottom %}

在这个文件中,我们通过 {% extends 'base.html' %}指定了我们要继承的模板,然后下面两个block都通过名称指定了要覆盖的内容,中间的 <p>外部的信息</p>是出现在继承了其他模板的模板中的独立代码,这一部分是不会被执行的,我们继承模板的时候,只能去覆盖block部分的内容。

最终界面如下显示:


8.4.2 导入其他模板内容

还有一种复用模板的情况是,我们把一些写好的模板整个导入到我们的模板中,这样可以把一些界面元素通过合理的拆分,变成单一的文件,自己管自己的业务,易于管理。
我们在templates目录下新建components目录,然后在components目录新建navbar.html

<nav>
    <ul>
        <li>首页</li>
        <li>联系人</li>
        <li>关于</li>
    </ul>
</nav>

然后修改base.html,在其中引入navbar.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    
    {% include 'components/navbar.html' %}

    <h1>base.html中的内容1</h1>

    {% block content %}
        <h1>默认内容</h1>
    {% endblock %}

    <h1>base.html中的内容2</h1>
    {% block bottom %}
        <h1>默认bottom</h1>
    {% endblock %}

    <h1>base.html中的内容3</h1>
    {% block other %}
        <h1>默认other信息</h1>
    {% endblock %}
</body>
</html>

最终效果如下:


navbar引入的效果

8.4.3 给模板传入上下文参数

views.py 中的 render方法中可以传入第三个参数,这个参数是一个dictionary,每一个dictionary的key都会变成模板中的一个变量供模板使用。
新建一个about.html,然后在views.py中渲染这个模板, 并且在urls.py中注册这个view,最终查看效果。
a. views.py中新增一个about_view

from django.shortcuts import render
from django.http import HttpResponse # 引入HttpResponse来返回请求

# Create your views here.
def home_view(request, *args, **kwargs):
    print(request.user)
    # return HttpResponse("<h1>this is the homepage</h1>") # 返回一段html
    return render(request, 'home.html', {})


def about_view(request, *args, **kwargs):
    context = {
        'name': 'hushi',
        'age' : 33
    }
    return render(request, 'about.html', context)

b. 在urls里面注册这个view

from django.contrib import admin
from django.urls import path
from pages.views import  home_view, about_view

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', home_view),
    path('about/', about_view)
]

c. 编写about.html

{% extends 'base.html' %}

{% block content %}
<p>当前登录的用户是: {{request.user}}</p>
{% endblock content%}


{% block bottom %}
<p>my name is {{name}}, and I'm {{age}} years old</p>
{% endblock bottom %}

这里我们通过 {{ }}来使用上下文中传入的参数,最终看到的效果是:

参数传入上下文

8.4.4 for循环

我们修改about.html,在其中加入for循环的代码:

{% extends 'base.html' %}

{% block content %}
<p>当前登录的用户是: {{request.user}}</p>
{% endblock content%}


{% block bottom %}
<p>my name is {{name}}, and I'm {{age}} years old</p>

<p>my friends are</p>
<ul>
{% for friend in friends %}
<li>{{forloop.counter}} - {{friend}}</li>
{% endfor %}
</ul>

{% endblock bottom %}

这一段代码就是for循环的主要语法:

{% for friend in friends %}
<li>{{forloop.counter}} - {{friend}}</li>
{% endfor %}

其中forloop.counter是循环的下标。

我们在views.py中传入参数查看一下效果:

def about_view(request, *args, **kwargs):
    context = {
        'name': 'hushi',
        'age' : 33,
        'friends' : [
            'Jim',
            'Alex',
            'Joe'
        ]
    }
    return render(request, 'about.html', context)
image.png

8.4.5 条件

条件判断比较好理解,我们这里就给出基本的写法

{% if some_condition %}
do something
{% elif other_condition %}
do sth
{% else %}
blah blah blah
{% endif %}

8.4.6 模板中的tag与filter

a. tag:
在模板中,我们已经使用了if、include、extends、block等等tag,这些概念在模板中就叫做tag,django默认提供了很多tag,可以参考文档:https://docs.djangoproject.com/en/3.0/ref/templates/builtins/#
b. filter:
filter是一些数据操作的方法,我们把数据输入进去,然后再输出一些数据,用法比较简单
{{ value| filter1 | filter2| ... }}
类似linux的管道运算符,把前面的值传给第一个filter,然后再把结果传给第二个filter...
具体可以参考文档:https://docs.djangoproject.com/en/3.0/ref/templates/builtins/#built-in-filter-reference

8.5 从后端获取数据并展示

上面我们已经了解了如何给模板传递上下文参数,那么如何从后端获取数据并显示其实就非常简单了。具体的思路就是在views.py对应的视图方法中,通过models.py中定义的模型上的方法来获取数据库中的数据,然后通过上下文参数传递给模板
我们以Product的展示为例:
a. 在products的app目录中的views.py中新增展示Product详情的方法:

from django.shortcuts import render
from .models import Product
# Create your views here.
def product_detail_view(request, *args, **kwargs):
    # context参数只能接受dictionary对象
    return render(request, 'products/detail.html', {
        'object': Product.objects.get(id='5')
    })

(第三个参数把id=5的Product对象封装到了一个dictionary对象中,这是因为传给模板的这个参数只接受dictionary类型的对象)

b. 在urls.py里面注册这个新的view

from django.contrib import admin
from django.urls import path
from pages.views import  home_view, about_view
from products.views import product_detail_view

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', home_view),
    path('about/', about_view),
    path('product', product_detail_view)
]

c. 编写detail.html
在templates里面新建products目录,然后新建detail.html

{% extends '../base.html' %}

{% block content %}
<h1>{{ object.title }}</h1>
<p>price is {{ object.price }}</p>
{% endblock content %}

这里我们还是保持之前的模板继承写法。最终效果如下:


普通对象和dictionary对象的转换
上面我们为了把Product对象转换成dictionary对象,而封装了一层,这就导致我们需要在模板里面每次引用属性的时候都要增加一层object.的开头,所以最好的还是能有一种方法能快速的把Product对象直接转换成dictionary对象。 还真有:
我们重写一下products/views.py 以及 templates/products/detail.html

from django.shortcuts import render
from .models import Product
# Create your views here.
def product_detail_view(request, *args, **kwargs):
    # context参数只能接受dictionary对象
    # return render(request, 'products/detail.html', {
    #     'object': Product.objects.get(id='5')
    # })
    return render(request, 'products/detail.html', Product.objects.get(id='5').__dict__)

直接使用Product对象的__dict__属性,这样模板里面就可以直接使用属性名称了:

{% extends '../base.html' %}

{% block content %}
<h1>{{ title }}</h1>
<p>price is {{ price }}</p>
{% endblock content %}

8.6 关于app


我们在前面创建过pages和products两个app,通过文件结构其实也很容易看出来,一个app其实就是一个可以被复用的完整的模块,这个模块完整的包含模型,界面,测试等等。
但是目前我们的app有一部分在内部,有一部分还在外部,其实更好的做法是做一定的解耦。
a. 界面的解耦
目前我们把模板文件都写到了外面的templates文件夹中,实际上我们可以把属于app的模板都写在app自己文件夹下面对应的templates目录里面,因为我们在settings.py里面我们做了APP_DIRS: True的配置:

那么如果两个位置都存在相同名称的模板,哪个会生效呢?其实我们思考一下就应该知道,app是模块,模块是被组合使用的,而外面的templates(也就是我们在settings.py的TEMPLATES里面配置的DIRS)是整个project里面的代码,所以它的优先级会高,会覆盖app里面的模板。

我们故意把路径拼错来看一下报错信息,里面已经写清楚了Django寻找模板的路径和顺序:


b. 路由的解耦
界面解耦之后,其实就剩下urls.py里面有对app结构的耦合了,但是我们查看一下admin的路由配置就不难发现,实际上也是有解耦办法的



通过这种方式,app可以只暴露一个信息,通过这一个路由信息,来跟project组合起来,而不是需要暴露app里面的方法、路径等等具体的实现。

8.7 表单

我们在views.py里面新增一个表单的view,如果是get请求,就返回一个空的表单,如果是表单被post请求提交,那么我们就检查表单内容是否有效,如果有效就把表单保存到数据库,然后再返回一个新的空表单对象给界面。

a. 在products目录里面新建forms.py, 创建输入三个属性的一个form

from django.forms import ModelForm, Textarea
from .models import Product

class ProductForm(ModelForm):

    class Meta:  
        # 指定我们的form是针对Product模型的
        model = Product
        # 指定
        fields = [
            'title',
            'description',
            'price'
        ]
        # 覆盖默认的控件
        widgets = {
            'description': Textarea(attrs={'cols': 80, 'rows': 20}),
        }

b. 在products/views.py里面增加product_create_view用来新建Product

from django.shortcuts import render
from .models import Product
from .forms import ProductForm
# Create your views here.
def product_detail_view(request, *args, **kwargs):
    # context参数只能接受dictionary对象
    # return render(request, 'products/detail.html', {
    #     'object': Product.objects.get(id='5')
    # })
    return render(request, 'products/detail.html', Product.objects.get(id='5').__dict__)


def product_create_view(request, *args, **kwargs):
    form = ProductForm(request.POST or None)  # 使用request.POST对象或者None来初始化Form里面的内容, 如果需要编辑一个数据库对象,那么需要传入第二个参数 instance=要编辑的对象
    if form.is_valid():  # 如果表单通过了校验
        form.save()      # 保存表单内容到数据库
        form = ProductForm()  #给界面重新初始化一个空的form对象,以重新填写下一个表单

    context = {
        'form' : form
    }
    return render(request, 'products/create.html', context)

c. 创建form的界面模板,在templates/products/里面创建create.html

{% extends '../base.html' %}

{% block content %}
<form method="post"> 
    {% csrf_token %} {% comment %} 因为使用了post去提交表单,所以需要提供csrf_token {% endcomment %}

    {{ form.as_p }}  {% comment %} form.as_p 会把form里面的每一个字段用<p>标签封装起来 {% endcomment %}
    <input type="submit" value="Save">
</form>
{% endblock content %}

d. 在urls里面增加product_create_view的路由:

from django.contrib import admin
from django.urls import path
from pages.views import  home_view, about_view
from products.views import product_detail_view, product_create_view

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', home_view),
    path('about/', about_view),
    path('product', product_detail_view),
    path('create', product_create_view)
]

e. 访问地址,可以新建一个product对象


image.png

f.点击save后,我们到admin的后台去确认一下是否增加成功:


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

推荐阅读更多精彩内容

  • AHA,上周五抽空写的稿子,今天被采用了,挺高兴的…… ​​​
    陌尘低语阅读 171评论 0 2
  • 1.Xcode导航快捷键 工程导航器: Command + 1 显示/隐藏导航器面板: Command + 0 显...
    大冲哥阅读 320评论 0 1
  • 胃对一个人很重要,平时要想吃香睡得好,那你胃就得好。我国不止在各方面,都是大国,在胃病这方面也是一个大国,每年死于...
    澜沧江漂流阅读 330评论 0 1
  • 初看被红姐和小溅(秋水)的日常互怼逗逼作劲吸引。而后,有为剧中怀旧气息感慨,一如剧中张楚的歌。 赵英男和小溅确实是...
    懋三阅读 543评论 1 1
  • 今天晚上很开心,其实没有什么喜事,但心情很平静,对于我来说,平静就算是一种幸运了。我给他发消息啦,虽然出人意料,但...
    4023阅读 140评论 0 0