使用Django建立Blog的記錄和總結

Django 版本為1.9以上

Django框架結構:
對django框架架構和request/response處理流程的分析

Blog搭建參考:
用Django搭建個人博客
Django 搭建簡易博客教程

代碼:
https://github.com/threegirl2014/blog

命令行常用命令:

  1. python manage.py rumserver xx.xx.xx.xx:yyyy
    運行Django Project唆迁,需要啟動服務器肆氓。默認情況下不用指定IP地址和端口號,默認為本地IP地址+8000端口號即:127.0.0.1:8000畦粮。
    也可單獨指定端口號粪牲,如8080胀屿。
    如果要監(jiān)聽所有外網(wǎng)IP(即Django Project與運行此Project的機器IP使用同一IP)帕胆,使用0.0.0.0轴捎。這樣可以在同一網(wǎng)絡的另外機器上與之建立連接召耘。
    一些操作不需要重新啟動服務器百炬,而另一些,比如文件添加污它,需要手動重啟剖踊。

  2. django-admin startproject project_name
    創(chuàng)建一個項目庶弃。一個項目可以包含多個應用。

  3. python manage.py startapp app_name
    創(chuàng)建一個應用德澈。一個應用可以用于多個項目歇攻。

  4. python manage.py migrate
    創(chuàng)建數(shù)據(jù)庫和表。對于Sqlite梆造,事先不需要創(chuàng)建任何東西缴守。對于MySQL或PostgreSQL,需要提前創(chuàng)建一個空的數(shù)據(jù)庫(與Project同名)镇辉。

  5. python manage.py makemigrations
    如果對Models做了更改屡穗,比如增刪表中的項,比如新增一個app(需要在setting.py文件中的INSTALLED_APPS表中增加app名)忽肛,運行該命令可以產(chǎn)生對應的遷移命令村砂,接著運行以上第4條命令,就可以將遷移命令執(zhí)行屹逛。
    在老版本中础废,并沒有4和5這兩條命令,數(shù)據(jù)庫的遷移要復雜的多罕模。

  6. python manage.py createsuperuser
    登陸project的admin后臺時需要提前創(chuàng)建超級用戶色迂。

  7. python manage.py shell
    和普通的python shell環(huán)境相比,此命令導入了setting.py中的設置手销。

如果在IDE(如Eclipse)中集成了開發(fā)環(huán)境,那么點擊右鍵彈出菜單中有與以上命令行等效的選項图张。

Model中的__str__()__unicode__()

稍加了解后就會知道__str__()是用于Python3锋拖,__unicode__()是用于Python2。

Python 2 had two string types: Unicode strings and non-Unicode strings.
Python 3 has one string type: Unicode strings.

由于以上區(qū)別祸轮,在Python3中兽埃,使用__str__()直接返回Model中的字段就可以了。

但是在Python2中适袜,兩者的返回類型是不同的:

    def __str__(self):
        return self.title.encode('utf-8')
    
    def __unicode__(self):
        return self.title

按規(guī)矩柄错,在Python2中使用__unicode__()就不會出現(xiàn)什么問題了。
但是如果選擇__str__()的話苦酱,平常情況也沒啥問題售貌,因為它本質上是override了基類django.db.models.Model中的同名函數(shù)。

不過使用中文時疫萤,又會遇到老生常談的encode和decode問題颂跨。
當模型中有外鍵時(如Blog模型中使用taggit.manager.TaggableManager類型作為tag外鍵時,ManytoMany),刪除模型記錄時(某一篇Blog)扯饶,就會報錯:

DjangoUnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128). You passed in <TaggedItem: [Bad Unicode data]> (<class 'taggit.models.TaggedItem'>)

錯誤報告的大意就是傳入的字符恒削,無法decode池颈。
我猜測是在程序中調用了__str__(),傳入的utf-8類型的字符程序無法正確decode钓丰,從而導致了錯誤躯砰。(雖然我找了大半天都沒找到在哪調用的,網(wǎng)上也沒有相關解答携丁。琢歇。。/(ㄒoㄒ)/~~)

結論就是则北,在Python2中矿微,使用__unicode__()就對了。

(20160925補充)在Django文檔中也有介紹:

__str__
還是 __unicode__
?

對于Python 3來說尚揣,這很簡單涌矢,只需使用__str__()

