mirror of
https://github.com/hequan2017/seal.git
synced 2026-11-05 14:47:22 +08:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
338511c8be | ||
|
|
584287b6fe | ||
|
|
13de060c9c | ||
|
|
4670698e5b | ||
|
|
4af4bf292a | ||
|
|
fac6fd52e3 | ||
|
|
2262e677ab | ||
|
|
ca5f853a61 | ||
|
|
c35c535081 | ||
|
|
25207e4ad1 | ||
|
|
277d2d0250 | ||
|
|
a0f2d68864 | ||
|
|
23173f32a8 |
75
README.md
75
README.md
@@ -3,25 +3,37 @@
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
> 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
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
## 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>
|
||||
* 欢迎提出你的需求和意见,或者来加入到本项目中一起开发。
|
||||
|
||||
### 作者
|
||||
* 何全
|
||||
|
||||
|
||||
@@ -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, )
|
||||
|
||||
@@ -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
BIN
document/demo/3.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 97 KiB |
BIN
document/demo/4.jpg
Normal file
BIN
document/demo/4.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
BIN
document/demo/5.jpg
Normal file
BIN
document/demo/5.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
@@ -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
105
seal/schema.py
Normal 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)
|
||||
|
||||
@@ -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',)
|
||||
|
||||
@@ -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)),
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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())
|
||||
]
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
修改密码
|
||||
|
||||
@@ -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">总: {{ 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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
{% block page-content %}
|
||||
<div class="wrapper wrapper-content">
|
||||
|
||||
|
||||
欢迎使用本项目!
|
||||
|
||||
</div>
|
||||
|
||||
@@ -21,4 +21,4 @@
|
||||
|
||||
{% block footer-js %}
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user