Browse Source

First Init Project

bigcat 2 years ago
commit
807ad95dc7
100 changed files with 2829 additions and 0 deletions
  1. 3 0
      .gitignore
  2. 8 0
      .idea/.gitignore
  3. 6 0
      .idea/inspectionProfiles/profiles_settings.xml
  4. 29 0
      .idea/limsbackend.iml
  5. 4 0
      .idea/misc.xml
  6. 8 0
      .idea/modules.xml
  7. 6 0
      .idea/vcs.xml
  8. 0 0
      Config/__init__.py
  9. 7 0
      Config/database_mysql.cnf
  10. 13 0
      Dockerfile
  11. 37 0
      README.md
  12. 3 0
      TestLaboratory/__init__.py
  13. 16 0
      TestLaboratory/asgi.py
  14. 285 0
      TestLaboratory/settings.py
  15. 29 0
      TestLaboratory/urls.py
  16. 16 0
      TestLaboratory/wsgi.py
  17. BIN
      api2.0.xlsx
  18. 0 0
      apps/__init__.py
  19. 0 0
      apps/file/__init__.py
  20. 3 0
      apps/file/admin.py
  21. 6 0
      apps/file/apps.py
  22. 30 0
      apps/file/migrations/0001_initial.py
  23. 18 0
      apps/file/migrations/0002_alter_filemanager_id.py
  24. 0 0
      apps/file/migrations/__init__.py
  25. 24 0
      apps/file/models.py
  26. 3 0
      apps/file/tests.py
  27. 7 0
      apps/file/urls.py
  28. 0 0
      apps/file/views/__init__.py
  29. 128 0
      apps/file/views/fileview.py
  30. 0 0
      apps/group/__init__.py
  31. 3 0
      apps/group/admin.py
  32. 6 0
      apps/group/apps.py
  33. 31 0
      apps/group/migrations/0001_initial.py
  34. 19 0
      apps/group/migrations/0002_group_users.py
  35. 18 0
      apps/group/migrations/0003_alter_group_code.py
  36. 0 0
      apps/group/migrations/__init__.py
  37. 25 0
      apps/group/models.py
  38. 3 0
      apps/group/tests.py
  39. 11 0
      apps/group/urls.py
  40. 3 0
      apps/group/views.py
  41. 0 0
      apps/group/views/__init__.py
  42. 93 0
      apps/group/views/grouplistview.py
  43. 46 0
      apps/group/views/groupuserview.py
  44. 57 0
      apps/group/views/groupview.py
  45. 0 0
      apps/log/__init__.py
  46. 3 0
      apps/log/admin.py
  47. 6 0
      apps/log/apps.py
  48. 0 0
      apps/log/middleware/__init__.py
  49. 33 0
      apps/log/middleware/loginfo.py
  50. 35 0
      apps/log/migrations/0001_initial.py
  51. 18 0
      apps/log/migrations/0002_alter_log_id.py
  52. 0 0
      apps/log/migrations/__init__.py
  53. 77 0
      apps/log/models.py
  54. 3 0
      apps/log/tests.py
  55. 6 0
      apps/log/urls.py
  56. 0 0
      apps/log/views/__init__.py
  57. 47 0
      apps/log/views/loglistview.py
  58. 0 0
      apps/plan/__init__.py
  59. 3 0
      apps/plan/admin.py
  60. 6 0
      apps/plan/apps.py
  61. 0 0
      apps/plan/dao/__init__.py
  62. 0 0
      apps/plan/dao/plandao.py
  63. 38 0
      apps/plan/migrations/0001_initial.py
  64. 18 0
      apps/plan/migrations/0002_alter_testplan_id.py
  65. 0 0
      apps/plan/migrations/__init__.py
  66. 68 0
      apps/plan/models.py
  67. 3 0
      apps/plan/tests.py
  68. 9 0
      apps/plan/urls.py
  69. 0 0
      apps/plan/views/__init__.py
  70. 168 0
      apps/plan/views/planlistview.py
  71. 147 0
      apps/plan/views/planview.py
  72. 0 0
      apps/software/__init__.py
  73. 3 0
      apps/software/admin.py
  74. 6 0
      apps/software/apps.py
  75. 49 0
      apps/software/migrations/0001_initial.py
  76. 18 0
      apps/software/migrations/0002_alter_software_name.py
  77. 23 0
      apps/software/migrations/0003_auto_20221004_2044.py
  78. 0 0
      apps/software/migrations/__init__.py
  79. 96 0
      apps/software/models.py
  80. 3 0
      apps/software/tests.py
  81. 0 0
      apps/software/tests/__init__.py
  82. 0 0
      apps/software/tests/softwaretest.py
  83. 16 0
      apps/software/urls.py
  84. 0 0
      apps/software/views/__init__.py
  85. 77 0
      apps/software/views/report.py
  86. 130 0
      apps/software/views/softwarelistview.py
  87. 132 0
      apps/software/views/softwareview.py
  88. 50 0
      apps/software/views/versionlistview.py
  89. 304 0
      apps/software/views/versionview.py
  90. 0 0
      apps/task/__init__.py
  91. 3 0
      apps/task/admin.py
  92. 6 0
      apps/task/apps.py
  93. 74 0
      apps/task/migrations/0001_initial.py
  94. 18 0
      apps/task/migrations/0002_alter_testtask_id.py
  95. 29 0
      apps/task/migrations/0003_auto_20220626_1630.py
  96. 18 0
      apps/task/migrations/0004_alter_testcase_id.py
  97. 0 0
      apps/task/migrations/__init__.py
  98. 69 0
      apps/task/models.py
  99. 3 0
      apps/task/tests.py
  100. 10 0
      apps/task/urls.py

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+.DS_Store
+*.pyc
+__pycache__

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 6 - 0
.idea/inspectionProfiles/profiles_settings.xml

@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <settings>
+    <option name="USE_PROJECT_PROFILE" value="false" />
+    <version value="1.0" />
+  </settings>
+</component>

+ 29 - 0
.idea/limsbackend.iml

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="PYTHON_MODULE" version="4">
+  <component name="FacetManager">
+    <facet type="django" name="Django">
+      <configuration>
+        <option name="rootFolder" value="$MODULE_DIR$" />
+        <option name="settingsModule" value="TestLaboratory/settings.py" />
+        <option name="manageScript" value="$MODULE_DIR$/manage.py" />
+        <option name="environment" value="&lt;map/&gt;" />
+        <option name="doNotUseTestRunner" value="false" />
+        <option name="trackFilePattern" value="migrations" />
+      </configuration>
+    </facet>
+  </component>
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/venv" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+  <component name="PyDocumentationSettings">
+    <option name="format" value="PLAIN" />
+    <option name="myDocStringFormat" value="Plain" />
+  </component>
+  <component name="TemplatesService">
+    <option name="TEMPLATE_CONFIGURATION" value="Django" />
+  </component>
+</module>

+ 4 - 0
.idea/misc.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (limsbackend)" project-jdk-type="Python SDK" />
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/limsbackend.iml" filepath="$PROJECT_DIR$/.idea/limsbackend.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

+ 0 - 0
Config/__init__.py


+ 7 - 0
Config/database_mysql.cnf

@@ -0,0 +1,7 @@
+[client]
+host = 101.35.139.78
+port = 3306
+database = lims-extend
+user = lims-extend
+password = FF8GTRwEzCwRSGZx
+default-character-set = utf8

+ 13 - 0
Dockerfile

@@ -0,0 +1,13 @@
+From python:3.9
+
+MAINTAINER Liufan<1649750212@qq.com>
+ENV PYTHONUNBUFFERED 1
+
+COPY . /code
+WORKDIR /code
+
+RUN pip install -r requirements.txt -i https://pypi.douban.com/simple
+
+EXPOSE 8003
+
+ENTRYPOINT ["/bin/sh","startup.sh"]

+ 37 - 0
README.md

@@ -0,0 +1,37 @@
+# lims测试实验室管理平台
+
+## 作者:刘凡
+
+## 系统地址
+
+http://127.0.0.1:8002 **(本地)**
+
+http://172.26.0.13:8002 **(局域网)**
+
+## 用户操作手册
+
+- 详见 **附件1**
+
+## 源代码地址
+
+- /home/dell/lims/TestLaboratory
+
+## 系统部署安装
+
+### step1:安装mysql镜像
+
+```shell
+docker pull mysql:8.0.26
+```
+
+### step2: 部署前后端镜像
+
+```shell
+docker-compose up -d
+```
+
+## 系统运行环境
+
+- linux系统
+- 浏览器建议使用最新的chrome(老版本浏览器可能出现文件无法下载的问题)
+- docker 2.0

+ 3 - 0
TestLaboratory/__init__.py

@@ -0,0 +1,3 @@
+import pymysql
+pymysql.version_info = (1, 4, 13, "final", 0)
+pymysql.install_as_MySQLdb()

+ 16 - 0
TestLaboratory/asgi.py

