• 开发一个博客园系统


      最近在学django框架,准备用django写一个博客园的系统,并且在写的过程中也遇到一些问题,实践出真知,对django开发web应用方面也有了进一步的了解。很多操作实现都是以我所认知的技术完成的,可能存在不合理的地方(毕竟实现的方法多种多样),基本完成后会将源码上传到git,也欢迎各位大神指正。

      首先,要写未登录主站(index)。这里需要注意文章的分类:

      文章的分类切换,网站本身有定义的文章类型:

       type_choices = [
            (1, "Python"),
            (2, "Linux"),
            (3, "OpenStack"),
            (4, "GoLang"),
        ]

      要实现主页的分类(分类标签样式要突出)需要使用一个前端与后端都有的id来显示分类。

        if request.method=='GET':
            type_id = int(kwargs.get('type_id')) if kwargs.get('type_id') else None
            #后台都是get传参
            if type_id:
                article_list = models.Article.objects.filter(article_type_id=type_id).extra(select={'c': "strftime('%%Y-%%m',create_time)"})
            else:
                article_list = models.Article.objects.all().extra(select={'c': "strftime('%%Y-%%m',create_time)"})
            type_choice_list = models.Article.type_choices#分类的
            # print(type_choice_list)#[(1, 'Python'), (2, 'Linux'), (3, 'OpenStack'), (4, 'GoLang')]
    后台代码
    {% if type_id %}
                            <li><a href="/">全部</a></li>
                        {% else %}
                            <li class="active"><a href="/">全部</a></li>
                        {% endif %}
    
                        {% for item in type_choice_list %}
                            {% if item.0 == type_id %}
                                <li class="active"><a href="/all/{{ item.0 }}/">{{ item.1 }}</a></li>
                            {% else %}
                                <li><a href="/all/{{ item.0 }}/">{{ item.1 }}</a></li>
                            {% endif %}
                        {% endfor %}
    前端代码

      登陆与注册页面

      登陆与注册的验证使用form表单功能完成,除此之外我们还需要有一个图片验证码用于认证。

       在前端设置一个图片,图片src属性指向后端(获取图片时向后端发生get请求方式,后端返回的),验证码图片由后端生成图片在上面显示,点击更换我们使用每次点击在src属性后面加一个?,这样url改变了前端向后台发送一个get请求,那么就会获得一个新的验证码图片了。

     <img style=" 120px;height: 30px;" src="/check_code/" title="点击更换" id="change_img">
    
            $(function(){
                change_img();
            });
            function change_img() {//get方式在url上加?刷新图片
                $('#change_img').click(function () {
                    $(this)[0].src=$(this)[0].src+'?';
                })
            }
    前端代码
    from PIL import Image,ImageDraw,ImageFont,ImageFilter
    import random
    
    def rd_check_code(width=120, height=30, char_length=4, font_file='kumo.ttf', font_size=28):
        code = []
        img = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255))
        draw = ImageDraw.Draw(img, mode='RGB')
     
        def rndChar():
            """
            生成随机字母   
            :return:
            """
            return chr(random.randint(65, 90))
     
        def rndColor():
            """
            生成随机颜色
            :return:
            """
            return (random.randint(0, 255), random.randint(10, 255), random.randint(64, 255))
     
        # 写文字
        font = ImageFont.truetype(font_file, font_size)
        for i in range(char_length):
            char = rndChar()
            code.append(char)
            h = random.randint(0, 4)
            draw.text([i * width / char_length, h], char, font=font, fill=rndColor())
     
        # 写干扰点
        for i in range(40):
            draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
     
        # 写干扰圆圈
        for i in range(40):
            draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
            x = random.randint(0, width)
            y = random.randint(0, height)
            draw.arc((x, y, x + 4, y + 4), 0, 90, fill=rndColor())
     
        # 画干扰线
        for i in range(5):
            x1 = random.randint(0, width)
            y1 = random.randint(0, height)
            x2 = random.randint(0, width)
            y2 = random.randint(0, height)
     
            draw.line((x1, y1, x2, y2), fill=rndColor())
     
        img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)
        return img,''.join(code)
    PIL生成随机码模块
    def check_code(request):
        from io import BytesIO
        from utils.random_check_code import rd_check_code
        img,code = rd_check_code()
        stream = BytesIO()#开辟一个内存空间,类似于文件句柄
        print(stream)#io空间,<_io.BytesIO object at 0x06D6EAE0>
        print(img)#pillow生成的图片对象
        img.save(stream,'png')
        print(stream)
        print(stream.getvalue())#bytes类型的图片信息,返回前端生成图片
        print(img)
        request.session['code'] = code#将生成的随机字符串存到session用于验证
        return HttpResponse(stream.getvalue())
    后端返回随机码

      登录后将session信息写入浏览器cookie,可以完成两周免登陆效果。注销我使用的时Ajax,后台需要清理session。这个过程中要注意,Ajax需要向后台发送自己的csrf码,否则后端默认是伪造的跨站请求,不给予服务。

     $(function () {
                $(".take_off").click(function () {
                {#                        $(".take_off").click(function () {#}
                {#            $.ajaxSetup({#}
                {#                data:{csrfmiddlewaretoken:'{{ csrf_token }}'}#}
                {#            });#}
                    $.ajax({
                    url:'/',
                    type:'POST',
                    {#data:{ 'csrftoken':{{ csrf_token}} },#}
                    data:{csrfmiddlewaretoken:'{{ csrf_token }}'},
                    dataType:"JSON",
                    success:function(arg){
                        console.log(arg);
                        if(arg.status){
                            location.href='/'
                        }else{
                        }}
                })
                });
           });
    Ajax注销

      注册也有一个地方需要注意,就是图片上传的问题,我使用的是硬解码的方式存放图片:

                with open(os.path.join('/static/imgs/', obj.cleaned_data.get('avatar').name), 'wb') as file:
                    all = obj.cleaned_data.get('avatar').chunks()  # 拿到整个文件
                    for trunk in all:
                        file.write(trunk)
                    file.close()
                obj.cleaned_data['avatar'] = os.path.join('/static/imgs/', obj.cleaned_data.get('avatar').name)
                models.UserInfo.objects.create(**obj.cleaned_data)
    直接在后端进行存储

      这种方法还是比较笨重的解决方法,在创建数据库的时候有一个upload_to字段可以直接指定文件存放路径。

    avatar = models.ImageField(verbose_name='头像',upload_to='static/imgs')
    创建数据表直接指定

      不过这两种方法都不够灵活,不能防止图片名重复的问题,这里有一篇博客对存储路径进行优化的方式。这已经解决了很多一部分命名问题了。http://blog.csdn.net/alxandral_brother/article/details/53415551。

      用Ajax完成图片预览功能

      首先文件上传的丑陋的接口我们是没有办法修改的(点击上传那个),所以我们使用默认图片遮住这个文件框。

    <div class="col-sm-10" style="position: relative;height:80px; 80px;">
         <img id="previewImg" style="position: absolute;height:80px; 80px;" src="/static/imgs/default.png">
        {{ obj.avatar }}<span>{{ obj.errors.avatar.0 }}</span>
    </div>

      接下来是关于上传预览的部分,最早我们使用Ajax把前端获取的图片发给后端,后端接收后保存再发送回前端显示预览,但是这样做会导致用户上传了图片但是没有注册成功,那么后端保存的图片信息就是垃圾数据,那么我们必须要进行定期的数据清理工作。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <link rel="stylesheet" href="/static/bootstrap-3.3.5-dist/css/bootstrap.css" />
        <style>
            .login{
                 600px;
                margin: 0 auto;
                padding: 20px;
                margin-top: 80px;
            }
            .f1{
                position: absolute;height:80px; 80px;top:0;left: 0;opacity: 0;
            }
    
        </style>
    </head>
    <body>
        <div class="login">
            <div style="position: relative;height:80px; 80px; left:260px;top: -10px ">
                <img id="previewImg" style="height:80px; 80px;" src="/static/image/default.png">
                <input id="imgSelect" style="height: 80px; 80px; position: absolute; top:0;left: 0; opacity: 0" type="file">
                {{ obj.avatar }}
            </div>
        </div>
    
        <script src="/static/jquery-3.2.1.js"></script>
    
        <script>
            $(function () {
                bindAvartar1();
            });
    
            function bindAvartar1() {
                $("#imgSelect").change(function () {
                    //$(this)[0]           #jquery变成DOM对象
                    //$(this)[0].files     #获取上传当前文件的上传对象
                    //$(this)[0].files[0]  #获取上传当前文件的上传对象的某个对象
                    var obj = $(this)[0].files[0];
                    console.log(obj);
    
                    //ajax 发送后台获取头像路径
                    //img src 重新定义新的路径
    
                    var formdata = new FormData();  //创建一个对象
                    formdata.append("file",obj);
                    var xhr = new XMLHttpRequest();
                    xhr.open("POST","/register/");
                    xhr.send(formdata);
    
                    xhr.onreadystatechange = function () {
                        if(xhr.readyState ==4){
                            var file_path = xhr.responseText;
                            console.log(file_path);
                            $("#previewImg").attr("src","/" + file_path)
                        }
                    };
    
                })
            }
        </script>
    </body>
    </html>
    Ajax上传预览
    import os
    def register(request):
        if request.method == "GET":
            return render(request,"register.html")
        else:
            print(request.POST)
            print(request.FILES)
            file_obj = request.FILES.get("file")
            print(file_obj)
            file_path = os.path.join("static", file_obj.name)
            with open(file_path, "wb") as f:
                for chunk in file_obj.chunks():
                    f.write(chunk)
            return HttpResponse(file_path)
    后端保存图片

      当然,我们还可以使用本地预览的方式。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <link rel="stylesheet" href="/static/bootstrap-3.3.5-dist/css/bootstrap.css" />
        <style>
            .login{
                 600px;
                margin: 0 auto;
                padding: 20px;
                margin-top: 80px;
            }
            .f1{
                position: absolute;height:80px; 80px;top:0;left: 0;opacity: 0;
            }
    
        </style>
    </head>
    <body>
        <div class="login">
            <div style="position: relative;height:80px; 80px; left:260px;top: -10px ">
                <img id="previewImg" style="height:80px; 80px;" src="/static/image/default.png">
                <input id="imgSelect" style="height: 80px; 80px; position: absolute; top:0;left: 0; opacity: 0" type="file">
                {{ obj.avatar }}
            </div>
        </div>
    
        <script src="/static/jquery-3.2.1.js"></script>
    
        <script>
            $(function () {
                bindAvartar2();
            });
    
          
    
            function bindAvartar2() {
                $("#imgSelect").change(function () {
                    var obj = $(this)[0].files[0];
                    console.log(obj);
    
                    //将文件对象上传到浏览器
                    //IE10 以下不支持
                    var v = window.URL.createObjectURL(obj);
                    $("#previewImg").attr("src",v);
    
                    //不会自动释放内存
                    //当加载完图片后,释放内存
                    document.getElementById("previewImg").onload= function () {
                        window.URL.revokeObjectURL(v);
                    };
                })
            }
    
    
    
    
    
            function bindAvartar3() {
                $("#imgSelect").change(function () {
                    var obj = $(this)[0].files[0];
                    console.log(obj);
    
                    var reader = new FileReader();
                    reader.onload = function (e) {
                        $("#previewImg").attr("src",this.result);
                    };
                    reader.readAsDataURL(obj)
                })
            }
    
        </script>
    </body>
    </html>
    本地上传预览的两种方式

      因为用户的浏览器版本限制,我们可以采用多重手段给不同的用户使用预览功能:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <link rel="stylesheet" href="/static/bootstrap-3.3.5-dist/css/bootstrap.css" />
        <style>
            .login{
                 600px;
                margin: 0 auto;
                padding: 20px;
                margin-top: 80px;
            }
            .f1{
                position: absolute;height:80px; 80px;top:0;left: 0;opacity: 0;
            }
    
        </style>
    </head>
    <body>
        <div class="login">
            <div style="position: relative;height:80px; 80px; left:260px;top: -10px ">
                <img id="previewImg" style="height:80px; 80px;" src="/static/image/default.png">
                <input id="imgSelect" style="height: 80px; 80px; position: absolute; top:0;left: 0; opacity: 0" type="file">
    
            </div>
        </div>
    
        <script src="/static/jquery-3.2.1.js"></script>
    
        <script>
            $(function(){
               bindAvatar();
            });
    
            function bindAvatar(){
                if(window.URL.createObjectURL){
                    bindAvatar2();
                }else if(window.FileReader){
                    bindAvatar3()
                }else{
                    bindAvatar1();
                }
            }
    
    
    
            function bindAvatar1() {
                $("#imgSelect").change(function () {
                    //$(this)[0]           #jquery变成DOM对象
                    //$(this)[0].files     #获取上传当前文件的上传对象
                    //$(this)[0].files[0]  #获取上传当前文件的上传对象的某个对象
                    var obj = $(this)[0].files[0];
                    console.log(obj);
    
                    //ajax 发送后台获取头像路径
                    //img src 重新定义新的路径
    
                    var formdata = new FormData();  //创建一个对象
                    formdata.append("file",obj);
                    var xhr = new XMLHttpRequest();
                    xhr.open("POST","/register/");
                    xhr.send(formdata);
    
                    xhr.onreadystatechange = function () {
                        if(xhr.readyState ==4){
                            var file_path = xhr.responseText;
    {#                        console.log(file_path);#}
                            $("#previewImg").attr("src","/" + file_path)
                        }
                    };
                })
            }
    
    
            function bindAvatar2() {
                $("#imgSelect").change(function () {
                    var obj = $(this)[0].files[0];
                    console.log(obj);
    
                    //将文件对象上传到浏览器
                    //IE10 以下不支持
    
    
                    //不会自动释放内存
                    //当加载完图片后,释放内存
    
                    document.getElementById("previewImg").onload= function () {
                        window.URL.revokeObjectURL(v);
                    };
    
                    var v = window.URL.createObjectURL(obj);
                    $("#previewImg").attr("src",v);
                })
            }
    
    
    
    
    
            function bindAvatar3() {
                $("#imgSelect").change(function () {
                    var obj = $(this)[0].files[0];
                    console.log(obj);
    
                    var reader = new FileReader();
                    reader.onload = function (e) {
                        $("#previewImg").attr("src",this.result);
                    };
                    reader.readAsDataURL(obj)
                })
            }
    
    
    
        </script>
    </body>
    </html>
    一步到位,大家都能用

      主页部分,主页部分的主要操作就是各项分类,你可以将标签,随笔和时间分开写,其实我一开始也是这么做的,但实际上重复代码有很多,这些按分类展现的页面,唯一的不同就是根据不同类型分类的文章也不同。根据这一点,我们可以将分类写到一个视图函数里面,这样代码更为精简。

    url(r'^(?P<site>w+)/(?P<key>((tag)|(date)|(category)))/(?P<val>w+-*w*)/', views.filter)

      而分类的过程中主要涉及的就是ORM的操作,并且也没有十分难的数据表操作。

      文章页

      文章页的部分主要是点赞与评论部分,先说一下评论部分,评论可以做成缩进的多级评论,但是需要将数据库获得的数据进行数据结构改造,快速索引。

        msg_list = [
            {'id':1,'content':'写的太好了','parent_id':None},
            {'id':2,'content':'你说得对','parent_id':None},
            {'id':3,'content':'顶楼上','parent_id':None},
            {'id':4,'content':'你眼瞎吗','parent_id':1},
            {'id':5,'content':'我看是','parent_id':4},
            {'id':6,'content':'鸡毛','parent_id':2},
            {'id':7,'content':'你是没呀','parent_id':5},
            {'id':8,'content':'惺惺惜惺惺想寻','parent_id':3},
        ]
        msg_list_dict = {}
        for item in msg_list:
            item['child'] = []#每一行加一个空列表child,存放子数据
            msg_list_dict[item['id']] = item#每个行加一个索引的序列改造成[1;{},2:{}]
    
        # #### msg_list_dict用于查找,msg_list
        result = []
        for item in msg_list:
            pid = item['parent_id']
            if pid:#如果有父id
                msg_list_dict[pid]['child'].append(item)#加到刚才的child列表中
            else:
                result.append(item)#列表里都是第一级的评论
        # ########################### 打印 ###################
        from utils.comment import comment_tree
        comment_str = comment_tree(result)#自定义把所有的评论一级一级递归的拨开,解析成HTML格式
    多级评论
    def comment_tree(comment_list):
        """
    
        :param result: [ {id,:child:[xxx]},{}]
        :return:
        """
        comment_str = "<div class='comment'>"
        for row in comment_list:
            tpl = "<div class='content'>%s</div>" %(row['content'])
            comment_str += tpl
            if row['child']:
                #
                child_str = comment_tree(row['child'])
                comment_str += child_str
        comment_str += "</div>"
    
        return comment_str
    util.comment

      个人觉得也可以写成博客园的@的方式,@的回复可跨表取到。

    {% for re in reply %}
        <div style="background-color: #e0e0e0">{{ re.comment__create_time }}&nbsp [发言人]{{ re.comment__user__username }}</div>
        {% if re.comment__reply__user__username %}
        <p>@{{ re.comment__reply__user__username }}</p>
            {% else %}
            <p></p>
        {% endif %}
    <div style=" 100%;border-bottom: #00b3ee 1px solid ;margin-top: 5px">  {{ re.comment__content }} </div>
    
    {% endfor %}
    数据可以后端跨表取

      点赞要给赞绑定点击事件,定义1为赞,0为踩,

    onclick="updown(this,{{ content.nid }},1);//传给绑定事件触发的函数
            function updown(ths,nid,val){
                $.ajax({
                    url: '/updown.html',
                    data:{'val':val,'nid':nid,'csrfmiddlewaretoken':'{{ csrf_token }}'},
                    type: "POST",
                    dataType:'JSON',
                    success:function(arg){
                        if(arg.status){
                            // 点赞成功刷新页面
                            location.reload();
                        }else{
                            alert(arg.msg)
                        }
                    }
                })
            }
    绑定事件

      后台管理可以使用xadmin来做,当然也可以写一个后台管理,我这个后台管理暂时使用管理员管理界面。

      xadmin使用方法

      这两天把后台搭起来再把源码上传。我发现这个xadmin功能很强大啊,待我修习几日直接用它来做后台管理。

  • 相关阅读:
    解决:Android 8.0检测不到当前的activity
    flask学习(十三):过滤器
    打开相册上传图片
    完整的项目
    解决ScrollView滑动RecyclerView的卡顿
    RxJava
    CoordinatorLayout
    NestedScrollView,RecyclerView
    ViewPageIndicator
    RxJava的实现原理
  • 原文地址:https://www.cnblogs.com/Jeffding/p/8035312.html
Copyright © 2020-2023  润新知