使用Django建立Blog的记录和总结
Django 版本为1.9以上
Django框架结构:
对django框架架构和request/response处理流程的分析
Blog搭建参考:
用Django搭建个人博客
Django 搭建简易博客教程
代码:
https://github.com/threegirl2014/blog
命令行常用命令:
-
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。这样可以在同一网络的另外机器上与之建立连接。
一些操作不需要重新启动服务器,而另一些,比如文件添加,需要手动重启。 -
django-admin startproject project_name
创建一个项目。一个项目可以包含多个应用。 -
python manage.py startapp app_name
创建一个应用。一个应用可以用于多个项目。 -
python manage.py migrate
创建数据库和表。对于Sqlite,事先不需要创建任何东西。对于MySQL或PostgreSQL,需要提前创建一个空的数据库(与Project同名)。 -
python manage.py makemigrations
如果对Models做了更改,比如增删表中的项,比如新增一个app(需要在setting.py文件中的INSTALLED_APPS表中增加app名),运行该命令可以产生对应的迁移命令,接着运行以上第4条命令,就可以将迁移命令执行。
在老版本中,并没有4和5这两条命令,数据库的迁移要复杂的多。 -
python manage.py createsuperuser
登陆project的admin后台时需要提前创建超级用户。 -
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相关
时间设置
#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>这种格式就无法正确返回。
常见的几种方法:
- filter
@register.filter(is_safe=True),设置为safe,不用自动转义。 - template
使用{% autoescape off %} ...{% endautoescape %} 即可关闭自动转义。 - 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中的什么。详情可直接搜索获得。
发送邮件需要指定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
Celery库
暂无