@@ -0,0 +1,16 @@
+"""
+ASGI config for TestLaboratory project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'TestLaboratory.settings')
+
+application = get_asgi_application()

+ 285 - 0
TestLaboratory/settings.py

@@ -0,0 +1,285 @@
+"""
+Django settings for TestLaboratory project.
+
+Generated by 'django-admin startproject' using Django 3.2.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/3.2/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/3.2/ref/settings/
+"""
+import datetime
+from pathlib import Path
+import sys
+import os
+
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+# import apps.user.middleware.authentication
+
+BASE_DIR = Path(__file__).resolve().parent.parent
+sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
+
+MEDIA_ROOT = os.path.join(BASE_DIR, 'static')
+MEDIA_URL = '/media/'
+
+SOFTWARE_ROOT = "static/software/"
+PLAN_ROOT = "static/plan-statement/"
+TASK_ROOT = 'static/task-statement/'
+CASE_TEMPLATE_ROOT = 'static/case-template/测试用例模版.xlsx'
+CASE_FILE_ROOT = 'static/case-file/'
+CASE_FILE_EXPORT = 'static/case-file-export/'
+USER_FILE_ROOT = 'static/user-file-export/'
+HTTP_HEAD = 'http://172.26.0.14:8003/'
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'django-insecure-zo64fvv02msf-se7!dek5*w$17#3nh6zta#!i=79bt9d#f88@i'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = ['*']
+# AUTH_USER_MODEL = "user.User"
+# AUTHENTICATION_BACKENDS = (
+#     'user.middleware.authentication.CustomBackend',
+# )
+# Application definition
+
+INSTALLED_APPS = [
+    'simpleui',
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+    'rest_framework',
+    'corsheaders',
+    'user',
+    'software',
+    'plan',
+    'apps.task',
+    'file',
+    'log',
+    'group'
+]
+
+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',
+    'corsheaders.middleware.CorsMiddleware',
+    'django.middleware.common.CommonMiddleware',
+]
+
+ROOT_URLCONF = 'TestLaboratory.urls'
+
+TEMPLATES = [
+    {
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [os.path.join(BASE_DIR, 'templates')],
+        'APP_DIRS': True,
+        'OPTIONS': {
+            'context_processors': [
+                'django.template.context_processors.debug',
+                'django.template.context_processors.request',
+                'django.contrib.auth.context_processors.auth',
+                'django.contrib.messages.context_processors.messages',
+            ],
+        },
+    },
+]
+
+WSGI_APPLICATION = 'TestLaboratory.wsgi.application'
+
+# Database
+# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.mysql',
+        'OPTIONS': {
+            'read_default_file': 'Config/database_mysql.cnf',
+        },
+    }
+}
+
+# Password validation
+# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+    },
+]
+
+REST_FRAMEWORK = {
+    'DEFAULT_PERMISSION_CLASSES': (
+        'rest_framework.permissions.IsAuthenticated'
+    ),
+    'DEFAULT_AUTHENTICATION_CLASSES': (
+        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
+        'rest_framework.authentication.BasicAuthentication',
+        'rest_framework.authentication.SessionAuthentication'
+    )
+}
+
+JWT_AUTH = {
+    # 设置token有效时间
+    'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=60 * 60 * 2)
+}
+
+# Internationalization
+# https://docs.djangoproject.com/en/3.2/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'Asia/Shanghai'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = False
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/3.2/howto/static-files/
+
+STATIC_URL = '/static/'
+STATICFILES_DIRS = [
+    os.path.join(BASE_DIR, "static"),
+]
+
+# Default primary key field type
+# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
+
+DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
+
+REST_FRAMEWORK = {
+    "DEFAULT_AUTHENTICATION_CLASSES": ["apps.user.middleware.authentication.JwtAutentication"]
+}
+
+CORS_ALLOW_CREDENTIALS = True
+CORS_ORIGIN_ALLOW_ALL = True
+
+CORS_ALLOW_METHODS = (
+    'GET',
+    'POST',
+    'PUT',
+    'PATCH',
+    'DELETE',
+    'OPTIONS'
+)
+
+CORS_ALLOW_HEADERS = (
+    'x-requested-with',
+    'content-type',
+    'accept',
+    'origin',
+    'authorization',
+    'x-csrftoken'
+)
+
+import time
+
+cur_path = os.path.dirname(os.path.realpath(__file__))  # log_path是存放日志的路径
+log_path = os.path.join(os.path.dirname(cur_path), 'logs')
+if not os.path.exists(log_path): os.mkdir(log_path)  # 如果不存在这个logs文件夹,就自动创建一个
+
+LOGGING = {
+    'version': 1,
+    'disable_existing_loggers': False,
+    'formatters': {
+        # 日志格式
+        'standard': {
+            'format': '[%(levelname)s] [%(asctime)s] [%(filename)s:%('
+                      'lineno)d] [%(module)s:%(funcName)s] '
+                      '- %(message)s'},
+        'simple': {  # 简单格式
+            'format': '%(levelname)s %(message)s'
+        },
+    },
+    # 过滤
+    'filters': {
+        'require_debug_true': {
+            '()': 'django.utils.log.RequireDebugTrue',  # 过滤掉调试信息
+        },
+    },
+    # 定义具体处理日志的方式
+    'handlers': {
+        # 默认记录所有日志
+        'default': {
+            'level': 'INFO',
+            'class': 'logging.handlers.RotatingFileHandler',
+            'filename': os.path.join(log_path, 'all-{}.log'.format(
+                time.strftime('%Y-%m-%d'))),
+            'maxBytes': 1024 * 1024 * 300,  # 文件大小
+            'backupCount': 10,  # 备份数
+            'formatter': 'standard',  # 输出格式
+            'encoding': 'utf-8',  # 设置默认编码,否则打印出来汉字乱码
+        },
+        # 输出错误日志
+        'error': {
+            'level': 'ERROR',
+            'class': 'logging.handlers.RotatingFileHandler',
+            'filename': os.path.join(log_path, 'error-{}.log'.format(
+                time.strftime('%Y-%m-%d'))),
+            'maxBytes': 1024 * 1024 * 5,  # 文件大小
+            'backupCount': 5,  # 备份数
+            'formatter': 'standard',  # 输出格式
+            'encoding': 'utf-8',  # 设置默认编码
+        },
+        # 控制台输出
+        'console': {
+            'level': 'DEBUG',
+            'class': 'logging.StreamHandler',
+            'formatter': 'standard'
+        },
+        #输出info日志
+        'info': {
+            'level': 'INFO',
+            'class': 'logging.handlers.RotatingFileHandler',
+            'filename': os.path.join(log_path, 'info-{}.log'.format(
+                time.strftime('%Y-%m-%d'))),
+            'maxBytes': 1024 * 1024 * 5,
+            'backupCount': 5,
+            'formatter': 'standard',
+            'encoding': 'utf-8',  # 设置默认编码
+        },
+    },
+    # 配置用哪几种 handlers 来处理日志
+    'loggers': {
+        # 类型 为 django 处理所有类型的日志, 默认调用
+        'django': {
+            'handlers': ['default', 'console'],
+            'level': 'INFO',
+            'propagate': False
+        },
+        # log 调用时需要当作参数传入
+        'log': {
+            'handlers': ['error', 'info', 'console', 'default'],
+            'level': 'INFO',
+            'propagate': True
+        },
+    }
+}
+"""
+python manage.py runserver 172.16.100.180:8000   
+"""

+ 29 - 0
TestLaboratory/urls.py

@@ -0,0 +1,29 @@
+"""TestLaboratory URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+    https://docs.djangoproject.com/en/3.2/topics/http/urls/
+Examples:
+Function views
+    1. Add an import:  from my_app import views
+    2. Add a URL to urlpatterns:  path('', views.home, name='home')
+Class-based views
+    1. Add an import:  from other_app.views import Home
+    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
+Including another URLconf
+    1. Import the include() function: from django.urls import include, path
+    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
+"""
+from django.conf.urls.static import static
+from django.urls import path, include
+
+from TestLaboratory import settings
+
+urlpatterns = [
+    path('api/v1/users/', include('user.urls')),
+    path('api/v1/software/', include('software.urls')),
+    path('api/v1/test-plans/', include('plan.urls')),
+    path('api/v1/test-tasks/', include('task.urls')),
+    path('api/v1/log-info/', include('log.urls')),
+    path('api/v1/files/', include('file.urls')),
+    path('api/v1/group/', include('group.urls'))
+] + static(settings.STATIC_URL, document_root=settings.STATICFILES_DIRS)

+ 16 - 0
TestLaboratory/wsgi.py

@@ -0,0 +1,16 @@
+"""
+WSGI config for TestLaboratory project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'TestLaboratory.settings')
+
+application = get_wsgi_application()

BIN
api2.0.xlsx


+ 0 - 0
apps/__init__.py


+ 0 - 0
apps/file/__init__.py


+ 3 - 0
apps/file/admin.py

@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.

+ 6 - 0
apps/file/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class FileConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'apps.file'

+ 30 - 0
apps/file/migrations/0001_initial.py

@@ -0,0 +1,30 @@
+# Generated by Django 3.2 on 2021-07-17 20:05
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='FileManager',
+            fields=[
+                ('id', models.CharField(default='TestLaboratory_V1_File_1', max_length=30, primary_key=True, serialize=False, unique=True, verbose_name='文件id')),
+                ('path', models.FilePathField(max_length=200, verbose_name='文件路径')),
+                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
+                ('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
+                ('delete', models.BooleanField(default=False)),
+                ('category', models.CharField(max_length=20, verbose_name='文件类型')),
+            ],
+            options={
+                'verbose_name': '文件管理',
+                'verbose_name_plural': '文件管理',
+                'db_table': 'file_manager',
+            },
+        ),
+    ]

+ 18 - 0
apps/file/migrations/0002_alter_filemanager_id.py

@@ -0,0 +1,18 @@
+# Generated by Django 3.2.4 on 2022-06-19 10:43
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('file', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='filemanager',
+            name='id',
+            field=models.CharField(default='TestLaboratory_V1_File_1', max_length=128, primary_key=True, serialize=False, unique=True, verbose_name='文件id'),
+        ),
+    ]

+ 0 - 0
apps/file/migrations/__init__.py


+ 24 - 0
apps/file/models.py

@@ -0,0 +1,24 @@
+from django.db import models
+
+
+# Create your models here.
+class FileManager(models.Model):
+    id = models.CharField(max_length=128, unique=True, verbose_name='文件id', primary_key=True,
+                          default='TestLaboratory_V1_File_1')
+    path = models.FilePathField(max_length=200, verbose_name='文件路径')
+    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
+    update_time = models.DateTimeField(auto_now=True, verbose_name='修改时间')
+    delete = models.BooleanField(default=False)
+    # category: software, report, plan_statement, task_statement, case_file
+    category = models.CharField(max_length=20, verbose_name='文件类型')
+
+    def __str__(self):
+        return self.path
+
+    class Meta:
+        verbose_name = '文件管理'
+        verbose_name_plural = verbose_name
+        db_table = 'file_manager'
+
+
+

+ 3 - 0
apps/file/tests.py

@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.

+ 7 - 0
apps/file/urls.py

@@ -0,0 +1,7 @@
+from django.urls import path
+
+from .views.fileview import FileView
+
+urlpatterns = [
+    path('', FileView.as_view()),
+]

+ 0 - 0
apps/file/views/__init__.py


+ 128 - 0
apps/file/views/fileview.py

@@ -0,0 +1,128 @@
+import datetime
+import os
+
+from django.http import HttpResponse
+from rest_framework.views import APIView
+
+from TestLaboratory.settings import PLAN_ROOT, TASK_ROOT
+from apps.file.models import FileManager
+from apps.log.models import get_log, gen_log
+from apps.plan.models import TestPlan
+from apps.task.models import TestTask
+from apps.user.middleware.rolecontrol import RoleControl
+from utils.util_add_id import gen_next_id
+
+import logging
+logger = logging.getLogger('django')
+
+
+class FileView(APIView):
+    # create file
+    @staticmethod
+    @RoleControl
+    def post(request, *args, **kwargs):
+        file_type = request.POST.get('type')
+        plan_id = request.POST.get('plan_id')
+        task_id = request.POST.get('task_id')
+        files = request.FILES.getlist('files')
+
+        model_object = None
+        path_head = None
+        if file_type == 'plan_statement':
+            model_object = TestPlan.objects.filter(id=plan_id, delete=False)
+            if not model_object:
+                logger.error("测试计划已删除或不存在")
+                return HttpResponse(status=404, content='测试计划已删除或不存在')
+            model_object = model_object[0]
+            path_head = PLAN_ROOT
+
+        elif file_type == 'task_statement':
+            model_object = TestTask.objects.filter(id=task_id, delete=False)
+            if not model_object:
+                logger.error("测试任务已删除或不存在")
+                return HttpResponse(status=404, content='测试任务已删除或不存在')
+            model_object = model_object[0]
+            path_head = TASK_ROOT
+
+        # step7:创建文件的数据库字段
+        fids = ''
+        file_paths = []
+        for file in files:
+            try:
+                create_time = update_time = datetime.datetime.now()
+                file_dir = path_head + "/" + model_object.id + "/" + str(create_time)
+                if not os.path.exists(file_dir):
+                    os.makedirs(file_dir)
+                file_path = file_dir + "/" + file.name
+                with open(file_path, 'wb') as f:
+                    f.write(file.read())
+                f.close()
+            except:
+                logger.error("文件上传失败")
+                return HttpResponse(status=500, content="文件上传失败")
+
+            if not FileManager.objects.last():
+                id_file = "TestLaboratory_V1_File_1"
+            else:
+                id_file = gen_next_id(FileManager.objects.order_by('create_time').last().id)
+
+            try:
+                file_new = FileManager.objects.create(id=id_file, category=file_type,
+                                                      path=model_object.id+ "/" + str(create_time) + "/" + file.name,
+                                                      create_time=create_time,
+                                                      update_time=update_time)
+            except:
+                logger.error("文件路径写入数据库失败")
+                return HttpResponse(status=500, content="文件路径写入数据库失败")
+            file_paths.append(PLAN_ROOT + file_new.path)
+            fids += id_file + "&&"
+        fids = fids[:-2]
+        model_object.statement_file += ("&&" + fids)
+        model_object.save()
+        executor, action, method = get_log(request)
+        gen_log(action, "说明文档", model_object.title, method, executor)
+        return HttpResponse(status=200, content='新建文件成功')
+
+    # create file
+    @staticmethod
+    @RoleControl
+    def delete(request, *args, **kwargs):
+        file_id = request.GET.get('file_id')
+        file_type = request.GET.get('type')
+        plan_id = request.GET.get('plan_id')
+        task_id = request.GET.get('task_id')
+
+        model_object = None
+        if file_type == 'plan_statement':
+            model_object = TestPlan.objects.filter(id=plan_id, delete=False)
+            if not model_object:
+                logger.error("测试计划已删除或不存在")
+                return HttpResponse(status=404, content='测试计划已删除或不存在')
+            model_object = model_object[0]
+
+        elif file_type == 'task_statement':
+            model_object = TestTask.objects.filter(id=task_id, delete=False)
+            if not model_object:
+                logger.error("测试任务已删除或不存在")
+                return HttpResponse(status=404, content='测试任务已删除或不存在')
+            model_object = model_object[0]
+
+        file = FileManager.objects.filter(id=file_id, delete=False)
+        if not file:
+            logger.error("文件已删除不存在")
+            return HttpResponse(status=404, content='文件已删除不存在')
+        file = file[0]
+        file_list = model_object.statement_file.split('&&')
+        if file.id not in file_list:
+            logger.error("该文件不存在于测试计划或测试任务中,数据库错误")
+            return HttpResponse(status=404, content='该文件不存在于测试计划或测试任务中,数据库错误')
+        file_list.remove(file.id)
+        file_str = ''
+        for file_name in file_list:
+            file_str += file_name + "&&"
+        file_str = file_str[:-2]
+        model_object.statement_file = file_str
+        model_object.save()
+        executor, action, method = get_log(request)
+        gen_log(action, "说明文档", file.path.split('/')[-1], method, executor)
+        return HttpResponse(status=200, content='删除成功')