對于Python 2來說快骗,你應該定義__unicode__()
方法并返回unicode
值娜庇。
Django 模型具有一個默認的__str__()
方法,它會調用__unicode__()
并將結果轉換為UTF-8 字節(jié)字符串方篮。
這意味著unicode(p)
將返回一個Unicode 字符串名秀,而str(p)
將返回一個字節(jié)字符串,其字符以UTF-8編碼藕溅。
Python 的行為則相反:對象
__unicode__
方法調用__str__
方法并將結果理解為ASCII 字節(jié)字符串匕得。
這個不同點可能會產(chǎn)生困惑。

defaultdict相關

python defaultdict

時間設置

    #pub_date = models.DateTimeField('date published', auto_now_add=True)
    #if set auto_now=True or auto_now_add=True, the time variable is read-only.
    #default=timezone.now(), can auto set the time and also give the choice to change it
    #to support this function, we should set USE_TZ=False
    pub_date = models.DateTimeField('date published', default=timezone.now())
    last_edit_date = models.DateTimeField('last edited', auto_now=True)

auto_now=True是每次修改都會更新時間巾表,是“最后一次修改的時間”汁掠。 auto_now_add=True是自動添加時間,是“創(chuàng)建的時間”集币。二者設置之后考阱,DateTimeField就變成只讀模式。

若要可以自動設置為創(chuàng)建時間鞠苟,還能夠在之后進行修改乞榨,則使用default=timezone.now(),前提是在setting.py中設置USE_TZ=False,防止沖突報錯当娱。

報錯local variable 'xxx' referenced before assignment

categorys = Category.objects.all()
def archive(request,name=''):
    args = dict()
    args['data'] = []
    blogs = Blog.objects.exclude(title__in=exclude_blog)
    if name != '':
        categorys_filtered = categorys.filter(short_name=name)
    else:
        categorys_filtered = categorys
    for category in categorys_filtered:
        bloglist = get_sorted_bloglist(blogs,category)
        if len(bloglist) > 0:#to make sure the category have related blogs
            args['data'].append((category,bloglist))
    args['categorys'] = categorys    
    return render(request, 'css3two_blog/archive.html', args)

如果沒有新建變量categories_filtered吃既,那么就會報此錯誤。
即如下所示情況下跨细,categorys將會被認為是函數(shù)內的變量态秧,而不是global變量:

    if name != '':
        categorys = categorys.filter(short_name=name)

轉義

參考:django的轉義總結:escape,autoescape扼鞋,safe申鱼,mark_safe

何謂轉義愤诱?就是把html語言的關鍵字過濾掉。例如捐友,<div>就是html的關鍵字淫半,如果要在html頁面上呈現(xiàn)<div>,其源代碼就必須是<div> PS:轉義其實就是把HTML代碼給轉換成HTML實體了匣砖!

也就是說科吭,如果我們要返回一個HTML格式的文本,一定要將轉義開關設置為關閉猴鲫,否則類似<div>這種格式就無法正確返回对人。

常見的幾種方法:

  1. filter
    @register.filter(is_safe=True),設置為safe拂共,不用自動轉義牺弄。
  2. template
    使用{% autoescape off %} ...{% endautoescape %} 即可關閉自動轉義。
  3. mark_safe
    from django.utils.safestring import mark_safe
    return mark_safe(str)
    標記為safe宜狐,不用自動轉義势告。

MEDIA_ROOT和MEDIA_URL,以及類似的STATIC抚恒、TEMPLATE

MEDIA_ROOT表示路徑咱台,MEDIA_URL表示目錄。
需要在setting.py中設置:

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR,'media')

同時俭驮,為了能夠獲取url回溺,還需要在urls.py中設置:

urlpatterns = [
    url(r'^admin/', admin.site.urls),
]+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

表示,如果遇到了MEDIA_URL混萝,要到MEDIA_ROOT路徑下去尋找馅而。

STATIC也是同樣的道理。
STATIC_URL = '/static/'
在DEBUG=True時譬圣,Django會到Project中的各個app中尋找對應的目錄。例如在Aproject中有Bapp雄坪,那么Django會到Bapp中尋找static目錄下是否有Bapp目錄厘熟,也就是說目錄為:./Aproject/Bapp/static/Bapp/xxx。
在DEBUG=False時维哈,有另外的處理方式绳姨。

TEMPLATE則需要在setting.py中的TEMPALTES列表中的’DIR'中加上templates所在的目錄,Django會到各個app下尋找阔挠。

字段Field

普通的有CharField飘庄,DateTimeField,TextField购撼。
有點特殊的見下跪削。

models.SlugField

