1. Django form类描述html表单,帮助或简化操作
- 接受和处理用户提交的数据
可检查提交的数据,可将数据转换成python的数据类型 - 可自动生成html代码
新建project/forms.py
from django import forms 导入django.formsfrom django.contrib import authfrom django.contrib.auth.models import User
登录login类
placeholder_account = 'please input your username'placehodler_passwd = 'please input your password'placeholder_email = 'please input your email'class LoginForm(forms.Form):username = forms.CharField(label='username',widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': placeholder_account+' or register'}),required=True) # false表示不需要填写password = forms.CharField(label='password',widget=forms.PasswordInput(attrs={'class': 'form-control', 'placeholder': placehodler_passwd}))def clean(self):# 数据清洗/对接受的数据进行校验username = self.cleaned_data['username']password = self.cleaned_data['password']user = auth.authenticate(username=username, password=password)if user is None:raise forms.ValidationError('Wrong password or account!')else:self.cleaned_data['user'] = userreturn self.cleaned_data
注册register类
class RegisterForm(forms.Form):
username = forms.CharField(label='username',
widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': placeholder_account}),
max_length=15,
min_length=2,
required=True) # false表示不需要填写
password = forms.CharField(label='password',
widget=forms.PasswordInput(attrs={'class': 'form-control', 'placeholder': placehodler_passwd}),
max_length=20,
min_length=6)
password_again = forms.CharField(label='password again',
widget=forms.PasswordInput(attrs={'class': 'form-control', 'placeholder': placehodler_passwd + ' again'}),
max_length=20,
min_length=6)
email = forms.EmailField(label='email',
widget=forms.EmailInput(attrs={'class':'form-control','placeholder':placeholder_email}))
'''
clean_xx(self)函数在xx字段传入时立刻调用。因此xx字段的建立传入和clean_xx()的顺序需一致,否则报错
'''
def clean_username(self): # 对注册类的每个字段进行检测是否合规。
username = self.cleaned_data['username']
if User.objects.filter(username=username).exists():
raise forms.ValidationError('Username already exists!')
return username
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise forms.ValidationError('Email already exists!')
return email
def clean_password_again(self):
password = self.cleaned_data['password']
password_again = self.cleaned_data['password_again']
if password != password_again:
raise forms.ValidationError('Passwords entered twice are inconsistent!')
return password_again
project/views.py/login(request)修改
def login(request):
'''
username = request.POST.get('username', '')
password = request.POST.get('password', '')
user = auth.authenticate(request, username=username, password=password)
referer = request.META.get('HTTP_REFERER', reverse('home'))
if user is not None:
auth.login(request, user)
return redirect(referer)
else:
return render(request, 'error.html', {'message': 'Wrong password or account! '})
'''
if request.method == 'POST':
login_from = LoginForm(request.POST)
if login_form.is_valid():
user = login_form.cleaned_data['user']
auth.login(request, user)
return redirect(request.GET.get('from', reverse('home'))) # 重定向
else:
login_form = LoginForm()
context = {}
context['login_form'] = login_form
return render(request,'login.html',context)
project/views.py/register(request)
def register(request):
if request.method == 'POST':
register_form = RegisterForm(request.POST)
if register_form.is_valid():
username = register_form.cleaned_data['username']
password = register_form.cleaned_data['password']
email = register_form.cleaned_data['email']
# register
user = User.objects.create_user(username, password, email)
user.save()
# sign in
# user = auth.authenticate(request, username=username, password=password) user already exists
auth.login(request, user)
return redirect(request.GET.get('from', reverse('home'))) # 重定向
‘’‘
{% url 'login' %}?from={{ request.get_full_path }}
‘’‘
else:
register_form = RegisterForm()
context = {}
context['register_form'] = register_form
# {'register_form': <RegisterForm bound=False, valid=Unknown, fields=(username;password;password_again;email)>}
return render(request,'register.html',context)
article_details.html
div class="panel panel-default">
<!-- comments enter -->
<div class="panel-heading">
<h4>Comments</h4>
</div>
<div class="panel-body">
<div class="comment-area">
{% if user.is_authenticated %}
<!-- 登录了 -->
<form action="{% url 'update_comment' %}" method="POST" style="overflow: hidden;">
{% csrf_token %}
<div class="form-group">
<label for="comment_text">Commenting publicly as {{ user.username }}</label>
<textarea id="comment_text" class="form-control" name="text" rows="4"></textarea>
</div>
<input type="hidden" name="object_id" value="{{article.id}}">
<input type="hidden" name="content_type" value="articles">
<input type="submit" value="comment" class="btn btn-primary" style="float:right">
</form>
{%else%}
<!-- 未登录 -->
<a style="color:darkblue" href="{% url 'login' %}?from={{ request.get_full_path }}">Sign in</a> to post
your comments
<span>or</span>
<a style="color:firebrick" href="{% url 'register' %}?from={{ request.get_full_path }}">Register</a>.
{%endif %}
</div>
</div>
<!-- END comments enter -->
<!-- comment list -->
<div class="comment-area">
<ul class="list-group">
{% for comment in comments %}
<li class="list-group-item">
{{ comment.user.username }}
{{ comment.comment_time|date:"Y-m-d H:i:s"}}
{{ comment.text }}
</li>
{%empty%}
<li class="list-group-item">Leave your comments!</li>
{% endfor %}
</ul>
</div>
<!-- END comment list -->
</div>
register.html
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Register</h3>
</div>
<div class="panel-body" >
<form action="" method="POST">
{% csrf_token %}
<!-- {{ register_form }} register_from已包含下面全部信息和html样式。但只能使用django.forms的内置css样式,故建议使用下面的写法进行美化-->
{% for field in register_form %}
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field }}
<p class="text-danger">{{ field.errors.as_text }}</p>
{% endfor %}
<span class="pull-left text-danger">{{ register_form.non_field_errors }}</span>
<input type="submit" value="Sign Up" class="btn btn-danger pull-right">
</form>
</div>
</div>
</div>
login.html
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Sign In</h3>
</div>
<div class="panel-body" >
<form action="" method="POST">
{% csrf_token %}
<!-- {{ login_form }} 同register -->
{% for field in login_form %}
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field }}
<p class="text-danger">{{ field.errors.as_text }}</p>
{% endfor %}
<span class="pull-left text-danger">{{ login_form.non_field_errors }}</span>
<input type="submit" value="Sign In" class="btn btn-primary pull-right">
</form>
</div>
</div>
</div>