+ 0 - 0
apps/group/__init__.py


+ 3 - 0
apps/group/admin.py

@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.

+ 6 - 0
apps/group/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class GroupConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'apps.group'

+ 31 - 0
apps/group/migrations/0001_initial.py

@@ -0,0 +1,31 @@
+# Generated by Django 3.2.4 on 2022-10-04 20:49
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Group',
+            fields=[
+                ('name', models.CharField(max_length=128, verbose_name='组名')),
+                ('id', models.CharField(default='TestLaboratory_V1_Group_1', max_length=128, primary_key=True, serialize=False, unique=True, verbose_name='分组id')),
+                ('code', models.IntegerField(verbose_name='分组code')),
+                ('manager', models.CharField(max_length=60, verbose_name='管理员id')),
+                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
+                ('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
+                ('delete', models.BooleanField(default=False)),
+            ],
+            options={
+                'verbose_name': '被测试软件',
+                'verbose_name_plural': '被测试软件',
+                'db_table': 'group',
+            },
+        ),
+    ]

+ 19 - 0
apps/group/migrations/0002_group_users.py

@@ -0,0 +1,19 @@
+# Generated by Django 3.2.4 on 2022-10-05 17:30
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('user', '0003_remove_user_group'),
+        ('group', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='group',
+            name='users',
+            field=models.ManyToManyField(to='user.User', verbose_name='成员'),
+        ),
+    ]

+ 18 - 0
apps/group/migrations/0003_alter_group_code.py

@@ -0,0 +1,18 @@
+# Generated by Django 3.2.4 on 2022-10-05 19:27
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('group', '0002_group_users'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='group',
+            name='code',
+            field=models.CharField(max_length=12, verbose_name='分组code'),
+        ),
+    ]

+ 0 - 0
apps/group/migrations/__init__.py


+ 25 - 0
apps/group/models.py

@@ -0,0 +1,25 @@
+from django.db import models
+
+
+# Create your models here.
+from apps.user.models import User
+
+
+class Group(models.Model):
+    name = models.CharField(max_length=128, verbose_name='组名')
+    id = models.CharField(max_length=128, unique=True, primary_key=True, verbose_name='分组id',
+                          default='TestLaboratory_V1_Group_1')
+    code = models.CharField(max_length=12, verbose_name='分组code')
+    manager = models.CharField(max_length=60, verbose_name='管理员id')
+    users = models.ManyToManyField(User, verbose_name='成员')
+    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
+    update_time = models.DateTimeField(auto_now=True, verbose_name='修改时间')
+    delete = models.BooleanField(default=False)
+
+    def __str__(self):
+        return self.name
+
+    class Meta:
+        verbose_name = '被测试软件'
+        verbose_name_plural = verbose_name
+        db_table = 'group'

+ 3 - 0
apps/group/tests.py

@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.

+ 11 - 0
apps/group/urls.py

@@ -0,0 +1,11 @@
+from django.urls import path
+from .views.grouplistview import GroupListView
+from .views.groupuserview import GroupUserView
+from .views.groupview import GroupView, GroupEdit
+
+urlpatterns = [
+    path('', GroupListView.as_view()),
+    path('user', GroupUserView.as_view()),
+    path('<str:group_id>', GroupView.as_view()),
+    path('<str:group_id>/edit', GroupEdit.as_view())
+]

+ 3 - 0
apps/group/views.py

@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.

+ 0 - 0
apps/group/views/__init__.py


+ 93 - 0
apps/group/views/grouplistview.py

@@ -0,0 +1,93 @@
+import datetime
+import logging
+
+from django.http import HttpResponse
+from rest_framework.views import APIView
+from rest_framework.response import Response
+
+from apps.group.models import Group
+from apps.log.models import get_log, gen_log
+from apps.user.middleware.rolecontrol import RoleControl
+from apps.user.models import User
+from utils.util_add_id import get_id
+from utils.util_random import Produce
+
+logger = logging.getLogger('django')
+
+
+class GroupListView(APIView):
+    # create group
+    @staticmethod
+    @RoleControl
+    def post(request, *args, **kwargs):
+        name = request.POST.get('name')
+        manager_id = request.POST.get('manager_id')
+        manager = User.objects.filter(id=manager_id)
+        if not manager:
+            logger.error("用户不存在")
+            return HttpResponse(status=500, content='用户不存在')
+        manager = manager[0]
+        group = Group.objects.filter(name=name, delete=False)
+        if group:
+            logger.error("组名已存在,请修改组名重新上传")
+            return HttpResponse(status=500, content='组名已存在,请修改组名重新上传')
+        create_time = update_time = datetime.datetime.now()
+        group_id = get_id(Group, 'TestLaboratory_V1_Group_1')
+        code = Produce.auth_code(option='num')
+        group = Group.objects.create(id=group_id, code=code, manager=manager_id, name=name, create_time=create_time,
+                                     update_time=update_time)
+        executor, action, method = get_log(request)
+        gen_log(action, "分组", group.name, method, executor)
+        return Response({
+            'id': group.id,
+            'name': group.name,
+            'code': group.code,
+            'manager': manager.username,
+        })
+
+    # view log_list
+    @staticmethod
+    @RoleControl
+    def get(request, *args, **kwargs):
+        group_id = request.GET.get('id')
+        name = request.GET.get('name')
+        code = request.GET.get('code')
+        manager_id = request.GET.get('manager_id')
+        user_id = request.GET.get('user_id')
+
+        groups = Group.objects.order_by('-create_time').filter(delete=False)
+        if group_id:
+            groups = groups.filter(id=group_id)
+        if name:
+            groups = groups.filter(name=name)
+        if code:
+            groups = groups.filter(code=code)
+        if manager_id:
+            groups = groups.filter(manager=manager_id)
+        if user_id:
+            user = User.objects.filter(id=user_id)
+            if not user:
+                logger.error("用户不存在")
+                return HttpResponse(status=500, content='用户不存在')
+            user = user[0]
+            groups = groups & user.group_set.all()
+
+        return_groups = list()
+        for group in groups:
+            manager = User.objects.filter(id=group.manager)[0]
+            user_info = list()
+            users = group.users.order_by('-create_time')
+            for user in users:
+                user_info.append({
+                    'id': user.id,
+                    'username': user.username,
+                    'identity': user.identify
+                })
+            return_groups.append({
+                'id': group.id,
+                'name': group.name,
+                'code': group.code,
+                'manager': manager.username,
+                'members': user_info
+            })
+        return Response(return_groups)

+ 46 - 0
apps/group/views/groupuserview.py

@@ -0,0 +1,46 @@
+import logging
+
+from rest_framework.views import APIView
+from rest_framework.response import Response
+
+from apps.group.models import Group
+from apps.user.middleware.rolecontrol import RoleControl
+from apps.user.models import User
+
+logger = logging.getLogger('django')
+
+
+class GroupUserView(APIView):
+    # create group
+    @staticmethod
+    @RoleControl
+    def get(request, *args, **kwargs):
+        groups = Group.objects.filter(delete=False)
+        users = set(User.objects.filter(delete=False))
+
+        users_info = []
+        for group in groups:
+            user_group = {'groupName': group.name,
+                          'userList': list()
+                          }
+            users = users - set(group.users.all())
+            for user in group.users.all():
+                user_group['userList'].append({
+                    'userid': user.id,
+                    'username': user.username,
+                    'identify': user.identify,
+                    'is_login': user.is_login
+                })
+            users_info.append(user_group)
+        user_group = {'groupName': '默认分组',
+                      'userList': list()
+                      }
+        for user in users:
+            user_group['userList'].append({
+                'userid': user.id,
+                'username': user.username,
+                'identify': user.identify,
+                'is_login': user.is_login
+            })
+        users_info.append(user_group)
+        return Response(users_info)

+ 57 - 0
apps/group/views/groupview.py

@@ -0,0 +1,57 @@
+from django.http import HttpResponse
+from rest_framework.response import Response
+from rest_framework.views import APIView
+
+from apps.group.models import Group
+from apps.log.models import get_log, gen_log
+from apps.user.middleware.rolecontrol import RoleControl
+from apps.user.models import User
+
+import logging
+logger = logging.getLogger('django')
+
+
+class GroupView(APIView):
+    authentication_classes = []
+
+    # 删除分组
+    @staticmethod
+    @RoleControl
+    def delete(request, group_id, *args, **kwargs):
+        group = Group.objects.filter(id=group_id, delete=False)
+        if not group:
+            logger.error("分组已删除或不存在")
+        group = group[0]
+        users = group.users.all()
+        for user in users:
+            group.users.remove(user)
+        group.delete = True
+        group.save()
+        executor, action, method = get_log(request)
+        gen_log(action, "分组", group.name, method, executor)
+        return HttpResponse("删除完成")
+
+
+class GroupEdit(APIView):
+    authentication_classes = []
+
+    # 编辑分组
+    @staticmethod
+    @RoleControl
+    def post(request, group_id, *args, **kwargs):
+        name = request.POST.get('group_name')
+
+        if not name:
+            logger.error("组名不能位空")
+            return HttpResponse(status=500, content='组名不能位空')
+
+        group = Group.objects.filter(id=group_id, delete=False)
+        if not group:
+            logger.error("id为" + group_id + "的分组不存在")
+            return HttpResponse(status=404, content="id为" + group_id + "的分组不存在")
+        group = group[0]
+        group.name = name
+        group.save()
+        executor, action, method = get_log(request)
+        gen_log(action, "分组", group.name, method, executor)
+        return HttpResponse(status=200, content='修改已保存')

+ 0 - 0
apps/log/__init__.py


+ 3 - 0
apps/log/admin.py

@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.

+ 6 - 0
apps/log/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class LogInfoConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'apps.log'

+ 0 - 0
apps/log/middleware/__init__.py


+ 33 - 0
apps/log/middleware/loginfo.py

@@ -0,0 +1,33 @@
+import re
+
+import jwt
+from django.http import HttpResponse
+
+from TestLaboratory.settings import SECRET_KEY
+from apps.user.models import User, Role
+
+
+class LogInfo(object):
+    """使用类装饰器来权限校验"""
+    def __init__(self, func):
+        self.func = func
+
+    def __call__(self, request, *args, **kwargs):
+        token = request.META.get('HTTP_ACCESSTOKEN')
+        SALT = SECRET_KEY
+
+        payload = jwt.decode(token, SALT, True)
+
+        user = User.objects.get(username=payload['username'])
+
+        role = Role.objects.get(id="TestLaboratory_V1_Role_"+str(user.identify+1))
+        permission = role.permissions.filter(method=request.method)
+
+        log = None
+        for per in permission:
+            if re.search(per.url, request.path[:-1]):
+                log = per
+                break
+
+        return self.func(request, *args, **kwargs)
+

+ 35 - 0
apps/log/migrations/0001_initial.py

@@ -0,0 +1,35 @@
+# Generated by Django 3.2 on 2021-07-17 20:05
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Log',
+            fields=[
+                ('id', models.CharField(default='TestLaboratory_V1_Log_1', max_length=50, primary_key=True, serialize=False, verbose_name='日志id')),
+                ('action', models.CharField(max_length=100, verbose_name='操作名称')),
+                ('entity_class', models.CharField(max_length=50, verbose_name='实体类型')),
+                ('entity_name', models.CharField(max_length=50, null=True, verbose_name='实体名称')),
+                ('method', models.CharField(max_length=30, verbose_name='操作方法')),
+                ('executor_id', models.CharField(max_length=50, verbose_name='操作者id')),
+                ('executor_username', models.CharField(max_length=50, verbose_name='操作者名称')),
+                ('executor_identify', models.IntegerField(verbose_name='操作者身份')),
+                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
+                ('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
+                ('delete', models.BooleanField(default=False)),
+            ],
+            options={
+                'verbose_name': '日志',
+                'verbose_name_plural': '日志',
+                'db_table': 'log',
+            },
+        ),
+    ]

+ 18 - 0
apps/log/migrations/0002_alter_log_id.py

@@ -0,0 +1,18 @@
+# Generated by Django 3.2.4 on 2022-06-19 10:43
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('log', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='log',
+            name='id',
+            field=models.CharField(default='TestLaboratory_V1_Log_1', max_length=128, primary_key=True, serialize=False, verbose_name='日志id'),
+        ),
+    ]

