参考 https://github.com/rfjer/autoAdmin/
创建数据库
CREATE DATABASE `devops` /*!40100 DEFAULT CHARACTER SET utf8 */;
安装工具
pip install Django==2.2.1
pip install coreapi-cli
pip install djangorestframework
pip install django-apscheduler
pip install tencentcloud-sdk-python
pip install django-filter
pip install requests -i https://pypi.douban.com/simple/
pip install pymysql
pip install django-rest-swagger
pip install django-cors-headers
pip install djangorestframework-jwt
创建项目
django-admin startproject devops
创建用户app
python manage.py startapp users
在devops 目录下创建 apps 目录,并把 users 目录移动到apps目录下
settings.py 文件中添加
import sys
sys.path.insert(0,os.path.join(BASE_DIR, "apps")) # 注意此行一定要在BASE_DIR的后面,不然就会报错
在 ALLOWED_HOSTS 行的后面添加
AUTH_USER_MODEL = 'users.User'
INSTALLED_APPS 中添加
'rest_framework',
'rest_framework.authtoken',
'users',
'corsheaders',
'django_apscheduler',
'django_filters',
在 INSTALLED_APPS 模块的后面添加
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_WHITELIST = (
'http://127.0.0.1:8000',
)
在 MIDDLEWARE 中注释掉下面这行
#'django.middleware.csrf.CsrfViewMiddleware',
在 MIDDLEWARE 中 'django.contrib.sessions.middleware.SessionMiddleware', 下面添加一行
'corsheaders.middleware.CorsMiddleware',
修改数据库连接
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'devops',
'USER': 'root',
'PASSWORD': 'chengce243',
'HOST': '127.0.0.1',
'PORT': 3306,
'OPTIONS':{
'init_command':"SET sql_mode='STRICT_TRANS_TABLES'",
'charset':'utf8mb4',
},
}
}
settings.py中加入DOMAIN,让自动生成邮件域名
DOMAIN = "@admin.com"
在最后添加如下
from rest_framework.settings import api_settings
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'devops.paginations.Pagination',
'PAGE_SIZE': 10,
'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend',
),
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}
## qcloud
QCLOUD_SECRETID = "AKIDFaq5EbGxiQx22qzUL2XtCJJ7j9NQuyTq"
QCLOUD_SECRETKEY = "LTP8fz72lOr8D8reFerunUyGQN7f4LTP"
users/models.py
from django.db import models
# Create your models here.
from django.contrib.auth.models import AbstractUser, Group
class User(AbstractUser):
name = models.CharField("姓名", max_length=32, null=True, help_text="姓名")
phone = models.CharField("电话", max_length=11, null=True, help_text="手机号")
class Meta:
verbose_name = "用户"
ordering = ["id"]
db_table = 'auth_user'
def __str__(self):
return self.username
users/views.py
from django.contrib.auth import get_user_model
from rest_framework import viewsets, mixins, permissions
from rest_framework.response import Response
from .serializers import UserSerializer, UserRegSerializer
from .filters import UserFilter
User = get_user_model()
class UserRegViewset(mixins.CreateModelMixin,
mixins.UpdateModelMixin,
viewsets.GenericViewSet):
"""
create:
创建用户
update:
修改密码
"""
queryset = User.objects.all()
serializer_class = UserRegSerializer
class UsersViewset(viewsets.ModelViewSet):
"""
retrieve:
获取用户信息
list:
获取用户列表
update:
更新用户信息
delete:
删除用户
"""
queryset = User.objects.all()
serializer_class = UserSerializer
filter_class = UserFilter
filter_fields = ("username",)
extra_perms_map = {
"GET": ["users.show_user_list"]
}
def get_queryset(self):
queryset = super(UsersViewset, self).get_queryset()
queryset = queryset.order_by("id")
queryset = queryset.exclude(is_superuser=True)
return queryset
users/serializers.py
from rest_framework import serializers
from django.contrib.auth.models import Group
from django.contrib.auth import get_user_model
from django.conf import settings
User = get_user_model()
#控制具体动作有哪些
class UserSerializer(serializers.ModelSerializer):
"""
用户序列化类
"""
#控制的你是返回的格式:
last_login = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S",
label="上次登录时间",
help_text="上次登录时间")
# 控制往前端返回哪些字段:
class Meta:
model = User
fields = ("id", "username", "name", "phone", "email", "is_active", "last_login")
#想修改哪些字段可在此方法中控制:
def update(self, instance, validated_data):
instance.phone = validated_data.get("phone", instance.phone)
instance.is_active = validated_data.get("is_active", instance.is_active)
instance.save()
return instance
class UserRegSerializer(serializers.ModelSerializer):
"""
用户注册序列化类
"""
#把密码处理成密文
password = serializers.CharField(style={"input_type": "password"},
label="密码",
write_only=True,
help_text="密码")
#验证
def validate(self, attrs):
attrs["is_active"] = False #默认创建完的用户不可直接登录
#给用户自动加email地址
attrs["email"] = "{}{}".format(attrs['username'], settings.DOMAIN)
return attrs
#此create可不要,但不要的结果是密码直接存进去了
def create(self, validated_data):
instance = super(UserRegSerializer, self).create(validated_data=validated_data)
instance.set_password(validated_data["password"])
instance.save()
return instance
def update(self, instance, validated_data):
password = validated_data.get("password", None)
if password:
instance.set_password(password)
instance.save()
return instance
class Meta:
model = User
#序列化时返回的数据
fields = ("username", "password", "name", "id", "phone")
users/router.py
from rest_framework.routers import DefaultRouter
from .views import UsersViewset, UserRegViewset
user_router = DefaultRouter()
user_router.register(r'userreg', UserRegViewset)
user_router.register(r'users', UsersViewset)
users/urls.py
from django.conf.urls import url, include
from rest_framework.routers import DefaultRouter
from rest_framework.documentation import include_docs_urls
from users.router import router as user_router
# from resources.router import router as resources_router
router = DefaultRouter()
router.registry.extend(user_router.registry)
# router.registry.extend(resources_router.registry)
from rest_framework.authtoken.views import obtain_auth_token
urlpatterns = [
url(r'^api/', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls')),
url(r'^docs/', include_docs_urls("51reboot接口文档")),
url(r'^api-token-auth/', obtain_auth_token)
]
users/filters.py
import django_filters
from django.contrib.auth import get_user_model
from django.db.models import Q
User = get_user_model()
class UserFilter(django_filters.rest_framework.FilterSet):
"""
用户过滤类
"""
username = django_filters.CharFilter(method='search_username')
def search_username(self, queryset, name, value):
return queryset.filter(Q(name__icontains=value)|Q(username__icontains=value))
class Meta:
model = User
fields = ['username']
devops/urls.py
from django.conf.urls import url, include
from rest_framework.routers import DefaultRouter
from rest_framework.documentation import include_docs_urls
from rest_framework_jwt.views import obtain_jwt_token
router = DefaultRouter()
from users.router import user_router
router.registry.extend(user_router.registry)
urlpatterns = [
url(r'^api/', include(router.urls)),
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
url(r'^api-token-auth/', obtain_jwt_token),
url(r'^docs/', include_docs_urls("开源运维平台"))
]
说明:
上述中是以api开头的全部交给router.urls去处理,
因为到时我前端和后端可能放在一台服务器上,然后调动的时候只要不是api开头的全部交给nginx去处理,
以api开头则交给django去处理。
devops/paginations.py
from rest_framework.pagination import PageNumberPagination
class Pagination(PageNumberPagination):
def get_page_size(self, request):
try:
page_size = int(request.query_params.get("page_size", -1))
if page_size >= 0:
return page_size
except:
pass
return self.page_size
在 users下的 的 __init__.py 文件新增内容
import pymysql
pymysql.version_info = (1, 4, 6, 'final', 0) # change mysqlclient version
pymysql.install_as_MySQLdb()
数据库同步
python manage.py makemigrations
python manage.py migrate
当执行 python manage.py makemigrations 的时候,报错如下:
File "C:\python\lib\site-packages\django\db\backends\mysql\operations.py", line 146, in last_executed_query
query = query.decode(errors='replace')
AttributeError: 'str' object has no attribute 'decode'
解决办法:
C:\python\lib\site-packages\django\db\backends\mysql\operations.py 文件146行
将 decode 改为 encode
def last_executed_query(self, cursor, sql, params):
# With MySQLdb, cursor objects have an (undocumented) "_executed"
# attribute where the exact query sent to the database is saved.
# See MySQLdb/cursors.py in the source distribution.
query = getattr(cursor, '_executed', None)
if query is not None:
query = query.encode(errors='replace')
return query
创建超级管理员
python manage.py createsuperuser
创建测试用户的数据
python manage.py shell
from django.contrib.auth import get_user_model
User = get_user_model()
def create_user(name):
for i in range(1,20):
username = "{}-{}".format(name,i)
User.objects.create_user(username, "{}@admin.com".format(username),"12345678")
create_user("rock")
create_user("panda")
create_user("wd")
清空数据(不想要时可用此命令清空数据)
User.objects.all().delete()
启动项目
python manage.py runserver 0.0.0.0:8000
vue环境搭建:
参考 https://www.cnblogs.com/dbslinux/p/13180500.html
下载并安装git: https://www.git-scm.com/download/win
vue开发环境搭建
下载二进制包 https://nodejs.org/zh-cn/
在cmd中如下显示说明安装成功
C:\Users\EDUTECH> node -v
v14.15.4
新建 web 目录,devops文件夹是后端代码,和web目录是同级
到 web目录 目录下
git clone https://github.com/gxhsh/vueAdmin-template.git
安装nodejs:
D:\ops\web\vue-admin-template> npm install --registry=https://registry.npm.taobao.org
运行
D:\ops\web\vue-admin-template> npm run dev
取消权限 src/permission.js
import router from './router'
// import store from './store'
import NProgress from 'nprogress' // Progress 进度条
import 'nprogress/nprogress.css'// Progress 进度条样式
// import { Message } from 'element-ui'
// import { getToken } from '@/utils/auth' // 验权
// const whiteList = ['/login'] // 不重定向白名单
router.beforeEach((to, from, next) => {
NProgress.start()
next()
// if (getToken()) {
// if (to.path === '/login') {
// next({ path: '/' })
// } else {
// if (store.getters.roles.length === 0) {
// store.dispatch('GetInfo').then(res => { // 拉取用户信息
// next()
// }).catch(() => {
// store.dispatch('FedLogOut').then(() => {
// Message.error('验证失败,请重新登录')
// next({ path: '/login' })
// })
// })
// } else {
// next()
// }
// }
// } else {
// if (whiteList.indexOf(to.path) !== -1) {
// next()
// } else {
// next('/login')
// NProgress.done()
// }
// }
})
router.afterEach(() => {
NProgress.done() // 结束Progress
})
重新指向路由 config/index.js
第17行修改如下
port: 8080
开发环境配置成本机 config/dev.env.js
BASE_API: '"http://127.0.0.1:8000"',
线上环境配置 config/prod.env.js
BASE_API: '"http://127.0.0.1:8000"',
去掉不需要的路由 router/index.js
// {
// path: '/example',
// component: Layout,
// redirect: '/example/table',
// name: 'Example',
// meta: { title: 'Example', icon: 'example' },
// children: [
// {
// path: 'table',
// name: 'Table',
// component: () => import('@/views/table/index'),
// meta: { title: 'Table', icon: 'table' }
// },
// {
// path: 'tree',
// name: 'Tree',
// component: () => import('@/views/tree/index'),
// meta: { title: 'Tree', icon: 'tree' }
// }
// ]
// },
//
// {
// path: '/form',
// component: Layout,
// children: [
// {
// path: 'index',
// name: 'Form',
// component: () => import('@/views/form/index'),
// meta: { title: 'Form', icon: 'form' }
// }
// ]
// },
用户展示,添加前端路由 router/index.js
{
path: '/users',
component: Layout,
name: 'users',
meta: { title: '用户管理', icon: 'example' },
children: [
{
path: 'user',
name: 'user',
component: () => import('@/views/users/user.vue'),
meta: { title: '用户', icon: 'table' }
}
]
},
前端视图 views/users/user.vue
<template>
<div class="app-container">
<el-row>
<el-col :span="12">
<el-input v-model="params.username" placeholder="搜索用户名" @keyup.enter.native="handleSearch">
<el-button slot="append" icon="el-icon-search" @click="handleSearch"/>
</el-input>
</el-col>
<el-col :span="12" align="right" style="padding-right:20px;">
<el-button type="primary" @click="addUserVisible=true">添加用户</el-button>
</el-col>
</el-row>
<el-table
:data="userList"
stripe
style="width: 100%">
<el-table-column
prop="username"
label="username"/>
<el-table-column
prop="name"
label="姓名"/>
<el-table-column
prop="phone"
label="电话"/>
<el-table-column
prop="email"
label="email"/>
<el-table-column
prop="is_active"
label="状态">
<template slot-scope="scope">
<el-switch v-model="scope.row.is_active" @change="handleUserStatusChange(scope.row)"/>
</template>
</el-table-column>
<el-table-column
prop="last_login"
label="last_login"/>
<el-table-column
fixed="right"
label="操作"
width="100">
<template slot-scope="scope">
<el-button type="text" size="small" @click="handleModify(scope.row)">修改</el-button>
</template>
</el-table-column>
</el-table>
<el-row v-show="total>10" type="flex" justify="center" style="padding-top:20px;">
<el-pagination
:total="total"
layout="prev, pager, next"
background
@current-change="handleChange" />
</el-row>
<AddUserForm v-model="addUserVisible" @fetch="handleFetch" />
<ModifyUserForm v-model="modifyUserVisible" :user-id="userId" @fetch="handleFetch" />
</div>
</template>
<script>
import { getUserList, modifyUser } from '@/api/user'
import AddUserForm from './components/addUserForm.vue'
import ModifyUserForm from './components/modifyUser.vue'
export default {
name: 'UserList',
components: {
AddUserForm,
ModifyUserForm
},
data() {
return {
userList: [],
addUserVisible: false,
modifyUserVisible: false,
userId: 0,
total: 0,
params: {
page: 1,
username: ''
}
}
},
created() {
this.fetchUserList()
},
methods: {
fetchUserList() {
getUserList(this.params).then(res => {
this.userList = res.results
this.total = res.count
})
},
handleUserStatusChange(obj) {
modifyUser(obj.id, { is_active: obj.is_active }).then(() => {
this.$message({
message: `修改 ${obj.name} 的状态成功`,
type: 'success'
})
})
},
handleFetch() {
this.fetchUserList()
},
handleModify(obj) {
this.userId = obj.id
this.modifyUserVisible = true
},
handleChange(val) {
this.params.page = val
this.fetchUserList()
},
handleSearch() {
this.params.page = 1
this.fetchUserList()
}
}
}
</script>
浏览器装vue插件https://www.gugeapps.com/
获取后端数据 api/user.js
import request from '@/utils/request'
export function getUserList(params) {
return request({
url: '/api/users/',
method: 'get',
params
})
}
export function getUser(userId) {
return request({
url: `/api/users/${userId}/`,
method: 'get'
})
}
export function modifyUser(id, data) {
return request({
url: `/api/users/${id}/`,
method: 'patch',
data
})
}
export function addUser(data) {
return request({
url: `/api/userreg/`,
method: 'post',
data
})
}
解决跨域参考github : django cors
解决前端request拦截器utils/request.js
import axios from 'axios'
import { Message } from 'element-ui'
// import store from '../store'
// import { getToken } from '@/utils/auth'
// 创建axios实例
const service = axios.create({
baseURL: process.env.BASE_API, // api的base_url
timeout: 15000 // 请求超时时间
})
// request拦截器
// service.interceptors.request.use(config => {
// if (store.getters.token) {
// config.headers['X-Token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
// }
// return config
// }, error => {
// // Do something with request error
// console.log(error) // for debug
// Promise.reject(error)
// })
// respone拦截器
service.interceptors.response.use(
response => {
/**
* code为非20000是抛错 可结合自己业务进行修改
*/
return response.data
// const res = response.data
// if (res.code !== 20000) {
// Message({
// message: res.message,
// type: 'error',
// duration: 5 * 1000
// })
//
// // 50008:非法的token; 50012:其他客户端登录了; 50014:Token 过期了;
// if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', {
// confirmButtonText: '重新登录',
// cancelButtonText: '取消',
// type: 'warning'
// }).then(() => {
// store.dispatch('FedLogOut').then(() => {
// location.reload()// 为了重新实例化vue-router对象 避免bug
// })
// })
// }
// return Promise.reject('error')
// } else {
// return response.data
// }
},
error => {
console.log('err' + error)// for debug
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
添加用户 users/components/addUserForm.vue
<template>
<div class="add-user-form">
<el-dialog :visible.sync="visible" title="用户信息" @close="handleClose">
<el-form ref="addUserForm" :model="form" :rules="rules" label-width="100px">
<el-form-item label="登陆名:" prop="username">
<el-input v-model="form.username" placeholder="请输入登陆名"/>
</el-form-item>
<el-form-item label="姓名:" prop="name">
<el-input v-model="form.name" placeholder="请输入姓名"/>
</el-form-item>
<el-form-item label="密码:" prop="password">
<el-input v-model="form.password" type="password" placeholder="请输入密码"/>
</el-form-item>
<el-form-item label="手机号:" prop="phone">
<el-input v-model="form.phone" placeholder="请输入手机号"/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm">立即创建</el-button>
<el-button @click="resetForm">重置</el-button>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="handleClose">取 消</el-button>
<el-button type="primary" @click="handleClose">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { addUser } from '@/api/user'
export default {
name: 'AddUserForm',
props: {
value: {
type: Boolean,
default: false
}
},
data() {
return {
visible: false,
form: {
username: '',
name: '',
password: '',
phone: ''
},
rules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' }
],
name: [
{ required: true, message: '请输入用户姓名', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' }
],
phone: [
{ required: true, message: '请输入联系电话', trigger: 'blur' }
]
}
}
},
watch: {
value(val) {
this.visible = val
}
},
methods: {
handleClose() {
this.visible = false
this.$emit('input', false)
this.resetForm()
},
resetForm() {
this.$refs.addUserForm.resetFields()
},
submitForm() {
this.$refs.addUserForm.validate((valid) => {
if (valid) {
addUser(this.form).then(() => {
this.$message({
message: `创建用户 ${this.form.name} 成功`,
type: 'success'
})
this.handleClose()
this.$emit('fetch')
})
} else {
console.log('error submit!!')
return false
}
})
}
}
}
</script>
用户修改功能 users/components/modifyUser.vue
<template>
<div class="modify-user-form">
<el-dialog :visible.sync="visible" :title="title" @close="handleClose">
<el-form ref="modifyUserForm" :model="form" :rules="rules" label-width="100px">
<el-form-item label="手机号:" prop="phone">
<el-input v-model="form.phone" placeholder="请输入手机号"/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm">修改</el-button>
<el-button @click="resetForm">重置</el-button>
</el-form-item>
</el-form>
</el-dialog>
</div>
</template>
<script>
import { getUser, modifyUser } from '@/api/user'
export default {
name: 'ModifyUser',
props: {
value: {
type: Boolean,
default: false
},
userId: {
type: Number,
default: 0
}
},
data() {
return {
visible: false,
userObj: null,
form: {
phone: ''
},
rules: {
phone: [
{ required: true, message: '请输入联系电话', trigger: 'blur' }
]
},
uid: 0,
title: ''
}
},
watch: {
value(val) {
this.visible = val
},
userId(val) {
if (val === 0) return
this.uid = val
this.fetchUser()
}
},
methods: {
handleClose() {
this.visible = false
this.$emit('input', false)
this.userId = 0
this.userObj = null
this.resetForm()
this.title = ''
},
resetForm() {
this.$refs.modifyUserForm.resetFields()
if (this.userId === 0) return
if (this.userObj === null) return
this.form.phone = this.userObj.phone
},
submitForm() {
this.$refs.modifyUserForm.validate((valid) => {
if (valid) {
modifyUser(this.userId, this.form).then(() => {
this.$message({
message: `创建用户 ${this.userObj.name} 手机号成功`,
type: 'success'
})
this.handleClose()
this.$emit('fetch')
})
} else {
console.log('error submit!!')
return false
}
})
},
fetchUser() {
getUser(this.uid).then(res => {
this.title = `修改 ${res.name} 的信息`
this.userObj = res
this.form.phone = res.phone
})
}
}
}
</script>