轻量级bug管理平台——管理中心

管理中心预计效果如下:

django写离线脚本

探讨业务

设计表结构

我的表结构

功能实现【任务】

  • 查看项目列表
  • 创建项目
  • 星标项目

今日详细

1、django 离线脚本

  •   django ,框架
  •   离线,非web运行时(运行时与django运行与否没有关系)
  • 脚本,一个或者几个py文件
  • 了解以上知识点后在某个py文件中对django项目做一些处理。

 web运行时的含义:运行django程序运行起来就叫django运行时,即Web网站运行起来,对于视图文件的视图函数是通过浏览器中访问网站,django网站自动触发函数,函数被称为web在运行时被django调用的函数。

示例1:使用离线脚本在用户表插入数据(仅运行i脚本文件)

# 仅运行脚本文件时要特地往数据库添加数据:链接数据库、操作、关闭链接
#而在django启动时,实际上是执行manage.py文件,在运行是会把所有的配置文件加载在项目中,。这时候就可以链接数据库并运行了,但离线脚本是不行的。
在django中写离线脚本的程序如下:
在init_user中:
import os
import sys
import django

base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 绝对路径,不断地往上一层找,从而找到它的根目录.
sys.path.append(base_dir) # 将根目录写在sys.path下,在内部就导入s25下面的那个settings

os.environ.setdefault("DJANGO_SETTINGS_MODULE",
"bugmanagment.settings") # 将bugmanagment.settings写入DJANGO_SETTINGS_MODULE环境变量中
django.setup() # os.environ['DJANGO_SETTINGS_MODULE'] #模拟django的manage.py文件,进行模拟运行django启动,从而读到配置文件

from app01 import models

# 仅运行脚本文件时:往数据库添加数据:链接数据库、操作、关闭链接
models.UserInfo.objects.create(username='陈硕', email='chengshuo@live.com', mobile_phone='13838383838', password='123123')

运行结果如下:(加入数据库里面的结果:

示例2:数据库录入全国省市县

示例3:朋友圈项目敏感字、词语

示例4:saas免费版(为所有人提供项目管理平台)【价格策略通过离线脚本添加进去)

注:大量数据要插入数据库中时可以利用一下django的离线脚本进行操作。

2. 探究业务( 表设计)

 

 注:request.tracer = 交易对象

 3

1、创建相应表结构:

知识点扩展:

 

 加入related_name解决一张表关联着同一张表的ForeignKey的问题

class ProjectUser(models.Model):
    # 项目参与者
    user = models.ForeignKey(verbose_name='参与者', to='UserInfo', related_name='a', on_delete=models.CASCADE)
    project = models.ForeignKey(verbose_name='项目', to='Project', on_delete=models.CASCADE)
    star = models.BooleanField(verbose_name='星标', default=False)
    invitee = models.ForeignKey(verbose_name='邀请者', to='UserInfo', related_name='b', on_delete=models.CASCADE)
    create_datetime = models.DateTimeField(verbose_name='加入时间', auto_now_add=True)


obj = UserInfo.objects.filter(id=1)  # 用户对象

# obj.projectuser_set.all()  #通过字段反向关联ProjectUser表,但是因为一张表关联着同一张的ForeignKey,则应该用下一个操作才能实现
obj.a.all()
#或者obj.b.all()

代码:

from django.db import models


class UserInfo(models.Model):
    username = models.CharField(verbose_name='用户名', max_length=32, db_index=True)  # db_index=True 创建索引
    email = models.EmailField(verbose_name='邮箱', max_length=32)
    mobile_phone = models.CharField(verbose_name='手机号', max_length=32)
    password = models.CharField(verbose_name='密码', max_length=32)

    # price_policy = models.ForeignKey(verbose_name="价格策略", to='PricePolicy', null=True, blank=True)

    def __str__(self):
        return self.username

    class Meta:
        db_table = "UserInfo"
        verbose_name_plural = '用户表'


