13 Commits
v0.2 ... v0.3.0

Author SHA1 Message Date
何全
338511c8be v0.3.0 增加 GraphQL 支持 2019-04-10 16:32:31 +08:00
何全
584287b6fe 0.2.1 修改说明文件 2019-04-08 11:01:25 +08:00
何全
13de060c9c 优化权限 2019-03-29 14:24:59 +08:00
何全
4670698e5b 修复头像不显示
修复头像不显示
2019-03-29 11:43:20 +08:00
何全
4af4bf292a 修改权限 2019-03-29 11:26:28 +08:00
何全
fac6fd52e3 修复 3个小bug 2019-03-28 18:39:36 +08:00
何全
2262e677ab 修改API 分页 2019-03-28 18:18:52 +08:00
何全
ca5f853a61 注销
注销
2019-03-27 17:06:45 +08:00
何全
c35c535081 0.2.2 完善 api 登录 退出
0.2.2 完善 api 登录 退出
2019-03-27 15:28:38 +08:00
何全
25207e4ad1 0.2.1 增加 API 例子 2019-03-26 17:29:40 +08:00
何全
277d2d0250 0.2.1 增加 API 例子 2019-03-26 17:29:31 +08:00
何全
a0f2d68864 0.2.1 增加 API 例子 2019-03-26 17:28:39 +08:00
何全
23173f32a8 0.2.1 增加 API 例子 2019-03-26 17:23:14 +08:00
17 changed files with 261 additions and 37 deletions

View File

