- 数据库的创建
from django.db import models from blog import settings # Create your models here. from django.contrib.auth.models import AbstractUser class UserInfo(AbstractUser): ''' 用户信息表 继承AbstractUser 可以直接使用相当于扩充django默认的user可以使用django内置的功能 需要导入 from django.contrib.auth.models import AbstractUser 并且需要在setting中添加设置 AUTH_USER_MODEL='app01.UserInfo' 告诉django你自定义的用户表app名字.表名 ''' nid=models.AutoField(primary_key=True) #主键 phone=models.CharField(verbose_name='手机号',max_length=11,null=True,unique=True) #手机号unique唯一 avatar=models.FileField(verbose_name='头像',upload_to=settings.UPLOAD_TO,default=settings.DEFAULT_AVATAR)#头像 create_time=models.DateTimeField(verbose_name='创建时间',auto_now_add=True) blog=models.OneToOneField(to='Blog',to_field='nid',null=True,on_delete=models.SET_NULL)#关联博客表,一对一 class Blog(models.Model): ''' 博客信息表 ''' nid=models.AutoField(primary_key=True) title=models.CharField(verbose_name='个人博客标题',max_length=64) site_name=models.CharField(verbose_name='站点名称',max_length=64) theme=models.CharField(verbose_name='博客主题',max_length=32,default='default.css') def __str__(self): return self.title class Category(models.Model): ''' 博主个人文章分类表 ''' nid=models.AutoField(primary_key=True) title=models.CharField(verbose_name='分类标题',max_length=32) blog=models.ForeignKey(verbose_name='所属博客',to='blog',to_field='nid',on_delete=models.CASCADE) class Tag(models.Model): ''' 标签表 ''' nid=models.AutoField(primary_key=True) title=models.CharField(verbose_name='标签名称',max_length=32) blog=models.ForeignKey(verbose_name='所属博客',to='Blog',to_field='nid',on_delete=models.CASCADE) class Article(models.Model): ''' 文章表 ''' nid=models.AutoField(primary_key=True) title=models.CharField(max_length=50,verbose_name='文章标题') desc=models.CharField(max_length=255,verbose_name='文章描述') create_time=models.DateTimeField(verbose_name='创建时间',auto_now_add=True) content = models.TextField(verbose_name='文章内容', ) comment_count=models.IntegerField(verbose_name='评论数',default=0) up_count=models.IntegerField(verbose_name='点赞数',default=0) down_count=models.IntegerField(verbose_name='被踩数',default=0) user=models.ForeignKey(verbose_name='作者',to='UserInfo',to_field='nid',on_delete=models.CASCADE) category =models.ForeignKey(to='Category',to_field='nid',null=True,verbose_name='文章分类',on_delete=models.SET_NULL) tags=models.ManyToManyField( verbose_name='文章关联标签', to='Tag', through='Article2Tag', through_fields=('article','tag'), ) def __str__(self): return self.title class Article2Tag(models.Model): nid=models.AutoField(primary_key=True) article=models.ForeignKey(verbose_name='文章',to='Article',to_field='nid',on_delete=models.CASCADE) tag=models.ForeignKey(verbose_name='标签',to='Tag',to_field='nid',on_delete=models.CASCADE) class Meta: unique_together=[ ('article','tag'), ] class ArticleUpDown(models.Model): ''' 点赞表 ''' nid=models.AutoField(primary_key=True) user=models.ForeignKey('UserInfo',null=True,on_delete=models.CASCADE) article=models.ForeignKey('Article',null=True,on_delete=models.CASCADE) is_up=models.BooleanField(default=True) class Meta: unique_together=[ ('article','user'), ] class Comment(models.Model): ''' 评论表 ''' nid=models.AutoField(primary_key=True) article=models.ForeignKey(verbose_name='评论文章',to='Article',to_field='nid',on_delete=models.CASCADE) user=models.ForeignKey(verbose_name='评论者',to='UserInfo',to_field='nid',on_delete=models.CASCADE) content=models.CharField(verbose_name='评论内容',max_length=255) create_time=models.DateTimeField(verbose_name='创建时间',auto_now_add=True) parent_comment=models.ForeignKey('self',null=True,on_delete=models.CASCADE)
- 如果配置了了ImageField 或者FileField,就需要配置存放文件的文件夹,类似static
#setting的配置 MEDIA_URL='/media/' #配置静态文件存放 用户上传的文件 MEDIA_ROOT=os.path.join(BASE_DIR,'media') # url的配置 from django.urls import path,re_path from django.views.static import serve from Game import settings urlpatterns = [ # 配置meadia re_path(r'^media/(?P<path>.*)$',serve,{'document_root':settings.MEDIA_ROOT}), ]
- setting的配置
""" Django settings for blog project. Generated by 'django-admin startproject' using Django 2.0.7. For more information on this file, see https://docs.djangoproject.com/en/2.0/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/2.0/ref/settings/ """ import os # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) DEFAULT_AVATAR='head_img/default_img/default_avatar.jpg' UPLOAD_TO='head_img/user_head/' # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'j4q-6*)a&=j0!q#lo8ld2u!%j2(w2t%bse-6vkpz0-si=)8%14' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = ['*'] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01.apps.App01Config', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'blog.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')] , 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'blog.wsgi.application' # Database # https://docs.djangoproject.com/en/2.0/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'blog', 'USER':'root', 'PASSWORD':'####', 'HOST':'##', 'PORT':62910, } } # Password validation # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/2.0/topics/i18n/ LANGUAGE_CODE = 'en-us' #当前时区 TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_L10N = True USE_TZ = False # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.0/howto/static-files/ STATIC_URL = '/static/' #配置静态文件存放 JS,CSS,IMG等 STATICFILES_DIRS=( os.path.join(BASE_DIR,'static'), ) MEDIA_URL='/media/' #配置静态文件存放 用户上传的文件 MEDIA_ROOT=os.path.join(BASE_DIR,'media') AUTH_USER_MODEL='app01.UserInfo' SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认) SESSION_COOKIE_PATH ="/" # Session的cookie保存的路径(默认) SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名(默认) SESSION_COOKIE_SECURE = False # 是否Https传输cookie(默认) SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输(默认) SESSION_COOKIE_AGE = 86400 # Session的cookie失效日期(2周)(默认) SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期(默认) SESSION_SAVE_EVERY_REQUEST = True # 是否每次请求都保存Session,默认修改之后才保存(默认) #配置登录页面 LOGIN_URL='/Account/login/' #配置邮箱服务器 # 邮箱类型 EMAIL_HOST='smtp.exmail.qq.com' # 如果是163 该成smtp.163.com # 邮箱端口号 EMAIL_PORT=465 # 用户名 EMAIL_HOST_USER='' # 授权码 EMAIL_HOST_PASSWORD='' # 设置默认用户 # DEFAULT_FROM_EMAIL=EMAIL_HOST_USER # 是否启用SSL EMAIL_USE_SSL=True ''' Django邮箱使用方法 from django.core.mail import send_mail from blog import setting send_mail('邮件标题','邮件内容','发送邮件的用户','[接收邮件的用户1,接收邮件的用户2....]') '''
- 基于PIL动态生成数字+字母验证码
from PIL import Image, ImageDraw, ImageFont from io import BytesIO import random def get_random_color(): # 使用random模块 随机生成RGB颜色 return (random.randint(0, 255),random.randint(0, 255),random.randint(0, 255)) def new_img_code(request): # 使用PIL 模块中的 Image生成一个 颜色模式:RGB 大小:270*40 背景色:随机生成的图片 img = Image.new('RGB', (270, 40), color=get_random_color()) # 使用PIL模块中的 ImageDraw 往图片中写入文字或者其他 draw = ImageDraw.Draw(img) # 如果写入文字,可以在这里设置文字的字体和大小 fonts_ShangWei = ImageFont.truetype('static/fonts/HYShangWeiShouShuW.ttf', size=30) # 创建一个变量用来存储验证码 verification_code = '' # 我们打算生成五个随机字符 for i in range(5): # 使用random模块随机生成0-9 的数字 number = str(random.randint(0, 9)) # 利用chr内置函数生成 A-Z 大写字母 chr函数可以将ASCII值转换成对应字符 super_char = chr(random.randint(65, 90)) # 利用chr内置函数生成 a-z 小写字母 chr函数可以将ASCII值转换成对应字符 low_char = chr(random.randint(97, 122)) # 使用random模块的choice功能在 生成的三个字符中随机选取一个 choice_char = random.choice([number, super_char, low_char]) verification_code += choice_char # 将选取到的字符写入图片,参数((x,y),字体颜色,字体样式) draw.text((i * 50 + 20, 5), choice_char, get_random_color(), font=fonts_ShangWei) # 将验证码保存到session中 request.session['verification_code'] = verification_code ''' request.session['verification_code']=verification_code 执行的过程 1.生成随机字符串 2.在客户浏览器生成一个键值对,保存生成的随机字符串,生成的key可以在setting中自己设置 3.将session保存到数据库中(表名:django-session)。 存储方式 session-key session-data 随机字符串 {'verification_code':验证码} ''' # 生成噪点,噪线 # 设定图片的宽高,用来定位噪线的位置 # draw.line():直线的绘制,第一个参数指定的是直线的端点坐标,形式为(x0, y0, x1, y1),第二个参数指定直线的颜色; # # draw.rectangle():矩形绘制,第一个参数指定矩形的对角线顶点(左上和右下),形式为(x0, y0, x1, y1),第二个指定填充颜色,第三个参数指定边界颜色; # # draw.arc():(椭)圆弧的绘制,第一个参数指定弧所在椭圆的外切矩形,第二、三两个参数分别是弧的起始和终止角度, 第四个参数是填充颜色,第五个参数是线条颜色; # # draw.chord():弦的绘制,和弧类似,只是将弧的起始和终止点通过直线连接起来; # # draw.pieslice():圆饼图的绘制,和弧与弦类似,只是分别将起始和终止点与所在(椭)圆中心相连; # # draw.ellipse():椭圆的绘制,第一个参数指定椭圆的外切矩形, 第二、三两个参数分别指定填充颜色和线条颜色,当外切矩形是正方形时,椭圆即为圆; # # draw.polygon():绘制多边形,第一个参数为多边形的端点,形式为(x0, y0, x1, y1, x2, y2,……),第二、三两个参数分别指定填充颜色和线条颜色; # # draw.text():文字的绘制,第一个参数指定绘制的起始点(文本的左上角所在位置),第二个参数指定文本内容,第三个参数指定文本的颜色,第四个参数指定字体(通过ImageFont类来定义)。 # draw.point():绘制一个点,第一个参数制定点的坐标,形式为[x,y],第二个参数制定点的颜色 width = 270 height = 40 for i in range(5): x1 = random.randint(0, width) x2 = random.randint(0, width) y1 = random.randint(0, height) y2 = random.randint(0, height) draw.line((x1, y1, x2, y2), fill=get_random_color()) for i in range(50): draw.point([random.randint(0, width),random.randint(0, height)], fill=get_random_color()) x = random.randint(0, width) y = random.randint(0, height) draw.arc((x, y, x + 4, y + 4), 0, 90, fill=get_random_color()) # 利用IO模块创建一个文件句柄,将文件直接存放进内存 f = BytesIO() # 将图片存放进内存,后缀名为png img.save(f, 'png') # 读出文件数据 data = f.getvalue() return data
- 注册功能的实现(使用事件功能实现数据库出错回滚)
#注册 class register(View): def post(self,request): head_img=request.FILES form = form_verification.register(request.POST) res={'user':None,'msg':None,'code':0} if form.is_valid(): # print(form.cleaned_data) # 所有干净的字段以及对应的值 user_info=form.cleaned_data file_obj = head_img.get('avatar') # avatar是form表单中的name # 将用户信息存入数据库 if file_obj: # 如果用户有上传头像,则将头像数据存进extra_fields,如果没有在创建数据的时候会存入默认数据 t = time.time() file_obj.name='%s_%s_%s'%(t,user_info.get('user_name'),file_obj.name) extra_fields={} extra_fields['avatar']=file_obj #使用django的事件函数 如果其中一条sql出错 两条语句操作都无效(from django.db import transaction) with transaction.atomic(): models.Blog.objects.create(title=user_info.get('blog_name'),site_name=user_info.get('user_name')) models.UserInfo.objects.create_user(username=user_info.get('user_name'), password=user_info.get('pwd'), email=user_info.get('email'), phone=user_info.get('phone'),**extra_fields) res['user']=form.cleaned_data.get('user_name') res['code']=1 else: res['msg']=form.errors res['code']=-1 return JsonResponse(res)
- 在CBV中无法正常使用Django提供的用户认证装饰器需要自行处理(详情:https://www.cnblogs.com/wtil/p/9350291.html)
from django.contrib.auth.decorators import login_required from django.utils.decorators import method_decorator from django.http import JsonResponse # 在用户get或者post请求时如果用户没有登录,则跳转到登录页面 class LoginRequiredBlog(object): @method_decorator(login_required(login_url='/app01/login/')) def dispatch(self,request,*args,**kwargs): return super(LoginRequiredBlog,self).dispatch(request,*args,**kwargs) # 判断ajax是否登录 def chack(): def _wrapper(func): def __wrapper(self,request, *args, **kwargs): if request.user.is_authenticated: return func(self,request,*args, **kwargs) else: return JsonResponse({'state':'OK'}) return __wrapper return _wrapper
- 使用Django自定义标签的功能实现代码的重用(@register.inclusion_tag(这里面填写的是待渲染模板)) 待渲染模板不是视图HTML
from django import template #变量名称不能变 Django固定名称 register=template.Library() from app01 import models from django.db.models import Count def get_tag(user,blog): # 每个分类对应的文章数 CategoryList = models.Category.objects.filter(blog=blog).values('pk').annotate( article_count=Count('article__title')).values('nid','title', 'article_count',) # 每个标签对应的文章数 TagsList = models.Tag.objects.filter(blog=blog).values('pk').annotate( article_count=Count('article__title')).values('title', 'article_count','nid') # 每个年月对应的文章数 DateList = models.Article.objects.filter(user=user).extra( select={'temp_date': "date_format(create_time,'%%Y-%%m')"}).values('temp_date').annotate( date_count=Count('title')).values('temp_date', 'date_count') return {'TagsList': TagsList, 'DateList': DateList, 'CategoryList': CategoryList, 'blog': blog} #使用此方法可以实现的功能是 # get_tag_data(user,blog)获取此函数的返回值,然后将返回值返回到@register.inclusion_tag('inclution/LeftDive.html')指定的 #模板然后进行渲染 @register.inclusion_tag('inclution/LeftDive.html') def get_tag_data(user,blog): return get_tag(user,blog) @register.inclusion_tag('inclution/TagAndCategory.html') def GetTagCategory(user,blog): return get_tag(user,blog)
使用方法
<div class="panel panel-default"> <div class="panel-heading">我的标签</div> <div class="panel-body"> <ul class="list-group"> {% for item in TagsList %} <li class="list-group-item"> <a href="/app01/{{ blog.site_name }}/tags/{{ item.title }}"> {{ item.title }}({{ item.article_count }})</a> </li> {% endfor %} </ul> </div> </div> <div class="panel panel-default"> <div class="panel-heading">我的分类</div> <div class="panel-body"> <ul class="list-group"> {% for item in CategoryList %} <li class="list-group-item"> <a href="/app01/{{ blog.site_name }}/category/{{ item.title }}"> {{ item.title }}({{ item.article_count }})</a> </li> {% endfor %} </ul> </div> </div> <div class="panel panel-default"> <div class="panel-heading">时间档案</div> <div class="panel-body"> <ul class="list-group"> {% for item in DateList %} <li class="list-group-item"> <a href="/app01/{{ blog.site_name }}/date/{{ item.temp_date }}"> {{ item.temp_date }}({{ item.date_count }})</a> </li> {% endfor %} </ul> </div> </div>
<div class="col-md-3"> {# 导入自定义的标签#} {% load MyTag%} {# 然后使用自定义的函数 传入需要的参数 user blog#} {% get_tag_data user blog %} </div>
自定义标签 --> 带渲染模板 --> 真正使用模板
- 实现树状评论的ajax
//显示树状结构评论 $.ajax({ url:'/app01/treecomment/', type:'post', data:{'article_id':$("#article_id").attr("class")}, dataType:'json', headers:{'X-CSRFTOKEN':token}, success:function (data) { data=data.data $(".temp_msg").fadeOut(3) $.each(data,function (index,item) { // 格式化时间 var date = new Date(item[4]); Y = date.getFullYear() + '-'; M = (date.getMonth()+1 < 10 ? '0'+(date.getMonth()+1) : date.getMonth()+1) + '-'; D = date.getDate() + ' '; h = date.getHours() + ':'; m = date.getMinutes() + ':'; s = date.getSeconds(); item[4]=Y+M+D+h+m+s //格式化时间结束 var tree={} //这里使用的功能类似于python的字符串格式化 //树状评论实现的思路: // 1.每一条评论都有自己的ul // 2.子评论的数据中带有父评论的ID,然后找到父评论将子评论插入到父评论的ul中 // 3.在css样式用加入样式使评论往右偏移10px,由于子评论在父评论中,会使子评论比父评论多往右偏移10px var comment = ` <ul class="list-group" id="${item[0]}"> <li class="list-group-item"> <a href="#" class="layer">#${index + 1}</a><!--显示楼层--> <span class="comment_date">${item[4]}</span><!--显示时间--> <a id="a_comment_author_${item[0]}" href="/app01/${item[2]}" target="_blank">${item[2]}</a><!--评论者--> <a href="javascript:Reply($('#${item[0]}'));" id="${item[0]}" user="${item[2]}" class="Reply">回复</a> </li> <li class="list-group-item"> <div id="comment_body_${item[0]}" class="blog_comment_body">${item[3]}</div><!--显示评论内容--> </li> <li class="list-group-item" id="ChildComment"></li> </ul> ` if(!item[5]) { $(".feedbackNoItems").append(comment) }else { $("#"+item[5]+">#ChildComment").append(comment) } }) } })
- 使用BeautifulSoup实现简单的XSS攻击
from bs4 import BeautifulSoup def defense(content): # html.parser是Python内置的一个解析器,当传入后会解析成一个结构的文档 soup = BeautifulSoup(content, 'html.parser') tags = { 'p': ['class'], # 设置p标签仅允许class属性 'strong': ['id', ] # 设置strong标签仅允许id属性 } for tag in soup.find_all(): # find_all()方法可以找到所有标签(子子孙孙查找) # 1. 只允许有p标签和strong标签 if tag.name in tags: pass else: tag.hidden = True #将标签中的内容隐藏 tag.clear() #将标签中的内容清空 tag.decompose() #删除标签 continue # 如果标签名不符合就不执行下面的for循环了 # 2. p标签只允许class属性,strong标签只允许id属性 # 用户提交的所有属性 input_attrs = tag.attrs # {'class': 'c1', 'id': 'i1'} # 允许对应标签的指定属性有哪些 valid_attrs = tags[tag.name] # ['class'] # 字典在for循环迭代时是不可以删除某个项的,这里利用list转换 for k in list(input_attrs.keys()): if k in valid_attrs: pass else: del tag.attrs[k] # 获取处理好的标签跟文字 content = soup.decode() # 去除标签,只留文字 desc=soup.text return {'content':content,'desc':desc} if __name__ == '__main__': content = """ <p class='c1' id='i1'> asdfaa<span style="font-family:NSimSun;" class='c1'>sdf<a>a</a>sdf</span>sdf </p> <p> <strong class='c2' id='i2' a=888>asdf</strong> <script>alert(123)</script> </p> <h2> asdf </h2> """ text=defense(content) print(text)
- 用户上传头像并预览
$('#avatar').change(function () { if($(this).val()!=''){ //读取用户上传的,文件对象 var file =$(this)[0].files[0]; // new一个文件读取器 var readfile=new FileReader(); //使用读取器读取文件路径 readfile.readAsDataURL(file); //由于读取器是单独的进程,一步读取,所以需要使用readfile.onload来等来读取器读取完成,之后在进行img地址的更新 readfile.onload=function () { //img地址更新 $('.avatar_img').attr('src',readfile.result) } } }) //这个方法只支持IE10 或者其他高级浏览器,如果要兼容低级浏览器需要用其他方法 //其他方法: //1.先用Ajax将图片上传到服务器 //2.服务器返回图片地址 //3.用JS代码将地址渲染到img标签 //注意:如果用户最后并没有注册,需要将服务器上的图片删除,以免浪费空间
- 判断浏览器是否支持某一函数(在遇到低版本浏览器不兼容的函数时使用)
function bindAvatar(){ if(window.URL.createObjectURL){ //如果if判断通过则表示浏览器中有window.URL.createObjectURL函数 //如果判断其他函数,只需要修改window.URL.createObjectURL } }
- Form表单
from django.forms import widgets from django import forms from app01 import models import sys #使用钩子必须导入这个ValidationError错误,如果字段校验失败必须抛出这个错误 from django.core.exceptions import ValidationError wid_text=widgets.TextInput(attrs={"class":"form-control"}) wid_pwd=widgets.PasswordInput(attrs={"class":"form-control"}) wid_email=widgets.EmailInput(attrs={"class":"form-control"}) error_messages={'required':'该字段不能为空.....','max_length':'输入内容过长','min_length':'输入内容过短'} class register(forms.Form): user_name=forms.CharField(min_length=3, label='用户名', widget=wid_text, error_messages=error_messages, ) blog_name=forms.CharField( label='博客名', widget=wid_text, error_messages=error_messages, ) pwd=forms.CharField(min_length=6, label='密码', widget=wid_pwd, error_messages=error_messages,) r_pwd=forms.CharField(min_length=6, label='确认密码', widget=wid_pwd, error_messages=error_messages,) email=forms.EmailField(label='邮箱', widget=wid_email, error_messages=error_messages,) phone=forms.CharField(min_length=11, label='手机号', widget=wid_text, error_messages=error_messages,) #局部钩子,在源码中规定函数的命名方法(clean_字段名) def clean_user_name(self): # 当定义字段时的规则判断完成时会把通过的字段放在cleaned_data中,这里我们将name取出来进行判断是否纯数字 val = self.cleaned_data.get("user_name") if models.UserInfo.objects.filter(username=val).first(): raise ValidationError('该用户已被注册....') if not val.isdigit(): return val else: #校验失败抛出错误 在错误字典(errors)中添加 'name':'错误信息'这样一个键值对 raise ValidationError("用户名不能是纯数字!") def clean_phone(self): val=self.cleaned_data.get('phone') if len(val)==11: return val else: raise ValidationError('手机号长度必须为11位') def clean_pwd(self): val=self.cleaned_data.get('pwd') if not val.isdigit(): return val else: raise ValidationError('密码不能纯数字') # 全局钩子 def clean(self): pwd = self.cleaned_data.get("pwd") r_pwd = self.cleaned_data.get("r_pwd") if pwd and r_pwd: if pwd == r_pwd: return self.cleaned_data else: # 如果验证没有通过,会在错误字典(errors)中添加 '__all__':'错误信息' 这样的一个键值对 raise ValidationError('两次密码不一致!') else: return self.cleaned_data
- 在Form中添加自己想要的参数
#在form中是不包含request参数的,但是我们可以通过构造函数自己添加 #编写构造函数 def __init__(self,request,*args,**kwargs): super(register,self).__init__(*args,**kwargs) self.request=request #这样写完之后就可以在其他方法中使用取到request中的数据了, #super(register,self).__init__(*args,**kwargs)中的register是你创建form类的类名 #调用方法 register(request,request.POST)
- 组合筛选
思路: 1. 我们使用url来传递 汽车之家组合筛选 https://car.autohome.com.cn/price/list-0_5-0-0-0-0-0-0-0-0-0-0-0-0-0-0-1.html (0_5(汽车价格区间)-0(汽车类型)-0-0-0-0-0(汽车产地)-0-0-0-0-0-0-0-0-1)不一一列举,类似这样的方法来传递参数 python中的url接收 re_path(r'^index-(?P<type_id>d+)-(?P<category_id>d+)-(?P<tag_id>d+)') 2.实现url动态生成 因为我们在点击筛选的时候不是用Ajax来实现,而是页面的刷新实现,所以我们在配置a标签的href时,可以重后台传递数值到url中 比如,你上传了这样一个url (index-1-2-2) 后台获取的时候 **kwargs 得到 type_id=1,category_id=2,tag_id=2 处理结束之后 return render(request, 'index/index.html', {'type_id':type_id,'category_id':category_id,'tag_id':tag_id}) 前台的模板可以这样写 type:<a href='/index-1-{{category_id}}-{{tag_id}}/'>这是类别1</a> category:<a href='/index-{{type_id}}-1-{{tag_id}}/'>这是分类1</a> tag:<a href='/index-{{type_id}}-{{category_id}}-1/'>这是标签1</a>
- kindeditor的使用(富文本编辑框)
var token=document.cookie.split('=')[1] KindEditor.ready(function(K) { // #content这个是页面上textarea的ID window.editor = K.create('#content',{ // 配置编辑框的高度 height:'400px', // 服务器接收文件的地址 也就是上传文件的地址 uploadJson:'/app01/upload/', // 在这个里面可以添加一些其他 需要上传到服务器的字段,这里添加的是CSRF extraFileUploadParams:{'csrfmiddlewaretoken':token}, // 上传文件的文件名 filePostName:'ArticleImg', // 这里可以选则需要的功能 item:[ 'source', '|', 'undo', 'redo', '|', 'preview', 'print', 'template', 'code', 'cut', 'copy', 'paste', 'plainpaste', 'wordpaste', '|', 'justifyleft', 'justifycenter', 'justifyright', 'justifyfull', 'insertorderedlist', 'insertunorderedlist', 'indent', 'outdent', 'subscript', 'superscript', 'clearhtml', 'quickformat', 'selectall', '|', 'fullscreen', '/', 'formatblock', 'fontname', 'fontsize', '|', 'forecolor', 'hilitecolor', 'bold', 'italic', 'underline', 'strikethrough', 'lineheight', 'removeformat', '|', 'image', 'multiimage', 'insertfile', 'table', 'hr', 'emoticons', 'pagebreak', 'anchor', 'link', 'unlink', '|', 'about' ], //添加了这几句之后可以在jQuery中使用 .val 的方式来获取编辑框的内容 afterCreate : function() { this.sync(); }, afterBlur:function(){ this.sync(); } }); });
class upload(View): @chack() def post(self,request): # kindeditor 编辑框 上传文件之后需要返回内容 error 1 表示错误,0表示成功 url 是用来返回图片地址,实现预览.通过http://127.0.0.1:8000/app01/upload/?dir=image地址的参数dir可以判断上传的是什么文件类型,地址是kindeditor自动生成的 res={"error":1,"url":"#"} file_obj = request.FILES.get('ArticleImg') # avatar是form表单中的name FileName='media/ArticleImg/%s_%s_%s'%(time.time(),request.user.username,file_obj.name) with open(FileName, 'wb') as f: # file_obj.name取到的是客户端上传的文件名 for line in file_obj: f.write(line) res["error"] = 0 res["url"] = '/app01/%s'%(FileName) return JsonResponse(res)
- 前端JavaScript自定义一个字符串格式化
<script> String.prototype.Format=function(arg){ /* this,当前字符串 arg,Format 方法传入的参数 return 返回处理后的内容 */ var temp =this.replace(/{(w+)}/g,function (k,kk) { /* replace 替换函数 第一个参数可以是要替换的字符串,也可以是正则表达式用来匹配字符串 第二个参数可以是用来替换的字符串,也可以是一个函数( 函数:他会将正则匹配到的字符串,当作参数传入函数 str=my name is {name} , age is {age} 匹配到 {name} {age} 传入 k 我们在w+ 上加了括号 所以会将里面的内容获取到 name age 传入kk 传入参数的的时候不是两个一起传入,而是循环传入,第一次传入name 得到返回值之后在传入age 函数有返回值 函数返回什么 就会用什么去替换 匹配到的字符 比如name返回小和尚 age返回18 最后的字符串变成 my name is 小和尚 , age is 18 ) */ return arg[kk] }) } /* 使用方法 */ var v1='i am {name},age is {age}' v1.Format({'name':'小和尚','age':18}) </script>