django之微应用html前后端传参

一、BootStrap的安装与使用
二、前端与后端的交互
1、值的互传
  • 前端朝后端发送请求的方式:

    • 浏览器地址栏直接输入url回车(GET请求)
    • a标签href属性(GET请求)
    • form表单(GET、POST请求)
    • ajax(GET、POST请求)
  • 前端html的value属性是给传送给后端的默认值,前端html的name属性是传送给后端的字典键
    在这里插入图片描述
    在这里插入图片描述

  • 前端通过form表单、input标签文本输入,input标签提交发送post请求, 后端通过request.POST来获取数据
    在这里插入图片描述
    在这里插入图片描述

  • 前端通过ajax传送数据,可通过id属性或者class属性进行点击操作,或者val属性取值或者获取值,通过data键值发送给后端request.POST来获取数据
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述

  • 后端传给前端可通过django的三板斧即( render, redirect, HttpResponse),前端即可通过双花括号取值{{}},以及条件取值{%%}
    在这里插入图片描述
    在这里插入图片描述

2、Ajax
  • 局部提交,异步刷新,当你在利用ajax进行前后端交互的时候,后端无论返回什么都只会被回调函数接收,而不再影响这个浏览器页面了
  • 小例子, ajax基本语法
    页面上有三个input框
    	在前两个狂中输入数字,点击按钮,朝后端发送ajax请求,
    	后端计算出结果,再返回给前端动态展示到第三个input框中
    	整个过程页面不准有刷新,也不能再前端计算
    
    // 朝后端发送ajax请求
    $.ajax({
         // 1、指定超哪个后端发送ajax请求,不写就是超当前地址提交
         url:'',
         // 2、请求方式,不指定默认就是get,都是小写
         type:'POST',
         // 3、数据 data:{'username':'jason', 'password':123},
         data:{'i1':$('#d1').val(), 'i2':$('#d2').val()},
         // 4、回调函数, 当后端给你返回结果的时候会自动触发,args接受后端的返回结果
         success:function(args){
             //alert(args)  // 通过DOM操作动态渲染到第三个input里面
             $('#d3').val(args)
         }
    
     })
    
  • 针对后端如果是用HttpResponse返回的数据,回调函数不会自动帮你反序列化,如果后端直接用的是JsonRespose返回的数据,回调函数回自动帮你反序列化。HttpRespose解决方式:自己再前端利用JSON.parse()或者dataType:‘JSON’
3、前后端传输数据的编码格式(contentType)
  • 前后端传输数据的编码格式:urlencoded, formdata, json;前后端传输数据的时候一定要确保编码格式跟数据真正的格式是一致的

  • form表单

    • 默认的数据编码格式是urlencoded,数据格式如:username=23&password=2,django后端针对符合urlencoded编码格式的数据都会自动帮你解析封装到request.POST中
    • 如果你把前端编码格式改成formdata(enctype=“multipart/form-data”),那么针对普通的键值对还是解析到request.POST中,而将文件解析到request.FILES中
    • form表单没有办法发送json格式的数据的
      在这里插入图片描述
  • ajax

    • 默认的数据编码格式是urlencoded,数据格式如:username=23&password=2,django后端针对符合urlencoded编码格式的数据都会自动帮你解析封装到request.POST中
    • json格式:{“username”:“jason”,“age”:32},在request.POST里面找不到,django针对json格式的数据不会做任何处理,在request.body里
      在这里插入图片描述在这里插入图片描述
4、ajax发送文件数据
  • ajax发送文件需借助js内置对象FormData,需要指定两个关键参数contentType,processData
  • django后端能够自动识别到FormData对象并且能够将内部的普通键值自动解析并封装到request.POST中,文件数据自动解析并封装到request.FILES中
    在这里插入图片描述
5、django自带的序列化组件
from django.http import JsonResponse
from django.core import serializers
def ab_ser(request):
    user_list = [{"name": 1}, {"name": 2}]
    return JsonResponse(user_list, safe=False)
    # res = serializers.serialize('json', user_queryset)
    # return HttpResponse(res)