+ 0 - 0
apps/log/migrations/__init__.py


+ 77 - 0
apps/log/models.py

@@ -0,0 +1,77 @@
+import datetime
+import re
+
+import jwt
+from django.db import models
+
+# Create your models here.
+from TestLaboratory.settings import SECRET_KEY
+from apps.user.models import User, Role
+from utils.util_add_id import get_id, gen_next_id
+
+
+class Log(models.Model):
+    id = models.CharField(max_length=128, primary_key=True, verbose_name='日志id', default='TestLaboratory_V1_Log_1')
+    action = models.CharField(max_length=100, verbose_name='操作名称')
+    entity_class = models.CharField(max_length=50, verbose_name='实体类型')
+    entity_name = models.CharField(max_length=50, null=True, verbose_name='实体名称')
+    method = models.CharField(max_length=30, verbose_name='操作方法')
+    executor_id = models.CharField(max_length=50, verbose_name='操作者id')
+    executor_username = models.CharField(max_length=50, verbose_name='操作者名称')
+    executor_identify = models.IntegerField(verbose_name='操作者身份')
+    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
+    update_time = models.DateTimeField(auto_now=True, verbose_name='修改时间')
+    delete = models.BooleanField(default=False)
+
+    def __str__(self):
+        return self.executor_username + self.action
+
+    class Meta:
+        verbose_name = '日志'
+        verbose_name_plural = verbose_name
+        db_table = 'log'
+
+
+def gen_log(action, entity_class, entity_name, method, executor):
+    log_id = get_id(Log, "TestLaboratory_V1_Log_1")
+    create_time = update_time = datetime.datetime.now()
+    try:
+        log_new = Log.objects.create(id=log_id, action=action, entity_class=entity_class, entity_name=entity_name,
+                                     method=method, executor_id=executor.id, executor_username=executor.username,
+                                     executor_identify=executor.identify, create_time=create_time,
+                                     update_time=update_time)
+    except:
+        log_id = gen_next_id(log_id)
+        log_new = Log.objects.create(id=log_id, action=action, entity_class=entity_class, entity_name=entity_name,
+                                     method=method, executor_id=executor.id, executor_username=executor.username,
+                                     executor_identify=executor.identify, create_time=create_time,
+                                     update_time=update_time)
+
+
+def get_log(request):
+    token = request.META.get('HTTP_ACCESSTOKEN')
+    SALT = SECRET_KEY
+
+    payload = jwt.decode(token, SALT, True)
+
+    user = User.objects.get(id=payload['userid'])
+
+    role = Role.objects.get(id="TestLaboratory_V1_Role_" + str(user.identify + 1))
+    permission = role.permissions.filter(method=request.method)
+    # print(user.username)
+
+    perm = None
+    for per in permission:
+        path = request.path
+        if request.path[-1] == '/':
+            path = request.path[:-1]
+        # print(per.url, per.name, path, re.search(per.url, path))
+
+        if re.search(per.url, path):
+            # print("匹配", per.url, request.path)
+            if not perm:
+                perm = per
+            elif len(per.url) > len(perm.url):
+                perm = per
+    # print(perm.url, perm.name, perm.method)
+    return user, perm.name, perm.method

+ 3 - 0
apps/log/tests.py

@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.

+ 6 - 0
apps/log/urls.py

@@ -0,0 +1,6 @@
+from django.urls import path
+from .views.loglistview import LogListView
+
+urlpatterns = [
+    path('', LogListView.as_view())
+]

+ 0 - 0
apps/log/views/__init__.py


+ 47 - 0
apps/log/views/loglistview.py

@@ -0,0 +1,47 @@
+from django.db.models import Q
+from rest_framework.response import Response
+from rest_framework.views import APIView
+from django.http import HttpResponse
+
+from apps.log.models import Log
+
+
+class LogListView(APIView):
+    # view log_list
+    @staticmethod
+    def get(request, *args, **kwargs):
+        entity_class = request.GET.get('entity_class')
+        action = request.GET.get('action')
+        executor_username = request.GET.get('executor_username')
+        executor_identify = request.GET.get('executor_identify')
+        sort = request.GET.get('sort')
+        dont_get = request.GET.get('dontGet')
+
+        log_all = Log.objects.order_by('-create_time')
+        if entity_class:
+            log_all = log_all.filter(entity_class=entity_class)
+        if action:
+            log_all = log_all.filter(method=action)
+        if executor_username:
+            log_all = log_all.filter(executor_username=executor_username)
+        if executor_identify:
+            log_all = log_all.filter(executor_identify=executor_identify)
+        if dont_get == '1':
+            log_all = log_all.filter(~Q(method='GET'))
+        if sort:
+            log_all = log_all.order_by(sort)
+
+        log_infos = []
+        for log in log_all:
+            log_infos.append({
+                'entity_class': log.entity_class,
+                'entity_name': log.entity_name,
+                'action': log.action,
+                'method': log.method,
+                'executor_id': log.executor_id,
+                'executor_username': log.executor_username,
+                'executor_identify': log.executor_identify,
+                'execute_time': log.create_time
+            })
+
+        return Response(log_infos)

+ 0 - 0
apps/plan/__init__.py


+ 3 - 0
apps/plan/admin.py

@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.

+ 6 - 0
apps/plan/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class TestPlanConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'apps.plan'

+ 0 - 0
apps/plan/dao/__init__.py


+ 0 - 0
apps/plan/dao/plandao.py


+ 38 - 0
apps/plan/migrations/0001_initial.py

@@ -0,0 +1,38 @@
+# Generated by Django 3.2 on 2021-07-17 20:05
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ('user', '0001_initial'),
+        ('software', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='TestPlan',
+            fields=[
+                ('id', models.CharField(default='TestLaboratory_V1_Plan_1', max_length=30, primary_key=True, serialize=False, unique=True, verbose_name='测试计划编号')),
+                ('title', models.CharField(max_length=30, verbose_name='测试计划标题')),
+                ('version', models.CharField(max_length=20, verbose_name='软件版本')),
+                ('state', models.IntegerField(verbose_name='测试计划执行状态')),
+                ('description', models.TextField(verbose_name='测试计划描述')),
+                ('statement_file', models.TextField(verbose_name='说明文档id路径')),
+                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
+                ('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
+                ('delete', models.BooleanField(default=False)),
+                ('creator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='user.user', verbose_name='测试计划执行者')),
+                ('software', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='software.software', verbose_name='测试软件')),
+            ],
+            options={
+                'verbose_name': '测试计划',
+                'verbose_name_plural': '测试计划',
+                'db_table': 'plan',
+            },
+        ),
+    ]

+ 18 - 0
apps/plan/migrations/0002_alter_testplan_id.py

@@ -0,0 +1,18 @@
+# Generated by Django 3.2.4 on 2022-06-19 10:43
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('plan', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='testplan',
+            name='id',
+            field=models.CharField(default='TestLaboratory_V1_Plan_1', max_length=128, primary_key=True, serialize=False, unique=True, verbose_name='测试计划编号'),
+        ),
+    ]

+ 0 - 0
apps/plan/migrations/__init__.py


+ 68 - 0
apps/plan/models.py

@@ -0,0 +1,68 @@
+import os
+
+from django.db import models
+
+# Create your models here.
+from TestLaboratory.settings import PLAN_ROOT
+from apps.file.models import FileManager
+from apps.software.models import Software
+from apps.user.models import User
+from utils.util_add_id import gen_next_id
+
+
+class TestPlan(models.Model):
+    id = models.CharField(max_length=128, unique=True, primary_key=True, verbose_name='测试计划编号',
+                          default='TestLaboratory_V1_Plan_1')
+    title = models.CharField(max_length=30, verbose_name='测试计划标题')
+    software = models.ForeignKey(Software, on_delete=models.CASCADE, verbose_name='测试软件')
+    version = models.CharField(max_length=20, verbose_name='软件版本')
+    creator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, verbose_name='测试计划执行者')
+    state = models.IntegerField(verbose_name='测试计划执行状态')
+    description = models.TextField(verbose_name='测试计划描述')
+    statement_file = models.TextField(verbose_name='说明文档id路径')
+    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
+    update_time = models.DateTimeField(auto_now=True, verbose_name='修改时间')
+    delete = models.BooleanField(default=False)
+
+    def __str__(self):
+        return self.title
+
+    class Meta:
+        verbose_name = '测试计划'
+        verbose_name_plural = verbose_name
+        db_table = 'plan'
+
+
+# def save_files(files, plan_id, time_stamp):
+#     """
+#
+#     """
+#     fids = []
+#     file_paths = []
+#     for file in files:
+#         try:
+#             file_dir = PLAN_ROOT + "/" + plan_id + "/" + str(time_stamp)
+#             if not os.path.exists(file_dir):
+#                 os.makedirs(file_dir)
+#             file_path = file_dir + "/" + file.name
+#             with open(file_path, 'wb') as f:
+#                 f.write(file.read())
+#             f.close()
+#         except:
+#             return 0
+#
+#         if not FileManager.objects.last():
+#             id_file = "TestLaboratory_V1_File_1"
+#         else:
+#             id_file = gen_next_id(FileManager.objects.order_by('create_time').last().id)
+#
+#         try:
+#             file_new = FileManager.objects.create(id=id_file, category="plan_statement",
+#                                                   path=plan_id + "/" + str(time_stamp) + "/" + file.name,
+#                                                   create_time=time_stamp,
+#                                                   update_time=time_stamp)
+#         except:
+#             return 1
+#         file_paths.append(PLAN_ROOT + file_new.path)
+#         fids.append(id_file)
+#     return file_paths, fids

+ 3 - 0
apps/plan/tests.py

@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.

+ 9 - 0
apps/plan/urls.py

@@ -0,0 +1,9 @@
+from django.urls import path
+from .views.planlistview import PlanListView
+from .views.planview import PlanView, PlanEdit
+
+urlpatterns = [
+    path('', PlanListView.as_view()),
+    path('<str:plan_id>', PlanView.as_view()),
+    path('<str:plan_id>/edit', PlanEdit.as_view())
+]

+ 0 - 0
apps/plan/views/__init__.py


+ 168 - 0
apps/plan/views/planlistview.py