class PricePolicy(models.Model):
    """价格策略"""
    category_choices = (
        (1, '免费版'),
        (2, '收费版'),
        (3, '其他'),
    )
    category = models.SmallIntegerField(verbose_name='收费类型', default=1, choices=category_choices)

    title = models.CharField(verbose_name='标题', max_length=32)
    price = models.PositiveIntegerField(verbose_name='价格')  # 存正整数

    project_num = models.PositiveIntegerField(verbose_name='项目数')
    project_member = models.PositiveIntegerField(verbose_name='项目成员数')
    project_space = models.PositiveIntegerField(verbose_name='单项目空间')
    per_file_size = models.PositiveIntegerField(verbose_name='单文件大小')

    create_datetime = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)


class Transaction(models.Model):
    # 交易记录
    status_choice = (
        (1, '未支付'),
        (2, '已支付')
    )
    status = models.SmallIntegerField(verbose_name='状态', choices=status_choice)

    order = models.CharField(verbose_name='订单号', max_length=64, unique=True)  # 唯一索引
    user = models.ForeignKey(verbose_name='用户', to='UserInfo', on_delete=models.CASCADE)

    price_policy = models.ForeignKey(verbose_name='价格策略', to='PricePolicy', on_delete=models.CASCADE)
    count = models.IntegerField(verbose_name='数量(年)', help_text='0表示无限期')
    price = models.IntegerField(verbose_name='实际支付价格')

    start_datetime = models.DateTimeField(verbose_name='开始时间', null=True, blank=True)
    end_datetime = models.DateTimeField(verbose_name='结束时间', null=True, blank=True)  # 为空代表无限期

    create_datetime = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)


class Project(models.Model):
    """项目表"""
    COLOR_CHOICES = (
        (1, "#56b8eb"),
        (2, "#f28033"),
        (3, "#ebc656"),
        (4, "#a2d148"),
        (5, "#20BFA4"),
        (6, "#7461c2"),
        (7, "#20bfa3"),
    )
    name = models.CharField(verbose_name='项目名', max_length=32)
    color = models.SmallIntegerField(verbose_name='颜色', choices=COLOR_CHOICES, default=1)
    desc = models.CharField(verbose_name='项目描述', max_length=255, null=True, blank=True)
    use_space = models.IntegerField(verbose_name='项目已使用空间', default=0)
    star = models.BooleanField(verbose_name='星标', default=False)
    join_count = models.SmallIntegerField(verbose_name='参与人数', default=1)
    creator = models.ForeignKey(verbose_name='创建者', to='UserInfo', on_delete=models.CASCADE)
    create_datetime = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)

    # 查询:可以省事;(根据project_user字段进行查询)
    # 增加、删除、修改、无法完成
    project_user = models.ManyToManyField(to='UserInfo', through="ProjectUser",
                                          through_fields=('project', 'user'))  # 定义多对多字段


class ProjectUser(models.Model):
    # 项目参与者
    project = models.ForeignKey(verbose_name='项目', to='Project', on_delete=models.CASCADE)
    user = models.ForeignKey(verbose_name='参与者', to='UserInfo', on_delete=models.CASCADE)
    star = models.BooleanField(verbose_name='星标', default=False)
    create_datetime = models.DateTimeField(verbose_name='加入时间', auto_now_add=True)

2、离线脚本

 scripts.py专门放脚本文件。

在base.py中:

# 离线脚本的模版
import django
import os
import sys

base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(base_dir)

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bugmanagment.settings")
django.setup()  # os.environ['DJANGO_SETTINGS_MODULE']

在init_price_policy中:

# 初始化价格策略
import base  # 调用base
from app01 import models


def run():
    exists = models.PricePolicy.objects.filter(category=1, title="个人免费版").exists()
    if not exists:
        models.PricePolicy.objects.create(
            category=1,
            title="个人免费版",
            price=0,
            project_num=3,
            project_member=2,
            project_space=20,
            per_file_size=5
        )


if __name__ == '__main__':
    run()

3、用户注册【对前面的代码进行修改】

  • 以前:创建用户
  • 现在:用户&交易记录(获得免费版的额度)