Slug 是一個新聞術語(通常叫做短標題)谴仙。一個slug只能包含字母、數(shù)字碾盐、下劃線或者是連字符晃跺,通常用來作為短標簽。通常它們是用來放在URL里的毫玖。

URL中不能有中文掀虎,還有一些特殊字符,如果一篇Blog需要靠title來生成URL付枫,則需要用到Slug烹玉。
用法如下,其中unidecode將一個Unicode編碼的對象音譯成一個ASCII對象(在實際中若沒有對應的ASCII碼阐滩,則需要將原始Unicode碼對應的字符進行音譯二打,然后再轉化為ASCII碼,對應關系如:“你好”和“nihao”):

from unidecode import unidecode
self.slug = slugify(unidecode(self.title))

taggit.managers.TaggableManager

用于Blog的標簽叶眉。
需要到setting.py中的INSTALLED_APPS增加'taggit’址儒。

models.FileField

如果有文件相關操作,就會用到models.FileField衅疙。models.ImageField繼承自它莲趣。

FileField.upload_to是一個路徑,它將附加到MEDIA_ROOT后面來確定url屬性的值饱溢,也就是說喧伞,它實際上是MEDIA_ROOT下的一個子路徑。數(shù)據(jù)庫中存儲該值绩郎,而實際上的文件本體存儲在該路徑下潘鲫。
除直接給出路徑外,還可以設置成一個可調用對象如函數(shù)肋杖,這個可調用對象必須有兩個參數(shù):FileField所在的模型實例溉仑,filename(帶有前向/)。
如:

def get_upload_md_name(obj,filename):
    if obj.pub_date:
        year = obj.pub_date.year
    else:
        year = datetime.now().year
    upload_to = mdfile_upload_dir % (year, obj.slug + '.markdown')
    return upload_to
class Blog(models.Model):
    md_file = models.FileField(upload_to=get_upload_md_name,blank=True)

若需要使用url屬性状植,以上述的md_file為例浊竟,則是object.md_file.url。

FieldFile.
save
(name, content, save=True)津畸,其中content應該是django.core.files.File的一個實例振定,而不是Python內建File對象。save參數(shù)表示關聯(lián)的文件被修改時是否保存肉拓,默認True后频。

self.md_file.save(self.slug + '.markdown', ContentFile(self.body.encode('utf-8')), save=False)

ModelForm

model表示Form和Model的關聯(lián)。
widgets表示admin界面顯示效果暖途。
exclude表示不顯示哪些參數(shù)卑惜,fields表示顯示哪些參數(shù)膏执。二者必須有其一。

class BlogAdminForm(forms.ModelForm):
    class Meta:
        model = Blog
        widgets = {
                   'body' : Textarea(attrs={'cols':100, 'rows':100}),
                   }
        exclude = ()

最后残揉,在Model對應的Admin類中進行form的關聯(lián)胧后。

class BlogAdmin(admin.ModelAdmin):
    form = BlogAdminForm

保存操作

model需要自定義保存,可實現(xiàn)如下函數(shù):

    def save(self, *args, **kwargs):
        do_something()
        super(Blog,self).save(*args,**kwargs)
        do_something()

同時抱环,在model相關的admin中也有保存函數(shù):

class BlogAdmin(admin.ModelAdmin):
    def save_model(self, request, obj, form, change):
        obj.save()
        print obj.slug, "save successfully"

obj即為該model的實例對象壳快,form就是上述提到的BlogAdminForm,change表示類型镇草。

分頁

分頁是一個很常見的功能眶痰。可以手工實現(xiàn)梯啤,Django也提供了更方便的方法竖伯。

from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
def home(request,page='1'):
    raw_blogs = Blog.objects.filter(status='p')
    paginator = Paginator(raw_blogs,5)
    page = int(page)
    try:
        blog_list = paginator.page(page)
    except PageNotAnInteger:
        # If page is not an integer, deliver first page.
        blog_list = paginator.page(1)
    except EmptyPage:
        # If page is out of range (e.g. 9999), deliver last page of results.
        blog_list = paginator.page(paginator.num_pages)
...
 

首先獲得原始blogs列表。根據(jù)此列表因宇,設定每頁最多顯示五條七婴,生成一個Paginator對象。page是傳入對象察滑,表示在請求哪一頁的blogs打厘。根據(jù)page的輸入返回結果。

markdown