@@ -0,0 +1,168 @@
+import datetime
+import json
+import os
+
+from rest_framework.response import Response
+from rest_framework.views import APIView
+from django.http import HttpResponse
+
+from apps.file.models import FileManager
+from apps.log.models import get_log, gen_log
+from apps.plan.models import TestPlan
+from apps.software.models import Software
+from apps.user.middleware.rolecontrol import RoleControl
+from apps.user.models import User
+from utils.util_add_id import gen_next_id, get_id
+from TestLaboratory.settings import PLAN_ROOT
+
+import logging
+logger = logging.getLogger('django')
+
+
+class PlanListView(APIView):
+    # 登录权限验证
+    authentication_classes = []
+
+    # create plan
+    @staticmethod
+    @RoleControl
+    def post(request, *args, **kwargs):
+        software_id = request.POST.get('software_id')
+        version_number = request.POST.get('version_number')
+        title = request.POST.get('title')
+        description = request.POST.get('description')
+        files = request.FILES.getlist('files')
+        creator_id = request.POST.get('creator')
+
+        # step1:判断所属软件及版本是否存在
+        software = Software.objects.filter(id=software_id, delete=False)
+        if not software:
+            logger.error("软件已删除或不存在")
+            return HttpResponse(status=404, content='软件已删除或不存在')
+        software = software[0]
+
+        # 判断版本是否存在
+        software_version = software.version_set.filter(number=version_number, delete=False)
+        if not software_version:
+            logger.error("软件版本不存在或已删除,请重新选择")
+            return HttpResponse(status=404, content='软件版本不存在或已删除,请重新选择')
+        software_version = software_version[0]
+
+        # 判断用户是否存在
+        creator = User.objects.filter(id=creator_id, delete=False)
+        if not creator:
+            logger.error("用户不存在,创建失败")
+            return HttpResponse(status=404, content='用户不存在,创建失败')
+        creator = creator[0]
+
+        # step5:设置创建时间和修改时间
+        create_time = update_time = datetime.datetime.now()
+
+        # step6:创建测试计划的数据库记录
+        id_plan = get_id(TestPlan, "TestLaboratory_V1_Plan_1")
+
+        # step7:创建文件的数据库字段
+        fids = ''
+        file_paths = []
+        file_ids = []
+        for file in files:
+            try:
+                file_dir = PLAN_ROOT + "/" + id_plan + "/" + str(create_time)
+                if not os.path.exists(file_dir):
+                    os.makedirs(file_dir)
+                file_path = file_dir + "/" + file.name
+                with open(file_path, 'wb') as f:
+                    f.write(file.read())
+                f.close()
+            except:
+                logger.error("文件上传失败")
+                return HttpResponse(status=500, content="文件上传失败")
+
+            if not FileManager.objects.last():
+                id_file = "TestLaboratory_V1_File_1"
+            else:
+                id_file = gen_next_id(FileManager.objects.order_by('create_time').last().id)
+
+            try:
+                file_new = FileManager.objects.create(id=id_file, category="plan_statement",
+                                                      path=id_plan + "/" + str(create_time) + "/" + file.name,
+                                                      create_time=create_time,
+                                                      update_time=update_time)
+            except:
+                logger.error("文件路径写入数据库失败")
+                return HttpResponse(status=500, content="文件路径写入数据库失败")
+            file_ids.append(id_file)
+            file_paths.append(PLAN_ROOT + file_new.path)
+            fids += id_file + "&&"
+        fids = fids[:-2]
+
+        try:
+            plan = TestPlan.objects.create(id=id_plan, title=title, description=description, state='0',
+                                           creator=creator, software=software, version=software_version.number,
+                                           statement_file=fids,
+                                           create_time=create_time, update_time=update_time)
+        except:
+            logger.error("测试计划写入数据库失败")
+            return HttpResponse(status=500, content='测试计划写入数据库失败')
+
+        executor, action, method = get_log(request)
+        gen_log(action, "测试计划", plan.title, method, executor)
+        return Response({
+            'software_id': software.id,
+            'software_name': software.name,
+            'version': version_number,
+            'creator_id': creator.id,
+            'creator': creator.username,
+            'id': plan.id,
+            'title': title,
+            'description': description,
+            'state': '执行中',
+            'files': [{'file_id': file_id, 'file_name': file.name, 'file_url': file_path} for (file_id, file, file_path)
+                      in zip(file_ids, files, file_paths)]
+        })
+
+    # view plan_list
+    @staticmethod
+    @RoleControl
+    def get(request, *args, **kwargs):
+        title = request.GET.get('title')
+        software_id = request.GET.get('software_id')
+        state = request.GET.get('state')
+        creator_id = request.GET.get('creator_id')
+        sort = request.GET.get('sort')
+
+        plan_all = TestPlan.objects.order_by('-create_time')
+        if title:
+            plan_all = plan_all.filter(title__contains=title)
+        if software_id:
+            plan_all = plan_all.filter(software__id__contains=software_id)
+        if state:
+            plan_all = plan_all.filter(state=state)
+        if creator_id:
+            plan_all = plan_all.filter(creator__id__contains=creator_id)
+        """
+        sort=update_time, -update_time, create_time, -create_time, title, -title, state
+        """
+        if sort:
+            plan_all = plan_all.order_by(sort)
+        plan_all = plan_all.filter(delete=False)
+
+        if not plan_all:
+            return Response([])
+
+        info = []
+        for plan in plan_all.all():
+            info.append({
+                'software_id': plan.software.id,
+                'software_name': plan.software.name,
+                'version': plan.version,
+                'creator_id': plan.creator.id,
+                'creator': plan.creator.username,
+                'id': plan.id,
+                'title': plan.title,
+                'description': plan.description,
+                'state': plan.state,
+                'create_time': plan.create_time,
+                'update_time': plan.update_time,
+            })
+        return Response(info)

+ 147 - 0
apps/plan/views/planview.py

@@ -0,0 +1,147 @@
+import datetime
+
+from rest_framework.response import Response
+from rest_framework.views import APIView
+from django.http import HttpResponse
+
+from TestLaboratory.settings import PLAN_ROOT, HTTP_HEAD
+from apps.log.models import get_log, gen_log
+from apps.plan.models import TestPlan
+from apps.file.models import FileManager
+from utils.util_add_id import get_id
+from utils.util_file.util_fileio import write_file
+
+import logging
+logger = logging.getLogger('django')
+
+
+class PlanView(APIView):
+    # 登录权限验证
+    authentication_classes = []
+
+    # view plan
+    @staticmethod
+    def get(request, plan_id, *args, **kwargs):
+        plan = TestPlan.objects.filter(id=plan_id, delete=False)
+        if not plan:
+            logger.error("测试计划不存在")
+            return HttpResponse(status=404, content='测试计划不存在')
+        plan = plan[0]
+
+        plan_sids = plan.statement_file.split('&&')
+
+        file_names = []
+        file_urls = []
+        if plan_sids[0]:
+            for statement_id in plan_sids:
+                statement = FileManager.objects.get(id=statement_id)
+                file_urls.append(PLAN_ROOT + statement.path)
+                file_names.append(statement.path.split('/')[-1])
+
+        tasks_info = []
+        tasks = plan.testtask_set.filter(delete=False)
+        if tasks:
+            for task in tasks:
+                tasks_info.append({
+                    'id': task.id,
+                    'title': task.title,
+                    'description': task.description,
+                    'state': task.state,
+                    'executor': {'id': task.executor.id, 'name': task.executor.username},
+                    'create_time': task.create_time,
+                    'update_time': task.update_time
+                })
+        executor, action, method = get_log(request)
+        gen_log(action, "测试计划", plan.title, method, executor)
+        return Response({
+            'software_id': plan.software.id,
+            'software_name': plan.software.name,
+            'version': plan.version,
+            'creator_id': plan.creator.id,
+            'creator': plan.creator.username,
+            'id': plan.id,
+            'title': plan.title,
+            'description': plan.description,
+            'state': plan.state,
+            'tasks': tasks_info,
+            'create_time': plan.create_time,
+            'update_time': plan.update_time,
+            'statements': [{'file_id': file_sid, 'name': file_name, 'url': HTTP_HEAD + file_url} for file_sid, file_name
+                           , file_url in zip(plan_sids, file_names, file_urls)]
+        })
+
+    # delete plan_list
+    @staticmethod
+    def delete(request, plan_id, *args, **kwargs):
+        plan = TestPlan.objects.filter(id=plan_id, delete=False)
+        if not plan:
+            logger.error("测试计划已删除或不存在")
+            return HttpResponse(status=404, content='测试计划已删除或不存在')
+        plan = plan[0]
+
+        plan_sids = plan.statement_file.split('&&')
+
+        if plan_sids[0]:
+            for statement_id in plan_sids:
+                statement = FileManager.objects.get(id=statement_id)
+                statement.delete = True
+                statement.save()
+        plan.delete = True
+        plan.save()
+        executor, action, method = get_log(request)
+        gen_log(action, "测试计划", plan.title, method, executor)
+        return HttpResponse("删除完成")
+
+
+class PlanEdit(APIView):
+    # 登录权限验证
+    authentication_classes = []
+
+    # edit plan_list
+    @staticmethod
+    def post(request, plan_id, *args, **kwargs):
+        title = request.POST.get('title')
+        state = request.POST.get('state')
+        description = request.POST.get('description')
+        files = request.FILES.getlist('files')
+
+        plan = TestPlan.objects.filter(id=plan_id, delete=False)
+        if not plan:
+            logger.error("测试计划已删除或不存在")
+            return HttpResponse(status=404, content='测试计划已删除或不存在')
+        plan = plan[0]
+
+        plan.title = title
+        plan.state = state
+        plan.description = description
+
+        fids = ''
+        for file in files:
+            create_time = update_time = datetime.datetime.now()
+            try:
+                write_file(file,
+                           PLAN_ROOT + "/" + plan_id + "/" + str(create_time))
+            except:
+                logger.error("文件上传失败")
+                return HttpResponse(status=500, content="文件上传失败")
+
+            id_file = get_id(FileManager, "TestLaboratory_V1_File_1")
+
+            try:
+                file_new = FileManager.objects.create(id=id_file,
+                                                      category="plan_statement",
+                                                      path=plan_id + "/" + str(
+                                                          create_time) + "/" + file.name,
+                                                      create_time=create_time,
+                                                      update_time=update_time)
+            except:
+                logger.error("文件路径写入数据库失败")
+                return HttpResponse(status=500, content="文件路径写入数据库失败")
+            fids += id_file + '&&'
+        fids = fids[:-2]
+
+        plan.statement_file += fids
+        plan.save()
+        executor, action, method = get_log(request)
+        gen_log(action, "测试计划", plan.title, method, executor)
+        return HttpResponse("编辑已保存")

+ 0 - 0
apps/software/__init__.py


+ 3 - 0
apps/software/admin.py

@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.

+ 6 - 0
apps/software/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class SoftwareConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'apps.software'

+ 49 - 0
apps/software/migrations/0001_initial.py

@@ -0,0 +1,49 @@
+# Generated by Django 3.2 on 2021-07-17 20:05
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Software',
+            fields=[
+                ('name', models.CharField(max_length=50, verbose_name='被测试软件名')),
+                ('id', models.CharField(default='TestLaboratory_V1_Software_1', max_length=30, primary_key=True, serialize=False, unique=True, verbose_name='被测试软件编号')),
+                ('type', models.CharField(max_length=20, verbose_name='被测试软件类型')),
+                ('report_file', models.TextField(null=True, verbose_name='测试报告id文件')),
+                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
+                ('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
+                ('delete', models.BooleanField(default=False)),
+            ],
+            options={
+                'verbose_name': '被测试软件',
+                'verbose_name_plural': '被测试软件',
+                'db_table': 'software',
+            },
+        ),
+        migrations.CreateModel(
+            name='Version',
+            fields=[
+                ('id', models.CharField(default='TestLaboratory_V1_Version_1', max_length=30, primary_key=True, serialize=False, unique=True, verbose_name='软件版本')),
+                ('number', models.CharField(max_length=20, verbose_name='版本号')),
+                ('version_file', models.TextField(verbose_name='软件路径id文件')),
+                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
+                ('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
+                ('delete', models.BooleanField(default=False)),
+                ('software', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='software.software', verbose_name='所属软件')),
+            ],
+            options={
+                'verbose_name': '软件版本',
+                'verbose_name_plural': '软件版本',
+                'db_table': 'version',
+            },
+        ),
+    ]

+ 18 - 0
apps/software/migrations/0002_alter_software_name.py

@@ -0,0 +1,18 @@
+# Generated by Django 3.2.4 on 2022-06-19 10:43
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('software', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='software',
+            name='name',
+            field=models.CharField(max_length=128, verbose_name='被测试软件名'),
+        ),
+    ]

+ 23 - 0
apps/software/migrations/0003_auto_20221004_2044.py

@@ -0,0 +1,23 @@
+# Generated by Django 3.2.4 on 2022-10-04 20:44
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('software', '0002_alter_software_name'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='software',
+            name='id',
+            field=models.CharField(default='TestLaboratory_V1_Software_1', max_length=128, primary_key=True, serialize=False, unique=True, verbose_name='被测试软件编号'),
+        ),
+        migrations.AlterField(
+            model_name='version',
+            name='id',
+            field=models.CharField(default='TestLaboratory_V1_Version_1', max_length=128, primary_key=True, serialize=False, unique=True, verbose_name='软件版本'),
+        ),
+    ]

+ 0 - 0
apps/software/migrations/__init__.py


+ 96 - 0
apps/software/models.py

