`

使用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。
    如果要监听所有外网IP(即Django Project与运行此Project的机器IP使用同一IP),使用0.0.0.0。这样可以在同一网络的另外机器上与之建立连接。
    一些操作不需要重新启动服务器,而另一些,比如文件添加,需要手动重启。

  2. django-admin startproject project_name
    创建一个项目。一个项目可以包含多个应用。

  3. python manage.py startapp app_name
    创建一个应用。一个应用可以用于多个项目。

  4. python manage.py migrate
    创建数据库和表。对于Sqlite,事先不需要创建任何东西。对于MySQL或PostgreSQL,需要提前创建一个空的数据库(与Project同名)。

  5. python manage.py makemigrations
    如果对Models做了更改,比如增删表中的项,比如新增一个app(需要在setting.py文件中的INSTALLED_APPS表中增加app名),运行该命令可以产生对应的迁移命令,接着运行以上第4条命令,就可以将迁移命令执行。
    在老版本中,并没有4和5这两条命令,数据库的迁移要复杂的多。

  6. python manage.py createsuperuser
    登陆project的admin后台时需要提前创建超级用户。

  7. python manage.py shell
    和普通的python shell环境相比,此命令导入了setting.py中的设置。

如果在IDE(如Eclipse)中集成了开发环境,那么点击右键弹出菜单中有与以上命令行等效的选项。

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.

由于以上区别,在Python3中,使用__str__()直接返回Model中的字段就可以了。

但是在Python2中,两者的返回类型是不同的:

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

    def __unicode__(self):
        return self.title

按规矩,在Python2中使用__unicode__()就不会出现什么问题了。
但是如果选择__str__()的话,平常情况也没啥问题,因为它本质上是override了基类django.db.models.Model中的同名函数。

不过使用中文时,又会遇到老生常谈的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,从而导致了错误。(虽然我找了大半天都没找到在哪调用的,网上也没有相关解答。。。/(ㄒoㄒ)/~~)

结论就是,在Python2中,使用__unicode__()就对了。

(20160925补充)在Django文档中也有介绍:

__str__
还是 __unicode__
?
对于Python 3来说,这很简单,只需使用__str__() 
对于Python 2来说,你应该定义__unicode__() 方法并返回unicode
值。
Django 模型具有一个默认的__str__() 方法,它会调用__unicode__() 并将结果转换为UTF-8 字节字符串。
这意味着unicode(p)
将返回一个Unicode 字符串,而str(p)
将返回一个字节字符串,其字符以UTF-8编码。
Python 的行为则相反:对象
__unicode__
方法调用__str__
方法并将结果理解为ASCII 字节字符串。
这个不同点可能会产生困惑。

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是自动添加时间,是“创建的时间”。二者设置之后,DateTimeField就变成只读模式。

若要可以自动设置为创建时间,还能够在之后进行修改,则使用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将会被认为是函数内的变量,而不是global变量:

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

转义

参考:django的转义总结:escape,autoescape,safe,mark_safe

何谓转义?就是把html语言的关键字过滤掉。例如,<div>就是html的关键字,如果要在html页面上呈现<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只能包含字母、数字、下划线或者是连字符,通常用来作为短标签。通常它们是用来放在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下的一个子路径。数据库中存储该值,而实际上的文件本体存储在该路径下。
除直接给出路径外,还可以设置成一个可调用对象如函数,这个可调用对象必须有两个参数: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参数表示关联的文件被修改时是否保存,默认True。

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

ModelForm

model表示Form和Model的关联。
widgets表示admin界面显示效果。
exclude表示不显示哪些参数,fields表示显示哪些参数。二者必须有其一。

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

最后,在Model对应的Admin类中进行form的关联。

class BlogAdmin(admin.ModelAdmin):
    form = BlogAdminForm

保存操作

model需要自定义保存,可实现如下函数:

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

同时,在model相关的admin中也有保存函数:

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

obj即为该model的实例对象,form就是上述提到的BlogAdminForm,change表示类型。

分页

分页是一个很常见的功能。可以手工实现,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列表。根据此列表,设定每页最多显示五条,生成一个Paginator对象。page是传入对象,表示在请求哪一页的blogs。根据page的输入返回结果。

markdown

markdown编辑器会将写好的markdown文本,转换为html文本,然后在浏览器上我们就可以看到非常漂亮的结果。
我使用python中的markdown库来完成编辑器的作用。Using Markdown as a Python Library
见下方的代码,mark_safe表示返回的不需要转义的字符串。
markdown.markdown(text [, **kwargs])。text为传入值,必须是Unicode类型。extensions参数是一系列扩展,fenced_code是识别代码用的;codehilite是代码高亮,具体使用哪种css需要在template中写明。safe_mode见上述转义章节。enable_attributes默认为True,在safe_mode为True时,默认为False,即将attributes的转换打开或关闭,具体什么是attributes未找到,存疑。还有其他一些参数。

templatetag

在Django的Template中可以使用过滤器来对内容进行过滤。内置过滤器参考
也可以自定义。自定义模板标签和过滤器
自定义的templatetag必须包含在某个app中,在这个app中需要建立一个templatetags目录,在目录中需要有一个__init__.py文件来使得该目录可以作为Python的包。
在template文件中使用该过滤器时,需要使用{% load xxx %}来导入,其中xxx为templatetags目录下的某个模块名字。

register是template.Library()的一个实例,所有的标签和过滤器都是在其中进行注册的。
custom_markdown(value, ...)就是自定义的过滤器函数,当然在未注册前它只是一个普通的函数。其中value表示输入的变量,后面还可以设定有其他参数。
为了能够使用它,需要在register中将其注册为过滤器。
使用装饰器方法@register.filter(),filter的意思就是过滤器。如果filter中对name参数进行了设置,那么Django就是用name值来作为过滤器的名字;如果没有,则使用函数的名字来作为过滤器。is_safe参数详见上述转义章节。由于返回的是经过markdown处理后的html,所以此处不转义。
另一个装饰器@stringfilter表示该模板过滤器只希望用一个字符串来作为第一个参数,那么在被传入过滤器函数前,将会把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

发送邮件需要指定SMTP主机和发送端口,在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是发送者邮箱地址,若赋值为None,则使用在setting.py中设置的DEFAULT_FROM_EMAIL来赋值;recipient_list是接收者的邮箱地址列表。
需要注意的是from_email设置的邮箱地址,必须和SMTP主机相匹配,否则无法使用。

以上是开发者手动发送邮件。有些情况下,Django有自动发送邮件的场景。比如在DEBUG=False时,无法直接看到错误报告,Django会为ADMINS设置中的用户发送邮件。
所以,为了发送邮件,除了上述提到的SMTP配置之外,还需要在ADMINS元组中添加接收者的相关信息(格式是(name,email)),设置SERVER_EMAIL来确定发送者的邮件地址。
如果有启用BrokenEmailLinksMiddleware,那么就需要设置MANAGERS,它的格式与ADMINS相同,用来接收死链报告。

form

在Template中使用<form action="xx" method="yy">...</form>来构建表单。
action属性指定的URL用来指出将表单数据发送到何处,若为空则是表单所在页面来处理。method指定是GET还是POST方法(只能是二者其一),通常会更改系统状态的请求需要使用POST方法。

Django 会处理表单工作中的三个显著不同的部分:
准备数据、重构数据,以便下一步提交。
为数据创建HTML 表单
接收并处理客户端提交的表单和数据

以下是一个用来发送邮件的例子,由于包含一些敏感信息,使用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类将创建一个空的表单实例然后放置到要渲染的模板的上下文中。
如果是POST请求,则使用request.POST对新创建的Form实例进行填充,这叫做“绑定数据到表单”。
然后调用is_valid()方法,它会为所有的表单数据进行验证。如果为False,那么就会将现有数据进行返回。如果为True,那么验证后的表单数据将会被放入cleaned_data属性中。

Admin Action

自定义django的admin后台action
Admin 界面上的Action

method_splitter

Django ------ 高级 view 和 URLconf 配置 额外URLconf参数技术应用到自己的工程

locals()函数

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

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics