Python Django脚手架fu-admin教程:快速上手(前后端模板代码Demo2)

最终效果

在这里插入图片描述

vim fu-admin/backend/fuadmin/settings.py

"""
Django settings for fuadmin project.

Generated by 'django-admin startproject' using Django 4.0.4.

For more information on this file, see
https://docs.djangoproject.com/en/4.0/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.0/ref/settings/
"""
import os
from datetime import timedelta
from pathlib import Path
from conf.env import *

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-x4k4^#6wovi1aep8%ow!5fr%(9o#1u=+0+nzi($_j=^d*ui6g3'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = locals().get('DEBUG', True)
ALLOWED_HOSTS = locals().get('ALLOWED_HOSTS', ['*'])
DEMO = locals().get('DEMO', False)

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django_celery_beat',
    'django_celery_results',
    'system',
    'demo',
    'generator',
    'demo2',
]

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',
    'utils.middleware.ApiLoggingMiddleware',

]

ROOT_URLCONF = 'fuadmin.urls'

TEMPLATES = [
    {
    
    
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [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 = 'fuadmin.wsgi.application'

# Password validation
# https://docs.djangoproject.com/en/4.0/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',
    },
]

# Internationalization
# https://docs.djangoproject.com/en/4.0/topics/i18n/

LANGUAGE_CODE = 'zh-hans'

TIME_ZONE = 'Asia/Shanghai'

USE_I18N = True

USE_L10N = True

USE_TZ = False

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/

STATIC_URL = 'static/'

# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

AUTH_USER_MODEL = 'system.Users'
USERNAME_FIELD = 'username'
ALL_MODELS_OBJECTS = []  # 所有app models 对象

# Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
# 数据库配置
if DATABASE_TYPE == "MYSQL":
    # Mysql数据库
    DATABASES = {
    
    
        "default": {
    
    
            "ENGINE": "django.db.backends.mysql",
            "HOST": DATABASE_HOST,
            "PORT": DATABASE_PORT,
            "USER": DATABASE_USER,
            "PASSWORD": DATABASE_PASSWORD,
            "NAME": DATABASE_NAME,
        }
    }
elif DATABASE_TYPE == "SQLSERVER":
    # SqlServer数据库
    DATABASES = {
    
    
        "default": {
    
    
            "ENGINE": "mssql",
            "HOST": DATABASE_HOST,
            "PORT": DATABASE_PORT,
            "USER": DATABASE_USER,
            "PASSWORD": DATABASE_PASSWORD,
            "NAME": DATABASE_NAME,
            # 全局开启事务,绑定的是http请求响应整个过程
            'ATOMIC_REQUESTS': True,
            'OPTIONS': {
    
    
                'driver': 'ODBC Driver 17 for SQL Server',
            },
        }
    }
else:
    # sqlite3 数据库
    DATABASES = {
    
    
        'default': {
    
    
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
            'OPTIONS': {
    
    
                'timeout': 20,
            },
        }
    }

# 缓存配置
CACHES = {
    
    
    "default": {
    
    
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": f'{
      
      REDIS_URL}/1',
        "TIMEOUT": None,
        "OPTIONS": {
    
    
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    },
}


# # ================================================= #
# # ********************* 日志配置 ******************* #
# # ================================================= #
# # log 配置部分BEGIN #
SERVER_LOGS_FILE = os.path.join(BASE_DIR, "logs", "server.log")
ERROR_LOGS_FILE = os.path.join(BASE_DIR, "logs", "error.log")
LOGS_FILE = os.path.join(BASE_DIR, "logs")
if not os.path.exists(os.path.join(BASE_DIR, "logs")):
    os.makedirs(os.path.join(BASE_DIR, "logs"))

# 格式:[2020-04-22 23:33:01][micoservice.apps.ready():16] [INFO] 这是一条日志:
# 格式:[日期][模块.函数名称():行号] [级别] 信息
STANDARD_LOG_FORMAT = (
    "[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
)
CONSOLE_LOG_FORMAT = (
    "[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
)
LOGGING = {
    
    
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
    
    
        "standard": {
    
    "format": STANDARD_LOG_FORMAT},
        "console": {
    
    
            "format": CONSOLE_LOG_FORMAT,
            "datefmt": "%Y-%m-%d %H:%M:%S",
        },
        "file": {
    
    
            "format": CONSOLE_LOG_FORMAT,
            "datefmt": "%Y-%m-%d %H:%M:%S",
        },
    },
    "handlers": {
    
    
        "file": {
    
    
            "level": "INFO",
            "class": "logging.handlers.RotatingFileHandler",
            "filename": SERVER_LOGS_FILE,
            "maxBytes": 1024 * 1024 * 100,  # 100 MB
            "backupCount": 5,  # 最多备份5个
            "formatter": "standard",
            "encoding": "utf-8",
        },
        "error": {
    
    
            "level": "ERROR",
            "class": "logging.handlers.RotatingFileHandler",
            "filename": ERROR_LOGS_FILE,
            "maxBytes": 1024 * 1024 * 100,  # 100 MB
            "backupCount": 3,  # 最多备份3个
            "formatter": "standard",
            "encoding": "utf-8",
        },
        "console": {
    
    
            "level": "INFO",
            "class": "logging.StreamHandler",
            "formatter": "console",
        },

    },
    "loggers": {
    
    
        "": {
    
    
            "handlers": ["console", "error", "file"],
            "level": "INFO",
        },
        "django": {
    
    
            "handlers": ["console", "error", "file"],
            "level": "INFO",
            "propagate": False,
        },
        'django.db.backends': {
    
    
            'handlers': ["console", "error", "file"],
            'propagate': False,
            'level': "INFO"
        },
        "uvicorn.error": {
    
    
            "level": "INFO",
            "handlers": ["console", "error", "file"],
        },
        "uvicorn.access": {
    
    
            "handlers": ["console", "error", "file"],
            "level": "INFO"
        },
    },
}


# celery 配置
CELERY_BROKER_URL = f'{
      
      REDIS_URL}/2'
DJANGO_CELERY_BEAT_TZ_AWARE = False
CELERY_ENABLE_UTC = False
CELERY_WORKER_CONCURRENCY = 2  # 并发数
CELERY_MAX_TASKS_PER_CHILD = 5  # 没个worker最多执行5个任务便自我销毁释放内存
CELERY_TIMEZONE = TIME_ZONE  # celery 时区问题
CELERY_RESULT_BACKEND = 'django-db'  # celery结果存储到数据库中
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'  # Backend数据库

# token 有效时间 时 分 秒
TOKEN_LIFETIME = 12 * 60 * 60
# TOKEN_LIFETIME = 50

# 接口白名单,不需要授权直接访问
WHITE_LIST = ['/api/system/userinfo', '/api/system/permCode', '/api/system/menu/route/tree', '/api/system/user/*',
              '/api/system/user/set/repassword']

# 接口日志记录
API_LOG_ENABLE = True
API_LOG_METHODS = ['POST', 'GET', 'DELETE', 'PUT']
API_MODEL_MAP = {
    
    }

# 初始化需要执行的列表,用来初始化后执行
INITIALIZE_RESET_LIST = []

vim fu-admin/backend/demo2/models.py

from django.db import models
from utils.models import CoreModel


class Demo2(CoreModel):
    name = models.CharField(null=False, max_length=64, verbose_name="项目名称", help_text="项目名称")
    code = models.CharField(max_length=32, verbose_name="项目编码", help_text="项目编码")
    status = models.CharField(max_length=64, verbose_name="项目状态", help_text="项目状态")

    class Meta:
        db_table = "Demo2"
        verbose_name = '项目演示2'
        verbose_name_plural = verbose_name
        ordering = ('-create_datetime',)

vim fu-admin/backend/demo2/api.py


from typing import List
from ninja import Router, ModelSchema, Query, Field
from ninja.pagination import paginate

from demo2.models import Demo2
from utils.fu_crud import create, delete, update, retrieve, ImportSchema, export_data, import_data
from utils.fu_ninja import MyPagination, FuFilters

router = Router()


# 设置过滤字段
class Filters(FuFilters):
    name: str = Field(None, alias="name")
    code: str = Field(None, alias="code")
    status: int = Field(None, alias="status")
    id: str = Field(None, alias="demo_id")


