Django高级(七):多文件上传

uploadMulti一次上传多个文件/图片

0.写在前面:

In this tutorial I will guide you through the steps to implement an AJAX multiple file upload with Django using jQuery. For this tutorial we will be using a specific plug-in called jQuery File Upload, which takes care of the server communication using AJAX and also the compatibility with different browsers.

Now you will see that the jQuery File Upload comes with several script files, they all have a purpose and you will only need some of them for certain features.

基本settings.py配置

# /root/suyn_website/uploadFiles/uploadFiles/settings.py
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 
#/root/suyn_website/uploadFiles

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'UploadMulti', #
]
注:基本配置和上篇‘uploadOne一次上传一个文件’类似

应用UploadMulti,文件夹树:

root@ubuntu:~/suyn_website/uploadFiles# tree
.
├── LICENSE
├── manage.py
├── media
│   └── photos
├── README.md
├── static
│   ├── css
│   │   ├── bootstrap.min.css
│   │   └── bootstrap.min.css.map
│   ├── fonts
│   │   ├── glyphicons-halflings-regular.eot
│   │   ├── glyphicons-halflings-regular.svg
│   │   ├── glyphicons-halflings-regular.ttf
│   │   ├── glyphicons-halflings-regular.woff
│   │   └── glyphicons-halflings-regular.woff2
│   └── js
│       ├── bootstrap.min.js
│       ├── jquery-3.2.0.min.js
│       └── jquery-file-upload
│           ├── jquery.fileupload.js
│           ├── jquery.iframe-transport.js
│           └── vendor
│               └── jquery.ui.widget.js
├── staticRoot
├── templates
│   ├── base.html
│   ├── home.html
│   └── includes
│       └── header.html
├── uploadFiles
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   ├── wsgi.py
└── UploadMulti
    ├── admin.py
    ├── apps.py
    ├── forms.py
    ├── __init__.py
    ├── migrations
    │   ├── 0001_initial.py
    │   ├── 0001_initial.pyc
    │   ├── __init__.py
    │   └── __init__.pyc
    ├── models.py
    ├── static
    │   └── UploadMulti
    │       └── js
    │           ├── basic-upload.js
    │           ├── drag-and-drop-upload.js
    │           └── progress-bar-upload.js
    ├── templates
    │   └── UploadMulti
    │       ├── base.html
    │       ├── basic_upload
    │       │   └── index.html
    │       ├── drag_and_drop_upload
    │       │   └── index.html
    │       └── progress_bar_upload
    │           └── index.html
    ├── tests.py
    ├── urls.py
    └── views.py

1.home主页只是简单的介绍

主页模板继承:
uploadFiles/templates/home.html继承自uploadFiles/templates/base.html的'block content'

127.0.0.0:8000
##### uploadFiles/uploadFiles/urls.py 
from django.conf.urls import url,include
from django.contrib import admin
from django.conf.urls.static import static
from uploadFiles import settings
from django.views.generic import TemplateView

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    #url(r'^UploadOne/',include('UploadOne.urls')),
    url(r'^$', TemplateView.as_view(template_name='home.html'), name='home'),  #这部分重点看home.html主页
    url(r'^UploadMulti/',include('UploadMulti.urls',namespace='UploadMulti')),
]

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)


##### uploadFiles/templates/home.html
{% extends 'base.html' %}  ##

{% block content %}
    <h1 class="page-header">Multiple File Upload Example</h1>
    <p class="lead">See the example live clicking in the Photos menu.</p>

    <hr>
    <h2>Static Assets</h2>
    <p>Following the list of static assets used in this example</p>
    <ul>
        <li><a href="https://github.com/twbs/bootstrap/releases/tag/v3.3.7" target="_blank">Bootstrap 3.3.7</a></li>
        <li><a href="https://github.com/jquery/jquery/releases/tag/3.1.1" target="_blank">jQuery 3.1.1</a></li>
        <li><a href="https://github.com/blueimp/jQuery-File-Upload/releases/tag/v9.14.1" target="_blank">jQuery File Upload 9.14.1</a></li>
    </ul>
{% endblock %}