markdown編輯器會將寫好的markdown文本贺辰,轉換為html文本户盯,然后在瀏覽器上我們就可以看到非常漂亮的結果。
我使用python中的markdown庫來完成編輯器的作用饲化。Using Markdown as a Python Library莽鸭。
見下方的代碼,mark_safe表示返回的不需要轉義的字符串吃靠。
markdown.markdown(text [, **kwargs])硫眨。text為傳入值,必須是Unicode類型巢块。extensions參數(shù)是一系列擴展礁阁,fenced_code是識別代碼用的;codehilite是代碼高亮夕冲,具體使用哪種css需要在template中寫明。safe_mode見上述轉義章節(jié)裂逐。enable_attributes默認為True歹鱼,在safe_mode為True時,默認為False卜高,即將attributes的轉換打開或關閉弥姻,具體什么是attributes未找到南片,存疑。還有其他一些參數(shù)庭敦。

templatetag

在Django的Template中可以使用過濾器來對內容進行過濾疼进。內置過濾器參考
也可以自定義。自定義模板標簽和過濾器
自定義的templatetag必須包含在某個app中秧廉,在這個app中需要建立一個templatetags目錄伞广,在目錄中需要有一個__init__.py文件來使得該目錄可以作為Python的包。
在template文件中使用該過濾器時疼电,需要使用{% load xxx %}來導入嚼锄,其中xxx為templatetags目錄下的某個模塊名字。

register是template.Library()的一個實例蔽豺,所有的標簽和過濾器都是在其中進行注冊的区丑。
custom_markdown(value, ...)就是自定義的過濾器函數(shù),當然在未注冊前它只是一個普通的函數(shù)修陡。其中value表示輸入的變量沧侥,后面還可以設定有其他參數(shù)。
為了能夠使用它魄鸦,需要在register中將其注冊為過濾器宴杀。
使用裝飾器方法@register.filter(),filter的意思就是過濾器号杏。如果filter中對name參數(shù)進行了設置婴氮,那么Django就是用name值來作為過濾器的名字;如果沒有盾致,則使用函數(shù)的名字來作為過濾器主经。is_safe參數(shù)詳見上述轉義章節(jié)。由于返回的是經(jīng)過markdown處理后的html庭惜,所以此處不轉義罩驻。
另一個裝飾器@stringfilter表示該模板過濾器只希望用一個字符串來作為第一個參數(shù),那么在被傳入過濾器函數(shù)前护赊,將會把value值轉化為字符串值惠遏。

import markdown

from django import template
from django.template.defaultfilters import stringfilter
from django.utils.safestring import mark_safe

register = template.Library()

@register.filter(is_safe=True)
@stringfilter
def custom_markdown(value):
#     print type(value)
    return mark_safe(markdown.markdown(value,
    extensions = ['markdown.extensions.fenced_code', 'markdown.extensions.codehilite'],
                                   safe_mode=True,
                                   enable_attributes=False))

RSS

RSS需要設置好返回的item的值具體是model中的什么。詳情可直接搜索獲得骏啰。

mail

發(fā)送郵件需要指定SMTP主機和發(fā)送端口节吮,在setting.py中使用EMAIL_HOST和EMAIL_PORT來給二者賦值。給EMAIL_HOST_USER和EMAIL_HOST_PASSOWRD賦值用來驗證SMTP主機判耕。如果需要使用加密鏈接透绩,則需要給EMAIL_USE_TSL或EMAIL_USE_SSL賦值。
send_mail

send_mail
(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None,connection=None, html_message=None)

subject是郵件的標題;message是郵件的正文帚豪;from_email是發(fā)送者郵箱地址碳竟,若賦值為None,則使用在setting.py中設置的DEFAULT_FROM_EMAIL來賦值狸臣;recipient_list是接收者的郵箱地址列表莹桅。
需要注意的是from_email設置的郵箱地址,必須和SMTP主機相匹配烛亦,否則無法使用诈泼。

以上是開發(fā)者手動發(fā)送郵件。有些情況下此洲,Django有自動發(fā)送郵件的場景厂汗。比如在DEBUG=False時,無法直接看到錯誤報告呜师,Django會為ADMINS設置中的用戶發(fā)送郵件娶桦。
所以,為了發(fā)送郵件汁汗,除了上述提到的SMTP配置之外衷畦,還需要在ADMINS元組中添加接收者的相關信息(格式是(name,email)),設置SERVER_EMAIL來確定發(fā)送者的郵件地址知牌。
如果有啟用BrokenEmailLinksMiddleware祈争,那么就需要設置MANAGERS,它的格式與ADMINS相同角寸,用來接收死鏈報告菩混。

form

