python 博客开发之散乱笔记
博客开发之旅:
# 回滚,数据存储失败时,还原修改操作 from django.db import transaction with transaction.atomic(): do... ... # ==========自定义form表单验证----------==== # 自定义验证规则 def mobile_validate(value): mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$') if not mobile_re.match(value): raise ValidationError('手机号码格式错误') # 使用自定义验证规则 phone = fields.CharField(validators=[mobile_validate, ], error_messages={'required': '手机不能为空'}, widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'手机号码'})) # 多选 course_id = fields.MultipleChoiceField( choices=models.Course.objects.all().values_list('id','title'), widget=widgets.SelectMultiple(attrs={'class':'form-control'}) )
#+============+++++++++++==扩展Django自带的用户认证表=========+++++++++++++===
from django.contrib.auth.models import AbstractUser class UserInfo(AbstractUser): """ 用户信息表 """ nid = models.AutoField(primary_key=True) phone = models.CharField(max_length=11, null=True, unique=True) avatar = models.FileField(upload_to="avatars/", default="avatars/default.png", verbose_name="头像") create_time = models.DateTimeField(auto_now_add=True) blog = models.OneToOneField(to="Blog", to_field="nid", null=True,on_delete=models.CASCADE) def __str__(self): return self.username # 在settings中告诉Django项目用哪张表做认证 AUTH_USER_MODEL = 'app01.UserInfo' from django.contrib import auth user = authenticate(username='theuser',password='thepassword') # 即验证用户名以及密码是否正确,一般需要username 、password两个关键字参数。 # 如果认证成功(用户名和密码正确有效),便会返回一个 User 对象。 user = authenticate(username=username, password=password) if user is not None: login(request, user) # 该函数接受一个HttpRequest对象,以及一个经过认证的User对象。 # 该函数实现一个用户登录的功能。它本质上会在后端为该用户生成相关session数据。 from django.contrib.auth import logout def logout_view(request): logout(request) # 该函数接受一个HttpRequest对象,无返回值。 # 当调用该函数时,当前请求的session信息会全部清除。该用户即使没有登录,使用该函数也不会报错。 if not request.user.is_authenticated(): pass # is_authenticated() # 用来判断当前请求是否通过了认证。 # auth 给我们提供的一个装饰器工具,用来快捷的给某个视图添加登录校验。 from django.contrib.auth.decorators import login_required @login_required def my_view(request): pass # 若用户没有登录,则会跳转到django默认的 登录URL '/accounts/login/ ' 并传递当前访问url的绝对路径 (登陆成功后,会重定向到该路径)。LOGIN_URL = '/login/' # 这里配置成你项目登录页面的路由 # create_user() # auth 提供的一个创建新用户的方法,需要提供必要参数(username、password)等。 from django.contrib.auth.models import User user = User.objects.create_user(username='用户名',password='密码',email='邮箱',...) # create_superuser() # auth 提供的一个创建新的超级用户的方法,需要提供必要参数(username、password)等。 from django.contrib.auth.models import User user = User.objects.create_superuser(username='用户名',password='密码',email='邮箱',...) # check_password(password) # auth 提供的一个检查密码是否正确的方法,需要提供当前请求用户的密码。密码正确返回True,否则返回False。 ok = user.check_password('密码') # set_password(password) # auth 提供的一个修改密码的方法,接收 要设置的新密码 作为参数。 注意:设置完一定要调用用户对象的save方法!!! user.set_password(password='') user.save() 极验: https://docs.geetest.com/install/deploy/server/python pip install geetest
# +++++++++++++++++++++++文件上传++++++++++++++++++++
views from django.views.decorators.csrf import csrf_exempt @csrf_exempt def upload(request): if request.method == 'POST': file_obj = request.FILES.get('file') with open('upload/'+file_obj.name,'wb')as f: for i in file_obj.chunks(): f.write(i) return render(request,'upload.html') html <form action="/upload/" method="post" enctype="multipart/form-data"> <input type="file" name="file"/> <input type="submit" value="提交"/> </form>
#++++++++++++==文档加载完之后才执行 JS 的三种方式===++++++++++++
<script src="/static/jquery-3.3.1.js"></script> <script> window.onload = function () { var a = document.getElementById('username'); alert(a); }; $(document).ready(function () { var a = document.getElementById('default_avatar'); alert(a); }); $(function () { var a = document.getElementById('default_avatar'); alert(a); }) </script>
#+++++++++++++++++====头像预览====++++++++++++++++
<div class="form-group"> <label class="col-sm-4 control-label">选择头像</label> <div class="col-sm-8"> <label for="id_avatar"> <img id="default_avatar" src="/static/img/hmbb.png" alt="默认头像"/> </label> <input type="file" id="id_avatar" name="avatar" style="display: none"/> # <input accept="image/*" type="file" id="avatar" name="avatar" style="display: none"/> </div> </div> <script src="/static/jquery-3.3.1.js"></script> <script> $('#id_avatar').change(function () { // 创建一个读取文件的对象 var fileReader = new FileReader(); // 读取到当前选中的文件 // console.log(this.files[0]); fileReader.readAsDataURL(this.files[0]); fileReader.onload = function () { $('#default_avatar').attr('src',fileReader.result); } }) </script>
#-------------Form表单验证在渲染成HTML标签时显示错误信息----------------
<div class="form-group"> <label for="{{ obj.username.id_for_label }}" class="col-sm-4 control-label"> {{ obj.username.label }} </label> <div class="col-sm-8"> {{ obj.username }}{{ obj.errors.username.0 }} </div> </div> {% for row in obj %} <div class="form-group"> <label for="{{ row.id_for_label }}" class="col-sm-4 control-label"> {{ row.label }} </label> <div class="col-sm-8"> {{ row }}{{ row.errors.0 }} </div> </div> {% endfor %}
# +++++++++++++++++++全局钩子验证密码一致性 以及实时跟新数据+++++++++++++++++++++
class RegForm(Form): ... re_password = fields.CharField( min_length=6, label="确认密码", widget=widgets.PasswordInput( attrs={"class": "form-control"}, render_value=True, ), error_messages={ "min_length": "确认密码至少要6位!", "required": "确认密码不能为空", } ) user_type = fields.ChoiceField( choices=models.UserType.objects.values_list('id','caption') ) # 重写全局的钩子函数,对确认密码做校验 def clean(self): password = self.cleaned_data.get('password') re_password = self.cleaned_data.get('re_password') if re_password and password != re_password: self.add_error('re_password',ValidationError('两次密码不一致!')) else: return self.cleaned_data # 注意返回 # 在使用选择标签时,需要注意choices的选项可以从数据库中获取,但是由于是静态字段 # ***获取的值无法实时更新***,那么需要自定义构造方法从而达到此目的。 def __init__(self, *args, **kwargs): super(RegForm,self).__init__(*args, **kwargs) self.fields['user_type'].choices = models.UserType.objects.values_list('id','caption')
# ++++==+++__++===-+_+_===# 自己生成验证码图片 # ++++==+++__++===-+_+_===#
from PIL import Image, ImageDraw, ImageFont img_obj = Image.new('RGB',(220,40),random_color()) # 图片对象 draw_obj = ImageDraw.Draw(img_obj) # 画笔对象 font_obj = ImageFont.truetype('static/fonts/kumo.ttf', 40) # 字体对象 char_list = random_char() # 验证码字符串 request.session["code_img"] = "".join(char_list) # 将字符串保存到session会话 for i in range(len(char_list)): # 将字符画到图片上 draw_obj.text((10+50*i,0),char_list[i],fill=random_color(),font=font_obj) # draw_obj.line((begin,end),fill=random_color(),width=random.randint(1,4)) # 画线条 # draw_obj.point(width, height, fill=random_color()) # 画点 draw_obj.arc((x, y, x+z, y+z), 0, 360, fill=random_color()) # 画弧线 圆 from io import BytesIO io_obj = BytesIO() # 将生成的图片数据保存在io对象中 img_obj.save(io_obj, "png") # 从io对象里面取上一步保存的数据 data = io_obj.getvalue() return HttpResponse(data) ++++ <img id="get_code" src="/get_code_img/" alt="验证码加载失败"> $('#get_code').click(function () { // 点击图片刷新验证码 $(this)[0].src += "?"; }); ++++
#+==================# 重写局部钩子函数,对用户名做校验和ajax实时检验===========================+
class RegForm(Form): username = fields.CharField( max_length=16, label="用户名", error_messages={ "max_length": "用户名最长16位", "required": "用户名不能为空", }, widget=widgets.TextInput( attrs={"class": "form-control"}, ) ) def clean_username(self): username = self.cleaned_data.get('username') is_exist = models.UserInfo.objects.filter(username=username) if is_exist: self.add_error('username',ValidationError('用户名已存在')) else: # 重写的是局部钩子,所以返回检验的字段 return username # username输入框失去焦点,使用ajax检验用户名是否存在 @csrf_exempt def check_username_exist(request): ret = {'status':False,'msg':None} is_exist = models.UserInfo.objects.filter(username=request.POST.get('username')) if is_exist: ret['status']=True ret['msg']='用户名已存在' return HttpResponse(json.dumps(ret)) return HttpResponse(json.dumps(ret)) # <form autocomplete="off"> #取消浏览器自动匹配 # $('#id_username').on('input',function () { # 内容变动就提交 $('#id_username').blur(function () { $.ajax({ url: '/check_username_exist/', data: {'username': $(this).val()}, method: 'post', dataType: 'json', success: function (data) { if (data.status) { $('#id_username').next().text(data.msg); console.log(data.msg); } } }) }) # Django admin 使用 # 在app/admin.py文件中注册表 from django.contrib import admin from app01 import models admin.site.register(models.UserInfo) admin.site.register(models.Blog) # 在settings配置显示中文 LANGUAGE_CODE = 'zh-hans' class UserInfo(): ... class Meta: verbose_name = '用户' # 给表起名 verbose_name_plural = verbose_name # 显示复数也用'用户'来显示 在admin后台中显示中文表名
#=========== # Django用户上传的都叫media文件================== # setting.py # media配置,用户上传的文件都默认放在这个文件夹下 MEDIA_ROOT = os.path.join(BASE_DIR,"media") MEDIA_URL = "/media/" # urls.py from django.views.static import serve from django.conf import settings re_path('media/(?P<path>.*)$',serve,{"document_root":settings.MEDIA_ROOT}), # 做了media配置,用户上传文件都会在media/xx中。
# _+_+_+_+_+_+_+_+_+_+_+导入Django,单独测试某个功能_+_+_+_+_+_+_+_+_+_+_+_+_+ test.py import os if __name__ == '__main__': os.environ.setdefault('DJANGO_SETTINGS_MODULE', '博客.settings') import django django.setup() from app01 import models obj = models.UserInfo.objects.all() print(obj.query)
# -=======----博客文章-=======----
<div class="article"> <h4><a href="">{{ article.title }}</a></h4> <div class="media"> <div class="media-left"> <a href="#"> <img id="user_avatar" class="media-object" src="/media/{{ article.user.avatar }}"alt="..."> </a> </div> <div class="media-body"> <p>{{ article.desc }}</p> </div> </div> <div class="article_footer"> <span><a href="">{{ article.user.username }}</a></span>发布于 <span>{{ article.create_time|date:'Y-m-d H:i:s' }}</span> <span class="glyphicon glyphicon-comment">评论({{ article.comment_set.count }})</span> <span class="glyphicon glyphicon-thumbs-up">点赞({{ article.articleupdown_set.count }})</span> </div> </div> 分组和聚合 https://www.cnblogs.com/liwenzhou/p/8660826.html 1. 分组 ORM中values或者values_list 里面写什么字段,就相当于select 什么字段 ret = models.Employee.objects.all().values("dept", "age") 相当于: SELECT `employee`.`dept`, `employee`.`age` FROM `employee` LIMIT 21; args=() 2. ORM中 annotate 前面是什么就按照什么分组! from django.db.models import Avg ret = models.Employee.objects.values("province").annotate(a=Avg("salary")).values("province", "a") 相当于: SELECT `employee`.`province`, AVG(`employee`.`salary`) AS `a` FROM `employee` GROUP BY `employee`.`province` ORDER BY NULL LIMIT 21; args=() 3. extra --> 在执行ORM查询的时候执行额外的SQL语句 # 查询person表,判断每个人的工资是否大于2000 ret = models.Person.objects.all().extra( select={"gt": "salary > 2000"} ) 相当于: SELECT (salary > 2000) AS `gt`, `person`.`id`, `person`.`name`, `person`.`salary`, `person`.`dept_id` FROM `person` LIMIT 21; args=() 4. 直接执行原生的SQL语句,类似pymysql的用法 from django.db import connection cursor = connection.cursor() # 获取光标,等待执行SQL语句 cursor.execute("""SELECT * from person where id = %s""", [1]) row = cursor.fetchone() print(row) # =====++++++创建数据库表,插入时间++++++======= mysql>create table test(d date, dt datetime, t time); mysql>insert into test(d,dt,t) values(now(),now(),now()); mysql>select date_format(dt,'%Y-%m') from test; # 格式化,只看年月
#=++++++++++++==========+++++母板,子板,自定义templates+++====+++++++++++===========
母板 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{{ blog.title }}</title> <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css"> <link rel="stylesheet" href="/static/fontawesome/css/font-awesome.min.css"> <link rel="stylesheet" href="/static/commons.css"/> <link rel="stylesheet" href="/static/theme/{{ blog.theme }}"/> </head> <body> <div class="blog"> <div class="header"> <div>{{ blog.title }}</div> </div> <div class="container"> <div class="col-md-3"> {% load my_tags %} # 在这使用自定义templates {% get_left_menu username %} </div> <div class="col-md-8"> {% block page-main %} {% endblock %} </div> </div> </div> <script src="/static/jquery-3.3.1.js"></script> <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script> </body> </html> 子板 {% extends 'base.html' %} {% block page-main %} <div class="article"> {% for article in article_list %} 。。。。。。。 {% endfor %} </div> {% endblock %} 自定义templates 在app下新建目录templatetags,在新建目录下新建文件my_tags.py from django import template from app01 import models from django.db.models import Count register = template.Library() # 固定写法 @register.inclusion_tag("left_menu.html") def get_left_menu(username): user = models.UserInfo.objects.filter(username=username).first() blog = user.blog # 查询文章分类及对应的文章数 category_list = models.Category.objects.filter(blog=blog) # 查文章标签及对应的文章数 tag_list = models.Tag.objects.filter(blog=blog).annotate(c=Count("article")).values("title", "c") # 按日期归档 # archive_list = models.Article.objects.filter(user=user).extra( # select={"archive_ym": "date_format(create_time,'%%Y-%%m')"} # ).values("archive_ym").annotate(c=Count("nid")).values("archive_ym", "c") return { "category_list" :category_list, "tag_list": tag_list, } 创建left_menu.html <div class="panel panel-primary"> <div class="panel-heading">文章分类</div> <div class="panel-body"> {% for category in category_list %} <p>{{ category.title }}({{ category.article_set.all.count }})</p> {% endfor %} </div> </div> <div class="panel panel-primary"> <div class="panel-heading">标签分类</div> <div class="panel-body"> {% for tag in tag_list %} <p>{{ tag.title }}({{ tag.c }})</p> {% endfor %} </div> </div>
# ===========-----====-----====-------点赞 js-------======-----======-----=============
def dianzan_up_down(request): ret = {'status':False,'msg':None} article_id = request.POST.get('article_id') is_up = json.loads(request.POST.get('is_up')) user = request.user if user: try: models.ArticleUpDown.objects.create(user=user,article_id=article_id,is_up=is_up) models.Article.objects.filter(nid=article_id).update(up_count=F('up_count')+1) ret['status'] = True ret['is_up'] = is_up return HttpResponse(json.dumps(ret)) except Exception as e: is_up = models.ArticleUpDown.objects.filter(user=user,article_id=article_id).first().is_up ret['first_action']=is_up return HttpResponse(json.dumps(ret)) ret['msg'] = '请先登录' return HttpResponse(json.dumps(ret)) <div class="dianzan_up_down"> <div id="div_digg"> <div class="diggit action"> <span class="diggnum" id="digg_count">{{ article.up_count }}</span> </div> <div class="buryit action"> <span class="burynum" id="bury_count">{{ article.down_count }}</span> </div> <div class="clear"></div> <div class="diggword" id="digg_tips" style="color: red;"></div> </div> {% csrf_token %} # 可以将文章id渲染出来 <div class="info" article_id="{{ article.pk }}"></div> </div> <script> // 点赞 $('#div_digg .action').click(function () { var is_up = $(this).hasClass('diggit'); var crticle_id = "{{ article.pk }}"; # var article_id = $('.info').attr('article_id');当JS保存为静态文件时,获取文章id $.ajax({ url: '/blog/article/up_down/', type: 'post', data: { 'article_id': crticle_id, 'is_up': is_up, 'csrfmiddlewaretoken': $('[name=csrfmiddlewaretoken]').val() }, dataType: 'json', success: function (data) { if (data.status) { if (data.is_up) { var count = $('#digg_count').text(); count = parseInt(count) + 1; $('#digg_count').text(count); } else { var count = $('#bury_count').text(); count = parseInt(count) + 1; $('#digg_count').text(count); } } else { if (data.msg) { $('#digg_tips').text(data.msg); } else { if (data.first_action) { $('#digg_tips').text('您已经推荐过啦'); } else { $('#digg_tips').text('您已经反对过啦'); } } setTimeout(function () { $("#digg_tips").html("") }, 1000) } } }) }) </script> # ========= 响应ajax,数据直接使用,不用写dataType:'json' ========= from django.http import JsonResponse return JsonResponse({'',''})
# +++++++++++++++++++使用JS动态绑定事件+++++++++++++++++++++++
//後面添加的元素無法綁定事件,需預加載 $(document).on('click','#reply',function () { $("#comment_content").focus(); var v = "@" + $(this).attr("username") + "\n"; $("#comment_content").val(v); pid = $(this).attr("comment_id") });
#_+__________++++++++评论树例子+__________+++++++++
from django.http import JsonResponse def comment_tree(request,article_id): comment_list = list(models.Comment.objects.filter(article_id=article_id).values("pk","content","parent_comment_id","create_time",'user__username')) print(comment_list) # 将<QuerySet [{'pk': 6,}]> 转为列表。若直接json.dumps(comment_list)会报错! return JsonResponse(comment_list,safe=False) # 静态文件中(外部js文件)需要使用的值,最好在渲染的时候将值作为属性存到标签中。方便取值。 $(function () { // 获取评论列表 $.ajax({ url: '/blog/comment/' + '{{ article.pk }}/', // dataType:'json', // return HttpResponse(json.dumps(dict(enumerate(comment_list)))) // enumerate(comment_list,start=1)指定起始值,那么下面的.each index就不用加值了,不过这个麻烦 // 瞎折腾得是,以上3行 等价 return JsonResponse(comment_list,safe=False) success: function (data) { $.each(data, function (index, comment_dict) { index = index + 1; var s = '<div class="comment_item well" comment_id="' + comment_dict.pk + '">\n' + ' <div class="left">\n' + ' <a href="">#' + index + '楼</a>\n' + ' <span>' + comment_dict.create_time + '</span>\n' + ' <a href="/blog/' + comment_dict.user__username + '/">' + comment_dict.user__username + '</a>\n' + ' </div>\n' + ' <div class="right">\n' + ' <a id="reply" comment_pk="' + comment_dict.pk + '" username="' + comment_dict.user__username + '">回复</a>\n' + ' </div>\n' + ' <div class="clear_float_before"><span>' + comment_dict.content + '</span></div>\n' + ' </div>'; if (comment_dict.parent_comment_id) { // 子评论 追加到父评论下 var pid = comment_dict.parent_comment_id; $('[comment_id="' + pid + '"]').append(s); } else { // 根评论 $('.comment_tree').append(s); } }) } }); pid = ""; // 有值即回复别人的评论内容,无值评论文章 // 子评论设置pid $(document).on('click', '#reply', function () { var v = "@" + $(this).attr("username") + "\n"; $("#comment_content").focus().val(v); pid = $(this).attr("comment_pk"); }); // 提交评论 $('#comment_btn').click(function () { var content = $('#comment_content').val(); var article_id = $('#info').attr('article_id'); $.ajax({ url: '/blog/comment/', type: 'post', data: { 'content': content, 'article_id': article_id, 'pid': pid, csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(), }, success: function (data) { var s = '<div class="well"><span>' + content + '</span></div>'; // 生成tag,添加到页面暂不刷新,清除文本框,将pid清空,避免影响提交数据。 $('.comment_tree').append(s); $('#comment_content').val(''); pid = ""; } }) }) })
# ===========================富文本编辑器=kindeditor===============================
<textarea name="article_content" id="article_content" cols="60" rows="20"></textarea> <script charset="utf-8" src="/static/kindeditor/kindeditor-all.js"></script> <script src="/static/jquery-3.3.1.js"></script> <script> KindEditor.ready(function (K) { window.editor = K.create('#article_content', { width: '800px', uploadJson: "/upload/", //上传图片什么的需要填参数 extraFileUploadParams: { "csrfmiddlewaretoken": $("[name='csrfmiddlewaretoken']").val() }, filePostName: "upload_file", //request.FILE.get('') 文件键名 }); }); </script> 上传图片 def upload(request): if request.method == 'POST': file_obj = request.FILES.get('upload_file') path = 'media/add_article/'+file_obj.name with open(path,'wb')as f: for i in file_obj.chunks(): f.write(i) res = { 'error':0, # 没有出错 'url':path, } return HttpResponse(json.dumps(res)) return HttpResponse('') 提交文章 # 使用beautifulSoup过滤文章中的JS代码,防止XSS攻击 def add_article(request): if request.method=="POST": title=request.POST.get('title') article_content=request.POST.get('article_content') user=request.user from bs4 import BeautifulSoup bs = BeautifulSoup(article_content,'html.parser') # 过滤非法字符 for tag in bs.find_all(): # print(tag.name) if tag.name in ['script','link']: tag.decompose() desc = bs.text[0:150]+'...' article_obj = models.Article.objects.create(user=user,title=title,desc=desc) models.ArticleDetail.objects.create(content=str(bs),article=article_obj) return redirect('/blog/%s/'%request.user.username) return render(request,'add_article.html') # orm查询,基于对象查询(子查询),反向查询按表名小写_set.all() # 基于queryset和__查询(join查询)正向查询:按字段 反向查询:表名小写 # select publish.email from Book # left join Publish on book.publish_id=publish.nid # where book.title="python" # 按逻辑来,对象查询是基于单个对象!?,join只要连上表就能拿值!。
# ———————————————————————————————————简单使用admin—————————————————————————————————————
from django.utils.safestring import mark_safe from app01 import models class UserInfoConfig(admin.ModelAdmin): def deletes(self): return mark_safe("<a href=''>删除</a>") list_display = ["username","email","create_time",'blog',deletes] # 在admin管理页面,显示出用户表的用户信息字段,deletes是自定义的跳转链接 list_display_links = ["email"] # 设置哪个字段可以点击跳转到编辑当前页信息的页面 list_filter=["username","email","create_time",'blog'] # 筛选功能,按字段条件筛选,指定多个字段组合筛选。 list_editable=["username",'blog'] # 此配置需要有list_display_links = ["email"]配置才能生效,即在当前页编辑其他字段信息,效果和直接在被编辑字段的编辑页面相同 search_fields=["username","email"] # 添加搜索功能,以列表中的字段过滤出的信息后进行查找 def patch_init(self,request,queryset): queryset.update(price=100) patch_init.short_description = "批量初始化" actions = [patch_init,] # 添加一个批量操作选项。传入执行方法名 change_list_template="login.html" # 自己的后台管理页面。
# -------------------Xadmin----------------------
流程 1、启动 在settings文件中配置上 'Xadmin.apps.XadminConfig', 将会自动执行ready方法,查找所有app中的Xadmin模块 autodiscover_modules('Xadmin') 2、注册 单例模式 from Xadmin.service.Xadmin import site from app01 import models site.registry(models.Test) 3、设计url 为每个app下的model设计增删改查url 127.0.0.1:8008/admin/app01/book/1/change/ :改id=1的数据 # url的路由分发 path('test/',([re_path('\d+',func),re_path('(\d+)',([],None,None)),],None,None)) # url path('xadmin/', Xadmin.site.urls), # Xadmin组件 -> apps.py from django.apps import AppConfig from django.utils.module_loading import autodiscover_modules class XadminConfig(AppConfig): name = 'Xadmin' def ready(self): autodiscover_modules('Xadmin') # Xadmin -> service -> Xadmin.py from django.urls import path,re_path from django.shortcuts import render,HttpResponse,redirect class ModelXadmin(object): def __init__(self,model,site): self.model=model self.site=site def list_view(self,request): return HttpResponse('list_view') def add_view(self,request): return HttpResponse('add_view') def change_view(self,request,id): return HttpResponse('change_view') def delete_view(self,request,id): return HttpResponse('delete_view') def get_urls2(self): temp=[] temp.append(re_path('^$',self.list_view)) temp.append(re_path('^add/$',self.add_view)) temp.append(re_path('^(\d+)/change/$',self.change_view)) temp.append(re_path('^(\d+)/delete/$',self.delete_view)) return temp @property def urls2(self): # 路由,某个表的增删改查url return self.get_urls2(),None,None class XadminSite(object): def __init__(self,name='admin'): self._registry = {} def get_urls(self): temp = [] for model,xadmin_class_obj in self._registry.items(): app_name = model._meta.app_label model_name = model._meta.model_name temp.append(re_path('^%s/%s/'%(app_name,model_name),xadmin_class_obj.urls2)) return temp @property def urls(self): # 路由,app下的某个表url return self.get_urls(),None,None def registry(self,model,xadmin_class=None,**kwargs): if not xadmin_class: xadmin_class = ModelXadmin self._registry[model] = xadmin_class(model,self) site = XadminSite() # app01 -> Xadmin.py from Xadmin.service.Xadmin import site from app01 import models site.registry(models.Test) 笔记暂断
# =-=-=-=-=-=-=-=-=rbac基于角色的访问权限控制,大致流程=--==-=-=-=-=-=-=-
# rbac -> views.py from django.shortcuts import render,redirect from rbac.models import * def rbac_list(request): '''用户权限信息''' role_list = Role.objects.all() user = Role.objects.values('o2o_user__user_id','o2o_user__user__username') user_list = [] for item in user: if item['o2o_user__user_id']: user_list.append((item['o2o_user__user_id'],item['o2o_user__user__username'])) obj = roleForm() return render(request,'rbac.html',{'role_list':role_list,'user_list':user_list,'obj':obj}) from django.forms import Form,fields,widgets class roleForm(Form): users = fields.ChoiceField( choices=O2o_User.objects.values_list('user__nid','user__username') ) roles = fields.MultipleChoiceField( choices=Role.objects.values_list('pk','title') ) def rbac_edit(request): '''修改权限''' id = request.POST.get('users') roles = request.POST.getlist('roles') obj = roleForm({'users':id,'roles':roles}) res = obj.is_valid() if res: obj = O2o_User.objects.filter(user_id=id).first() obj.roles.set(roles) return redirect('/rbac/') # 中间件过滤请求 import re from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse,redirect class ValidPermission(MiddlewareMixin): def process_request(self,request): current_path = request.path_info pass_url = ["/index/",'/info/','/register/.*','/login/.*','/logout/','/admin/.*',] # 白名单直接放行 for rule in pass_url: ret = re.match(rule, current_path) if ret: return None # 不在白名单内,判断是否登录用户。 if not request.user.username: return redirect('/login/') permission_dict = request.session.get('permission_dict') for item in permission_dict.values(): urls = item['url'] for rule in urls: rule = '^%s$'%rule ret = re.match(rule,current_path) if ret: # 注入权限,用于控制template模板显示增删改 request.action = item['action'] return None print('没有权限%s'%current_path) return HttpResponse('没有权限%s'%current_path) # rbac -> permission.py from rbac import models def valid_permission(request): '''提取用户权限并存储到session''' # print('ssssssssssssssss',request.path_info,user.email) res = models.O2o_User.objects.filter(user=request.user).values( 'roles__permissions__url', # 'roles__permissions__title', 'roles__permissions__action', 'roles__permissions__group_id',) per = {} for item in res: if item['roles__permissions__group_id'] in per: per[item['roles__permissions__group_id']]['url'].append(item['roles__permissions__url']) # per[item['roles__permissions__group_id']]['title'].append(item['roles__permissions__title']) per[item['roles__permissions__group_id']]['action'].append(item['roles__permissions__action']) else: per[item['roles__permissions__group_id']] = { 'url':[item['roles__permissions__url'],], # 'title':[item['roles__permissions__title'],], 'action':[item['roles__permissions__action'],] } request.session['permission_dict'] = per print(per) # 使用auth认证,登录成功就将用户权限保存到session中 auth.login(request,user) valid_permission(request) # 在rbac中创建一个模板用于rbac权限设置,用户权限不满足就只渲染部分功能。
# -===-=-=-=-=-=-=-=-=-=-=-=-=-返回顶部=-=-=-=-=-=-=-=-
<span id="back_top" class="hidden">返回顶部</span> <script> $(window).scroll(function () { var $height = $(window).scrollTop(); if ($height > 200) { $('#back_top').removeClass('hidden'); } else { $('#back_top').addClass('hidden'); } }) $('#back_top').click(function () { $('body,html').animate({ scrollTop: 0 }, 1000); }) </script>
# -=-=-=-=-=-=-=-ajax请求后端返回对象,前端直接使用
comment_list = list(models.Comment.objects.filter(article_id=article_id).values("pk","content")) return JsonResponse(comment_list, safe=False) 传送对象 success: function (data) { $.each(data, function (index, comment_dict) { comment_dict.pk }}
#-=-=-=-==========页面跳转=-=-=-=-=-=-=-=-=-
<script language="javascript" type="text/javascript"> // 以下方式直接跳转 window.location.href='hello.html'; // 以下方式定时跳转 setTimeout("javascript:location.href='hello.html'", 5000); </script> <head> <!-- 以下方式只是刷新不跳转到其他页面 --> <meta http-equiv="refresh" content="10"> <!-- 以下方式定时转到其他页面 --> <meta http-equiv="refresh" content="5;url=hello.html"> </head> # 结合了倒数的javascript实现 <script language="javascript" type="text/javascript"> var second = document.getElementById('totalSecond').textContent; setInterval("redirect()", 1000); function redirect() { document.getElementById('totalSecond').textContent = --second; if (second < 0) location.href = 'hello.html'; } </script>