# 设置请求接收字段
class DemoSchemaIn(ModelSchema):
    class Config:
        model = Demo2
        model_fields = ['name', 'code', 'sort', 'status']


# 设置响应字段
class DemoSchemaOut(ModelSchema):
    class Config:
        model = Demo2
        model_fields = "__all__"


# 创建Demo
@router.post("/demo2", response=DemoSchemaOut)
def create_demo(request, data: DemoSchemaIn):
    demo = create(request, data, Demo2)
    return demo


# 删除Demo
@router.delete("/demo2/{demo_id}")
def delete_demo(request, demo_id: int):
    delete(demo_id, Demo2)
    return {
    
    "success": True}


# 更新Demo
@router.put("/demo2/{demo_id}", response=DemoSchemaOut)
def update_demo(request, demo_id: int, data: DemoSchemaIn):
    demo = update(request, demo_id, data, Demo2)
    return demo


# 获取Demo
@router.get("/demo2", response=List[DemoSchemaOut])
@paginate(MyPagination)
def list_demo(request, filters: Filters = Query(...)):
    qs = retrieve(request, Demo2, filters)
    return qs


# 导入
@router.get("/demo2/all/export")
def export_demo(request):
    title_dict = {
    
    
        'name': '名称',
        'code': '编码',
        'status': '状态',
        'sort': '排序',
    }
    return export_data(request, Demo2, DemoSchemaOut, title_dict)


# 导出
@router.post("/demo2/all/import")
def import_demo(request, data: ImportSchema):
    title_dict = {
    
    
        '名称': 'name',
        '编码': 'code',
        '状态': 'status',
        '排序': 'sort',
    }
    return import_data(request, Demo2, DemoSchemaIn, data, title_dict)

vim fu-admin/backend/demo2/router.py

from ninja import Router
from demo2.api import router

demo_router = Router()
demo_router.add_router('/', router, tags=["Demo2"])

vim fu-admin/backend/fuadmin/api.py

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

from demo.router import demo_router
from demo2.router import demo_router as demo2_router
from system.router import system_router
from utils.fu_auth import GlobalAuth
from utils.fu_ninja import FuNinjaAPI
from generator.router import generator_router


api = FuNinjaAPI(auth=GlobalAuth())


# 统一处理server异常
@api.exception_handler(Exception)
def a(request, exc):

    if hasattr(exc, 'errno'):
        return api.create_response(request, data=[], msg=str(exc), code=exc.errno)
    else:
        return api.create_response(request, data=[], msg=str(exc), code=500)


api.add_router('/system/', system_router)
api.add_router('/demo/', demo_router)
api.add_router('/generator/', generator_router)
api.add_router('/demo2/', demo2_router)

--------前端--------

vim fu-admin/web/src/views/demo2/api.ts


import {
    
     defHttp } from '/@/utils/http/axios';

enum DeptApi {
    
    
  prefix = '/api/demo2/demo2',
}

export const getList = (params) => {
    
    
  return defHttp.get({
    
     url: DeptApi.prefix, params });
};

/**
 * 保存或更新
 */

export const createOrUpdate = (params, isUpdate) => {
    
    
  if (isUpdate) {
    
    
    return defHttp.put({
    
     url: DeptApi.prefix + '/' + params.id, params });
  } else {
    
    
    return defHttp.post({
    
     url: DeptApi.prefix, params });
  }
};

export const importData = (params) => {
    
    
  return defHttp.post({
    
     url: DeptApi.prefix + '/all/import', params });
};

export const exportData = () => {
    
    
  return defHttp.get(
          {
    
     url: DeptApi.prefix + '/all/export', responseType: 'blob' },
          {
    
     isReturnNativeResponse: true },
  );
};

/**
 * 删除
 */

export const deleteItem = (id) => {
    
    
  return defHttp.delete({
    
     url: DeptApi.prefix + '/' + id });
};

vim fu-admin/web/src/views/demo2/data.ts


import {
    
     BasicColumn } from '/@/components/Table';
import {
    
     FormSchema } from '/@/components/Table';