@@ -0,0 +1,96 @@
+import datetime
+
+from django.db import models
+
+from utils.util_add_id import gen_next_id
+from utils.util_create_file import create_version_file
+
+# Create your models here.
+
+
+class Software(models.Model):
+    name = models.CharField(max_length=128, verbose_name='被测试软件名')
+    id = models.CharField(max_length=128, unique=True, primary_key=True, verbose_name='被测试软件编号',
+                          default='TestLaboratory_V1_Software_1')
+    type = models.CharField(max_length=20, verbose_name='被测试软件类型')
+    creator_id = models.CharField(max_length=128, verbose_name='创建者id')
+    report_file = models.TextField(null=True, verbose_name='测试报告id文件')
+    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
+    update_time = models.DateTimeField(auto_now=True, verbose_name='修改时间')
+    delete = models.BooleanField(default=False)
+
+    def __str__(self):
+        return self.name
+
+    class Meta:
+        verbose_name = '被测试软件'
+        verbose_name_plural = verbose_name
+        db_table = 'software'
+
+
+class Version(models.Model):
+    id = models.CharField(max_length=128, unique=True, primary_key=True, verbose_name='软件版本',
+                          default='TestLaboratory_V1_Version_1')
+    number = models.CharField(max_length=20, verbose_name='版本号')
+    software = models.ForeignKey(Software, on_delete=models.CASCADE, verbose_name='所属软件')
+    version_file = models.TextField(verbose_name='软件路径id文件')
+    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
+    update_time = models.DateTimeField(auto_now=True, verbose_name='修改时间')
+    delete = models.BooleanField(default=False)
+
+    def __str__(self):
+        return self.software.name + " " + self.number
+
+    class Meta:
+        verbose_name = '软件版本'
+        verbose_name_plural = verbose_name
+        db_table = 'version'
+
+
+def create_software(name, software_type, version_num, file):
+    # duplicated
+    # # step1:判断软件是否已存在
+    # if Software.objects.filter(name=name):
+    #     return {'code': 500, 'msg': '被测试软件已存在,请修改软件名重新上传'}
+
+    # step2:创建id和时间
+    pass
+
+
+def get_software(name, software_type, sort):
+    pass
+
+
+def create_version(software_id, version_num, file):
+    # # step1:判断版本所属软件是否存在
+    # software = Software.objects.filter(id=software_id, delete=False)
+    # if not software:
+    #     return {'code': 500, 'msg': '被测软件不存在,请检查是否未创建或已删除'}
+    # software = software[0]
+    #
+    # # step2:判断版本是否存在
+    # software_version = software.version_set.filter(num=version_num, delete=False)
+    # if software_version:
+    #     return {'code': 500, 'msg': '软件版本已存在,请修改版本号后重新上传'}
+    #
+    # # step3:上传版本软件实体到指定路径,返回文件路径
+    # create_info = create_version_file(file, software.name, version_num)
+    # if create_info['code'] != 200:
+    #     return create_info
+    # file_path = create_info['file_path']
+    #
+    # # step4:创建版本id
+    # id_version = None
+    # if not Version.objects.order_by('create_time').last():
+    #     id_version = "Version_1"
+    # else:
+    #     id_version = gen_next_id(Version.objects.order_by('create_time').last().id)
+    #
+    # # step5:设置创建时间和修改时间
+    # create_time = update_time = datetime.datetime.now()
+    #
+    # # step6:数据库新增字段
+    # version = Version.objects.create(id=id_version, num=version_num, software=software, create_time=create_time,
+    #                                  update_time=update_time, file=file_path)
+    # return {'code': 200, 'msg': '新版本创建成功'}
+    pass

+ 3 - 0
apps/software/tests.py

@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.

+ 0 - 0
apps/software/tests/__init__.py


+ 0 - 0
apps/software/tests/softwaretest.py


+ 16 - 0
apps/software/urls.py

@@ -0,0 +1,16 @@
+from django.urls import path
+from .views.softwarelistview import SoftwareListView
+from .views.softwareview import SoftwareView, SoftwareEdit
+from .views.report import Report
+from .views.versionlistview import VersionListView
+from .views.versionview import VersionView, VersionEdit
+
+urlpatterns = [
+    path('versions', VersionListView.as_view()),
+    path('', SoftwareListView.as_view()),
+    path('<str:software_id>', SoftwareView.as_view()),
+    path('<str:software_id>/edit', SoftwareEdit.as_view()),
+    path('<str:software_id>/versions', VersionView.as_view()),
+    path('<str:software_id>/versions/edit', VersionEdit.as_view()),
+    path('<str:software_id>/report', Report.as_view())
+]

+ 0 - 0
apps/software/views/__init__.py


+ 77 - 0
apps/software/views/report.py

@@ -0,0 +1,77 @@
+from django.http import HttpResponse
+from rest_framework.views import APIView
+
+import util_doc
+from apps.software.models import Software
+
+import logging
+logger = logging.getLogger('django')
+
+
+class Report(APIView):
+    authentication_classes = []
+
+    # generate report
+    @staticmethod
+    # @RoleControl
+    def get(request, software_id, *args, **kwargs):
+        software = Software.objects.filter(id=software_id, delete=False)
+        if not software:
+            logger.error("软件已删除或不存在")
+            return HttpResponse(status=404, content='软件已删除或不存在')
+        software = software[0]
+
+        executor_id = request.GET.get('executor_id')
+        if not executor_id:
+            logger.error("测试人员id不能为空")
+            return HttpResponse(status=404, content="测试人员id不能为空")
+        cases_info = {}
+        cases = software.testcase_set.filter(executor_id=executor_id, delete=False)
+        executors = set()
+        if cases:
+            for case in cases:
+                state = '未执行'
+                if case.state == 1:
+                    state = '执行成功'
+                elif case.state == 2:
+                    state = '执行失败'
+                cases_info[case.id_in_task] = {
+                    'id': case.id_in_task,
+                    'version': case.version,
+                    'name': case.name,
+                    'requisite': case.requisite,
+                    'priority': case.priority,
+                    'environment': case.environment,
+                    'type': case.type,
+                    'process': case.process,
+                    'expected_result': case.expected_result,
+                    'actual_result': case.actual_result,
+                    'state': state,
+                    'writer': case.writer,
+                    'assessor': case.assessor,
+                    'executor': case.executor,
+                    'remark': case.remark,
+                    'execute_time': str(case.update_time)[:10]
+                }
+                executors.add(case.executor)
+
+        creators = []
+        plans = software.testplan_set.filter(delete=False)
+        if plans:
+            for plan in plans:
+                creators.append(plan.creator.name)
+
+        version_number = software.version_set.order_by('-create_time')[0].number
+
+        file_name = '软件测试报告.docx'
+        print(str(software.create_time).split(' ')[0])
+        document = util_doc.process_doc(software.name, software.type, version_number, str(software.create_time)
+                                        .split(' ')[0], executors, cases_info)
+
+        response = HttpResponse(content_type='application/octet-stream')
+        response['Content-Disposition'] = 'attachment; filename="{0}"'.format(file_name).encode('utf-8', 'ISO-8859-1')
+        document.save(response)
+
+        # executor, action, method = get_log(request)
+        # gen_log(action, "测试报告", software_id+"-测试报告", method, executor)
+        return response

+ 130 - 0
apps/software/views/softwarelistview.py

@@ -0,0 +1,130 @@
+import datetime
+import os
+
+from rest_framework.response import Response
+from rest_framework.views import APIView
+from django.http import HttpResponse, JsonResponse, Http404
+
+from TestLaboratory.settings import SOFTWARE_ROOT
+from apps.file.models import FileManager
+from apps.log.models import gen_log, get_log
+from apps.software.models import Software, create_software, get_software, Version
+import json
+
+from apps.user.middleware.rolecontrol import RoleControl
+from utils.util_add_id import gen_next_id
+
+import logging
+logger = logging.getLogger('django')
+
+
+class SoftwareListView(APIView):
+    # create software
+    @staticmethod
+    @RoleControl
+    def post(request, *args, **kwargs):
+        name = request.POST.get('name')
+        software_type = request.POST.get('type')
+        version_num = request.POST.get('version')
+        creator_id = request.POST.get('creator_id')
+        file = request.FILES.get('file')
+        if not file:
+            logger.error("软件上传失败")
+            return HttpResponse(status=404, content='软件上传失败')
+
+        # create_msg = create_software(name, software_type, version, file)
+        id_ = None
+        if not Software.objects.order_by('create_time').last():
+            id_ = "TestLaboratory_V1_Software_1"
+        else:
+            id_ = gen_next_id(Software.objects.order_by('create_time').last().id)
+        create_time = update_time = datetime.datetime.now()
+
+        # step4:创建版本id
+        id_version = None
+        if not Version.objects.last():
+            id_version = "TestLaboratory_V1_Version_1"
+        else:
+            id_version = gen_next_id(Version.objects.order_by('create_time').last().id)
+
+        # step6:上传文件:
+        file_dir = SOFTWARE_ROOT + id_ + "/" + id_version + "/" + str(create_time)
+        if not os.path.exists(file_dir):
+            os.makedirs(file_dir)
+        file_path = file_dir + "/" + file.name
+        with open(file_path, 'wb') as f:
+            f.write(file.read())
+        f.close()
+
+        # step7:创建文件对象
+        id_file = None
+        if not FileManager.objects.last():
+            id_file = "TestLaboratory_V1_File_1"
+        else:
+            id_file = gen_next_id(FileManager.objects.order_by('create_time').last().id)
+
+        try:
+            file_new = FileManager.objects.create(id=id_file, category="software",
+                                                  path=id_ + "/" + id_version + "/" +
+                                                       str(create_time) + "/" + file.name,
+                                                  create_time=create_time, update_time=update_time)
+
+            # step3:数据库新增软件字段
+            software = Software.objects.create(name=name, id=id_, type=software_type,
+                                               creator_id=creator_id,
+                                               create_time=create_time,
+                                               update_time=update_time)
+            # step5:创建版本
+            version = Version.objects.create(id=id_version, number=version_num, software=software,
+                                             version_file=id_file,
+                                             create_time=create_time,
+                                             update_time=update_time)
+
+        except:
+            logger.error("测试项目创建失败")
+            return HttpResponse(content='创建失败', status=500)
+        executor, action, method = get_log(request)
+        gen_log(action, "测试目标", software.name, method, executor)
+        return Response({
+            'id': software.id,
+            'name': software.name,
+            'type': software.type,
+            'create_time': software.create_time,
+            'update_time': software.update_time,
+        })
+
+    # software list
+    @staticmethod
+    @RoleControl
+    def get(request, *args, **kwargs):
+        name = request.GET.get('name')
+        software_type = request.GET.get('type')
+        sort = request.GET.get('sort')
+        creator_id = request.GET.get('creator_id')
+
+        software_all = Software.objects.order_by('-create_time')
+        if name:
+            software_all = software_all.filter(name__contains=name)
+        if software_type:
+            software_all = software_all.filter(type__contains=software_type)
+        """
+        sort=update_time, -update_time, create_time, -create_time, name, -name, type, -type
+        """
+        if sort:
+            software_all = software_all.order_by(sort)
+
+        software_all = software_all.filter(delete=False)
+        software_all = software_all.filter(creator_id=creator_id)
+        if not software_all:
+            return Response([])
+
+        info = []
+        for software in software_all.all():
+            info.append({
+                'id': software.id,
+                'name': software.name,
+                'type': software.type,
+                'create_time': software.create_time,
+                'update_time': software.update_time,
+            })
+        return Response(info)

+ 132 - 0
apps/software/views/softwareview.py