def register(request):
    # 注册
    if request.method == 'GET':
        form = RegisterModelForm()
        return render(request, 'web/register.html', {'form': form})
    # print(request.POST)  # 后台拿到的数据,传到ModelForm进行校验

    form = RegisterModelForm(data=request.POST)
    if form.is_valid():
        # 验证通过后,写入数据库(密码要是密文)
        # instance = form.save,在数据库中新增一条数据,并将新增的这条数据赋值给instance,指的是当前我们添加的这条数据的用户对象
        # 相当于models.UserInfo.objects.filter(id=1).first()

        # 用户表中新建一条数据(注册)
        # form.instance.password ="iudasndfiajsd;fj" #在保存之前将instance中的password进行重置
        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': '/web/login/'})  
    return JsonResponse({'status': False, 'error': form.errors})

4、添加项目

4.1 项目列表母版+样式

  • 后台:登录成功之后才可以访问
  • 官网:都可以访问
  • 通过中间件+白名单  对后台管理的权限进行处理
  • 当前的拥有的价格策略【额度】

 模态对话框要用ajax请求,而不能用form表单提交。

所有代码:

在母版manage.html中:

{#管理中心模版#}
{% load static %}
<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' %}">

    <style>
        .navbar-inverse {
            border-radius: 0;
        }

        .error-msg {
            color: red;
            position: absolute;
            font-size: 13px;
        }

    </style>
    {% 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="{% 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">

                <li class="dropdown active">
                    <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>

            <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>
                {% if request.tracer %}
                    <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>
                {% else %}
                    <li><a href="{% url 'login' %}">登 录</a></li>
                    <li><a href="{% url 'register' %}">注 册</a></li>
                {% endif %}

            </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</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>

在project_list中:

{% extends 'layout/manage.html' %}
{% block  css %}
    <style>
        .project {
            margin-top: 10px;
        }</style>
{% endblock %}

{% block  content %}
    <div class="container-fluid project">
        <a class="btn btn-primary" data-toggle="modal" data-target="#addModal">
            <i class="fa fa-plus-circle" aria-hidden="true"></i>
            新建项目</a>
    </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 () {
            {#console.log("加載頁面");#}
            bindSubmit();
        });

        function bindSubmit() {
            console.log("加載頁面SSSS");
            $('#btnSubmit').click(function () {
                {#console.log("my");#}
                {#向后台发送ajax请求.#}
                $.ajax({
                    url: "{% url 'project_list' %}",
                    {#POST请求代表添加项目,GET请求代表展示项目#}
                    type: "POST",
                    {#获取表单中的数据#}
                    data: $('#addForm').serialize(),
                    dataType: "JSON",
                    success: function (res) {
                        if (res.status) {
                            console.log(res);
                            {#让页面主动刷新#}
                            {#location.href = location.href;#}
                            location.reload()
                        } else {
                            //错误信息
                            //返回的res是字典:{status:False, error:{mobile_phone:["错误信息",],code:["错误信息”,]}}
                            $.each(res.error, function (key, value) {
                                $("#id_" + key).next().text(value[0]);
                            })
                        }
                    }

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

在views\project.py中:

from django.shortcuts import render

from app01 import models
from web.forms.project import ProjectModelForm
from django.http import JsonResponse


def project_list(request):
    """项目列表"""
    # print(request.transaction.user)
    # print(request.transaction.price_policy.project_num)
    if request.method == "GET":
        return render(request, 'web/project_list.html', {'form': form})
    # POST.对话框的ajax添加项目.
    form = ProjectModelForm(request, data=request.POST)  # 接收到请求(前端是用POST请求),进行表单验证
    if form.is_valid():
        # 验证通过:项目名、颜色、描述+creator谁创建的项目?  其他的内容,项目表中已经有默认值了
        form.instance.creator = request.tracer.user  # 当前登录的用户对象

        # 创建项目
        form.save()  # 在保存之前要让models.py项目表中的字段都有值
        return JsonResponse({'status': True})

    return JsonResponse({'status': False, 'error': form.errors})  # 校验失败,返回错误信息

在forms\project.py中

from django import forms
from app01 import models
from web.forms.bootstrap import BootStrapForm
from django.core.exceptions import ValidationError


class ProjectModelForm(BootStrapForm, forms.ModelForm):
    # desc = forms.CharField(widget=forms.Textarea())

    class Meta:
        model = models.Project
        fields = ['name', 'color', 'desc']
        widgets = {
            'desc': forms.Textarea
        }
    #ModelForm里没有request,要重写init方法,在接口传一个request进来
    def __init__(self, request, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.request = request

    def clean_name(self):
        """项目校验"""
        name = self.cleaned_data['name']
        # 1.当前用户(self.request.tracer.user)是否已创建过此类项目(项目名不能重复)?
        # 1.1 所有人是否已创建过这个项目
        # exists = models.Project.objects.filter(name=name).exists()
        # self.request.tracer.user
        exists = models.Project.objects.filter(name=name, creator=self.request.tracer.user).exists()
        if exists:
            raise ValidationError("项目名已存在")
            # 2.当前用户是否还有额度进行创建项目?
            # 最多创建N个项目
            # 项目额度(self.request.tracer.price_policy.project_num)
            # 现在已创建多少项目?
        count = models.Project.objects.filter(creator=self.request.tracer.user).count()

        if count >=self.request.tracer.price_policy.project_num:
            raise ValidationError('项目个数超限,请购买套餐')

        return name

 

内容概要:

  • 展示项目
  • 星标项目
  • 添加项目:颜色选择
  • 项目切换&颜色选择
  • 项目切换&项目管理菜单处置
  • wiki管理

 1、展示项目:

1.1数据

1、从数据库中获取两部分数据

我创建的所有项目:含有已星标,未星标项目

我参与的所有项目:含有已星标,未星标项目

2、提取已星标项目

列表 = 循环 【我创建的所有项目】+【我参与的所有项目】把已星标的数据提取

3、页面渲染循环三组数据进行页面展示。

得到三个列表 : 星标、创建、参与

2、星标项目(去除星标)

2.1星标

我创建的项目:表中Project的star=True

我参与的项目:表中ProjectUser的star=True

2.2移除星标

我创建的项目:表中Project的star=False 
我参与的项目:
表中ProjectUser的star=False

3.选择颜色

颜色的选择将表中choice默认生成的select标签改成radio标签.

3.1、部分样式应用BootStrap

 

# 面向对象的继承,在form和ModelForm中进行样式的设置
class BootStrapForm(object):
    bootstrap_class_exclude = []  # 支持某个字段不加样式

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for name, field in self.fields.items():  # 循环字段进行样式的设置
            if name in self.bootstrap_class_exclude:  # 不运用bootstrap样式
                continue
            field.widget.attrs['class'] = 'form-control'
            field.widget.attrs['placeholder'] = '请输入%s' % (field.label,)

 

class ProjectModelForm(BootStrapForm, forms.ModelForm):
    bootstrap_class_exclude = ['color']  # 走BootStrapForm中的init方法,把颜色的样式排除掉,此为不走BootStrapForm样式的方法
   
class Meta: model = models.Project fields ="_all_"

3.2定制ModelForm的插件(不用django默认提供的插件,不来自django的forms中)

class ProjectModelForm(BootStrapForm, forms.ModelForm):

    class Meta:
        model = models.Project
        fields = "_all_"
        widgets = {
            'desc': forms.Textarea,
            'color': ColorRadioSelect(attrs={'class': 'color-radio'})
        }

注:'desc'利用的是默认的插件,color用的是自定义的插件。

from django.forms import RadioSelect


class ColorRadioSelect(RadioSelect):

    template_name = 'widgets/color_radio/radio.html'
    option_template_name = 'widgets/color_radio/radio_option.html'
自定义样式:
在radio.html中:
{% with  id=widget.attrs.id %}
    <div {% if id %} id="{{ id }}"{% endif %}{% if widget.attrs.class %} class="{{ widget.attrs.class }}"{% endif %}>
        {% for  group,options,index in widget.optgroups %}
            {% for option in options %}
                <label {% if option.attrs.id %} for="{{ option.attrs.id }}"{% endif %}>
                    {% include option.template_name with widget=option %}
                </label>
            {% endfor %}
        {% endfor %}
    </div>
{% endwith %}

在radio_option.html中:

{% include 'django/forms/widgets/input.html' %}
<span class="cycle" style="background-color: {{ option.label }}"></span>

3.3项目选择颜色

  • 就是3.1、3.2的关于知识点的应用+前端样式的编写

4、切换菜单

1.数据库中获取

  我创建的:

  我参与的:

2.循环显示

3.当前页面需要显示/其他页面也需要显示【模版中自定义的方法——inclusion_tag】

代码如下 :

在templatetags\project.py中:

from django.template import Library

from app01 import models

register = Library()


@register.inclusion_tag('inclusion/all_project_list.html')
def all_project_list(request):
    # 1.获取我创建的所有项目
    my_project_list = models.Project.objects.filter(creator=request.tracer.user)
    # 2.获取我参与的所有项目
    join_project_list = models.ProjectUser.objects.filter(user=request.tracer.user)
    # return {'my': [], 'join': []} #返回一个这样的字典.
    return {'my': my_project_list, 'join': join_project_list}

# 运用all_project_list这个inclusion_tag后,返回模版

在all_project_list.html中

<ul class="nav navbar-nav">

    <li class="dropdown active">
        <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">
{#            my有值才展示#}
            {% if my %}
                <li><i class="fa fa-list" aria-hidden="true"></i>我创建的项目</li>
                {% for item in my %}
                    <li><a href="#">{{ item.name }}</a></li>
                {% endfor %}
                <li role="separator" class="divider"></li>
            {% endif %}
            {% if join %}
                <li><i class="fa fa-handshake-o" aria-hidden="true"></i>我参与的项目</li>

                {% for item in join %}
                    <li><a href="#">{{ item.project.name }}</a></li>
                {% endfor %}

                <li role="separator" class="divider"></li>
            {% endif %}
            <li><a href="{% url 'project_list' %}">所有项目</a></li>
        </ul>
    </li>
</ul>

在母版manage.html中:插入两行代码即可

   {% all_project_list request %}
            {#调用inclusion_tag的时候记得要传入参数request#}

5.项目管理

 写上它的多个功能以及他的路由url:

/manage/项目ID/dashboard

/manage/项目ID/issues

/manage/项目ID/statistics

/manage/项目ID/file

/manage/项目ID/wiki

/manage/项目ID/setting

 5.1 进入项目展示菜单

  •  进入项目
  • 展示菜单

5.1.1 是否进入项目?【访问url前通过访问中间件进行判断】

  • 判断URL是否以manage开头  [以它开头的就是进入了]
  • project_id是我创建or我参与的  (否则进入的很可能是别人的项目或者进不去)

 

5.1.2 显示菜单

依赖:是否已经进入项目?(即request.tracer.project有值表明进入了项目)

  <ul class="nav navbar-nav">
                <li><a href="#">产品功能</a></li>
                <li><a href="#">企业方案</a></li>
                <li><a href="#">帮助文档</a></li>
                <li><a href="#">价格</a></li>

            </ul>

5.1.3 修复bug

5.1.4 默认选中菜单

 总结

  • 项目实现思路
  • 星标/取消星标
  • inclusion_tag实现项目切换
  • 项目菜单
    •  中间件 process_view
    • 点击后默认选中菜单:基于inclusion_tag选中
    • 路由分发
      •   include(”xxx.url")
      • include([ssfdsds,sdf]) #列表,找对应关系
      • 颜色选择:源码+扩展【实现】 (了解即可)  其他的方法例如 :通过下拉框,或者通通过页面自己循环写input框
posted @ 2022-09-09 16:52  费皿啊  阅读(281)  评论(0)    收藏  举报