6、ajax结合sweetalert实现二次确认
{#<a href="{% url 'book_delete' book_obj.id %}" class="btn btn-danger btn-xs">删除</a>#}
<button class="btn btn-danger btn-xs del" delete_id="{{ book_obj.id }}">删除</button>

<script>
    $('.del').on('click', function(){
        {#alert($(this).attr('delete_id'))#}
        // 先将当前标签对象存储起来
        let currentBtn = $(this);
        // 二次确认弹框
        swal({
          title: "Are you sure?",
          text: "You will not be able to recover this imaginary file!",
          type: "warning",
          showCancelButton: true,
          confirmButtonClass: "btn-danger",
          confirmButtonText: "Yes, delete it!",
          cancelButtonText: "No, cancel plx!",
          closeOnConfirm: false,
          closeOnCancel: false,
          showLoaderOnConfirm: true
        },
        function(isConfirm) {
          if (isConfirm) {
              // 朝后端发送ajax请求删除数据之后,再弹下面的提示框
              $.ajax({
                  {#url:'/delete/user/' + currentBtn.attr('delete_id'),#}
                  url:'/delete/user/',
                  type:'POST',
                  data: {'delete_id': currentBtn.attr('delete_id')},
                  success: function (args){  // args = {'code':'', 'msg':''}
                      // args为后端return的JsonResponse(back_dic)
                      if(args.code === 1000){
                          swal("Deleted!", args.msg, "success");
                          currentBtn.parent().parent().remove()  // 利用DOM操作,移除当前标签, 动态刷新
                      }else{
                          swal("no Deleted!", '未知错误', "info")
                      }
                  }

              })

          } else {
            swal("Cancelled", "Your imaginary file is safe :)", "error");
          }
        });
    })
</script>
7、批量插入
  • 当你批量插入数据的时候,使用orm给你提供的buLk_create能够大大的减少操作时间
def ab_pl(request):
    # 先插入一万条数据
    # 当你批量插入数据的时候,使用orm给你提供的buLk_create能够大大的减少操作时间
    book_list = []
    for i in range(10000):
        book_obj = models.Book(title=i)
        book_list.append(book_obj)
    models.Book.objects.bulk_create(book_list)
    # 再将所有数据查询并展示到前端页面
    return render(request, 'ab_pl.html', locals())

8、分页操作
  • 自定义分页器代码基于bootstrap样式,存在utils文件夹下

  • 后端views.py
    在这里插入图片描述

  • 前端ab_pl.html

    {% extends 'home.html' %}
    
    {% block content %}
        {% for j in page_queryset %}
            <p> {{ j}}</p>
            <nav aria-label="Page navigation"></nav>
        {% endfor %}
    
        {{ page_obj.page_html|safe }}   {#    // 因为是后端传来拼接好的html代码,标记为safe#}
    {% endblock %}
    
  • 自定义分页器封装代码:

    class Pagination(object):
        def __init__(self, current_page, all_count, per_page_num=2, pager_count=11):
            """
            封装分页相关数据
            :param current_page: 当前页
            :param all_count:    数据库中的数据总条数
            :param per_page_num: 每页显示的数据条数
            :param pager_count:  最多显示的页码个数
    
            用法:
            queryset = model.objects.all()
            page_obj = Pagination(current_page,all_count)
            page_data = queryset[page_obj.start:page_obj.end]
            获取数据用page_data而不再使用原始的queryset
            获取前端分页样式用page_obj.page_html
            """
            try:
                current_page = int(current_page)
            except Exception as e:
                current_page = 1
    
            if current_page < 1:
                current_page = 1
    
            self.current_page = current_page
    
            self.all_count = all_count
            self.per_page_num = per_page_num
    
            # 总页码
            all_pager, tmp = divmod(all_count, per_page_num)
            if tmp:
                all_pager += 1
            self.all_pager = all_pager
    
            self.pager_count = pager_count
            self.pager_count_half = int((pager_count - 1) / 2)
    
        @property
        def start(self):
            return (self.current_page - 1) * self.per_page_num
    
        @property
        def end(self):
            return self.current_page * self.per_page_num
    
        def page_html(self):
            # 如果总页码 < 11个:
            if self.all_pager <= self.pager_count:
                pager_start = 1
                pager_end = self.all_pager + 1
            # 总页码  > 11
            else:
                # 当前页如果<=页面上最多显示11/2个页码
                if self.current_page <= self.pager_count_half:
                    pager_start = 1
                    pager_end = self.pager_count + 1
    
                # 当前页大于5
                else:
                    # 页码翻到最后
                    if (self.current_page + self.pager_count_half) > self.all_pager:
                        pager_end = self.all_pager + 1
                        pager_start = self.all_pager - self.pager_count + 1
                    else:
                        pager_start = self.current_page - self.pager_count_half
                        pager_end = self.current_page + self.pager_count_half + 1
    
            page_html_list = list()
            # 添加前面的nav和ul标签
            page_html_list.append('''
                        <nav aria-label='Page navigation>'
                        <ul class='pagination'>
                    ''')
            first_page = '<li><a href="?page=%s">首页</a></li>' % (1)
            page_html_list.append(first_page)
    
            if self.current_page <= 1:
                prev_page = '<li class="disabled"><a href="#">上一页</a></li>'
            else:
                prev_page = '<li><a href="?page=%s">上一页</a></li>' % (self.current_page - 1,)
    
            page_html_list.append(prev_page)
    
            for i in range(pager_start, pager_end):
                if i == self.current_page:
                    temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i,)
                else:
                    temp = '<li><a href="?page=%s">%s</a></li>' % (i, i,)
                page_html_list.append(temp)
    
            if self.current_page >= self.all_pager:
                next_page = '<li class="disabled"><a href="#">下一页</a></li>'
            else:
                next_page = '<li><a href="?page=%s">下一页</a></li>' % (self.current_page + 1,)
            page_html_list.append(next_page)
    
            last_page = '<li><a href="?page=%s">尾页</a></li>' % (self.all_pager,)
            page_html_list.append(last_page)
            # 尾部添加标签
            page_html_list.append('''
                                               </nav>
                                               </ul>
                                           ''')
            return ''.join(page_html_list)
    
    
    
9、forms组件
  • 能够渲染html代码,校验数据,展示提示信息,可多传但不能少传;
  • 数据校验前端可有可无,但是后端必须要有,因为前端的校验是弱不经风的,你可以直接修改或者利用爬虫程序绕过前端页面直接朝后端提交数据
def ab_form(request):
    from django import forms
    class MyForm(forms.Form):
        # username最小3位最大8位
        username = forms.CharField(
            min_length=3, max_length=8, label='用户名',
            error_messages={
                'min_length': '用户名最少3位', 'max_length': '用户名最大8位', 'required': '用户名不能为空'
            },
            initial='shirmay',
            required=False,
            widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
        )
        password = forms.CharField(
            min_length=3, max_length=8, label='密码',
            widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
        )
        confirm_password = forms.CharField(
            min_length=3, max_length=8, label='确认密码',
            error_messages={
                'min_length': '确认密码最少3位', 'max_length': '确认密码最大8位', 'required': '确认密码不能为空'
            },
            widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
        )
        # email字段必须符合邮箱格式
        email = forms.EmailField(
            label='邮箱', error_messages={'invalid': '邮箱格式不正确', 'required': '用户名不能为空'},
            widget=forms.widgets.EmailInput(attrs={'class': 'form-control'})
        )

        # 局部钩子
        def clean_username(self):
            # 获取到用户名
            username = self.cleaned_data.get('username')
            # 提示前端展示错误信息
            if '666' in username:
                self.add_error('username', '不能有666')
            # 将钩子函数钩出来数据再放回去
            return username

        # 全局钩子
        def clean(self):
            password = self.cleaned_data.get("password")
            confirm_password = self.cleaned_data.get("confirm_password")
            if not confirm_password == password:
                self.add_error('confirm_password', '两次密码不一致')
            # 将钩子函数钩去出来数据再放回去
            return self.cleaned_data

    # form_obj = MyForm({'username':'dad', 'password':'123', 'email':'uerio'})  # 将带校验的数据组织成字典的形式传入即可
    # form_obj.is_valid()  # 检测数据是否合法,注意该方法只有在所有数据全部合法的情况下才返回True
    # print(form_obj.cleaned_data)    # 查看所有校验通过的数据
    # print(form_obj.errors)    # 查看所有不符合校验规则以及不符合的原因

    # 先产生一个空对象
    form_obj = MyForm()
    if request.method == 'POST':
        form_obj = MyForm(request.POST)
        if form_obj.is_valid():
            return HttpResponse('ok')
    # 直接将空对象传递给html页面
    return render(request, 'ab_form.html', locals())
  • 渲染标签, label属性默认展示的是类中定义字段首字母大写的形式,可修改;
  • forms组件当你的数据不合法的情况下,会保存你上次的数据,让你基于之前的结果进行修改,更加的人性化
    {% extends 'home.html' %}
    
    {% block content %}
        <form action="" method="post" novalidate>
    {#        <p>第一种渲染方式,代码书写极少,封装程度太高,不便于后续的扩展,一般情况下只在本地测试使用 </p>#}
    {#        {{ form_obj.as_p }}#}
    {#        {{ form_obj.as_ul }}#}
    {#        {{ form_obj.as_table }}#}
    {#        <p>第二种渲染方式,可扩展性很强, 但是需要书写的代码太多</p>#}
    {#        {{ form_obj.username.label }}:{{ form_obj.username }}#}
            <p>第三种渲染方式推荐使用, 代码书写简单,并且扩展性也高</p>
            {% for form in form_obj %}
                <p>
                {{ form.label }}:{{ form }}
                <span style="color:red"> {{ form.errors.0 }}</span>
                </p>
            {% endfor %}
            <input type="submit" class="btn btn-info">
        </form>
    {% endblock %}
    
  • 钩子函数(HOOK):在特定的节点自动触发响应操作,勾子函数在forms组件中就类似于第二道关卡,能够让我们自定义校验规则,在forms组件中有两类钩子:
    • 局部钩子:当你需要给单个字段增加校验规则的时候可以使用
    • 全局钩子:当你需要给多个字段增加校验规则的时候可以使用
    • 案例:校验用户名中不能含有666(局部钩子), 校验密码和确认密码是否一致(全局钩子)
    • 钩子函数在类中书写方法即可
      在这里插入图片描述
  • forms组件其它参数及补充知识点
    • label:字段名
    • error_messages:自定义报错信息
    • initial:默认值
    • required:控制字段是否必填
    • widget=forms.widgets.PasswordInput(attrs={‘class’: ‘form-control’}):字段样式设置,针对不同类型(text, password,date, radio, checkbox)的input如何修改
    • validators=[RegexValidator(还支持正则校验
    • 其它的forms组件
10、cookie与session
  • cookie是保护在客户端浏览器上的信息,session是保存在服务端上的信息,session是基于cookie工作的(其实大部分的保存用户状态的操作都需要使用到cookie)
  • 设置和获取cookie,先完成一个登录功能
    • 设置,obj.set_cookie(‘username’, ‘shirmay’)
    • 获取,request.COOKIES.get(‘username’)
    • 设置cookie可以设置一个超时时间已秒为单位,obj.set_cookie(‘username’, ‘shirmay’, max_age=10, expires=10)
    • 注销:obj.delete_cookie(‘username’)
    # 校验用户登录的装饰器
    """
    用户如果在没有登录的情况下想访问一个需要登录的页面,
    那么先跳转到登录页面,当用户输入正确的用户名和密码之后
    应跳转到用户之前想要访问的页面去,而不是写死
    """
    def login_auth(func):
        def inner(request, *args, **kwargs):
            target_url = request.get_full_path()
            if request.COOKIES.get('username'):
                return func(request, *args, **kwargs)
            else:
                return redirect(f'/app02/login/?next={target_url}')
        return inner
    
    
    @login_auth
    def index(request):
        # # 获取cookie信息,判断你有没有
        # if request.COOKIES.get('username') == 'shirmay':
        #     if request.method == 'POST':
        #         print(request.POST)
        #         print(request.FILES)
        #     return render(request, 'index.html')
        # # 没有登录应该跳转到登录页面
        # else:
        #     return redirect('/app02/login/')
        return render(request, 'index.html')
    
    
    def login(request):
        if request.method == 'POST':
            username = request.POST.get('username')
            password = request.POST.get('password')
            if username == 'shirmay' and password == '123':
                # 获取用户上一次想要访问的url获取用户上一次想要访问的url
                target_url = request.GET.get('next')
                if target_url:
                    obj = redirect(target_url)
                else:
                    # 保存用户登录状态
                    obj = redirect('/app02/home/')
                # 让浏览器记录cookie数据, 浏览器不单单会帮你存,而且后面每次访问你的时候还会带着它过来
                obj.set_cookie('username', 'shirmay')
                # 跳转到一个需要用户登录之后才能看到的页面
                return obj
        return render(request, 'login_cookie.html')
        
    @login_auth
    def logout(request):
        obj = redirect('/app02/login/')
        obj.delete_cookie('username')
        return obj
    
    
  • 设置和获取session操作,保存在服务端的,给客户端返回的是一个随机字符串
    • 在默认情况下操作session的时候需要django默认的一张django_session表(执行数据库迁移命令即可自动创建),django_session表中的数据条数是取决于浏览器的同一个计算机同一个浏览器只会有一条数据生效
    • django默认session的过期时间是14天,但是你也可以人为的修改它
    • 设置session:request.session[‘hobby’] = ‘cat’
    • 设置session过期时间: request.session.set_expiry(),可传值整数,日期对象,不写
    • 获取session:request.session.get(‘hobby’)
    • 清除session,request.session.delete(); request.session.flush()
    • session是保存在服务端的,但是保存在哪个位置可以选择
    • xiaoyuanqujing@6666
      在这里插入图片描述
      在这里插入图片描述
    def set_session(request):
        request.session['hobby'] = 'cat'
        return HttpResponse('hhh')
    
    
    def get_session(request):
        print(request.session.get('hobby'))
        return HttpResponse('hsdkhaksjh')
     
    def del_session(request):
        print(request.session.flush())
        return HttpResponse('delete session')
    
11、CBV如何添加装饰器
  • (1)方式一:指名道姓
@method_decorator(check_login)
def post(self, request):
    print("Home View POST method...")
    return redirect("/index/")
  • (2)方式二:可以添加多个针对不同的方法加不同的装饰器
@method_decorator(check_login, name="get")
@method_decorator(check_login, name="post")
class HomeView(View):
  • (3)方式三:直接作用与当前类里面所有的方法
@method_decorator(check_login)
def dispatch(self, request, *args, **kwargs):
     return super(HomeView, self).dispatch(request, *args, **kwargs)
from django.utils.decorators import method_decorator

@method_decorator(check_login, name="get")
@method_decorator(check_login, name="post")
class HomeView(View):
	@method_decorator(check_login)
    def dispatch(self, request, *args, **kwargs):
        return super(HomeView, self).dispatch(request, *args, **kwargs)

    def get(self, request):
        return render(request, "home.html")
    
    @method_decorator(check_login)
    def post(self, request):
        print("Home View POST method...")
        return redirect("/index/")

12、django中间件
  • django自带七个中间件,每个中间件都有各自对应的功能,如全局身份校验,全局访问频率校验,全局权限校验

  • 并且django还支持程序员自定义中间件,并且暴露给程序员五个可以自定义的方法,只要涉及到全局相关的功能都可以使用中间件完成

    • 必须掌握的方法 process_request, process_response;其余了解process_view,process_template_response, process_expection
    • process_request:请求来的时候需要经过每一个中间件里面的process_request方法,结果的顺序是按配置文件中注册的中间件从上往下的顺序依次执行, 如果中间件没有定义该方法,直接跳过执行下一个中间件;如果该方法返回了HttpResponse对象,那么请求将不再继续往后执行,而是直接原路返回(校验失败不允许访问…);process_request方法就是用来做全局相关的所有限制功能
    • process_response:响应走到时候需要经过每一个中间件里面的process_response方法,该方法有两个额外的参数request,response;该方法必须返回一个HttpResponse对象,默认返回的是形参response;结果的顺序是按配置文件中注册的中间件从下往上的顺序依次执行 如果中间件没有定义该方法,直接跳过执行下一个中间件;
  • 如何自定义中间件: 创建文件夹>创建py文件>创建一个类,必须继承MiddlewareMixin,然后自定义五个方法,用几个写几个>将类的路径已字符串的形式注册到配置文件中才能生效

    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',
        # 自定义的中间件
        'mymiddleware.mydb.MyMiddleware',
    ]
    
    from django.utils.deprecation import MiddlewareMixin
    
    
    class MyMiddleware(MiddlewareMixin):
        def process_request(self, request):
            print("the first before request")
    
        def process_response(self, request, response):
            """
            response就是django后端返回给前端的内容
            """
            print("the first before response")
            return response
    
13、csrf跨站请求网站
  • 网站在给用户返回一个具有提交数据功能页面的时候会给这个页面加一个唯一标识,当这个页面朝后端发送post请求的时候,我的后端会先校验唯一标识,如果唯一标识不对直接拒绝(403 fobbiden)
  • 如何符合校验:
    • form表单,{% csrf_token %}
    <input type='hidden' name='csrfmiddlewaretoken' value='唯一字符串'/>
    
    • ajax,优先选择第三种方式
    {% load static %}
    <script src="{% static 'js/mysetup.js' %}"></script>
    <script>
        $.ajax({
          url: "",
          type: "POST", 
          {#// 第一种 利用标签查找获取页面上的随机字符串使用JQuery取出csrfmiddlewaretoken的值,拼接到data中#}
          {#data: {"username": "Tonny","password": 123456,"csrfmiddlewaretoken": $("[name = 'csrfmiddlewaretoken']").val() },#}
          {#// 第二种 利用模板语法提供的快捷书写#}
          {#data: {"username": "Tonny","password": 123456,"csrfmiddlewaretoken": '{{ csrf_token }}' },#}
          // 第三种 通用方式,导入已写好的mysetup.js文件,并引用到自己的html
          data: {"username": "Tonny","password": 123456},
          success: function (data) {
            console.log(data);
      }
    })
    </script>
    
    • mysetup.js
    function getCookie(name) {
        var cookieValue = null;
        if (document.cookie && document.cookie !== '') {
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) === (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }
    var csrftoken = getCookie('csrftoken');
    
    function csrfSafeMethod(method) {
      // these HTTP methods do not require CSRF protection
      return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
    }
    
    $.ajaxSetup({
      beforeSend: function (xhr, settings) {
        if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
          xhr.setRequestHeader("X-CSRFToken", csrftoken);
        }
      }
    });
    
    
  • csrf相关装饰器:@csrf_protect指定哪个必须校验csrf ;@csrf_exempt指定哪个可以不用校验csrf
from django.views.decorators.csrf import csrf_exempt,csrf_protect

#FBV使用直接加就行
@csrf_exempt  
def index(request):  
    pass
#CBV模式,除了csrf_exempt装饰器要按dispatch的方法加,csrf_protect三种加装饰器的方法都可行
@method_decorator(csrf_exempt,name="dispatch")
class Login(View):
    def get(self, request, *args, **kwargs):
        pass
    def post(self, request, *args, **kwargs):
        pass   
三、前端与后端的交互文件样
1、urls.py
from django.conf.urls import url
from app02orm import views

urlpatterns = (
    # 首页
    url(r'^home/', views.home),
    # 图书的展示
    url(r'^book/list/', views.book_list, name="book_list"),
    # 书籍的添加
    url(r'^book/add', views.book_add, name="book_add"),
    # 书籍的编辑
    url(r'^book/edit/(?P<edit_id>\d+)', views.book_edit, name="book_edit"),
    # 书籍的删除
    url(r'^book/delete/(\d+)', views.book_delete, name="book_delete"),
    # ajax相关
    url(r'ab_ajax/', views.ab_ajax),
    # 前后端传输数据编码格式研究
    url(r'^index/', views.index),
    # ajax发送json格式数据
    url(r'^ab_json/', views.ab_json),
    # ajax发送文件数据
    url(r'^ab_file/', views.ab_file),
    # 序列化组件
    url(r'^ab_ser/', views.ab_ser),
    # 批量
    url(r'^ab_pl/', views.ab_pl),
    # form组件
    url(r'ab_form/', views.ab_form),
    # 登录功能
    url(r'^login/', views.login),
    # 注销功能
    url(r'^logout/', views.logout),
    # session操作
    url(r'^set_session/', views.set_session),
    url(r'^get_session/', views.get_session),
    url(r'^del_session/', views.del_session),

)
2、views.py
from django.shortcuts import render, redirect, HttpResponse
import json
from django.http import JsonResponse
from django.core import serializers

# Create your views here.

book_querysets = [
    {"title": "python2", "pub_date": "2019/3/2", 'author': ['aa', 'bb'], 'source': 'A出版社', 'id': 1},
    {"title": "python2", "pub_date": "2019/3/2", 'author': ['aa'], 'source': 'B出版社', 'id': 2},
    {"title": "python2", "pub_date": "2019/3/2", 'author': ['aa'], 'source': 'B出版社', 'id': 3},
    {"title": "python2", "pub_date": "2019/3/2", 'author': ['aa'], 'source': 'A出版社', 'id': 4},
]


def home(request):
    return render(request, "home.html")


def book_list(request):
    book_queryset = book_querysets
    return render(request, 'book_list.html', locals())


def book_add(request):
    if request.method == 'POST':
        # 1 获取前端提交过来的所有数据
        title = request.POST.get("title")
        date = request.POST.get("pub_date")
        publish_id = request.POST.get("publish")
        authors_list = request.POST.getlist("authors")
        # 2 操作数据库存储数据
        print(title, date, publish_id, authors_list)
        # 3 跳转到数据展示页面, 则会有新增的数据展示出来
        # redirect括号内可以直接写url也可以直接写别名,但是如果你的别名需要给额外的参数的话,那么就必须使用reverser解析了
        return redirect('book_list')
    authors_list = [{'id': '1', 'name': 'aa'}, {'id': '2', 'name': 'bb'}, {'id': '3', 'name': 'cc'}]
    source_list = [{'id': '1', 'name': 'A出版社'}, {'id': '2', 'name': 'B出版社'}]
    return render(request, 'book_add.html', locals())


def book_edit(request, edit_id):
    if request.method == 'POST':
        return HttpResponse("修改数据的脚本")
    edit_obj = {}
    for obj in book_querysets:
        if obj['id'] == int(edit_id):
            edit_obj = obj
            break
    authors_list = [{'id': '1', 'name': 'aa'}, {'id': '2', 'name': 'bb'}, {'id': '3', 'name': 'cc'}]
    source_list = [{'id': '1', 'name': 'A出版社'}, {'id': '2', 'name': 'B出版社'}]
    return render(request, "book_edit.html", locals())


def book_delete(request, delete_id):
    # 删除脚本, pass
    # 直接跳转到展示页
    return redirect('book_list')


def ab_ajax(request):
    if request.method == 'POST':
        # print(request.POST)  # <QueryDict: {'username': ['jason'], 'password': ['123']}>
        i1 = request.POST.get('i1')
        i2 = request.POST.get('i2')
        i3 = int(i1) + int(i2)
        return HttpResponse(i3)
    return render(request, 'add_ajax.html')


def ab_json(request):
    print(request.is_ajax())
    print(request.POST)
    print(request.body)  # b'{"username":"jason","age":32}'
    if request.is_ajax():
        # json.loads括号内如果传入了一个二进制格式的数据那么内部自动解码再反序列化
        json_dict = json.loads(request.body) # {"username":"jason","age":32}
        print(json_dict)
    return render(request, 'ab_json.html')


def ab_file(request):
    if request.is_ajax():
        print(request.POST)
        print(request.FILES)
    return render(request, 'ab_file.html')


def ab_ser(request):
    user_list = [{"name": 1}, {"name": 2}]
    return JsonResponse(user_list, safe=False)
    # res = serializers.serialize('json', user_queryset)
    # return HttpResponse(res)


def ab_pl(request):
    from utils.mypage import Pagination
    # 先插入一万条数据
    query_list = [
        {'id': '1', 'name': 'aa'}, {'id': '2', 'name': 'bb'}, {'id': '3', 'name': 'cc'},
        {'id': '4', 'name': 'aaa'}, {'id': '5', 'name': 'bbb'}, {'id': '6', 'name': 'ccc'}
    ]
    # 当你批量插入数据的时候,使用orm给你提供的buLk_create能够大大的减少操作时间
    # book_list = []
    # for i in range(10000):
    #     book_obj = models.Book(title=i)
    #     book_list.append(book_obj)
    # models.Book.objects.bulk_create(book_list)
    # 再将所有数据查询并展示到前端页面
    current_page = request.GET.get('page', 1)
    all_count = len(query_list)
    # 1 传值生成对象
    page_obj = Pagination(current_page=current_page, all_count=all_count)
    # 2 直接对总数据进行切片操作
    page_queryset = query_list[page_obj.start:page_obj.end]
    return render(request, 'ab_pl.html', locals())


def ab_form(request):
    from django import forms
    from django.core.validators import RegexValidator
    class MyForm(forms.Form):
        # username最小3位最大8位
        username = forms.CharField(
            min_length=3, max_length=8, label='用户名',
            error_messages={
                'min_length': '用户名最少3位', 'max_length': '用户名最大8位', 'required': '用户名不能为空'
            },
            initial='shirmay',
            required=False,
            widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
        )
        password = forms.CharField(
            min_length=3, max_length=8, label='密码',
            widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
        )
        confirm_password = forms.CharField(
            min_length=3, max_length=8, label='确认密码',
            error_messages={
                'min_length': '确认密码最少3位', 'max_length': '确认密码最大8位', 'required': '确认密码不能为空'
            },
            widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
        )
        # email字段必须符合邮箱格式
        email = forms.EmailField(
            label='邮箱', error_messages={'invalid': '邮箱格式不正确', 'required': '用户名不能为空'},
            widget=forms.widgets.EmailInput(attrs={'class': 'form-control'})
        )

        phone = forms.CharField(
            validators=[
                RegexValidator(r'^[0-9]+$', '请输入数字'),
                RegexValidator(r'^159[0-9]+$', '数字必须以159开头')
            ],
        )
        gender = forms.fields.ChoiceField(
            choices=((1, "男"), (2, "女"), (3, "保密")),
            label="性别",
            initial=3,
            widget=forms.widgets.RadioSelect()
        )
        hobby = forms.ChoiceField(
            choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
            label="爱好",
            initial=3,
            widget=forms.widgets.Select()
        )
        # 多选
        hobby1 = forms.MultipleChoiceField(
            choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
            label="爱好",
            initial=[1, 3],
            widget=forms.widgets.SelectMultiple()
        )
        # 单选checkbox
        keep = forms.ChoiceField(
            label="是否记住密码",
            initial="checked",
            widget=forms.widgets.CheckboxInput()
        )
        # 多选checkbox
        hobby2 = forms.MultipleChoiceField(
            choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
            label="爱好",
            initial=[1, 3],
            widget=forms.widgets.CheckboxSelectMultiple()
        )

        # 局部钩子
        def clean_username(self):
            # 获取到用户名
            username = self.cleaned_data.get('username')
            # 提示前端展示错误信息
            if '666' in username:
                self.add_error('username', '不能有666')
            # 将钩子函数钩出来数据再放回去
            return username

        # 全局钩子
        def clean(self):
            password = self.cleaned_data.get("password")
            confirm_password = self.cleaned_data.get("confirm_password")
            if not confirm_password == password:
                self.add_error('confirm_password', '两次密码不一致')
            # 将钩子函数钩去出来数据再放回去
            return self.cleaned_data

    # form_obj = MyForm({'username':'dad', 'password':'123', 'email':'uerio'})  # 将带校验的数据组织成字典的形式传入即可
    # form_obj.is_valid()  # 检测数据是否合法,注意该方法只有在所有数据全部合法的情况下才返回True
    # print(form_obj.cleaned_data)    # 查看所有校验通过的数据
    # print(form_obj.errors)    # 查看所有不符合校验规则以及不符合的原因

    # 先产生一个空对象
    form_obj = MyForm()
    if request.method == 'POST':
        form_obj = MyForm(request.POST)
        if form_obj.is_valid():
            return HttpResponse('ok')
    # 直接将空对象传递给html页面
    return render(request, 'ab_form.html', locals())


# 校验用户登录的装饰器
"""
用户如果在没有登录的情况下想访问一个需要登录的页面,
那么先跳转到登录页面,当用户输入正确的用户名和密码之后
应跳转到用户之前想要访问的页面去,而不是写死
"""
def login_auth(func):
    def inner(request, *args, **kwargs):
        target_url = request.get_full_path()
        if request.COOKIES.get('username'):
            return func(request, *args, **kwargs)
        else:
            return redirect(f'/app02/login/?next={target_url}')
    return inner


@login_auth
def index(request):
    # # 获取cookie信息,判断你有没有
    # if request.COOKIES.get('username') == 'shirmay':
    #     if request.method == 'POST':
    #         print(request.POST)
    #         print(request.FILES)
    #     return render(request, 'index.html')
    # # 没有登录应该跳转到登录页面
    # else:
    #     return redirect('/app02/login/')
    return render(request, 'index.html')


def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'shirmay' and password == '123':
            # 获取用户上一次想要访问的url获取用户上一次想要访问的url
            target_url = request.GET.get('next')
            if target_url:
                obj = redirect(target_url)
            else:
                # 保存用户登录状态
                obj = redirect('/app02/home/')
            # 让浏览器记录cookie数据, 浏览器不单单会帮你存,而且后面每次访问你的时候还会带着它过来
            obj.set_cookie('username', 'shirmay', max_age=10, expires=10)
            # 跳转到一个需要用户登录之后才能看到的页面
            return obj
    return render(request, 'login_cookie.html')


@login_auth
def logout(request):
    obj = redirect('/app02/login/')
    obj.delete_cookie('username')
    return obj


def set_session(request):
    request.session['hobby'] = 'cat'
    return HttpResponse('hhh')


def get_session(request):
    print(request.session.get('hobby'))
    return HttpResponse('hsdkhaksjh')


def del_session(request):
    print(request.session.flush())
    return HttpResponse('delete session')

3、templates
(1)home.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Home</title>
    {% load static %}
    <link rel="stylesheet" href= "{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
    <script src="{% static 'bootstrap-3.3.7-dist/js/jquery-3.5.1.min.js' %}"></script>
    <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
    {% block css%}
        
    {% endblock %}
</head>
<body>
<nav class="navbar navbar-inverse">
  <div class="container-fluid">
    <!-- Brand and toggle get grouped for better mobile display -->
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="#">图书管理系统</a>
    </div>

    <!-- Collect the nav links, forms, and other content for toggling -->
    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
      <ul class="nav navbar-nav">
        <li class="active"><a href="#">图书<span class="sr-only">(current)</span></a></li>
        <li><a href="#">作者</a></li>
        <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">更多 <span class="caret"></span></a>
          <ul class="dropdown-menu">
            <li><a href="#">Action</a></li>
            <li><a href="#">Another action</a></li>
            <li><a href="#">Something else here</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">Separated link</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">One more separated link</a></li>
          </ul>
        </li>
      </ul>
      <form class="navbar-form navbar-left">
        <div class="form-group">
          <input type="text" class="form-control" placeholder="Search">
        </div>
        <button type="submit" class="btn btn-default">Submit</button>
      </form>
      <ul class="nav navbar-nav navbar-right">
        <li><a href="#">Shirmay</a></li>
        <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">更多操作 <span class="caret"></span></a>
          <ul class="dropdown-menu">
            <li><a href="#">Action</a></li>
            <li><a href="#">Another action</a></li>
            <li><a href="#">Something else here</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">Separated link</a></li>
          </ul>
        </li>
      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-3">
            <div class="list-group">
                <a href="#" class="list-group-item active">
                  首页
                </a>
                <a href="{% url 'book_list' %}" class="list-group-item">图书列表</a>
                <a href="#" class="list-group-item">出版社列表</a>
                <a href="#" class="list-group-item">作者列表</a>
                <a href="#" class="list-group-item">更多</a>
              </div>

        </div>
        <div class="col-md-9">
            <div class="panel panel-primary">
                <div class="panel-heading">
                  <h3 class="panel-title">BMS</h3>
                </div>
                <div class="panel-body">
                    {% block content %}
                    <div class="jumbotron">
                        <h1>欢迎来到读书平台</h1>
                        <p>这里都有</p>
                        <p><a class="btn btn-primary btn-lg" href="#" role="button">点击看好多</a></p>
                    </div>
                    <div class="row">
                        <div class="col-sm-6 col-md-4">
                          <div class="thumbnail">
                            <img src="https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=4076820763,4198936175&fm=26&gp=0.jpg" alt="...">
                            <div class="caption">
                              <h3>Thumbnail label</h3>
                              <p>...</p>
                              <p><a href="#" class="btn btn-primary" role="button">Button</a> <a href="#" class="btn btn-default" role="button">Button</a></p>
                            </div>
                          </div>
                        </div>
                        <div class="col-sm-6 col-md-4">
                            <div class="thumbnail">
                              <img src="https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2156579844,1915236356&fm=26&gp=0.jpg" alt="...">
                              <div class="caption">
                                <h3>Thumbnail label</h3>
                                <p>...</p>
                                <p><a href="#" class="btn btn-primary" role="button">Button</a> <a href="#" class="btn btn-default" role="button">Button</a></p>
                              </div>
                            </div>
                          </div>
                          <div class="col-sm-6 col-md-4">
                            <div class="thumbnail">
                              <img src="https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=269085220,2221469621&fm=26&gp=0.jpg" alt="...">
                              <div class="caption">
                                <h3>Thumbnail label</h3>
                                <p>...</p>
                                <p><a href="#" class="btn btn-primary" role="button">Button</a> <a href="#" class="btn btn-default" role="button">Button</a></p>
                              </div>
                            </div>
                          </div>
                    </div>
                    {% endblock %}
                </div>
              </div>
        </div>

    </div>
</div>

{% block js %}

{% endblock %}
</body>
</html>
(2)book_list.html
{% extends 'home.html' %}

{% block content %}
    <a href="{% url 'book_add' %}" class="btn btn-success">添加</a>
    <br><br>
    <table class="table table-hover table-striped">
    <thead>
       <tr>
           <th>ID</th>
           <th>书名</th>
           <th>日期</th>
           <th>出版社</th>
           <th>作者</th>
           <th>操作</th>
        </tr>
    </thead>
    <tbody>
        {% for book_obj in book_queryset %}
            <tr>
                <td>{{ book_obj.id}}</td>
                <td>{{ book_obj.title}}</td>
                <td>{{ book_obj.pub_date}}</td>
                <td>{{ book_obj.source}}</td>
                <td>{{ book_obj.author}}</td>
                <td>
                    <a href="{% url 'book_edit' book_obj.id %}" class="btn btn-primary btn-xs">编辑</a>
                    <a href="{% url 'book_delete' book_obj.id %}" class="btn btn-danger btn-xs">删除</a>
                </td>
            </tr>
        {% endfor %}

    </tbody>
    </table>
{% endblock %}
(3)book_add.html
{% extends 'home.html' %}

{% block content %}
    <h1 class="text-center">书籍添加</h1>
    <form action="" method="post">
        <p>书名:
            <input type="text" name="title" class="form-control">
        </p>
        <p>日期:
            <input type="date" name="pub_date" class="form-control">
        </p>
        <p>出版社:
            <select name="publish" id="" class="form-control">
                {% for p in source_list %}
                    <option value="{{ p.id }}">{{ p.name }}</option>
                {% endfor %}
            </select>
        </p>
        <p>作者:
            <select name="authors" id="" multiple class="form-control">
                {% for author in authors_list %}
                    <option value="{{ author.id }}">{{ author.name }}</option>
                {% endfor %}
            </select>
        </p>
        <input type="submit" value="新增" class="btn btn-primary btn-block">
    </form>

{% endblock %}
(4)book_edit.html
{% extends 'home.html' %}

{% block content %}
    <h1 class="text-center">书籍辑</h1>
    <form action="" method="post">
        <p>书名:
            <input type="text" name="title" class="form-control" value="{{ edit_obj.title }}">
        </p>
        <p>日期:
            <input type="text" name="pub_date" class="form-control" value="{{ edit_obj.pub_date}}">
        </p>
        <p>出版社:
            <select name="publish" id="" class="form-control">
                {% for p in source_list %}
                    {% if edit_obj.publish == p %}
                        <option value="{{ p.id }}" selected>{{ p.name }}</option>
                    {% else %}
                        <option value="{{ p.id }}">{{ p.name }}</option>
                    {% endif %}
                {% endfor %}
            </select>
        </p>
        <p>作者:
            <select name="authors" id="" multiple class="form-control">
                {% for author in authors_list %}
                    {% if author.name in edit_obj.author %}
                        <option value="{{ author.id }}" selected>{{ author.name }}</option>
                    {% else %}
                        <option value="{{ author.id }}">{{ author.name }}</option>
                    {% endif %}

                {% endfor %}
            </select>
        </p>
        <input type="submit" value="编辑" class="btn btn-info btn-block">
    </form>

{% endblock %}

(5)add_ajax.html之ajax小应用
{% extends 'home.html' %}

{% block content %}
    <input type="text" id="d1">+
    <input type="text" id="d2">=
    <input type="text" id="d3">
    <p><button id="btn">click </button></p>
    <script>
    $('#btn').click(  // 先给按钮绑定一个点击事件
        function(){
            $.ajax({ // 朝后端发送ajax请求
                url:'',  // 1、指定超哪个后端发送ajax请求,不写就是超当前地址提交
                type:'post', // 2、请求方式,不指定默认就是get
                // 3、数据 {#data:{'username':'jason', 'password':123},#}
                data:{'i1':$('#d1').val(), 'i2':$('#d2').val()},
                // 4、回调函数, 当后端给你返回结果的时候会自动触发,args接受后端的返回结果
                success:function(args){
                    $('#d3').val(args)  {#alert(args)  // 通过DOM操作动态渲染到第三个input里面#}
                }
            })
        }
    )
    </script>

{% endblock %}
(6)index.html之ajax与form的编码格式
{% extends 'home.html' %}

{% block content %}
    <form action="" method="post" enctype="multipart/form-data">
        <p>username:<input type="text" name="username" class="form-control"></p>
        <p>password:<input type="password" name="password" class="form-control"></p>
        <p>file:<input type="file" name="file"></p>
        <input type="submit" class="btn btn-success">
        <input type="button" class="btn btn-danger" value="按钮" id="d1">
    </form>

    <script>
        $('#d1').click(
            function(){
                $.ajax({
                    url: '',
                    type:'POST',
                    data:{'username':'jason', 'age':32},
                    success: function (args){

                    }
                })
            }
        )
    </script>
{% endblock %}

(7)ab_json.html之ajax传送json格式数据
{% extends 'home.html' %}

{% block content %}
    <button class="btn btn-danger" id="d1">click me</button>
    <script>
        $('#d1').click(
            function(){
                $.ajax({
                    url: '',
                    type:'POST',
                    data: JSON.stringify({'username':'jason', 'age':32}),
                    contentType: 'application/json',  // 指定编码格式
                    success: function (args){

                    }
                })
            }
        )
    </script>
{% endblock %}

(8)ab_file.html之ajax发送文件
{% extends 'home.html' %}

{% block content %}
    <p>username:<input type="text" id="d1"></p>
    <p>password:<input type="text" id="d2"></p>
    <p><input type="file" id="d3"></p>
    <button class="btn btn-info" id="d4">click</button>
    <script>
        $('#d4').click(  // 点击按钮超后端发送普通键值对和文件数据
            function(){
                // 1、需要先利用FormData内置对象
                let formDataObj = new FormData();
                // 2、添加普通的键值对
                formDataObj.append('username', $('#d1').val());
                formDataObj.append('password', $('#d2').val());
                // 3、添加文件对象
                formDataObj.append('myfile', $('#d3')[0].files[0])
                // 4、将对象基于ajax发送给后端
                $.ajax({
                    url: '',
                    type:'POST',
                    data: formDataObj,  // 直接将对象放在data后面即可
                    // ajax发送文件必须要指定的两个参数
                    contentType: false,   // 不需使用任何编码,django后端能够自动识别FormData对象
                    processData: false,   // 告诉浏览器不要对你的数据进行任何处理
                    success: function (args){

                    }
                })
            }
        )
    </script>
{% endblock %}
(9)ab_pl.html之forms组件
{% extends 'home.html' %}

{% block content %}
    {% for j in page_queryset %}
        <p> {{ j}}</p>
        <nav aria-label="Page navigation"></nav>
    {% endfor %}

    {{ page_obj.page_html|safe }}   {#    // 因为是后端传来拼接好的html代码,标记为safe#}
{% endblock %}
(10)ab_form.html批量操作
{% extends 'home.html' %}

{% block content %}
    <form action="" method="post" novalidate>
{#        <p>第一种渲染方式,代码书写极少,封装程度太高,不便于后续的扩展,一般情况下只在本地测试使用 </p>#}
{#        {{ form_obj.as_p }}#}
{#        {{ form_obj.as_ul }}#}
{#        {{ form_obj.as_table }}#}
{#        <p>第二种渲染方式,可扩展性很强, 但是需要书写的代码太多</p>#}
{#        {{ form_obj.username.label }}:{{ form_obj.username }}#}
        <p>第三种渲染方式推荐使用, 代码书写简单,并且扩展性也高</p>
        {% for form in form_obj %}
            <p>
            {{ form.label }}:{{ form }}
            <span style="color:red"> {{ form.errors.0 }}</span>
            </p>
        {% endfor %}
        <input type="submit" class="btn btn-info">
    </form>
{% endblock %}
(11)login.html登录功能
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登陆</title>
<!--    <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">-->
<!--    <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>-->
    {% load static %}
    <script src="{% static 'bootstrap-3.3.7-dist/js/jquery-3.5.1.min.js' %}"></script>
    <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
    <link rel="stylesheet" href= "{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
</head>
<body>
<h1 class="text-center">登陆</h1>
<div class="container">
    <div class="row" >
        <div class="col-md-8 col-mod-offset-2">
            <form action="" method="post">
                <p>username:<input type="text" name="username" class="form-control"></p>
                <p>password:<input type="password" name="password" class="form-control"></p>
                <input type="submit" class="btn btn-success btn-block">
            </form>
        </div>

    </div>

</div>
</body>
</html>

  • 1
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值