• bbs技术总结


    bbs技术点总结

    js只要用到内置对象,直接用new生成就可以了

    1. 用户注册上传头像

    <div class="form-group">
                    <label for="myfile">头像
                        {% load static %}
                        <img id="myimg" src="{% static 'img/default.png' %}" alt="" width="80" style="margin-left: 20px"></label>
                    <input type="file" id="myfile" name="avater" style="display: none">
    </div>
    
    
    <script>
        //文本域变化事件
        $('#myfile').change(function () {
            //文件阅读器对象
            //1.先生成一个文件阅读器对象
            let myFileReadObj = new FileReader(); // 文件阅读器
            //2. 获取用户上传的头像文件
            let fileObj = $(this)[0].files[0]; //获取文件
            // 3.将文件对象交给文件阅读器读取
            myFileReadObj.readAsDataURL(fileObj) //异步操作,io操作,这行代码文件还没有读完,就已经开始执行下一句代码,在前端不显示
            //4.利用文件阅读器将文件展示到页面去 修改src属性
            //等待文件阅读器加载完之后,在执行
            myFileReadObj.onload = function(){  $('#myimg').attr('src',myFileReadObj.result)}
    
    
        })
    
    
    • 这里我们要用到文本与变化事件,利用文件阅读器对象,我们要先生成一个文件阅读器对象,
    • 获取用户上传的头像文件读取出来
    • 将文件对象交给文件阅读器读取出来文件
    • 利用文件阅读器将文件展示到页面上去,就要修改src属性,myFileReadObj.readAsDataURL(fileObj) $('#myimg').attr('src',myFileReadObj.result)这两部是一个异步操作,如果你在执上一代码的同时文件还没有读取出来,同时他还在执行下一句代码,这样会造成你上传之后在前端是不显示你上传的头像,空白区,我们要等文件加载完毕在执行下一局, myFileReadObj.onload
     $('#id_commit').click(function () {
            // 发送ajax请求     我们发送的数据中即包含普通的键值也包含文件
            let formDataObj = new FormData();
            // 1.添加普通的键值对
            {#console.log($('#myform').serializeArray())  // [{},{},{},{},{}]  只包含普通键值对#}
            $.each($('#myform').serializeArray(),function (index,obj) {
                {#console.log(index,obj)#}  // obj = {}
                formDataObj.append(obj.name,obj.value)
            });
            // 2.添加文件数据
            formDataObj.append('avatar',$('#myfile')[0].files[0]);
    
            // 3.发送ajax请求
            $.ajax({
                url:"",
                type:'post',
                data:formDataObj,
    
                // 需要指定两个关键性的参数
                contentType:false,
                processData:false,
    
                success:function (args) {
                    if (args.code==1000){
                        // 跳转到登陆页面
                        window.location.href = args.url
                    }els
                        // 如何将对应的错误提示展示到对应的input框下面
                        // forms组件渲染的标签的id值都是 id_字段名
                        $.each(args.msg,function (index,obj) {
                            {#console.log(index,obj)  //  username        ["用户名不能为空"]#}
                            let targetId = '#id_' + index;
                            $(targetId).next().text(obj[0]).parent().addClass('has-error')
                        })
                    }
                }
            })
        })
    
    
    def register(request):
        # 产生一个空对象
        register_form = myforms.MyRegForm()
        if request.method == 'POST':
            back_dic = {'code':'', 'msg': ''}
            # 校验数据是否合法
            register_form = myforms.MyRegForm(request.POST)
            # 判断数据是否合法
            if register_form.is_valid():
                clean_data = register_form.cleaned_data # 将校验通过的数据字典赋值给一个变量
                # 将字典里面吗的confirm_password键值对删除
                clean_data.pop('confirm_password')
                # 用户头像
                file_obj = request.FILES.get('avatar')
                """
                针对用户头像一定要判断是否传之,不能直接添加到字典里面去
                """
                if file_obj:
                    clean_data['avatar'] = file_obj
                # 直接操作数据库保存到字典里面
                models.UserInfo.objects.create_user(**clean_data) # 将键值对**打散传到数据库
                # 判断正确跳转到登录页面
                back_dic['url'] = '/login/'
            else:
                back_dic['code'] = 2000
                back_dic['msg'] = register_form.errors
                return JsonResponse(back_dic)
    
        return render(request, 'register.html', locals())
    
    

    前端:

    • 头像的功能完成之后,剩下的就是将利用ajax将文件发送到后端,前端要先利用内置对象获取数据,添加不同键值对,我们可以利用serializeArray()拿到他所有的键值对,利用each循环拿到每一个对象的键值对

    • 利用append添加文件数据,发送ajax请求,这里面我们要指定两个关键参数contentType: false,processData: false,

    • 如果后端保存数据成功,就跳转到后端写好传过来的指定页面,如果校验数据失败就在input框下面展现出对应的错误

    • 当我们看见错误的信息之后,如果把鼠标放上去,指定的错误就会消失,给所有的input框绑定获取焦点事件,将input下面的span标签和input外面的div标签修改内容和属性

    后端

    • 校验数据输入的是否合法,将合法的数据赋值给一个变量,赋值给一个变量方便我们删除确认密码的键值对,因为我们在写models的时候没有这个字段,针对用户头像一定要判断是否传之,不能直接添加到字典里面去,在models里面我们给他默认了一个头像
    • 操作数据库保存数据,定义字典将信息返回给ajax,ajax都到在页面展示对应的页面信息

    2. 生成登录验证码

    如何生成一个验证码,然后点击它就可以刷新呢。

    利用pip3 install pillow ,这个是图片相关的模块。

    导入模块:from PIL import Image, ImageDraw, ImageFont

    • Image 生成图片
    • ImageDraw 能够在图片上添加东西
    • ImageFont 控制字体的样式

    推导1

    直接获取后端生成的图片二进制数据发送给前端

    with open(r'static/img/111.jpg','rb') as f:
          data = f.read()
    return HttpResponse(data)
    

    推导2

    利用pillow模块动态产生图片

    img_obj = Image.new('RGB',(430,35),'green')
    img_obj = Image.new('RGB',(430,35),get_random())
    
    # 将图片对象 保存起来
    with open('xxx.png','wb') as f:
        img_obj.save(f,'png')
    # 再将文件对象读取出来
    with open('xxx.png','rb') as f:
        data = f.read()
    return HttpResponse(data)
    

    推导3
    我们可以看到利用文件存储繁琐而且IO操作效率低,我们可以借助内存管理模块

    from io import BytesIO,StringIO
    img_obj = Image.new('RGB', (430, 35), get_random())
    io_obj = BytesIO()
    img_obj.save(io_obj,'png') # 生成一个内存管理器对象
    return HttpResponse(io_obj.getvalue()) # 从内存管理器中读取二进制的图片数据返回给前端
    

    推导4

    写成图片验证码

    img_obj = Image.new('RGB', (430, 35), get_random())
    img_draw = ImageDraw.Draw(img_obj)  # 产生一个画笔对象
    img_font = ImageFont.truetype('static/font/222.ttf',30)  # 字体样式 大小
    

    最终成型

    import random
    def get_random():
        return random.randint(0,255),random.randint(0,255),random.randint(0,255)
    def get_code(request):
        img_obj = Image.new('RGB', (430, 35), get_random()) # 注意:这里的430,35要和前端的一致
        img_draw = ImageDraw.Draw(img_obj)
        img_font = ImageFont.truetype('static/font/222.ttf',30)
        code = ''
        for i in range(5):
            random_upper = chr(random.randint(65,90))
            random_lower = chr(random.randint(97,122))
            random_int = str(random.randint(0,9))
            tmp = random.choice([random_lower,random_upper,random_int])
            img_draw.text((i*60+60,-2),tmp,get_random(),img_font)
            code += tmp
        print(code)
        request.session['code'] = code
        io_obj = BytesIO()
        img_obj.save(io_obj,'png')
        return HttpResponse(io_obj.getvalue())
    
    
    • 实现低级验证码图片刷新验证码,这样设置后每次点击图片相当于超后端发送一次get请求获取一个新的验证码图片
    {# 点击验证码图片刷新验证码   #}
    $('#code_img').click(function () {
        $(this).attr('src', '{% url "get_code" %}?')	// src='/get_code/?'url后面加?的操作
    });
    

    3. admin的使用

    以前写图书的展示列表,我们给它添加增删改查的功能,特别的麻烦,现在有很多张表的展示,我们不可能慢慢的写这些功能,django给我们提供了admin后台管理,我们可以利用admin实现增删改查,添加数据。

    首先要创建超级用户,只有超级用户才能够操作admin的这些功能。

    刚开始登录进去的时候只用一张用户表,我们想要添加其他的表,必须添加注册

    from django.contrib import admin
    from app01 import models
    # Register your models here.admin
    admin.site.register(models.UserInfo)
    admin.site.register(models.Blog)
    admin.site.register(models.Article)
    admin.site.register(models.Atricle2Tag)
    admin.site.register(models.UpAndDown)
    admin.site.register(models.Category)
    admin.site.register(models.Tag)
    admin.site.register(models.Comment)
    

    这样我们就可以使用这些表,但是他们是英文的,我们可以在model.py里面改变他们的名字

    class Meta:
         verbose_name_plural='用户表'
    
    

    我们进去操作添加添加一条数据显示的是对象,

    class UserInfo(AbstractUser):
        ...
    def __str__(self):
            return self.username	
    

    避免造成不便语义不明,打印出来

    在model里面的字段添加verbose_name='创建时间'会在admin后天帮助我们把字段的名称改成中文

    4. 制作站点

    打开博客园,在博客园的url后面输入别人的站点名称就可以进到他们的站点,如https://home.cnblogs.com/the3times在后面输入the3times就会进到这个人的博客园的主页,这个是url来配置的,利用urlre匹配规则就可以实现

    url(r'^(?P<username>w+)/$',views.site,name='site'),
    

    5. media的配置

    网站用户使用的静态文件默认放在static里面,用户上传的静态文件应该单独放在一个文件夹下,可以使用media配置,该配置可以让用户上传的所有的文件 固定存放在指定的文件夹下

    settings.py

    # 配置用户上传的文件
    MEDIA_ROOT = os.path.jion(BASE_DIR,'media')
    

    我们配置后,我们上传文件会自动生成media文件,比如我们注册上传的头像会到这个地方,你存的路径在数据库中也不会改变,

    假如你在model.py里面存的路径是avatar/111.pngmedia里面就会多出一个存放头像的avatar/111.png这个就是你存放的头像路径。

    class UserInfo(AbstractUser):
        avatar = models.FileField(upload_to='avatar/', default='avatar/default.png', verbose_name='用户头像')
    

    在前端点击这个头像能够查看到这个头像,需要我们在后端开设一个指定的文件夹资源

    在url.py配置参数

    from django.views.static import serve
    from bbs import settings
    
    url(r'^media/(?P<path>.*)',serve,{'document_root':settings.MEDIA_ROOT}),
    

    如果你还想暴漏更多资源,在settings修改文件夹名,想暴露源码也可以,问题就大了

    最后在html配置,渲染后的路径/media/avatar/default.png/

    <img class="media-object" src="/media/{{ article_obj.blog.userinfo.avatar }}" alt="..." width="40">
    

    这样就可以看到别人的头像

    6. 站点左边栏展示

    左侧边栏展示如何展示的,要清楚orm的查询

     # 1.查询当前用户所有分类及分类下的文章数
    category_list=models.Category.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name','count_num','pk')
        # 2.查询当前用户所有标签及标签的文章数
    tag_list=models.Tag.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name','count_num','pk') # <QuerySet [('tank的标签一', 1), ('tank的标签二', 1), ('tank的标签三', 2)]>
    # 3.按照日期归档
    date_list=models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(count_num=Count('pk')).values_list('month','count_num')
    

    这里的日期归档我们按照年月归档,但是数据库里面有年月日,我们如何把日去掉官方提供了一个方法

    from django.db.models.functions import TruncMonth
    			Sales.objects
    			.annotate(month=TruncMonth('timestamp'))  # Truncate to month and add to select list
    			.values('month')  # Group By month
    			.annotate(c=Count('id'))  # Select the count of the grouping
    			.values('month', 'c')  # (might be redundant, haven't tested) select month and count
    

    上面的日期归档就是按照这个模式搬出来的

    7. 侧边栏筛选功能

    • 显示分类做好之后,点击文章标签、分类、日期归档统计的链接后,显示该条件下的所有文章列表

    博客园url设计案例

    https://www.cnblogs.com/jason/tag/Python/  		  标签
    https://www.cnblogs.com/jason/category/850028.html 分类
    https://www.cnblogs.com/jason/archive/2016/10.html 日期
    

    规律:前面的还是站点名,后面是每个分类后的url

    按照这个模式设计我们的url

    • 个人站点文章是我们筛选后该站点用户所有的文章,这些标签,分类都是在在上面筛选过后加条件在筛选一次
    • 设计url,处理该url视图函数进一步过滤符合条件的文章
    • 为了显示在一个页面,就在站点的视图里面进行筛选,这样就不要开辟页面
    • 按照上面的url设计在每个分类的后面还有参数,点击不同的文章分类后面出现不同的url,利用每个文章分类的主键值为文章列表
    # url(r'^(?P<username>w+)/category/(d+)/',views.site),
    # url(r'^(?P<username>w+)/tag/(d+)/',views.site),
    # url(r'^(?P<username>w+)/archive/(w+)/',views.site),
    
    

    前面是匹配站点的名称,后面跟不同的分类,最后匹配主键值

    三句和成一句

    url(r'^(?P<username>w+)/(?P<condition>category|tag|archive/)(?P<param>.*)/',views.site),
    

    后端业务逻辑

    def site(request,username,**kwargs):
        # 先校验当前用户名的站点是否存在
        user_obj = models.UserInfo.objects.filter(username=username).first()
        # 不存在404页面
        if not user_obj:
            return render(request,'error.html')
        # 先到个人站点
        blog = user_obj.blog
        # 查询当前个人站点下所有的文章
        article_list = models.Article.objects.filter(blog=blog)
        if kwargs:
            # print(kwargs)  # {'condition': 'tag', 'param': '1'}
            condition = kwargs.get('condition')
            param = kwargs.get('param')
            # 判断用户到底想按照哪个条件筛选数据
            if condition == 'category':
                article_list = article_list.filter(category_id=param)
            elif condition == 'tag':
                article_list = article_list.filter(tags__id=param)
            else:
                year, month = param.split('-')
                article_list = article_list.filter(create_time__year=year, create_time__month=month)
    
        # 1.查询当前用户所有分类及分类下的文章数
        category_list = models.Category.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name','count_num','pk')
        # 2.查询当前用户所有标签及标签的文章数
        tag_list = models.Tag.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name','count_num','pk')
        # 3.按照日期归档
        date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(count_num=Count('pk')).values_list('month','count_num')
        return render(request,'site.html',locals())
    
    
    
    

    解析:**kwargs可能接收额外的参数,先写上去,判断kwargs是否有值,有值再进行操作,``` {'condition': 'tag', 'param': '1'}我们点击标签,后面出现我们url设计好的tag,1是标签1的主键值,拿到之后,判断用户像按照哪个条件筛选的,article_list = article_list.filter(tags__id=param)`这个在宅筛选当前站点用户所有的文章之后在进行一次条件筛选

    前端

    	
    <!-- 标签 -->
    {% for tag in tag_list %}
       <p><a href="/{{ username }}/tag/{{ tag.pk }}/">{{ tag.name }}({{tag.c}})</a></p>
    {% endfor %}
     
    <!-- 分类 -->
    {% for category in category_list %}
       <p><a href="/{{username}}/category/{{ category.pk }}">{{ category.name }}({{category.c}})</a></p>
    {% endfor %}
     
    <!-- 归档 -->
    {% for archive in archive_list %}
       <p><a href="/{{ username }}/archive/{{ archive.month|date:'Y-m' }}/">{{ archive.month|date:'Y年m月' }}({{ archive.c }})</a></p>
    {% endfor %}
    

    /{{username}}/category/{{ category.pk }}把这些url补全,这里的主键值,我们在获取文章查询当前用户所有分类及分类的时候写上去,这样我们就可以在前端拿到主键值values_list('name','count_num','pk')

    8. inclusion_tag的制作

    我们制作完站点页面之后制作文章详情页,我们可以在站点详情页上面显示文章,但是在一个页面上显示,显示的代码在后端特别的冗余,我们从新开辟一个文章详情页

    • 我们建立文章详情页之后,左侧的侧边栏就不会显示,因为我们需要站点的一些数据
    • 该侧边栏在许多页面显示。
    • 直接拷贝代码冗余

    将侧边栏制作成inclusion_tag

    • 在应用下创建一个名字必须叫templatetags文件夹

    • 在该文件夹内创建一个任意名字的py文件

    • 在该py文件内固定先写两汉代码

      from django import template
      register = template.Library()
      
      

    后端封装代码

    from django import template
    from django.db.models import Count
    from django.db.models.functions import TruncMonth
    from app01 import models
    
    register = template.Library()
    
    
    # 自定义inclusion_tag
    @register.inclusion_tag('left_meun.html')
    def left_meun(username):
        user_obj = models.UserInfo.objects.filter(username=username).first()
        # 1.查询当前用户所有分类及分类下的文章数
        blog = user_obj.blog
        category_list = models.Category.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list(
            'name', 'count_num', 'pk')
        # 2.查询当前用户所有标签及标签的文章数
        tag_list = models.Tag.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name',
                                                                                                             'count_num',  
        # 3.按照日期归档
        date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values(
            'month').annotate(count_num=Count('pk')).values_list('month', 'count_num')
        return locals()
    
    

    前端建立一个letf_meun.html把左侧边栏的代码放在这里

    <div class="panel panel-success">...</div>
    <div class="panel panel-primary">...</div>
    <div class="panel panel-danger">... </div>
    

    在需要侧边栏的地方写如在base.py

    {% load mytag %}
    {% left_meun username %}
    

    这样在每个页面需要左侧边栏的地方都可以显示了。这样就可以避免大量的冗余代码,也可以少些

    根评论子评论

    • 允许根评论和子评论(评论评论的评论),可以评论自己的文章。
    • 用户未登录不能评论且隐藏评论输入框(request.user.is_authenticated)。
    • 评论内容有两种渲染方式:
      • 刷新页面时,从后端取出评论数据,前端循环展示
      • 评论后DOM操作临时将评论内容渲染到评论列表,使用的是js的模版字符串语法。
    • 根评论朝后端提交的数据:文章主键、评论内容、
    • 子评论朝后端提交的数据:文章主键、评论内容、父评论主键
    • 获取父评论的方式:给回复按钮绑定一个自定义属性,属性值为父评论主键
    • 区分子评论和根评论关键在于是否有父评论,这里面为了统一,提交根评论时也携带父评论(只不过值为null,因为数据库该字段支持为空)。

    后端

    • 需要登录后才能评论,所以使用一个登录校验装饰器
    • 后端逻辑比较简单,接收评论内容、文章主键、父评论主键
    • 评论内容为空值,响应提示信息
    • 使用事物同时更新文章表和评论表。

    代码主要都在前端

        
        
        
        //设置一个全局的parentId字段
        let parentId =null
        // 用户发表评论按钮发送ajax请求
        $('#id_submit').click(function () {
            //先拿到用户评论的内容
            let conTent = $('#id_comment').val();
            //因为子评论存的时候不应该有@人名,所以我们要手动去除@username
            if (parentId){
                let indexNum = conTent.indexOf('
    ') + 1;//找到
    对应的索引。然后切片,但是骨头不顾尾要+1
                conTent = conTent.slice(indexNum)//将indexNum之前的所有数据清楚,只保留后面的部分
            }
            $.ajax({
                url:'/comment/',
                type:'post',
                data: {
                    'article_id':'{{ article_obj.pk }}',
                    'content':conTent,
                    // 如果parentId没有值,就是null,后面数据库可以为null没任何问题
                    'parent_id':parentId,
                    'csrfmiddlewaretoken':'{{ csrf_token }}',
                },
                success:function (args) {
                        if (args.code==1000)
                        {
                            $('#error').text(args.msg)
                            //评论框里面的内容清空
                            $('id_comment').val('');
                            //临时渲染
                            let userName='{{ request.user.username }}';
                            let temp = `
                            <li class="list-group-item">
    
                                <span>${userName}</span>
                                <span><a href="#" class="pull-right">回复</a></span>
                                <div>
                                    ${conTent}
                                </div>
                                </li>
                            `
                            //添加到ul里面
                            $('.list-group').append(temp)
                            //清空全局的parentId
                            parentId = null;
    
    
    
                        }
    
    
                }
            })
    
        })
        //  给回复按钮绑定点击事件
        $('.reply').click(function () {
            //需要评论对应的评论人姓名,还需要评论的主键值
            //获取用户名和主键值,自定义属性
            let commentUsername = $(this).attr('username');
            //直接修改全局
            parentId =$(this).attr('comment_id');
            //拼接信息塞给评论框
            $('#id_comment').val('@'+ commentUsername + '
    ').focus()
    
        })
    

    解析步骤

    • 用户放松ajax请求

    • 拿到用户评论内容

    • 临时渲染评论框,但是只显示一个人的名字

    • 这个时候把临时渲染的评论框加到ul里面,根评论完成

    • 子评论给回复按钮绑定点击事件

    • 这个时候我们要拿到对应的评论人的用户名,还有评论的id主键值

    • 如何获取用户名和主键值,给他们自定义属性

      <span><a class="pull-right reply" username="{{ comment.user.username }}" comment_id="{{ comment.pk }}">回复</a></span>

    • 拼接信息给评论框

    • 发送信息如何发送呢,设置一个全局的子评论字段,子评论的内容直接修改全局

    • 但是我们发送的时候parentId没有值,就是null,正好数据库的parent_id字段可以为null没有任何问题

    • 评论时存储的有@用户名,所以我们要手动去除@username

    • 找到 ,因为我们在拼接信息的时候 前面就是@username用户名,我们截取到 ,切片固头不顾尾+1let indexNum = conTent.indexOf(' ') + 1

    • 将indexNum之前的所有数据清除,保留后面的部分

    • 这个时候我们在写根评论的时候还有子评论的主键值,要清除全局的parentId

    后端

    # 开启事务操作两种表
    from django.db import transaction
    
    
    def comment(request):
        # 自己也能评论
        if request.is_ajax():
            if request.method == 'POST':
                back_dic = {'code': 1000, 'msg': ''}
                if request.user.is_authenticated:
                    article_id = request.POST.get('article_id')
                    content = request.POST.get('content')
                    parent_id = request.POST.get('parent_id')
                    # 直接操作评论表存数据,两张表
                    with transaction.atomic():
                        models.Article. objects.filter(pk=article_id).update(comment_num=F('comment_num') + 1)
                        models.Comment.objects.create(parent_id=parent_id,user=request.user, article_id=article_id, content=content)
    
                    back_dic['msg'] = '评论成功'
                else:
                    back_dic['code'] = 10001
                    back_dic['msg'] = '用户未登录'
                return JsonResponse(back_dic)
    
    
    
    • 我们需要操作多张表的时候可以开启事务

    • 因为子评论可以为空,直接接收,可以省很多事情

      beautifulsuop4

    • 编辑别的博客复制在富文本编辑器,会有html页面代码,截取文本内容代码一起截取

    • xss攻击,如在里面使用script代码写内容,保存不显示

    • 筛选标签去除html代码

    content = request.POST.get('content')
    
    soup = BeautifulSoup(content, 'html.parser') // 把要筛选的内容放进去
    tags = soup.find_all() // 找到所有标签
    for tag in tags:
        if tag.name == 'script': //
        找到script标签
        tag.decompose() // 删除标签
    desc = soup.text[0:150] // 截取文章描述
    article_obj = models.Article.objects.create(
        title=title,
        content=str(soup),
        desc=desc,
        category_id=category_id,
        blog=request.user.blog
    )
    
    

    富文本编辑器遗留问题

    • 在上传图片会出现下载问题
    • 403:forbidden
    • 自定义富文本编辑器

    前端

    添加参数:uploadJson:'/路径/'
    自定义参数:extraFileUploadParams : {}
    添加:  csrfmiddlewaretoken:'{{ csrf_token }}'
                    
    

    后端:

    • 获取上传的图片文件的键
    • 拼接上传路径
    • 返回media开放的资源路径
    import os
    
    from bbs import settings
    def upload_img(request):
        back_dic = {'error':0,'url':''}
        if request.method == 'POST':
            file_obj = request.FILES.get('imgFile')//这里不知道键是多少,用request.FILES打印看一下
            # 手动拼接
            file_path = os.path.join(settings.BASE_DIR,'media','article_img')
            if not os.path.isdir(file_path):
                os.mkdir(file_path)
            file_img = os.path.join(file_path,file_obj.name)
            with open(file_img,'wb') as f:
                for i in file_obj:
                    f.write(i)
            back_dic['url'] = '/media/article_img/%s'%file_obj.name
        return JsonResponse(back_dic)
    
    
    • 获取键,手动拼接路径,判断是否存在文件夹,不存在创建
    • with open保存文件
    • 开放资源路径

    修改头像问题

    修改头像出现了csrf-403-forbidden问题

         $('#id_set_avatar').click(function () {
    
            let formDataObj = new FormData();		// 将普通数据和文件添加到该对象中
            formDataObj.append('avatar', $('#myfile')[0].files[0]);
            formDataObJ.append('csrfmiddelwaretoken':'{{csrf_token}}')
    
             $.ajax({
                 url: '{% url 'set_avatar' %}',
    
                 type: 'post',
                 data:formDataObj,
    
    
                 contentType: false,  			// 必须的
                 processData: false,
                 // 必须的
                 success: function (args) {
                     if (args.code===1000){
                         window.location.href = args.url
                     }
                 },
             })
         })
    

    这个是正确的书写,我在加中间件的csrf的时候

         $('#id_set_avatar').click(function () {
    
            let formDataObj = new FormData();		// 将普通数据和文件添加到该对象中
             $.ajax({
                 url: '{% url 'set_avatar' %}',
    
                 type: 'post',
                 data:{'avatar', $('#myfile')[0].files[0],
                      	'csrfmiddelwaretoken':'{{csrf_token}}'}// 因为他的数据本身就是一个对象,这样写对象套对象访问不到这个数据
                 contentType: false,  			// 必须的
                 processData: false,
                 // 必须的
                 success: function (args) {
                     if (args.code===1000){
                         window.location.href = args.url
                     }
                 },
             })
         })
    
  • 相关阅读:
    关于 L3 缓存行 cacheLIne 的研究!还是对程序有举足轻重的作用!
    所谓的科学,根本就没有解决问题的根本。如框架,框架再好,也需要内容。
    编译器开发,手动把汇编转 机器码
    测试HTML
    神奇的经历,顶上去保证上帝保佑你!!!!!
    ECS框架研究 ,unity 以及缓存行的研究
    AVX 指令详解 ,还有SSE指令
    关于 Visual Studio 2017 ,或2019 ,Installer 没检测到已安装的程序.以及C++ 创建项目失败
    TCP 协议 精解
    opc 相关组件
  • 原文地址:https://www.cnblogs.com/zc110/p/13110307.html
Copyright © 2020-2023  润新知