报错
1. KeyError at /register/ ‘password_again’
froms.py中将clean_password 改成clean_password_again
django的forms.py运行顺序是执行定义字段的语句,然后查找是否存在该字段的
clean_xxx函数,如果有则执行。password_again字段创建在password,所以先执行了clean_password函数时没有password_again字段报错。
2. forms.py中的RegisterForm必须定义clean_xxx()函数才能做字段检测。其他名称不会直接调用
3. AnonymousUser object has no attribute ‘_meta’
>删除`view.py`中`register`函数中的 `user = auth.authenticate(request, username=username, password=password)` **重复创建user报错**
富文本编辑评论以及ajax动态提交评论
1 对上一节的评论进行改进
原则:能够用forms验证实现的功能尽量让forms去实现,让form来分担views的工作。
新建comment/forms.py
from django import forms
class CommentFrom(forms.Form):
content_type = forms.CharField(widget=forms.HiddenInput)
object_id = forms.IntegerField(widget=forms.HiddenInput)
text = forms.CharField(widget=forms.Textarea)
'''
widget设置渲染出来的html样式
'''
评论在details的页面中显示,s2aclab/views.py涉及到details页面的方法。
from comment.forms import CommentFrom
......
def article_details(request, art_pk):
......
article_content_type = ContentType.objects.get_for_model(article)
print(f'model={type(article_content_type.model)}') # model=<class 'str'>
# article_content_type = <class 'django.contrib.contenttypes.models.ContentType' >#
print(f'article_content_type={type(article_content_type)}')
......
context['comment_form'] = CommentFrom(initial={'content_type': article_content_type, 'object_id': art_pk})
......
article_details.html
{{comment_form}}语句可以在html中渲染成
<label for="id_text">Text:</label>
<textarea name="text" cols="40" rows="10" required="" id="id_text"></textarea>
<input type="hidden" name="content_type" value="articles" id="id_content_type">
<input type="hidden" name="object_id" value="33" id="id_object_id">
因此可以将上一节的html中评论模块改写成:
<form action="{% url 'update_comment' %}" method="POST" style="overflow: hidden;">
{% csrf_token %}
<label for="comment_text">Commenting publicly as {{ user.username }}</label>
{{comment_form}}
<input type="submit" value="comment" class="btn btn-info"style="float:right">
</form>
2. django-ckeditor 富文本表单
每个字段类型都有一个适当的默认widget类。
django-ckeditor提供widget, [参考文档](https://github.com/django-ckeditor/django-ckeditor)
修改comment/forms.py
from ckeditor.widgets import CKEditorWidget
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.db.models import ObjectDoesNotExist
from ckeditor.widgets import CKEditorWidget
class CommentFrom(forms.Form):
content_type = forms.CharField(widget=forms.HiddenInput, label=False)
object_id = forms.IntegerField(widget=forms.HiddenInput, label=False)
text = forms.CharField(widget=CKEditorWidget(config_name='comment_ckeditor'),
label=False, error_messages={'required': "Comment can't be empty! "})
'''
widget设置渲染出来的html样式
'''
def __init__(self, *arg, **kwargs):
# 得到user变量
if 'user' in kwargs:
self.user = kwargs.pop('user')
super(CommentFrom, self).__init__(*arg, **kwargs)
def clean(self):
# 判断用户是否登录
if self.user.is_authenticated:
self.cleaned_data['user'] = self.user
else:
raise forms.ValidationError('Please sign in.')
# 评论对象判断验证
content_type = self.cleaned_data['content_type']
object_id = self.cleaned_data['object_id']
try:
model_class = ContentType.objects.get(model=content_type).model_class()
model_obj = model_class.objects.get(pk=object_id)
self.cleaned_data['content_object'] = model_obj
except ObjectDoesNotExist:
raise forms.ValidationError('The comment object does not exist!')
return self.cleaned_data
settings.py中添加CKEDITOR属性
CKEDITOR_CONFIGS = {
'default': {},
'comment_ckeditor': {
'toolbar': 'custom',
'toolbar_custom': [
['Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript'],
["TextColor", "BGColor", 'RemoveFormat'],
['NumberedList', 'BulletedList'],
['Link', 'Unlink'],
["Smiley", "SpecialChar", 'Blockquote'],
],
'width': 'auto',
'height': '130',
'tabSpaces': 4,
'removePlugins': 'elementspath',
'resize_enabled': False,
'extraPlugins':'placeholder',
}
}
3 Ajax = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。
base.html中添加
...
{% block script_extends%}
{% endblock %}
</body>
</html>
article_details.html添加ajax脚本
{% block script_extends%}
<script type="text/javascript" src="{% static " ckeditor/ckeditor-init.js" %}"></script>
<script type="text/javascript" src="{% static "ckeditor/ckeditor/ckeditor.js" %}"></script>
<script type="text/javascript">
$("#comment_form").submit(function(){
// 判断是否输入为空,若空直接ban下面代码,减少空评论提交对服务器的请求
// 错误信息置空
$("#comment_error").text("");
if(CKEDITOR.instances['id_text'].document.getBody().getText().trim()==""){
// 显示错误信息
$("#comment_error").text("Comment can't be empty! ");
return false;
}
// 更新数据到textarea
CKEDITOR.instances['id_text'].updateElement();
// 异步提交 ajax
$.ajax({
url: "{% url 'update_comment' %}",
type: "POST",
data: $(this).serialize(),
cache: false,
success:function(data){
console.log(data);
if (data['status'] == 'SUCCESS'){
// 动态刷新插入数据
var comment_html = '<div>' +data['username'] + ' ' +data['comment_time'] + ' (new)' + data['text']+'</div>'
$("#comment_list").prepend(comment_html);
// 清空编辑框的内容
CKEDITOR.instances['id_text'].setData('');
}else{
// 显示错误信息
$("#comment_error").text(data['message']);
}
},
error: function(xhr){
console.log(xhr);
},
});
return false;
});
</script>
{% endblock %}
修改评论模块html 同时对css部分进行美化
<div class="panel panel-default">
<!-- comments enter -->
<div class="panel-heading">
<h4>Comments</h4>
</div>
<div class="panel-body">
<div class="comment-area">
{% if user.is_authenticated %}
<!-- 登录了 -->
<form id="comment_form" action="{% url 'update_comment' %}" method="POST" style="overflow: hidden;">
{% csrf_token %}
<label for="comment_text">Commenting publicly as {{ user.username }}</label>
{{comment_form}}
<!-- {% for field in comment_form%}
{{ field }}
{% endfor %} -->
<span id="comment_error" class="text-danger pull-left"></span>
<input type="submit" value="comment" class="btn btn-info pull-right">
</form>
{%else%}
<!-- 未登录 -->
<a style="color:darkblue" href="{% url 'login' %}?from={{ request.get_full_path }}">Sign in</a> to post
your comments
<span>or</span>
<a style="color:firebrick" href="{% url 'register' %}?from={{ request.get_full_path }}">Register</a>.
{%endif %}
</div>
</div>
<!-- END comments enter -->
<!-- comment list -->
<div class="comment-area">
<ul class="list-group">
<li id="comment_list" class="list-group-item" style="color:steelblue"></li>
{% for comment in comments %}
<li class="list-group-item">
{{ comment.user.username }}
{{ comment.comment_time|date:"Y-m-d H:i:s"}}
{{ comment.text|safe }}
</li>
{%empty%}
<li class="list-group-item">Leave your comments!</li>
{% endfor %}
</ul>
</div>
<!-- END comment list -->
</div>
更改comment/views.py中update_comment()
JsonResponse(data)返回数据给前端ajax脚本
from time import strftime
from django.utils.timezone import localtime
from django.shortcuts import render, redirect
from django.contrib.contenttypes.models import ContentType
from django.urls import reverse
from django.http import JsonResponse
from .models import Comment
from .forms import CommentFrom
def update_comment(request):
referer = request.META.get('HTTP_REFERER', reverse('home'))
comment_form = CommentFrom(request.POST, user = request.user)
data = {}
if comment_form.is_valid():
# 检查通过,保存数据
comment = Comment()
comment.user = comment_form.cleaned_data['user']
comment.text = comment_form.cleaned_data['text']
comment.content_object = comment_form.cleaned_data['content_object']
comment.save()
data['status'] = 'SUCCESS'
data['username'] = comment.user.username
data['comment_time'] = comment.comment_time.strftime('%Y-%m-%d %H:%M:%S')
data['text'] = comment.text
else:
data['status'] = 'ERROR'
data['message'] = list(comment_form.errors.values())[0]
# return render(request, 'error.html', {'message': comment_form.errors, 'redirect_to': referer})
return JsonResponse(data)