@@ -3,25 +3,37 @@
![语言](https://img.shields.io/badge/language-python3.6-blue.svg)
![语言](https://img.shields.io/badge/env-django2.1.7-red.svg)
![bootstrap4](https://img.shields.io/badge/model-bootstrap4-mauve.svg)
![RESETful](https://img.shields.io/badge/env-RESETful-red.svg)
![GraphQL](https://img.shields.io/badge/env-GraphQL-red.svg)
> django-base-templastes
> 因本项目开始时间为3月1日,是 国际海豹日,故项目起名为 海豹 seal
> 主要为 django 基础开发平台, MVC 模式 开发.(非前后端分离) ,可以拿来直接开发 django项目。作者会在周末进行开发、更新。
> 主要为 django 基础开发平台, MVC 模式 开发.支持 非前后端分离 和 前后端分离模式,可以拿来参考 开发 django项目
> 支持 RESETful 和 GraphQL
> vue 前端地址 https://github.com/hequan2017/seal-vue 持续开发中
> 作者会在周末进行开发、更新。
## 介绍
* 基于bootstrap4+django2.1+python3.6+celery4 异步任务
* 前端模板 inspinia 2.9
* 会尽量多加一些注释
* 采用cbv开发方式,提高开发效率
* 增加 drf api 例子
* 采用cbv开发方式
* drf api 例子
* 前端 Vue版本
* GraphQL
## DEMO
![列表](document/demo/1.jpg)
![添加](document/demo/2.jpg)
![API](document/demo/3.jpg)
![API](document/demo/4.jpg)
![API](document/demo/5.jpg)
## templates
@@ -32,6 +44,46 @@
* document 代码规范
## GraphQL
> 具体代码 请参考 seal/schema.py
> 请求地址 : http://localhost/graphql
> GraphQL 请求参数
```
query{
users{
id,
username,
email
}
}
mutation createUser {
createUser (username: "test1") {
info {
id,
},
ok
}
}
mutation updateUser {
updateUser (pk:2,username: "test2") {
info {
id,
},
ok
}
}
mutation deleteUser {
deleteUser (pk:2) {
ok
}
}
```
## 部署
@@ -50,7 +102,7 @@ python manage.py runserver 0.0.0.0:80
```
## 异步任务
* 扩展功能-异步1 推荐 定时任务用celery
```bash
#需要安装redis
@@ -83,15 +135,16 @@ for i in Users.objects.all():
```
### 售后服务
## 售后服务
* 有问题 可以加QQ群: 620176501 <a target="_blank" href="//shang.qq.com/wpa/qunwpa?idkey=bbe5716e8bd2075cb27029bd5dd97e22fc4d83c0f61291f47ed3ed6a4195b024"><img border="0" src="https://github.com/hequan2017/cmdb/blob/master/static/img/group.png" alt="django开发讨论群" title="django开发讨论群"></a>
* 欢迎提出你的需求和意见,或者来加入到本项目中一起开发。
* inspinia 2.9 model 前端基础模板,可以加群,在群共享里面有。
* bootstrap4 中文文档 https://code.z01.com/v4/
* cbv 中文文档 http://ccbv.co.uk/projects/Django/2.1/django.views.generic.edit/
* GraphQL 中文参考文档 https://passwo.gitbook.io/graphql/index/drf
## 作者
### 其他
* 有问题 可以加QQ群: 620176501 <a target="_blank" href="//shang.qq.com/wpa/qunwpa?idkey=bbe5716e8bd2075cb27029bd5dd97e22fc4d83c0f61291f47ed3ed6a4195b024"><img border="0" src="https://github.com/hequan2017/cmdb/blob/master/static/img/group.png" alt="django开发讨论群" title="django开发讨论群"></a>
* 欢迎提出你的需求和意见,或者来加入到本项目中一起开发。
### 作者
* 何全

View File

@@ -8,9 +8,10 @@ class Ecs(models.Model):
('华为云', '华为云'),
('亚马逊', '亚马逊'),
('其他', '其他'),
(None,None),
)
hostname = models.CharField(max_length=96, verbose_name='主机名', blank=True, null=True, )
type = models.CharField(choices=TYPE_CHOICES, max_length=16, verbose_name='主机类型')
type = models.CharField(choices=TYPE_CHOICES, max_length=16, verbose_name='主机类型', blank=True, null=True, )
instance_id = models.CharField(max_length=64, verbose_name='实例ID', unique=True)
instance_name = models.CharField(max_length=96, verbose_name='标签', blank=True, null=True, )
os_name = models.CharField(max_length=64, verbose_name='系统版本', blank=True, null=True, )

View File

@@ -50,7 +50,7 @@ class EcsCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
def get_success_url(self):
return self.request.POST['__next__']
def form_valid(self, form): # 保存结果 可以进行 手动 修改 再保存
def form_valid(self, form): # 保存结果 可以进行 手动 修改 再保存
obj = form.save(commit=False)
obj.save()
return super().form_valid(form)
@@ -78,7 +78,6 @@ class EcsListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
context = {
"ecs_list": ecs_list,
'ecs_count': self.queryset.count() if self.queryset != '' else 0,
"filter_dict": self.filter_dict # 把查询条件返回给前端
}
@@ -147,7 +146,7 @@ class ApiEcsList(generics.ListCreateAPIView):
queryset = Ecs.objects.get_queryset().order_by('id')
serializer_class = EcsSerializer
filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter)
filter_fields = ('id', 'hostname', 'type', 'instance_id')
filter_fields = ('id', 'hostname','type','instance_id')
search_fields = ('id', 'hostname',)
permission_classes = (permissions.DjangoModelPermissions,) # 继承 django的权限
@@ -156,3 +155,4 @@ class ApiEcsDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Ecs.objects.get_queryset().order_by('id')
serializer_class = EcsSerializer
permission_classes = (permissions.DjangoModelPermissions,)

BIN
document/demo/3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

BIN
document/demo/4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
document/demo/5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -1,4 +1,5 @@
amqp==2.4.2
aniso8601==3.0.2
argh==0.26.2
backcall==0.1.0
billiard==3.5.0.5
@@ -8,7 +9,7 @@ chardet==3.0.4
coreapi==2.3.3
coreschema==0.0.4
decorator==4.3.0
Django==2.1.7
Django==2.2rc1
django-bootstrap4==0.0.7
django-celery-beat==1.4.0
django-celery-results==1.0.4
@@ -20,6 +21,10 @@ django-timezone-field==3.0
djangorestframework==3.9.2
dramatiq==1.5.0
gevent==1.4.0
graphene==2.1.3
graphene-django==2.2.0
graphql-core==2.1
graphql-relay==0.4.5
greenlet==0.4.15
idna==2.8
ipython==6.4.0
@@ -37,6 +42,7 @@ pathtools==0.1.2
pexpect==4.6.0
pickleshare==0.7.4
prometheus-client==0.2.0
promise==2.2.1
prompt-toolkit==1.0.15
ptyprocess==0.5.2
Pygments==2.2.0
@@ -46,13 +52,16 @@ pytz==2018.4
PyYAML==5.1
redis==3.2.0
requests==2.21.0
Rx==1.6.1
simplegeneric==0.8.1
simplejson==3.16.0
singledispatch==3.4.0.3
six==1.11.0
sqlparse==0.3.0
traitlets==4.3.2
uritemplate==3.0.0
urllib3==1.24.1
vine==1.2.0
watchdog==0.8.3
watchdog-gevent==0.1.0
wcwidth==0.1.7
wcwidth==0.1.7

105
seal/schema.py Normal file
View File

@@ -0,0 +1,105 @@
from system.models import Users
from graphene_django import DjangoObjectType
import graphene
# 相关文档 https://passwo.gitbook.io/graphql/index/drf
class UserType(DjangoObjectType):
class Meta:
model = Users
class Query(graphene.ObjectType):
users = graphene.List(UserType)
# List == Field:
# List 返回结果会是遍历所有查询结果
# Field 返回结果只存在单个 (其中可添加参数, ex. pk)
single_user = graphene.Field(UserType, pk=graphene.Int())
# 定义函数名的格式: resolve_字段
# **kwargs 传递参数
# pk: 如果在字段中定义, 则方法参数中必含
def resolve_users(self, info, **kwargs):
return Users.objects.all()
def resolve_single_user(self, info, pk):
return Users.objects.get(id=pk)
class TQuery(Query, graphene.ObjectType):
pass
class CreateUser(graphene.Mutation):
class Arguments:
username = graphene.String(required=True)
info = graphene.Field(UserType)
ok = graphene.Boolean()
def mutate(self, info, **kwargs):
# print(info.context.user, '==当前用户==')
# kwargs 是传递参数中的变量
# user = info.context.user
user_obj = Users(**kwargs)
try:
user_obj.save()
ok = True
except Exception as e:
print(e)
ok = False
return CreateUser(ok=ok, info=user_obj)
class CMutation(object):
create_user = CreateUser.Field()
class UpdateUser(graphene.Mutation):
class Arguments:
username = graphene.String()
pk = graphene.Int(required=True)
info = graphene.Field(UserType)
ok = graphene.Boolean()
def mutate(self, info, **kwargs):
pk = kwargs.get('pk')
user_obj = Users.objects.get(id=pk)
if not user_obj:
return UpdateUser(ok=False)
user_obj.__dict__.update(**kwargs)
user_obj.save()
ok = True
return UpdateUser(ok=ok, info=user_obj)
class UMutation(object):
update_user = UpdateUser.Field()
class DeleteUser(graphene.Mutation):
class Arguments:
pk = graphene.Int()
ok = graphene.Boolean()
def mutate(self, info, **kwargs):
pk = kwargs.get('pk')
user = Users.objects.get(id=pk)
user.delete()
return DeleteUser(ok=True)
class DMutation(object):
delete_user = DeleteUser.Field()
class Mutations(CMutation, UMutation,DMutation,graphene.ObjectType):
pass
schema = graphene.Schema(query=TQuery, mutation=Mutations)

View File

@@ -46,11 +46,17 @@ INSTALLED_APPS = [
'rest_framework.authtoken',
'corsheaders',
'django_filters',
'graphene_django',
]
GRAPHENE = {
'SCHEMA': 'seal.schema.schema'
}
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
@@ -230,8 +236,8 @@ REST_FRAMEWORK = {
# 'rest_framework.permissions.AllowAny',
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 15,
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
}
@@ -240,4 +246,4 @@ CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_WHITELIST = (
'*',
)
MIDDLEWARE_CLASSES = ('system.views.DisableCSRFCheck',)
MIDDLEWARE_CLASSES = ('system.views.DisableCSRFCheck',)

View File

@@ -4,6 +4,8 @@ from django.conf.urls import include
from system.views import index
from rest_framework.authtoken import views
from rest_framework.documentation import include_docs_urls
from graphene_django.views import GraphQLView
from seal.schema import schema
API_TITLE = '海豹 API 文档'
API_DESCRIPTION = '海豹 API 文档'
@@ -16,5 +18,6 @@ urlpatterns = [
path('admin/', admin.site.urls, ),
path('api/token', views.obtain_auth_token),
path('api/docs/', include_docs_urls(title=API_TITLE, description=API_DESCRIPTION, authentication_classes=[],
permission_classes=[]))
permission_classes=[])),
path('graphql/', GraphQLView.as_view(graphiql=True, schema=schema)),
]

View File

@@ -1,5 +1,5 @@
@import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700");
@import url("https://fonts.googleapis.com/css?family=Roboto:400,300,500,700");
@import url("https://fonts.lug.ustc.edu.cn/css?family=Open+Sans:300,400,600,700");
@import url("https://fonts.lug.ustc.edu.cn/css?family=Roboto:400,300,500,700");
/*
*
* INSPINIA - Responsive Admin Theme

View File

@@ -5,7 +5,7 @@
<link rel=stylesheet href="../../doc/docs.css">
<link rel="stylesheet" href="../../lib/codemirror.css">
<link href="http://fonts.googleapis.com/css?family=Inconsolata" rel="stylesheet" type="text/css">
<link href="http://fonts.lug.ustc.edu.cn/css?family=Inconsolata" rel="stylesheet" type="text/css">
<script src="../../lib/codemirror.js"></script>
<script src="vb.js"></script>
<script type="text/javascript" src="../../addon/runmode/runmode.js"></script>
@@ -44,7 +44,7 @@ function test(golden, text) {
}
i++;
}
CodeMirror.runMode(text, "text/x-vb",callback);
CodeMirror.runMode(text, "text/x-vb",callback);
if (ok) return "Tests OK";
}
@@ -88,7 +88,7 @@ function init() {
function runTest() {
document.getElementById('testresult').innerHTML = testAll();
initText(editor);
}
document.body.onload = init;
</script>

View File

@@ -1,5 +1,5 @@
from django.urls import path
from system.views import login_view, UserPasswordUpdateView, logout_view
from system.views import login_view, UserPasswordUpdateView, logout_view, UserInfo, UserLogout
app_name = "system"
@@ -7,4 +7,8 @@ urlpatterns = [
path('login', login_view, name="login"),
path('password_update', UserPasswordUpdateView.as_view(), name="password_update"),
path('logout', logout_view, name="logout"),
path('api/user_info', UserInfo.as_view()),
path('api/logout', UserLogout.as_view())
]

View File

@@ -1,5 +1,6 @@
import logging
from django.shortcuts import render
import json
from django.shortcuts import render, HttpResponse
from django.contrib.auth import authenticate, login
from django.shortcuts import redirect, render
from system.models import Users
@@ -11,12 +12,49 @@ from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMix
from django.contrib.auth.decorators import login_required
from django.views.generic import ListView, View, DetailView, CreateView, UpdateView
from django.contrib.auth import logout
from rest_framework.authentication import TokenAuthentication
from rest_framework.authtoken.models import Token
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import permissions
from rest_framework import generics
logger = logging.getLogger('system')
class CustomBackend(ModelBackend):
class UserInfo(APIView):
"""
获取用户信息
"""
permission_classes = (permissions.IsAuthenticated,)
def post(self, request):
token = (json.loads(request.body))['token']
obj = Token.objects.get(key=token).user
result = {
'name': obj.username,
'user_id': obj.id,
'access': list(obj.get_all_permissions())+['admin'] if obj.is_superuser else list(obj.get_all_permissions()),
'token': token,
'avatar': 'https://file.iviewui.com/dist/a0e88e83800f138b94d2414621bd9704.png'
}
return HttpResponse(json.dumps(result))
class UserLogout(APIView):
permission_classes = (permissions.IsAuthenticated,)
def post(self, request):
token = (json.loads(request.body))['token']
obj = Token.objects.get(key=token)
obj.delete()
result = {
"status": True
}
return HttpResponse(json.dumps(result))
class CustomBackend(ModelBackend):
"""
用户名字/邮箱名字 登录
:param request:
@@ -61,7 +99,6 @@ def login_view(request):
return render(request, 'system/login.html', {'error_msg': error_msg, })
@login_required(login_url="/system/login")
def index(request):
"""
@@ -72,8 +109,6 @@ def index(request):
return render(request, 'system/index.html')
class UserPasswordUpdateView(LoginRequiredMixin, UpdateView):
"""
修改密码

View File

@@ -178,7 +178,7 @@
{% for page in ecs_list.pages %}
{% if page %}
{% ifequal page ecs_list.number %}
<li class="active"><a href="?{{ page.querystring }}">{{ page }}</a>
<li class="active" ><a style="background-color: #E0E0E0" href="?{{ page.querystring }}">{{ page }}</a>
</li>
{% else %}
<li><a href="?{{ page.querystring }}" class="page">{{ page }}</a>
@@ -200,7 +200,7 @@
style="color: #0a0a0a">总: &nbsp;{{ ecs_list.paginator.num_pages }} 页</span>
</li>
<li><span
style="color: #0a0a0a"> 数量: {{ ecs_count }}</span>
style="color: #0a0a0a"> 数量: {{ ecs_list.paginator.count }}</span>
</li>
</nav>
</div>
@@ -277,4 +277,4 @@
</script>
{% endblock %}
{% endblock %}
{% endblock %}

View File

@@ -20,4 +20,12 @@
<link href="{% static "css/plugins/toastr/toastr.min.css" %}" rel="stylesheet">
<link href="{% static "css/plugins/steps/jquery.steps.css" %}" rel="stylesheet">
<style>
.select2-container--default .select2-selection--single .select2-selection__rendered {
line-height: 32px;
}
.select2-container .select2-selection--single {
height: 32px;
}
</style>

View File

@@ -11,7 +11,7 @@
{% block page-content %}
<div class="wrapper wrapper-content">
欢迎使用本项目!
</div>
@@ -21,4 +21,4 @@
{% block footer-js %}
{% endblock %}
{% endblock %}