##### uploadFiles/templates/base.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{% block title %}Photos Library - Simple is Better Than Complex{% endblock %}</title>
    <link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet">
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
    <style type="text/css">
      .page-header {
        margin-top: 0;
      }
    </style>
</head>
<body>
    {% include 'includes/header.html' %}  ##
    <div class="container">      
      {% block content %}
      {% endblock %}
    </div>

{### Bootstrap and jQuery in the base template, 
## and the jQuery File Upload plug-in will be added using the {% block javascript %} #}
    <script src="{% static 'js/jquery-3.2.0.min.js' %}"></script>  ##引入支持的Jquery脚本
    <script src="{% static 'js/bootstrap.min.js' %}"></script>  ##引入支持的bootstrap脚本
    {% block javascript %}
    {% endblock %}
</body>
</html>

##### uploadFiles/templates/includes/header.html
<nav class="navbar navbar-default">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#main-menu" aria-expanded="false">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="{% url 'home' %}">Photos Library</a>  ##
        </div>
        <div class="collapse navbar-collapse" id="main-menu">
            <ul class="nav navbar-nav">
                <li{% if '/UploadMulti/' in request.path %} class="active"{% endif %}>
                    <a href="{% url 'UploadMulti:basic_upload' %}">Photos</a>  ##Photos指向三种链接页面,默认basic_upload
                </li>
                <li><a href="https://simpleisbetterthancomplex.com">simpleisbetterthancomplex.com</a></li>
            </ul>
        </div>
    </div>
</nav>

2.Photos涉及有三个链接,针对三种多文件上传方式。

  • Basic Upload
  • Progress Bar Upload
  • Drag and Drop Upload

三种方式模板继承:

各自的uploadFiles/UploadMulti/templates/UploadMulti/basic_upload/index.html继承自uploadFiles/UploadMulti/templates/UploadMulti/base.html'block photocontent''bock title'('Photos/xxx'指定三种方式)。

上面的base.html的'Example'包含三种方式的链接请求,分别指向三种视图函数,该base.html再继承自uploadFiles/templates/base.html(包含includes/header.html)。

关键的一点是,三种方式的Index.html文件都包含与服务器通信的js脚本文件,是继承自uploadFiles/templates/base.html的'block javascript'

一些公共代码及模型表单创建:

##### uploadFiles/UploadMulti/urls.py
# 关于'Class-Based Views vs. Function-Based Views'可参看其他文章
from django.conf.urls import url

from . import views

app_name = 'UploadMulti'

urlpatterns = [
    url(r'^clear/$', views.clear_database, name='clear_database'),
    url(r'^basic-upload/$', views.BasicUploadView.as_view(), name='basic_upload'),
    url(r'^progress-bar-upload/$', views.ProgressBarUploadView.as_view(), name='progress_bar_upload'),
    url(r'^drag-and-drop-upload/$', views.DragAndDropUploadView.as_view(), name='drag_and_drop_upload'),
]

##### uploadFiles/UploadMulti/models.py
class Photo(models.Model):
    title = models.CharField(max_length=255, blank=True)
    file = models.FileField(upload_to='photos/')  ##FileField 文件图片都可以上传
    uploaded_at = models.DateTimeField(auto_now_add=True)

##### uploadFiles/UploadMulti/forms.py
from django import forms
from .models import Photo

class PhotoForm(forms.ModelForm):
    class Meta:
        model = Photo
        fields = ('file', )

2.0. 写在前面:关于'clear_database'

##### views.py
def clear_database(request):
    for photo in Photo.objects.all():
        photo.file.delete()
        photo.delete()
    return redirect(request.POST.get('next'))


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

{% block content %}
{# #### 这里的表单实现对上传文件/图片的清空,同时清空本地 #}
<form method="post" action="{% url 'UploadMulti:clear_database' %}">
    {% csrf_token %}
    <input type="hidden" name="next" value="{{ request.path }}">
    <button type="submit" class="btn btn-danger pull-right">
        <span class="glyphicon glyphicon-trash"></span> Clear Database
    </button>
</form>

<h1 class="page-header">
    Photos
    <small>/ {% block title %}{% endblock %}</small>   ###
</h1>

<div class="row">
    <div class="col-md-3">
        <div class="panel panel-default">
            <div class="panel-heading"><h3 class="panel-title">Examples</h3></div>
            <div class="list-group">
                <a href="{% url 'UploadMulti:basic_upload' %}" class="list-group-item{% if request.path == '/UploadMulti/basic-upload/' %} active{% endif %}">
                    Basic Upload
                </a><br>
                <a href="{% url 'UploadMulti:progress_bar_upload' %}" class="list-group-item{% if request.path == '/UploadMulti/progress-bar-upload/' %} active{% endif %}">
                    Progress Bar Upload
                </a><br>
                <a href="{% url 'UploadMulti:drag_and_drop_upload' %}" class="list-group-item{% if request.path == '/UploadMulti/drag-and-drop-upload/' %} active{% endif %}">
                    Drag and Drop Upload
                </a><br>
            </div>
        </div>
    </div>
    <div class="col-md-9">
        {% block photos_content %}  ###
        {% endblock %}
    </div>
</div>
{% endblock %}


2.1. Basic Upload

http://127.0.0.1:8000/UploadMulti/basic-upload/

We are using a basic Class-Based View, defining two request processing for the GET and POST methods. For the POST, it is where the upload handling happens, using Django’s Model Forms. When we call form.save(), Django will create a Photo instance and save the file in the file system.

2.1.1.介绍下用到的jquery-file-upload插件功能:

{% block javascript %}  ##from templates/base.html用到了jquery-file-upload插件
    {# JQUERY FILE UPLOAD SCRIPTS #}
    <script src="{% static 'js/jquery-file-upload/vendor/jquery.ui.widget.js' %}"></script>
    <script src="{% static 'js/jquery-file-upload/jquery.iframe-transport.js' %}"></script>
    <script src="{% static 'js/jquery-file-upload/jquery.fileupload.js' %}"></script>

    {# PHOTOS PAGE SCRIPTS #}
    <script src="{% static 'UploadMulti/js/basic-upload.js' %}"></script>
{% endblock %}
  • jquery.ui.widget.js It’s a dependency for the plug-in
  • jquery.iframe-transport.js The Iframe Transport is required for browsers without support for XHR file uploads
  • jquery.fileupload.js The basic File Upload plug-in
    And finally the script basic-upload.js is where we will implement our photo upload.

2.1.2.介绍下index.html页面上几个代码块:

{% block photos_content %}  ##from UploadMulti/base.html
<div style="margin-bottom: 20px;">
{# 1. BUTTON TO TRIGGER THE ACTION #}
    <button type="button" class="btn btn-primary js-upload-photos">
        <span class="glyphicon glyphicon-cloud-upload"></span> Upload photos
    </button>

{# 2. FILE INPUT TO BE USED BY THE PLUG-IN #} 
    <input id="fileupload" type="file" name="file" multiple
           style="display: none;"
           data-url="{% url 'UploadMulti:basic_upload' %}"
           data-form-data='{"csrfmiddlewaretoken": "{{ csrf_token }}"}'>
</div>

{# 3. TABLE TO DISPLAY THE UPLOADED PHOTOS #}
<table id="gallery" class="table table-bordered">
    <thead>
        <tr>
            <th>Photo</th>
        </tr>
    </thead>
    <tbody>
        {% for photo in photos %}
            <tr>
                <td><a href="{{ photo.file.url }}">{{ photo.file.name }}</a></td>
            </tr>
        {% endfor %}
    </tbody>
</table>
{% endblock %}
  • block 1 of the snippet, is the button to start the workflow. We will hook into the css class .js-upload-photos to open the file explorer window.
  • Block 2 is the most important part of the page. It’s the the file input that will be used to load the jQuery File Upload component. A few things to note:

The name of the input must match with the *Model Form Field(FileField). 也就是,name属性必须和‘file = models.FileField(upload_to='photos/')’一致。
The multiple attribute will enable multiple file selection in the file explorer window.
The data-url attribute must point to the route/view where the file form will be processed.
The data-form-data attribute should be defined exactly this way! This line is important so to instruct the plug-in to send the file along with the csrf middleware token.

  • block 3 is just a regular table displaying the photos.
##### uploadFiles/UploadMulti/views.py
from django.shortcuts import render,redirect
from django.http import JsonResponse
from django.views import View
import time
from .forms import PhotoForm
from .models import Photo
# Create your views here.

class BasicUploadView(View):
    def get(self, request):
        photos_list = Photo.objects.all()
        return render(self.request, 'UploadMulti/basic_upload/index.html', {'photos': photos_list})  ##渲染index.html,传递文件/图像对象

    def post(self, request):
        form = PhotoForm(self.request.POST, self.request.FILES)
        if form.is_valid():
            photo = form.save()   ##保存到本地
            data = {'is_valid': True, 'name': photo.file.name, 'url': photo.file.url}
        else:
            data = {'is_valid': False}
        return JsonResponse(data)  ###JSON格式的data,传递给js



##### uploadFiles/UploadMulti/templates/UploadMulti/basic_upload/index.html
{% extends 'UploadMulti/base.html' %}  ##

{% load static %}

{% block title %}Basic Upload{% endblock %}  ##from UploadMulti/base.html

##(公共代码:各自选择自己的upload js实现)
{% block javascript %}  ##from templates/base.html用到了jquery-file-upload插件
    {# JQUERY FILE UPLOAD SCRIPTS #}
    <script src="{% static 'js/jquery-file-upload/vendor/jquery.ui.widget.js' %}"></script>
    <script src="{% static 'js/jquery-file-upload/jquery.iframe-transport.js' %}"></script>
    <script src="{% static 'js/jquery-file-upload/jquery.fileupload.js' %}"></script>

    {# PHOTOS PAGE SCRIPTS #}
    <script src="{% static 'UploadMulti/js/basic-upload.js' %}"></script>
{% endblock %}

{% block photos_content %}  ##from UploadMulti/base.html
<div style="margin-bottom: 20px;">
    <button type="button" class="btn btn-primary js-upload-photos">
        <span class="glyphicon glyphicon-cloud-upload"></span> Upload photos
    </button>

    {#### id="fileupload"会在js函数中进一步处理,FILE INPUT TO BE USED BY THE PLUG-IN #}
    <input id="fileupload" type="file" name="file" multiple
           style="display: none;"
           data-url="{% url 'UploadMulti:basic_upload' %}"
           data-form-data='{"csrfmiddlewaretoken": "{{ csrf_token }}"}'>
</div>

## 罗列出已经上传的文件/图片(公共代码)
<table id="gallery" class="table table-bordered">
    <thead>
        <tr>
            <th>Photo</th>
        </tr>
    </thead>
    <tbody>
        {% for photo in photos %}
            <tr>
                <td><a href="{{ photo.file.url }}">{{ photo.file.name }}</a></td>
            </tr>
        {% endfor %}
    </tbody>
</table>
{% endblock %}




##### uploadFiles/UploadMulti/static/UploadMulti/js/basic-upload.js
$(function () {
/* 1. OPEN THE FILE EXPLORER WINDOW */
    $(".js-upload-photos").click(function () {
        $("#fileupload").click();
    });

  /* 2. INITIALIZE THE FILE UPLOAD COMPONENT */
    $("#fileupload").fileupload({  ####fileupload
        dataType: 'json',
        done: function (e, data) {  /* 3. PROCESS THE RESPONSE FROM THE SERVER  这里的data来自于JsonResponse传来的data*/
              if (data.result.is_valid) {
                  $("#gallery tbody").prepend(
                      "<tr><td><a href='" + data.result.url + "'>" + data.result.name + "</a></td></tr>"
                  )
            }
        }
    });
});

2.1.3. 介绍data在post方法和js脚本函数中的传递:

    def post(self, request):
        form = PhotoForm(self.request.POST, self.request.FILES)
        if form.is_valid():
            photo = form.save()   ##保存到本地
            data = {'is_valid': True, 'name': photo.file.name, 'url': photo.file.url}
        else:
            data = {'is_valid': False}
        return JsonResponse(data)  ###JSON格式的data,传递给js

  /* 2. INITIALIZE THE FILE UPLOAD COMPONENT */
    $("#fileupload").fileupload({  ####fileupload
        dataType: 'json',
        done: function (e, data) {  /* 3. PROCESS THE RESPONSE FROM THE SERVER  这里的data来自于JsonResponse传来的data*/
              if (data.result.is_valid) {
                  $("#gallery tbody").prepend(
                      "<tr><td><a href='" + data.result.url + "'>" + data.result.name + "</a></td></tr>"
                  )
            }
        }
    });
});

This JsonResponse(post method) will end up in the data parameter,
passed to the anonymous function hooked to the done event of the File Upload component.

See what we are doing here? When we are accessing data.result.name, we are accessing the name we returned in the JsonResponse.
So, let’s say, if we returned:
return JsonResponse({'message': 'Success'})

We would be able to catch it inside the done function, this way:

done: function (e, data) {
  if (data.result.message === 'Success') {
    // do something...
  }
}

2.2. Progress Bar Upload

http://127.0.0.1:8000/UploadMulti/progress-bar-upload/
##### views.py
一样



##### index.html
{# ####这里是页面差别,显示上传的进度条,It’s a Bootstrap modal #}
<div class="modal fade" id="modal-progress" data-backdrop="static" data-keyboard="false">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h4 class="modal-title">Uploading...</h4>
            </div>
            <div class="modal-body">
                <div class="progress">
                    <div class="progress-bar" role="progressbar" style="width: 0%;">0%</div>
                </div>
            </div>
        </div>
    </div>
</div>
{% endblock %}

##### progress-bar-upload.js
$(function () {
    $(".js-upload-photos").click(function () {
        $("#fileupload").click();
    });

    $("#fileupload").fileupload({
        dataType: 'json',
        sequentialUploads: true,  /* 1. SEND THE FILES ONE BY ONE 这个属性指示该组件一次发送一个文件*/

        start: function (e) {   /* 2. WHEN THE UPLOADING PROCESS STARTS, SHOW THE MODAL */
            $("#modal-progress").modal("show");
        },

        stop: function (e) {  /* 3. WHEN THE UPLOADING PROCESS FINALIZE, HIDE THE MODAL */
            $("#modal-progress").modal("hide");
        },

        progressall: function (e, data) {  #/* 4. UPDATE THE PROGRESS BAR */
            var progress = parseInt(data.loaded / data.total * 100, 10);  ##
            var strProgress = progress + "%";
            $(".progress-bar").css({"width": strProgress});
            $(".progress-bar").text(strProgress);
        },

        done: function (e, data) {
            if (data.result.is_valid) {
                $("#gallery tbody").prepend(
                    "<tr><td><a href='" + data.result.url + "'>" + data.result.name + "</a></td></tr>"
                )
            }
        }
    });
});
注:What we are doing here basically is :
showing the loading modal when the upload starts, 
closing it when it finalizes, 
meanwhile we update the percentage in the progress bar.

2.3. Drag and Drop Upload

http://127.0.0.1:8000/UploadMulti/drag-and-drop-upload/
##### views.py
一样




##### index.html

{# ### 这里是差异,显示拖拽位置部分,不在是‘Upload photos’按钮 #}
<div class="well text-muted text-center" style="padding-top: 4rem; padding-bottom: 4rem;">
    <span class="glyphicon glyphicon-arrow-down" style="font-size: 4rem;"></span>
    <h3>Drop Photos Here to Upload</h3>
</div>



##### drag-and-drop-upload.js
一样


提交到github

$ cd /root/suyn_website
# 因为Git是分布式版本控制系统,所以每个机器都必须自报家门:你的名字和Email地址。
$ git config --global user.name "Your Name"
$ git config --global user.email "email@example.com"

$ git init  #把当前目录变成Git可以管理的仓库,生成的.git目录(Git的版本库),是Git来跟踪管理版本库的。

$ git add uploadFiles/  #把xx添加到仓库

## 跳到uploadFiles目录里执行以下命令:
$ git commit -m "first commit to my github"  #把xx提交到仓库

## Before below:在GitHub创建同名空仓库
$ git remote add origin git@github.com:userName/uploadFiles.git

# 把当前分支master(默认)推送到远程,远程库的名字就是origin(默认)
$ git push -u origin master

注:源码请参考GitHub-uploadFiles

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

推荐阅读更多精彩内容