10-day06-项目开发-用户注册设置免费额度

一、用户注册设置免费额度

  • 第一步 :调整用户注册的视图,增加设置免费额度
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/views/account.py  

"""
用户账户相关功能 :注册、登录、短信、注销
"""
from django.shortcuts import render, HttpResponse,redirect
from django.http import JsonResponse
from web_app.forms.account import RegisterModelForm,SendSmsForm, LoginSMSForm, LoginForm
from web_app import models
import uuid
import datetime

def register(request):
    """ 注册 """
    if request.method =='GET':
        form = RegisterModelForm()
        return render(request, 'register.html', {'form': form})
    # POST
    # form 数据校验
    form = RegisterModelForm(data=request.POST)
    # 若form 数据校验成功
    if form.is_valid():
        # print(form.cleaned_data)    # form.cleaned_data 存放校验成功的数据

        # 写入数据库也可以使用create方法,但是需要手动剔除不存在的字段
        # from web_app import models
        # data = form.cleaned_data
        # data.pop('code')
        # data.pop('confirm_password')
        # instance = models.UserInfo.objects.create(**data)

        # 验证通过,写入数据库 (数据库密码加密) ,通过save()方法进行保存数据库,还可以将那些models中不存在的字段会自动剔除
        # 用户表中创建一条数据
        instance = form.save()
        # 创建交易记录
        policy_object = models.PricePolicy.objects.filter(category=1, title="个人免费版").first()
        models.Transaction.objects.create(
            # 状态
            status=2,
            # 订单号
            order=str(uuid.uuid4()),
            # 用户
            user=instance,
            # 价格策略
            price_policy=policy_object,
            # 数量(年)
            count=0,
            # 实际支付价格
            price=0,
            # 开始时间
            start_datetime=datetime.datetime.now(),
        )

        return JsonResponse({'status': True, 'data': '/login/'})
    return JsonResponse({'status': False, 'error': form.errors})    # 如果校验未通过则返回 Form 校验的错误信息

二、后台母版样式设计

  • 第一步 :项目管理路由
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/urls.py   

from django.conf.urls import url, include
from web_app.views import account
from web_app.views import home
from web_app.views import project

urlpatterns = [
    url(r'^send/sms/$', account.send_sms, name='send_sms'),          # 发送SMS
    url(r'^login/sms/$', account.login_sms, name='login_sms'),       # 短信登录
    url(r'^register/$', account.register, name='register'),          # 用户注册
    url(r'^login/$', account.login, name='login'),                   # 用户名邮箱验证码登录
    url(r'^image/code/$', account.image_code, name='image_code'),    # 图片验证码
    url(r'^index/$', home.index, name='index'),                      # 登录首页
    url(r'^logout/$', account.logout, name='logout'),                # 退出登录

    url(r'^project/list/$', project.project_list, name='project_list'), # 项目列表
]
  • 第二步 :后台母模板Template模板
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/layout/manage.html 

{% load static %}
{% load project %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}{% endblock %}</title>
    <link rel="stylesheet" href="{% static 'plugin/bootstrap/css/bootstrap.min.css' %}">
    <link rel="stylesheet" href="{% static 'plugin/font-awesome/css/font-awesome.min.css' %}">
    <link rel="stylesheet" href="{% static 'css/manage.css' %}">
    <style>
        .navbar-av {
            border-radius: 0;
        }

        .error-msg {
            color: red;
            position: absolute;
            font-size: 13px;
        }
    </style>
    {% block css %}{% endblock %}
</head>
<body>