@@ -0,0 +1,132 @@
+import json
+
+from django.http import HttpResponse
+from rest_framework.response import Response
+from rest_framework.views import APIView
+
+from TestLaboratory.settings import SOFTWARE_ROOT, HTTP_HEAD
+from apps.file.models import FileManager
+from apps.log.models import get_log, gen_log
+from apps.software.models import Software
+
+from django.http.request import QueryDict
+
+from apps.user.middleware.rolecontrol import RoleControl
+
+import logging
+logger = logging.getLogger('django')
+
+
+class SoftwareView(APIView):
+    # 登录权限验证
+    authentication_classes = []
+
+    # view software
+    @staticmethod
+    @RoleControl
+    def get(request, software_id, *args, **kwargs):
+        software = Software.objects.filter(id=software_id, delete=False)
+        if not software:
+            logger.error("软件已删除或不存在")
+            return HttpResponse(status=404, content='软件已删除或不存在')
+        software = software[0]
+        try:
+            versions = []
+            for version in software.version_set.filter(delete=False):
+                version_fid = version.version_file
+                file_path = FileManager.objects.filter(id=version_fid)[0].path
+
+                versions.append({
+                    'id': version.id, 'version': version.number,
+                    'file': file_path.split('/')[-1],
+                    'file_url': HTTP_HEAD+SOFTWARE_ROOT+file_path,
+                    'create_time': version.create_time, 'update_time': version.update_time
+                })
+
+            task_num = 0
+            if software.testplan_set.filter(delete=False):
+                for plan in software.testplan_set.filter(delete=False):
+                    task_num += len(plan.testtask_set.filter(delete=False))
+
+            software_info = {'name': software.name, 'type': software.type, 'create_time': software.create_time,
+                             'update_time': software.update_time,
+                             'versions': versions,
+                             'case_num': len(software.testcase_set.filter(delete=False)),
+                             'task_num': task_num}
+        except:
+            logger.error("测试项目查询失败")
+            return HttpResponse(status=500, content='查询失败')
+        executor, action, method = get_log(request)
+        gen_log(action, "测试目标", software.name, method, executor)
+        return Response(software_info)
+
+    # delete software
+    @staticmethod
+    @RoleControl
+    def delete(request, software_id, *args, **kwargs):
+        software = Software.objects.filter(id=software_id, delete=False)
+        if not software:
+            logger.error("软件已删除或不存在")
+            return HttpResponse(status=404, content='软件{}已删除或不存在'.format(software_id))
+        software = software[0]
+
+        if software.version_set.exists():
+            for version in software.version_set.filter():
+                version_fid = version.version_file
+                file_version = FileManager.objects.filter(id=version_fid)[0]
+                file_version.delete = True
+                file_version.save()
+
+                # 删除测试计划
+                plan_set = software.testplan_set.filter(delete=False)
+                if plan_set:
+                    for plan in plan_set:
+                        task_set = plan.testtask_set.filter(delete=False)
+                        if task_set:
+                            for task in task_set:
+                                task.delete = True
+                                task.save()
+                        plan.delete = True
+                        plan.save()
+
+                version.delete = True
+                version.save()
+
+        software.delete = True
+        software.save()
+
+        executor, action, method = get_log(request)
+        gen_log(action, "测试目标", software.name, method, executor)
+        return HttpResponse(status=200, content='删除完成')
+
+
+class SoftwareEdit(APIView):
+    # 登录权限验证
+    authentication_classes = []
+
+    # edit software
+    @staticmethod
+    @RoleControl
+    def post(request, software_id, *args, **kwargs):
+        software = Software.objects.filter(id=software_id, delete=False)
+        if not software:
+            logger.error("软件已删除或不存在")
+            return HttpResponse(status=404, content='软件已删除或不存在')
+        software = software[0]
+
+        name = request.POST.get('name')
+        software_type = request.POST.get('type')
+
+        if not name:
+            logger.error("软件名不能为空")
+            return HttpResponse(status=500, content='软件名不能为空')
+        if name == software.name and software_type == software.type:
+            logger.error("未监测到软件名称及类型变化,编辑不保存")
+            return HttpResponse(status=500, content='未监测到软件名称及类型变化,编辑不保存')
+        software.name = name
+        software.type = software_type
+        software.save()
+        # print(software)
+        executor, action, method = get_log(request)
+        gen_log(action, "测试目标", software.name, method, executor)
+        return HttpResponse(status=200, content='编辑已保存')

+ 50 - 0
apps/software/views/versionlistview.py

@@ -0,0 +1,50 @@
+from django.http import HttpResponse
+from rest_framework.response import Response
+from rest_framework.views import APIView
+
+from apps.software.models import Software, Version
+from apps.task.models import TestCase
+from apps.user.middleware.rolecontrol import RoleControl
+
+import logging
+logger = logging.getLogger('django')
+
+
+class VersionListView(APIView):
+
+    # create version_list
+    @staticmethod
+    @RoleControl
+    def get(request, *args, **kwargs):
+        software_id = request.GET.get('software_id')
+        sort = request.GET.get('sort')
+        version_all = Version.objects.filter(delete=False).order_by('-create_time')
+
+        if software_id:
+
+            software = Software.objects.filter(id=software_id, delete=False)
+            if not software:
+                logger.error("软件不存在")
+                return HttpResponse(status=404, content='软件不存在')
+            software = software[0]
+            version_all = version_all.filter(software=software)
+
+        version_infos = []
+
+        if not version_all:
+            return Response(version_infos)
+
+        if sort:
+            version_all = version_all.order_by(sort)
+
+        for version in version_all:
+            software = Software.objects.filter(id=version.software_id, delete=False)[0]
+            fail_num = len(TestCase.objects.filter(software=software, version=version.number, state=2, delete=False))
+            version_infos.append({
+                'software_id': software.id,
+                'software_name': software.name,
+                'version_number': version.number,
+                'create_time': version.create_time,
+                'fail_num': fail_num
+            })
+        return Response(version_infos)

+ 304 - 0
apps/software/views/versionview.py

@@ -0,0 +1,304 @@
+import datetime
+
+from django.db.models import Q
+from rest_framework.response import Response
+from rest_framework.views import APIView
+from django.http import HttpResponse
+
+from TestLaboratory.settings import SOFTWARE_ROOT, CASE_FILE_EXPORT
+from apps.file.models import FileManager
+from apps.log.models import get_log, gen_log
+from apps.software.models import Software, Version
+from apps.task.models import TestCase
+from apps.user.middleware.rolecontrol import RoleControl
+from util_xlsx import write_to_xlsx
+from utils.util_add_id import gen_next_id
+
+import os
+
+from utils.util_file.util_fileio import write_file
+
+import logging
+logger = logging.getLogger('django')
+
+
+class VersionView(APIView):
+    authentication_classes = []
+
+    # create version
+    @staticmethod
+    @RoleControl
+    def post(request, software_id, *args, **kwargs):
+        version_num = request.POST.get('version')
+        file = request.FILES.get('file')
+
+        # step1:判断版本所属软件是否存在
+        software = Software.objects.filter(id=software_id, delete=False)
+        if not software:
+            logger.error("软件已删除或不存在")
+            return HttpResponse(status=404, content='软件已删除或不存在')
+        software = software[0]
+
+        # step2:判断版本是否存在
+        software_version = software.version_set.filter(number=version_num, delete=False)
+        if software_version:
+            logger.error("软件版本已存在,请修改版本号后重新上传")
+            return HttpResponse(status=404, content='软件版本已存在,请修改版本号后重新上传')
+
+        # step3:判断文件是否上传
+        if not file:
+            logger.error("软件上传失败")
+            return HttpResponse(status=404, content='软件上传失败')
+
+        # step4:创建版本id
+        id_version = None
+        if not Version.objects.order_by('create_time').last():
+            id_version = "TestLaboratory_V1_Version_1"
+        else:
+            id_version = gen_next_id(Version.objects.order_by('create_time').last().id)
+
+        # step5:设置创建时间和修改时间
+        create_time = update_time = datetime.datetime.now()
+
+        # step6:上传文件:
+        file_dir = SOFTWARE_ROOT + software.id + "/" + id_version + "/" + str(create_time)
+        if not os.path.exists(file_dir):
+            os.makedirs(file_dir)
+        file_path = file_dir + "/" + file.name
+        with open(file_path, 'wb') as f:
+            f.write(file.read())
+        f.close()
+
+        # step7:创建文件对象
+        id_file = None
+        if not FileManager.objects.last():
+            id_file = "TestLaboratory_V1_File_1"
+        else:
+            id_file = gen_next_id(FileManager.objects.order_by('create_time').last().id)
+
+        try:
+            # step5:创建版本
+            version = Version.objects.create(id=id_version, number=version_num, software=software,
+                                             version_file=id_file,
+                                             create_time=create_time,
+                                             update_time=update_time)
+        except:
+            logger.error("软件版本创建失败")
+            return HttpResponse(content='软件版本创建失败', status=500)
+        try:
+            file_new = FileManager.objects.create(id=id_file, category="software",
+                                                  path=software.id + "/" + version.id + "/" +
+                                                       str(create_time) + "/" + file.name,
+                                                  create_time=create_time, update_time=update_time)
+        except:
+            logger.error("文件数据创建失败")
+            return HttpResponse(content='文件数据创建失败', status=500)
+        executor, action, method = get_log(request)
+        gen_log(action, "目标版本", software.name+"-"+version.number, method, executor)
+        return Response({
+            'software_name': software.name,
+            'software_id': software.id,
+            'id': version.id,
+            'number': version.number,
+            'file_id': file_new.id,
+            'file_name': file_new.path.split('/')[-1],
+            'create_time': version.create_time,
+            'update_time': version.update_time,
+        })
+
+    # delete version
+    @staticmethod
+    @RoleControl
+    def delete(request, software_id, *args, **kwargs):
+        version_number = request.GET.get('version_number')
+        software = Software.objects.filter(id=software_id, delete=False)
+        if not software:
+            logger.error("软件已删除或不存在")
+            return HttpResponse(content='软件已删除或不存在', status=500)
+        software = software[0]
+
+        versions = len(list(software.version_set.filter(delete=False)))
+        if versions == 0:
+            logger.error("被测软件无版本信息,请检查错误")
+            return HttpResponse(content='被测软件无版本信息,请检查错误', status=500)
+        elif versions == 1:
+            version = software.version_set.filter(number=version_number, delete=False)[0]
+            version_fid = version.version_file
+            version_file = FileManager.objects.filter(id=version_fid)[0]
+            software.delete = True
+            version.delete = True
+            version_file.delete = True
+
+            # 删除测试计划
+            plan_set = software.testplan_set.filter(delete=False)
+            if plan_set:
+                for plan in plan_set:
+                    task_set = plan.testtask_set.filter(delete=False)
+                    if task_set:
+                        for task in task_set:
+                            task.delete = True
+                            task.save()
+                    plan.delete = True
+                    plan.save()
+
+            software.save()
+            version.save()
+            version_file.save()
+            return HttpResponse(status=200, content='软件及版本信息删除完成')
+        else:
+            version = software.version_set.filter(number=version_number, delete=False)[0]
+            version_fid = version.version_file
+            version_file = FileManager.objects.filter(id=version_fid)[0]
+            version.delete = True
+            version_file.delete = True
+
+            # 删除测试计划
+            plan_set = software.testplan_set.filter(delete=False)
+            if plan_set:
+                for plan in plan_set:
+                    task_set = plan.testtask_set.filter(delete=False)
+                    if task_set:
+                        for task in task_set:
+                            task.delete = True
+                            task.save()
+                    plan.delete = True
+                    plan.save()
+
+            version.save()
+            version_file.save()
+            executor, action, method = get_log(request)
+            gen_log(action, "目标版本", software.name + "-" + version.number, method, executor)
+            return HttpResponse(status=200, content='删除完成')
+
+    @staticmethod
+    # @RoleControl
+    def get(request, software_id, *args, **kwargs):
+        export_type = request.GET.get('type')
+        version_number = request.GET.get('version_number')
+        software = Software.objects.filter(id=software_id, delete=False)
+        if not software:
+            logger.error("软件已删除或不存在")
+            return HttpResponse(content='软件已删除或不存在', status=500)
+        software = software[0]
+
+        version = software.version_set.filter(number=version_number, delete=False)
+        if not version:
+            logger.error("软件版本已删除或不存在")
+            return HttpResponse(status=404, content='软件版本已删除或不存在')
+        version = version[0]
+
+        cases = None
+        file_name = None
+        if export_type == '0':
+            cases = TestCase.objects.filter(software=software, version=version_number, delete=False)
+            file_name = '全部测试用例.xls'
+        elif export_type == '1':
+            cases = TestCase.objects.filter(~Q(state=0), software=software, version=version_number, delete=False)
+            file_name = '已执行测试用例.xls'
+        else:
+            cases = TestCase.objects.filter(software=software, version=version_number, state=2, delete=False)
+            file_name = '不合格测试用例.xls'
+
+        if not cases:
+            logger.error("无符合要求的测试用例")
+            return HttpResponse(status=404, content='无符合要求的测试用例')
+
+        update_time = datetime.datetime.now()
+        file_dir = CASE_FILE_EXPORT + software_id + "/" + str(update_time)
+        if not os.path.exists(file_dir):
+            os.makedirs(file_dir)
+        file_path = file_dir + "/" + file_name
+
+        cases_info = [['被测软件ID:', software_id],
+                      ['被测软件名:', software.name],
+                      ['被测软件版本', version_number],
+                      ['编号', '名称', '前置条件', '优先级', '测试环境', '测试类型', '测试步骤', '预期结果', '实际结果', '状态',
+                       '编写人', '评审员', '执行人', '备注']]
+
+        for case in cases:
+            state = '未执行'
+            if case.state == 1:
+                state = '执行成功'
+            elif case.state == 2:
+                state = '执行失败'
+            cases_info.append([case.id_in_task, case.name, case.requisite, case.priority, case.environment,
+                               case.type, case.process, case.expected_result, case.actual_result, state,
+                               case.writer, case.assessor, case.executor, case.remark])
+
+        sio = write_to_xlsx(file_path, cases_info)
+        sio.seek(0)
+        response = HttpResponse(sio.getvalue(), content_type='multipart/form-data')
+        response['Content-Disposition'] = 'attachment; filename="{0}"'.format(file_name).encode('utf-8')
+        response.write(sio.getvalue())
+        # executor, action, method = get_log(request)
+        # gen_log(action, "目标版本", software.name + "-" + version.number, method, executor)
+        return response
+
+
+class VersionEdit(APIView):
+    authentication_classes = []
+
+    # edit version
+    # 版本号不能修改,本质上只能修改上传的文件
+    @staticmethod
+    @RoleControl
+    def post(request, software_id, *args, **kwargs):
+        file = request.FILES.get('file')
+        version_number = request.POST.get('version_number')
+
+        # 判断软件是否存在
+        software = Software.objects.filter(id=software_id, delete=False)
+        if not software:
+            return HttpResponse(status=404, content='软件已删除或不存在')
+        software = software[0]
+
+        # 判断版本是否存在
+        software_version = software.version_set.filter(number=version_number,
+                                                       delete=False)
+        if not software_version:
+            return HttpResponse(status=404,
+                                content='软件版本不存在或已删除,请重新选择需要编辑的软件版本')
+        software_version = software_version[0]
+
+        # 删除原版本文件
+        version_fid = software_version.version_file
+        version_file = FileManager.objects.filter(id=version_fid)
+        if not version_file:
+            logger.error("软件不存在,无法编辑")
+            return HttpResponse(status=500, content='软件不存在,无法编辑')
+        version_file = version_file[0]
+        version_file.delete = True
+        version_file.save()
+
+        # step5:设置创建时间和修改时间
+        create_time = update_time = datetime.datetime.now()
+
+        # step6:上传文件:
+        write_file(file,
+                   SOFTWARE_ROOT + software.id + "/" + software_version.id + "/" + str(
+                       create_time))
+
+        # step7:创建文件对象
+        id_file = None
+        if not FileManager.objects.last():
+            id_file = "TestLaboratory_V1_File_1"
+        else:
+            id_file = gen_next_id(
+                FileManager.objects.order_by('create_time').last().id)
+        software_version.version_file = id_file
+        software_version.save()
+
+        try:
+            file_new = FileManager.objects.create(id=id_file,
+                                                  category="software",
+                                                  path=software.id + "/" + software_version.id + "/" +
+                                                       str(create_time) + "/" + file.name,
+                                                  create_time=create_time,
+                                                  update_time=update_time)
+        except:
+            logger.error("软件版本编辑失败,请重试")
+            return HttpResponse(content='软件版本编辑失败,请重试', status=500)
+        executor, action, method = get_log(request)
+        gen_log(action, "目标版本", software.name + "-" + software_version.number,
+                method, executor)
+        return HttpResponse(status=200, content='修改已保存')