export const columns: BasicColumn[] = [
  {
    
    
    title: '项目名称',
    dataIndex: 'name',
    width: 200,
  },
  {
    
    
    title: '项目编码',
    dataIndex: 'code',
    width: 180,
  },
  {
    
    
    title: '项目排序',
    dataIndex: 'sort',
    width: 100,
  },
  {
    
    
    title: '项目状态',
    dataIndex: 'status',
    width: 100,
  },
  {
    
    
    title: '创建时间',
    dataIndex: 'create_datetime',
    width: 180,
  },
];

export const searchFormSchema: FormSchema[] = [
  {
    
    
    field: 'name',
    label: '项目名称',
    component: 'Input',
    colProps: {
    
     span: 6 },
  },
];

export const formSchema: FormSchema[] = [
  {
    
    
    field: 'id',
    label: 'id',
    component: 'Input',
    show: false,
  },
  {
    
    
    field: 'name',
    label: '项目名称',
    required: true,
    component: 'Input',
  },
  {
    
    
    field: 'code',
    label: '项目编码',
    required: true,
    component: 'Input',
  },
  {
    
    
    field: 'status',
    component: 'DictSelect',
    label: '项目状态',
    componentProps: {
    
    
      dictCode: 'project_status',
    },
  },
  {
    
    
    field: 'sort',
    label: '岗位排序',
    component: 'InputNumber',
    required: true,
  },
];

vim fu-admin/web/src/views/demo2/index.vue

<template>
  <div>
    <BasicTable @register="registerTable">
      <template #tableTitle>
        <Space style="height: 40px">
          <a-button
                  type="primary"
                  v-auth="['demo2:add']"
                  preIcon="ant-design:plus-outlined"
                  @click="handleCreate"
          >
            新增
          </a-button>
          <a-button
                  type="error"
                  v-auth="['demo2:delete']"
                  preIcon="ant-design:delete-outlined"
                  @click="handleBulkDelete"
          >
            删除
          </a-button>
          <BasicUpload
                  :maxSize="20"
                  :maxNumber="1"
                  @change="handleChange"
                  class="my-5"
                  type="warning"
                  text="导入"
                  v-auth="['demo2:update']"
          />
          <a-button
                  type="success"
                  v-auth="['demo2:update']"
                  preIcon="carbon:cloud-download"
                  @click="handleExportData"
          >
            导出
          </a-button>
        </Space>
      </template>
      <template #action="{ record }">
        <TableAction
                :actions="[
            {
    
    
              type: 'button',
              icon: 'clarity:note-edit-line',
              color: 'primary',
              auth: ['demo2:update'],
              onClick: handleEdit.bind(null, record),
            },
            {
    
    
              icon: 'ant-design:delete-outlined',
              type: 'button',
              color: 'error',
              placement: 'left',
              auth: ['demo2:delete'],
              popConfirm: {
    
    
                title: '是否确认删除',
                confirm: handleDelete.bind(null, record.id),
              },
            },
          ]"
        />
      </template>
    </BasicTable>
    <DemoDrawer @register="registerDrawer" @success="handleSuccess" />
  </div>
</template>
<script lang="ts">
import {
    
     defineComponent } from 'vue';

import {
    
     BasicTable, useTable, TableAction } from '/@/components/Table';
import {
    
     usePermission } from '/@/hooks/web/usePermission';
import {
    
     useDrawer } from '/@/components/Drawer';
import DemoDrawer from './Drawer.vue';
import {
    
     Space } from 'ant-design-vue';
import {
    
     BasicUpload } from '/@/components/Upload';
import {
    
     deleteItem, getList, exportData, importData } from './api';
import {
    
     columns, searchFormSchema } from './data';
import {
    
     message } from 'ant-design-vue';
import {
    
     useMessage } from '/@/hooks/web/useMessage';
import {
    
     downloadByData } from '/@/utils/file/download';