<nav class="navbar navbar-av">
    <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="{% url 'project_list' %}">Tracer</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">#}
{#                {% all_project_list request %}#}
{##}
{#                {% if request.tracer.project %}#}
{#                    {% manage_menu_list request %}#}
{#                {% endif %}#}
{#            </ul>#}

            <ul class="nav navbar-nav navbar-right">
                <li><a href="#">工作台</a></li>
                <li><a href="#">日历</a></li>
                <li><a href="#"> <i class="fa fa-bell-o" aria-hidden="true"></i> </a></li>
                <li><a href="#"> <i class="fa fa-bookmark" aria-hidden="true"></i> </a></li>

                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                       aria-expanded="false">{{ request.tracer.user.username }} <span class="caret"></span></a>
                    <ul class="dropdown-menu">
                        <li><a href="{% url 'index' %}">官 网</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="{% url 'logout' %}">退 出</a></li>
                    </ul>
                </li>
            </ul>
        </div>
    </div>
</nav>
{% block content %}{% endblock %}


<script src="{% static 'js/jquery-3.4.1.min.js' %}"></script>
<script src="{% static 'plugin/bootstrap/js/bootstrap.min.js' %}"></script>
{% block js %}{% endblock %}
</body>
</html>
  • 第三步 :基于BootStrap进行了CSS样式调整
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/static/css/manage.css 

body {
    margin: 0;
    font-weight: 200;
}

@media (min-width: 1500px) {
    .container {
        width: 1400px;
    }
}

.navbar {
    border-radius: 0;
    margin-bottom: 0;
    color: #ffffff;
    height: 52px;
}

@media (min-width: 900px) {
    .navbar-header {
        float: left;
    }
}

.navbar-av {
    background-color: #499ef3;
    border-color: #499ef3;
}

.navbar-av .navbar-brand {
    color: #ffffff;
    margin: 0 15px;
}

.navbar-av .navbar-brand:hover,
.navbar-av .navbar-brand:focus {
    color: #ffffff;
    background-color: #4dabff;
}

.navbar-av .navbar-text {
    color: #ffffff;
}

.navbar-av .navbar-nav {
    margin: 0 -15px;

}

.navbar-av .navbar-nav > li {
    z-index: 1001;

}

.navbar-av .navbar-nav > li > a {
    color: #ffffff;
    background-color: #499ef3;
    padding-left: 13px;
    padding-right: 13px;

}

.navbar-av .navbar-nav > li > a:hover,
.navbar-av .navbar-nav > li > a:focus {
    color: #ffffff;
    background-color: #4dabff;

}

.navbar-av .navbar-nav > .active > a,
.navbar-av .navbar-nav > .active > a:hover,
.navbar-av .navbar-nav > .active > a:focus {
    color: #ffffff;
    background-color: #4dabff;

}

.navbar-av .navbar-nav > .disabled > a,
.navbar-av .navbar-nav > .disabled > a:hover,
.navbar-av .navbar-nav > .disabled > a:focus {
    color: #ffffff;
    background-color: #4dabff;
}

.navbar-av .navbar-toggle {
    border-color: #499ef3;
}

.navbar-av .navbar-toggle:hover,
.navbar-av .navbar-toggle:focus {
    background-color: #4dabff;
}

.navbar-av .navbar-toggle .icon-bar {
    background-color: #ffffff;
}

.navbar-av .navbar-collapse,
.navbar-av .navbar-form {
    border-color: #e7e7e7;
}

.navbar-av .navbar-nav > .open > a,
.navbar-av .navbar-nav > .open > a:hover,
.navbar-av .navbar-nav > .open > a:focus {
    color: #ffffff;
    background-color: #4dabff;
}

@media (max-width: 767px) {
    .navbar-av .navbar-nav .open .dropdown-menu {
        padding: 0;
    }

    .navbar-av .navbar-nav .open .dropdown-menu > li > a {
        color: #ffffff;
        background-color: #499ef3;
    }

    .navbar-av .navbar-nav .open .dropdown-menu > li > a:hover,
    .navbar-av .navbar-nav .open .dropdown-menu > li > a:focus {
        color: #ffffff;
        background-color: #4dabff;
    }

    .navbar-av .navbar-nav .open .dropdown-menu > .active > a,
    .navbar-av .navbar-nav .open .dropdown-menu > .active > a:hover,
    .navbar-av .navbar-nav .open .dropdown-menu > .active > a:focus {
        color: #ffffff;
        background-color: #4dabff;
    }

    .navbar-av .navbar-nav .open .dropdown-menu > .disabled > a,
    .navbar-av .navbar-nav .open .dropdown-menu > .disabled > a:hover,
    .navbar-av .navbar-nav .open .dropdown-menu > .disabled > a:focus {
        color: #ffffff;
        background-color: #4dabff;
    }
}

.navbar-av .navbar-link {
    color: #ffffff;
}

.navbar-av .navbar-link:hover {
    color: #ffffff;
}

.navbar-av .btn-link {
    color: #ffffff;
}

.navbar-av .btn-link:hover,
.navbar-av .btn-link:focus {
    color: #ffffff;
}

.navbar-av .btn-link[disabled]:hover,
fieldset[disabled] .navbar-av .btn-link:hover,
.navbar-av .btn-link[disabled]:focus,
fieldset[disabled] .navbar-av .btn-link:focus {
    color: #ffffff;
}

.navbar-av .navbar-nav > li > .sep{
    padding: 15px 5px;display: inline-block
}

.dropdown-menu>li{
    padding: 0 5px;
    color: black;
}
  • 第四步 :编写项目管理页面子模板继承自母模板
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/project_list.html 

{% extends 'layout/manage.html' %}
  • 第五步 :编写views视图
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/views/project.py            

from django.shortcuts import render, redirect

def project_list(request):
    """ 项目列表 """
    return render(request, 'project_list.html')

image

三、添加按钮和权限中间件处理

  • 后台 :登录成功之后才可以访问;
  • 官网 :都可以访问
  • 实现方案 :通过 中间件+白名单 对后台管理的权限 进行处理

  • 第一步 :在settings中定义无需登录就可以访问的白名单路由
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat Bug_manager/settings.py 

...
...
######## 登录报名单 :无需登录就可以访问的页面 ########
WHITE_REGEX_URL_LIST = [
    "/register/",
    "/send/sms/",
    "/login/",
    "/login/sms/",
    "/image/code/",
    "/index/",
]
...
...
  • 第二步 :调整认证的中间件,增加URL对白名单以外的路径,检查用户是否登录
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/middleware/auth.py 

from django.utils.deprecation import MiddlewareMixin
from web_app import models
from django.conf import settings
from django.shortcuts import redirect

class AuthMiddleware(MiddlewareMixin):

    def process_request(self, request):
        """ 如果用户已经登录,则request中赋值 """
        user_id = request.session.get('user_id', 0)    # 用户登录则session中有用户ID,如果没有则为0

        # 如果登录的用户存在则返回用户对象,不存在返回None
        user_object = models.UserInfo.objects.filter(id=user_id).first()
        request.bug_manager = user_object

        # 白名单:没有登录都可以访问的URL
        """
        1. 获取当用户访问的URL
        2. 检查URL是否在白名单中,如果再则可以继续向后访问,如果不在则进行判断是否已登录
            request.path_info 表示用户访问的URI
        """
        if request.path_info in settings.WHITE_REGEX_URL_LIST:
            return  # 中间件返回None表示成功

        # 检查用户是否已登录,已登录继续往后走;未登录则返回登录页面。
        if not request.bug_manager.user:
            return redirect('login')

四、中间件对的登录的用户额度获取及用户信息的封装

4.1、方式一 :免费额度在交易记录中存储

  • 在登录成功后,获取当前用户拥有的价格策略【额度】,也使用 request 自定义字段存放;
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/middleware/auth.py 

from django.utils.deprecation import MiddlewareMixin
from web_app import models
from django.conf import settings
from django.shortcuts import redirect
import datetime


class AuthMiddleware(MiddlewareMixin):

    def process_request(self, request):
        """ 如果用户已经登录,则request中赋值 """
        user_id = request.session.get('user_id', 0)    # 用户登录则session中有用户ID,如果没有则为0

        # 如果登录的用户存在则返回用户对象,不存在返回None
        user_object = models.UserInfo.objects.filter(id=user_id).first()
        request.bug_manager = user_object

        # 白名单:没有登录都可以访问的URL
        """
        1. 获取当用户访问的URL
        2. 检查URL是否在白名单中,如果再则可以继续向后访问,如果不在则进行判断是否已登录
            request.path_info 表示用户访问的URI
        """
        if request.path_info in settings.WHITE_REGEX_URL_LIST:
            return  # 中间件返回None表示成功

        # 检查用户是否已登录,已登录继续往后走;未登录则返回登录页面。
        if not request.bug_manager:
            return redirect('login')

        # 登录成功之后,访问后台管理时:获取当前用户所拥有的额度(交易记录表)

        # 方式一:免费额度在交易记录中存储
        # 获取当前用户ID值最大(最近交易记录)
        _object = models.Transaction.objects.filter(user=user_object, status=2).order_by('-id').first()
        # 判断是否已过期
        current_datetime = datetime.datetime.now()
        if _object.end_datetime and _object.end_datetime < current_datetime:
            _object = models.Transaction.objects.filter(user=user_object, status=2, price_policy__category=1).first()
        # request.transaction = _object   # (存储了整张交易记录表中的信息)
        request.price_policy = _object.price_policy # (存储了登录的用户及用户的额度信息)

4.2、方式二 :免费的额度存储配置文件


...
...
        # 方式二:免费的额度存储配置文件
        """
        # 获取当前用户ID值最大(最近交易记录)
        _object = models.Transaction.objects.filter(user=user_object, status=2).order_by('-id').first()

        if not _object:
            # 没有购买
            request.price_policy = models.PricePolicy.objects.filter(category=1, title="个人免费版").first()
        else:
            # 付费版
            current_datetime = datetime.datetime.now()
            if _object.end_datetime and _object.end_datetime < current_datetime:
                request.price_policy = models.PricePolicy.objects.filter(category=1, title="个人免费版").first()
            else:
                request.price_policy = _object.price_policy
        """
...
...

4.3、优化

  • 第一步 :上面中间件对的登录的用户获取额度,可以以如下方式在视图中使用
from django.shortcuts import render, redirect

def project_list(request):
    """ 项目列表 """
    request.bug_manager
    request.price_polcy
    return render(request, 'project_list.html')
  • 第二步 :如果觉得上面获取方式不好,可以再次对中间件进行优化;
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/middleware/auth.py 

from django.utils.deprecation import MiddlewareMixin
from web_app import models
from django.conf import settings
from django.shortcuts import redirect
import datetime

class Bug_manager(object):
    def __init__(self):
        self.user = None
        self.price_policy = None
        self.project = None


class AuthMiddleware(MiddlewareMixin):

    def process_request(self, request):
        """ 如果用户已经登录,则request中赋值 """
        request.bug_manager = Bug_manager()

        user_id = request.session.get('user_id', 0)    # 用户登录则session中有用户ID,如果没有则为0

        # 如果登录的用户存在则返回用户对象,不存在返回None
        user_object = models.UserInfo.objects.filter(id=user_id).first()
        request.bug_manager.user = user_object

        # 白名单:没有登录都可以访问的URL
        """
        1. 获取当用户访问的URL
        2. 检查URL是否在白名单中,如果再则可以继续向后访问,如果不在则进行判断是否已登录
            request.path_info 表示用户访问的URI
        """
        if request.path_info in settings.WHITE_REGEX_URL_LIST:
            return  # 中间件返回None表示成功

        # 检查用户是否已登录,已登录继续往后走;未登录则返回登录页面。
        if not request.bug_manager.user:
            return redirect('login')

        # 登录成功之后,访问后台管理时:获取当前用户所拥有的额度(交易记录表)

        # 方式一:免费额度在交易记录中存储
        # 获取当前用户ID值最大(最近交易记录)
        _object = models.Transaction.objects.filter(user=user_object, status=2).order_by('-id').first()
        # 判断是否已过期
        current_datetime = datetime.datetime.now()
        if _object.end_datetime and _object.end_datetime < current_datetime:
            _object = models.Transaction.objects.filter(user=user_object, status=2, price_policy__category=1).first()
        # request.transaction = _object   # (存储了整张交易记录表中的信息)
        request.bug_manager.price_policy = _object.price_policy # (存储了登录的用户及用户的额度信息)
  • 第三步 :优化后的中间件对的登录的用户获取额度,可以以如下方式在视图中使用;
from django.shortcuts import render, redirect

def project_list(request):
    """ 项目列表 """
    request.bug_manager.user
    request.bug_manager.price_policy
    return render(request, 'project_list.html')
  • 第四步 :善后调整,封装完成后,回想此前首页模板对用户是否登录的判断也需要调整;
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/layout/basic.html 

...
...

      <ul class="nav navbar-nav navbar-right">
          {% if request.bug_manager.user %}
                <li class="dropdown">
                <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                   aria-expanded="false">{{ request.bug_manager.user.username }} <span class="caret"></span></a>
                <ul class="dropdown-menu">
{#                <li><a href="#">Action</a></li>#}
                <li><a href="#">管理中心</a></li>
{#                <li><a href="#">Something else here</a></li>#}
                <li role="separator" class="divider"></li>
                <li><a href="{% url 'logout' %}">退 出</a></li>
                </ul>
                </li>
          {% else %}
              <li><a href="{% url 'login' %}">登 录</a></li>
              <li><a href="{% url 'register' %}">注 册</a></li>
          {% endif %}
      </ul>
...
...
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/layout/manage.html 

...
...
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                       aria-expanded="false">{{ request.bug_manager.user.username }} <span class="caret"></span></a>
                    <ul class="dropdown-menu">
                        <li><a href="{% url 'index' %}">官 网</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="{% url 'logout' %}">退 出</a></li>
                    </ul>
                </li>
            </ul>
        </div>
    </div>
</nav>
{% block content %}{% endblock %}
...
...

image
image

五、创建项目

(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/forms/project.py 
# -*- coding:utf-8 -*-

from django import forms
from django.core.exceptions import ValidationError

from web_app.forms.bootstrap import BootStrapForm
from web_app import models


class ProjectModelForm(BootStrapForm, forms.ModelForm):
    bootstrap_class_exclude = ['color']

    class Meta:
        model = models.Project
        fields = ['name', 'color', 'desc']
  • 第二步 :定义Views视图,将form传入前端
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/views/project.py 

from django.shortcuts import render, redirect
from web_app.forms.project import ProjectModelForm

def project_list(request):
    """ 项目列表 """
    if request.method == 'GET':
        form = ProjectModelForm()
        return render(request, 'project_list.html', {'form': form})
  • 第三步 :编写Template模板,遍历form,生成模态框中的字段
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/project_list.html


{% extends 'layout/manage.html' %}

{% block css %}
    <style>
        .project {
            margin-top: 10px;
        }

        .panel-body {
            padding: 0;
            display: flex;
            flex-direction: row;
            justify-content: left;
            align-items: flex-start;
            flex-wrap: wrap;
        }

        .panel-body > .item {
            border-radius: 6px;
            width: 228px;
            border: 1px solid #dddddd;
            margin: 20px 10px;

        }

        .panel-body > .item:hover {
            border: 1px solid #f0ad4e;
        }

        .panel-body > .item > .title {
            height: 104px;
            color: white;
            display: flex;
            justify-content: center;
            align-items: center;
            border-top-left-radius: 6px;
            border-top-right-radius: 6px;
            font-size: 15px;
            text-decoration: none;
        }

        .panel-body > .item > .info {
            padding: 10px 10px;

            display: flex;
            justify-content: space-between;

            border-bottom-left-radius: 6px;
            border-bottom-right-radius: 6px;
            color: #8c8c8c;

        }

        .panel-body > .item > .info a {
            text-decoration: none;
        }

        .panel-body > .item > .info .fa-star {
            font-size: 18px;
        }

        .color-radio label {
            margin-left: 0;
            padding-left: 0;
        }

        .color-radio input[type="radio"] {
            display: none;
        }

        .color-radio input[type="radio"] + .cycle {
            display: inline-block;
            height: 25px;
            width: 25px;
            border-radius: 50%;
            border: 2px solid #dddddd;
        }

        .color-radio input[type="radio"]:checked + .cycle {
            border: 2px solid black;
        }
    </style>
{% endblock %}
{% block content %}
    <div class="container-fluid project">
        <div style="margin: 10px 0;">
            <a class="btn btn-primary" data-toggle="modal" data-target="#addModal">
                <i class="fa fa-plus-circle" aria-hidden="true"></i> 新建项目
            </a>
        </div>
    </div>

    <!-- Modal -->
    <div class="modal fade" id="addModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
                            aria-hidden="true">&times;</span></button>
                    <h4 class="modal-title" id="myModalLabel">新建项目</h4>
                </div>
                <div class="modal-body">

                    <form id="addForm">
                        {% csrf_token %}
                        {% for field in form %}
                            <div class="form-group">
                                <label for="{{ field.id_for_label }}">{{ field.label }}</label>
                                {{ field }}
                                <span class="error-msg"></span>
                            </div>
                        {% endfor %}
                    </form>
                </div>

                <div class="modal-footer">
                    <button type="button" class="btn btn-default" data-dismiss="modal">取 消</button>
                    <button id="btnSubmit" type="button" class="btn btn-primary">确 定</button>
                </div>
            </div>
        </div>
    </div>
{% endblock %}
  • 可以发现由于描述字段设置的数据库字段类型为CharField,所以需要在Form中对此字段进行部件调整;

image

  • 第四步 :对描述字段进行部件调整
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/forms/project.py 
# -*- coding:utf-8 -*-

from django import forms
from django.core.exceptions import ValidationError

from web_app.forms.bootstrap import BootStrapForm
from web_app import models


class ProjectModelForm(BootStrapForm, forms.ModelForm):
    # desc = forms.CharField(widget=forms.Textarea(attrs={'xx': 123}))
    class Meta:
        model = models.Project
        fields = ['name', 'color', 'desc']
        # # widgets 重写字段类型
        widgets = {
            'desc': forms.Textarea,
        }

image

  • 第五步 :Template模板,编写Ajax请求模态框,必须使用Ajax发送请求,为确定的IDbtnSubmit,绑定一个Ajax POST
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/project_list.html

{% extends 'layout/manage.html' %}

{% block css %}
    <style>
        .project {
            margin-top: 10px;
        }

        .panel-body {
            padding: 0;
            display: flex;
            flex-direction: row;
            justify-content: left;
            align-items: flex-start;
            flex-wrap: wrap;
        }

        .panel-body > .item {
            border-radius: 6px;
            width: 228px;
            border: 1px solid #dddddd;
            margin: 20px 10px;

        }

        .panel-body > .item:hover {
            border: 1px solid #f0ad4e;
        }

        .panel-body > .item > .title {
            height: 104px;
            color: white;
            display: flex;
            justify-content: center;
            align-items: center;
            border-top-left-radius: 6px;
            border-top-right-radius: 6px;
            font-size: 15px;
            text-decoration: none;
        }

        .panel-body > .item > .info {
            padding: 10px 10px;

            display: flex;
            justify-content: space-between;

            border-bottom-left-radius: 6px;
            border-bottom-right-radius: 6px;
            color: #8c8c8c;

        }

        .panel-body > .item > .info a {
            text-decoration: none;
        }

        .panel-body > .item > .info .fa-star {
            font-size: 18px;
        }

        .color-radio label {
            margin-left: 0;
            padding-left: 0;
        }

        .color-radio input[type="radio"] {
            display: none;
        }

        .color-radio input[type="radio"] + .cycle {
            display: inline-block;
            height: 25px;
            width: 25px;
            border-radius: 50%;
            border: 2px solid #dddddd;
        }

        .color-radio input[type="radio"]:checked + .cycle {
            border: 2px solid black;
        }
    </style>
{% endblock %}
{% block content %}
    <div class="container-fluid project">
        <div style="margin: 10px 0;">
            <a class="btn btn-primary" data-toggle="modal" data-target="#addModal">
                <i class="fa fa-plus-circle" aria-hidden="true"></i> 新建项目
            </a>
        </div>
    </div>

    <!-- Modal -->
    <div class="modal fade" id="addModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
                            aria-hidden="true">&times;</span></button>
                    <h4 class="modal-title" id="myModalLabel">新建项目</h4>
                </div>
                <div class="modal-body">

                    <form id="addForm">
                        {% csrf_token %}
                        {% for field in form %}
                            <div class="form-group">
                                <label for="{{ field.id_for_label }}">{{ field.label }}</label>
                                {{ field }}
                                <span class="error-msg"></span>
                            </div>
                        {% endfor %}
                    </form>
                </div>

                <div class="modal-footer">
                    <button type="button" class="btn btn-default" data-dismiss="modal">取 消</button>
                    <button id="btnSubmit" type="button" class="btn btn-primary">确 定</button>
                </div>
            </div>
        </div>
    </div>
{% endblock %}

{% block js %}
    <script>
        $(function () {
            bindSubmit();
        });


        function bindSubmit() {
            $('#btnSubmit').click(function () {

                $.ajax({
                    url: "{% url 'project_list' %}",
                    type: "POST",
                    data: $('#addForm').serialize(),
                    dataType: "JSON",
                    success: function (res) {
                        console.log(res);
                        if (res.status) {
                            location.href = location.href; // 页面主动刷新,同 location.reload()
                            // location.reload()
                        } else {
                            $.each(res.error, function (key, value) {
                                $("#id_" + key).next().text(value[0]);
                            })
                        }
                    }
                })

            })
        }
    </script>
{% endblock %}
  • 第六步 :Form校验,校验项目名是否存在当前用户是否还有额度创建项目
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/forms/project.py   

# -*- coding:utf-8 -*-

from django import forms
from django.core.exceptions import ValidationError

from web_app.forms.bootstrap import BootStrapForm
from web_app import models


class ProjectModelForm(BootStrapForm, forms.ModelForm):

    # 重写__init__ 仅为在views视图调用Form初始化函数式多传递request参数
    def __init__(self, request, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.request = request

    # desc = forms.CharField(widget=forms.Textarea(attrs={'xx': 123}))
    class Meta:
        model = models.Project
        fields = ['name', 'color', 'desc']
        # # widgets 重写字段类型
        widgets = {
            'desc': forms.Textarea,
        }

    def clean_name(self):
        """ 项目校验 """
        # 1、当前用户是否已创建过此项目
        name = self.cleaned_data['name']
        exists = models.Project.objects.filter(name=name, creator=self.request.bug_manager.user).exists()
        print(exists)
        if exists:
            raise ValidationError("项目名称已存在")

        # 2、当前用户是否还有额度进行创建项目
            # 最多创建N个项目
            # self.request.tracer.price_policy.project_num

        # 现在已创建多少项目?
        count = models.Project.objects.filter(creator=self.request.bug_manager.user).count()

        if count >= self.request.bug_manager.price_policy.project_num:
            raise ValidationError('项目个数超限,请购买套餐')
        return name
  • 第七步 :Views视图中接收到Ajax POST数据,传递给Form进行表单验证
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/views/project.py           

from django.shortcuts import render, redirect
from web_app.forms.project import ProjectModelForm
from django.http import JsonResponse

def project_list(request):
    """ 项目列表 """
    if request.method == 'GET':
        form = ProjectModelForm(request=request)
        return render(request, 'project_list.html', {'form': form})

    # POST
    form = ProjectModelForm(data=request.POST, request=request)
    if form.is_valid():
        # 通过验证,创建项目 :用户仅填写了项目名称、颜色、描述 ,查看设计的项目表,只有创建者没有默认值,所以获取创建人再写入数据库,不然会报错

        form.instance.creator = request.bug_manager.user    # 获取创建者
        form.save()
        return JsonResponse({'status': True})
    return JsonResponse({'status': False, 'error': form.errors})
  • 第八步 :创建项目测试,免费版默认为最多创建三个

image

posted @ 2021-07-17 14:09  SRE运维充电站  阅读(217)  评论(0编辑  收藏  举报