+ 0 - 0
apps/task/__init__.py


+ 3 - 0
apps/task/admin.py

@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.

+ 6 - 0
apps/task/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class TestTaskConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'apps.task'

+ 74 - 0
apps/task/migrations/0001_initial.py

@@ -0,0 +1,74 @@
+# Generated by Django 3.2 on 2021-07-17 20:05
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ('user', '0001_initial'),
+        ('plan', '0001_initial'),
+        ('software', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='TestTask',
+            fields=[
+                ('id', models.CharField(default='TestLaboratory_V1_Task_1', max_length=30, primary_key=True, serialize=False, unique=True, verbose_name='测试任务编号')),
+                ('title', models.CharField(max_length=50, verbose_name='测试任务标题')),
+                ('state', models.IntegerField(verbose_name='测试任务状态')),
+                ('description', models.TextField(verbose_name='测试任务描述')),
+                ('statement_file', models.TextField(verbose_name='说明文档id文件')),
+                ('case_file', models.TextField(null=True, verbose_name='测试用例文档')),
+                ('case_all', models.IntegerField(null=True, verbose_name='全部测试用例数量')),
+                ('case_not_execute', models.IntegerField(null=True, verbose_name='未执行测试用例数量')),
+                ('case_success', models.IntegerField(null=True, verbose_name='成功测试用例数量')),
+                ('case_fail', models.IntegerField(null=True, verbose_name='失败测试用例数量')),
+                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
+                ('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
+                ('delete', models.BooleanField(default=False)),
+                ('executor', models.ManyToManyField(to='user.User', verbose_name='测试任务执行者')),
+                ('plan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='plan.testplan', verbose_name='测试任务所属计划')),
+            ],
+            options={
+                'verbose_name': '测试任务',
+                'verbose_name_plural': '测试任务',
+                'db_table': 'task',
+            },
+        ),
+        migrations.CreateModel(
+            name='TestCase',
+            fields=[
+                ('id', models.CharField(default='TestLaboratory_V1_Case_1', max_length=30, primary_key=True, serialize=False, unique=True, verbose_name='测试用例编号')),
+                ('id_in_task', models.CharField(default='1', max_length=30, verbose_name='在测试任务中编号')),
+                ('version', models.CharField(max_length=30, null=True, verbose_name='软件版本')),
+                ('name', models.CharField(max_length=50, verbose_name='名称')),
+                ('state', models.IntegerField(verbose_name='状态')),
+                ('writer', models.CharField(max_length=30, null=True, verbose_name='测试用例编写人')),
+                ('executor', models.CharField(max_length=30, null=True, verbose_name='测试用例执行人')),
+                ('assessor', models.CharField(max_length=30, null=True, verbose_name='测试用例评审员')),
+                ('requisite', models.TextField(null=True, verbose_name='前置条件')),
+                ('priority', models.CharField(max_length=10, null=True, verbose_name='优先级')),
+                ('environment', models.TextField(null=True, verbose_name='测试环境')),
+                ('type', models.CharField(max_length=30, null=True, verbose_name='测试类型')),
+                ('process', models.TextField(verbose_name='测试步骤')),
+                ('expected_result', models.TextField(verbose_name='预期结果')),
+                ('actual_result', models.TextField(null=True, verbose_name='实际结果')),
+                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
+                ('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
+                ('delete', models.BooleanField(default=False)),
+                ('remark', models.TextField(null=True, verbose_name='备注')),
+                ('software', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='software.software', verbose_name='测试软件')),
+                ('task', models.ForeignKey(default='TestLaboratory_V1_Task_1', on_delete=django.db.models.deletion.CASCADE, to='task.testtask', verbose_name='测试用例所在测试任务')),
+            ],
+            options={
+                'verbose_name': '测试用例',
+                'verbose_name_plural': '测试用例',
+                'db_table': 'test_case',
+            },
+        ),
+    ]

+ 18 - 0
apps/task/migrations/0002_alter_testtask_id.py

@@ -0,0 +1,18 @@
+# Generated by Django 3.2.4 on 2022-06-19 10:43
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('task', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='testtask',
+            name='id',
+            field=models.CharField(default='TestLaboratory_V1_Task_1', max_length=128, primary_key=True, serialize=False, unique=True, verbose_name='测试任务编号'),
+        ),
+    ]

+ 29 - 0
apps/task/migrations/0003_auto_20220626_1630.py

@@ -0,0 +1,29 @@
+# Generated by Django 3.2.4 on 2022-06-26 16:30
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('user', '0002_auto_20220619_1043'),
+        ('task', '0002_alter_testtask_id'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='testcase',
+            name='executor_id',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='user.user', verbose_name='执行者id'),
+        ),
+        migrations.RemoveField(
+            model_name='testtask',
+            name='executor',
+        ),
+        migrations.AddField(
+            model_name='testtask',
+            name='executor',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='user.user', verbose_name='测试任务执行者'),
+        ),
+    ]

+ 18 - 0
apps/task/migrations/0004_alter_testcase_id.py

@@ -0,0 +1,18 @@
+# Generated by Django 3.2.4 on 2022-10-04 20:44
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('task', '0003_auto_20220626_1630'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='testcase',
+            name='id',
+            field=models.CharField(default='TestLaboratory_V1_Case_1', max_length=128, primary_key=True, serialize=False, unique=True, verbose_name='测试用例编号'),
+        ),
+    ]

+ 0 - 0
apps/task/migrations/__init__.py


+ 69 - 0
apps/task/models.py

@@ -0,0 +1,69 @@
+from django.db import models
+
+# Create your models here.
+from apps.plan.models import TestPlan
+from apps.software.models import Software
+from apps.user.models import User
+
+
+class TestTask(models.Model):
+    id = models.CharField(max_length=128, unique=True, primary_key=True, verbose_name='测试任务编号',
+                          default='TestLaboratory_V1_Task_1')
+    title = models.CharField(max_length=50, verbose_name='测试任务标题')
+    executor = models.ForeignKey(User, null=True, on_delete=models.CASCADE, verbose_name='测试任务执行者')
+    # 0: 发布,release;(此阶段可以上传未执行的测试用例)1:执行测试用例,execute(此阶段需上传包含测试结果的测试用例);2:执行完成
+    state = models.IntegerField(verbose_name='测试任务状态')
+    description = models.TextField(verbose_name='测试任务描述')
+    plan = models.ForeignKey(TestPlan, on_delete=models.CASCADE, verbose_name='测试任务所属计划')
+    statement_file = models.TextField(verbose_name='说明文档id文件')
+    case_file = models.TextField(null=True, verbose_name='测试用例文档')
+    case_all = models.IntegerField(null=True, verbose_name='全部测试用例数量')
+    case_not_execute = models.IntegerField(null=True, verbose_name='未执行测试用例数量')
+    case_success = models.IntegerField(null=True, verbose_name='成功测试用例数量')
+    case_fail = models.IntegerField(null=True, verbose_name='失败测试用例数量')
+    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
+    update_time = models.DateTimeField(auto_now=True, verbose_name='修改时间')
+    delete = models.BooleanField(default=False)
+
+    def __str__(self):
+        return self.title
+
+    class Meta:
+        verbose_name = '测试任务'
+        verbose_name_plural = verbose_name
+        db_table = 'task'
+
+
+class TestCase(models.Model):
+    id = models.CharField(max_length=128, unique=True, primary_key=True, verbose_name='测试用例编号',
+                          default='TestLaboratory_V1_Case_1')
+    task = models.ForeignKey(TestTask, on_delete=models.CASCADE, verbose_name='测试用例所在测试任务',
+                             default='TestLaboratory_V1_Task_1')
+    id_in_task = models.CharField(max_length=30, verbose_name='在测试任务中编号', default='1')
+    software = models.ForeignKey(Software, null=True, on_delete=models.CASCADE, verbose_name='测试软件')
+    executor_id = models.ForeignKey(User, null=True, on_delete=models.CASCADE, verbose_name='执行者id')
+    version = models.CharField(max_length=30, null=True, verbose_name='软件版本')
+    name = models.CharField(max_length=50, verbose_name='名称')
+    state = models.IntegerField(verbose_name='状态')
+    writer = models.CharField(max_length=30, verbose_name='测试用例编写人', null=True)
+    executor = models.CharField(max_length=30, verbose_name='测试用例执行人', null=True)
+    assessor = models.CharField(max_length=30, verbose_name='测试用例评审员', null=True)
+    requisite = models.TextField(null=True, verbose_name='前置条件')
+    priority = models.CharField(max_length=10, null=True, verbose_name='优先级')
+    environment = models.TextField(null=True, verbose_name='测试环境')
+    type = models.CharField(max_length=30, null=True, verbose_name='测试类型')
+    process = models.TextField(verbose_name='测试步骤')
+    expected_result = models.TextField(verbose_name='预期结果')
+    actual_result = models.TextField(verbose_name='实际结果', null=True)
+    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
+    update_time = models.DateTimeField(auto_now=True, verbose_name='修改时间')
+    delete = models.BooleanField(default=False)
+    remark = models.TextField(null=True, verbose_name='备注')
+
+    def __str__(self):
+        return self.name
+
+    class Meta:
+        verbose_name = '测试用例'
+        verbose_name_plural = verbose_name
+        db_table = 'test_case'

+ 3 - 0
apps/task/tests.py

@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.

+ 10 - 0
apps/task/urls.py

@@ -0,0 +1,10 @@
+from django.urls import path
+from .views import tasklistview, taskview, caseview
+
+urlpatterns = [
+    path('', tasklistview.TaskListView.as_view()),
+    path('<str:task_id>', taskview.TaskView.as_view()),
+    path('<str:task_id>/edit', taskview.TaskEdit.as_view()),
+    path('<str:task_id>/case-file', caseview.CaseView.as_view()),
+    path('<str:task_id>/case-file/execute', caseview.CaseExecute.as_view())
+]

Some files were not shown because too many files changed in this diff