在Template中使用<form action="xx" method="yy">...</form>來構建表單。
action屬性指定的URL用來指出將表單數(shù)據(jù)發(fā)送到何處扁藕,若為空則是表單所在頁面來處理沮峡。method指定是GET還是POST方法(只能是二者其一),通常會更改系統(tǒng)狀態(tài)的請求需要使用POST方法亿柑。

Django 會處理表單工作中的三個顯著不同的部分:
準備數(shù)據(jù)邢疙、重構數(shù)據(jù),以便下一步提交望薄。
為數(shù)據(jù)創(chuàng)建HTML 表單
接收并處理客戶端提交的表單和數(shù)據(jù)

以下是一個用來發(fā)送郵件的例子疟游,由于包含一些敏感信息,使用POST方法痕支。
forms.py如下:

from django import forms

class ContactForm(forms.Form):
    subject = forms.CharField(label='Subject:', max_length=100)
    message = forms.CharField(label = 'Message:', widget=forms.Textarea)
    email = forms.EmailField(label='E-mail:')
    name = forms.CharField(label='Name:',max_length=50,required=False)

views.py如下:

def contact(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            subject = form.cleaned_data['subject']
            sender = form.cleaned_data['email']
            message = form.cleaned_data['message']
            recipients = [settings.DEFAULT_FROM_EMAIL,sender]
            send_mail(subject=subject, message=message, recipient_list=recipients,from_email=None)
            return HttpResponseRedirect('/thanks/')
    else:
        form = ContactForm()    
    return render(request, 'css3two_blog/contact.html', {'form' : form, 'categorys' : categorys}) 

首先在forms.py中定義一個Form類颁虐。
如果訪問的視圖是GET請求,那么Form類將創(chuàng)建一個空的表單實例然后放置到要渲染的模板的上下文中卧须。
如果是POST請求另绩,則使用request.POST對新創(chuàng)建的Form實例進行填充瞬痘,這叫做“綁定數(shù)據(jù)到表單”。
然后調用is_valid()方法板熊,它會為所有的表單數(shù)據(jù)進行驗證。如果為False察绷,那么就會將現(xiàn)有數(shù)據(jù)進行返回干签。如果為True,那么驗證后的表單數(shù)據(jù)將會被放入cleaned_data屬性中拆撼。

Admin Action

自定義django的admin后臺action
Admin 界面上的Action

method_splitter

Django ------ 高級 view 和 URLconf 配置 額外URLconf參數(shù)技術應用到自己的工程

locals()函數(shù)

Django:locals()小技巧
django-using-locals

Celery庫

暫無

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末容劳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子闸度,更是在濱河造成了極大的恐慌竭贩,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件莺禁,死亡現(xiàn)場離奇詭異留量,居然都是意外死亡,警方通過查閱死者的電腦和手機哟冬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進店門楼熄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人浩峡,你說我怎么就攤上這事可岂。” “怎么了翰灾?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵缕粹,是天一觀的道長。 經(jīng)常有香客問我纸淮,道長平斩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任萎馅,我火速辦了婚禮双戳,結果婚禮上,老公的妹妹穿的比我還像新娘糜芳。我一直安慰自己飒货,他們只是感情好,可當我...
    茶點故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布峭竣。 她就那樣靜靜地躺著塘辅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪皆撩。 梳的紋絲不亂的頭發(fā)上扣墩,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天哲银,我揣著相機與錄音,去河邊找鬼呻惕。 笑死荆责,一個胖子當著我的面吹牛,可吹牛的內容都是我干的亚脆。 我是一名探鬼主播做院,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼濒持!你這毒婦竟也來了键耕?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤柑营,失蹤者是張志新(化名)和其女友劉穎屈雄,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體官套,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡酒奶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了奶赔。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讥蟆。...
    茶點故事閱讀 40,865評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖纺阔,靈堂內的尸體忽然破棺而出瘸彤,到底是詐尸還是另有隱情,我是刑警寧澤笛钝,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布质况,位于F島的核電站,受9級特大地震影響玻靡,放射性物質發(fā)生泄漏结榄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一囤捻、第九天 我趴在偏房一處隱蔽的房頂上張望臼朗。 院中可真熱鬧,春花似錦蝎土、人聲如沸视哑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽挡毅。三九已至,卻和暖如春暴构,著一層夾襖步出監(jiān)牢的瞬間跪呈,已是汗流浹背段磨。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留耗绿,地道東北人苹支。 一個月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像误阻,于是被迫代替她去往敵國和親沐序。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,870評論 2 361

推薦閱讀更多精彩內容