export default defineComponent({
    
    
  name: 'Demo2',
  components: {
    
     BasicTable, DemoDrawer, TableAction, BasicUpload, Space },
  setup() {
    
    
    const [registerDrawer, {
    
     openDrawer }] = useDrawer();
    const {
    
     createConfirm } = useMessage();
    const {
    
     hasPermission } = usePermission();
    const [registerTable, {
    
     reload, getSelectRows }] = useTable({
    
    
      api: getList,
      columns,
      formConfig: {
    
    
        labelWidth: 80,
        schemas: searchFormSchema,
      },
      useSearchForm: true,
      showTableSetting: true,
      tableSetting: {
    
     fullScreen: true },
      bordered: true,
      showIndexColumn: false,
      rowSelection: {
    
    
        type: 'checkbox',
      },
      actionColumn: {
    
    
        width: 150,
        title: '操作',
        dataIndex: 'action',
        slots: {
    
     customRender: 'action' },
        fixed: undefined,
      },
    });

    function handleCreate() {
    
    
      openDrawer(true, {
    
    
        isUpdate: false,
      });
    }

    function handleEdit(record: Recordable) {
    
    
      openDrawer(true, {
    
    
        record,
        isUpdate: true,
      });
    }

    async function handleDelete(id: number) {
    
    
      await deleteItem(id);
      message.success('删除成功');
      await reload();
    }

    function handleBulkDelete() {
    
    
      if (getSelectRows().length == 0) {
    
    
        message.warning('请选择一个选项');
      } else {
    
    
        createConfirm({
    
    
          iconType: 'warning',
          title: '提示',
          content: '是否确认删除?',
          async onOk() {
    
    
            for (const item of getSelectRows()) {
    
    
              await deleteItem(item.id);
            }
            message.success('删除成功');
            await reload();
          },
        });
      }
    }

    async function handleChange(list: string[]) {
    
    
      console.log(list[0]);
      await importData({
    
     path: list[0] });
      message.success(`导入成功`);
      await reload();
    }

    async function handleExportData() {
    
    
      const response = await exportData();
      await downloadByData(response.data, '项目数据.xlsx');
    }

    function handleSuccess() {
    
    
      message.success('请求成功');
      reload();
    }

    return {
    
    
      registerTable,
      registerDrawer,
      handleCreate,
      handleEdit,
      handleDelete,
      handleSuccess,
      hasPermission,
      handleBulkDelete,
      getSelectRows,
      handleExportData,
      handleChange,
    };
  },
});
</script>

vim fu-admin/web/src/views/demo2/Drawer.vue

<template>
    <BasicDrawer
            v-bind="$attrs"
            @register="registerDrawer"
            showFooter
            :title="getTitle"
            width="50%"
            @ok="handleSubmit"
    >
      <BasicForm @register="registerForm" />
    </BasicDrawer>
  </template>
  <script lang="ts">
  import {
    
     defineComponent, ref, computed, unref } from 'vue';
  import {
    
     BasicForm, useForm } from '/@/components/Form/index';
  import {
    
     BasicDrawer, useDrawerInner } from '/@/components/Drawer';
  import {
    
     createOrUpdate } from './api';
  import {
    
     formSchema } from './data';
  
  export default defineComponent({
    
    
    name: 'ButtonDrawer',
    components: {
    
     BasicDrawer, BasicForm },
    emits: ['success', 'register'],
    setup(_, {
     
      emit }) {
    
    
      const isUpdate = ref(true);
  
      const [registerForm, {
    
     resetFields, setFieldsValue, validate }] = useForm({
    
    
        labelWidth: 100,
        schemas: formSchema,
        showActionButtonGroup: false,
        baseColProps: {
    
     lg: 12, md: 24 },
      });
  
      const [registerDrawer, {
    
     setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
    
    
        await resetFields();
        setDrawerProps({
    
     confirmLoading: false });
        isUpdate.value = !!data?.isUpdate;
  
        if (unref(isUpdate)) {
    
    
          await setFieldsValue({
    
    
            ...data.record,
          });
        }
      });
  
      const getTitle = computed(() => (!unref(isUpdate) ? '新增项目' : '编辑项目'));
  
      async function handleSubmit() {
    
    
        try {
    
    
          const values = await validate();
          setDrawerProps({
    
     confirmLoading: true });
          await createOrUpdate(values, unref(isUpdate));
          closeDrawer();
          emit('success');
        } finally {
    
    
          setDrawerProps({
    
     confirmLoading: false });
        }
      }
  
      return {
    
    
        registerDrawer,
        registerForm,
        getTitle,
        handleSubmit,
      };
    },
  });
  </script>
  

猜你喜欢

转载自blog.csdn.net/a772304419/article/details/133557092